gsd 4 months ago
parent
commit
25d2ece326
  1. 3
      service.py
  2. 44
      tileManager.py
  3. 4
      ui/angular.json
  4. 25
      ui/package-lock.json
  5. 2
      ui/package.json
  6. 2
      ui/src/app/app-routing.module.ts
  7. 1
      ui/src/app/app.component.ts
  8. 4
      ui/src/app/app.module.ts
  9. 69
      ui/src/app/components/nodes/nodes-map.component.ts
  10. 21
      ui/src/app/components/nodes/nodes.styles.scss
  11. 6
      ui/src/app/entities/NodeDTO.ts
  12. 7
      ui/src/app/entities/node/DeviceMetricsDTO.ts
  13. 6
      ui/src/app/entities/node/PositionDTO.ts
  14. 17
      webExtensions/publicEndpoints.py

3
service.py

@ -126,6 +126,9 @@ class MongoDriver(MeshArgsParse):
sys.exit(1) sys.exit(1)
self.dbStore = self.dbClient[self.args.mongo_db] self.dbStore = self.dbClient[self.args.mongo_db]
self.dbService = DbService(self.dbStore) self.dbService = DbService(self.dbStore)
from tileManager import TileManager
self.tileManager = TileManager(self)
async def dbSaveRadio(self, new_from_radio): async def dbSaveRadio(self, new_from_radio):
'''try: '''try:

44
tileManager.py

@ -0,0 +1,44 @@
import aiohttp
from pymongo.asynchronous.database import AsyncDatabase
from logger import logger
from time import time
class TileManager:
domain = 'a.tile.openstreetmap.org'
format = "png"
def __init__(self, core):
self.core = core
self.dbStore:AsyncDatabase = self.core.dbStore
def generateHeaders(self):
return {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"Referer": "http://localhost:4200/",
"Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
}
async def grabTile(self, z:int, x:int, y:int):
#grab from db
collection = self.dbStore['openstreetmap']
query = {"x":x, "y":y, "z": z}
t = await collection.find_one(query)
if t:
return t["img"]
else:
#ищем картинку чтож поделать
url = f"https://{self.domain}/{z}/{x}/{y}.{self.format}"
async with aiohttp.ClientSession() as session:
async with session.get(url, ssl=False, headers=self.generateHeaders()) as resp:
# Read the entire response body as bytes
img = await resp.read()
logger.info(url, resp.status)
if resp.status == 200:
query['ts'] = time()
query['img'] = img
query['format'] = self.format
await collection.insert_one(query)
return img
else:
raise Exception("cannot get img")

4
ui/angular.json

@ -29,7 +29,8 @@
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss" "src/styles.scss",
"./node_modules/leaflet/dist/leaflet.css"
], ],
"scripts": [] "scripts": []
}, },
@ -101,6 +102,7 @@
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"./node_modules/leaflet/dist/leaflet.css",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [] "scripts": []

25
ui/package-lock.json

