Browse Source

ui start

main
gsd 4 months ago
parent
commit
60f54c2f9b
  1. 3
      ui/angular.json
  2. 7
      ui/src/app/app-routing.module.ts
  3. 4
      ui/src/app/app.component.ts
  4. 39
      ui/src/app/app.module.ts
  5. 120
      ui/src/app/auth/AuthInterceptor.ts
  6. 9
      ui/src/app/components/botCommands/BotCommands.component.ts
  7. 9
      ui/src/app/components/messages/MessageHistory.component.ts
  8. 9
      ui/src/app/components/nodes/AllNodes.component.ts
  9. 39
      ui/src/app/components/nodes/DirectNodes.component.ts
  10. 11
      ui/src/app/components/nodes/nodes.styles.scss
  11. 7
      ui/src/app/entities/NodeDTO.ts
  12. 5
      ui/src/app/entities/NodeMiniDTO.ts
  13. 6
      ui/src/proxy.conf.json
  14. 6
      webExtensions/messageList.py

3
ui/angular.json

@ -76,6 +76,9 @@
"browserTarget": "ui:build:development"
}
},
"options": {
"proxyConfig": "src/proxy.conf.json"
},
"defaultConfiguration": "development"
},
"extract-i18n": {

7
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)],

4
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:""},
]
}

39
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 { }

120
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: `
<h2 mat-dialog-title>Авторизация</h2>
<div mat-dialog-content>
<mat-tab-group>
<mat-tab label="Через поиск">
<div>
<div>
<mat-form-field appearance="fill" style="width: 100%">
<mat-label>Имя ноды</mat-label>
<input
type="text"
matInput
[formControl]="myControl"
[matAutocomplete]="auto"
>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayNode">
<mat-option *ngFor="let node of foundedNodes | async" [value]="node">
{{ node.long_name }} ({{node.num}})
</mat-option>
</mat-autocomplete>
</mat-form-field>
<button mat-button mat-raised-button (click)="sendCodeToNode()">Отправить код на ноду</button>
</div>
<mat-divider></mat-divider>
<div>
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Код из сообщения</mat-label>
<input [(ngModel)]="code" matInput maxlength="4" placeholder="Код из сообщения">
</mat-form-field>
<button mat-button (click)="acceptCode()">Проверить код</button>
</div>
</div>
</mat-tab>
<mat-tab label="Написать боту" disabled>
<div></div>
</mat-tab>
</mat-tab-group>
</div>
`
})
export class AuthDialog implements OnInit {
constructor(private http: HttpClient,
private dialogRef: MatDialogRef<any>,
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<NodeMiniDTO[]>
}
)
}
myControl = new FormControl();
foundedNodes: Observable<NodeMiniDTO[]> = new Observable<NodeMiniDTO[]>()
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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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);
})
)
}
}

9
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 {
}

9
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 {
}

9
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 {
}

39
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: `
<!--<p *ngFor="let node of nodes">{{node.long_name}}</p>-->
<div class="card-wrapper">
<mat-card *ngFor="let node of nodes" style="min-width: 250px">
<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-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>
</mat-card-actions>
</mat-card>
</div>
`
})
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[]
)
}
}

11
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 */
}

7
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
}

5
ui/src/app/entities/NodeMiniDTO.ts

@ -0,0 +1,5 @@
export interface NodeMiniDTO {
num: number,
long_name: string,
short_name: string
}

6
ui/src/proxy.conf.json

@ -0,0 +1,6 @@
{
"/api": {
"target": "http://192.168.3.2:8680/",
"secure": false
}
}

6
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]
Loading…
Cancel
Save