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 {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}
];

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

@ -4,9 +4,10 @@
</button>
<span>MeshCenter</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-drawer-container class="container" autosize>
<mat-drawer-container class="container" autosize style="height: calc(100% - 36px)">
<mat-drawer #drawer class="sidenav" mode="over">
<div style="width: 100px">
<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 {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()
)
}
}

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 {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,

30
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";
<mat-tab-group>
<mat-tab label="Через поиск">
<div>
<div>
<div style="border-radius: 1px; border: 1px solid black">
<mat-form-field appearance="fill" style="width: 100%">
<mat-label>Имя ноды</mat-label>
<input
@ -38,15 +39,14 @@ import {MatSnackBar} from "@angular/material/snack-bar";
</mat-autocomplete>
</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>
<mat-divider></mat-divider>
<div>
<mat-form-field class="example-full-width" appearance="fill">
<div [hidden]="!showAcceptCode" style="border-radius: 1px; border: 1px solid black; margin-top: 5px">
<mat-form-field style="width: 100%" appearance="fill">
<mat-label>Код из сообщения</mat-label>
<input [(ngModel)]="code" matInput maxlength="4" placeholder="Код из сообщения">
</mat-form-field>
<button mat-button (click)="acceptCode()">Проверить код</button>
<button mat-button (click)="acceptCode()" style="width: 100%">Проверить код</button>
</div>
</div>
</mat-tab>
@ -74,6 +74,8 @@ export class AuthDialog implements OnInit {
myControl = new FormControl();
foundedNodes: Observable<NodeMiniDTO[]> = new Observable<NodeMiniDTO[]>()
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);

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({
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 {NodeDTO} from "../../entities/NodeDTO";
//todo abs this
@Component({
selector: 'app-direct-nodes',
styleUrls: ['nodes.styles.scss'],
template: `
<!--<p *ngFor="let node of nodes">{{node.long_name}}</p>-->
<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">
<mat-card *ngFor="let node of nodes">
<mat-card *ngFor="let node of nodes | NodeDtoSort: sort.type">
<mat-card-header>
<mat-card-title>{{node.long_name}}</mat-card-title>
<mat-card-subtitle>{{node.short_name}} ({{node.num}})</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>график</p>
</mat-card-content>
<!--<mat-card-content>
</mat-card-content>-->
<mat-card-actions>
<button mat-button *ngIf="node.hops_away > 0">Прыжков: {{node.hops_away}}</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>
</div>
@ -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]
});
}
}

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