@ -19,6 +19,7 @@
"@angular/platform-browser-dynamic": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0", "@angular/router": "^14.2.0",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"leaflet": "^1.9.4",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
@ -28,6 +29,7 @@
"@angular/cli": "~14.2.13", "@angular/cli": "~14.2.13",
"@angular/compiler-cli": "^14.2.0", "@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/leaflet": "^1.9.21",
"jasmine-core": "~4.3.0", "jasmine-core": "~4.3.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
@ -3343,6 +3345,13 @@
"@types/send": "*" "@types/send": "*"
} }
}, },
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/http-errors": { "node_modules/@types/http-errors": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
@ -3374,6 +3383,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/leaflet": {
"version": "1.9.21",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz",
"integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@ -8099,6 +8118,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/less": { "node_modules/less": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",

2
ui/package.json

@ -21,6 +21,7 @@
"@angular/platform-browser-dynamic": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0", "@angular/router": "^14.2.0",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"leaflet": "^1.9.4",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
@ -30,6 +31,7 @@
"@angular/cli": "~14.2.13", "@angular/cli": "~14.2.13",
"@angular/compiler-cli": "^14.2.0", "@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0", "@types/jasmine": "~4.0.0",
"@types/leaflet": "^1.9.21",
"jasmine-core": "~4.3.0", "jasmine-core": "~4.3.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",

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

@ -4,8 +4,10 @@ import {NodesListComponent} from "./components/nodes/nodes-list.component";
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 {NetworkStatusComponent} from "./components/packet/NetworkStatus.component"; import {NetworkStatusComponent} from "./components/packet/NetworkStatus.component";
import {NodesMapComponent} from "./components/nodes/nodes-map.component";
const routes: Routes = [ const routes: Routes = [
{path: "nodes/map", component: NodesMapComponent},
{path: "nodes/:type", component: NodesListComponent}, {path: "nodes/:type", component: NodesListComponent},
{path: "messages", component: MessageHistoryComponent}, {path: "messages", component: MessageHistoryComponent},
{path: "network/status/:num", component: NetworkStatusComponent}, {path: "network/status/:num", component: NetworkStatusComponent},

1
ui/src/app/app.component.ts

@ -26,6 +26,7 @@ export class AppComponent implements OnInit {
{name: "История сообщений", url:"messages"}, {name: "История сообщений", url:"messages"},
{name: "Прямые ноды", url:"nodes/direct"}, {name: "Прямые ноды", url:"nodes/direct"},
{name: "Все ноды", url:"nodes/list"}, {name: "Все ноды", url:"nodes/list"},
{name: "Карта сети", url: "nodes/map"}
//{name: "Все ноды", url:""}, //{name: "Все ноды", url:""},
] ]

4
ui/src/app/app.module.ts

@ -25,6 +25,7 @@ import {MatSelectModule} from "@angular/material/select";
import {NetworkStatusComponent} from "./components/packet/NetworkStatus.component"; import {NetworkStatusComponent} from "./components/packet/NetworkStatus.component";
import {MatRadioModule} from "@angular/material/radio"; import {MatRadioModule} from "@angular/material/radio";
import {DatePipe} from "@angular/common"; import {DatePipe} from "@angular/common";
import {NodesMapComponent} from "./components/nodes/nodes-map.component";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -33,7 +34,8 @@ import {DatePipe} from "@angular/common";
MessageHistoryComponent, MessageHistoryComponent,
NodesListComponent, NodesListComponent,
AuthDialog, AuthDialog,
NetworkStatusComponent NetworkStatusComponent,
NodesMapComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

69
ui/src/app/components/nodes/nodes-map.component.ts

@ -0,0 +1,69 @@
import {Component, OnInit} from "@angular/core";
import * as L from 'leaflet';
import {HttpClient} from "@angular/common/http";
import {NodeDTO} from "../../entities/NodeDTO";
import {Subscription} from "rxjs";
@Component({
selector: "app-nodes-map",
styleUrls: ['nodes.styles.scss'],
template: `
<div style="width:80%">
<div class="map-container">
<div class="map-frame">
<div id="map"></div>
</div>
</div>
</div>
`
})
export class NodesMapComponent implements OnInit {
map!: L.Map;
nodes: NodeDTO[] = []
constructor(private http: HttpClient) {
}
ngOnInit(): void {
this.http.get(`api/nodes/list?p=true`).subscribe(
(obj) => {
this.nodes = (obj as NodeDTO[])
}
).add(
() => {
this.createMap(this.convertPosition(this.nodes.filter((node) => node.havePosition).pop())).add(
() => {
this.nodes.filter((node) => node.havePosition).forEach(
(node) => {
L.marker(this.convertPosition(node)).addTo(this.map)
}
)
}
)
}
)
}
convertPosition(node:NodeDTO|undefined):[number, number] {
if (node) {
let lat = node.position.latitude_i / 10000000;
let lng = node.position.longitude_i / 10000000;
return [lat, lng];
} else return [0,0];
}
createMap(center:[number, number]) {
this.map = L.map("map",{
center: center,
zoom: 11
});
const tiles = L.tileLayer('api/tile/{z}/{x}/{y}.png', {
maxZoom: 18,
minZoom: 3,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
tiles.addTo(this.map)
return Subscription.EMPTY;
}
}

21
ui/src/app/components/nodes/nodes.styles.scss

@ -0,0 +1,21 @@
.map-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 30px;
}
.map-frame {
border: 2px solid black;
height: 100%;
}
#map {
height: 100%;
}
:host ::ng-deep .leaflet-control-attribution {
display: none;
}

6
ui/src/app/entities/NodeDTO.ts

@ -1,7 +1,13 @@
import {NodeMiniDTO} from "./NodeMiniDTO"; import {NodeMiniDTO} from "./NodeMiniDTO";
import {PositionDTO} from "./node/PositionDTO";
import {DeviceMetricsDTO} from "./node/DeviceMetricsDTO";
export interface NodeDTO extends NodeMiniDTO { export interface NodeDTO extends NodeMiniDTO {
snr: number, snr: number,
hops_away: number, hops_away: number,
ts: number ts: number
havePosition:boolean,
position: PositionDTO,
haveMetrics:boolean,
device_metrics: DeviceMetricsDTO
} }

7
ui/src/app/entities/node/DeviceMetricsDTO.ts

@ -0,0 +1,7 @@
export interface DeviceMetricsDTO {
battery_level: number,
voltage: number,
channel_utilization: number,
air_util_tx: number,
uptime_seconds: number
}

6
ui/src/app/entities/node/PositionDTO.ts

@ -0,0 +1,6 @@
export interface PositionDTO {
latitude_i: number,
longitude_i: number,
time: number,
location_source: number
}

17
webExtensions/publicEndpoints.py

@ -1,8 +1,9 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.requests import Request from fastapi.requests import Request
from fastapi.responses import Response, JSONResponse from fastapi.responses import Response, JSONResponse, StreamingResponse
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from extra.NodeDTO import NodeDTO from extra.NodeDTO import NodeDTO
import traceback
class WebExtension: class WebExtension:
app: FastAPI app: FastAPI
@ -59,4 +60,16 @@ class WebExtension:
if userNum is not None: if userNum is not None:
return NodeDTO(userNode) return NodeDTO(userNode)
else: else:
return {} return {}
@self.app.get(self.core.context+"/tile/{z}/{x}/{y}.png")
@self.core.authManager.authRequest()
async def grabTile(z:int, x:int, y:int):
try:
img = await self.core.tileManager.grabTile(z, x, y)
print(type(img))
#return StreamingResponse(img, media_type="image/png")
return Response(content=img, media_type="image/png")
except:
traceback.print_exc()
raise HTTPException(status_code=404, detail="not found tile")
Loading…
Cancel
Save