From 60f54c2f9bf1bbf113f1d18a3400a004ff9f026f Mon Sep 17 00:00:00 2001 From: gsd Date: Wed, 11 Feb 2026 15:16:10 +0300 Subject: [PATCH] ui start --- ui/angular.json | 3 + ui/src/app/app-routing.module.ts | 7 +- ui/src/app/app.component.ts | 4 +- ui/src/app/app.module.ts | 39 +++++- ui/src/app/auth/AuthInterceptor.ts | 120 ++++++++++++++++++ .../botCommands/BotCommands.component.ts | 9 ++ .../messages/MessageHistory.component.ts | 9 ++ .../components/nodes/AllNodes.component.ts | 9 ++ .../components/nodes/DirectNodes.component.ts | 39 ++++++ ui/src/app/components/nodes/nodes.styles.scss | 11 ++ ui/src/app/entities/NodeDTO.ts | 7 + ui/src/app/entities/NodeMiniDTO.ts | 5 + ui/src/proxy.conf.json | 6 + webExtensions/messageList.py | 6 +- 14 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 ui/src/app/auth/AuthInterceptor.ts create mode 100644 ui/src/app/components/botCommands/BotCommands.component.ts create mode 100644 ui/src/app/components/messages/MessageHistory.component.ts create mode 100644 ui/src/app/components/nodes/AllNodes.component.ts create mode 100644 ui/src/app/components/nodes/DirectNodes.component.ts create mode 100644 ui/src/app/components/nodes/nodes.styles.scss create mode 100644 ui/src/app/entities/NodeDTO.ts create mode 100644 ui/src/app/entities/NodeMiniDTO.ts create mode 100644 ui/src/proxy.conf.json diff --git a/ui/angular.json b/ui/angular.json index d435038..7774b5c 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -76,6 +76,9 @@ "browserTarget": "ui:build:development" } }, + "options": { + "proxyConfig": "src/proxy.conf.json" + }, "defaultConfiguration": "development" }, "extract-i18n": { diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index 0297262..06a8d29 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -1,7 +1,12 @@ 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"; -const routes: Routes = []; +const routes: Routes = [ + {path: "nodes/direct", component: DirectNodesComponent}, + {path: "", component: BotCommandsComponent} +]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 0741c53..f8336b6 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -9,7 +9,7 @@ export class AppComponent { routes: {name: string, url: string}[] = [ {name: "Команды бота", url:""}, {name: "История сообщений", url:""}, - {name: "Статистика по нодам", url:""}, - {name: "Связь", url:""}, + {name: "Прямые ноды", url:""}, + {name: "Все ноды", url:""}, ] } diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 81f9a69..5204118 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -8,11 +8,29 @@ import {MatToolbarModule} from "@angular/material/toolbar"; import {MatIconModule} from "@angular/material/icon"; import {MatSidenavModule} from "@angular/material/sidenav"; import {MatButtonModule} from "@angular/material/button"; -import {HttpClientModule} from "@angular/common/http"; +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 {AllNodesComponent} from "./components/nodes/AllNodes.component"; +import {AuthDialog, AuthInterceptor} from "./auth/AuthInterceptor"; +import {MatDialogModule} from "@angular/material/dialog"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatInputModule} from "@angular/material/input"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +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"; @NgModule({ declarations: [ - AppComponent + AppComponent, + BotCommandsComponent, + MessageHistoryComponent, + DirectNodesComponent, + AllNodesComponent, + AuthDialog ], imports: [ BrowserModule, @@ -22,9 +40,22 @@ import {HttpClientModule} from "@angular/common/http"; MatIconModule, MatSidenavModule, MatButtonModule, - HttpClientModule + HttpClientModule, + MatDialogModule, + MatTabsModule, + MatInputModule, + MatAutocompleteModule, + ReactiveFormsModule, + FormsModule, + MatSnackBarModule, + MatDividerModule, + MatCardModule ], - providers: [], + providers: [{ + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptor, + multi: true + }], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/ui/src/app/auth/AuthInterceptor.ts b/ui/src/app/auth/AuthInterceptor.ts new file mode 100644 index 0000000..5986a88 --- /dev/null +++ b/ui/src/app/auth/AuthInterceptor.ts @@ -0,0 +1,120 @@ +import { + HttpClient, + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest +} from "@angular/common/http"; +import {Component, Injectable, OnInit, ViewContainerRef} from "@angular/core"; +import {catchError, Observable, startWith, throwError} from "rxjs"; +import {MatDialog, MatDialogRef} from "@angular/material/dialog"; +import {FormControl} from "@angular/forms"; +import {NodeMiniDTO} from "../entities/NodeMiniDTO"; +import {MatSnackBar} from "@angular/material/snack-bar"; + + +@Component({ + selector: "app-auth-dialog", + template: ` +

Авторизация

+
+ + +
+
+ + Имя ноды + + + + {{ node.long_name }} ({{node.num}}) + + + + + +
+ +
+ + Код из сообщения + + + +
+
+
+ +
+
+
+
+ ` +}) +export class AuthDialog implements OnInit { + constructor(private http: HttpClient, + private dialogRef: MatDialogRef, + private snack: MatSnackBar) { + } + + ngOnInit(): void { + this.myControl.valueChanges.subscribe( + (v) => { + if (typeof v == "string") + this.foundedNodes = this.http.get(`api/nodes/search?name=${v}`) as Observable + } + ) + } + myControl = new FormControl(); + foundedNodes: Observable = new Observable() + code!: number + + acceptCode() { + this.http.get(`api/auth/code/check?code=${this.code}`) + // @ts-ignore + .subscribe((res:{status:boolean}) => { + if (res.status) { + this.dialogRef.close() + } else { + this.snack.open("Неправильный код") + } + }) + } + + displayNode(node: NodeMiniDTO) { + if (!node) return '' + return `${node.long_name} (${node.num})` + } + + sendCodeToNode() { + let node:NodeMiniDTO = this.myControl.value as NodeMiniDTO + this.http.get(`api/auth/code?num=${node.num}`).subscribe( + (res) => this.snack.open(`Код отправлен на ноду ${node.long_name}`) + ) + } +} + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + constructor(private dialog: MatDialog) {} + dialogOpened: boolean = false; + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === 401 && !this.dialogOpened) { + const ref = this.dialog.open(AuthDialog, {disableClose: true, width:"50%"}) + ref.afterClosed().subscribe((res:any) => this.dialogOpened = false) + } + return throwError(() => error); + }) + ) + } + +} diff --git a/ui/src/app/components/botCommands/BotCommands.component.ts b/ui/src/app/components/botCommands/BotCommands.component.ts new file mode 100644 index 0000000..f597f0d --- /dev/null +++ b/ui/src/app/components/botCommands/BotCommands.component.ts @@ -0,0 +1,9 @@ +import {Component} from "@angular/core"; + +@Component({ + selector: "app-bot-commands", + template: `` +}) +export class BotCommandsComponent { + +} diff --git a/ui/src/app/components/messages/MessageHistory.component.ts b/ui/src/app/components/messages/MessageHistory.component.ts new file mode 100644 index 0000000..df572d7 --- /dev/null +++ b/ui/src/app/components/messages/MessageHistory.component.ts @@ -0,0 +1,9 @@ +import {Component} from "@angular/core"; + +@Component({ + selector: "app-message-history", + template: `` +}) +export class MessageHistoryComponent { + +} diff --git a/ui/src/app/components/nodes/AllNodes.component.ts b/ui/src/app/components/nodes/AllNodes.component.ts new file mode 100644 index 0000000..5eac8c3 --- /dev/null +++ b/ui/src/app/components/nodes/AllNodes.component.ts @@ -0,0 +1,9 @@ +import {Component} from "@angular/core"; + +@Component({ + selector: "app-all-nodes", + template: `` +}) +export class AllNodesComponent { + +} diff --git a/ui/src/app/components/nodes/DirectNodes.component.ts b/ui/src/app/components/nodes/DirectNodes.component.ts new file mode 100644 index 0000000..bffd966 --- /dev/null +++ b/ui/src/app/components/nodes/DirectNodes.component.ts @@ -0,0 +1,39 @@ +import {Component, OnInit} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {NodeDTO} from "../../entities/NodeDTO"; + +@Component({ + selector: 'app-direct-nodes', + styleUrls: ['nodes.styles.scss'], + template: ` + +
+ + + {{node.long_name}} + {{node.short_name}} ({{node.num}}) + + +

график

+
+ + + + + +
+
+ ` +}) +export class DirectNodesComponent implements OnInit { + constructor(private http: HttpClient) { + } + + nodes: NodeDTO[] = []; + + ngOnInit(): void { + this.http.get(`api/nodes/direct`).subscribe( + (res) => this.nodes = res as NodeDTO[] + ) + } +} diff --git a/ui/src/app/components/nodes/nodes.styles.scss b/ui/src/app/components/nodes/nodes.styles.scss new file mode 100644 index 0000000..8b71b74 --- /dev/null +++ b/ui/src/app/components/nodes/nodes.styles.scss @@ -0,0 +1,11 @@ +.card-wrapper { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 8px; +} + +.grid-card { + width: 100%; + height: 100%; + /* Additional styling as needed */ +} diff --git a/ui/src/app/entities/NodeDTO.ts b/ui/src/app/entities/NodeDTO.ts new file mode 100644 index 0000000..406a78e --- /dev/null +++ b/ui/src/app/entities/NodeDTO.ts @@ -0,0 +1,7 @@ +import {NodeMiniDTO} from "./NodeMiniDTO"; + +export interface NodeDTO extends NodeMiniDTO { + snr: number, + hops_away: number, + ts: number +} diff --git a/ui/src/app/entities/NodeMiniDTO.ts b/ui/src/app/entities/NodeMiniDTO.ts new file mode 100644 index 0000000..a802fe1 --- /dev/null +++ b/ui/src/app/entities/NodeMiniDTO.ts @@ -0,0 +1,5 @@ +export interface NodeMiniDTO { + num: number, + long_name: string, + short_name: string +} diff --git a/ui/src/proxy.conf.json b/ui/src/proxy.conf.json new file mode 100644 index 0000000..5aa24d7 --- /dev/null +++ b/ui/src/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://192.168.3.2:8680/", + "secure": false + } +} diff --git a/webExtensions/messageList.py b/webExtensions/messageList.py index 99290f5..348d42a 100644 --- a/webExtensions/messageList.py +++ b/webExtensions/messageList.py @@ -17,8 +17,12 @@ class WebExtension: self.dbStore = core.dbStore @self.app.get(f"{self.core.context}/messages") + @self.core.authManager.authRequest() async def listOfMessages(limit: int = Query(10), offset: int = Query(0)): collection = self.dbStore['packet'] - c = collection.find({"to": int(self.core.PUB_CH), "portnum":self.MESSAGE_PORTNUM}).sort("ts", DESCENDING).skip(offset).limit(limit) + c = collection.find({ + "to": int(self.core.PUB_CH), + "portnum":self.MESSAGE_PORTNUM + }).sort("ts", DESCENDING).skip(offset).limit(limit) l = await c.to_list() return [MessageDTO(msg) for msg in l] \ No newline at end of file