mirror of
https://github.com/jwetzell/showbridge-webui.git
synced 2026-09-06 13:59:00 +00:00
formatting
This commit is contained in:
+3
-6
@@ -1,15 +1,12 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
test.beforeEach(async ({page})=>{
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/')
|
await page.goto('/');
|
||||||
await expect(page.locator('div').nth(2)).toContainClass('bg-green-400');
|
await expect(page.locator('div').nth(2)).toContainClass('bg-green-400');
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
test('has title', async ({ page }) => {
|
test('has title', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
// Expect a title "to contain" a substring.
|
// Expect a title "to contain" a substring.
|
||||||
await expect(page).toHaveTitle(/Showbridge/);
|
await expect(page).toHaveTitle(/Showbridge/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
test('add module', async ({ page }) => {
|
test('add module', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.locator('app-module-list').getByText('add').click();
|
await page.locator('app-module-list').getByText('add').click();
|
||||||
await page.getByRole('menuitem', { name: 'HTTP Server' }).click();
|
await page.getByRole('menuitem', { name: 'HTTP Server' }).click();
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ export default defineConfig({
|
|||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
webServer: [
|
webServer: [
|
||||||
{
|
{
|
||||||
name: "showbridge",
|
name: 'showbridge',
|
||||||
command: "showbridge --config config.yaml",
|
command: 'showbridge --config config.yaml',
|
||||||
url: "http://localhost:8080/health",
|
url: 'http://localhost:8080/health',
|
||||||
timeout: 10000
|
timeout: 10000,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [provideBrowserGlobalErrorListeners()],
|
||||||
provideBrowserGlobalErrorListeners(),
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|||||||
+25
-13
@@ -1,7 +1,7 @@
|
|||||||
<div class="h-screen max-h-screen flex flex-col">
|
<div class="h-screen max-h-screen flex flex-col">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<mat-toolbar color="primary">
|
<mat-toolbar color="primary">
|
||||||
@if(eventsService.status() === 'open') {
|
@if (eventsService.status() === 'open') {
|
||||||
<div matTooltip="Connected" class="h-3 w-3 rounded-full bg-green-400 mr-2"></div>
|
<div matTooltip="Connected" class="h-3 w-3 rounded-full bg-green-400 mr-2"></div>
|
||||||
} @else if (eventsService.status() === 'closed') {
|
} @else if (eventsService.status() === 'closed') {
|
||||||
<div matTooltip="Disconnected" class="h-3 w-3 rounded-full bg-orange-400 mr-2"></div>
|
<div matTooltip="Disconnected" class="h-3 w-3 rounded-full bg-orange-400 mr-2"></div>
|
||||||
@@ -10,18 +10,23 @@
|
|||||||
}
|
}
|
||||||
<span>showbridge</span>
|
<span>showbridge</span>
|
||||||
<div>
|
<div>
|
||||||
<label class="ml-4 mr-2">URL:</label>
|
<label class="ml-4 mr-2">URL:</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
|
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
|
||||||
[placeholder]="'http://localhost:8000'"
|
[placeholder]="'http://localhost:8000'"
|
||||||
[value]="settingsService.baseUrl()"
|
[value]="settingsService.baseUrl()"
|
||||||
(change)="settingsService.updateBaseUrl($event.target.value)"
|
(change)="settingsService.updateBaseUrl($event.target.value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="flex-auto"></span>
|
<span class="flex-auto"></span>
|
||||||
@if (configService.pendingConfigIsValid()) {
|
@if (configService.pendingConfigIsValid()) {
|
||||||
<button mat-icon-button matTooltip="Apply Config" (click)="applyConfig()" [disabled]="!configService.configIsDirty()">
|
<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 {
|
||||||
@@ -34,11 +39,18 @@
|
|||||||
}
|
}
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
</div>
|
</div>
|
||||||
@if (eventsService.status() === 'open' && schemaService.schemasLoaded()) {
|
@if (eventsService.status() === 'open' && schemaService.schemasLoaded()) {
|
||||||
<div class="grow overflow-hidden flex m-2">
|
<div class="grow overflow-hidden flex m-2">
|
||||||
<div class="grow overflow-y-auto">
|
<div class="grow overflow-y-auto">
|
||||||
<app-module-list [modules]="config()?.modules" (modulesChange)="modulesUpdated($event)"></app-module-list>
|
<app-module-list
|
||||||
<app-route-list [routes]="config()?.routes" [moduleIds]="moduleIds()" (routesChange)="routesUpdated($event)"></app-route-list>
|
[modules]="config()?.modules"
|
||||||
|
(modulesChange)="modulesUpdated($event)"
|
||||||
|
></app-module-list>
|
||||||
|
<app-route-list
|
||||||
|
[routes]="config()?.routes"
|
||||||
|
[moduleIds]="moduleIds()"
|
||||||
|
(routesChange)="routesUpdated($event)"
|
||||||
|
></app-route-list>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 overflow-y-hidden">
|
<div class="w-1/2 overflow-y-hidden">
|
||||||
<app-config-preview [config]="config()"></app-config-preview>
|
<app-config-preview [config]="config()"></app-config-preview>
|
||||||
|
|||||||
+3
-4
@@ -28,13 +28,12 @@ import { ConfigPreviewComponent } from './components/config-preview/config-previ
|
|||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
ModuleListComponent,
|
ModuleListComponent,
|
||||||
RouteListComponent,
|
RouteListComponent,
|
||||||
ConfigPreviewComponent
|
ConfigPreviewComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.css',
|
styleUrl: './app.css',
|
||||||
})
|
})
|
||||||
export class App {
|
export class App {
|
||||||
|
|
||||||
config = computed<Config | undefined>(() => this.configService.currentlyShownConfig());
|
config = computed<Config | undefined>(() => this.configService.currentlyShownConfig());
|
||||||
|
|
||||||
modules = computed(() => this.config()?.modules ?? []);
|
modules = computed(() => this.config()?.modules ?? []);
|
||||||
@@ -47,7 +46,7 @@ export class App {
|
|||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
public configService = inject(ConfigService);
|
public configService = inject(ConfigService);
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ export class ArrayFormComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.paramFormControl && this.paramInfo?.schema) {
|
if (this.paramFormControl && this.paramInfo?.schema) {
|
||||||
if (!Array.isArray(this.paramFormControl.value)) {
|
if (!Array.isArray(this.paramFormControl.value)) {
|
||||||
this.arrayValue = parseStringToArray(
|
this.arrayValue = parseStringToArray(this.paramFormControl.value, this.paramInfo.schema);
|
||||||
this.paramFormControl.value,
|
|
||||||
this.paramInfo.schema,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.arrayValue = this.paramFormControl.value;
|
this.arrayValue = this.paramFormControl.value;
|
||||||
}
|
}
|
||||||
@@ -101,7 +98,7 @@ export class ArrayFormComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(jwetzel): this is only needed for object item types
|
// NOTE(jwetzel): this is only needed for object item types
|
||||||
updateItem(index: number, value: any) {
|
updateItem(index: number, value: any) {
|
||||||
if (this.arrayValue) {
|
if (this.arrayValue) {
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ import { JsonPipe } from '@angular/common';
|
|||||||
import { Component, computed, input } from '@angular/core';
|
import { Component, computed, input } from '@angular/core';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import { Config } from '../../models/config';
|
import { Config } from '../../models/config';
|
||||||
import { MatIcon, MatIconModule } from "@angular/material/icon";
|
import { MatIcon, MatIconModule } from '@angular/material/icon';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-config-preview',
|
selector: 'app-config-preview',
|
||||||
imports: [
|
imports: [MatIconModule, MatButtonModule, MatTooltipModule],
|
||||||
MatIconModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatTooltipModule
|
|
||||||
],
|
|
||||||
templateUrl: './config-preview.html',
|
templateUrl: './config-preview.html',
|
||||||
styleUrl: './config-preview.css',
|
styleUrl: './config-preview.css',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,13 +10,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-module-list',
|
selector: 'app-module-list',
|
||||||
imports: [
|
imports: [MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule, ModuleComponent],
|
||||||
MatMenuModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
ModuleComponent
|
|
||||||
],
|
|
||||||
templateUrl: './module-list.html',
|
templateUrl: './module-list.html',
|
||||||
styleUrl: './module-list.css',
|
styleUrl: './module-list.css',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,49 +1,56 @@
|
|||||||
@if (module() && schema()) {
|
@if (module() && schema()) {
|
||||||
<div
|
<div
|
||||||
class="flex flex-col bg-gray-800 border-2 {{
|
class="flex flex-col bg-gray-800 border-2 {{
|
||||||
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">
|
<div class="flex items-center justify-center">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator">keyboard_arrow_down</mat-icon>
|
<mat-icon [style.color]="inputIndicatorColor()" matTooltip="Input Indicator"
|
||||||
<mat-icon [style.color]="outputIndicatorColor()" matTooltip="Output Indicator">keyboard_arrow_up</mat-icon>
|
>keyboard_arrow_down</mat-icon
|
||||||
</div>
|
>
|
||||||
<div class="text-white">
|
<mat-icon [style.color]="outputIndicatorColor()" matTooltip="Output Indicator"
|
||||||
{{ schema().title || module()?.type }}
|
>keyboard_arrow_up</mat-icon
|
||||||
</div>
|
>
|
||||||
<div class="grow"></div>
|
|
||||||
<div class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer" (click)="delete.emit()">
|
|
||||||
<mat-icon class="text-red-500!">close</mat-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<hr class="bg-gray-600 h-0.5 border-0">
|
<div class="text-white">
|
||||||
<div>
|
{{ schema().title || module()?.type }}
|
||||||
<form [formGroup]="formGroup" class="h-full">
|
</div>
|
||||||
<div class="m-2">
|
<div class="grow"></div>
|
||||||
<div class="flex items-center m-1">
|
<div
|
||||||
<label class="mr-2 text-white"> ID: </label>
|
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
|
||||||
<input
|
(click)="delete.emit()"
|
||||||
type="text"
|
>
|
||||||
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
|
<mat-icon class="text-red-500!">close</mat-icon>
|
||||||
[placeholder]="'module-1'"
|
</div>
|
||||||
[formControlName]="'id'"
|
</div>
|
||||||
/>
|
<hr class="bg-gray-600 h-0.5 border-0" />
|
||||||
<mat-icon class="text-white!" matTooltip="String">abc</mat-icon>
|
<div>
|
||||||
@if (!formGroup.controls['id'].valid) {
|
<form [formGroup]="formGroup" class="h-full">
|
||||||
<!-- TODO(jwetzell): better error displaying -->
|
<div class="m-2">
|
||||||
<div class="ml-2 text-red-500">{{ formGroup.controls['id'].errors | json }}</div>
|
<div class="flex items-center m-1">
|
||||||
}
|
<label class="mr-2 text-white"> ID: </label>
|
||||||
</div>
|
<input
|
||||||
<app-params-form
|
type="text"
|
||||||
[paramsSchema]="schema()?.properties?.params"
|
class="pl-1 border-2 border-gray-200 text-white border-solid rounded-sm"
|
||||||
[data]="params()"
|
[placeholder]="'module-1'"
|
||||||
(updated)="paramsUpdated($event)"
|
[formControlName]="'id'"
|
||||||
></app-params-form>
|
/>
|
||||||
|
<mat-icon class="text-white!" matTooltip="String">abc</mat-icon>
|
||||||
|
@if (!formGroup.controls['id'].valid) {
|
||||||
|
<!-- TODO(jwetzell): better error displaying -->
|
||||||
|
<div class="ml-2 text-red-500">{{ formGroup.controls['id'].errors | json }}</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<app-params-form
|
||||||
</div>
|
[paramsSchema]="schema()?.properties?.params"
|
||||||
<div>
|
[data]="params()"
|
||||||
|
(updated)="paramsUpdated($event)"
|
||||||
|
></app-params-form>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
@if (moduleConfigErrors().length > 0) {
|
@if (moduleConfigErrors().length > 0) {
|
||||||
<div class="m-2 p-2 border-2 border-red-500">
|
<div class="m-2 p-2 border-2 border-red-500">
|
||||||
<div class="flex items-center mb-2 text-red-500">
|
<div class="flex items-center mb-2 text-red-500">
|
||||||
@@ -58,5 +65,5 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +1,95 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { ParamsFormComponent } from './params-form';
|
import { ParamsFormComponent } from './params-form';
|
||||||
import { SchemaService } from '../../services/schema';
|
import { SchemaService } from '../../services/schema';
|
||||||
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
||||||
|
|
||||||
describe('ParamsForm', ()=>{
|
describe('ParamsForm', () => {
|
||||||
let component: ParamsFormComponent;
|
let component: ParamsFormComponent;
|
||||||
let fixture: ComponentFixture<ParamsFormComponent>;
|
let fixture: ComponentFixture<ParamsFormComponent>;
|
||||||
|
|
||||||
beforeEach(async ()=>{
|
beforeEach(async () => {
|
||||||
fixture = TestBed.createComponent(ParamsFormComponent);
|
fixture = TestBed.createComponent(ParamsFormComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should create', ()=>{
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should create form for schema', async ()=>{
|
it('should create form for schema', async () => {
|
||||||
const paramsSchema: SomeJSONSchema = {
|
const paramsSchema: SomeJSONSchema = {
|
||||||
type: "object",
|
type: 'object',
|
||||||
properties: {},
|
properties: {},
|
||||||
required: []
|
required: [],
|
||||||
}
|
};
|
||||||
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
fixture.componentRef.setInput('paramsSchema', paramsSchema);
|
||||||
await fixture.whenStable()
|
await fixture.whenStable();
|
||||||
expect(fixture.nativeElement.querySelector('form')).toBeTruthy()
|
expect(fixture.nativeElement.querySelector('form')).toBeTruthy();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('form should have input for string property', async ()=>{
|
it('form should have input for string property', async () => {
|
||||||
const paramsSchema: SomeJSONSchema = {
|
const paramsSchema: SomeJSONSchema = {
|
||||||
type: "object",
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
"test": {
|
test: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
required: []
|
required: [],
|
||||||
}
|
};
|
||||||
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
fixture.componentRef.setInput('paramsSchema', paramsSchema);
|
||||||
await fixture.whenStable()
|
await fixture.whenStable();
|
||||||
const formEl = fixture.nativeElement.querySelector('form')
|
const formEl = fixture.nativeElement.querySelector('form');
|
||||||
expect(formEl).toBeDefined()
|
expect(formEl).toBeDefined();
|
||||||
const inputEl = formEl.querySelector('input')
|
const inputEl = formEl.querySelector('input');
|
||||||
expect(inputEl).toBeDefined()
|
expect(inputEl).toBeDefined();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('form should have select for string enum property', async ()=>{
|
it('form should have select for string enum property', async () => {
|
||||||
const paramsSchema: SomeJSONSchema = {
|
const paramsSchema: SomeJSONSchema = {
|
||||||
type: "object",
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
"test": {
|
test: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
enum: ['one','two','three']
|
enum: ['one', 'two', 'three'],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
required: []
|
required: [],
|
||||||
}
|
};
|
||||||
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
fixture.componentRef.setInput('paramsSchema', paramsSchema);
|
||||||
await fixture.whenStable()
|
await fixture.whenStable();
|
||||||
const formEl = fixture.nativeElement.querySelector('form')
|
const formEl = fixture.nativeElement.querySelector('form');
|
||||||
expect(formEl).toBeDefined()
|
expect(formEl).toBeDefined();
|
||||||
const selectEl = formEl.querySelector('select')
|
const selectEl = formEl.querySelector('select');
|
||||||
expect(selectEl).toBeDefined()
|
expect(selectEl).toBeDefined();
|
||||||
})
|
});
|
||||||
|
|
||||||
it('form input should reflect data', async ()=>{
|
it('form input should reflect data', async () => {
|
||||||
const paramsSchema: SomeJSONSchema = {
|
const paramsSchema: SomeJSONSchema = {
|
||||||
type: "object",
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
"test": {
|
test: {
|
||||||
type: "string",
|
type: 'string',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
required: []
|
required: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
fixture.componentRef.setInput('paramsSchema', paramsSchema);
|
||||||
await fixture.whenStable()
|
await fixture.whenStable();
|
||||||
fixture.componentRef.setInput('data', {
|
fixture.componentRef.setInput('data', {
|
||||||
test: 'hello'
|
test: 'hello',
|
||||||
})
|
});
|
||||||
await fixture.whenStable()
|
await fixture.whenStable();
|
||||||
const inputEl = fixture.nativeElement.querySelector('input')
|
const inputEl = fixture.nativeElement.querySelector('input');
|
||||||
expect(inputEl).toBeDefined()
|
expect(inputEl).toBeDefined();
|
||||||
expect(inputEl.value).toBe('hello')
|
expect(inputEl.value).toBe('hello');
|
||||||
|
|
||||||
fixture.componentRef.setInput('data', {
|
fixture.componentRef.setInput('data', {
|
||||||
test: 'changed'
|
test: 'changed',
|
||||||
})
|
});
|
||||||
await fixture.whenStable()
|
await fixture.whenStable();
|
||||||
expect(inputEl.value).toBe('changed')
|
expect(inputEl.value).toBe('changed');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { JsonPipe } from '@angular/common';
|
import { JsonPipe } from '@angular/common';
|
||||||
import {
|
import { Component, effect, input, OnDestroy, output } from '@angular/core';
|
||||||
Component,
|
|
||||||
effect,
|
|
||||||
input,
|
|
||||||
OnDestroy,
|
|
||||||
output
|
|
||||||
} from '@angular/core';
|
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } 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';
|
||||||
|
|||||||
@@ -9,12 +9,15 @@
|
|||||||
<div class="grow ml-1 mr-3 text-white">
|
<div class="grow ml-1 mr-3 text-white">
|
||||||
{{ schema()?.title || processor()?.type }}
|
{{ schema()?.title || processor()?.type }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer" (click)="delete.emit()">
|
<div
|
||||||
|
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
|
||||||
|
(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">
|
<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"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class ProcessorComponent {
|
|||||||
paramsUpdated(params: any) {
|
paramsUpdated(params: any) {
|
||||||
this.processor.update((processor) => {
|
this.processor.update((processor) => {
|
||||||
if (processor !== undefined) {
|
if (processor !== undefined) {
|
||||||
if (params !== undefined){
|
if (params !== undefined) {
|
||||||
return {
|
return {
|
||||||
...processor,
|
...processor,
|
||||||
params: params,
|
params: params,
|
||||||
@@ -45,7 +45,7 @@ export class ProcessorComponent {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...processor,
|
...processor,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<div class="grow overflow-x-hidden overflow-y-auto gap-1.5">
|
<div class="grow overflow-x-hidden overflow-y-auto gap-1.5">
|
||||||
@for (route of routes(); track $index; let i = $index) {
|
@for (route of routes(); track $index; let i = $index) {
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
<app-route
|
<app-route
|
||||||
[path]="`routes/${i}`"
|
[path]="`routes/${i}`"
|
||||||
(routeChange)="routeUpdated(i, $event)"
|
(routeChange)="routeUpdated(i, $event)"
|
||||||
[route]="route"
|
[route]="route"
|
||||||
|
|||||||
@@ -10,24 +10,17 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-route-list',
|
selector: 'app-route-list',
|
||||||
imports: [
|
imports: [RouteComponent, MatMenuModule, MatIconModule, MatButtonModule, MatTooltipModule],
|
||||||
RouteComponent,
|
|
||||||
MatMenuModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
],
|
|
||||||
templateUrl: './route-list.html',
|
templateUrl: './route-list.html',
|
||||||
styleUrl: './route-list.css',
|
styleUrl: './route-list.css',
|
||||||
})
|
})
|
||||||
export class RouteListComponent {
|
export class RouteListComponent {
|
||||||
|
|
||||||
routes = model<RouteConfig[]>();
|
routes = model<RouteConfig[]>();
|
||||||
moduleIds = input<string[]>();
|
moduleIds = input<string[]>();
|
||||||
public schemaService = inject(SchemaService);
|
public schemaService = inject(SchemaService);
|
||||||
|
|
||||||
private snackBar = inject(MatSnackBar);
|
private snackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
deleteRoute(index: number) {
|
deleteRoute(index: number) {
|
||||||
this.routes.update((routes) => {
|
this.routes.update((routes) => {
|
||||||
if (routes) {
|
if (routes) {
|
||||||
|
|||||||
@@ -7,14 +7,19 @@
|
|||||||
>
|
>
|
||||||
<div class="flex items-center justify-end">
|
<div class="flex items-center justify-end">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator">chevron_right</mat-icon>
|
<mat-icon [style.color]="indicatorColor()" matTooltip="Route Indicator"
|
||||||
|
>chevron_right</mat-icon
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="grow text-left ml-2 text-white">Route {{ index() }}</div>
|
<div class="grow text-left ml-2 text-white">Route {{ index() }}</div>
|
||||||
<div class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer" (click)="delete.emit()">
|
<div
|
||||||
|
class="flex items-center justify-center hover:bg-gray-700 hover:cursor-pointer"
|
||||||
|
(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">
|
<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">
|
||||||
@@ -58,7 +63,7 @@
|
|||||||
<div class="text-md text-white text-center">Processors</div>
|
<div class="text-md text-white text-center">Processors</div>
|
||||||
</div>
|
</div>
|
||||||
@if (processors() !== undefined && processors()!.length > 0) {
|
@if (processors() !== undefined && processors()!.length > 0) {
|
||||||
<div class="border-2 border-gray-500 m-2">
|
<div class="border-2 border-gray-500 m-2">
|
||||||
@for (processor of processors(); track $index; let i = $index) {
|
@for (processor of processors(); track $index; let i = $index) {
|
||||||
<div cdkDrag>
|
<div cdkDrag>
|
||||||
<app-processor
|
<app-processor
|
||||||
@@ -70,7 +75,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -91,14 +91,17 @@ export class RouteComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.index() !== undefined) {
|
if (this.index() !== undefined) {
|
||||||
this.eventsService.getRouteEventsForIndex(this.index()!).pipe(
|
this.eventsService
|
||||||
tap((routeEvent)=>{
|
.getRouteEventsForIndex(this.index()!)
|
||||||
this.indicatorColor.set(routeEvent.error ? 'red' : 'greenyellow');
|
.pipe(
|
||||||
}),
|
tap((routeEvent) => {
|
||||||
debounceTime(100)
|
this.indicatorColor.set(routeEvent.error ? 'red' : 'greenyellow');
|
||||||
).subscribe((routeEvent) => {
|
}),
|
||||||
this.indicatorColor.set('gray')
|
debounceTime(100),
|
||||||
})
|
)
|
||||||
|
.subscribe((routeEvent) => {
|
||||||
|
this.indicatorColor.set('gray');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ export class RouteComponent {
|
|||||||
this.route.update((route) => {
|
this.route.update((route) => {
|
||||||
if (route) {
|
if (route) {
|
||||||
const processors = route.processors || [];
|
const processors = route.processors || [];
|
||||||
|
|
||||||
processors.push(processorTemplate);
|
processors.push(processorTemplate);
|
||||||
return {
|
return {
|
||||||
...route,
|
...route,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export type Config = {
|
export type Config = {
|
||||||
api: ApiConfig
|
api: ApiConfig;
|
||||||
modules: ModuleConfig[];
|
modules: ModuleConfig[];
|
||||||
routes: RouteConfig[];
|
routes: RouteConfig[];
|
||||||
};
|
};
|
||||||
@@ -31,20 +31,19 @@ export type ProcessorConfig = {
|
|||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type ModuleError = {
|
export type ModuleError = {
|
||||||
index: number;
|
index: number;
|
||||||
config: ModuleConfig;
|
config: ModuleConfig;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type RouteError = {
|
export type RouteError = {
|
||||||
index: number;
|
index: number;
|
||||||
config: RouteConfig;
|
config: RouteConfig;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ConfigError = {
|
export type ConfigError = {
|
||||||
moduleErrors?: ModuleError[];
|
moduleErrors?: ModuleError[];
|
||||||
routeErrors?: RouteError[];
|
routeErrors?: RouteError[];
|
||||||
}
|
};
|
||||||
|
|||||||
+11
-11
@@ -1,19 +1,19 @@
|
|||||||
import { Config } from "./config"
|
import { Config } from './config';
|
||||||
|
|
||||||
export type RouterEvent<T, D> = {
|
export type RouterEvent<T, D> = {
|
||||||
type: T,
|
type: T;
|
||||||
data?: D,
|
data?: D;
|
||||||
error?: string,
|
error?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type RouteEventData = {
|
export type RouteEventData = {
|
||||||
index: number
|
index: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type InputEventData = {
|
export type InputEventData = {
|
||||||
source: string
|
source: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type OutputEventData = {
|
export type OutputEventData = {
|
||||||
destination: string
|
destination: string;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -34,20 +34,20 @@ export class ConfigService {
|
|||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
private eventsService = inject(EventsService);
|
private eventsService = inject(EventsService);
|
||||||
|
|
||||||
constructor(private schemaService: SchemaService) {
|
constructor(private schemaService: SchemaService) {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
console.log('config state changed', this.currentlyShownConfig());
|
console.log('config state changed', this.currentlyShownConfig());
|
||||||
});
|
});
|
||||||
|
|
||||||
effect(() =>{
|
effect(() => {
|
||||||
switch (this.eventsService.status()) {
|
switch (this.eventsService.status()) {
|
||||||
case 'open':
|
case 'open':
|
||||||
console.log('Websocket connection opened, reloading config');
|
console.log('Websocket connection opened, reloading config');
|
||||||
this.loadConfig();
|
this.loadConfig();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadConfig() {
|
loadConfig() {
|
||||||
@@ -107,9 +107,9 @@ export class ConfigService {
|
|||||||
setEmptyConfig() {
|
setEmptyConfig() {
|
||||||
console.log('Setting empty config');
|
console.log('Setting empty config');
|
||||||
this.updateCurrentlyShownConfig({
|
this.updateCurrentlyShownConfig({
|
||||||
api: {
|
api: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
port: 8080
|
port: 8080,
|
||||||
},
|
},
|
||||||
modules: [],
|
modules: [],
|
||||||
routes: [],
|
routes: [],
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { effect, inject, Injectable, signal } from '@angular/core';
|
import { effect, inject, Injectable, signal } from '@angular/core';
|
||||||
import {
|
import { InputEventData, OutputEventData, RouteEventData, RouterEvent } from '../models/events';
|
||||||
InputEventData,
|
|
||||||
OutputEventData,
|
|
||||||
RouteEventData,
|
|
||||||
RouterEvent,
|
|
||||||
} from '../models/events';
|
|
||||||
import { filter, Subject } from 'rxjs';
|
import { filter, Subject } from 'rxjs';
|
||||||
import { SettingsService } from './settings';
|
import { SettingsService } from './settings';
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
|||||||
@@ -286,8 +286,6 @@ export class SchemaService {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cleanArray(values: any[], itemSchema: SomeJSONSchema) {
|
cleanArray(values: any[], itemSchema: SomeJSONSchema) {
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
switch (itemSchema.type) {
|
switch (itemSchema.type) {
|
||||||
|
|||||||
+13
-13
@@ -1,6 +1,13 @@
|
|||||||
import { FormGroup, ValidatorFn, Validators, FormControl, AbstractControl, ValidationErrors } from "@angular/forms";
|
import {
|
||||||
import { SomeJSONSchema } from "ajv/dist/types/json-schema";
|
FormGroup,
|
||||||
import { ParamsFormInfo } from "../models/form";
|
ValidatorFn,
|
||||||
|
Validators,
|
||||||
|
FormControl,
|
||||||
|
AbstractControl,
|
||||||
|
ValidationErrors,
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
||||||
|
import { ParamsFormInfo } from '../models/form';
|
||||||
|
|
||||||
export function schemaToParamsFormInfo(schema: SomeJSONSchema): ParamsFormInfo {
|
export function schemaToParamsFormInfo(schema: SomeJSONSchema): ParamsFormInfo {
|
||||||
const paramsFormInfo: ParamsFormInfo = {
|
const paramsFormInfo: ParamsFormInfo = {
|
||||||
@@ -67,10 +74,7 @@ export function schemaToParamsFormInfo(schema: SomeJSONSchema): ParamsFormInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO(jwetzell): figure out how to disable a control but not have to deal with undefined values on disabled controls
|
//TODO(jwetzell): figure out how to disable a control but not have to deal with undefined values on disabled controls
|
||||||
paramsFormInfo.formGroup.addControl(
|
paramsFormInfo.formGroup.addControl(paramKey, new FormControl(formDefault, validators));
|
||||||
paramKey,
|
|
||||||
new FormControl(formDefault, validators),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (paramSchema.enum) {
|
if (paramSchema.enum) {
|
||||||
paramsFormInfo.paramsInfo[paramKey].options = paramSchema.enum;
|
paramsFormInfo.paramsInfo[paramKey].options = paramSchema.enum;
|
||||||
@@ -100,11 +104,7 @@ export function cleanParams(paramsSchema: SomeJSONSchema, params: any): any {
|
|||||||
const paramSchema = paramsSchema.properties[paramKey];
|
const paramSchema = paramsSchema.properties[paramKey];
|
||||||
|
|
||||||
// delete null/undefined/empty params that aren't required
|
// delete null/undefined/empty params that aren't required
|
||||||
if (
|
if (params[paramKey] === undefined || params[paramKey] === null || params[paramKey] === '') {
|
||||||
params[paramKey] === undefined ||
|
|
||||||
params[paramKey] === null ||
|
|
||||||
params[paramKey] === ''
|
|
||||||
) {
|
|
||||||
if (paramSchema.required) {
|
if (paramSchema.required) {
|
||||||
if (!paramSchema.includes(paramKey)) {
|
if (!paramSchema.includes(paramKey)) {
|
||||||
delete params[paramKey];
|
delete params[paramKey];
|
||||||
@@ -203,4 +203,4 @@ export function parseStringToArray(value: any, schema: SomeJSONSchema): any[] |
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user