From 46dcd47c33e24456caa8543f8ab4fe5493750dd2 Mon Sep 17 00:00:00 2001 From: gsd Date: Wed, 11 Feb 2026 18:24:38 +0300 Subject: [PATCH] ui start 2.5.5 --- ui/src/app/app-routing.module.ts | 2 + ui/src/app/app.component.html | 5 +- ui/src/app/app.component.ts | 29 +++++++-- ui/src/app/app.module.ts | 7 +- ui/src/app/auth/AuthInterceptor.ts | 30 ++++++--- .../messages/MessageHistory.component.ts | 64 ++++++++++++++++++- .../components/nodes/DirectNodes.component.ts | 44 +++++++++++-- ui/src/app/entities/KeyValueMap.ts | 3 + ui/src/app/entities/MessageDTO.ts | 11 ++++ 9 files changed, 169 insertions(+), 26 deletions(-) create mode 100644 ui/src/app/entities/KeyValueMap.ts create mode 100644 ui/src/app/entities/MessageDTO.ts diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index 06a8d29..2262358 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -2,9 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import {DirectNodesComponent} from "./components/nodes/DirectNodes.component"; import {BotCommandsComponent} from "./components/botCommands/BotCommands.component"; +import {MessageHistoryComponent} from "./components/messages/MessageHistory.component"; const routes: Routes = [ {path: "nodes/direct", component: DirectNodesComponent}, + {path: "messages", component: MessageHistoryComponent}, {path: "", component: BotCommandsComponent} ]; diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index ab34212..37c00e4 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -4,9 +4,10 @@ MeshCenter -

Имя ноды чела который авторизовался (мб еще snnr rxxi шуе)

+

{{userNode.long_name}} ({{userNode.num}}) snr: {{userNode.snr}}

+ - +

{{u.name}}

diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 3288fd8..3674400 100644 --- a/ui/src/app/app.component.ts +++ b/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 {NodeDTO} from "./entities/NodeDTO"; +import {HttpClient} from "@angular/common/http"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent { - constructor(private route: Router) { +export class AppComponent implements OnInit { + 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}[] = [ - {name: "Команды бота", url:""}, - {name: "История сообщений", url:""}, + //{name: "Команды бота", url:""}, + {name: "История сообщений", url:"messages"}, {name: "Прямые ноды", url:"nodes/direct"}, - {name: "Все ноды", url:""}, + //{name: "Все ноды", url:""}, ] goTo(url: string) { this.route.navigate([url]); } + + logout() { + this.http.get(`api/auth/logout`).subscribe( + () => location.reload() + ) + } } diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 5204118..bbd2447 100644 --- a/ui/src/app/app.module.ts +++ b/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 {BotCommandsComponent} from "./components/botCommands/BotCommands.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 {AuthDialog, AuthInterceptor} from "./auth/AuthInterceptor"; import {MatDialogModule} from "@angular/material/dialog"; @@ -22,6 +22,7 @@ import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {MatSnackBarModule} from "@angular/material/snack-bar"; import {MatDividerModule} from "@angular/material/divider"; import {MatCardModule} from "@angular/material/card"; +import {MatSelectModule} from "@angular/material/select"; @NgModule({ declarations: [ @@ -49,7 +50,9 @@ import {MatCardModule} from "@angular/material/card"; FormsModule, MatSnackBarModule, MatDividerModule, - MatCardModule + MatCardModule, + NodeDtoSortPipe, + MatSelectModule ], providers: [{ provide: HTTP_INTERCEPTORS, diff --git a/ui/src/app/auth/AuthInterceptor.ts b/ui/src/app/auth/AuthInterceptor.ts index 5986a88..c3b104b 100644 --- a/ui/src/app/auth/AuthInterceptor.ts +++ b/ui/src/app/auth/AuthInterceptor.ts @@ -12,6 +12,7 @@ import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import {FormControl} from "@angular/forms"; import {NodeMiniDTO} from "../entities/NodeMiniDTO"; import {MatSnackBar} from "@angular/material/snack-bar"; +import {D} from "@angular/cdk/keycodes"; @Component({ @@ -22,7 +23,7 @@ import {MatSnackBar} from "@angular/material/snack-bar";
-
+
Имя ноды - +
- -
- +
+ Код из сообщения - +
@@ -74,6 +74,8 @@ export class AuthDialog implements OnInit { myControl = new FormControl(); foundedNodes: Observable = new Observable() code!: number + showAcceptCode: boolean = false; + lastCodeSend: number = 0; acceptCode() { this.http.get(`api/auth/code/check?code=${this.code}`) @@ -81,6 +83,7 @@ export class AuthDialog implements OnInit { .subscribe((res:{status:boolean}) => { if (res.status) { this.dialogRef.close() + location.reload() } else { this.snack.open("Неправильный код") } @@ -94,8 +97,19 @@ export class AuthDialog implements OnInit { sendCodeToNode() { 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( - (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( catchError((error: HttpErrorResponse) => { 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) } return throwError(() => error); diff --git a/ui/src/app/components/messages/MessageHistory.component.ts b/ui/src/app/components/messages/MessageHistory.component.ts index df572d7..adfa53e 100644 --- a/ui/src/app/components/messages/MessageHistory.component.ts +++ b/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({ selector: "app-message-history", - template: `` + template: ` +
+
+ + + {{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}} + {{msg.decoded_payload}} + +
+
+ ` }) -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 = {} + + 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;}) + } } diff --git a/ui/src/app/components/nodes/DirectNodes.component.ts b/ui/src/app/components/nodes/DirectNodes.component.ts index e2319bf..1efc2ab 100644 --- a/ui/src/app/components/nodes/DirectNodes.component.ts +++ b/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 {NodeDTO} from "../../entities/NodeDTO"; +//todo abs this @Component({ selector: 'app-direct-nodes', styleUrls: ['nodes.styles.scss'], template: `
+
+ + Сортировать по ... + + + {{s.name}} + + + +
- + {{node.long_name}} {{node.short_name}} ({{node.num}}) - -

график

-
+ - +
@@ -32,6 +43,12 @@ export class DirectNodesComponent implements OnInit { } 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 { 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] + }); + } +} diff --git a/ui/src/app/entities/KeyValueMap.ts b/ui/src/app/entities/KeyValueMap.ts new file mode 100644 index 0000000..bb80bd3 --- /dev/null +++ b/ui/src/app/entities/KeyValueMap.ts @@ -0,0 +1,3 @@ +export interface KeyValueMap { + [key: string]: T; +} diff --git a/ui/src/app/entities/MessageDTO.ts b/ui/src/app/entities/MessageDTO.ts new file mode 100644 index 0000000..1e38c7c --- /dev/null +++ b/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; +}