Browse Source

ui start 2.5.5

main
gsd 4 months ago
parent
commit
46dcd47c33
  1. 2
      ui/src/app/app-routing.module.ts
  2. 5
      ui/src/app/app.component.html
  3. 29
      ui/src/app/app.component.ts
  4. 7
      ui/src/app/app.module.ts
  5. 30
      ui/src/app/auth/AuthInterceptor.ts
  6. 64
      ui/src/app/components/messages/MessageHistory.component.ts
  7. 44
      ui/src/app/components/nodes/DirectNodes.component.ts
  8. 3
      ui/src/app/entities/KeyValueMap.ts
  9. 11
      ui/src/app/entities/MessageDTO.ts

2
ui/src/app/app-routing.module.ts

@ -2,9 +2,11 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import {DirectNodesComponent} from "./components/nodes/DirectNodes.component"; import {DirectNodesComponent} from "./components/nodes/DirectNodes.component";
import {BotCommandsComponent} from "./components/botCommands/BotCommands.component"; import {BotCommandsComponent} from "./components/botCommands/BotCommands.component";
import {MessageHistoryComponent} from "./components/messages/MessageHistory.component";
const routes: Routes = [ const routes: Routes = [
{path: "nodes/direct", component: DirectNodesComponent}, {path: "nodes/direct", component: DirectNodesComponent},
{path: "messages", component: MessageHistoryComponent},
{path: "", component: BotCommandsComponent} {path: "", component: BotCommandsComponent}
]; ];

5
ui/src/app/app.component.html

@ -4,9 +4,10 @@
</button> </button>
<span>MeshCenter</span> <span>MeshCenter</span>
<span class="spacer"></span> <span class="spacer"></span>
<p>Имя ноды чела который авторизовался (мб еще snnr rxxi шуе)</p> <p *ngIf="userNode">{{userNode.long_name}} ({{userNode.num}}) snr: {{userNode.snr}}</p>
<button *ngIf="userNode" mat-button (click)="logout()">Выйти</button>
</mat-toolbar> </mat-toolbar>
<mat-drawer-container class="container" autosize> <mat-drawer-container class="container" autosize style="height: calc(100% - 36px)">
<mat-drawer #drawer class="sidenav" mode="over"> <mat-drawer #drawer class="sidenav" mode="over">
<div style="width: 100px"> <div style="width: 100px">
<p class="routeUrl" *ngFor="let u of routes" [routerLink]="u.url" (click)="goTo(u.url)">{{u.name}}</p> <p class="routeUrl" *ngFor="let u of routes" [routerLink]="u.url" (click)="goTo(u.url)">{{u.name}}</p>

29
ui/src/app/app.component.ts

@ -1,23 +1,40 @@
import { Component } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {Route, Router} from "@angular/router"; import {Route, Router} from "@angular/router";
import {NodeDTO} from "./entities/NodeDTO";
import {HttpClient} from "@angular/common/http";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent { export class AppComponent implements OnInit {
constructor(private route: Router) { constructor(private route: Router,
private http: HttpClient) {
} }
ngOnInit() {
this.http.get(`api/auth/me`).subscribe(
(res) => this.userNode = res as NodeDTO
)
}
userNode!: NodeDTO
routes: {name: string, url: string}[] = [ routes: {name: string, url: string}[] = [
{name: "Команды бота", url:""}, //{name: "Команды бота", url:""},
{name: "История сообщений", url:""}, {name: "История сообщений", url:"messages"},
{name: "Прямые ноды", url:"nodes/direct"}, {name: "Прямые ноды", url:"nodes/direct"},
{name: "Все ноды", url:""}, //{name: "Все ноды", url:""},
] ]
goTo(url: string) { goTo(url: string) {
this.route.navigate([url]); this.route.navigate([url]);
} }
logout() {
this.http.get(`api/auth/logout`).subscribe(
() => location.reload()
)
}
} }

7
ui/src/app/app.module.ts

