Initial web server implementation
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": [
|
||||
"projects/**/*"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"common/utils/websrv/frontend/tsconfig.json"
|
||||
],
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/recommended",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
],
|
||||
"rules": {
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "attribute",
|
||||
"prefix": "app",
|
||||
"style": "camelCase"
|
||||
}
|
||||
],
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "element",
|
||||
"prefix": "app",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/template/recommended"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/artifacts
|
||||
/builds
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
config.toml
|
||||
@@ -0,0 +1 @@
|
||||
# SoftModemNgx
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"cli": {
|
||||
"analytics": false,
|
||||
"schematicCollections": [
|
||||
"@angular-eslint/schematics"
|
||||
]
|
||||
},
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"softmodemngx": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/softmodem-ngx",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"./node_modules/@angular/material/prebuilt-themes/purple-green.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"allowedCommonJsDependencies": [
|
||||
"@firebase/auth",
|
||||
"firebase"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1.5mb",
|
||||
"maximumError": "2mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "softmodemngx:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "softmodemngx:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "softmodemngx:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const {SpecReporter, StacktraceOption} = require("jasmine-spec-reporter");
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout : 11000,
|
||||
specs : [ "./src/**/*.e2e-spec.ts" ],
|
||||
capabilities : {browserName : "chrome", chromeOptions : {args : [ "--headless", "--no-sandbox" ]}},
|
||||
directConnect : true,
|
||||
SELENIUM_PROMISE_MANAGER : true,
|
||||
baseUrl : "http://localhost:4200/",
|
||||
framework : "jasmine",
|
||||
jasmineNodeOpts : {showColors : true, defaultTimeoutInterval : 30000, print : function() {}},
|
||||
onPrepare() {
|
||||
require("ts-node").register({project : require("path").join(__dirname, "./tsconfig.json")});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({spec : {displayStacktrace : StacktraceOption.PRETTY}}));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const {SpecReporter, StacktraceOption} = require("jasmine-spec-reporter");
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout : 11000,
|
||||
specs : [ "./src/**/*.e2e-spec.ts" ],
|
||||
capabilities : {browserName : "chrome"},
|
||||
directConnect : true,
|
||||
SELENIUM_PROMISE_MANAGER : false,
|
||||
baseUrl : "http://localhost:4200/",
|
||||
framework : "jasmine",
|
||||
jasmineNodeOpts : {showColors : true, defaultTimeoutInterval : 30000, print : function() {}},
|
||||
onPrepare() {
|
||||
require("ts-node").register({project : require("path").join(__dirname, "./tsconfig.json")});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({spec : {displayStacktrace : StacktraceOption.PRETTY}}));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import {browser, logging} from "protractor";
|
||||
import {AppPage} from "./app.po";
|
||||
|
||||
describe("workspace-project App", () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => { page = new AppPage(); });
|
||||
|
||||
// it('should display welcome message', async () => {
|
||||
// await page.navigateTo();
|
||||
// expect(await page.getTitleText()).toEqual('example app is running!');
|
||||
// });
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level : logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import {browser, by, element} from "protractor";
|
||||
|
||||
export class AppPage {
|
||||
async navigateTo(): Promise<unknown>
|
||||
{
|
||||
return browser.get(browser.baseUrl);
|
||||
}
|
||||
|
||||
async getTitleText(): Promise<string>
|
||||
{
|
||||
return element(by.css("app-root .content span")).getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"types": ["jasmine", "node"]
|
||||
}
|
||||
}
|
||||
+24319
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "softmodemngx",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"lint": "ng lint",
|
||||
"build": "ng build",
|
||||
"dev": "ng serve --configuration=development",
|
||||
"prod": "ng serve --configuration=production"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular-slider/ngx-slider": "^2.0.4",
|
||||
"@angular/animations": "^14.1.0",
|
||||
"@angular/cdk": "^14.1.0",
|
||||
"@angular/common": "^14.1.0",
|
||||
"@angular/compiler": "^14.1.0",
|
||||
"@angular/core": "^14.1.0",
|
||||
"@angular/flex-layout": "^14.0.0-beta.40",
|
||||
"@angular/forms": "^14.1.0",
|
||||
"@angular/material": "^14.1.0",
|
||||
"@angular/platform-browser": "^14.1.0",
|
||||
"@angular/platform-browser-dynamic": "^14.1.0",
|
||||
"@angular/router": "^14.1.0",
|
||||
"chart.js": "^3.8.2",
|
||||
"moment": "^2.29.3",
|
||||
"ng2-charts": "^4.0.0",
|
||||
"rxjs": "^6.6.6",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "^0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/architect": "^0.1401.0",
|
||||
"@angular-devkit/build-angular": "^14.1.0",
|
||||
"@angular-eslint/builder": "14.0.2",
|
||||
"@angular-eslint/eslint-plugin": "14.0.2",
|
||||
"@angular-eslint/eslint-plugin-template": "14.0.2",
|
||||
"@angular-eslint/schematics": "14.0.2",
|
||||
"@angular-eslint/template-parser": "14.0.2",
|
||||
"@angular/cli": "^14.1.0",
|
||||
"@angular/compiler-cli": "^14.1.0",
|
||||
"@angular/language-service": "^14.1.0",
|
||||
"@types/node": "^12.20.23",
|
||||
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
||||
"@typescript-eslint/parser": "^5.29.0",
|
||||
"eslint": "^8.18.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"fuzzy": "^0.1.3",
|
||||
"husky": "^5.1.3",
|
||||
"inquirer": "^6.2.2",
|
||||
"inquirer-autocomplete-prompt": "^1.0.1",
|
||||
"jasmine-core": "~3.7.1",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"open": "^7.0.3",
|
||||
"ts-node": "^8.10.2",
|
||||
"tslint-angular": "^3.0.3",
|
||||
"typescript": "~4.6.4"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "pretty-quick --staged && ng lint && ng test",
|
||||
"pre-push": "ng build --aot true"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }} msg skipped: {{ skippedmsg }} msg buffered: {{ bufferedmsg }} </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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts", "src/polyfills.ts"],
|
||||
"include": ["src/**/*.d.ts"]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": ["jasmine"]
|
||||
},
|
||||
"files": ["src/test.ts", "src/polyfills.ts"],
|
||||
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user