Initial web server implementation

This commit is contained in:
frtabu
2022-11-30 11:34:54 +01:00
parent 0cacf29dbb
commit 21b3484e02
111 changed files with 34101 additions and 526 deletions
@@ -0,0 +1,114 @@
import {HttpClient} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {environment} from "src/environments/environment";
export interface IInfo {
name: string;
value: string;
type: IArgType;
modifiable: boolean; // set command ?
}
export interface IModule {
name: string;
}
export enum ILogLvl {
error = "error",
warn = "warn",
analysis = "analysis",
info = "info",
debug = "debug",
trace = "trace"
}
export enum ILogOutput {
stdout = "stdout",
telnet = "telnet",
web = "web",
file = "/tmp/<component>.log",
}
export enum IArgType {
boolean = "boolean",
list = "list",
loglvl = "loglvl",
range = "range",
number = "number",
string = "string",
configfile = "configfile",
simuTypes = "simuTypes",
}
export enum ICommandOptions {
update = "update", // result can be updated, triggers update button on result page when set
help = "help" // help tooltip available on command buttons
}
export interface IVariable {
name: string;
value: string;
type: IArgType;
modifiable: boolean; // set command ?
}
export interface ICommand {
name: string;
confirm?: string;
question?: IQuestion;
param?: IVariable;
options?: ICommandOptions[];
}
export interface ITable {
columns: IColumn[];
rows: string[];
}
export interface IQuestion {
display: string;
pname: string;
type: IArgType;
}
export interface IResp {
display: string[], table?: ITable
}
export interface IParam {
value: string, col: IColumn
}
export interface IColumn { // should use IVariable ?
name: string;
type: IArgType;
modifiable: boolean; // set command ?
help: boolean; // is help available
}
export interface IRow {
params: IParam[], rawIndex: number, cmdName: string,
param?: IVariable // to transmit the initial command parameter, ex: the channel model index when modify a channel model
}
export const route = "oaisoftmodem/";
@Injectable({
providedIn : "root",
})
export class CommandsApi {
constructor(private httpClient: HttpClient)
{
}
public readInfos$ = () => this.httpClient.get<IInfo[]>(environment.backend + route + "variables/");
public setInfo$ = (info: IInfo) => this.httpClient.post<IResp>(environment.backend + route + "variables/", info);
public readModules$ = () => this.httpClient.get<IModule[]>(environment.backend + route + "commands/");
public readVariables$ = (moduleName: string) => this.httpClient.get<IInfo[]>(environment.backend + route + moduleName + "/variables/");
public readCommands$ = (moduleName: string) => this.httpClient.get<ICommand[]>(environment.backend + route + moduleName + "/commands/");
public runCommand$ = (command: ICommand, moduleName: string) => this.httpClient.post<IResp>(environment.backend + route + moduleName + "/commands/", command);
public setCmdVariable$ = (variable: IInfo, moduleName: string) => this.httpClient.post<IResp>(environment.backend + route + moduleName + "/variables/", variable);
public setCmdParams$ = (row: IRow, moduleName: string) => this.httpClient.post<IResp>(environment.backend + route + moduleName + "/set/", row);
}
@@ -0,0 +1,36 @@
import {HttpClient} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {map} from "rxjs/operators";
import {environment} from "src/environments/environment";
export interface HelpRequest {
module: string;
command: string;
object: string;
}
export interface HelpResp {
text: string;
}
const hlproute = "oaisoftmodem/helpfiles/";
@Injectable({
providedIn : "root",
})
export class HelpApi {
constructor(private httpClient: HttpClient)
{
}
public getHelp$ = (req: HelpRequest) => this.httpClient.get<HelpResp>(environment.backend + hlproute + req.module + "_" + req.command + "_" + req.object + ".html", {observe : "response"});
public getHelpText(module: string, command: string, object: string): Observable<string>
{
return this.getHelp$({module : module, command : command.replace(" ", "_"), object : object.replace(" ", "_")})
.pipe(map(
(response => { return (response.status == 201) ? response.body!.text : ""; }),
)); // pipe
}
}
@@ -0,0 +1,49 @@
import {HttpClient} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {environment} from "src/environments/environment";
export enum IScopeGraphType {
IQs = "IQs",
LLR = "LLR",
WF = "WF",
TRESP = "TRESP",
UNSUP = "UNSUP"
}
export interface IGraphDesc {
title: string;
type: IScopeGraphType;
id: number;
srvidx: number;
}
export interface IScopeDesc {
title: string;
graphs: IGraphDesc[];
}
export interface IScopeCmd {
name: string;
graphid?: number; // the graph srvidx
value: string;
}
export interface ISigDesc {
target_id: number;
antenna_id: number;
}
const route = "oaisoftmodem/scopectrl/";
@Injectable({
providedIn : "root",
})
export class ScopeApi {
constructor(private httpClient: HttpClient)
{
}
public getScopeInfos$ = () => this.httpClient.get<IScopeDesc>(environment.backend + route);
public setScopeParams$ = (cmd: IScopeCmd) => this.httpClient.post(environment.backend + route, cmd, {observe : "response"});
}
@@ -0,0 +1,18 @@
import {NgModule} from "@angular/core";
import {RouterModule, Routes} from "@angular/router";
import {AppComponent} from "./app.component";
import {CommandsComponent} from "./components/commands/commands.component";
const routes: Routes = [
{path : "", redirectTo : "/websrv", pathMatch : "full"},
{path : "websrv", component : AppComponent},
{path : "**", redirectTo : ""},
];
@NgModule({
imports : [ RouterModule.forRoot(routes, {relativeLinkResolution : "legacy"}) ],
exports : [ RouterModule ],
})
export class AppRoutingModule {
}
@@ -0,0 +1,11 @@
.mat-tab-group {
margin-bottom: 10px;
}
.mat-ink-bar{
height: 8px !important;
}
.mat-tab-label-active
{
color: #000000 !important;
}
@@ -0,0 +1,8 @@
<mat-tab-group backgroundColor="primary" color="accent">
<mat-tab label="Commands">
<app-commands></app-commands>
</mat-tab>
<mat-tab label={{scopelabel}} disabled={{!isscopeavailable}}>
<app-scope (ScopeEnabled)="onScopeEnabled($event)"></app-scope>
</mat-tab>
</mat-tab-group>
@@ -0,0 +1,24 @@
import {Component} from "@angular/core";
import {MatTabsModule} from "@angular/material/tabs";
@Component({
selector : "app-root",
templateUrl : "./app.component.html",
styleUrls : [ "./app.component.css" ],
})
export class AppComponent {
title = "oai softmodem";
isscopeavailable = false;
scopelabel = "";
constructor()
{
this.scopelabel = "";
this.isscopeavailable = false;
}
onScopeEnabled(enabled: boolean)
{
this.isscopeavailable = enabled;
this.scopelabel = enabled ? "Scope" : "";
}
}
@@ -0,0 +1,59 @@
import {DragDropModule} from "@angular/cdk/drag-drop";
import {HttpClientModule} from "@angular/common/http";
import {NgModule} from "@angular/core";
import {FlexLayoutModule} from "@angular/flex-layout";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatButtonModule} from "@angular/material/button";
import {MatCardModule} from "@angular/material/card";
import {MatChipsModule} from "@angular/material/chips";
import {MatDialogModule} from "@angular/material/dialog";
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatGridListModule} from "@angular/material/grid-list";
import {MatInputModule} from "@angular/material/input";
import {MatListModule} from "@angular/material/list";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MatSelectModule} from "@angular/material/select";
import {MatSlideToggleModule} from "@angular/material/slide-toggle";
import {MatSliderModule} from "@angular/material/slider";
import {MatSnackBarModule} from "@angular/material/snack-bar";
import {MatTableModule} from "@angular/material/table";
import {MatTabsModule} from "@angular/material/tabs";
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatTooltipModule} from "@angular/material/tooltip";
import {BrowserModule} from "@angular/platform-browser";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {NgChartsModule} from "ng2-charts";
import {CommandsApi} from "./api/commands.api";
import {ScopeApi} from "./api/scope.api";
import {AppRoutingModule} from "./app-routing.module";
import {AppComponent} from "./app.component";
import {CommandsComponent} from "./components/commands/commands.component";
import {ConfirmDialogComponent} from "./components/confirm/confirm.component";
import {DialogComponent} from "./components/dialog/dialog.component";
import {QuestionDialogComponent} from "./components/question/question.component";
import {ScopeComponent} from "./components/scope/scope.component";
import {InterceptorProviders} from "./interceptors/interceptors";
import {LoadingService} from "./services/loading.service";
import {WebSocketService} from "./services/websocket.service";
@NgModule({
declarations : [ AppComponent, CommandsComponent, ConfirmDialogComponent, QuestionDialogComponent, DialogComponent, ScopeComponent ],
imports : [
BrowserModule, AppRoutingModule, FormsModule, ReactiveFormsModule, BrowserAnimationsModule, HttpClientModule, MatButtonModule, FlexLayoutModule, MatDialogModule, DragDropModule,
MatSliderModule, MatFormFieldModule, MatInputModule, MatChipsModule, MatProgressSpinnerModule, MatToolbarModule, MatTableModule, MatListModule, MatSelectModule, MatSnackBarModule,
MatSlideToggleModule, MatGridListModule, MatCardModule, MatTabsModule, MatTooltipModule, NgChartsModule,
],
providers : [
// services
LoadingService,
WebSocketService,
// api
CommandsApi,
ScopeApi,
// interceptors
InterceptorProviders,
],
bootstrap : [ AppComponent ]
})
export class AppModule {
}
@@ -0,0 +1,127 @@
<div class="grid-container" >
<mat-grid-list cols="2" rowHeight="15vh" >
<mat-grid-tile [colspan]="1" [rowspan]="2" >
<mat-card class="dashboard-card">
<mat-card-header >
<mat-card-title>Connection info</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div *ngIf="infos$ | async as infos">
<div class="spaceddiv" *ngFor="let info of infos">
<mat-form-field class="scrollablefield" >
<mat-label>{{ info.nameFC.value }}</mat-label>
<input matInput [formControl]="info.valueFC" [readonly]="!info.modifiableFC.value" />
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!info.modifiableFC.value"
(click)="onInfoSubmit(info)">
{{ info.btnTxtFC }}
</button>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="1" [rowspan]="2">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Softmodem commands</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div class="spaceddiv" *ngIf="modules$ | async as modules" fxLaypout="row">
<mat-form-field>
<mat-label>Module</mat-label>
<mat-select (selectionChange)="onModuleSelect($event.value)" [(value)]="selectedModule">
<mat-option *ngFor="let module of modules" [value]="module"> {{ module.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="cmds$ | async as cmds">
<div *ngIf="cmds.length">
<mat-chip-list>
<mat-chip *ngFor="let cmd of cmds" (click)="onCmdSubmit(cmd)" [matTooltip]="cmd.hlp_cmd" > {{cmd.nameFC.value}}
</mat-chip>
</mat-chip-list>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile *ngIf="vars$ | async as vars" [colspan]="1" [rowspan]="4">
<mat-card *ngIf="vars.length" class="dashboard-card">
<mat-card-header>
<mat-card-title>{{ selectedModule!.name }} variables</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div class="spaceddiv" *ngFor="let variable of vars">
<mat-form-field class="scrollablefield">
<mat-label>{{ variable.nameFC.value }}</mat-label>
<input matInput [formControl]="variable.valueFC" [readonly]="!variable.modifiableFC.value" />
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!variable.modifiableFC.value"
(click)="onVarsubmit(variable)">
set
</button>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<mat-grid-tile *ngIf="(rows$ | async)?.length" [colspan]="1" [rowspan]="4">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>{{ selectedModule!.name }} {{ selectedCmd!.name }} {{ selectedCmd!.param?.value}}</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div >
<mat-table mat-table [dataSource]="rows$" multiTemplateDataRows class="mat-elevation-z8">
<div class="TableRowdiv" *ngFor="let col of columns; index as colIndex" matColumnDef="{{col.name}}">
<mat-header-cell *matHeaderCellDef fxLayoutAlign="start center">
<div [matTooltip]="hlp_cc[colIndex]">
<h4>{{ col.name }}</h4>
</div>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<div [ngSwitch]="col.type" [formGroup]="row" fxLayoutAlign="start center">
<mat-slide-toggle *ngSwitchCase="IArgType.boolean" [formControl]="row.paramsCtrls[colIndex].valueFC"
[checked]="row.paramsCtrls[colIndex].valueFC.value">
</mat-slide-toggle>
<mat-list-item role="listitem">
<mat-form-field *ngSwitchCase="IArgType.loglvl">
<mat-select [formControl]="row.paramsCtrls[colIndex].valueFC"
[(value)]="row.paramsCtrls[colIndex].valueFC.value">
<mat-option *ngFor="let level of logLvlValues" [value]="level"> {{ level }}
</mat-option>
</mat-select>
</mat-form-field>
</mat-list-item>
<div *ngSwitchDefault>
<mat-form-field>
<input matInput [formControl]="row.paramsCtrls[colIndex].valueFC" [readonly]="!col.modifiable" />
</mat-form-field>
</div>
</div>
</mat-cell>
</div>
<div matColumnDef="button">
<mat-header-cell *matHeaderCellDef> </mat-header-cell>
<mat-cell *matCellDef="let row" fxLayoutAlign="center">
<button class="TableBtn" mat-raised-button color="primary" (click)="onParamSubmit(row)" [disabled]="row.pristine"> set
</button>
</mat-cell>
</div>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>
@@ -0,0 +1,80 @@
.spaceddiv {
height: 45px;
width: 40vw;
display: flex;
justify-content: space-between;
margin: 0 auto;
padding: 10px 0;
}
.TableRowdiv {
height: 38px;
display: flex;
/* justify-content: space-between;*/
margin: 2px;
width: 95%;
.item:nth-child(1n){
width: 10%;
}
.item {
width: 4%;
margin: 10px;
}
}
.TableBtn {
height: 25px;
width: 35p;
display:flex;
padding-bottom:8px;
}
.TableText {
display: flex;
width: 8em;
overflow-x: auto;
}
.scrollablefield {
width: 80%;
height: 20px;
.mat-input-element {
overflow-x: auto;
}
}
.mat-card {
height: 95%;
overflow-y: auto;
overflow-x: scroll;
}
.status {
color: green;
font-weight: 800;
}
.ng-invalid .status {
color: red;
}
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}
.dashboard-card-content {
text-align: center;
}
@@ -0,0 +1,207 @@
import {Component} from "@angular/core";
import {ViewEncapsulation} from "@angular/core";
import {UntypedFormArray} from "@angular/forms";
import {BehaviorSubject, forkJoin, Observable, of, timer} from "rxjs";
import {filter, map, switchMap, tap} from "rxjs/operators";
import {CommandsApi, IArgType, IColumn, ICommand, ICommandOptions, IInfo, ILogLvl, IParam, IRow} from "src/app/api/commands.api";
import {HelpApi, HelpRequest, HelpResp} from "src/app/api/help.api";
import {CmdCtrl} from "src/app/controls/cmd.control";
import {InfoCtrl} from "src/app/controls/info.control";
import {ModuleCtrl} from "src/app/controls/module.control";
import {RowCtrl} from "src/app/controls/row.control";
import {VarCtrl} from "src/app/controls/var.control";
import {DialogService} from "src/app/services/dialog.service";
import {DownloadService} from "src/app/services/download.service";
import {LoadingService} from "src/app/services/loading.service";
const CHANNEL_MOD_MODULE = "channelmod"
const PREDEF_CMD = "show predef"
@Component({
selector : "app-commands",
templateUrl : "./commands.component.html",
styleUrls : [ "./commands.component.scss" ],
encapsulation : ViewEncapsulation.None,
}) export class CommandsComponent {
hlp_cc: string[] = [];
hlp_cmd: string[] = [];
IArgType = IArgType;
logLvlValues = Object.values(ILogLvl);
// softmodem
infos$: Observable<VarCtrl[]>;
modules$: Observable<ModuleCtrl[]>;
// module
selectedModule?: ModuleCtrl;
vars$?: Observable<VarCtrl[]>;
cmds$?: Observable<CmdCtrl[]>;
// command
selectedCmd?: ICommand
displayedColumns: string[] = [];
rows$: BehaviorSubject<RowCtrl[]> = new BehaviorSubject<RowCtrl[]>([]);
columns: IColumn[] = [];
constructor(
public commandsApi: CommandsApi,
public helpApi: HelpApi,
public loadingService: LoadingService,
public dialogService: DialogService,
public downloadService: DownloadService,
)
{
this.infos$ = this.commandsApi.readInfos$().pipe(map((infos) => infos.map(info => new InfoCtrl(info))));
this.modules$ = this.commandsApi.readModules$().pipe(
map(imodules => imodules.map(imodule => new ModuleCtrl(imodule))), filter(controls => controls.length > 0), tap(controls => this.onModuleSelect(controls[0])));
}
// get types$() {
// return this.modules$.pipe(
// filter(modules => modules.map(module => module.name).includes(CHANNEL_MOD_MODULE)),
// mergeMap(modules => this.commandsApi.readCommands$(modules[0]!.name)),
// map(icmds => icmds.filter(cmd => cmd.name === PREDEF_CMD)),
// filter(icmds => icmds.length > 0),
// map(icmds => new CmdCtrl(icmds[0])),
// mergeMap(control => this.commandsApi.runCommand$(control.api(), CHANNEL_MOD_MODULE)),
// map(resp => resp.display.map(line => line.match('/\s*[0-9]*\s*(\S*)\n/gm')![0])),
// tap(types => console.log(types.join(', ')))
// );
// }
onInfoSubmit(control: InfoCtrl)
{
let info: IInfo = control.api();
if (info.type === IArgType.configfile) {
this.downloadService.getFile(info.value)
} else {
this.commandsApi.setInfo$(info).subscribe();
}
}
onModuleSelect(module: ModuleCtrl)
{
this.selectedModule = module
this.selectedCmd = undefined
this.cmds$ = this.commandsApi.readCommands$(module.name).pipe(
map(icmds => icmds.map(icmd => new CmdCtrl(icmd))),
map(cmds => {
module.cmdsFA = new UntypedFormArray(cmds)
for (let i = 0; i < cmds.length; i++)
{
cmds[i].get_cmd_help(this.helpApi, module.name)
}
return module.cmdsFA.controls as CmdCtrl[]
})
)
this.vars$ = this.commandsApi.readVariables$(module.name).pipe(
map(ivars => ivars.map(ivar => new VarCtrl(ivar))),
map(vars => {
module.varsFA = new UntypedFormArray(vars)
return module.varsFA.controls as VarCtrl[]
})
)
}
onVarsubmit(control: VarCtrl)
{
this.commandsApi.setCmdVariable$(control.api(), this.selectedModule!.name).pipe(map(resp => this.dialogService.openVarRespDialog(resp))).subscribe();
}
onCmdSubmit(control: CmdCtrl)
{
this.selectedCmd = control.api()
const obsparam$ = forkJoin([ control.confirm ? this.dialogService.openConfirmDialog(control.confirm)
: control.question ? this.dialogService.openQuestionDialog(this.selectedModule! + " " + this.selectedModule!.name, control)
: of(true) ]);
obsparam$
.pipe(switchMap(results => {
if (!results[0])
return of(null);
return this.execCmd$(control);
}))
.subscribe();
}
private execCmd$(control: CmdCtrl)
{
let cmd = control!.api();
if (this.selectedCmd!.param)
this.selectedCmd!.param!.value = cmd.param!.value;
this.commandsApi.runCommand$(cmd, this.selectedModule!.name)
.subscribe(
resp => {
if (resp.display[0])
this.dialogService.updateCmdDialog(control, resp, "cmd " + control.nameFC.value + " response:")
// else return of(resp)
const controls: RowCtrl[] = [];
this.displayedColumns = [];
if (resp.table) {
this.columns = resp.table.columns;
this.displayedColumns = this.columns.map(col => col.name);
this.displayedColumns.push("button");
// possibly load help..
for (let i = 0; i < this.columns.length; i = i + 1) {
if (this.columns[i].help) {
this.helpApi.getHelp$({module : this.selectedModule!.name, command : control!.api().name.replace(" ", "_"), object : this.columns[i].name.replace(" ", "_")})
.subscribe(
response => {
if (response.status == 201)
this.hlp_cc[i] = response.body!.text;
},
err => { this.hlp_cc[i] = ""; },
);
} else {
this.hlp_cc[i] = "";
}
}
for (let rawIndex = 0; rawIndex < resp.table.rows.length; rawIndex++) {
let params: IParam[] = [];
for (let i = 0; i < this.columns.length; i = i + 1) {
params.push({value : resp.table.rows[rawIndex][i], col : this.columns[i]})
}
const irow: IRow = {params : params, rawIndex : rawIndex, cmdName : this.selectedCmd!.name}
controls[rawIndex] = new RowCtrl(irow)
}
}
this.rows$.next(controls)
},
err => console.error("execCmd error: " + err),
() => {
console.log("execCmd completed: ");
if (control.isResUpdatable()) {
if (!(control.ResUpdTimerSubscriber) || control.ResUpdTimerSubscriber.closed) {
if (!control.ResUpdTimer)
control.ResUpdTimer = timer(1000, 1000);
control.ResUpdTimerSubscriber = control.ResUpdTimer.subscribe(iteration => {
console.log("Update timer fired" + iteration);
if (control.updbtnname === "Stop update")
this.execCmd$(control);
});
}
}
},
) // map resp
return of(null);
}
onParamSubmit(control: RowCtrl)
{
if (this.selectedCmd!.param)
control.set_cmdparam(this.selectedCmd!.param);
this.commandsApi.setCmdParams$(control.api(), this.selectedModule!.name).subscribe(() => this.execCmd$(new CmdCtrl(this.selectedCmd!)));
}
}
@@ -0,0 +1,5 @@
<h3>{{ data.title }}</h3>
<div fxLayoutGap="10px" mat-dialog-actions fxLayout="row" fxLayoutAlign="center start">
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>Ok</button>
<button mat-button (click)="onNoClick()">Cancel</button>
</div>
@@ -0,0 +1,15 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {Component, Inject} from "@angular/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
@Component({selector : "app-confirm", templateUrl : "./confirm.component.html", styleUrls : [ "./confirm.component.css" ]})
export class ConfirmDialogComponent {
constructor(public dialogRef: MatDialogRef<void>, @Inject(MAT_DIALOG_DATA) public data: any)
{
}
onNoClick()
{
this.dialogRef.close();
}
}
@@ -0,0 +1,34 @@
/* icon that can be used to drag a dialog */
::ng-deep.dialogdrag-handle {
top: 1px;
left: 1px;
color: #ccc;
cursor: move;
width: 24px;
height: 24px;
}
/* make dialog for command response using the display field scrollable */
::ng-deep.cmdRespDialog {
resize: both;
overflow: scroll;
}
/* make only the dialog for command response using the display field movable */
/* (hide the close button and the drag icon) */
::ng-deep.varRespDialog #dialogclose {
display:none ;
}
::ng-deep.varRespDialog #dialogdrag {
display:none;
}
::ng-deep.errRespDialog #dialogclose {
display:none ;
}
::ng-deep.errRespDialog #dialogdrag {
display:none;
}
@@ -0,0 +1,17 @@
<div id="dialogdrag" cdkDrag cdkDragRootElement=".cdk-overlay-pane">
<div class="dialogdrag-handle" cdkDragHandle>
<svg width="24px" fill="currentColor" viewBox="0 0 24 24">
<path
d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z">
</path>
<path d="M0 0h24v24H0z" fill="none"></path>
</svg>
</div>
</div>
<h3> {{ data.title }}</h3>
<pre [innerHTML]="data.body"></pre>
<mat-dialog-actions fxLayoutAlign="end">
<button id="dialogclose" mat-button mat-dialog-close>Close</button>
<button id="dialogupdate" mat-button (click)="onUpdate(data.control)"
*ngIf="data.updatable">{{data.control.updbtnname}}</button>
</mat-dialog-actions>
@@ -0,0 +1,23 @@
import {Component, ElementRef, Inject, OnInit, ViewChild} from "@angular/core";
import {MAT_DIALOG_DATA} from "@angular/material/dialog";
import {CmdCtrl} from "src/app/controls/cmd.control";
import {DialogService} from "src/app/services/dialog.service";
@Component({
selector : "app-dialog",
templateUrl : "./dialog.component.html",
styleUrls : [ "./dialog.component.css" ],
})
export class DialogComponent {
constructor(@Inject(MAT_DIALOG_DATA) public data: any)
{
}
onUpdate(control: CmdCtrl)
{
if (control.updbtnname === "Stop update") {
control.stopUpdate();
} else {
control.startUpdate();
}
}
}
@@ -0,0 +1,11 @@
<h3>{{ data.title }}</h3>
<div mat-dialog-content >
<mat-form-field appearance="fill">
<label for="answer">{{data.control.question?.display}}</label>
<input id="answer" type="text" style="width:10vw" [formControl]="data.control.answerFC">
</mat-form-field>
</div>
<div fxLayoutGap=" 10px" mat-dialog-actions fxLayout="row" fxLayoutAlign="center start">
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>Ok</button>
<button mat-button [mat-dialog-close]="false">Cancel</button>
</div>
@@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/naming-convention */
import {Component, Inject} from "@angular/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {CmdCtrl} from "src/app/controls/cmd.control";
export interface QuestionDialogData {
title: string;
control: CmdCtrl;
}
@Component({selector : "app-question", templateUrl : "./question.component.html", styleUrls : [ "./question.component.css" ]})
export class QuestionDialogComponent {
constructor(
public dialogRef: MatDialogRef<QuestionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: QuestionDialogData,
)
{
}
onNoClick()
{
this.dialogRef.close();
}
}
@@ -0,0 +1,42 @@
mat-form-field {
width: 80%;
}
.status {
color: green;
font-weight: 800;
}
.ng-invalid .status {
color: red;
}
.grid-container {
margin: 20px;
}
.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}
.more-button {
position: absolute;
top: 5px;
right: 10px;
}
.dashboard-card-content {
text-align: center;
}
.scope-slider {
width: 90%;
}
.mat-mini-fab.mat-started {
background-color: #00a825;
}
@@ -0,0 +1,219 @@
<div class="grid-container">
<mat-grid-list cols="11" rowHeight="100px">
<!-- top fullwidth area for scope status display -->
<mat-grid-tile [colspan]="11" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>{{ scopetitle }} {{ scopesubtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div FxLayoutAlign="row">
<label>{{ scopetime }} {{ scopestatus }} &nbsp; msg skipped: {{ skippedmsg }} &nbsp; msg buffered: {{ bufferedmsg }} &nbsp;</label>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<!-- left full heigth, narrow width area, for global scope controls -->
<mat-grid-tile [colspan]="1" [rowspan]="8">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Scope control</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<button mat-mini-fab color={{startstop_color}} (click)="startorstop()">{{startstop}}</button>
<br><br>
<div class="scope-slider-div">
<label id="scope-slider-refrate-labelid" class="scope-slider-label">refresh rate</label>
<label class="scope-slider-value-label"> {{rfrate}} </label>
</div>
<mat-slider class="scope-slider" min=".1" max="10" step="0.1" [value]="rfrate"
aria-labelledby="scope-slider-refrate-labelid" vertical="true" thumbLabel="true"
[(ngModel)]="rfrate" (change)="OnRefrateChange()" >
</mat-slider>
<br><br>
<mat-list-item class="sigselect" role="listitem">
<mat-form-field class="select-form">
<mat-select placeholder="sig select" name="SigSelect" class="SigSelect" [value]="selected_sig.target_id" (selectionChange)="SigChanged($event.value)" >
<mat-option *ngFor="let targetid of sig_list" [value]="targetid.target_id" >
{{targetid.target_id}}
</mat-option>
</mat-select>
</mat-form-field>
</mat-list-item>
<br><br>
<mat-slide-toggle [matTooltip]="help_ack" [checked]="data_ACK" (change)="onDataACKchange()" [(ngModel)]="data_ACK">ack
</mat-slide-toggle>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<!-- area for watter fall data display -->
<mat-grid-tile [colspan]="7" [rowspan]="3">
<mat-card class="dashboard-card">
<mat-card-content class="dashboard-card-content">
<div fxLayout="column">
<!-- area for the chart -->
<div fxFlex="1 1 90% " style="width:100%">
<canvas baseChart id="wf"
[datasets]="WFDatasets"
[options]="WFOptions"
[type]="'scatter'">
</canvas>
</div>
<br><br>
<!-- area for the control used to select WF data -->
<div fxFlex="0 1 10%" style="width:100%">
<div class="form-group" fxFlex="50%"><mat-list-item class="WFselect" role="listitem">
<mat-form-field class="select-form">
<mat-select placeholder="select data" name="WFSelect" class="WFSelect" [value]="selected_WF" (selectionChange)="WFChanged($event.value)" >
<mat-option *ngFor="let graph of WFgraph_list" [value]="graph.title" >
{{graph.title}}
</mat-option>
<mat-option>none</mat-option>
</mat-select>
</mat-form-field>
</mat-list-item></div>
<div class="form-group" fxFlex="25%"><button mat-raised-button color="primary" (click)="RefreshWF()">Refresh</button> </div>
</div>
</div></mat-card-content>
</mat-card>
</mat-grid-tile>
<!-- right area for Time Response data -->
<mat-grid-tile [colspan]="3" [rowspan]="3">
<mat-card class="dashboard-card">
<mat-card-content class="dashboard-card-content">
<div fxLayout="column">
<!-- area for the chart -->
<div fxFlex="1 1 80% " style="width:100%">
<canvas baseChart id="tresp"
[datasets]="TRespDatasets"
[options]="TRespOptions"
[labels]="TRespLabels"
[type]="'line'">
</canvas>
</div>
<br><br>
<!-- area for the control used to enable -->
<div fxFlex="0 1 20%" style="width:60%">
<div class="form-group" fxFlex="50%"><mat-slide-toggle [checked]="enable_TResp" (change)="onEnableTResp()" [(ngModel)]="enable_TResp">enable:
</mat-slide-toggle> </div>
<div class="form-group" fxFlex="25%"><button mat-raised-button color="primary" (click)="RefreshTResp()">Refresh</button> </div>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<!-- area for LLR data display -->
<mat-grid-tile [colspan]="5" [rowspan]="4">
<mat-card class="dashboard-card">
<mat-card-content class="dashboard-card-content">
<div fxLayout="column">
<div fxFlex="1 1 80% ">
<canvas baseChart id="llr" width="90%"
[datasets]=" LLRDatasets"
[options]="LLROptions"
[type]="'scatter'">
</canvas>
</div>
<div fxFlex="0 1 10%" style="width:100%">
<div class="form-group" fxFlex="50%"> <mat-list-item class="llrchannelselect" role="listitem">
<mat-form-field class="select-form">
<mat-select placeholder="Channels(llr view)" name="llrChannelsSelect" class="ChannelsSelect" multiple [value]="selected_llrchannels" (selectionChange)="llrchannelsChanged($event.value)" >
<mat-option *ngFor="let graph of llrgraph_list" [value]="graph.title" >
{{graph.title}}
</mat-option>
</mat-select>
</mat-form-field>
</mat-list-item> </div>
<div class="form-group" fxFlex="50%">
<div class="scope-slider-div">
<label id="scope-slider-ythresh-labelid" class="scope-slider-label">y threshold </label>
<label class="scope-slider-value-label"> {{llrythresh}} </label>
</div>
<mat-slider class="scope-slider" min="0" max="100" step="1" [value]="llrythresh"
aria-labelledby="scope-slider-ythresh-labelid" thumbLabel="true"
[(ngModel)]="llrythresh" (change)="OnYthreshChange()" >
</mat-slider>
</div>
</div>
<div fxFlex="0 1 10%">
<div class="form-group" fxFlex="25%"> <mat-form-field appearance="fill">
<mat-label>xmin</mat-label>
<input matInput type="number" (change)="OnLLRxminChange()" [value]="llrxmin" [(ngModel)]="llrxmin" min="llrmin" [max]="llrmax">
</mat-form-field></div>
<div class="form-group" fxFlex="25%"><mat-form-field appearance="fill">
<mat-label>xmax</mat-label>
<input matInput type="number" (change)="OnLLRxmaxChange()" [value]="llrxmax" [(ngModel)]="llrxmax" min="llrmin" [max]="llrmax">
</mat-form-field></div>
<div class="form-group" fxFlex="25%"><button mat-raised-button color="primary" (click)="RefreshLLR()">Refresh</button> </div>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile> <!-- end of LLR area -->
<!-- area for iq constellation data display -->
<mat-grid-tile [colspan]="5" [rowspan]="6">
<mat-card class="dashboard-card">
<mat-card-content>
<div fxLayout="column">
<!-- area for the chart -->
<div fxFlex="1 1 80% " style="width:60%">
<canvas baseChart id="iqc"
[datasets]="IQDatasets"
[options]="IQOptions"
[type]="'scatter'">
</canvas>
</div>
<br><br>
<!-- area for the control used to select channel's -->
<div fxFlex="0 1 10%" style="width:60%">
<div class="form-group" fxFlex="50%"> <mat-list-item class="channelselect" role="listitem">
<mat-form-field class="select-form">
<mat-select placeholder="Channels(IQ view)" name="ChannelsSelect" class="ChannelsSelect" multiple [value]="selected_channels" (selectionChange)="channelsChanged($event.value)" >
<mat-option *ngFor="let graph of iqgraph_list" [value]="graph.title" >
{{graph.title}}
</mat-option>
</mat-select>
</mat-form-field>
</mat-list-item></div>
<div class="form-group" fxFlex="25%"><button mat-raised-button color="primary" (click)="RefreshIQV()">Refresh</button> </div>
</div>
<!-- area for the 4 controls used to select x and y limits for data -->
<div fxFlex="0 1 5%">
<div class="form-group" fxFlex="25%">
<mat-form-field appearance="fill">
<mat-label>xmin</mat-label>
<input matInput type="number" (change)="OnIQxminChange()" [value]="iqxmin" [(ngModel)]="iqxmin" min="iqmin" [max]="iqmax">
</mat-form-field>
</div>
<div class="form-group" fxFlex="25%">
<mat-form-field appearance="fill">
<mat-label>xmax</mat-label>
<input matInput type="number" (change)="OnIQxmaxChange()" [value]="iqxmax" [(ngModel)]="iqxmax" min="iqmin" [max]="iqmax">
</mat-form-field>
</div>
<div class="form-group" fxFlex="25%">
<mat-form-field appearance="fill">
<mat-label>ymin</mat-label>
<input matInput type="number" (change)="OnIQyminChange()" [value]="iqymin" [(ngModel)]="iqymin" min="iqmin" [max]="iqmax">
</mat-form-field>
</div>
<div class="form-group" fxFlex="25%">
<mat-form-field appearance="fill">
<mat-label>ymax</mat-label>
<input matInput type="number" (change)="OnIQymaxChange()" [value]="iqymax" [(ngModel)]="iqymax" min="iqmin" [max]="iqmax">
</mat-form-field>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile> <!-- end of iq area -->
</mat-grid-list>
</div>
@@ -0,0 +1,705 @@
import {Component, EventEmitter, OnDestroy, OnInit, Output, QueryList, ViewChildren} from "@angular/core";
import {Chart, ChartConfiguration} from "chart.js";
import {BaseChartDirective} from "ng2-charts";
import {Subscription} from "rxjs";
import {HelpApi} from "src/app/api/help.api";
import {IGraphDesc, IScopeDesc, IScopeGraphType, ISigDesc, ScopeApi} from "src/app/api/scope.api";
import {arraybuf_data_offset, Message, WebSocketService, webSockSrc} from "src/app/services/websocket.service";
export interface RxScopeMessage {
msgtype: number;
chartid: number;
dataid: number;
segnum: number;
update: boolean;
content: ArrayBuffer;
}
const deserialize = (fullbuff: ArrayBuffer):
RxScopeMessage => {
const header = new DataView(fullbuff, 0, arraybuf_data_offset);
return {
// source: src.getUint8(0), //header
msgtype : header.getUint8(1), // header
chartid : header.getUint8(3), // header
dataid : header.getUint8(4), // header
segnum : header.getUint8(2), // header
update : (header.getUint8(5) == 1) ? true : false, // header
content : fullbuff.slice(arraybuf_data_offset) // data
};
}
const serialize = (msg: TxScopeMessage):
ArrayBuffer => {
const byteArray = new TextEncoder().encode(msg.content);
let arr = new Uint8Array(byteArray.byteLength + arraybuf_data_offset);
arr.set(byteArray, arraybuf_data_offset) // data
let buffview = new DataView(arr.buffer);
buffview.setUint8(1, msg.msgtype); // header
return buffview.buffer;
}
export interface TxScopeMessage {
msgtype: number;
content: string;
}
/*------------------------------------*/
/* constants that must match backend (websrv.h or phy_scope.h) */
const SCOPEMSG_TYPE_TIME = 3;
const SCOPEMSG_TYPE_DATA = 10;
const SCOPEMSG_TYPE_DATAACK = 11;
const SCOPEMSG_TYPE_DATAFLOW = 12;
const SCOPEMSG_DATA_IQ = 1;
const SCOPEMSG_DATA_LLR = 2;
const SCOPEMSG_DATA_WF = 3;
const SCOPEMSG_DATA_TRESP = 4;
/*---------------------------------------*/
@Component({
selector : "app-scope",
templateUrl : "./scope.component.html",
styleUrls : [ "./scope.component.css" ],
})
export class ScopeComponent implements OnInit, OnDestroy {
// data for scope status area
scopetitle = "";
scopesubtitle = "";
scopetime = "";
scopestatus = "stopped";
skippedmsg = "0";
bufferedmsg = "0";
// data for scope control area
startstop = "start";
startstop_color = "warn";
rfrate = 2;
data_ACK = false;
// data for Time Response chart
TRespgraph: IGraphDesc = {title : "", type: IScopeGraphType.TRESP, id: -1, srvidx: -1};
enable_TResp = false;
// data for scope iq constellation area
iqgraph_list: IGraphDesc[] = [];
selected_channels = [ "" ];
iqmax = 32767;
iqmin = -32767;
iqxmin = this.iqmin;
iqymin = this.iqmin;
iqxmax = this.iqmax;
iqymax = this.iqmax;
// data for scope LLR area
llrgraph_list: IGraphDesc[] = [];
selected_llrchannels = [ "" ];
sig_list: ISigDesc[] = [
{target_id : 0, antenna_id: 0},
{target_id : 1, antenna_id: 0},
{target_id : 2, antenna_id: 0},
{target_id : 3, antenna_id: 0},
];
selected_sig: ISigDesc = {target_id : 0, antenna_id: 0};
llrythresh = 5;
llrmin = 0;
llrmax = 200000;
llrxmin = this.llrmin;
llrxmax = this.llrmax;
// data for scope WatterFall area
WFgraph_list: IGraphDesc[] = [];
selected_WF = "";
llrchart?: Chart;
iqcchart?: Chart;
wfchart?: Chart;
trespchart?: Chart;
nwf: number[] = [ 0, 0, 0, 0 ];
// websocket service object and related subscription for message reception
wsSubscription?: Subscription;
@Output() ScopeEnabled = new EventEmitter<boolean>();
@ViewChildren(BaseChartDirective) charts?: QueryList<BaseChartDirective>;
public TRespDatasets: ChartConfiguration<"line">[ "data" ]["datasets"] = [
{
data : [],
label: "",
pointRadius: 1,
showLine: true,
animation: false,
fill: false,
pointStyle: "circle",
backgroundColor: "rgba(255,0,0,0)",
borderColor: "red",
pointBorderColor: "red",
},
];
public IQDatasets: ChartConfiguration<"scatter">[ "data" ]["datasets"] = [
{
data : [],
label: "C1",
pointRadius: 0.5,
showLine: false,
animation: false,
fill: false,
pointStyle: "circle",
// pointBackgroundColor: 'yellow',
backgroundColor: "yellow",
borderWidth: 0,
pointBorderColor: "yellow",
// parsing: false,
},
{
data : [],
label: "C2",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "cyan",
backgroundColor: "cyan",
borderWidth: 0,
pointBorderColor: "cyan",
// parsing: false,
},
{
data : [],
label: "C3",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "red",
backgroundColor: "red",
borderWidth: 0,
pointBorderColor: "red",
// parsing: false,
}
];
public LLRDatasets: ChartConfiguration<"scatter">[ "data" ]["datasets"] = [
{
data : [],
label: "LLR1",
pointRadius: 0.5,
showLine: false,
animation: false,
fill: false,
pointStyle: "circle",
pointBackgroundColor: "yellow",
backgroundColor: "yellow",
borderWidth: 0,
pointBorderColor: "yellow",
// parsing: false,
},
{
data : [],
label: "LL2",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "cyan",
backgroundColor: "cyan",
borderWidth: 0,
pointBorderColor: "cyan",
parsing: false,
},
{
data : [],
label: "LLR3",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "red",
backgroundColor: "red",
borderWidth: 0,
pointBorderColor: "red",
parsing: false,
}
];
public WFDatasets: ChartConfiguration<"scatter">[ "data" ]["datasets"] = [
{
data : [],
label: "WFblue",
pointRadius: 0.5,
showLine: false,
animation: false,
fill: false,
pointStyle: "circle",
pointBackgroundColor: "blue",
backgroundColor: "blue",
borderWidth: 0,
pointBorderColor: "blue",
// parsing: false,
},
{
data : [],
label: "WFgreen",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "green",
backgroundColor: "green",
borderWidth: 0,
pointBorderColor: "green",
// parsing: false,
},
{
data : [],
label: "WFyellow",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "yellow",
backgroundColor: "yellow",
borderWidth: 0,
pointBorderColor: "yellow",
// parsing: false,
},
{
data : [],
label: "WFred",
pointRadius: 0.5,
showLine: false,
animation: false,
pointStyle: "circle",
pointBackgroundColor: "red",
backgroundColor: "red",
borderWidth: 0,
pointBorderColor: "red",
// parsing: false,
}
];
// help text from backend
help_ack: string = "";
public TRespOptions: ChartConfiguration<"line">[ "options" ] = {
responsive : true,
// aspectRatio: 2,
plugins: {
legend: {display: true, labels: {boxWidth: 10, boxHeight: 10}},
tooltip: {
enabled: false,
},
},
};
TRespLabels: number[] = [ 0 ];
public IQOptions: ChartConfiguration<"scatter">[ "options" ] = {
responsive : true,
aspectRatio: 1,
// scales: {
// xAxes: {
// min: -5,
// max:5,
// }
// },
plugins: {
legend: {display: true, labels: {boxWidth: 10, boxHeight: 10}},
tooltip: {
enabled: false,
},
},
};
public LLROptions: ChartConfiguration<"scatter">[ "options" ] = {
responsive : true,
aspectRatio: 3,
scales: {
xAxes: {
min: 0,
},
// yAxes: {
// min: -100,
// max: 100
// }
},
plugins: {
legend: {display: true, labels: {boxWidth: 10, boxHeight: 10}},
tooltip: {
enabled: false,
},
},
};
public WFOptions: ChartConfiguration<"scatter">[ "options" ] = {
responsive : true,
aspectRatio: 5,
scales: {
xAxes: {
min: 0,
// max:800,
},
yAxes: {
min: 0,
max: 80,
reverse: true,
}
},
plugins: {
legend: {display: true, labels: {boxWidth: 10, boxHeight: 10}},
tooltip: {
enabled: false,
},
},
}
constructor(private scopeApi: ScopeApi, private wsService: WebSocketService, public helpApi: HelpApi)
{
console.log("Scope constructor ");
}
ngOnInit()
{
console.log("Scope ngOnInit ");
this.scopeApi.getScopeInfos$().subscribe(resp => { this.configScope(resp); });
this.helpApi.getHelpText("scope", "control", "dataack").subscribe(resp => { this.help_ack = resp; }, err => { this.help_ack = ""; });
}
ngOnDestroy()
{
console.log("Scope ngOnDestroy ");
this.wsSubscription?.unsubscribe();
}
DecodScopeBinmsgToString(message: ArrayBuffer): string
{
const enc = new TextDecoder("utf-8");
return enc.decode(message);
}
private configScope(resp: IScopeDesc)
{
if (resp.title === "none") {
this.ScopeEnabled.emit(false);
} else {
this.ScopeEnabled.emit(true);
this.scopetitle = resp.title;
this.iqgraph_list.length = 0;
this.llrgraph_list.length = 0;
for (let graphIndex = 0; graphIndex < resp.graphs.length; graphIndex++) {
if (resp.graphs[graphIndex].type == IScopeGraphType.IQs) {
this.iqgraph_list.push(resp.graphs[graphIndex]);
this.IQDatasets[this.iqgraph_list.length - 1].label = resp.graphs[graphIndex].title;
}
if (resp.graphs[graphIndex].type == IScopeGraphType.LLR) {
this.llrgraph_list.push(resp.graphs[graphIndex]);
this.LLRDatasets[this.llrgraph_list.length - 1].label = resp.graphs[graphIndex].title;
}
if (resp.graphs[graphIndex].type == IScopeGraphType.WF) {
this.WFgraph_list.push(resp.graphs[graphIndex]);
}
if (resp.graphs[graphIndex].type == IScopeGraphType.TRESP) {
this.TRespgraph = resp.graphs[graphIndex];
this.TRespDatasets[0].label = resp.graphs[graphIndex].title;
}
}
this.charts?.forEach((child) => {child.chart?.update()});
}
}
private ProcessScopeMsg(message: RxScopeMessage)
{
if (this.scopestatus === "starting") {
this.scopestatus = "started";
this.startstop = "stop";
this.startstop_color = "started";
}
let d = 0;
let x = 0;
let y = 0;
switch (message.msgtype) {
case SCOPEMSG_TYPE_TIME:
this.scopetime = this.DecodScopeBinmsgToString(message.content);
break;
case SCOPEMSG_TYPE_DATAFLOW:
let infobuff = this.DecodScopeBinmsgToString(message.content).split("|");
if (infobuff.length >= 2) {
this.skippedmsg = infobuff[0]!;
this.bufferedmsg = infobuff[1]!;
}
break;
case SCOPEMSG_TYPE_DATA:
const bufferview = new DataView(message.content);
if (message.update) {
console.log("Starting scope update chart " + message.chartid.toString() + ", dataset " + message.dataid.toString() + " data length: " + bufferview.byteLength);
}
switch (message.chartid) {
case SCOPEMSG_DATA_TRESP:
d = 0;
for (let i = 0; i < bufferview.byteLength; i = i + 4) {
this.TRespDatasets[0].data[d] = {x : d, y : bufferview.getFloat32(i, true)};
this.TRespLabels[d] = d;
d++;
}
if (message.update) {
this.trespchart!.update();
console.log(" TRESP view update completed " + d.toString() + " points ");
}
break;
case SCOPEMSG_DATA_IQ:
this.IQDatasets[message.dataid].data.length = 0;
for (let i = 0; i < bufferview.byteLength; i = i + 4) {
this.IQDatasets[message.dataid].data[i / 4] = {x : bufferview.getInt16(i, true), y : bufferview.getInt16(i + 2, true)};
}
if (message.update) {
this.iqcchart!.update();
}
break;
case SCOPEMSG_DATA_LLR:
this.LLRDatasets[message.dataid].data.length = 0;
let xoffset = 0;
d = 0;
for (let i = 4; i < (bufferview.byteLength - 2); i = i + 4) {
xoffset = xoffset + bufferview.getInt16(i + 2, true);
this.LLRDatasets[message.dataid].data[d] = {x : xoffset, y : bufferview.getInt16(i, true)};
d++;
}
this.LLRDatasets[message.dataid].data[d] = {x : bufferview.getInt32(0, true), y : 0};
if (message.update) {
this.llrchart!.update();
}
break;
case SCOPEMSG_DATA_WF:
if (message.update) {
if (message.segnum == 0) {
for (let i = 0; i < this.WFDatasets.length; i++) {
this.nwf[i] = 0;
this.WFDatasets[i].data.length = 0;
}
}
}
for (let i = 2; i < bufferview.byteLength - 2; i = i + 4) { // first 16 bits in buffer contains the number of points in the message
x = bufferview.getInt16(i, true);
y = bufferview.getInt16(i + 2, true);
this.wfchart!.scales.xAxes.max = bufferview.getInt16(bufferview.byteLength - 4, true);
this.WFDatasets[message.dataid].data[this.nwf[message.dataid]] = {x : x, y : y};
this.nwf[message.dataid]++;
}
if (message.update) {
this.wfchart!.update();
console.log(" WF view update completed " + d.toString() + "points, ");
}
break;
default:
break;
}
this.sendMsg(SCOPEMSG_TYPE_DATAACK, "Chart " + message.chartid.toString() + ", dataset " + message.dataid.toString());
break;
default:
break;
}
}
private sendMsg(type: number, strmessage: string)
{
this.wsService.send({source : webSockSrc.softscope, fullbuff : serialize({msgtype : type, content : strmessage})});
console.log("Scope sent msg type " + type.toString() + " " + strmessage);
}
private SendScopeParams(name: string, value: string, graphid: number): number
{
let status = 0;
this.scopeApi.setScopeParams$({name : name, value : value, graphid : graphid})
.subscribe(response => { console.log(response.status); },
err => {
console.log("scope SendScopeParams: error received: " + err);
this.StopScope();
},
() => console.log("scope SendScopeParams OK"));
return status;
}
private StopScope()
{
if (this.wsSubscription)
this.wsSubscription.unsubscribe();
this.scopestatus = "stopped";
this.startstop = "start";
this.startstop_color = "warn";
this.skippedmsg = "0";
this.bufferedmsg = "0";
}
startorstop()
{
if (this.scopestatus === "stopped") {
let status = this.SendScopeParams("startstop", "start", 0);
if (status == 0) {
this.llrchart = Chart.getChart("llr");
this.iqcchart = Chart.getChart("iqc");
this.wfchart = Chart.getChart("wf");
this.trespchart = Chart.getChart("tresp");
this.IQDatasets.forEach((dataset) => {dataset.data.length = 0});
this.LLRDatasets.forEach((dataset) => {dataset.data.length = 0});
this.WFDatasets.forEach((dataset) => {dataset.data.length = 0});
this.TRespDatasets.forEach((dataset) => {dataset.data.length = 0});
this.charts?.forEach((child, index) => {child.chart?.update()});
this.scopestatus = "starting";
this.SigChanged(this.selected_sig.target_id);
this.OnRefrateChange();
this.OnIQxminChange();
this.OnIQxmaxChange();
this.OnIQyminChange();
this.OnIQymaxChange();
this.OnYthreshChange();
this.OnLLRxminChange();
this.OnLLRxmaxChange();
this.channelsChanged(this.selected_channels);
this.llrchannelsChanged(this.selected_llrchannels);
this.WFChanged(this.selected_WF);
this.onEnableTResp();
for (let i = 0; i < this.WFDatasets.length; i++) {
this.nwf[i] = 0;
this.WFDatasets[i].data.length = 0;
}
this.wsService = new (WebSocketService);
this.wsSubscription = this.wsService.subject$.subscribe(msg => this.ProcessScopeMsg(deserialize(msg.fullbuff)),
err => {
console.error("WebSocket Observer got an error: " + err);
this.StopScope()
},
() => { console.error("WebSocket Observer completed: "); })
}
} else {
this.SendScopeParams("startstop", "stop", 0);
this.StopScope();
}
}
OnRefrateChange()
{
this.SendScopeParams("refrate", (this.rfrate * 10).toString(), 0);
}
OnLLRxminChange()
{
this.SendScopeParams("llrxmin", (this.llrxmin).toString(), 0);
}
OnLLRxmaxChange()
{
this.SendScopeParams("llrxmax", (this.llrxmax).toString(), 0);
}
OnIQxminChange()
{
this.SendScopeParams("xmin", (this.iqxmin).toString(), 0);
}
OnIQxmaxChange()
{
this.SendScopeParams("xmax", (this.iqxmax).toString(), 0);
}
OnIQyminChange()
{
this.SendScopeParams("ymin", (this.iqymin).toString(), 0);
}
OnIQymaxChange()
{
this.SendScopeParams("ymax", (this.iqymax).toString(), 0);
}
channelsChanged(titles: string[])
{
this.selected_channels = titles;
this.iqgraph_list.forEach(graph => {
const [enabled] = titles.filter(value => graph.title === value);
this.SendScopeParams("enabled", enabled ? "true" : "false", graph.srvidx);
})
}
llrchannelsChanged(titles: string[])
{
this.selected_llrchannels = titles;
this.llrgraph_list.forEach(graph => {
const [enabled] = titles.filter(value => graph.title === value);
this.SendScopeParams("enabled", enabled ? "true" : "false", graph.srvidx);
})
}
SigChanged(value: number)
{
this.selected_sig.target_id = value;
if (this.scopetitle === "gNB")
this.scopesubtitle = " - sig from UE" + value.toString() + " antenna" + this.selected_sig.antenna_id;
else
this.scopesubtitle = " - sig from gNB" + value.toString() + " antenna" + this.selected_sig.antenna_id;
;
this.SendScopeParams("TargetSelect", value.toString(), 0);
}
WFChanged(value: string)
{
this.selected_WF = value;
for (let i = 0; i < this.WFgraph_list.length; i++) {
if (this.WFgraph_list[i].title === value) {
this.SendScopeParams("enabled", "true", this.WFgraph_list[i].srvidx);
this.WFDatasets[0].label = value;
this.WFDatasets[1].label = "(>avg*2)";
this.WFDatasets[2].label = "(>avg*10)";
this.WFDatasets[3].label = "(>avg*100)";
} else {
this.SendScopeParams("enabled", "false", this.WFgraph_list[i].srvidx);
}
}
for (let i = 0; i < this.WFDatasets.length; i++) {
this.nwf[i] = 0;
this.WFDatasets[i].data.length = 0;
}
}
onEnableTResp()
{
this.SendScopeParams("enabled", this.enable_TResp.toString(), this.TRespgraph.srvidx);
}
onDataACKchange()
{
this.SendScopeParams("DataAck", this.data_ACK.toString(), 0);
}
OnYthreshChange()
{
this.SendScopeParams("llrythresh", this.llrythresh.toString(), 0);
}
RefreshTResp()
{
this.TRespDatasets[0].data.length = 0;
this.trespchart!.update();
}
RefreshIQV()
{
this.IQDatasets.forEach((dataset) => {dataset.data.length = 0});
this.iqxmin = this.iqmin;
this.iqymin = this.iqmin;
this.iqxmax = this.iqmax;
this.iqymax = this.iqmax;
this.iqcchart!.update();
}
RefreshLLR()
{
this.LLRDatasets.forEach((dataset) => {dataset.data.length = 0});
this.llrxmin = this.llrmin;
this.llrxmax = this.llrmax;
this.llrchart!.update();
}
RefreshWF()
{
this.WFDatasets.forEach((dataset) => {dataset.data.length = 0});
this.nwf = [ 0, 0, 0, 0 ];
this.wfchart!.update();
}
}
@@ -0,0 +1,112 @@
import {UntypedFormArray, UntypedFormControl, UntypedFormGroup} from "@angular/forms";
import {Subscription} from "rxjs";
import {Observable} from "rxjs/internal/Observable";
import {ICommand, ICommandOptions, IQuestion} from "src/app/api/commands.api";
import {HelpApi, HelpRequest, HelpResp} from "src/app/api/help.api";
const enum CmdFCN {
name = "name",
vars = "variables",
confirm = "confirm",
answer = "answer"
}
export class CmdCtrl extends UntypedFormGroup {
confirm?: string;
question?: IQuestion;
cmdname: string;
options?: ICommandOptions[];
public ResUpdTimer?: Observable<number>;
public ResUpdTimerSubscriber?: Subscription;
updbtnname: string;
hlp_cmd: string = "";
constructor(cmd: ICommand)
{
super({});
this.addControl(CmdFCN.name, new UntypedFormControl(cmd.name));
this.addControl(CmdFCN.answer, new UntypedFormControl(""));
this.addControl(CmdFCN.vars, new UntypedFormArray([]));
this.confirm = cmd.confirm;
this.question = cmd.question;
this.cmdname = cmd.name;
this.options = cmd.options;
this.updbtnname = "Start update"
}
api()
{
const doc: ICommand = {
name : this.nameFC.value,
param : this.question ? {name : this.question!.pname, value : this.answerFC.value, type : this.question!.type, modifiable : false} : undefined,
options : this.options
};
return doc;
}
isResUpdatable(): boolean
{
if (this.options) {
for (let opt = 0; opt < this.options.length; opt++) {
if (this.options[opt] == ICommandOptions.update)
return true;
}
} else {
return false;
}
return false;
}
stopUpdate()
{
if (this.ResUpdTimerSubscriber) {
this.updbtnname = "Start update"
}
}
startUpdate()
{
if (this.ResUpdTimerSubscriber && this.ResUpdTimer) {
this.updbtnname = "Stop update"
}
}
get nameFC()
{
return this.get(CmdFCN.name) as UntypedFormControl;
}
set nameFC(fc: UntypedFormControl)
{
this.setControl(CmdFCN.name, fc);
}
get answerFC()
{
return this.get(CmdFCN.answer) as UntypedFormControl;
}
get varsFA()
{
return this.get(CmdFCN.vars) as UntypedFormArray;
}
set varsFA(fa: UntypedFormArray)
{
this.setControl(CmdFCN.vars, fa);
}
public get_cmd_help(helpApi: HelpApi, module: string)
{
if (this.options) {
for (let j = 0; j < this.options!.length; j++) {
if (this.options![j] == ICommandOptions.help) {
helpApi.getHelpText("cmd", module, this.cmdname).subscribe(resp => { this.hlp_cmd = resp; }, err => { this.hlp_cmd = ""; });
}
}
}
}
}
@@ -0,0 +1,82 @@
import {UntypedFormControl, UntypedFormGroup} from "@angular/forms";
import {IInfo} from "../api/commands.api";
import {IArgType} from "../api/commands.api";
const enum InfosFCN {
name = "name",
value = "value",
type = "type",
modifiable = "modifiable"
}
export class InfoCtrl extends UntypedFormGroup {
type: IArgType;
constructor(ivar: IInfo)
{
super({});
this.type = ivar.type;
this.addControl(InfosFCN.name, new UntypedFormControl(ivar.name));
this.addControl(InfosFCN.value, new UntypedFormControl(ivar.value));
this.addControl(InfosFCN.type, new UntypedFormControl(ivar.type));
this.addControl(InfosFCN.modifiable, new UntypedFormControl(ivar.modifiable));
}
api()
{
const doc: IInfo = {
name : this.nameFC.value,
value : String(this.valueFC.value), // FIXME
type : this.typeFC.value,
modifiable : this.modifiableFC.value
};
return doc;
}
get nameFC()
{
return this.get(InfosFCN.name) as UntypedFormControl;
}
set nameFC(control: UntypedFormControl)
{
this.setControl(InfosFCN.name, control);
}
get valueFC()
{
return this.get(InfosFCN.value) as UntypedFormControl;
}
set valueFC(control: UntypedFormControl)
{
this.setControl(InfosFCN.value, control);
}
get typeFC()
{
return this.get(InfosFCN.type) as UntypedFormControl;
}
set typeFC(control: UntypedFormControl)
{
this.setControl(InfosFCN.type, control);
}
get modifiableFC()
{
return this.get(InfosFCN.modifiable) as UntypedFormControl;
}
set modifiableFC(control: UntypedFormControl)
{
this.setControl(InfosFCN.modifiable, control);
}
get btnTxtFC()
{
if (this.type != IArgType.configfile)
return "set"
else return "download"
}
}
@@ -0,0 +1,39 @@
import {UntypedFormArray, UntypedFormGroup} from "@angular/forms";
import {IModule} from "../api/commands.api";
const enum ModuleFCN {
vars = "variables",
cmds = "commands"
}
export class ModuleCtrl extends UntypedFormGroup {
name: string
constructor(imodule: IModule)
{
super({});
this.name = imodule.name;
this.addControl(ModuleFCN.vars, new UntypedFormArray([]));
this.addControl(ModuleFCN.cmds, new UntypedFormArray([]));
}
get varsFA()
{
return this.get(ModuleFCN.vars) as UntypedFormArray;
}
set varsFA(fa: UntypedFormArray)
{
this.setControl(ModuleFCN.vars, fa);
}
get cmdsFA()
{
return this.get(ModuleFCN.cmds) as UntypedFormArray;
}
set cmdsFA(fa: UntypedFormArray)
{
this.setControl(ModuleFCN.cmds, fa);
}
}
@@ -0,0 +1,69 @@
import {UntypedFormControl, UntypedFormGroup} from "@angular/forms";
import {IArgType, IColumn, IParam} from "../api/commands.api";
enum ParamFCN {
value = "value",
}
export class ParamCtrl extends UntypedFormGroup {
col: IColumn
constructor(public param: IParam)
{
super({})
this.col = param.col
let control: UntypedFormControl
switch (param.col.type)
{
case IArgType.boolean:
control = new UntypedFormControl((param.value === "true") ? true : false);
break;
case IArgType.loglvl:
control = new UntypedFormControl(param.value);
break;
default:
control = new UntypedFormControl(param.value)
}
if (!param.col.modifiable)
control
.disable()
this.addControl(ParamFCN.value, control)
}
get valueFC()
{
return this.get(ParamFCN.value) as UntypedFormControl
}
set valueFC(fc: UntypedFormControl)
{
this.setControl(ParamFCN.value, fc);
}
api()
{
let value: string
switch (this.col.type)
{
case IArgType.boolean:
value = String(this.valueFC.value);
break;
default:
value = this.valueFC.value
}
const doc: IParam = {
value : value,
col : this.col
}
return doc
}
}
@@ -0,0 +1,54 @@
import {FormControl, UntypedFormArray, UntypedFormGroup} from "@angular/forms";
import {IArgType, IParam, IRow, IVariable} from "../api/commands.api";
import {ParamCtrl} from "./param.control";
enum RowFCN {
paramsFA = "params",
}
export class RowCtrl extends UntypedFormGroup {
cmdName: string;
rawIndex: number;
cmdparam?: IVariable;
constructor(row: IRow)
{
super({})
this.cmdName = row.cmdName;
this.rawIndex = row.rawIndex;
this.addControl(RowFCN.paramsFA, new UntypedFormArray(row.params.map(param => new ParamCtrl(param))));
}
get paramsFA()
{
return this.get(RowFCN.paramsFA) as UntypedFormArray
}
set paramsFA(fa: UntypedFormArray)
{
this.setControl(RowFCN.paramsFA, fa);
}
set_cmdparam(cmdparam: IVariable)
{
this.cmdparam = cmdparam;
}
get paramsCtrls(): ParamCtrl[]{return this.paramsFA.controls as ParamCtrl[]}
api()
{
const doc: IRow = {
rawIndex : this.rawIndex,
cmdName : this.cmdName,
params : this.paramsCtrls.map(control => control.api()),
param : this.cmdparam
}
return doc
}
}
@@ -0,0 +1,82 @@
import {UntypedFormControl, UntypedFormGroup} from "@angular/forms";
import {IInfo} from "../api/commands.api";
import {IArgType} from "../api/commands.api";
const enum VariablesFCN {
name = "name",
value = "value",
type = "type",
modifiable = "modifiable"
}
export class VarCtrl extends UntypedFormGroup {
type: IArgType;
constructor(ivar: IInfo)
{
super({});
this.type = ivar.type;
this.addControl(VariablesFCN.name, new UntypedFormControl(ivar.name));
this.addControl(VariablesFCN.value, new UntypedFormControl(ivar.value));
this.addControl(VariablesFCN.type, new UntypedFormControl(ivar.type));
this.addControl(VariablesFCN.modifiable, new UntypedFormControl(ivar.modifiable));
}
api()
{
const doc: IInfo = {
name : this.nameFC.value,
value : String(this.valueFC.value), // FIXME
type : this.typeFC.value,
modifiable : this.modifiableFC.value
};
return doc;
}
get nameFC()
{
return this.get(VariablesFCN.name) as UntypedFormControl;
}
set nameFC(control: UntypedFormControl)
{
this.setControl(VariablesFCN.name, control);
}
get valueFC()
{
return this.get(VariablesFCN.value) as UntypedFormControl;
}
set valueFC(control: UntypedFormControl)
{
this.setControl(VariablesFCN.value, control);
}
get typeFC()
{
return this.get(VariablesFCN.type) as UntypedFormControl;
}
set typeFC(control: UntypedFormControl)
{
this.setControl(VariablesFCN.type, control);
}
get modifiableFC()
{
return this.get(VariablesFCN.modifiable) as UntypedFormControl;
}
set modifiableFC(control: UntypedFormControl)
{
this.setControl(VariablesFCN.modifiable, control);
}
get btnTxtFC()
{
if (this.type != IArgType.configfile)
return "set"
else return "download"
}
}
@@ -0,0 +1,60 @@
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {Observable, throwError} from "rxjs";
import {catchError, tap} from "rxjs/operators";
import {DialogService as DialogService} from "../services/dialog.service";
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(public dialogService: DialogService)
{
}
intercept(request: HttpRequest<unknown>, next: HttpHandler)
{
return next.handle(request).pipe(
// tap((event: HttpEvent<any>) => {
// if (event instanceof HttpResponse) {
// switch (event.status) {
// case 200:
// case 201:
// this.log(GREEN, request.method + ' ' + event.status + ' Success');
// this.dialogService.openSnackBar(request.method + ' ' + event.status + ' Success');
// break;
// default:
// break;
// }
// // return throwError(event.body);
// }
// }),
catchError((error: HttpErrorResponse) => {
let prefix: string = "oai web interface [API]: ";
let message: string = " ";
switch (error.status) {
case 400:
case 403:
console.error(prefix + request.method + " " + error.status + " Error: " + error.error.toString());
break;
case 501:
case 500:
console.warn(prefix + request.method + " " + error.status + " Error: " + error.error.toString());
break;
default:
console.log(prefix + request.method + " " + error.status + " Error: " + error.error.toString());
break;
}
if (error.error instanceof ErrorEvent) {
message = error.error.message;
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong
message = JSON.stringify(error.error);
}
this.dialogService.openErrorDialog(prefix + " " + error.status, message);
return throwError(error);
}),
);
}
}
@@ -0,0 +1,8 @@
import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {ErrorInterceptor} from "./error.interceptor";
import {SpinnerInterceptor} from "./spinner.interceptor";
export const InterceptorProviders = [
{provide : HTTP_INTERCEPTORS, useClass : SpinnerInterceptor, multi : true},
{provide : HTTP_INTERCEPTORS, useClass : ErrorInterceptor, multi : true},
];
@@ -0,0 +1,33 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {finalize} from "rxjs/operators";
import {LoadingService} from "../services/loading.service";
@Injectable()
export class SpinnerInterceptor implements HttpInterceptor {
activeRequests = 0;
constructor(private loadingService: LoadingService)
{
}
intercept(request: HttpRequest<unknown>, next: HttpHandler)
{
if (this.activeRequests === 0) {
this.loadingService.startLoading();
}
this.activeRequests++;
return next.handle(request).pipe(
finalize(() => {
this.activeRequests--;
if (this.activeRequests === 0) {
this.loadingService.stopLoading();
}
}),
);
}
}
@@ -0,0 +1,150 @@
import {HttpErrorResponse} from "@angular/common/http";
import {ElementRef, Injectable, ViewChild} from "@angular/core";
import {NgModule} from "@angular/core";
import {MatDialog} from "@angular/material/dialog";
import {MatSnackBar} from "@angular/material/snack-bar";
import {BrowserModule} from "@angular/platform-browser";
import {Observable} from "rxjs";
import {of} from "rxjs";
import {tap} from "rxjs/operators";
import {CmdCtrl} from "src/app/controls/cmd.control";
import {ICommandOptions, IResp} from "../api/commands.api";
import {CommandsComponent} from "../components/commands/commands.component";
import {ConfirmDialogComponent} from "../components/confirm/confirm.component";
import {DialogComponent} from "../components/dialog/dialog.component";
import {QuestionDialogComponent} from "../components/question/question.component";
@Injectable({
providedIn : "root",
})
export class DialogService {
public isDialogOpen = false;
cmdDialogRef: any;
constructor(
private _dialog: MatDialog,
private _snackBar: MatSnackBar,
)
{
}
openErrorDialog(title: string, message: string): void
{
const ErrDiag = this._dialog.open(DialogComponent, {
width : "900px",
data : {
title : title,
body : message,
},
panelClass : "errRespDialog",
});
ErrDiag.afterClosed().subscribe(result => {});
}
openCmdDialog(control: CmdCtrl, resp: IResp, title?: string): Observable<IResp>
{
this.isDialogOpen = true;
console.log("Open Cmd dialog");
var updatable = false;
if (control.options) {
for (let opt = 0; opt < control.options.length; opt++) {
if (control.options[opt] == ICommandOptions.update)
updatable = true;
}
}
this.cmdDialogRef = this._dialog.open(DialogComponent, {
height : "80%",
hasBackdrop : false,
data : {
control : control,
title : title,
body : resp.display!.join("</p><p>"),
updatable : updatable,
},
panelClass : "cmdRespDialog",
});
this.cmdDialogRef.afterClosed().subscribe(() => {
console.log("The dialog was closed");
this.isDialogOpen = false;
control.stopUpdate();
if (control.ResUpdTimerSubscriber) {
control.ResUpdTimerSubscriber.unsubscribe();
}
});
return of(resp)
}
updateCmdDialog(control: CmdCtrl, resp: IResp, title?: string): Observable<IResp>
{
if (this.cmdDialogRef && this.isDialogOpen && resp.display.length) {
this.cmdDialogRef.componentInstance.data = {control : control, title : title, body : resp.display!.join("</p><p>"), updatable : true};
return of(resp);
} else {
return this.openCmdDialog(control, resp, title);
}
}
openVarRespDialog(resp: IResp): Observable<IResp>
{
if (this.isDialogOpen || !resp.display.length) {
return of(resp);
}
console.log("Open Var dialog");
this.isDialogOpen = true;
const dialogRef = this._dialog.open(DialogComponent, {
width : "900px",
hasBackdrop : true,
data : {
title : resp.display![0],
},
panelClass : "varRespDialog",
});
dialogRef.afterClosed().subscribe(() => {
console.log("The dialog was closed");
this.isDialogOpen = false;
});
return of(resp)
}
openSnackBar(title: string): void
{
this._snackBar.open(title, undefined, {
duration : 500,
horizontalPosition : "center",
verticalPosition : "bottom",
});
}
openConfirmDialog(question: string)
{
if (this.isDialogOpen) {
return of(undefined);
}
this.isDialogOpen = true;
return this._dialog.open(ConfirmDialogComponent, {width : "300px", data : {title : question}}).afterClosed().pipe(tap(() => this.isDialogOpen = false));
}
openQuestionDialog(title: string, control: CmdCtrl)
{
if (this.isDialogOpen) {
return of(control);
}
this.isDialogOpen = true;
const dialogRef = this._dialog.open(QuestionDialogComponent, {
width : "300px",
data : {title : title, control : control},
panelClass : "questionDialog",
})
return dialogRef.afterClosed().pipe(tap(() => this.isDialogOpen = false));
}
}
@@ -0,0 +1,33 @@
import {HttpClient} from "@angular/common/http";
import {HttpHeaders} from "@angular/common/http";
import {HttpParams} from "@angular/common/http";
import {Injectable} from "@angular/core";
import {route} from "src/app/api/commands.api";
import {environment} from "src/environments/environment";
@Injectable({
providedIn : "root",
})
export class DownloadService {
constructor(private http: HttpClient)
{
}
getFile(url: string)
{
const token = "my JWT";
const headers = new HttpHeaders().set("authorization", "Bearer " + token);
const postparams = new HttpParams().set("fname", url);
this.http.post(environment.backend + route + "/file", postparams, {headers, responseType : "blob" as "json"}).subscribe((response: any) => {
let dataType = response.type;
let binaryData = [];
binaryData.push(response);
let downloadLink = document.createElement("a");
downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, {type : dataType}));
downloadLink.setAttribute("download", url);
document.body.appendChild(downloadLink);
downloadLink.click();
})
}
};
@@ -0,0 +1,23 @@
import {Injectable} from "@angular/core";
import {Subject} from "rxjs";
@Injectable({
providedIn : "root",
})
export class LoadingService {
isLoading$ = new Subject<boolean>();
constructor()
{
}
startLoading()
{
this.isLoading$.next(true);
}
stopLoading()
{
this.isLoading$.next(false);
}
}
@@ -0,0 +1,74 @@
import {Injectable} from "@angular/core";
import {webSocket, WebSocketSubject} from "rxjs/webSocket";
import {environment} from "src/environments/environment";
export enum webSockSrc {
softscope = "s".charCodeAt(0),
logview = "l".charCodeAt(0),
}
export interface Message {
source: webSockSrc;
fullbuff: ArrayBuffer;
}
export const arraybuf_data_offset = 8; // 64 bits (8 bytes) header
const deserialize = (fullbuff: ArrayBuffer):
Message => {
const header = new DataView(fullbuff, 0, arraybuf_data_offset); // header
return {source : header.getUint8(0), fullbuff : fullbuff};
}
const serialize = (msg: Message):
ArrayBuffer => {
let buffview = new DataView(msg.fullbuff);
buffview.setUint8(0, msg.source); // header
return buffview.buffer;
}
@Injectable() export class WebSocketService {
public subject$: WebSocketSubject<Message>;
constructor()
{
this.subject$ = webSocket<Message>({
url : environment.backend.replace("http", "ws") + "softscope",
openObserver : {next : () => { console.log("WS connection established") }},
closeObserver : {next : () => { console.log("WS connextion closed") }},
serializer : msg => serialize(msg),
deserializer : msg => deserialize(msg.data),
binaryType : "arraybuffer"
});
}
// public get scopeSubject$() {
// return this.subject$.multiplex(
// () => { console.log('WS scope2 connection established') },
// () => { console.log('WS scope2 connection closed') },
// msg => msg.source === webSockSrc.softscope,
// );
// }
// public get loggerSubject$() {
// return this.subject$.multiplex(
// () => { console.log('WS logger connection established') },
// () => { console.log('WS logger connection closed') },
// msg => msg.source === webSockSrc.logview,
// );
// }
public send(msg: Message)
{
console.log("Message sent to websocket: ", msg.fullbuff);
this.subject$.next(msg);
}
// public close() {
// this.subject$.complete();
// }
// public error() {
// this.subject$.error({ code: 4000, reason: 'I think our app just broke!' });
// }
}
@@ -0,0 +1,8 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production : true,
backend : window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/"
};
@@ -0,0 +1,18 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production : false,
// backend: 'localhost:8090/',
backend : "http://oaifh.devhost:8090/" // define oaifh.devhost host in your linux /etc/hosts or the windows equivalent PLEASE NO HARDCODED IP HERE
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>oai softmodem</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
</head>
<body class="mat-typography">
<app-root></app-root>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
import {enableProdMode} from "@angular/core";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {AppModule} from "./app/app.module";
import {environment} from "./environments/environment";
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule).catch((err) => console.error(err));
@@ -0,0 +1,52 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import "zone.js/dist/zone"; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
@@ -0,0 +1,10 @@
/* You can add global styles to this file, and also import other style files */
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: Roboto, 'Helvetica Neue', sans-serif;
}
+20
View File
@@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import "zone.js/dist/zone-testing";
import {getTestBed} from "@angular/core/testing";
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp,
): {keys(): string[];<T>(id: string) : T;};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context("./", true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);