@ -11,7 +11,7 @@ import {MatButtonModule} from "@angular/material/button";
import {HttpClientModule, HTTP_INTERCEPTORS} from "@angular/common/http"; import {HttpClientModule, HTTP_INTERCEPTORS} from "@angular/common/http";
import {BotCommandsComponent} from "./components/botCommands/BotCommands.component"; import {BotCommandsComponent} from "./components/botCommands/BotCommands.component";
import {MessageHistoryComponent} from "./components/messages/MessageHistory.component"; import {MessageHistoryComponent} from "./components/messages/MessageHistory.component";
import {DirectNodesComponent} from "./components/nodes/DirectNodes.component"; import {DirectNodesComponent, NodeDtoSortPipe} from "./components/nodes/DirectNodes.component";
import {AllNodesComponent} from "./components/nodes/AllNodes.component"; import {AllNodesComponent} from "./components/nodes/AllNodes.component";
import {AuthDialog, AuthInterceptor} from "./auth/AuthInterceptor"; import {AuthDialog, AuthInterceptor} from "./auth/AuthInterceptor";
import {MatDialogModule} from "@angular/material/dialog"; import {MatDialogModule} from "@angular/material/dialog";
@ -22,6 +22,7 @@ import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {MatSnackBarModule} from "@angular/material/snack-bar"; import {MatSnackBarModule} from "@angular/material/snack-bar";
import {MatDividerModule} from "@angular/material/divider"; import {MatDividerModule} from "@angular/material/divider";
import {MatCardModule} from "@angular/material/card"; import {MatCardModule} from "@angular/material/card";
import {MatSelectModule} from "@angular/material/select";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -49,7 +50,9 @@ import {MatCardModule} from "@angular/material/card";
FormsModule, FormsModule,
MatSnackBarModule, MatSnackBarModule,
MatDividerModule, MatDividerModule,
MatCardModule MatCardModule,
NodeDtoSortPipe,
MatSelectModule
], ],
providers: [{ providers: [{
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,

30
ui/src/app/auth/AuthInterceptor.ts

@ -12,6 +12,7 @@ import {MatDialog, MatDialogRef} from "@angular/material/dialog";
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
import {NodeMiniDTO} from "../entities/NodeMiniDTO"; import {NodeMiniDTO} from "../entities/NodeMiniDTO";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar} from "@angular/material/snack-bar";
import {D} from "@angular/cdk/keycodes";
@Component({ @Component({
@ -22,7 +23,7 @@ import {MatSnackBar} from "@angular/material/snack-bar";
<mat-tab-group> <mat-tab-group>
<mat-tab label="Через поиск"> <mat-tab label="Через поиск">
<div> <div>
<div> <div style="border-radius: 1px; border: 1px solid black">
<mat-form-field appearance="fill" style="width: 100%"> <mat-form-field appearance="fill" style="width: 100%">
<mat-label>Имя ноды</mat-label> <mat-label>Имя ноды</mat-label>
<input <input
@ -38,15 +39,14 @@ import {MatSnackBar} from "@angular/material/snack-bar";
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>
<button mat-button mat-raised-button (click)="sendCodeToNode()">Отправить код на ноду</button> <button mat-button mat-stroked-button-button (click)="sendCodeToNode()" style="width: 100%;">Отправить код на ноду</button>
</div> </div>
<mat-divider></mat-divider> <div [hidden]="!showAcceptCode" style="border-radius: 1px; border: 1px solid black; margin-top: 5px">
<div> <mat-form-field style="width: 100%" appearance="fill">
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Код из сообщения</mat-label> <mat-label>Код из сообщения</mat-label>
<input [(ngModel)]="code" matInput maxlength="4" placeholder="Код из сообщения"> <input [(ngModel)]="code" matInput maxlength="4" placeholder="Код из сообщения">
</mat-form-field> </mat-form-field>
<button mat-button (click)="acceptCode()">Проверить код</button> <button mat-button (click)="acceptCode()" style="width: 100%">Проверить код</button>
</div> </div>
</div> </div>
</mat-tab> </mat-tab>
@ -74,6 +74,8 @@ export class AuthDialog implements OnInit {
myControl = new FormControl(); myControl = new FormControl();
foundedNodes: Observable<NodeMiniDTO[]> = new Observable<NodeMiniDTO[]>() foundedNodes: Observable<NodeMiniDTO[]> = new Observable<NodeMiniDTO[]>()
code!: number code!: number
showAcceptCode: boolean = false;
lastCodeSend: number = 0;
acceptCode() { acceptCode() {
this.http.get(`api/auth/code/check?code=${this.code}`) this.http.get(`api/auth/code/check?code=${this.code}`)
@ -81,6 +83,7 @@ export class AuthDialog implements OnInit {
.subscribe((res:{status:boolean}) => { .subscribe((res:{status:boolean}) => {
if (res.status) { if (res.status) {
this.dialogRef.close() this.dialogRef.close()
location.reload()
} else { } else {
this.snack.open("Неправильный код") this.snack.open("Неправильный код")
} }
@ -94,8 +97,19 @@ export class AuthDialog implements OnInit {
sendCodeToNode() { sendCodeToNode() {
let node:NodeMiniDTO = this.myControl.value as NodeMiniDTO let node:NodeMiniDTO = this.myControl.value as NodeMiniDTO
if (node == null) {
this.snack.open("Сначала надо выбрать свою ноду");
return;
}
this.http.get(`api/auth/code?num=${node.num}`).subscribe( this.http.get(`api/auth/code?num=${node.num}`).subscribe(
(res) => this.snack.open(`Код отправлен на ноду ${node.long_name}`) (res) => {
this.lastCodeSend = new Date().getTime()/1000;
this.snack.open(`Код отправлен на ноду ${node.long_name}`);
this.showAcceptCode = true;
}, (err) => {
this.snack.open("Возможно стоит подождать перд отправкой следующего кода")
}
) )
} }
} }
@ -109,7 +123,7 @@ export class AuthInterceptor implements HttpInterceptor {
return next.handle(req).pipe( return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => { catchError((error: HttpErrorResponse) => {
if (error.status === 401 && !this.dialogOpened) { if (error.status === 401 && !this.dialogOpened) {
const ref = this.dialog.open(AuthDialog, {disableClose: true, width:"50%"}) const ref = this.dialog.open(AuthDialog, {disableClose: true, width:"30%"})
ref.afterClosed().subscribe((res:any) => this.dialogOpened = false) ref.afterClosed().subscribe((res:any) => this.dialogOpened = false)
} }
return throwError(() => error); return throwError(() => error);

64
ui/src/app/components/messages/MessageHistory.component.ts

@ -1,9 +1,67 @@
import {Component} from "@angular/core"; import {Component, OnInit} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {MessageDTO} from "../../entities/MessageDTO";
import {NodeDTO} from "../../entities/NodeDTO";
import {KeyValueMap} from "../../entities/KeyValueMap";
import {Subscription} from "rxjs";
@Component({ @Component({
selector: "app-message-history", selector: "app-message-history",
template: `` template: `
<div>
<div style="height: 100%; overflow-y: scroll">
<button [disabled]="!canLoadMoreMessage || loading" mat-button mat-stroked-button style="width: 100%;" (click)="getMessages()">Загрузить еще...</button>
<mat-card *ngFor="let msg of messages" style="margin-top: 4px">
<mat-card-subtitle>{{knownNodes[msg.from.toString()] == null ? msg.from : knownNodes[msg.from.toString()].long_name}} | {{msg.ts * 1000 | date: 'HH:mm:ss dd.MM.yyyy'}} | {{msg.hop_start - msg.hop_limit == 0 ? 'напрямую' : (msg.hop_start - msg.hop_limit) + ' хопов'}} | rssi: {{msg.rx_rssi}} snr: {{msg.rx_snr}}</mat-card-subtitle>
<mat-card-content>{{msg.decoded_payload}}</mat-card-content>
</mat-card>
</div>
</div>
`
}) })
export class MessageHistoryComponent { export class MessageHistoryComponent implements OnInit {
constructor(private http: HttpClient) {}
loading: boolean = false;
canLoadMoreMessage: boolean = true;
messages: MessageDTO[] = []
offset = 0;
limit = 10;
knownNodes:KeyValueMap<NodeDTO> = {}
ngOnInit() {
this.getMessages();
}
tryKnownNodes(nums: number[]) {
let notKnownNodes = nums.filter(num => Object.keys(this.knownNodes).indexOf(`${num}`) == -1)
if (notKnownNodes.length == 0) return Subscription.EMPTY;
let params = notKnownNodes.length > 1 ? notKnownNodes.join("&nums=") : `$nums=${notKnownNodes.pop()}`;
return this.http.get(`api/nodes?s=1${params}`).subscribe(
(res) => {
(res as NodeDTO[]).forEach(
(node) => this.knownNodes[`${node.num}`] = node
)
}
)
}
getMessages() {
this.loading = true;
this.http.get(`api/messages?offset=${this.offset}&limit=${this.limit}`)
.subscribe((res) => {
let new_msgs = res as MessageDTO[]
this.tryKnownNodes(new_msgs.map(msg => msg.from)).add(
() => {
this.messages = this.messages.concat(new_msgs)
.sort((m1, m2) => m1.ts - m2.ts);
let msgSizes = (res as MessageDTO[]).length
if (msgSizes == 0) this.canLoadMoreMessage = false;
this.offset += msgSizes;
}
)
this.loading = false;
}, (err) => {this.loading = false;})
}
} }

44
ui/src/app/components/nodes/DirectNodes.component.ts

@ -2,25 +2,36 @@ import {Component, OnInit} from "@angular/core";
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {NodeDTO} from "../../entities/NodeDTO"; import {NodeDTO} from "../../entities/NodeDTO";
//todo abs this
@Component({ @Component({
selector: 'app-direct-nodes', selector: 'app-direct-nodes',
styleUrls: ['nodes.styles.scss'], styleUrls: ['nodes.styles.scss'],
template: ` template: `
<!--<p *ngFor="let node of nodes">{{node.long_name}}</p>--> <!--<p *ngFor="let node of nodes">{{node.long_name}}</p>-->
<div style="width: 80%; padding: 0 10%; padding-top: 8px"> <div style="width: 80%; padding: 0 10%; padding-top: 8px">
<div style="display: flex; justify-content: start">
<mat-form-field appearance="fill" style="width: 250px">
<mat-label>Сортировать по ...</mat-label>
<mat-select [(ngModel)]="sort" >
<mat-option *ngFor="let s of sortVars" [value]="s">
{{s.name}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="card-wrapper"> <div class="card-wrapper">
<mat-card *ngFor="let node of nodes"> <mat-card *ngFor="let node of nodes | NodeDtoSort: sort.type">
<mat-card-header> <mat-card-header>
<mat-card-title>{{node.long_name}}</mat-card-title> <mat-card-title>{{node.long_name}}</mat-card-title>
<mat-card-subtitle>{{node.short_name}} ({{node.num}})</mat-card-subtitle> <mat-card-subtitle>{{node.short_name}} ({{node.num}})</mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-card-content> <!--<mat-card-content>
<p>график</p>
</mat-card-content> </mat-card-content>-->
<mat-card-actions> <mat-card-actions>
<button mat-button *ngIf="node.hops_away > 0">Прыжков: {{node.hops_away}}</button> <button mat-button *ngIf="node.hops_away > 0">Прыжков: {{node.hops_away}}</button>
<button mat-button>SNR: {{node.snr}}</button> <button mat-button>SNR: {{node.snr}}</button>
<button mat-button>{{node.ts * 1000 | date:"hh:mm dd.MM.yyyy"}}</button> <button mat-button>{{node.ts * 1000 | date:"HH:mm dd.MM.yyyy"}}</button>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>
</div> </div>
@ -32,6 +43,12 @@ export class DirectNodesComponent implements OnInit {
} }
nodes: NodeDTO[] = []; nodes: NodeDTO[] = [];
sortVars:{name: string, type: string}[] = [
{name: "Последнему пингу", type: "ts"},
{name: "SNR", type: "snr"},
//{name: "Имени", type: "long_name"}
]
sort: {name: string, type: string} = this.sortVars[0]
ngOnInit(): void { ngOnInit(): void {
this.http.get(`api/nodes/direct`).subscribe( this.http.get(`api/nodes/direct`).subscribe(
@ -39,3 +56,20 @@ export class DirectNodesComponent implements OnInit {
) )
} }
} }
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'NodeDtoSort',
standalone: true
})
export class NodeDtoSortPipe implements PipeTransform {
transform(values: NodeDTO[], field: string = ""): NodeDTO[] {
if (field == "") return values;
return values.sort((n1,n2) =>
{
// @ts-ignore
return n2[field] - n1[field]
});
}
}

3
ui/src/app/entities/KeyValueMap.ts

@ -0,0 +1,3 @@
export interface KeyValueMap<T> {
[key: string]: T;
}

11
ui/src/app/entities/MessageDTO.ts

@ -0,0 +1,11 @@
export interface MessageDTO {
from: number;
to: number;
rx_snr: number;
hop_limit: number;
rx_rssi: number;
hop_start: number;
ts: number;
decoded_payload: string;
want_ack: boolean;
}
Loading…
Cancel
Save