mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-07-09 11:21:21 +02:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 350aa9e4a3 | |||
| e5bbf972c7 | |||
| 4326e12e88 | |||
| 00aa3216ff | |||
| 3d6c01f020 | |||
| d3bf0ede67 | |||
| 2b02166d82 | |||
| 2fd36b4b11 | |||
| 8aa1c59873 | |||
| cd036b8004 | |||
| 989da239fb | |||
| 31626494d3 | |||
| 960a7ef075 | |||
| 60c4d22d2d | |||
| 13a094be00 | |||
| 7744cedd8c | |||
| ad42c1aeaf | |||
| 41f7bf42a3 | |||
| 0543aeb650 | |||
| 679071cc14 | |||
| 198afcc7d8 | |||
| 191a01a03c | |||
| fd653f8234 | |||
| 2149fed8c5 | |||
| 5609d18284 | |||
| 705b0b79fc | |||
| 32ad8e3a9c | |||
| e77428661c | |||
| e68cdf8cc1 |
@@ -4,6 +4,19 @@
|
|||||||
|
|
||||||
The project serves as a real-time monitoring and diagnostic tool for the Meshtastic mesh network. It provides detailed insights into network activity, including message traffic, node positions, and telemetry data.
|
The project serves as a real-time monitoring and diagnostic tool for the Meshtastic mesh network. It provides detailed insights into network activity, including message traffic, node positions, and telemetry data.
|
||||||
|
|
||||||
|
### Version 3.0.1 — December 2025
|
||||||
|
|
||||||
|
#### 🌐 Multi-Language Support (i18n)
|
||||||
|
- New `/api/lang` endpoint for serving translations
|
||||||
|
- Section-based translation loading (e.g., `?section=firehose`)
|
||||||
|
- Default language controlled via config file language section
|
||||||
|
- JSON-based translation files for easy expansion
|
||||||
|
- Core pages updated to support `data-translate-lang` attributes
|
||||||
|
|
||||||
|
### 🛠 Improvements
|
||||||
|
- Updated UI elements across multiple templates for localization readiness
|
||||||
|
- General cleanup to support future language additions
|
||||||
|
|
||||||
### Version 3.0.0 update - November 2025
|
### Version 3.0.0 update - November 2025
|
||||||
|
|
||||||
**Major Infrastructure Improvements:**
|
**Major Infrastructure Improvements:**
|
||||||
@@ -84,6 +97,20 @@ Samples of currently running instances:
|
|||||||
- https://meshview-salzburg.jmt.gr/ (Salzburg / Austria)
|
- https://meshview-salzburg.jmt.gr/ (Salzburg / Austria)
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
### Updating from 2.x to 3.x
|
||||||
|
We are adding the use of Alembic. If using GitHub
|
||||||
|
Update your codebase by running the pull command
|
||||||
|
```bash
|
||||||
|
cd meshview
|
||||||
|
git pull origin master
|
||||||
|
```
|
||||||
|
Install Alembic in your environment
|
||||||
|
```bash
|
||||||
|
./env/bin/pip install alembic
|
||||||
|
```
|
||||||
|
Start your scripts or services. This process will update your database with the latest schema.
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
### Using Docker (Recommended)
|
### Using Docker (Recommended)
|
||||||
@@ -184,6 +211,9 @@ acme_challenge =
|
|||||||
# The domain name of your site.
|
# The domain name of your site.
|
||||||
domain =
|
domain =
|
||||||
|
|
||||||
|
# Select language (this represents the name of the json file in the /lang directory)
|
||||||
|
language = es
|
||||||
|
|
||||||
# Site title to show in the browser title bar and headers.
|
# Site title to show in the browser title bar and headers.
|
||||||
title = Bay Area Mesh
|
title = Bay Area Mesh
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -770,6 +770,7 @@ class SharedContact(google.protobuf.message.Message):
|
|||||||
NODE_NUM_FIELD_NUMBER: builtins.int
|
NODE_NUM_FIELD_NUMBER: builtins.int
|
||||||
USER_FIELD_NUMBER: builtins.int
|
USER_FIELD_NUMBER: builtins.int
|
||||||
SHOULD_IGNORE_FIELD_NUMBER: builtins.int
|
SHOULD_IGNORE_FIELD_NUMBER: builtins.int
|
||||||
|
MANUALLY_VERIFIED_FIELD_NUMBER: builtins.int
|
||||||
node_num: builtins.int
|
node_num: builtins.int
|
||||||
"""
|
"""
|
||||||
The node number of the contact
|
The node number of the contact
|
||||||
@@ -778,6 +779,10 @@ class SharedContact(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Add this contact to the blocked / ignored list
|
Add this contact to the blocked / ignored list
|
||||||
"""
|
"""
|
||||||
|
manually_verified: builtins.bool
|
||||||
|
"""
|
||||||
|
Set the IS_KEY_MANUALLY_VERIFIED bit
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def user(self) -> meshtastic.protobuf.mesh_pb2.User:
|
def user(self) -> meshtastic.protobuf.mesh_pb2.User:
|
||||||
"""
|
"""
|
||||||
@@ -790,9 +795,10 @@ class SharedContact(google.protobuf.message.Message):
|
|||||||
node_num: builtins.int = ...,
|
node_num: builtins.int = ...,
|
||||||
user: meshtastic.protobuf.mesh_pb2.User | None = ...,
|
user: meshtastic.protobuf.mesh_pb2.User | None = ...,
|
||||||
should_ignore: builtins.bool = ...,
|
should_ignore: builtins.bool = ...,
|
||||||
|
manually_verified: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def HasField(self, field_name: typing.Literal["user", b"user"]) -> builtins.bool: ...
|
def HasField(self, field_name: typing.Literal["user", b"user"]) -> builtins.bool: ...
|
||||||
def ClearField(self, field_name: typing.Literal["node_num", b"node_num", "should_ignore", b"should_ignore", "user", b"user"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["manually_verified", b"manually_verified", "node_num", b"node_num", "should_ignore", b"should_ignore", "user", b"user"]) -> None: ...
|
||||||
|
|
||||||
global___SharedContact = SharedContact
|
global___SharedContact = SharedContact
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_chann
|
|||||||
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
|
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/apponly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\"\x81\x01\n\nChannelSet\x12\x36\n\x08settings\x18\x01 \x03(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12;\n\x0blora_config\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigBb\n\x13\x63om.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/apponly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\"\x81\x01\n\nChannelSet\x12\x36\n\x08settings\x18\x01 \x03(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12;\n\x0blora_config\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigBc\n\x14org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.apponly_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.apponly_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_CHANNELSET']._serialized_start=128
|
_globals['_CHANNELSET']._serialized_start=128
|
||||||
_globals['_CHANNELSET']._serialized_end=257
|
_globals['_CHANNELSET']._serialized_end=257
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\xa5\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x12\x10\n\x06\x64\x65tail\x18\x07 \x01(\x0cH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42_\n\x13\x63om.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\xa5\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x12\x10\n\x06\x64\x65tail\x18\x07 \x01(\x0cH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42`\n\x14org.meshtastic.protoB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_TEAM']._serialized_start=721
|
_globals['_TEAM']._serialized_start=721
|
||||||
_globals['_TEAM']._serialized_end=913
|
_globals['_TEAM']._serialized_end=913
|
||||||
_globals['_MEMBERROLE']._serialized_start=915
|
_globals['_MEMBERROLE']._serialized_start=915
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(meshtastic/protobuf/cannedmessages.proto\x12\x13meshtastic.protobuf\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBn\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n(meshtastic/protobuf/cannedmessages.proto\x12\x13meshtastic.protobuf\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBo\n\x14org.meshtastic.protoB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cannedmessages_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cannedmessages_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_start=65
|
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_start=65
|
||||||
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_end=110
|
_globals['_CANNEDMESSAGEMODULECONFIG']._serialized_end=110
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -13,22 +13,22 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/channel.proto\x12\x13meshtastic.protobuf\"\xc1\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12<\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32#.meshtastic.protobuf.ModuleSettings\"E\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x17\n\x0fis_client_muted\x18\x02 \x01(\x08\"\xb3\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\x36\n\x08settings\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12/\n\x04role\x18\x03 \x01(\x0e\x32!.meshtastic.protobuf.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x62\n\x13\x63om.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!meshtastic/protobuf/channel.proto\x12\x13meshtastic.protobuf\"\xc1\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12<\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32#.meshtastic.protobuf.ModuleSettings\">\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x10\n\x08is_muted\x18\x02 \x01(\x08\"\xb3\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\x36\n\x08settings\x18\x02 \x01(\x0b\x32$.meshtastic.protobuf.ChannelSettings\x12/\n\x04role\x18\x03 \x01(\x0e\x32!.meshtastic.protobuf.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x63\n\x14org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.channel_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.channel_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_CHANNELSETTINGS.fields_by_name['channel_num']._options = None
|
_CHANNELSETTINGS.fields_by_name['channel_num']._options = None
|
||||||
_CHANNELSETTINGS.fields_by_name['channel_num']._serialized_options = b'\030\001'
|
_CHANNELSETTINGS.fields_by_name['channel_num']._serialized_options = b'\030\001'
|
||||||
_globals['_CHANNELSETTINGS']._serialized_start=59
|
_globals['_CHANNELSETTINGS']._serialized_start=59
|
||||||
_globals['_CHANNELSETTINGS']._serialized_end=252
|
_globals['_CHANNELSETTINGS']._serialized_end=252
|
||||||
_globals['_MODULESETTINGS']._serialized_start=254
|
_globals['_MODULESETTINGS']._serialized_start=254
|
||||||
_globals['_MODULESETTINGS']._serialized_end=323
|
_globals['_MODULESETTINGS']._serialized_end=316
|
||||||
_globals['_CHANNEL']._serialized_start=326
|
_globals['_CHANNEL']._serialized_start=319
|
||||||
_globals['_CHANNEL']._serialized_end=505
|
_globals['_CHANNEL']._serialized_end=498
|
||||||
_globals['_CHANNEL_ROLE']._serialized_start=457
|
_globals['_CHANNEL_ROLE']._serialized_start=450
|
||||||
_globals['_CHANNEL_ROLE']._serialized_end=505
|
_globals['_CHANNEL_ROLE']._serialized_end=498
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -127,23 +127,23 @@ class ModuleSettings(google.protobuf.message.Message):
|
|||||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
||||||
IS_CLIENT_MUTED_FIELD_NUMBER: builtins.int
|
IS_MUTED_FIELD_NUMBER: builtins.int
|
||||||
position_precision: builtins.int
|
position_precision: builtins.int
|
||||||
"""
|
"""
|
||||||
Bits of precision for the location sent in position packets.
|
Bits of precision for the location sent in position packets.
|
||||||
"""
|
"""
|
||||||
is_client_muted: builtins.bool
|
is_muted: builtins.bool
|
||||||
"""
|
"""
|
||||||
Controls whether or not the phone / clients should mute the current channel
|
Controls whether or not the client / device should mute the current channel
|
||||||
Useful for noisy public channels you don't necessarily want to disable
|
Useful for noisy public channels you don't necessarily want to disable
|
||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
position_precision: builtins.int = ...,
|
position_precision: builtins.int = ...,
|
||||||
is_client_muted: builtins.bool = ...,
|
is_muted: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["is_client_muted", b"is_client_muted", "position_precision", b"position_precision"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["is_muted", b"is_muted", "position_precision", b"position_precision"]) -> None: ...
|
||||||
|
|
||||||
global___ModuleSettings = ModuleSettings
|
global___ModuleSettings = ModuleSettings
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_loc
|
|||||||
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"\xc4\x03\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x15\n\x08ringtone\x18\x07 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"\xc4\x03\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x15\n\x08ringtone\x18\x07 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBf\n\x14org.meshtastic.protoB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.clientonly_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.clientonly_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_DEVICEPROFILE']._serialized_start=131
|
_globals['_DEVICEPROFILE']._serialized_start=131
|
||||||
_globals['_DEVICEPROFILE']._serialized_end=583
|
_globals['_DEVICEPROFILE']._serialized_end=583
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -64,6 +64,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
||||||
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
||||||
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
||||||
|
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain.
|
||||||
"""
|
"""
|
||||||
TRACKER: Config.DeviceConfig._Role.ValueType # 5
|
TRACKER: Config.DeviceConfig._Role.ValueType # 5
|
||||||
"""
|
"""
|
||||||
@@ -155,6 +156,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
Description: Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.
|
||||||
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
Technical Details: Mesh packets will simply be rebroadcasted over this node. Nodes configured with this role will not originate NodeInfo, Position, Telemetry
|
||||||
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
or any other packet type. They will simply rebroadcast any mesh packets on the same frequency, channel num, spread factor, and coding rate.
|
||||||
|
Deprecated in v2.7.11 because it creates "holes" in the mesh rebroadcast chain.
|
||||||
"""
|
"""
|
||||||
TRACKER: Config.DeviceConfig.Role.ValueType # 5
|
TRACKER: Config.DeviceConfig.Role.ValueType # 5
|
||||||
"""
|
"""
|
||||||
@@ -938,80 +940,20 @@ class Config(google.protobuf.message.Message):
|
|||||||
|
|
||||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
class _GpsCoordinateFormat:
|
class _DeprecatedGpsCoordinateFormat:
|
||||||
ValueType = typing.NewType("ValueType", builtins.int)
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
V: typing_extensions.TypeAlias = ValueType
|
V: typing_extensions.TypeAlias = ValueType
|
||||||
|
|
||||||
class _GpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._GpsCoordinateFormat.ValueType], builtins.type):
|
class _DeprecatedGpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._DeprecatedGpsCoordinateFormat.ValueType], builtins.type):
|
||||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||||
DEC: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 0
|
UNUSED: Config.DisplayConfig._DeprecatedGpsCoordinateFormat.ValueType # 0
|
||||||
|
|
||||||
|
class DeprecatedGpsCoordinateFormat(_DeprecatedGpsCoordinateFormat, metaclass=_DeprecatedGpsCoordinateFormatEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
GPS coordinates are displayed in the normal decimal degrees format:
|
Deprecated in 2.7.4: Unused
|
||||||
DD.DDDDDD DDD.DDDDDD
|
|
||||||
"""
|
|
||||||
DMS: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 1
|
|
||||||
"""
|
|
||||||
GPS coordinates are displayed in the degrees minutes seconds format:
|
|
||||||
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
|
||||||
"""
|
|
||||||
UTM: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 2
|
|
||||||
"""
|
|
||||||
Universal Transverse Mercator format:
|
|
||||||
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
|
||||||
"""
|
|
||||||
MGRS: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 3
|
|
||||||
"""
|
|
||||||
Military Grid Reference System format:
|
|
||||||
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
|
||||||
E is easting, N is northing
|
|
||||||
"""
|
|
||||||
OLC: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 4
|
|
||||||
"""
|
|
||||||
Open Location Code (aka Plus Codes).
|
|
||||||
"""
|
|
||||||
OSGR: Config.DisplayConfig._GpsCoordinateFormat.ValueType # 5
|
|
||||||
"""
|
|
||||||
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
|
||||||
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
|
||||||
E is the easting, N is the northing
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class GpsCoordinateFormat(_GpsCoordinateFormat, metaclass=_GpsCoordinateFormatEnumTypeWrapper):
|
UNUSED: Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType # 0
|
||||||
"""
|
|
||||||
How the GPS coordinates are displayed on the OLED screen.
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEC: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 0
|
|
||||||
"""
|
|
||||||
GPS coordinates are displayed in the normal decimal degrees format:
|
|
||||||
DD.DDDDDD DDD.DDDDDD
|
|
||||||
"""
|
|
||||||
DMS: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 1
|
|
||||||
"""
|
|
||||||
GPS coordinates are displayed in the degrees minutes seconds format:
|
|
||||||
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
|
||||||
"""
|
|
||||||
UTM: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 2
|
|
||||||
"""
|
|
||||||
Universal Transverse Mercator format:
|
|
||||||
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
|
||||||
"""
|
|
||||||
MGRS: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 3
|
|
||||||
"""
|
|
||||||
Military Grid Reference System format:
|
|
||||||
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
|
||||||
E is easting, N is northing
|
|
||||||
"""
|
|
||||||
OLC: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 4
|
|
||||||
"""
|
|
||||||
Open Location Code (aka Plus Codes).
|
|
||||||
"""
|
|
||||||
OSGR: Config.DisplayConfig.GpsCoordinateFormat.ValueType # 5
|
|
||||||
"""
|
|
||||||
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
|
||||||
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
|
||||||
E is the easting, N is the northing
|
|
||||||
"""
|
|
||||||
|
|
||||||
class _DisplayUnits:
|
class _DisplayUnits:
|
||||||
ValueType = typing.NewType("ValueType", builtins.int)
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
@@ -1221,12 +1163,13 @@ class Config(google.protobuf.message.Message):
|
|||||||
WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int
|
WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int
|
||||||
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
|
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
|
||||||
USE_12H_CLOCK_FIELD_NUMBER: builtins.int
|
USE_12H_CLOCK_FIELD_NUMBER: builtins.int
|
||||||
|
USE_LONG_NODE_NAME_FIELD_NUMBER: builtins.int
|
||||||
screen_on_secs: builtins.int
|
screen_on_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
Number of seconds the screen stays on after pressing the user button or receiving a message
|
Number of seconds the screen stays on after pressing the user button or receiving a message
|
||||||
0 for default of one minute MAXUINT for always on
|
0 for default of one minute MAXUINT for always on
|
||||||
"""
|
"""
|
||||||
gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType
|
gps_format: global___Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType
|
||||||
"""
|
"""
|
||||||
Deprecated in 2.7.4: Unused
|
Deprecated in 2.7.4: Unused
|
||||||
How the GPS coordinates are formatted on the OLED screen.
|
How the GPS coordinates are formatted on the OLED screen.
|
||||||
@@ -1274,11 +1217,16 @@ class Config(google.protobuf.message.Message):
|
|||||||
If false (default), the device will display the time in 24-hour format on screen.
|
If false (default), the device will display the time in 24-hour format on screen.
|
||||||
If true, the device will display the time in 12-hour format on screen.
|
If true, the device will display the time in 12-hour format on screen.
|
||||||
"""
|
"""
|
||||||
|
use_long_node_name: builtins.bool
|
||||||
|
"""
|
||||||
|
If false (default), the device will use short names for various display screens.
|
||||||
|
If true, node names will show in long format
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
screen_on_secs: builtins.int = ...,
|
screen_on_secs: builtins.int = ...,
|
||||||
gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType = ...,
|
gps_format: global___Config.DisplayConfig.DeprecatedGpsCoordinateFormat.ValueType = ...,
|
||||||
auto_screen_carousel_secs: builtins.int = ...,
|
auto_screen_carousel_secs: builtins.int = ...,
|
||||||
compass_north_top: builtins.bool = ...,
|
compass_north_top: builtins.bool = ...,
|
||||||
flip_screen: builtins.bool = ...,
|
flip_screen: builtins.bool = ...,
|
||||||
@@ -1289,8 +1237,9 @@ class Config(google.protobuf.message.Message):
|
|||||||
wake_on_tap_or_motion: builtins.bool = ...,
|
wake_on_tap_or_motion: builtins.bool = ...,
|
||||||
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
|
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
|
||||||
use_12h_clock: builtins.bool = ...,
|
use_12h_clock: builtins.bool = ...,
|
||||||
|
use_long_node_name: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "use_long_node_name", b"use_long_node_name", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class LoRaConfig(google.protobuf.message.Message):
|
class LoRaConfig(google.protobuf.message.Message):
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/connection_status.proto\x12\x13meshtastic.protobuf\"\xd5\x02\n\x16\x44\x65viceConnectionStatus\x12<\n\x04wifi\x18\x01 \x01(\x0b\x32).meshtastic.protobuf.WifiConnectionStatusH\x00\x88\x01\x01\x12\x44\n\x08\x65thernet\x18\x02 \x01(\x0b\x32-.meshtastic.protobuf.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x46\n\tbluetooth\x18\x03 \x01(\x0b\x32..meshtastic.protobuf.BluetoothConnectionStatusH\x02\x88\x01\x01\x12@\n\x06serial\x18\x04 \x01(\x0b\x32+.meshtastic.protobuf.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"p\n\x14WifiConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"X\n\x18\x45thernetConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x65\n\x13\x63om.geeksville.meshB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n+meshtastic/protobuf/connection_status.proto\x12\x13meshtastic.protobuf\"\xd5\x02\n\x16\x44\x65viceConnectionStatus\x12<\n\x04wifi\x18\x01 \x01(\x0b\x32).meshtastic.protobuf.WifiConnectionStatusH\x00\x88\x01\x01\x12\x44\n\x08\x65thernet\x18\x02 \x01(\x0b\x32-.meshtastic.protobuf.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x46\n\tbluetooth\x18\x03 \x01(\x0b\x32..meshtastic.protobuf.BluetoothConnectionStatusH\x02\x88\x01\x01\x12@\n\x06serial\x18\x04 \x01(\x0b\x32+.meshtastic.protobuf.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"p\n\x14WifiConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"X\n\x18\x45thernetConnectionStatus\x12<\n\x06status\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x66\n\x14org.meshtastic.protoB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.connection_status_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.connection_status_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_DEVICECONNECTIONSTATUS']._serialized_start=69
|
_globals['_DEVICECONNECTIONSTATUS']._serialized_start=69
|
||||||
_globals['_DEVICECONNECTIONSTATUS']._serialized_end=410
|
_globals['_DEVICECONNECTIONSTATUS']._serialized_end=410
|
||||||
_globals['_WIFICONNECTIONSTATUS']._serialized_start=412
|
_globals['_WIFICONNECTIONSTATUS']._serialized_start=412
|
||||||
|
|||||||
@@ -13,28 +13,30 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xda\x04\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\x12\x36\n\x0c\x63ompass_mode\x18\x10 \x01(\x0e\x32 .meshtastic.protobuf.CompassMode\x12\x18\n\x10screen_rgb_color\x18\x11 \x01(\r\x12\x1b\n\x13is_clockface_analog\x18\x12 \x01(\x08\"\xa7\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t\"=\n\x08GeoPoint\x12\x0c\n\x04zoom\x18\x01 \x01(\x05\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"U\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\r\n\x05style\x18\x02 \x01(\t\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*>\n\x0b\x43ompassMode\x12\x0b\n\x07\x44YNAMIC\x10\x00\x12\x0e\n\nFIXED_RING\x10\x01\x12\x12\n\x0e\x46REEZE_HEADING\x10\x02*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xb4\x02\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\x12\t\n\x05\x43ZECH\x10\x12\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x63\n\x13\x63om.geeksville.meshB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xff\x05\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\x12\x36\n\x0c\x63ompass_mode\x18\x10 \x01(\x0e\x32 .meshtastic.protobuf.CompassMode\x12\x18\n\x10screen_rgb_color\x18\x11 \x01(\r\x12\x1b\n\x13is_clockface_analog\x18\x12 \x01(\x08\x12K\n\ngps_format\x18\x13 \x01(\x0e\x32\x37.meshtastic.protobuf.DeviceUIConfig.GpsCoordinateFormat\"V\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\x12\x07\n\x03MLS\x10\x06\"\xa7\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t\"=\n\x08GeoPoint\x12\x0c\n\x04zoom\x18\x01 \x01(\x05\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"U\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\r\n\x05style\x18\x02 \x01(\t\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*>\n\x0b\x43ompassMode\x12\x0b\n\x07\x44YNAMIC\x10\x00\x12\x0e\n\nFIXED_RING\x10\x01\x12\x12\n\x0e\x46REEZE_HEADING\x10\x02*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xc0\x02\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\x12\t\n\x05\x43ZECH\x10\x12\x12\n\n\x06\x44\x41NISH\x10\x13\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x64\n\x14org.meshtastic.protoB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_ui_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_ui_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_COMPASSMODE']._serialized_start=1113
|
_globals['_COMPASSMODE']._serialized_start=1278
|
||||||
_globals['_COMPASSMODE']._serialized_end=1175
|
_globals['_COMPASSMODE']._serialized_end=1340
|
||||||
_globals['_THEME']._serialized_start=1177
|
_globals['_THEME']._serialized_start=1342
|
||||||
_globals['_THEME']._serialized_end=1214
|
_globals['_THEME']._serialized_end=1379
|
||||||
_globals['_LANGUAGE']._serialized_start=1217
|
_globals['_LANGUAGE']._serialized_start=1382
|
||||||
_globals['_LANGUAGE']._serialized_end=1525
|
_globals['_LANGUAGE']._serialized_end=1702
|
||||||
_globals['_DEVICEUICONFIG']._serialized_start=61
|
_globals['_DEVICEUICONFIG']._serialized_start=61
|
||||||
_globals['_DEVICEUICONFIG']._serialized_end=663
|
_globals['_DEVICEUICONFIG']._serialized_end=828
|
||||||
_globals['_NODEFILTER']._serialized_start=666
|
_globals['_DEVICEUICONFIG_GPSCOORDINATEFORMAT']._serialized_start=742
|
||||||
_globals['_NODEFILTER']._serialized_end=833
|
_globals['_DEVICEUICONFIG_GPSCOORDINATEFORMAT']._serialized_end=828
|
||||||
_globals['_NODEHIGHLIGHT']._serialized_start=835
|
_globals['_NODEFILTER']._serialized_start=831
|
||||||
_globals['_NODEHIGHLIGHT']._serialized_end=961
|
_globals['_NODEFILTER']._serialized_end=998
|
||||||
_globals['_GEOPOINT']._serialized_start=963
|
_globals['_NODEHIGHLIGHT']._serialized_start=1000
|
||||||
_globals['_GEOPOINT']._serialized_end=1024
|
_globals['_NODEHIGHLIGHT']._serialized_end=1126
|
||||||
_globals['_MAP']._serialized_start=1026
|
_globals['_GEOPOINT']._serialized_start=1128
|
||||||
_globals['_MAP']._serialized_end=1111
|
_globals['_GEOPOINT']._serialized_end=1189
|
||||||
|
_globals['_MAP']._serialized_start=1191
|
||||||
|
_globals['_MAP']._serialized_end=1276
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumT
|
|||||||
"""
|
"""
|
||||||
Czech
|
Czech
|
||||||
"""
|
"""
|
||||||
|
DANISH: _Language.ValueType # 19
|
||||||
|
"""
|
||||||
|
Danish
|
||||||
|
"""
|
||||||
SIMPLIFIED_CHINESE: _Language.ValueType # 30
|
SIMPLIFIED_CHINESE: _Language.ValueType # 30
|
||||||
"""
|
"""
|
||||||
Simplified Chinese (experimental)
|
Simplified Chinese (experimental)
|
||||||
@@ -259,6 +263,10 @@ CZECH: Language.ValueType # 18
|
|||||||
"""
|
"""
|
||||||
Czech
|
Czech
|
||||||
"""
|
"""
|
||||||
|
DANISH: Language.ValueType # 19
|
||||||
|
"""
|
||||||
|
Danish
|
||||||
|
"""
|
||||||
SIMPLIFIED_CHINESE: Language.ValueType # 30
|
SIMPLIFIED_CHINESE: Language.ValueType # 30
|
||||||
"""
|
"""
|
||||||
Simplified Chinese (experimental)
|
Simplified Chinese (experimental)
|
||||||
@@ -277,6 +285,91 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
|||||||
|
|
||||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
|
class _GpsCoordinateFormat:
|
||||||
|
ValueType = typing.NewType("ValueType", builtins.int)
|
||||||
|
V: typing_extensions.TypeAlias = ValueType
|
||||||
|
|
||||||
|
class _GpsCoordinateFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[DeviceUIConfig._GpsCoordinateFormat.ValueType], builtins.type):
|
||||||
|
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||||
|
DEC: DeviceUIConfig._GpsCoordinateFormat.ValueType # 0
|
||||||
|
"""
|
||||||
|
GPS coordinates are displayed in the normal decimal degrees format:
|
||||||
|
DD.DDDDDD DDD.DDDDDD
|
||||||
|
"""
|
||||||
|
DMS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 1
|
||||||
|
"""
|
||||||
|
GPS coordinates are displayed in the degrees minutes seconds format:
|
||||||
|
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||||
|
"""
|
||||||
|
UTM: DeviceUIConfig._GpsCoordinateFormat.ValueType # 2
|
||||||
|
"""
|
||||||
|
Universal Transverse Mercator format:
|
||||||
|
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||||
|
"""
|
||||||
|
MGRS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 3
|
||||||
|
"""
|
||||||
|
Military Grid Reference System format:
|
||||||
|
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||||
|
E is easting, N is northing
|
||||||
|
"""
|
||||||
|
OLC: DeviceUIConfig._GpsCoordinateFormat.ValueType # 4
|
||||||
|
"""
|
||||||
|
Open Location Code (aka Plus Codes).
|
||||||
|
"""
|
||||||
|
OSGR: DeviceUIConfig._GpsCoordinateFormat.ValueType # 5
|
||||||
|
"""
|
||||||
|
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||||
|
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||||
|
E is the easting, N is the northing
|
||||||
|
"""
|
||||||
|
MLS: DeviceUIConfig._GpsCoordinateFormat.ValueType # 6
|
||||||
|
"""
|
||||||
|
Maidenhead Locator System
|
||||||
|
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
|
||||||
|
"""
|
||||||
|
|
||||||
|
class GpsCoordinateFormat(_GpsCoordinateFormat, metaclass=_GpsCoordinateFormatEnumTypeWrapper):
|
||||||
|
"""
|
||||||
|
How the GPS coordinates are displayed on the OLED screen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEC: DeviceUIConfig.GpsCoordinateFormat.ValueType # 0
|
||||||
|
"""
|
||||||
|
GPS coordinates are displayed in the normal decimal degrees format:
|
||||||
|
DD.DDDDDD DDD.DDDDDD
|
||||||
|
"""
|
||||||
|
DMS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 1
|
||||||
|
"""
|
||||||
|
GPS coordinates are displayed in the degrees minutes seconds format:
|
||||||
|
DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||||
|
"""
|
||||||
|
UTM: DeviceUIConfig.GpsCoordinateFormat.ValueType # 2
|
||||||
|
"""
|
||||||
|
Universal Transverse Mercator format:
|
||||||
|
ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||||
|
"""
|
||||||
|
MGRS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 3
|
||||||
|
"""
|
||||||
|
Military Grid Reference System format:
|
||||||
|
ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||||
|
E is easting, N is northing
|
||||||
|
"""
|
||||||
|
OLC: DeviceUIConfig.GpsCoordinateFormat.ValueType # 4
|
||||||
|
"""
|
||||||
|
Open Location Code (aka Plus Codes).
|
||||||
|
"""
|
||||||
|
OSGR: DeviceUIConfig.GpsCoordinateFormat.ValueType # 5
|
||||||
|
"""
|
||||||
|
Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||||
|
Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||||
|
E is the easting, N is the northing
|
||||||
|
"""
|
||||||
|
MLS: DeviceUIConfig.GpsCoordinateFormat.ValueType # 6
|
||||||
|
"""
|
||||||
|
Maidenhead Locator System
|
||||||
|
Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
|
||||||
|
"""
|
||||||
|
|
||||||
VERSION_FIELD_NUMBER: builtins.int
|
VERSION_FIELD_NUMBER: builtins.int
|
||||||
SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int
|
SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int
|
||||||
SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int
|
SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int
|
||||||
@@ -295,6 +388,7 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
|||||||
COMPASS_MODE_FIELD_NUMBER: builtins.int
|
COMPASS_MODE_FIELD_NUMBER: builtins.int
|
||||||
SCREEN_RGB_COLOR_FIELD_NUMBER: builtins.int
|
SCREEN_RGB_COLOR_FIELD_NUMBER: builtins.int
|
||||||
IS_CLOCKFACE_ANALOG_FIELD_NUMBER: builtins.int
|
IS_CLOCKFACE_ANALOG_FIELD_NUMBER: builtins.int
|
||||||
|
GPS_FORMAT_FIELD_NUMBER: builtins.int
|
||||||
version: builtins.int
|
version: builtins.int
|
||||||
"""
|
"""
|
||||||
A version integer used to invalidate saved files when we make incompatible changes.
|
A version integer used to invalidate saved files when we make incompatible changes.
|
||||||
@@ -345,6 +439,10 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
|||||||
Clockface analog style
|
Clockface analog style
|
||||||
true for analog clockface, false for digital clockface
|
true for analog clockface, false for digital clockface
|
||||||
"""
|
"""
|
||||||
|
gps_format: global___DeviceUIConfig.GpsCoordinateFormat.ValueType
|
||||||
|
"""
|
||||||
|
How the GPS coordinates are formatted on the OLED screen.
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def node_filter(self) -> global___NodeFilter:
|
def node_filter(self) -> global___NodeFilter:
|
||||||
"""
|
"""
|
||||||
@@ -384,9 +482,10 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
|||||||
compass_mode: global___CompassMode.ValueType = ...,
|
compass_mode: global___CompassMode.ValueType = ...,
|
||||||
screen_rgb_color: builtins.int = ...,
|
screen_rgb_color: builtins.int = ...,
|
||||||
is_clockface_analog: builtins.bool = ...,
|
is_clockface_analog: builtins.bool = ...,
|
||||||
|
gps_format: global___DeviceUIConfig.GpsCoordinateFormat.ValueType = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def HasField(self, field_name: typing.Literal["map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ...
|
def HasField(self, field_name: typing.Literal["map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ...
|
||||||
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "compass_mode", b"compass_mode", "is_clockface_analog", b"is_clockface_analog", "language", b"language", "map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_rgb_color", b"screen_rgb_color", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "compass_mode", b"compass_mode", "gps_format", b"gps_format", "is_clockface_analog", b"is_clockface_analog", "language", b"language", "map_data", b"map_data", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_rgb_color", b"screen_rgb_color", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
|
||||||
|
|
||||||
global___DeviceUIConfig = DeviceUIConfig
|
global___DeviceUIConfig = DeviceUIConfig
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_tel
|
|||||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\x94\x02\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\xf0\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x10\n\x08next_hop\x18\x0c \x01(\r\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xa1\x03\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBm\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\x94\x02\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\xf0\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x10\n\x08next_hop\x18\x0c \x01(\r\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xa1\x03\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBn\n\x14org.meshtastic.protoB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceonly_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceonly_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
|
||||||
_USERLITE.fields_by_name['macaddr']._options = None
|
_USERLITE.fields_by_name['macaddr']._options = None
|
||||||
_USERLITE.fields_by_name['macaddr']._serialized_options = b'\030\001'
|
_USERLITE.fields_by_name['macaddr']._serialized_options = b'\030\001'
|
||||||
_DEVICESTATE.fields_by_name['no_save']._options = None
|
_DEVICESTATE.fields_by_name['no_save']._options = None
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\"s\n\nSensorData\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .meshtastic.protobuf.MessageType\x12\x15\n\x0b\x66loat_value\x18\x02 \x01(\x02H\x00\x12\x16\n\x0cuint32_value\x18\x03 \x01(\rH\x00\x42\x06\n\x04\x64\x61ta\"_\n\x12InterdeviceMessage\x12\x0e\n\x04nmea\x18\x01 \x01(\tH\x00\x12\x31\n\x06sensor\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.SensorDataH\x00\x42\x06\n\x04\x64\x61ta*\xd5\x01\n\x0bMessageType\x12\x07\n\x03\x41\x43K\x10\x00\x12\x15\n\x10\x43OLLECT_INTERVAL\x10\xa0\x01\x12\x0c\n\x07\x42\x45\x45P_ON\x10\xa1\x01\x12\r\n\x08\x42\x45\x45P_OFF\x10\xa2\x01\x12\r\n\x08SHUTDOWN\x10\xa3\x01\x12\r\n\x08POWER_ON\x10\xa4\x01\x12\x0f\n\nSCD41_TEMP\x10\xb0\x01\x12\x13\n\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n\tSCD41_CO2\x10\xb2\x01\x12\x0f\n\nAHT20_TEMP\x10\xb3\x01\x12\x13\n\x0e\x41HT20_HUMIDITY\x10\xb4\x01\x12\x0f\n\nTVOC_INDEX\x10\xb5\x01\x42\x66\n\x13\x63om.geeksville.meshB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%meshtastic/protobuf/interdevice.proto\x12\x13meshtastic.protobuf\"s\n\nSensorData\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .meshtastic.protobuf.MessageType\x12\x15\n\x0b\x66loat_value\x18\x02 \x01(\x02H\x00\x12\x16\n\x0cuint32_value\x18\x03 \x01(\rH\x00\x42\x06\n\x04\x64\x61ta\"_\n\x12InterdeviceMessage\x12\x0e\n\x04nmea\x18\x01 \x01(\tH\x00\x12\x31\n\x06sensor\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.SensorDataH\x00\x42\x06\n\x04\x64\x61ta*\xd5\x01\n\x0bMessageType\x12\x07\n\x03\x41\x43K\x10\x00\x12\x15\n\x10\x43OLLECT_INTERVAL\x10\xa0\x01\x12\x0c\n\x07\x42\x45\x45P_ON\x10\xa1\x01\x12\r\n\x08\x42\x45\x45P_OFF\x10\xa2\x01\x12\r\n\x08SHUTDOWN\x10\xa3\x01\x12\r\n\x08POWER_ON\x10\xa4\x01\x12\x0f\n\nSCD41_TEMP\x10\xb0\x01\x12\x13\n\x0eSCD41_HUMIDITY\x10\xb1\x01\x12\x0e\n\tSCD41_CO2\x10\xb2\x01\x12\x0f\n\nAHT20_TEMP\x10\xb3\x01\x12\x13\n\x0e\x41HT20_HUMIDITY\x10\xb4\x01\x12\x0f\n\nTVOC_INDEX\x10\xb5\x01\x42g\n\x14org.meshtastic.protoB\x11InterdeviceProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.interdevice_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.interdevice_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021InterdeviceProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021InterdeviceProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_MESSAGETYPE']._serialized_start=277
|
_globals['_MESSAGETYPE']._serialized_start=277
|
||||||
_globals['_MESSAGETYPE']._serialized_end=490
|
_globals['_MESSAGETYPE']._serialized_end=490
|
||||||
_globals['_SENSORDATA']._serialized_start=62
|
_globals['_SENSORDATA']._serialized_start=62
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
|
|||||||
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
|
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBd\n\x13\x63om.geeksville.meshB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBe\n\x14org.meshtastic.protoB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.localonly_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.localonly_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_LOCALCONFIG']._serialized_start=136
|
_globals['_LOCALCONFIG']._serialized_start=136
|
||||||
_globals['_LOCALCONFIG']._serialized_end=642
|
_globals['_LOCALCONFIG']._serialized_end=642
|
||||||
_globals['_LOCALMODULECONFIG']._serialized_start=645
|
_globals['_LOCALMODULECONFIG']._serialized_start=645
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -453,9 +453,9 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
Seeed Tracker L1 EINK driver
|
Seeed Tracker L1 EINK driver
|
||||||
"""
|
"""
|
||||||
QWANTZ_TINY_ARMS: _HardwareModel.ValueType # 101
|
MUZI_R1_NEO: _HardwareModel.ValueType # 101
|
||||||
"""
|
"""
|
||||||
Reserved ID for future and past use
|
Muzi Works R1 Neo
|
||||||
"""
|
"""
|
||||||
T_DECK_PRO: _HardwareModel.ValueType # 102
|
T_DECK_PRO: _HardwareModel.ValueType # 102
|
||||||
"""
|
"""
|
||||||
@@ -465,9 +465,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
Lilygo TLora Pager
|
Lilygo TLora Pager
|
||||||
"""
|
"""
|
||||||
GAT562_MESH_TRIAL_TRACKER: _HardwareModel.ValueType # 104
|
M5STACK_RESERVED: _HardwareModel.ValueType # 104
|
||||||
"""
|
"""
|
||||||
GAT562 Mesh Trial Tracker
|
M5Stack Reserved
|
||||||
|
0x68
|
||||||
"""
|
"""
|
||||||
WISMESH_TAG: _HardwareModel.ValueType # 105
|
WISMESH_TAG: _HardwareModel.ValueType # 105
|
||||||
"""
|
"""
|
||||||
@@ -494,6 +495,34 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
New Heltec LoRA32 with ESP32-S3 CPU
|
New Heltec LoRA32 with ESP32-S3 CPU
|
||||||
"""
|
"""
|
||||||
|
M5STACK_C6L: _HardwareModel.ValueType # 111
|
||||||
|
"""
|
||||||
|
M5Stack C6L
|
||||||
|
"""
|
||||||
|
M5STACK_CARDPUTER_ADV: _HardwareModel.ValueType # 112
|
||||||
|
"""
|
||||||
|
M5Stack Cardputer Adv
|
||||||
|
"""
|
||||||
|
HELTEC_WIRELESS_TRACKER_V2: _HardwareModel.ValueType # 113
|
||||||
|
"""
|
||||||
|
ESP32S3 main controller with GPS and TFT screen.
|
||||||
|
"""
|
||||||
|
T_WATCH_ULTRA: _HardwareModel.ValueType # 114
|
||||||
|
"""
|
||||||
|
LilyGo T-Watch Ultra
|
||||||
|
"""
|
||||||
|
THINKNODE_M3: _HardwareModel.ValueType # 115
|
||||||
|
"""
|
||||||
|
Elecrow ThinkNode M3
|
||||||
|
"""
|
||||||
|
WISMESH_TAP_V2: _HardwareModel.ValueType # 116
|
||||||
|
"""
|
||||||
|
RAK WISMESH_TAP_V2 with ESP32-S3 CPU
|
||||||
|
"""
|
||||||
|
RAK3401: _HardwareModel.ValueType # 117
|
||||||
|
"""
|
||||||
|
RAK3401
|
||||||
|
"""
|
||||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -930,9 +959,9 @@ SEEED_WIO_TRACKER_L1_EINK: HardwareModel.ValueType # 100
|
|||||||
"""
|
"""
|
||||||
Seeed Tracker L1 EINK driver
|
Seeed Tracker L1 EINK driver
|
||||||
"""
|
"""
|
||||||
QWANTZ_TINY_ARMS: HardwareModel.ValueType # 101
|
MUZI_R1_NEO: HardwareModel.ValueType # 101
|
||||||
"""
|
"""
|
||||||
Reserved ID for future and past use
|
Muzi Works R1 Neo
|
||||||
"""
|
"""
|
||||||
T_DECK_PRO: HardwareModel.ValueType # 102
|
T_DECK_PRO: HardwareModel.ValueType # 102
|
||||||
"""
|
"""
|
||||||
@@ -942,9 +971,10 @@ T_LORA_PAGER: HardwareModel.ValueType # 103
|
|||||||
"""
|
"""
|
||||||
Lilygo TLora Pager
|
Lilygo TLora Pager
|
||||||
"""
|
"""
|
||||||
GAT562_MESH_TRIAL_TRACKER: HardwareModel.ValueType # 104
|
M5STACK_RESERVED: HardwareModel.ValueType # 104
|
||||||
"""
|
"""
|
||||||
GAT562 Mesh Trial Tracker
|
M5Stack Reserved
|
||||||
|
0x68
|
||||||
"""
|
"""
|
||||||
WISMESH_TAG: HardwareModel.ValueType # 105
|
WISMESH_TAG: HardwareModel.ValueType # 105
|
||||||
"""
|
"""
|
||||||
@@ -971,6 +1001,34 @@ HELTEC_V4: HardwareModel.ValueType # 110
|
|||||||
"""
|
"""
|
||||||
New Heltec LoRA32 with ESP32-S3 CPU
|
New Heltec LoRA32 with ESP32-S3 CPU
|
||||||
"""
|
"""
|
||||||
|
M5STACK_C6L: HardwareModel.ValueType # 111
|
||||||
|
"""
|
||||||
|
M5Stack C6L
|
||||||
|
"""
|
||||||
|
M5STACK_CARDPUTER_ADV: HardwareModel.ValueType # 112
|
||||||
|
"""
|
||||||
|
M5Stack Cardputer Adv
|
||||||
|
"""
|
||||||
|
HELTEC_WIRELESS_TRACKER_V2: HardwareModel.ValueType # 113
|
||||||
|
"""
|
||||||
|
ESP32S3 main controller with GPS and TFT screen.
|
||||||
|
"""
|
||||||
|
T_WATCH_ULTRA: HardwareModel.ValueType # 114
|
||||||
|
"""
|
||||||
|
LilyGo T-Watch Ultra
|
||||||
|
"""
|
||||||
|
THINKNODE_M3: HardwareModel.ValueType # 115
|
||||||
|
"""
|
||||||
|
Elecrow ThinkNode M3
|
||||||
|
"""
|
||||||
|
WISMESH_TAP_V2: HardwareModel.ValueType # 116
|
||||||
|
"""
|
||||||
|
RAK WISMESH_TAP_V2 with ESP32-S3 CPU
|
||||||
|
"""
|
||||||
|
RAK3401: HardwareModel.ValueType # 117
|
||||||
|
"""
|
||||||
|
RAK3401
|
||||||
|
"""
|
||||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -874,6 +874,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
|
HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
|
||||||
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
||||||
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
|
||||||
|
DEVICE_TELEMETRY_ENABLED_FIELD_NUMBER: builtins.int
|
||||||
device_update_interval: builtins.int
|
device_update_interval: builtins.int
|
||||||
"""
|
"""
|
||||||
Interval in seconds of how often we should try to send our
|
Interval in seconds of how often we should try to send our
|
||||||
@@ -934,6 +935,11 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Enable/Disable the health telemetry module on-device display
|
Enable/Disable the health telemetry module on-device display
|
||||||
"""
|
"""
|
||||||
|
device_telemetry_enabled: builtins.bool
|
||||||
|
"""
|
||||||
|
Enable/Disable the device telemetry module to send metrics to the mesh
|
||||||
|
Note: We will still send telemtry to the connected phone / client every minute over the API
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -950,8 +956,9 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
health_measurement_enabled: builtins.bool = ...,
|
health_measurement_enabled: builtins.bool = ...,
|
||||||
health_update_interval: builtins.int = ...,
|
health_update_interval: builtins.int = ...,
|
||||||
health_screen_enabled: builtins.bool = ...,
|
health_screen_enabled: builtins.bool = ...,
|
||||||
|
device_telemetry_enabled: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_telemetry_enabled", b"device_telemetry_enabled", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
class CannedMessageConfig(google.protobuf.message.Message):
|
class CannedMessageConfig(google.protobuf.message.Message):
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
|
|||||||
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\x83\x04\n\tMapReport\x12\x11\n\tlong_name\x18\x01 \x01(\t\x12\x12\n\nshort_name\x18\x02 \x01(\t\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x18\n\x10\x66irmware_version\x18\x05 \x01(\t\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12\x1e\n\x16num_online_local_nodes\x18\r \x01(\r\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42_\n\x13\x63om.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mqtt.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"j\n\x0fServiceEnvelope\x12/\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\t\"\x83\x04\n\tMapReport\x12\x11\n\tlong_name\x18\x01 \x01(\t\x12\x12\n\nshort_name\x18\x02 \x01(\t\x12;\n\x04role\x18\x03 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x18\n\x10\x66irmware_version\x18\x05 \x01(\t\x12\x41\n\x06region\x18\x06 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12H\n\x0cmodem_preset\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x1b\n\x13has_default_channel\x18\x08 \x01(\x08\x12\x12\n\nlatitude_i\x18\t \x01(\x0f\x12\x13\n\x0blongitude_i\x18\n \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x0b \x01(\x05\x12\x1a\n\x12position_precision\x18\x0c \x01(\r\x12\x1e\n\x16num_online_local_nodes\x18\r \x01(\r\x12!\n\x19has_opted_report_location\x18\x0e \x01(\x08\x42`\n\x14org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mqtt_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.mqtt_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_SERVICEENVELOPE']._serialized_start=121
|
_globals['_SERVICEENVELOPE']._serialized_start=121
|
||||||
_globals['_SERVICEENVELOPE']._serialized_end=227
|
_globals['_SERVICEENVELOPE']._serialized_end=227
|
||||||
_globals['_MAPREPORT']._serialized_start=230
|
_globals['_MAPREPORT']._serialized_start=230
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/paxcount.proto\x12\x13meshtastic.protobuf\"5\n\x08Paxcount\x12\x0c\n\x04wifi\x18\x01 \x01(\r\x12\x0b\n\x03\x62le\x18\x02 \x01(\r\x12\x0e\n\x06uptime\x18\x03 \x01(\rBc\n\x13\x63om.geeksville.meshB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/paxcount.proto\x12\x13meshtastic.protobuf\"5\n\x08Paxcount\x12\x0c\n\x04wifi\x18\x01 \x01(\r\x12\x0b\n\x03\x62le\x18\x02 \x01(\r\x12\x0e\n\x06uptime\x18\x03 \x01(\rBd\n\x14org.meshtastic.protoB\x0ePaxcountProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.paxcount_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.paxcount_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PaxcountProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016PaxcountProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_PAXCOUNT']._serialized_start=59
|
_globals['_PAXCOUNT']._serialized_start=59
|
||||||
_globals['_PAXCOUNT']._serialized_end=112
|
_globals['_PAXCOUNT']._serialized_end=112
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xf6\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xf6\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42^\n\x14org.meshtastic.protoB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.portnums_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.portnums_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_PORTNUM']._serialized_start=60
|
_globals['_PORTNUM']._serialized_start=60
|
||||||
_globals['_PORTNUM']._serialized_end=690
|
_globals['_PORTNUM']._serialized_end=690
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBc\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBd\n\x14org.meshtastic.protoB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.powermon_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.powermon_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016PowerMonProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_POWERMON']._serialized_start=60
|
_globals['_POWERMON']._serialized_start=60
|
||||||
_globals['_POWERMON']._serialized_end=284
|
_globals['_POWERMON']._serialized_end=284
|
||||||
_globals['_POWERMON_STATE']._serialized_start=73
|
_globals['_POWERMON_STATE']._serialized_start=73
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)meshtastic/protobuf/remote_hardware.proto\x12\x13meshtastic.protobuf\"\xdf\x01\n\x0fHardwareMessage\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).meshtastic.protobuf.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x63\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)meshtastic/protobuf/remote_hardware.proto\x12\x13meshtastic.protobuf\"\xdf\x01\n\x0fHardwareMessage\x12\x37\n\x04type\x18\x01 \x01(\x0e\x32).meshtastic.protobuf.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x64\n\x14org.meshtastic.protoB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.remote_hardware_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.remote_hardware_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_HARDWAREMESSAGE']._serialized_start=67
|
_globals['_HARDWAREMESSAGE']._serialized_start=67
|
||||||
_globals['_HARDWAREMESSAGE']._serialized_end=290
|
_globals['_HARDWAREMESSAGE']._serialized_end=290
|
||||||
_globals['_HARDWAREMESSAGE_TYPE']._serialized_start=182
|
_globals['_HARDWAREMESSAGE_TYPE']._serialized_start=182
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/rtttl.proto\x12\x13meshtastic.protobuf\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBf\n\x13\x63om.geeksville.meshB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/rtttl.proto\x12\x13meshtastic.protobuf\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBg\n\x14org.meshtastic.protoB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.rtttl_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.rtttl_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_RTTTLCONFIG']._serialized_start=56
|
_globals['_RTTTLCONFIG']._serialized_start=56
|
||||||
_globals['_RTTTLCONFIG']._serialized_end=87
|
_globals['_RTTTLCONFIG']._serialized_end=87
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&meshtastic/protobuf/storeforward.proto\x12\x13meshtastic.protobuf\"\xc0\x07\n\x0fStoreAndForward\x12@\n\x02rr\x18\x01 \x01(\x0e\x32\x34.meshtastic.protobuf.StoreAndForward.RequestResponse\x12@\n\x05stats\x18\x02 \x01(\x0b\x32/.meshtastic.protobuf.StoreAndForward.StatisticsH\x00\x12?\n\x07history\x18\x03 \x01(\x0b\x32,.meshtastic.protobuf.StoreAndForward.HistoryH\x00\x12\x43\n\theartbeat\x18\x04 \x01(\x0b\x32..meshtastic.protobuf.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBj\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&meshtastic/protobuf/storeforward.proto\x12\x13meshtastic.protobuf\"\xc0\x07\n\x0fStoreAndForward\x12@\n\x02rr\x18\x01 \x01(\x0e\x32\x34.meshtastic.protobuf.StoreAndForward.RequestResponse\x12@\n\x05stats\x18\x02 \x01(\x0b\x32/.meshtastic.protobuf.StoreAndForward.StatisticsH\x00\x12?\n\x07history\x18\x03 \x01(\x0b\x32,.meshtastic.protobuf.StoreAndForward.HistoryH\x00\x12\x43\n\theartbeat\x18\x04 \x01(\x0b\x32..meshtastic.protobuf.StoreAndForward.HeartbeatH\x00\x12\x0e\n\x04text\x18\x05 \x01(\x0cH\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xbc\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x16\n\x12ROUTER_TEXT_DIRECT\x10\x08\x12\x19\n\x15ROUTER_TEXT_BROADCAST\x10\t\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBk\n\x14org.meshtastic.protoB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.storeforward_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.storeforward_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_STOREANDFORWARD']._serialized_start=64
|
_globals['_STOREANDFORWARD']._serialized_start=64
|
||||||
_globals['_STOREANDFORWARD']._serialized_end=1024
|
_globals['_STOREANDFORWARD']._serialized_end=1024
|
||||||
_globals['_STOREANDFORWARD_STATISTICS']._serialized_start=366
|
_globals['_STOREANDFORWARD_STATISTICS']._serialized_start=366
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -203,6 +203,10 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
TSL2561 light sensor
|
TSL2561 light sensor
|
||||||
"""
|
"""
|
||||||
|
BH1750: _TelemetrySensorType.ValueType # 45
|
||||||
|
"""
|
||||||
|
BH1750 light sensor
|
||||||
|
"""
|
||||||
|
|
||||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -389,6 +393,10 @@ TSL2561: TelemetrySensorType.ValueType # 44
|
|||||||
"""
|
"""
|
||||||
TSL2561 light sensor
|
TSL2561 light sensor
|
||||||
"""
|
"""
|
||||||
|
BH1750: TelemetrySensorType.ValueType # 45
|
||||||
|
"""
|
||||||
|
BH1750 light sensor
|
||||||
|
"""
|
||||||
global___TelemetrySensorType = TelemetrySensorType
|
global___TelemetrySensorType = TelemetrySensorType
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
@@ -1026,6 +1034,7 @@ class LocalStats(google.protobuf.message.Message):
|
|||||||
NUM_TX_RELAY_CANCELED_FIELD_NUMBER: builtins.int
|
NUM_TX_RELAY_CANCELED_FIELD_NUMBER: builtins.int
|
||||||
HEAP_TOTAL_BYTES_FIELD_NUMBER: builtins.int
|
HEAP_TOTAL_BYTES_FIELD_NUMBER: builtins.int
|
||||||
HEAP_FREE_BYTES_FIELD_NUMBER: builtins.int
|
HEAP_FREE_BYTES_FIELD_NUMBER: builtins.int
|
||||||
|
NUM_TX_DROPPED_FIELD_NUMBER: builtins.int
|
||||||
uptime_seconds: builtins.int
|
uptime_seconds: builtins.int
|
||||||
"""
|
"""
|
||||||
How long the device has been running since the last reboot (in seconds)
|
How long the device has been running since the last reboot (in seconds)
|
||||||
@@ -1080,6 +1089,10 @@ class LocalStats(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Number of bytes free in the heap
|
Number of bytes free in the heap
|
||||||
"""
|
"""
|
||||||
|
num_tx_dropped: builtins.int
|
||||||
|
"""
|
||||||
|
Number of packets that were dropped because the transmit queue was full.
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1096,8 +1109,9 @@ class LocalStats(google.protobuf.message.Message):
|
|||||||
num_tx_relay_canceled: builtins.int = ...,
|
num_tx_relay_canceled: builtins.int = ...,
|
||||||
heap_total_bytes: builtins.int = ...,
|
heap_total_bytes: builtins.int = ...,
|
||||||
heap_free_bytes: builtins.int = ...,
|
heap_free_bytes: builtins.int = ...,
|
||||||
|
num_tx_dropped: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "heap_free_bytes", b"heap_free_bytes", "heap_total_bytes", b"heap_total_bytes", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
|
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "heap_free_bytes", b"heap_free_bytes", "heap_total_bytes", b"heap_total_bytes", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_dropped", b"num_tx_dropped", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
|
||||||
|
|
||||||
global___LocalStats = LocalStats
|
global___LocalStats = LocalStats
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/xmodem.proto\x12\x13meshtastic.protobuf\"\xbf\x01\n\x06XModem\x12\x34\n\x07\x63ontrol\x18\x01 \x01(\x0e\x32#.meshtastic.protobuf.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x61\n\x13\x63om.geeksville.meshB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/xmodem.proto\x12\x13meshtastic.protobuf\"\xbf\x01\n\x06XModem\x12\x34\n\x07\x63ontrol\x18\x01 \x01(\x0e\x32#.meshtastic.protobuf.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x62\n\x14org.meshtastic.protoB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.xmodem_pb2', _globals)
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.xmodem_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\024org.meshtastic.protoB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_globals['_XMODEM']._serialized_start=58
|
_globals['_XMODEM']._serialized_start=58
|
||||||
_globals['_XMODEM']._serialized_end=249
|
_globals['_XMODEM']._serialized_end=249
|
||||||
_globals['_XMODEM_CONTROL']._serialized_start=166
|
_globals['_XMODEM_CONTROL']._serialized_start=166
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
__version__ = "3.0.0"
|
__version__ = "3.0.1"
|
||||||
__release_date__ = "2025-11-05"
|
__release_date__ = "2025-12-4"
|
||||||
|
|
||||||
|
|
||||||
def get_git_revision():
|
def get_git_revision():
|
||||||
|
|||||||
+112
-45
@@ -22,47 +22,68 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
|
"chat_title": "Chats:",
|
||||||
"replying_to": "Replying to:",
|
"replying_to": "Replying to:",
|
||||||
"view_packet_details": "View packet details"
|
"view_packet_details": "View packet details"
|
||||||
},
|
},
|
||||||
"nodelist": {
|
"nodelist": {
|
||||||
"search_placeholder": "Search by name or ID...",
|
"search_placeholder": "Search by name or ID...",
|
||||||
"all_roles": "All Roles",
|
"all_roles": "All Roles",
|
||||||
"all_channels": "All Channels",
|
"all_channels": "All Channels",
|
||||||
"all_hw_models": "All HW Models",
|
"all_hw": "All HW Models",
|
||||||
"all_firmware": "All Firmware",
|
"all_firmware": "All Firmware",
|
||||||
"export_csv": "Export CSV",
|
|
||||||
"clear_filters": "Clear Filters",
|
"show_favorites": "⭐ Show Favorites",
|
||||||
"showing": "Showing",
|
"show_all": "⭐ Show All",
|
||||||
"nodes": "nodes",
|
"export_csv": "Export CSV",
|
||||||
"short": "Short",
|
"clear_filters": "Clear Filters",
|
||||||
"long_name": "Long Name",
|
|
||||||
"hw_model": "HW Model",
|
"showing_nodes": "Showing",
|
||||||
"firmware": "Firmware",
|
"nodes_suffix": "nodes",
|
||||||
"role": "Role",
|
|
||||||
"last_lat": "Last Latitude",
|
"loading_nodes": "Loading nodes...",
|
||||||
"last_long": "Last Longitude",
|
"error_loading_nodes": "Error loading nodes",
|
||||||
"channel": "Channel",
|
"no_nodes_found": "No nodes found",
|
||||||
"last_update": "Last Update",
|
|
||||||
"loading_nodes": "Loading nodes...",
|
"short_name": "Short",
|
||||||
"no_nodes": "No nodes found",
|
"long_name": "Long Name",
|
||||||
"error_nodes": "Error loading nodes"
|
"hw_model": "HW Model",
|
||||||
|
"firmware": "Firmware",
|
||||||
|
"role": "Role",
|
||||||
|
"last_lat": "Last Latitude",
|
||||||
|
"last_long": "Last Longitude",
|
||||||
|
"channel": "Channel",
|
||||||
|
"last_seen": "Last Seen",
|
||||||
|
"favorite": "Favorite",
|
||||||
|
|
||||||
|
"time_just_now": "just now",
|
||||||
|
"time_min_ago": "min ago",
|
||||||
|
"time_hr_ago": "hr ago",
|
||||||
|
"time_day_ago": "day ago",
|
||||||
|
"time_days_ago": "days ago"
|
||||||
},
|
},
|
||||||
|
|
||||||
"net": {
|
"net": {
|
||||||
"number_of_checkins": "Number of Check-ins:",
|
"net_title": "Weekly Net:",
|
||||||
"view_packet_details": "View packet details",
|
"total_messages": "Number of messages:",
|
||||||
"view_all_packets_from_node": "View all packets from this node",
|
"view_packet_details": "More details"
|
||||||
"no_packets_found": "No packets found."
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"map": {
|
"map": {
|
||||||
"channel": "Channel:",
|
"show_routers_only": "Show Routers Only",
|
||||||
"model": "Model:",
|
"share_view": "Share This View",
|
||||||
"role": "Role:",
|
"reset_filters": "Reset Filters To Defaults",
|
||||||
|
"channel_label": "Channel:",
|
||||||
|
"model_label": "Model:",
|
||||||
|
"role_label": "Role:",
|
||||||
"last_seen": "Last seen:",
|
"last_seen": "Last seen:",
|
||||||
"firmware": "Firmware:",
|
"firmware": "Firmware:",
|
||||||
"show_routers_only": "Show Routers Only",
|
"link_copied": "Link Copied!",
|
||||||
"share_view": "Share This View"
|
"legend_traceroute": "Traceroute (with arrows)",
|
||||||
|
"legend_neighbor": "Neighbor"
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"stats":
|
"stats":
|
||||||
{
|
{
|
||||||
"mesh_stats_summary": "Mesh Statistics - Summary (all available in Database)",
|
"mesh_stats_summary": "Mesh Statistics - Summary (all available in Database)",
|
||||||
@@ -82,21 +103,20 @@
|
|||||||
"all_channels": "All Channels",
|
"all_channels": "All Channels",
|
||||||
"node_id": "Node ID"
|
"node_id": "Node ID"
|
||||||
},
|
},
|
||||||
"top":
|
"top": {
|
||||||
{
|
"top_traffic_nodes": "Top Nodes Traffic",
|
||||||
"top_traffic_nodes": "Top Traffic Nodes (last 24 hours)",
|
"channel": "Channel",
|
||||||
"chart_description_1": "This chart shows a bell curve (normal distribution) based on the total \"Times Seen\" values for all nodes. It helps visualize how frequently nodes are heard, relative to the average.",
|
"search": "Search",
|
||||||
"chart_description_2": "This \"Times Seen\" value is the closest that we can get to Mesh utilization by node.",
|
"search_placeholder": "Search nodes...",
|
||||||
"mean_label": "Mean:",
|
|
||||||
"stddev_label": "Standard Deviation:",
|
|
||||||
"long_name": "Long Name",
|
"long_name": "Long Name",
|
||||||
"short_name": "Short Name",
|
"short_name": "Short Name",
|
||||||
"channel": "Channel",
|
"packets_sent": "Sent (24h)",
|
||||||
"packets_sent": "Packets Sent",
|
"times_seen": "Seen (24h)",
|
||||||
"times_seen": "Times Seen",
|
"avg_gateways": "Avg Gateways",
|
||||||
"seen_percent": "Seen % of Mean",
|
"showing_nodes": "Showing",
|
||||||
"no_nodes": "No top traffic nodes available."
|
"nodes_suffix": "nodes"
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodegraph":
|
"nodegraph":
|
||||||
{
|
{
|
||||||
"channel_label": "Channel:",
|
"channel_label": "Channel:",
|
||||||
@@ -119,7 +139,6 @@
|
|||||||
"to": "To",
|
"to": "To",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"links": "Links",
|
"links": "Links",
|
||||||
|
|
||||||
"unknown_app": "UNKNOWN APP",
|
"unknown_app": "UNKNOWN APP",
|
||||||
"text_message": "Text Message",
|
"text_message": "Text Message",
|
||||||
"position": "Position",
|
"position": "Position",
|
||||||
@@ -131,11 +150,59 @@
|
|||||||
"telemetry": "Telemetry",
|
"telemetry": "Telemetry",
|
||||||
"trace_route": "Trace Route",
|
"trace_route": "Trace Route",
|
||||||
"neighbor_info": "Neighbor Info",
|
"neighbor_info": "Neighbor Info",
|
||||||
|
|
||||||
"direct_to_mqtt": "direct to MQTT",
|
"direct_to_mqtt": "direct to MQTT",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"map": "Map",
|
"map": "Map",
|
||||||
"graph": "Graph"
|
"graph": "Graph"
|
||||||
}
|
},
|
||||||
|
"node": {
|
||||||
|
"specifications": "Specifications",
|
||||||
|
"node_id": "Node ID",
|
||||||
|
"long_name": "Long Name",
|
||||||
|
"short_name": "Short Name",
|
||||||
|
"hw_model": "Hardware Model",
|
||||||
|
"firmware": "Firmware",
|
||||||
|
"role": "Role",
|
||||||
|
"channel": "Channel",
|
||||||
|
"latitude": "Latitude",
|
||||||
|
"longitude": "Longitude",
|
||||||
|
"last_update": "Last Update",
|
||||||
|
"battery_voltage": "Battery & Voltage",
|
||||||
|
"air_channel": "Air & Channel Utilization",
|
||||||
|
"environment": "Environment Metrics",
|
||||||
|
"neighbors_chart": "Neighbors (Signal-to-Noise)",
|
||||||
|
"expand": "Expand",
|
||||||
|
"export_csv": "Export CSV",
|
||||||
|
"time": "Time",
|
||||||
|
"packet_id": "Packet ID",
|
||||||
|
"from": "From",
|
||||||
|
"to": "To",
|
||||||
|
"port": "Port",
|
||||||
|
"direct_to_mqtt": "Direct to MQTT",
|
||||||
|
"all_broadcast": "All"
|
||||||
|
},
|
||||||
|
"packet": {
|
||||||
|
"loading": "Loading packet information...",
|
||||||
|
"packet_id_label": "Packet ID",
|
||||||
|
"from_node": "From Node",
|
||||||
|
"to_node": "To Node",
|
||||||
|
"channel": "Channel",
|
||||||
|
"port": "Port",
|
||||||
|
"raw_payload": "Raw Payload",
|
||||||
|
"decoded_telemetry": "Decoded Telemetry",
|
||||||
|
"location": "Location",
|
||||||
|
"seen_by": "Seen By",
|
||||||
|
"gateway": "Gateway",
|
||||||
|
"rssi": "RSSI",
|
||||||
|
"snr": "SNR",
|
||||||
|
"hops": "Hop",
|
||||||
|
"time": "Time",
|
||||||
|
"packet_source": "Packet Source",
|
||||||
|
"distance": "Distance",
|
||||||
|
"node_id_short": "Node ID",
|
||||||
|
"all_broadcast": "All",
|
||||||
|
"direct_to_mqtt": "Direct to MQTT"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+122
-71
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"base": {
|
"base": {
|
||||||
"conversations": "Conversaciones",
|
"chat": "Conversaciones",
|
||||||
"nodes": "Nodos",
|
"nodes": "Nodos",
|
||||||
"everything": "Mostrar Todo",
|
"everything": "Mostrar todo",
|
||||||
"graph": "Gráficos de la Malla",
|
"graphs": "Gráficos de la Malla",
|
||||||
"net": "Red Semanal",
|
"net": "Red Semanal",
|
||||||
"map": "Mapa en Vivo",
|
"map": "Mapa en Vivo",
|
||||||
"stats": "Estadísticas",
|
"stats": "Estadísticas",
|
||||||
"top": "Nodos con Mayor Tráfico",
|
"top": "Nodos con Mayor Tráfico",
|
||||||
"footer": "Visita <strong><a href=\"https://github.com/pablorevilla-meshtastic/meshview\">Meshview</a></strong> en Github.",
|
"footer": "Visita <strong><a href=\"https://github.com/pablorevilla-meshtastic/meshview\">Meshview</a></strong> en Github.",
|
||||||
"node id": "ID de Nodo",
|
"node_id": "ID de Nodo",
|
||||||
"go to node": "Ir al nodo",
|
"go_to_node": "Ir al nodo",
|
||||||
"all": "Todos",
|
"all": "Todos",
|
||||||
"portnum_options": {
|
"portnum_options": {
|
||||||
"1": "Mensaje de Texto",
|
"1": "Mensaje de Texto",
|
||||||
@@ -21,48 +21,65 @@
|
|||||||
"71": "Información de Vecinos"
|
"71": "Información de Vecinos"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"chat": {
|
"chat": {
|
||||||
"replying_to": "Respondiendo a:",
|
"chat_title": "Conversaciones:",
|
||||||
"view_packet_details": "Ver detalles del paquete"
|
"replying_to": "Respondiendo a:",
|
||||||
|
"view_packet_details": "Ver detalles del paquete"
|
||||||
},
|
},
|
||||||
|
|
||||||
"nodelist": {
|
"nodelist": {
|
||||||
"search_placeholder": "Buscar por nombre o ID...",
|
"search_placeholder": "Buscar por nombre o ID...",
|
||||||
"all_roles": "Todos los Roles",
|
"all_roles": "Todos los roles",
|
||||||
"all_channels": "Todos los Canales",
|
"all_channels": "Todos los canales",
|
||||||
"all_hw_models": "Todos los Modelos",
|
"all_hw": "Todos los modelos",
|
||||||
"all_firmware": "Todo el Firmware",
|
"all_firmware": "Todo el firmware",
|
||||||
|
"show_favorites": "⭐ Mostrar favoritos",
|
||||||
|
"show_all": "⭐ Mostrar todos",
|
||||||
"export_csv": "Exportar CSV",
|
"export_csv": "Exportar CSV",
|
||||||
"clear_filters": "Limpiar Filtros",
|
"clear_filters": "Limpiar filtros",
|
||||||
"showing": "Mostrando",
|
"showing_nodes": "Mostrando",
|
||||||
"nodes": "nodos",
|
"nodes_suffix": "nodos",
|
||||||
"short": "Corto",
|
"loading_nodes": "Cargando nodos...",
|
||||||
"long_name": "Largo",
|
"error_loading_nodes": "Error al cargar nodos",
|
||||||
"hw_model": "Modelo",
|
"no_nodes_found": "No se encontraron nodos",
|
||||||
|
"short_name": "Corto",
|
||||||
|
"long_name": "Nombre largo",
|
||||||
|
"hw_model": "Modelo HW",
|
||||||
"firmware": "Firmware",
|
"firmware": "Firmware",
|
||||||
"role": "Rol",
|
"role": "Rol",
|
||||||
"last_lat": "Última Latitud",
|
"last_lat": "Última latitud",
|
||||||
"last_long": "Última Longitud",
|
"last_long": "Última longitud",
|
||||||
"channel": "Canal",
|
"channel": "Canal",
|
||||||
"last_update": "Última Actualización",
|
"last_seen": "Última vez visto",
|
||||||
"loading_nodes": "Cargando nodos...",
|
"favorite": "Favorito",
|
||||||
"no_nodes": "No se encontraron nodos",
|
"time_just_now": "justo ahora",
|
||||||
"error_nodes": "Error al cargar nodos"
|
"time_min_ago": "min atrás",
|
||||||
|
"time_hr_ago": "h atrás",
|
||||||
|
"time_day_ago": "día atrás",
|
||||||
|
"time_days_ago": "días atrás"
|
||||||
},
|
},
|
||||||
|
|
||||||
"net": {
|
"net": {
|
||||||
"number_of_checkins": "Número de registros:",
|
"net_title": "Red Semanal:",
|
||||||
"view_packet_details": "Ver detalles del paquete",
|
"total_messages": "Número de mensajes:",
|
||||||
"view_all_packets_from_node": "Ver todos los paquetes de este nodo",
|
"view_packet_details": "Más Detalles"
|
||||||
"no_packets_found": "No se encontraron paquetes."
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"map": {
|
"map": {
|
||||||
"channel": "Canal:",
|
"filter_routers_only": "Mostrar solo enrutadores",
|
||||||
"model": "Modelo:",
|
"share_view": "Compartir esta vista",
|
||||||
"role": "Rol:",
|
"reset_filters": "Restablecer filtros",
|
||||||
|
"channel_label": "Canal:",
|
||||||
|
"model_label": "Modelo:",
|
||||||
|
"role_label": "Rol:",
|
||||||
"last_seen": "Visto por última vez:",
|
"last_seen": "Visto por última vez:",
|
||||||
"firmware": "Firmware:",
|
"firmware": "Firmware:",
|
||||||
"show_routers_only": "Mostrar solo enrutadores",
|
"link_copied": "¡Enlace copiado!",
|
||||||
"share_view": "Compartir esta vista"
|
"legend_traceroute": "Ruta de traceroute (flechas de dirección)",
|
||||||
|
"legend_neighbor": "Vínculo de vecinos"
|
||||||
},
|
},
|
||||||
|
|
||||||
"stats": {
|
"stats": {
|
||||||
"mesh_stats_summary": "Estadísticas de la Malla - Resumen (completas en la base de datos)",
|
"mesh_stats_summary": "Estadísticas de la Malla - Resumen (completas en la base de datos)",
|
||||||
"total_nodes": "Nodos Totales",
|
"total_nodes": "Nodos Totales",
|
||||||
@@ -80,22 +97,22 @@
|
|||||||
"export_csv": "Exportar CSV",
|
"export_csv": "Exportar CSV",
|
||||||
"all_channels": "Todos los Canales"
|
"all_channels": "Todos los Canales"
|
||||||
},
|
},
|
||||||
|
|
||||||
"top": {
|
"top": {
|
||||||
"top_traffic_nodes": "Tráfico (últimas 24 horas)",
|
"top_traffic_nodes": "Tráfico de Nodos (24h)",
|
||||||
"chart_description_1": "Este gráfico muestra una curva normal (distribución normal) basada en el valor total de \"Veces Visto\" para todos los nodos. Ayuda a visualizar con qué frecuencia se detectan los nodos en relación con el promedio.",
|
"channel": "Canal",
|
||||||
"chart_description_2": "Este valor de \"Veces Visto\" es lo más aproximado que tenemos al nivel de uso de la malla por nodo.",
|
"search": "Buscar",
|
||||||
"mean_label": "Media:",
|
"search_placeholder": "Buscar nodos...",
|
||||||
"stddev_label": "Desviación Estándar:",
|
|
||||||
"long_name": "Nombre Largo",
|
"long_name": "Nombre Largo",
|
||||||
"short_name": "Nombre Corto",
|
"short_name": "Nombre Corto",
|
||||||
"channel": "Canal",
|
"packets_sent": "Enviados (24h)",
|
||||||
"packets_sent": "Paquetes Enviados",
|
"times_seen": "Visto (24h)",
|
||||||
"times_seen": "Veces Visto",
|
"avg_gateways": "Promedio de Gateways",
|
||||||
"seen_percent": "% Visto respecto a la Media",
|
"showing_nodes": "Mostrando",
|
||||||
"no_nodes": "No hay nodos con mayor tráfico disponibles."
|
"nodes_suffix": "nodos"
|
||||||
},
|
},
|
||||||
"nodegraph":
|
|
||||||
{
|
"nodegraph": {
|
||||||
"channel_label": "Canal:",
|
"channel_label": "Canal:",
|
||||||
"search_placeholder": "Buscar nodo...",
|
"search_placeholder": "Buscar nodo...",
|
||||||
"search_button": "Buscar",
|
"search_button": "Buscar",
|
||||||
@@ -109,34 +126,68 @@
|
|||||||
"unknown": "Desconocido",
|
"unknown": "Desconocido",
|
||||||
"node_not_found": "¡Nodo no encontrado en el canal actual!"
|
"node_not_found": "¡Nodo no encontrado en el canal actual!"
|
||||||
},
|
},
|
||||||
"firehose":
|
|
||||||
{
|
|
||||||
"live_feed": "📡 Flujo en Vivo",
|
|
||||||
"pause": "Pausar",
|
|
||||||
"resume": "Continuar",
|
|
||||||
"time": "Hora",
|
|
||||||
"packet_id": "ID del Paquete",
|
|
||||||
"from": "De",
|
|
||||||
"to": "Para",
|
|
||||||
"port": "Puerto",
|
|
||||||
"links": "Enlaces",
|
|
||||||
|
|
||||||
"unknown_app": "APLICACIÓN DESCONOCIDA",
|
"firehose": {
|
||||||
"text_message": "Mensaje de Texto",
|
"live_feed": "📡 Flujo en vivo",
|
||||||
"position": "Posición",
|
"pause": "Pausar",
|
||||||
"node_info": "Información del Nodo",
|
"resume": "Reanudar",
|
||||||
"routing": "Enrutamiento",
|
"time": "Hora",
|
||||||
"administration": "Administración",
|
"packet_id": "ID de paquete",
|
||||||
"waypoint": "Punto de Ruta",
|
"from": "De",
|
||||||
"store_forward": "Almacenar y Reenviar",
|
"to": "A",
|
||||||
"telemetry": "Telemetría",
|
"port": "Puerto",
|
||||||
"trace_route": "Rastreo de Ruta",
|
"direct_to_mqtt": "Directo a MQTT",
|
||||||
"neighbor_info": "Información de Vecinos",
|
"all_broadcast": "Todos"
|
||||||
|
},
|
||||||
|
|
||||||
"direct_to_mqtt": "Directo a MQTT",
|
"node": {
|
||||||
"all": "Todos",
|
"specifications": "Especificaciones",
|
||||||
"map": "Mapa",
|
"node_id": "ID de Nodo",
|
||||||
"graph": "Gráfico"
|
"long_name": "Nombre Largo",
|
||||||
}
|
"short_name": "Nombre Corto",
|
||||||
|
"hw_model": "Modelo de Hardware",
|
||||||
|
"firmware": "Firmware",
|
||||||
|
"role": "Rol",
|
||||||
|
"channel": "Canal",
|
||||||
|
"latitude": "Latitud",
|
||||||
|
"longitude": "Longitud",
|
||||||
|
"last_update": "Última Actualización",
|
||||||
|
"battery_voltage": "Batería y voltaje",
|
||||||
|
"air_channel": "Utilización del aire y del canal",
|
||||||
|
"environment": "Métricas Ambientales",
|
||||||
|
"neighbors_chart": "Vecinos (Relación Señal/Ruido)",
|
||||||
|
"expand": "Ampliar",
|
||||||
|
"export_csv": "Exportar CSV",
|
||||||
|
"time": "Hora",
|
||||||
|
"packet_id": "ID del Paquete",
|
||||||
|
"from": "De",
|
||||||
|
"to": "A",
|
||||||
|
"port": "Puerto",
|
||||||
|
"direct_to_mqtt": "Directo a MQTT",
|
||||||
|
"all_broadcast": "Todos"
|
||||||
|
},
|
||||||
|
|
||||||
|
"packet": {
|
||||||
|
"loading": "Cargando información del paquete...",
|
||||||
|
"packet_id_label": "ID del Paquete",
|
||||||
|
"from_node": "De",
|
||||||
|
"to_node": "A",
|
||||||
|
"channel": "Canal",
|
||||||
|
"port": "Puerto",
|
||||||
|
"raw_payload": "Payload sin procesar",
|
||||||
|
"decoded_telemetry": "Telemetría Decodificada",
|
||||||
|
"location": "Ubicación",
|
||||||
|
"seen_by": "Visto por",
|
||||||
|
"gateway": "Gateway",
|
||||||
|
"rssi": "RSSI",
|
||||||
|
"snr": "SNR",
|
||||||
|
"hops": "Saltos",
|
||||||
|
"time": "Hora",
|
||||||
|
"packet_source": "Origen del Paquete",
|
||||||
|
"distance": "Distancia",
|
||||||
|
"node_id_short": "ID de Nodo",
|
||||||
|
"all_broadcast": "Todos",
|
||||||
|
"direct_to_mqtt": "Directo a MQTT",
|
||||||
|
"signal": "Señal"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>API Documentation - Config</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
|
|
||||||
<style>
|
|
||||||
body { margin: 0; background: #ffffff; color: #000; }
|
|
||||||
#swagger-ui { background: #ffffff; color: #000; }
|
|
||||||
.swagger-ui { background-color: #ffffff !important; color: #000 !important; }
|
|
||||||
.swagger-ui .topbar,
|
|
||||||
.swagger-ui .info,
|
|
||||||
.swagger-ui .opblock-summary-description,
|
|
||||||
.swagger-ui .parameters-col_description,
|
|
||||||
.swagger-ui .response-col_description { color: #000 !important; }
|
|
||||||
.swagger-ui .opblock { background-color: #f9f9f9 !important; border-color: #ddd !important; }
|
|
||||||
.swagger-ui .opblock-summary { background-color: #eaeaea !important; color: #000 !important; }
|
|
||||||
.swagger-ui .opblock-section-header { color: #000 !important; }
|
|
||||||
.swagger-ui .parameters,
|
|
||||||
.swagger-ui .response { background-color: #fafafa !important; color: #000 !important; }
|
|
||||||
.swagger-ui table { color: #000 !important; }
|
|
||||||
.swagger-ui a { color: #1a0dab !important; }
|
|
||||||
.swagger-ui input,
|
|
||||||
.swagger-ui select,
|
|
||||||
.swagger-ui textarea { background-color: #fff !important; color: #000 !important; border: 1px solid #ccc !important; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
const spec = {
|
|
||||||
openapi: "3.0.0",
|
|
||||||
info: {
|
|
||||||
title: "Site Config API",
|
|
||||||
version: "1.0.0",
|
|
||||||
description: "API for retrieving the site configuration. This endpoint does not take any parameters."
|
|
||||||
},
|
|
||||||
paths: {
|
|
||||||
"/api/config": {
|
|
||||||
get: {
|
|
||||||
summary: "Get site configuration",
|
|
||||||
description: "Returns the current site configuration object.",
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
site_config: {
|
|
||||||
type: "object",
|
|
||||||
additionalProperties: true,
|
|
||||||
example: {
|
|
||||||
site_name: "MeshView",
|
|
||||||
firehose_interval: 1000,
|
|
||||||
starting: "/nodes",
|
|
||||||
theme: "dark"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
description: "Server error",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
error: { type: "string", example: "Internal server error" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
SwaggerUIBundle({
|
|
||||||
spec,
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
presets: [
|
|
||||||
SwaggerUIBundle.presets.apis,
|
|
||||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
||||||
],
|
|
||||||
layout: "BaseLayout"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>API Documentation - Edges</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
#swagger-ui { background: #ffffff; color: #000; }
|
|
||||||
.swagger-ui { background-color: #ffffff !important; color: #000 !important; }
|
|
||||||
.swagger-ui .topbar,
|
|
||||||
.swagger-ui .info,
|
|
||||||
.swagger-ui .opblock-summary-description,
|
|
||||||
.swagger-ui .parameters-col_description,
|
|
||||||
.swagger-ui .response-col_description { color: #000 !important; }
|
|
||||||
.swagger-ui .opblock { background-color: #f9f9f9 !important; border-color: #ddd !important; }
|
|
||||||
.swagger-ui .opblock-summary { background-color: #eaeaea !important; color: #000 !important; }
|
|
||||||
.swagger-ui .opblock-section-header { color: #000 !important; }
|
|
||||||
.swagger-ui .parameters,
|
|
||||||
.swagger-ui .response { background-color: #fafafa !important; color: #000 !important; }
|
|
||||||
.swagger-ui table { color: #000 !important; }
|
|
||||||
.swagger-ui a { color: #1a0dab !important; }
|
|
||||||
.swagger-ui input,
|
|
||||||
.swagger-ui select,
|
|
||||||
.swagger-ui textarea { background-color: #fff !important; color: #000 !important; border: 1px solid #ccc !important; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
const spec = {
|
|
||||||
openapi: "3.0.0",
|
|
||||||
info: {
|
|
||||||
title: "Network Edges API",
|
|
||||||
version: "1.0.0",
|
|
||||||
description: "API for retrieving network edges derived from traceroutes and neighbor info packets, with optional type filtering."
|
|
||||||
},
|
|
||||||
paths: {
|
|
||||||
"/api/edges": {
|
|
||||||
get: {
|
|
||||||
summary: "Get network edges",
|
|
||||||
description: "Returns edges between nodes in the network. Optionally filter by type (`traceroute` or `neighbor`).",
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: "type",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Optional filter to only return edges of this type (`traceroute` or `neighbor`).",
|
|
||||||
schema: { type: "string", enum: ["traceroute", "neighbor"] }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
edges: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
from: { type: "integer", example: 101 },
|
|
||||||
to: { type: "integer", example: 102 },
|
|
||||||
type: { type: "string", example: "traceroute" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
description: "Invalid request parameters",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: { type: "object", properties: { error: { type: "string" } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
SwaggerUIBundle({
|
|
||||||
spec,
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
presets: [
|
|
||||||
SwaggerUIBundle.presets.apis,
|
|
||||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
||||||
],
|
|
||||||
layout: "BaseLayout"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>API Documentation - Nodes</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
|
|
||||||
<style>
|
|
||||||
body { margin: 0; background: #ffffff; color: #000; }
|
|
||||||
#swagger-ui { background: #ffffff; color: #000; }
|
|
||||||
.swagger-ui { background-color: #ffffff !important; color: #000 !important; }
|
|
||||||
.swagger-ui .topbar,
|
|
||||||
.swagger-ui .info,
|
|
||||||
.swagger-ui .opblock-summary-description,
|
|
||||||
.swagger-ui .parameters-col_description,
|
|
||||||
.swagger-ui .response-col_description { color: #000 !important; }
|
|
||||||
.swagger-ui .opblock { background-color: #f9f9f9 !important; border-color: #ddd !important; }
|
|
||||||
.swagger-ui .opblock-summary { background-color: #eaeaea !important; color: #000 !important; }
|
|
||||||
.swagger-ui .opblock-section-header { color: #000 !important; }
|
|
||||||
.swagger-ui .parameters,
|
|
||||||
.swagger-ui .response { background-color: #fafafa !important; color: #000 !important; }
|
|
||||||
.swagger-ui table { color: #000 !important; }
|
|
||||||
.swagger-ui a { color: #1a0dab !important; }
|
|
||||||
.swagger-ui input,
|
|
||||||
.swagger-ui select,
|
|
||||||
.swagger-ui textarea { background-color: #fff !important; color: #000 !important; border: 1px solid #ccc !important; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
const spec = {
|
|
||||||
openapi: "3.0.0",
|
|
||||||
info: {
|
|
||||||
title: "Network Nodes API",
|
|
||||||
version: "1.0.0",
|
|
||||||
description: "API for retrieving nodes in the network with optional filters by last seen time."
|
|
||||||
},
|
|
||||||
paths: {
|
|
||||||
"/api/nodes": {
|
|
||||||
get: {
|
|
||||||
summary: "Get network nodes",
|
|
||||||
description: "Returns a list of nodes with optional filtering by recent activity.",
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: "hours",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Return nodes seen in the last X hours.",
|
|
||||||
schema: { type: "integer", example: 24 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "days",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Return nodes seen in the last X days.",
|
|
||||||
schema: { type: "integer", example: 7 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "last_seen_after",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Return nodes last seen after this ISO8601 timestamp.",
|
|
||||||
schema: { type: "string", format: "date-time", example: "2025-08-25T14:00:00" }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
nodes: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
node_id: { type: "integer", example: 101 },
|
|
||||||
long_name: { type: "string", example: "Node Alpha" },
|
|
||||||
short_name: { type: "string", example: "A" },
|
|
||||||
channel: { type: "string", example: "2" },
|
|
||||||
last_seen: { type: "string", format: "date-time", example: "2025-08-25T12:00:00" },
|
|
||||||
last_lat: { type: "number", format: "float", example: 37.7749 },
|
|
||||||
last_long: { type: "number", format: "float", example: -122.4194 },
|
|
||||||
hardware: { type: "string", example: "Heltec V3" },
|
|
||||||
firmware: { type: "string", example: "1.0.5" },
|
|
||||||
role: { type: "string", example: "router" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
description: "Invalid request parameters",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: { type: "object", properties: { error: { type: "string" } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
description: "Server error",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: { type: "object", properties: { error: { type: "string" } } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
SwaggerUIBundle({
|
|
||||||
spec,
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
presets: [
|
|
||||||
SwaggerUIBundle.presets.apis,
|
|
||||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
||||||
],
|
|
||||||
layout: "BaseLayout"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>API Documentation - Packets</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
#swagger-ui {
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Override Swagger UI colors for white background */
|
|
||||||
.swagger-ui {
|
|
||||||
background-color: #ffffff !important;
|
|
||||||
color: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .topbar,
|
|
||||||
.swagger-ui .info,
|
|
||||||
.swagger-ui .opblock-summary-description,
|
|
||||||
.swagger-ui .parameters-col_description,
|
|
||||||
.swagger-ui .response-col_description {
|
|
||||||
color: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .opblock {
|
|
||||||
background-color: #f9f9f9 !important;
|
|
||||||
border-color: #ddd !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .opblock-summary {
|
|
||||||
background-color: #eaeaea !important;
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .opblock-section-header {
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .parameters,
|
|
||||||
.swagger-ui .response {
|
|
||||||
background-color: #fafafa !important;
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui table {
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui a {
|
|
||||||
color: #1a0dab !important; /* classic link blue */
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui input,
|
|
||||||
.swagger-ui select,
|
|
||||||
.swagger-ui textarea {
|
|
||||||
background-color: #fff !important;
|
|
||||||
color: #000 !important;
|
|
||||||
border: 1px solid #ccc !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
const spec = {
|
|
||||||
openapi: "3.0.0",
|
|
||||||
info: {
|
|
||||||
title: "Packets API",
|
|
||||||
version: "1.0.0",
|
|
||||||
description: "API for retrieving packet records with optional filters."
|
|
||||||
},
|
|
||||||
paths: {
|
|
||||||
"/api/packets": {
|
|
||||||
get: {
|
|
||||||
summary: "Get packets",
|
|
||||||
description: "Returns a list of recent packets, optionally filtered by a timestamp and limited by count.",
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: "limit",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Maximum number of packets to return. Default is 200.",
|
|
||||||
schema: {
|
|
||||||
type: "integer",
|
|
||||||
default: 200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "since",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Only return packets imported after this ISO8601 timestamp (e.g., `2025-08-12T14:15:20`).",
|
|
||||||
schema: {
|
|
||||||
type: "string",
|
|
||||||
format: "date-time"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
packets: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: { type: "integer", example: 196988973 },
|
|
||||||
from_node_id: { type: "integer", example: 2381019191 },
|
|
||||||
to_node_id: { type: "integer", example: 1234567890 },
|
|
||||||
portnum: { type: "integer", example: 1 },
|
|
||||||
import_time: { type: "string", format: "date-time", example: "2025-08-12T14:15:20.503827" },
|
|
||||||
payload: { type: "string", example: "Hello Mesh" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
description: "Internal server error",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
error: { type: "string", example: "Failed to fetch packets" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
SwaggerUIBundle({
|
|
||||||
spec,
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
presets: [
|
|
||||||
SwaggerUIBundle.presets.apis,
|
|
||||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
||||||
],
|
|
||||||
layout: "BaseLayout"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>API Documentation - Packet Stats</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
#swagger-ui {
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Override Swagger UI colors for white background */
|
|
||||||
.swagger-ui {
|
|
||||||
background-color: #ffffff !important;
|
|
||||||
color: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .topbar,
|
|
||||||
.swagger-ui .info,
|
|
||||||
.swagger-ui .opblock-summary-description,
|
|
||||||
.swagger-ui .parameters-col_description,
|
|
||||||
.swagger-ui .response-col_description {
|
|
||||||
color: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .opblock {
|
|
||||||
background-color: #f9f9f9 !important;
|
|
||||||
border-color: #ddd !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .opblock-summary {
|
|
||||||
background-color: #eaeaea !important;
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .opblock-section-header {
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui .parameters,
|
|
||||||
.swagger-ui .response {
|
|
||||||
background-color: #fafafa !important;
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui table {
|
|
||||||
color: #000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui a {
|
|
||||||
color: #1a0dab !important; /* classic link blue */
|
|
||||||
}
|
|
||||||
|
|
||||||
.swagger-ui input,
|
|
||||||
.swagger-ui select,
|
|
||||||
.swagger-ui textarea {
|
|
||||||
background-color: #fff !important;
|
|
||||||
color: #000 !important;
|
|
||||||
border: 1px solid #ccc !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
const spec = {
|
|
||||||
openapi: "3.0.0",
|
|
||||||
info: {
|
|
||||||
title: "Packet Statistics API",
|
|
||||||
version: "1.0.0",
|
|
||||||
description: "API for retrieving packet statistics over a given period with optional filters."
|
|
||||||
},
|
|
||||||
paths: {
|
|
||||||
"/api/stats": {
|
|
||||||
get: {
|
|
||||||
summary: "Get packet statistics",
|
|
||||||
description: "Returns packet statistics for a given period type and length, with optional filters.",
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: "period_type",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Type of period to group by (`hour` or `day`). Default is `hour`.",
|
|
||||||
schema: {
|
|
||||||
type: "string",
|
|
||||||
enum: ["hour", "day"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "length",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Number of periods to include. Default is 24.",
|
|
||||||
schema: {
|
|
||||||
type: "integer",
|
|
||||||
default: 24
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "channel",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Filter by channel name.",
|
|
||||||
schema: {
|
|
||||||
type: "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "portnum",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Filter by port number.",
|
|
||||||
schema: {
|
|
||||||
type: "integer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "to_node",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Filter by destination node ID.",
|
|
||||||
schema: {
|
|
||||||
type: "integer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "from_node",
|
|
||||||
in: "query",
|
|
||||||
required: false,
|
|
||||||
description: "Filter by source node ID.",
|
|
||||||
schema: {
|
|
||||||
type: "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
responses: {
|
|
||||||
"200": {
|
|
||||||
description: "Successful response",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
hourly: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
period_type: { type: "string" },
|
|
||||||
length: { type: "integer" },
|
|
||||||
filters: { type: "object" },
|
|
||||||
data: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
period: { type: "string", example: "2025-08-06 19:00" },
|
|
||||||
node_id: { type: "integer" },
|
|
||||||
long_name: { type: "string" },
|
|
||||||
short_name: { type: "string" },
|
|
||||||
packets: { type: "integer" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
description: "Invalid request parameters",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
error: { type: "string" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
SwaggerUIBundle({
|
|
||||||
spec,
|
|
||||||
dom_id: '#swagger-ui',
|
|
||||||
presets: [
|
|
||||||
SwaggerUIBundle.presets.apis,
|
|
||||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
||||||
],
|
|
||||||
layout: "BaseLayout"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
+254
-72
@@ -1,91 +1,273 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<title>Meshview API Documentation</title>
|
||||||
<title>API Index</title>
|
<style>
|
||||||
<style>
|
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
background: #121212;
|
||||||
background-color: #1e1e1e;
|
color: #eee;
|
||||||
color: #eaeaea;
|
font-family: monospace;
|
||||||
margin: 0;
|
margin: 20px;
|
||||||
padding: 0;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
header {
|
h1, h2, h3 { color: #79c0ff; }
|
||||||
background: #2a2a2a;
|
code {
|
||||||
padding: 20px;
|
background: #1e1e1e;
|
||||||
text-align: center;
|
padding: 3px 6px;
|
||||||
font-size: 1.6em;
|
border-radius: 4px;
|
||||||
font-weight: bold;
|
color: #ffd479;
|
||||||
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
.container {
|
.endpoint {
|
||||||
max-width: 800px;
|
border: 1px solid #333;
|
||||||
margin: 30px auto;
|
padding: 12px;
|
||||||
padding: 20px;
|
margin-bottom: 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #1a1a1a;
|
||||||
}
|
}
|
||||||
ul {
|
.method {
|
||||||
list-style: none;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 6px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
li {
|
.get { background: #0066cc; }
|
||||||
background: #272b2f;
|
.path { font-weight: bold; color: #fff; }
|
||||||
border: 1px solid #474b4e;
|
table {
|
||||||
padding: 15px 20px;
|
width: 100%;
|
||||||
margin-bottom: 15px;
|
border-collapse: collapse;
|
||||||
border-radius: 10px;
|
margin-top: 8px;
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
}
|
||||||
li:hover {
|
th, td {
|
||||||
background: #33383d;
|
border: 1px solid #444;
|
||||||
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
a {
|
th {
|
||||||
color: #4cafef;
|
background: #222;
|
||||||
text-decoration: none;
|
color: #9ddcff;
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
}
|
||||||
p {
|
.example {
|
||||||
margin: 8px 0 0 0;
|
margin-top: 10px;
|
||||||
font-size: 0.9em;
|
padding: 10px;
|
||||||
color: #bbbbbb;
|
background: #161616;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #333;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header>
|
<h1>Meshview API Documentation</h1>
|
||||||
API Index
|
<p>This page describes all REST endpoints provided by Meshview.</p>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<ul>
|
<!------------------------------ NODES ------------------------------>
|
||||||
<li>
|
<h2>/api/nodes</h2>
|
||||||
<a href="/api-chat">Chat API</a>
|
|
||||||
<p> View chat messages.</p>
|
<div class="endpoint">
|
||||||
</li>
|
<span class="method get">GET</span>
|
||||||
<li>
|
<span class="path">/api/nodes</span>
|
||||||
<a href="/api-nodes">Node API</a>
|
<p>Returns a list of mesh nodes.</p>
|
||||||
<p>Retrieve node information.</p>
|
|
||||||
</li>
|
<h3>Query Parameters</h3>
|
||||||
<li>
|
<table>
|
||||||
<a href="/api-packets">Packet API</a>
|
<tr><th>Parameter</th><th>Description</th></tr>
|
||||||
<p>Access raw packet data.</p>
|
<tr><td>role</td><td>Filter by node role</td></tr>
|
||||||
</li>
|
<tr><td>channel</td><td>Filter by channel</td></tr>
|
||||||
<li>
|
<tr><td>hw_model</td><td>Hardware model filter</td></tr>
|
||||||
<a href="/api-stats">Statistics API </a>
|
<tr><td>days_active</td><td>Only nodes seen within X days</td></tr>
|
||||||
<p>View system and traffic statistics.</p>
|
</table>
|
||||||
</li>
|
|
||||||
<li>
|
<div class="example">
|
||||||
<a href="/api-edges">Edges API</a>
|
<b>Example:</b><br>
|
||||||
<p>Get edges details.</p>
|
<code>/api/nodes?days_active=3</code>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
|
||||||
<a href="/api-config">Configuration API</a>
|
|
||||||
<p>Get and update configuration details.</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ PACKETS ------------------------------>
|
||||||
|
<h2>/api/packets</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/packets</span>
|
||||||
|
<p>Fetch packets with many filters. Returns decoded packet data.</p>
|
||||||
|
|
||||||
|
<h3>Query Parameters</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Parameter</th><th>Description</th></tr>
|
||||||
|
<tr><td>packet_id</td><td>Return exactly one packet</td></tr>
|
||||||
|
<tr><td>limit</td><td>Max number of results (1–100)</td></tr>
|
||||||
|
<tr><td>since</td><td>Only packets newer than import_time_us</td></tr>
|
||||||
|
<tr><td>from_node_id</td><td>Filter by sender node</td></tr>
|
||||||
|
<tr><td>to_node_id</td><td>Filter by destination node</td></tr>
|
||||||
|
<tr><td>node_id</td><td>Legacy: match either from or to</td></tr>
|
||||||
|
<tr><td>portnum</td><td>Filter by port number</td></tr>
|
||||||
|
<tr><td>contains</td><td>Substring filter for payload</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<b>Example:</b><br>
|
||||||
|
<code>/api/packets?from_node_id=123&limit=100</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ PACKETS SEEN ------------------------------>
|
||||||
|
<h2>/api/packets_seen/{packet_id}</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/packets_seen/<packet_id></span>
|
||||||
|
<p>Returns list of gateways that heard the packet (RSSI/SNR/hops).</p>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<b>Example:</b><br>
|
||||||
|
<code>/api/packets_seen/3314808102</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ STATS ------------------------------>
|
||||||
|
<h2>/api/stats</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/stats</span>
|
||||||
|
<p>Returns aggregated packet statistics for a node or globally.</p>
|
||||||
|
|
||||||
|
<h3>Query Parameters</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Parameter</th><th>Description</th></tr>
|
||||||
|
<tr><td>period_type</td><td>"hour" or "day"</td></tr>
|
||||||
|
<tr><td>length</td><td>How many hours/days</td></tr>
|
||||||
|
<tr><td>node</td><td>Node ID for combined sent+seen stats</td></tr>
|
||||||
|
<tr><td>from_node</td><td>Filter by sender</td></tr>
|
||||||
|
<tr><td>to_node</td><td>Filter by receiver</td></tr>
|
||||||
|
<tr><td>portnum</td><td>Filter by port</td></tr>
|
||||||
|
<tr><td>channel</td><td>Filter by channel</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<b>Example:</b><br>
|
||||||
|
<code>/api/stats?node=1128180332&period_type=day&length=1</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ STATS COUNT ------------------------------>
|
||||||
|
<h2>/api/stats/count</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/stats/count</span>
|
||||||
|
<p>
|
||||||
|
Returns <b>total packets</b> and <b>total packet_seen entries</b>.
|
||||||
|
When no filters are provided, it returns global totals.
|
||||||
|
When filters are specified, they narrow the time, channel,
|
||||||
|
direction, or specific packet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Query Parameters</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Parameter</th><th>Description</th></tr>
|
||||||
|
<tr><td>period_type</td><td>"hour" or "day"</td></tr>
|
||||||
|
<tr><td>length</td><td>Number of hours or days (depends on period_type)</td></tr>
|
||||||
|
<tr><td>channel</td><td>Filter by channel</td></tr>
|
||||||
|
<tr><td>from_node</td><td>Only packets sent by this node</td></tr>
|
||||||
|
<tr><td>to_node</td><td>Only packets received by this node</td></tr>
|
||||||
|
<tr><td>packet_id</td><td>Filter seen counts for specific packet_id</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<b>Example:</b><br>
|
||||||
|
<code>/api/stats/count?from_node=1128180332&period_type=day&length=1</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ EDGES ------------------------------>
|
||||||
|
<h2>/api/edges</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/edges</span>
|
||||||
|
<p>Returns traceroute and/or neighbor edges for graph rendering.</p>
|
||||||
|
|
||||||
|
<h3>Query Parameters</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Parameter</th><th>Description</th></tr>
|
||||||
|
<tr><td>type</td><td>"traceroute", "neighbor", or omitted for both</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<b>Example:</b><br>
|
||||||
|
<code>/api/edges?type=neighbor</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ CONFIG ------------------------------>
|
||||||
|
<h2>/api/config</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/config</span>
|
||||||
|
<p>Returns Meshview configuration (site, MQTT, cleanup, etc.).</p>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<code>/api/config</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ LANG ------------------------------>
|
||||||
|
<h2>/api/lang</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/api/lang</span>
|
||||||
|
<p>Returns translated text for the UI.</p>
|
||||||
|
|
||||||
|
<h3>Parameters</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>lang</th><td>Language code (e.g. "en")</td></tr>
|
||||||
|
<tr><th>section</th><td>Optional UI section (firehose, map, top...)</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<code>/api/lang?lang=en§ion=firehose</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ HEALTH ------------------------------>
|
||||||
|
<h2>/health</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/health</span>
|
||||||
|
<p>Returns API + database status.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------ VERSION ------------------------------>
|
||||||
|
<h2>/version</h2>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<span class="method get">GET</span>
|
||||||
|
<span class="path">/version</span>
|
||||||
|
<p>Returns Meshview version and Git revision.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
<hr>
|
||||||
|
<p style="text-align:center; color:#666;">Meshview API — generated documentation</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+35
-16
@@ -190,32 +190,51 @@ body { margin: 0; font-family: monospace; background: #121212; color: #eee; }
|
|||||||
activeBlinks.set(marker,interval);
|
activeBlinks.set(marker,interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastImportTime=null;
|
let lastImportTimeUs = null;
|
||||||
|
|
||||||
async function fetchLatestPacket(){
|
async function fetchLatestPacket(){
|
||||||
try{
|
try{
|
||||||
const res=await fetch(`/api/packets?limit=1`);
|
const res = await fetch(`/api/packets?limit=1`);
|
||||||
const data=await res.json();
|
const data = await res.json();
|
||||||
lastImportTime=data.packets?.[0]?.import_time || new Date().toISOString();
|
lastImportTimeUs = data.packets?.[0]?.import_time_us || 0;
|
||||||
}catch(err){ console.error(err); }
|
}catch(err){
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchNewPackets(){
|
async function fetchNewPackets(){
|
||||||
if(!lastImportTime) return;
|
if (!lastImportTimeUs) return;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
const res=await fetch(`/api/packets?since=${lastImportTime}`);
|
const res = await fetch(`/api/packets?since=${lastImportTimeUs}`);
|
||||||
const data=await res.json();
|
const data = await res.json();
|
||||||
if(!data.packets || !data.packets.length) return;
|
if(!data.packets || !data.packets.length) return;
|
||||||
let latest=lastImportTime;
|
|
||||||
data.packets.forEach(packet=>{
|
let latest = lastImportTimeUs;
|
||||||
if(packet.import_time && packet.import_time>latest) latest=packet.import_time;
|
|
||||||
const marker=markerById[packet.from_node_id];
|
data.packets.forEach(packet => {
|
||||||
const nodeData=nodeMap.get(packet.from_node_id);
|
|
||||||
if(marker && nodeData) blinkNode(marker,nodeData.long_name,packet.portnum);
|
// Track newest microsecond timestamp
|
||||||
|
if (packet.import_time_us && packet.import_time_us > latest) {
|
||||||
|
latest = packet.import_time_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up marker and blink it
|
||||||
|
const marker = markerById[packet.from_node_id];
|
||||||
|
const nodeData = nodeMap.get(packet.from_node_id);
|
||||||
|
if (marker && nodeData) {
|
||||||
|
blinkNode(marker, nodeData.long_name, packet.portnum);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
lastImportTime=latest;
|
|
||||||
}catch(err){ console.error(err); }
|
lastImportTimeUs = latest;
|
||||||
|
|
||||||
|
}catch(err){
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(mapInterval>0){ fetchLatestPacket(); setInterval(fetchNewPackets,mapInterval*1000); }
|
if(mapInterval>0){ fetchLatestPacket(); setInterval(fetchNewPackets,mapInterval*1000); }
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Mesh Nodes Live Map</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
||||||
<style>
|
|
||||||
body { margin: 0; }
|
|
||||||
#map { height: 100vh; width: 100%; }
|
|
||||||
|
|
||||||
#legend {
|
|
||||||
position: absolute; bottom: 10px; right: 10px;
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
color: white; padding: 8px 12px;
|
|
||||||
font-family: monospace; font-size: 13px;
|
|
||||||
border-radius: 5px; z-index: 1000;
|
|
||||||
}
|
|
||||||
.legend-item { display: flex; align-items: center; margin-bottom: 4px; }
|
|
||||||
.legend-color { width: 16px; height: 16px; margin-right: 6px; border-radius: 4px; }
|
|
||||||
|
|
||||||
.pulse-label span {
|
|
||||||
background: rgba(0,0,0,0.6);
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
pointer-events: none;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="map"></div>
|
|
||||||
<div id="legend"></div>
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
||||||
<script>
|
|
||||||
const map = L.map("map");
|
|
||||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap" }).addTo(map);
|
|
||||||
|
|
||||||
const nodeMarkers = new Map();
|
|
||||||
let lastPacketTime = null;
|
|
||||||
|
|
||||||
const portColors = {
|
|
||||||
1:"red",
|
|
||||||
67:"cyan",
|
|
||||||
3:"orange",
|
|
||||||
70:"purple",
|
|
||||||
4:"yellow",
|
|
||||||
71:"brown",
|
|
||||||
73:"pink"
|
|
||||||
};
|
|
||||||
const portLabels = {
|
|
||||||
1:"Text",
|
|
||||||
67:"Telemetry",
|
|
||||||
3:"Position",
|
|
||||||
70:"Traceroute",
|
|
||||||
4:"Node Info",
|
|
||||||
71:"Neighbour Info",
|
|
||||||
73:"Map Report"
|
|
||||||
};
|
|
||||||
function getPulseColor(portnum) { return portColors[portnum] || "green"; }
|
|
||||||
|
|
||||||
// Legend
|
|
||||||
const legend = document.getElementById("legend");
|
|
||||||
for (const [port, color] of Object.entries(portColors)) {
|
|
||||||
const item = document.createElement("div");
|
|
||||||
item.className = "legend-item";
|
|
||||||
const colorBox = document.createElement("div");
|
|
||||||
colorBox.className = "legend-color";
|
|
||||||
colorBox.style.background = color;
|
|
||||||
const label = document.createElement("span");
|
|
||||||
label.textContent = `${portLabels[port] || "Custom"} (${port})`;
|
|
||||||
item.appendChild(colorBox);
|
|
||||||
item.appendChild(label);
|
|
||||||
legend.appendChild(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pulse marker
|
|
||||||
function pulseMarker(marker, highlightColor = "red") {
|
|
||||||
if (!marker || marker.activePulse) return;
|
|
||||||
marker.activePulse = true;
|
|
||||||
|
|
||||||
const originalColor = marker.options.originalColor;
|
|
||||||
const originalRadius = marker.options.originalRadius;
|
|
||||||
marker.bringToFront();
|
|
||||||
|
|
||||||
const nodeInfo = marker.options.nodeInfo || {};
|
|
||||||
const portLabel = marker.currentPortLabel || "";
|
|
||||||
const displayName = `${nodeInfo.long_name || nodeInfo.short_name || "Unknown"}${portLabel ? ` (<i>${portLabel}</i>)` : ""}`;
|
|
||||||
|
|
||||||
marker.bindTooltip(displayName, {
|
|
||||||
permanent: true,
|
|
||||||
direction: 'top',
|
|
||||||
className: 'pulse-label',
|
|
||||||
offset: [0, -10],
|
|
||||||
html: true
|
|
||||||
}).openTooltip();
|
|
||||||
|
|
||||||
const flashDuration = 2000, fadeDuration = 1000, flashInterval = 100, maxRadius = originalRadius + 5;
|
|
||||||
let flashTime = 0;
|
|
||||||
|
|
||||||
const flashTimer = setInterval(() => {
|
|
||||||
flashTime += flashInterval;
|
|
||||||
const isOn = (flashTime / flashInterval) % 2 === 0;
|
|
||||||
marker.setStyle({ fillColor: isOn ? highlightColor : originalColor, radius: isOn ? maxRadius : originalRadius });
|
|
||||||
|
|
||||||
if (flashTime >= flashDuration) {
|
|
||||||
clearInterval(flashTimer);
|
|
||||||
const fadeStart = performance.now();
|
|
||||||
function fade(now) {
|
|
||||||
const t = Math.min((now - fadeStart) / fadeDuration, 1);
|
|
||||||
const radius = originalRadius + (maxRadius - originalRadius) * (1 - t);
|
|
||||||
marker.setStyle({ fillColor: highlightColor, radius: radius, fillOpacity: 1 });
|
|
||||||
if (t < 1) requestAnimationFrame(fade);
|
|
||||||
else {
|
|
||||||
marker.setStyle({ fillColor: originalColor, radius: originalRadius, fillOpacity: 1 });
|
|
||||||
marker.unbindTooltip();
|
|
||||||
marker.activePulse = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestAnimationFrame(fade);
|
|
||||||
}
|
|
||||||
}, flashInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Load nodes ---
|
|
||||||
async function loadNodes() {
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/nodes");
|
|
||||||
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
|
|
||||||
const data = await res.json();
|
|
||||||
const nodes = data.nodes || [];
|
|
||||||
|
|
||||||
nodes.forEach(node => {
|
|
||||||
const lat = node.last_lat / 1e7;
|
|
||||||
const lng = node.last_long / 1e7;
|
|
||||||
if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
|
|
||||||
const color = "blue";
|
|
||||||
const marker = L.circleMarker([lat,lng], {
|
|
||||||
radius:7, color:"white", fillColor:color, fillOpacity:1, weight:0.7
|
|
||||||
}).addTo(map);
|
|
||||||
marker.options.originalColor = color;
|
|
||||||
marker.options.originalRadius = 7;
|
|
||||||
marker.options.nodeInfo = node;
|
|
||||||
marker.bindPopup(`<b>${node.long_name||node.short_name||"Unknown"}</b><br>ID: ${node.node_id}<br>Role: ${node.role}`);
|
|
||||||
nodeMarkers.set(node.node_id, marker);
|
|
||||||
} else {
|
|
||||||
nodeMarkers.set(node.node_id, {options:{nodeInfo:node}});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const markersWithCoords = Array.from(nodeMarkers.values()).filter(m=>m instanceof L.CircleMarker);
|
|
||||||
if(markersWithCoords.length>0) await setMapBoundsFromConfig();
|
|
||||||
else map.setView([37.77,-122.42],9);
|
|
||||||
|
|
||||||
} catch(err){
|
|
||||||
console.error("Failed to load nodes:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Map bounds ---
|
|
||||||
async function setMapBoundsFromConfig() {
|
|
||||||
try {
|
|
||||||
const res = await fetch("/api/config");
|
|
||||||
const config = await res.json();
|
|
||||||
const topLat = parseFloat(config.site.map_top_left_lat);
|
|
||||||
const topLon = parseFloat(config.site.map_top_left_lon);
|
|
||||||
const bottomLat = parseFloat(config.site.map_bottom_right_lat);
|
|
||||||
const bottomLon = parseFloat(config.site.map_bottom_right_lon);
|
|
||||||
|
|
||||||
if ([topLat, topLon, bottomLat, bottomLon].some(v => isNaN(v))) {
|
|
||||||
throw new Error("Map bounds contain NaN");
|
|
||||||
}
|
|
||||||
|
|
||||||
map.fitBounds([[topLat, topLon], [bottomLat, bottomLon]]);
|
|
||||||
} catch(err) {
|
|
||||||
console.error("Failed to load map bounds from config:", err);
|
|
||||||
map.setView([37.77,-122.42],9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Poll packets ---
|
|
||||||
async function pollPackets() {
|
|
||||||
try {
|
|
||||||
let url = "/api/packets?limit=10";
|
|
||||||
if (lastPacketTime) url += `&since=${lastPacketTime}`;
|
|
||||||
const res = await fetch(url);
|
|
||||||
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
|
|
||||||
const data = await res.json();
|
|
||||||
const packets = data.packets || [];
|
|
||||||
if (packets.length) lastPacketTime = packets[0].import_time;
|
|
||||||
|
|
||||||
packets.forEach(pkt => {
|
|
||||||
const marker = nodeMarkers.get(pkt.from_node_id);
|
|
||||||
if (marker instanceof L.CircleMarker) { // only real markers
|
|
||||||
marker.currentPortLabel = portLabels[pkt.portnum] || `${pkt.portnum}`;
|
|
||||||
pulseMarker(marker, getPulseColor(pkt.portnum));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch(err){
|
|
||||||
console.error("Failed to fetch packets:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Initialize ---
|
|
||||||
loadNodes().then(() => setInterval(pollPackets, 1000));
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
+21
-21
@@ -1,6 +1,5 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from sqlalchemy import select, and_, or_, func, cast, Text
|
||||||
from sqlalchemy import and_, func, or_, select, text
|
|
||||||
from sqlalchemy.orm import lazyload
|
from sqlalchemy.orm import lazyload
|
||||||
|
|
||||||
from meshview import database, models
|
from meshview import database, models
|
||||||
@@ -27,18 +26,12 @@ async def get_fuzzy_nodes(query):
|
|||||||
async def get_packets(
|
async def get_packets(
|
||||||
from_node_id=None,
|
from_node_id=None,
|
||||||
to_node_id=None,
|
to_node_id=None,
|
||||||
node_id=None, # legacy: match either from OR to
|
node_id=None, # legacy
|
||||||
portnum=None,
|
portnum=None,
|
||||||
after=None,
|
after=None,
|
||||||
contains=None, # NEW: SQL-level substring match
|
contains=None, # substring search
|
||||||
limit=50,
|
limit=50,
|
||||||
):
|
):
|
||||||
"""
|
|
||||||
SQLAlchemy 2.0 async ORM version.
|
|
||||||
Supports strict from/to/node filtering, substring payload search,
|
|
||||||
portnum, since, and limit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async with database.async_session() as session:
|
async with database.async_session() as session:
|
||||||
stmt = select(models.Packet)
|
stmt = select(models.Packet)
|
||||||
conditions = []
|
conditions = []
|
||||||
@@ -51,36 +44,40 @@ async def get_packets(
|
|||||||
if to_node_id is not None:
|
if to_node_id is not None:
|
||||||
conditions.append(models.Packet.to_node_id == to_node_id)
|
conditions.append(models.Packet.to_node_id == to_node_id)
|
||||||
|
|
||||||
# Legacy node ID filter: match either direction
|
# Legacy node_id (either direction)
|
||||||
if node_id is not None:
|
if node_id is not None:
|
||||||
conditions.append(
|
conditions.append(
|
||||||
or_(models.Packet.from_node_id == node_id, models.Packet.to_node_id == node_id)
|
or_(
|
||||||
|
models.Packet.from_node_id == node_id,
|
||||||
|
models.Packet.to_node_id == node_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Port filter
|
# Port filter
|
||||||
if portnum is not None:
|
if portnum is not None:
|
||||||
conditions.append(models.Packet.portnum == portnum)
|
conditions.append(models.Packet.portnum == portnum)
|
||||||
|
|
||||||
# Timestamp filter
|
# Timestamp filter using microseconds
|
||||||
if after is not None:
|
if after is not None:
|
||||||
conditions.append(models.Packet.import_time_us > after)
|
conditions.append(models.Packet.import_time_us > after)
|
||||||
|
|
||||||
# Case-insensitive substring search on UTF-8 payload (stored as BLOB)
|
# Case-insensitive substring search on payload (BLOB → TEXT)
|
||||||
if contains:
|
if contains:
|
||||||
contains_lower = contains.lower()
|
contains_lower = f"%{contains.lower()}%"
|
||||||
conditions.append(func.lower(models.Packet.payload).like(f"%{contains_lower}%"))
|
payload_text = cast(models.Packet.payload, Text)
|
||||||
|
conditions.append(func.lower(payload_text).like(contains_lower))
|
||||||
|
|
||||||
# Apply all conditions
|
# Apply WHERE conditions
|
||||||
if conditions:
|
if conditions:
|
||||||
stmt = stmt.where(and_(*conditions))
|
stmt = stmt.where(and_(*conditions))
|
||||||
|
|
||||||
# Order newest → oldest
|
# Order by newest first
|
||||||
stmt = stmt.order_by(models.Packet.import_time_us.desc())
|
stmt = stmt.order_by(models.Packet.import_time_us.desc())
|
||||||
|
|
||||||
# Apply limit
|
# Limit
|
||||||
stmt = stmt.limit(limit)
|
stmt = stmt.limit(limit)
|
||||||
|
|
||||||
# Execute query
|
# Run query
|
||||||
result = await session.execute(stmt)
|
result = await session.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
@@ -263,11 +260,12 @@ async def get_node_traffic(node_id: int):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def get_nodes(role=None, channel=None, hw_model=None, days_active=None):
|
async def get_nodes(node_id=None, role=None, channel=None, hw_model=None, days_active=None):
|
||||||
"""
|
"""
|
||||||
Fetches nodes from the database based on optional filtering criteria.
|
Fetches nodes from the database based on optional filtering criteria.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
node_id
|
||||||
role (str, optional): The role of the node (converted to uppercase for consistency).
|
role (str, optional): The role of the node (converted to uppercase for consistency).
|
||||||
channel (str, optional): The communication channel associated with the node.
|
channel (str, optional): The communication channel associated with the node.
|
||||||
hw_model (str, optional): The hardware model of the node.
|
hw_model (str, optional): The hardware model of the node.
|
||||||
@@ -283,6 +281,8 @@ async def get_nodes(role=None, channel=None, hw_model=None, days_active=None):
|
|||||||
query = select(Node)
|
query = select(Node)
|
||||||
|
|
||||||
# Apply filters based on provided parameters
|
# Apply filters based on provided parameters
|
||||||
|
if node_id is not None:
|
||||||
|
query = query.where(Node.node_id == node_id)
|
||||||
if role is not None:
|
if role is not None:
|
||||||
query = query.where(Node.role == role.upper()) # Ensure role is uppercase
|
query = query.where(Node.role == role.upper()) # Ensure role is uppercase
|
||||||
if channel is not None:
|
if channel is not None:
|
||||||
|
|||||||
@@ -53,8 +53,9 @@
|
|||||||
|
|
||||||
<!-- ⭐ CHAT TITLE WITH ICON, aligned to container ⭐ -->
|
<!-- ⭐ CHAT TITLE WITH ICON, aligned to container ⭐ -->
|
||||||
<div class="container px-2">
|
<div class="container px-2">
|
||||||
<h2 data-translate="chat_title" style="color:white; margin:0 0 10px 0;">
|
<h2 style="color:white; margin:0 0 10px 0;">
|
||||||
💬 Chat
|
<span class="icon">💬</span>
|
||||||
|
<span data-translate="chat_title"></span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -71,24 +72,45 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const packetMap = new Map();
|
const packetMap = new Map();
|
||||||
let chatLang = {};
|
let chatLang = {};
|
||||||
|
|
||||||
function applyTranslations(dict, root = document) {
|
/* ==========================================================
|
||||||
|
TRANSLATIONS FOR CHAT PAGE
|
||||||
|
========================================================== */
|
||||||
|
function applyTranslations(dict, root=document) {
|
||||||
root.querySelectorAll("[data-translate]").forEach(el => {
|
root.querySelectorAll("[data-translate]").forEach(el => {
|
||||||
const key = el.dataset.translate;
|
const key = el.dataset.translate;
|
||||||
const val = dict[key];
|
const val = dict[key];
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
if (el.placeholder) el.placeholder = val;
|
if (el.placeholder) el.placeholder = val;
|
||||||
else if (el.tagName === "INPUT" && el.value) el.value = val;
|
|
||||||
else if (key === "footer") el.innerHTML = val;
|
|
||||||
else el.textContent = val;
|
else el.textContent = val;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadChatLang() {
|
||||||
|
try {
|
||||||
|
const cfg = await window._siteConfigPromise;
|
||||||
|
const langCode = cfg?.site?.language || "en";
|
||||||
|
const res = await fetch(`/api/lang?lang=${langCode}§ion=chat`);
|
||||||
|
chatLang = await res.json();
|
||||||
|
|
||||||
|
// Apply to existing DOM
|
||||||
|
applyTranslations(chatLang);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Chat translation load failed:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================
|
||||||
|
SAFE HTML
|
||||||
|
========================================================== */
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.textContent = text ?? "";
|
div.textContent = text ?? "";
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================
|
||||||
|
RENDERING PACKETS
|
||||||
|
========================================================== */
|
||||||
function renderPacket(packet, highlight = false) {
|
function renderPacket(packet, highlight = false) {
|
||||||
if (renderedPacketIds.has(packet.id)) return;
|
if (renderedPacketIds.has(packet.id)) return;
|
||||||
renderedPacketIds.add(packet.id);
|
renderedPacketIds.add(packet.id);
|
||||||
@@ -139,20 +161,31 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "row chat-packet" + (highlight ? " flash" : "");
|
div.className = "row chat-packet" + (highlight ? " flash" : "");
|
||||||
div.dataset.packetId = packet.id;
|
div.dataset.packetId = packet.id;
|
||||||
|
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<span class="col-2 timestamp" title="${packet.import_time_us}">${formattedTimestamp}</span>
|
<span class="col-2 timestamp" title="${packet.import_time_us}">
|
||||||
|
${formattedTimestamp}
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="col-2 channel">
|
<span class="col-2 channel">
|
||||||
<a href="/packet/${packet.id}" title="${chatLang.view_packet_details || 'View details'}">🔎</a>
|
<a href="/packet/${packet.id}" title="${chatLang.view_packet_details || 'View details'}">🔎</a>
|
||||||
${escapeHtml(packet.channel || "")}
|
${escapeHtml(packet.channel || "")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="col-3 nodename">
|
<span class="col-3 nodename">
|
||||||
<a href="/node/${packet.from_node_id}">
|
<a href="/node/${packet.from_node_id}">
|
||||||
${escapeHtml((packet.long_name || "").trim() || `Node ${packet.from_node_id}`)}
|
${escapeHtml((packet.long_name || "").trim() || `Node ${packet.from_node_id}`)}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-5 message">${escapeHtml(packet.payload)}${replyHtml}</span>
|
|
||||||
|
<span class="col-5 message">
|
||||||
|
${escapeHtml(packet.payload)}${replyHtml}
|
||||||
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
chatContainer.prepend(div);
|
chatContainer.prepend(div);
|
||||||
|
|
||||||
|
// Translate newly added DOM
|
||||||
applyTranslations(chatLang, div);
|
applyTranslations(chatLang, div);
|
||||||
|
|
||||||
if (highlight) setTimeout(() => div.classList.remove("flash"), 2500);
|
if (highlight) setTimeout(() => div.classList.remove("flash"), 2500);
|
||||||
@@ -161,26 +194,27 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
function renderPacketsEnsureDescending(packets, highlight=false) {
|
function renderPacketsEnsureDescending(packets, highlight=false) {
|
||||||
if (!Array.isArray(packets) || packets.length===0) return;
|
if (!Array.isArray(packets) || packets.length===0) return;
|
||||||
const sortedDesc = packets.slice().sort((a,b)=>{
|
const sortedDesc = packets.slice().sort((a,b)=>{
|
||||||
const aTime =
|
const aTime = a.import_time_us || (new Date(a.import_time).getTime() * 1000);
|
||||||
(a.import_time_us && a.import_time_us > 0)
|
const bTime = b.import_time_us || (new Date(b.import_time).getTime() * 1000);
|
||||||
? a.import_time_us
|
|
||||||
: (a.import_time ? new Date(a.import_time).getTime() * 1000 : 0);
|
|
||||||
const bTime =
|
|
||||||
(b.import_time_us && b.import_time_us > 0)
|
|
||||||
? b.import_time_us
|
|
||||||
: (b.import_time ? new Date(b.import_time).getTime() * 1000 : 0);
|
|
||||||
return bTime - aTime;
|
return bTime - aTime;
|
||||||
});
|
});
|
||||||
for (let i=sortedDesc.length-1; i>=0; i--) renderPacket(sortedDesc[i], highlight);
|
for (let i=sortedDesc.length-1; i>=0; i--) {
|
||||||
|
renderPacket(sortedDesc[i], highlight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================
|
||||||
|
FETCHING PACKETS
|
||||||
|
========================================================== */
|
||||||
async function fetchInitial() {
|
async function fetchInitial() {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch("/api/packets?portnum=1&limit=100");
|
const resp = await fetch("/api/packets?portnum=1&limit=100");
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets);
|
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets);
|
||||||
lastTime = data?.latest_import_time || lastTime;
|
lastTime = data?.latest_import_time || lastTime;
|
||||||
} catch(err){ console.error("Initial fetch error:", err); }
|
} catch(err){
|
||||||
|
console.error("Initial fetch error:", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUpdates() {
|
async function fetchUpdates() {
|
||||||
@@ -192,21 +226,19 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets, true);
|
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets, true);
|
||||||
lastTime = data?.latest_import_time || lastTime;
|
lastTime = data?.latest_import_time || lastTime;
|
||||||
} catch(err){ console.error("Fetch updates error:", err); }
|
} catch(err){
|
||||||
|
console.error("Fetch updates error:", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadChatLang() {
|
/* ==========================================================
|
||||||
try {
|
INIT
|
||||||
const cfg = await window._siteConfigPromise;
|
========================================================== */
|
||||||
const langCode = cfg?.site?.language || "en";
|
await loadChatLang(); // load translations FIRST
|
||||||
const res = await fetch(`/api/lang?lang=${langCode}§ion=chat`);
|
await fetchInitial(); // then fetch initial packets
|
||||||
chatLang = await res.json();
|
|
||||||
applyTranslations(chatLang);
|
|
||||||
} catch(err){ console.error("Chat translation load failed:", err); }
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([loadChatLang(), fetchInitial()]);
|
|
||||||
setInterval(fetchUpdates, 5000);
|
setInterval(fetchUpdates, 5000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -12,6 +12,16 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.port-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Packet table */
|
||||||
.packet-table {
|
.packet-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -31,6 +41,7 @@
|
|||||||
.packet-table tr:nth-of-type(odd) { background-color: #272b2f; }
|
.packet-table tr:nth-of-type(odd) { background-color: #272b2f; }
|
||||||
.packet-table tr:nth-of-type(even) { background-color: #212529; }
|
.packet-table tr:nth-of-type(even) { background-color: #212529; }
|
||||||
|
|
||||||
|
/* Port tag */
|
||||||
.port-tag {
|
.port-tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 1px 6px;
|
padding: 1px 6px;
|
||||||
@@ -39,29 +50,22 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.port-0 { background-color: #6c757d; }
|
|
||||||
.port-1 { background-color: #007bff; }
|
|
||||||
.port-3 { background-color: #28a745; }
|
|
||||||
.port-4 { background-color: #ffc107; }
|
|
||||||
.port-5 { background-color: #dc3545; }
|
|
||||||
.port-6 { background-color: #20c997; }
|
|
||||||
.port-65 { background-color: #ff66b3; }
|
|
||||||
.port-67 { background-color: #17a2b8; }
|
|
||||||
.port-70 { background-color: #6f42c1; }
|
|
||||||
.port-71 { background-color: #fd7e14; }
|
|
||||||
|
|
||||||
.to-mqtt { font-style: italic; color: #aaa; }
|
.to-mqtt { font-style: italic; color: #aaa; }
|
||||||
|
|
||||||
|
/* Payload rows */
|
||||||
.payload-row { display: none; background-color: #1b1e22; }
|
.payload-row { display: none; background-color: #1b1e22; }
|
||||||
.payload-cell {
|
.payload-cell {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
color: #b0bec5;
|
color: #b0bec5;
|
||||||
border-top: none;
|
|
||||||
}
|
}
|
||||||
.packet-table tr.expanded + .payload-row { display: table-row; }
|
.packet-table tr.expanded + .payload-row {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle arrow */
|
||||||
.toggle-btn {
|
.toggle-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
.toggle-btn:hover { color: #fff; }
|
.toggle-btn:hover { color: #fff; }
|
||||||
|
|
||||||
/* Link next to port tag */
|
/* Inline link next to port tag */
|
||||||
.inline-link {
|
.inline-link {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -84,9 +88,16 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<form class="d-flex align-items-center justify-content-between mb-3">
|
<form class="d-flex align-items-center justify-content-between mb-3">
|
||||||
<h5 class="mb-0" data-translate-lang="live_feed">📡 Live Feed</h5>
|
<h2 class="mb-0" data-translate-lang="live_feed">📡 Live Feed</h2>
|
||||||
<button type="button" id="pause-button" class="btn btn-sm btn-outline-secondary" data-translate-lang="pause">Pause</button>
|
|
||||||
|
<button type="button"
|
||||||
|
id="pause-button"
|
||||||
|
class="btn btn-sm btn-outline-secondary"
|
||||||
|
data-translate-lang="pause">
|
||||||
|
Pause
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table class="packet-table">
|
<table class="packet-table">
|
||||||
@@ -101,34 +112,71 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="packet_list"></tbody>
|
<tbody id="packet_list"></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let lastImportTimeUs = null;
|
/* ======================================================
|
||||||
let updatesPaused = false;
|
FIREHOSE TRANSLATION SYSTEM (isolated from base)
|
||||||
let nodeMap = {};
|
====================================================== */
|
||||||
let updateInterval = 3000;
|
|
||||||
let firehoseTranslations = {};
|
let firehoseTranslations = {};
|
||||||
|
|
||||||
function applyTranslations(translations, root=document) {
|
function applyTranslationsFirehose(translations, root=document) {
|
||||||
root.querySelectorAll("[data-translate-lang]").forEach(el => {
|
root
|
||||||
const key = el.dataset.translateLang;
|
.querySelectorAll("[data-translate-lang]")
|
||||||
if (translations[key]) el.textContent = translations[key];
|
.forEach(el => {
|
||||||
});
|
const key = el.dataset.translateLang;
|
||||||
|
if (!translations[key]) return;
|
||||||
|
|
||||||
|
if (el.tagName === "INPUT" && el.placeholder !== undefined) {
|
||||||
|
el.placeholder = translations[key];
|
||||||
|
} else {
|
||||||
|
el.textContent = translations[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadTranslations() {
|
async function loadTranslationsFirehose() {
|
||||||
try {
|
try {
|
||||||
const cfg = await window._siteConfigPromise;
|
const cfg = await window._siteConfigPromise;
|
||||||
const langCode = cfg?.site?.language || "en";
|
const lang = cfg?.site?.language || "en";
|
||||||
const res = await fetch(`/api/lang?lang=${langCode}§ion=firehose`);
|
|
||||||
|
const res = await fetch(`/api/lang?lang=${lang}§ion=firehose`);
|
||||||
firehoseTranslations = await res.json();
|
firehoseTranslations = await res.json();
|
||||||
applyTranslations(firehoseTranslations, document);
|
|
||||||
|
applyTranslationsFirehose(firehoseTranslations);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Firehose translation load failed:", err);
|
console.error("Firehose translation load failed:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
NODE LOOKUP
|
||||||
|
====================================================== */
|
||||||
|
let nodeMap = {};
|
||||||
|
|
||||||
|
async function loadNodes() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/nodes");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
for (const n of data.nodes || []) {
|
||||||
|
nodeMap[n.node_id] = n.long_name || n.short_name || n.id || n.node_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeMap[4294967295] = firehoseTranslations.all_broadcast || "All";
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed loading nodes:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeName(id) {
|
||||||
|
return nodeMap[id] || id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
PORT COLORS & NAMES
|
||||||
|
====================================================== */
|
||||||
const PORT_MAP = {
|
const PORT_MAP = {
|
||||||
0: "UNKNOWN APP",
|
0: "UNKNOWN APP",
|
||||||
1: "Text Message",
|
1: "Text Message",
|
||||||
@@ -140,51 +188,38 @@ const PORT_MAP = {
|
|||||||
65: "Store Forward",
|
65: "Store Forward",
|
||||||
67: "Telemetry",
|
67: "Telemetry",
|
||||||
70: "Trace Route",
|
70: "Trace Route",
|
||||||
71: "Neighbor Info",
|
71: "Neighbor Info"
|
||||||
};
|
};
|
||||||
|
|
||||||
const PORT_COLORS = {
|
const PORT_COLORS = {
|
||||||
0: "#6c757d",
|
0: "#6c757d",
|
||||||
1: "#007bff",
|
1: "#007bff",
|
||||||
3: "#28a745",
|
3: "#28a745",
|
||||||
4: "#ffc107",
|
4: "#ffc107",
|
||||||
5: "#dc3545",
|
5: "#dc3545",
|
||||||
6: "#20c997",
|
6: "#20c997",
|
||||||
65: "#6610f2",
|
65: "#6610f2",
|
||||||
67: "#17a2b8",
|
67: "#17a2b8",
|
||||||
68: "#fd7e14",
|
68: "#fd7e14",
|
||||||
69: "#6f42c1",
|
69: "#6f42c1",
|
||||||
70: "#ff4444",
|
70: "#ff4444",
|
||||||
71: "#ff66cc",
|
71: "#ff66cc",
|
||||||
72: "#00cc99",
|
72: "#00cc99",
|
||||||
73: "#9999ff",
|
73: "#9999ff",
|
||||||
74: "#cc00cc",
|
74: "#cc00cc",
|
||||||
75: "#ffbb33",
|
75: "#ffbb33",
|
||||||
76: "#00bcd4",
|
76: "#00bcd4",
|
||||||
77: "#8bc34a",
|
77: "#8bc34a",
|
||||||
78: "#795548"
|
78: "#795548"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load node names
|
|
||||||
async function loadNodes() {
|
|
||||||
const res = await fetch("/api/nodes");
|
|
||||||
if (!res.ok) return;
|
|
||||||
const data = await res.json();
|
|
||||||
for (const n of data.nodes || []) {
|
|
||||||
nodeMap[n.node_id] = n.long_name || n.short_name || n.id || n.node_id;
|
|
||||||
}
|
|
||||||
nodeMap[4294967295] = "All";
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeName(id) {
|
|
||||||
if (id === 4294967295) return `<span class="to-mqtt">All</span>`;
|
|
||||||
return nodeMap[id] || id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function portLabel(portnum, payload, linksHtml) {
|
function portLabel(portnum, payload, linksHtml) {
|
||||||
const name = PORT_MAP[portnum] || "Unknown";
|
const name = PORT_MAP[portnum] || "Unknown";
|
||||||
const color = PORT_COLORS[portnum] || "#6c757d";
|
const color = PORT_COLORS[portnum] || "#6c757d";
|
||||||
const safePayload = payload ? payload.replace(/"/g, """) : "";
|
|
||||||
|
const safePayload = payload
|
||||||
|
? payload.replace(/"/g, """)
|
||||||
|
: "";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<span class="port-tag" style="background-color:${color}" title="${safePayload}">
|
<span class="port-tag" style="background-color:${color}" title="${safePayload}">
|
||||||
@@ -195,31 +230,46 @@ function portLabel(portnum, payload, linksHtml) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
TIME FORMAT
|
||||||
|
====================================================== */
|
||||||
function formatLocalTime(importTimeUs) {
|
function formatLocalTime(importTimeUs) {
|
||||||
const ms = importTimeUs / 1000;
|
const ms = importTimeUs / 1000;
|
||||||
const date = new Date(ms);
|
return new Date(ms).toLocaleTimeString([], {
|
||||||
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
FIREHOSE FETCHING
|
||||||
|
====================================================== */
|
||||||
|
let lastImportTimeUs = null;
|
||||||
|
let updatesPaused = false;
|
||||||
|
let updateInterval = 3000;
|
||||||
|
|
||||||
async function configureFirehose() {
|
async function configureFirehose() {
|
||||||
try {
|
try {
|
||||||
const cfg = await window._siteConfigPromise;
|
const cfg = await window._siteConfigPromise;
|
||||||
const intervalSec = cfg?.site?.firehose_interval;
|
const sec = cfg?.site?.firehose_interval;
|
||||||
if (intervalSec && !isNaN(intervalSec)) updateInterval = parseInt(intervalSec) * 1000;
|
if (sec && !isNaN(sec)) updateInterval = sec * 1000;
|
||||||
} catch (err) {
|
} catch {}
|
||||||
console.warn("Failed to read firehose interval:", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUpdates() {
|
async function fetchUpdates() {
|
||||||
if (updatesPaused) return;
|
if (updatesPaused) return;
|
||||||
|
|
||||||
const url = new URL("/api/packets", window.location.origin);
|
const url = new URL("/api/packets", window.location.origin);
|
||||||
if (lastImportTimeUs) url.searchParams.set("since", lastImportTimeUs);
|
|
||||||
url.searchParams.set("limit", 50);
|
url.searchParams.set("limit", 50);
|
||||||
|
|
||||||
|
if (lastImportTimeUs)
|
||||||
|
url.searchParams.set("since", lastImportTimeUs);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const packets = data.packets || [];
|
const packets = data.packets || [];
|
||||||
if (!packets.length) return;
|
if (!packets.length) return;
|
||||||
@@ -227,19 +277,34 @@ async function fetchUpdates() {
|
|||||||
const list = document.getElementById("packet_list");
|
const list = document.getElementById("packet_list");
|
||||||
|
|
||||||
for (const pkt of packets.reverse()) {
|
for (const pkt of packets.reverse()) {
|
||||||
const from = pkt.from_node_id === 4294967295
|
|
||||||
? `<span class="to-mqtt">All</span>`
|
|
||||||
: `<a href="/node/${pkt.from_node_id}" style="text-decoration:underline; color:inherit;">${nodeMap[pkt.from_node_id] || pkt.from_node_id}</a>`;
|
|
||||||
|
|
||||||
const to = pkt.to_node_id === 1
|
/* FROM — includes translation */
|
||||||
? `<span class="to-mqtt">direct to MQTT</span>`
|
const from =
|
||||||
: pkt.to_node_id === 4294967295
|
pkt.from_node_id === 4294967295
|
||||||
? `<span class="to-mqtt">All</span>`
|
? `<span class="to-mqtt" data-translate-lang="all_broadcast">
|
||||||
: `<a href="/node/${pkt.to_node_id}" style="text-decoration:underline; color:inherit;">${nodeMap[pkt.to_node_id] || pkt.to_node_id}</a>`;
|
${firehoseTranslations.all_broadcast || "All"}
|
||||||
|
</span>`
|
||||||
|
: `<a href="/node/${pkt.from_node_id}" style="text-decoration:underline; color:inherit;">
|
||||||
|
${nodeMap[pkt.from_node_id] || pkt.from_node_id}
|
||||||
|
</a>`;
|
||||||
|
|
||||||
|
/* TO — includes translation */
|
||||||
|
const to =
|
||||||
|
pkt.to_node_id === 1
|
||||||
|
? `<span class="to-mqtt" data-translate-lang="direct_to_mqtt">
|
||||||
|
${firehoseTranslations.direct_to_mqtt || "direct to MQTT"}
|
||||||
|
</span>`
|
||||||
|
: pkt.to_node_id === 4294967295
|
||||||
|
? `<span class="to-mqtt" data-translate-lang="all_broadcast">
|
||||||
|
${firehoseTranslations.all_broadcast || "All"}
|
||||||
|
</span>`
|
||||||
|
: `<a href="/node/${pkt.to_node_id}" style="text-decoration:underline; color:inherit;">
|
||||||
|
${nodeMap[pkt.to_node_id] || pkt.to_node_id}
|
||||||
|
</a>`;
|
||||||
|
|
||||||
// Inline link next to port tag
|
|
||||||
let inlineLinks = "";
|
let inlineLinks = "";
|
||||||
|
|
||||||
|
// Position link
|
||||||
if (pkt.portnum === 3 && pkt.payload) {
|
if (pkt.portnum === 3 && pkt.payload) {
|
||||||
const latMatch = pkt.payload.match(/latitude_i:\s*(-?\d+)/);
|
const latMatch = pkt.payload.match(/latitude_i:\s*(-?\d+)/);
|
||||||
const lonMatch = pkt.payload.match(/longitude_i:\s*(-?\d+)/);
|
const lonMatch = pkt.payload.match(/longitude_i:\s*(-?\d+)/);
|
||||||
@@ -247,36 +312,57 @@ async function fetchUpdates() {
|
|||||||
if (latMatch && lonMatch) {
|
if (latMatch && lonMatch) {
|
||||||
const lat = parseInt(latMatch[1]) / 1e7;
|
const lat = parseInt(latMatch[1]) / 1e7;
|
||||||
const lon = parseInt(lonMatch[1]) / 1e7;
|
const lon = parseInt(lonMatch[1]) / 1e7;
|
||||||
inlineLinks += ` <a class="inline-link" href="https://www.google.com/maps?q=${lat},${lon}" target="_blank">📍</a>`;
|
inlineLinks += ` <a class="inline-link"
|
||||||
|
href="https://www.google.com/maps?q=${lat},${lon}"
|
||||||
|
target="_blank">📍</a>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Traceroute link
|
||||||
if (pkt.portnum === 70) {
|
if (pkt.portnum === 70) {
|
||||||
let traceId = pkt.id;
|
let traceId = pkt.id;
|
||||||
const match = pkt.payload.match(/ID:\s*(\d+)/i);
|
const match = pkt.payload.match(/ID:\s*(\d+)/i);
|
||||||
if (match) traceId = match[1];
|
if (match) traceId = match[1];
|
||||||
|
|
||||||
inlineLinks += ` <a class="inline-link" href="/graph/traceroute/${traceId}" target="_blank">⮕</a>`;
|
inlineLinks += ` <a class="inline-link"
|
||||||
|
href="/graph/traceroute/${traceId}"
|
||||||
|
target="_blank">⮕</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const safePayload = (pkt.payload || "").replace(/</g, "<").replace(/>/g, ">");
|
const safePayload = (pkt.payload || "")
|
||||||
const localTime = formatLocalTime(pkt.import_time_us);
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">");
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
<tr class="packet-row" data-id="${pkt.id}">
|
<tr class="packet-row">
|
||||||
<td>${localTime}</td>
|
|
||||||
<td><span class="toggle-btn">▶</span> <a href="/packet/${pkt.id}" style="text-decoration:underline; color:inherit;">${pkt.id}</a></td>
|
<td>${formatLocalTime(pkt.import_time_us)}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="toggle-btn">▶</span>
|
||||||
|
<a href="/packet/${pkt.id}"
|
||||||
|
style="text-decoration:underline; color:inherit;">
|
||||||
|
${pkt.id}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td>${from}</td>
|
<td>${from}</td>
|
||||||
<td>${to}</td>
|
<td>${to}</td>
|
||||||
<td>${portLabel(pkt.portnum, pkt.payload, inlineLinks)}</td>
|
<td>${portLabel(pkt.portnum, pkt.payload, inlineLinks)}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="payload-row">
|
<tr class="payload-row">
|
||||||
<td colspan="5" class="payload-cell">${safePayload}</td>
|
<td colspan="5" class="payload-cell">${safePayload}</td>
|
||||||
</tr>`;
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
list.insertAdjacentHTML("afterbegin", html);
|
list.insertAdjacentHTML("afterbegin", html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Limit table size
|
||||||
while (list.rows.length > 400) list.deleteRow(-1);
|
while (list.rows.length > 400) list.deleteRow(-1);
|
||||||
|
|
||||||
lastImportTimeUs = packets[packets.length - 1].import_time_us;
|
lastImportTimeUs = packets[packets.length - 1].import_time_us;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -284,29 +370,40 @@ async function fetchUpdates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Initialize ---
|
/* ======================================================
|
||||||
|
INITIALIZE PAGE
|
||||||
|
====================================================== */
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
|
||||||
const pauseBtn = document.getElementById("pause-button");
|
const pauseBtn = document.getElementById("pause-button");
|
||||||
|
|
||||||
pauseBtn.addEventListener("click", () => {
|
pauseBtn.addEventListener("click", () => {
|
||||||
updatesPaused = !updatesPaused;
|
updatesPaused = !updatesPaused;
|
||||||
pauseBtn.textContent = updatesPaused
|
|
||||||
? (firehoseTranslations.resume || "Resume")
|
pauseBtn.textContent =
|
||||||
: (firehoseTranslations.pause || "Pause");
|
updatesPaused
|
||||||
|
? (firehoseTranslations.resume || "Resume")
|
||||||
|
: (firehoseTranslations.pause || "Pause");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", e => {
|
||||||
const btn = e.target.closest(".toggle-btn");
|
const btn = e.target.closest(".toggle-btn");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|
||||||
const row = btn.closest(".packet-row");
|
const row = btn.closest(".packet-row");
|
||||||
row.classList.toggle("expanded");
|
row.classList.toggle("expanded");
|
||||||
btn.textContent = row.classList.contains("expanded") ? "▼" : "▶";
|
|
||||||
|
btn.textContent =
|
||||||
|
row.classList.contains("expanded") ? "▼" : "▶";
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadTranslations();
|
await loadTranslationsFirehose();
|
||||||
await configureFirehose();
|
await configureFirehose();
|
||||||
await loadNodes();
|
await loadNodes();
|
||||||
|
|
||||||
fetchUpdates();
|
fetchUpdates();
|
||||||
setInterval(fetchUpdates, updateInterval);
|
setInterval(fetchUpdates, updateInterval);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+367
-143
@@ -7,59 +7,162 @@
|
|||||||
<style>
|
<style>
|
||||||
.legend { background:white;padding:8px;line-height:1.5;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,0.3);font-size:14px;color:black; }
|
.legend { background:white;padding:8px;line-height:1.5;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,0.3);font-size:14px;color:black; }
|
||||||
.legend i { width:12px;height:12px;display:inline-block;margin-right:6px;border-radius:50%; }
|
.legend i { width:12px;height:12px;display:inline-block;margin-right:6px;border-radius:50%; }
|
||||||
|
|
||||||
#filter-container { text-align:center;margin-top:10px; }
|
#filter-container { text-align:center;margin-top:10px; }
|
||||||
.filter-checkbox { margin:0 10px; }
|
.filter-checkbox { margin:0 10px; }
|
||||||
#share-button, #reset-filters-button {
|
|
||||||
|
#share-button,
|
||||||
|
#reset-filters-button {
|
||||||
padding:5px 15px;border:none;border-radius:4px;font-size:14px;cursor:pointer;color:white;
|
padding:5px 15px;border:none;border-radius:4px;font-size:14px;cursor:pointer;color:white;
|
||||||
}
|
}
|
||||||
#share-button { margin-left:20px; background-color:#4CAF50; }
|
#share-button { margin-left:20px; background-color:#4CAF50; }
|
||||||
#share-button:hover { background-color:#45a049; }
|
#share-button:hover { background-color:#45a049; }
|
||||||
#share-button:active { background-color:#3d8b40; }
|
#share-button:active { background-color:#3d8b40; }
|
||||||
|
|
||||||
#reset-filters-button { margin-left:10px; background-color:#f44336; }
|
#reset-filters-button { margin-left:10px; background-color:#f44336; }
|
||||||
#reset-filters-button:hover { background-color:#da190b; }
|
#reset-filters-button:hover { background-color:#da190b; }
|
||||||
#reset-filters-button:active { background-color:#c41e0d; }
|
#reset-filters-button:active { background-color:#c41e0d; }
|
||||||
|
|
||||||
.blinking-tooltip { background:white;color:black;border:1px solid black;border-radius:4px;padding:2px 5px; }
|
.blinking-tooltip { background:white;color:black;border:1px solid black;border-radius:4px;padding:2px 5px; }
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div id="map" style="width:100%;height:calc(100vh - 270px)"></div>
|
|
||||||
<div id="filter-container">
|
<div id="map" style="width:100%; height:calc(100vh - 270px)"></div>
|
||||||
<input type="checkbox" class="filter-checkbox" id="filter-routers-only"> Show Routers Only
|
|
||||||
|
<div id="map-legend"
|
||||||
|
class="legend"
|
||||||
|
style="position:absolute;
|
||||||
|
bottom:30px;
|
||||||
|
right:15px;
|
||||||
|
z-index:500;
|
||||||
|
pointer-events:none;">
|
||||||
|
<div>
|
||||||
|
<i style="background:orange; width:15px; height:3px; border-radius:0;"></i>
|
||||||
|
<span data-translate-lang="legend_traceroute">Traceroute Path (arrowed)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:6px;">
|
||||||
|
<i style="background:gray; width:15px; height:3px; border-radius:0;"></i>
|
||||||
|
<span data-translate-lang="legend_neighbor">Neighbor Link</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="filter-container">
|
||||||
|
<input type="checkbox" class="filter-checkbox" id="filter-routers-only">
|
||||||
|
<span data-translate-lang="show_routers_only">Show Routers Only</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="text-align:center;margin-top:5px;">
|
<div style="text-align:center;margin-top:5px;">
|
||||||
<button id="share-button" onclick="shareCurrentView()">🔗 Share This View</button>
|
<button id="share-button" onclick="shareCurrentView()" data-translate-lang="share_view">
|
||||||
<button id="reset-filters-button" onclick="resetFiltersToDefaults()">↺ Reset Filters To Defaults</button>
|
🔗 Share This View
|
||||||
|
</button>
|
||||||
|
<button id="reset-filters-button" onclick="resetFiltersToDefaults()" data-translate-lang="reset_filters">
|
||||||
|
↺ Reset Filters To Defaults
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||||
crossorigin=""></script>
|
crossorigin=""></script>
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylinedecorator.js"
|
<script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylinedecorator.js"
|
||||||
integrity="sha384-FhPn/2P/fJGhQLeNWDn9B/2Gml2bPOrKJwFqJXgR3xOPYxWg5mYQ5XZdhUSugZT0"
|
integrity="sha384-FhPn/2P/fJGhQLeNWDn9B/2Gml2bPOrKJwFqJXgR3xOPYxWg5mYQ5XZdhUSugZT0"
|
||||||
crossorigin></script>
|
crossorigin></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ---------------------- Map Initialization ----------------------
|
/* ======================================================
|
||||||
var map = L.map('map');
|
MAP PAGE TRANSLATION SYSTEM
|
||||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom:19, attribution:'© OpenStreetMap' }).addTo(map);
|
====================================================== */
|
||||||
|
|
||||||
// ---------------------- Globals ----------------------
|
let mapTranslations = {};
|
||||||
var nodes=[], markers={}, markerById={}, nodeMap = new Map();
|
|
||||||
var edgesData=[], edgeLayer = L.layerGroup().addTo(map), selectedNodeId = null;
|
async function loadTranslationsMap() {
|
||||||
|
try {
|
||||||
|
const cfg = await window._siteConfigPromise;
|
||||||
|
const lang = cfg?.site?.language || "en";
|
||||||
|
const res = await fetch(`/api/lang?lang=${lang}§ion=map`);
|
||||||
|
mapTranslations = await res.json();
|
||||||
|
applyTranslationsMap();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Map translation load failed:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTranslationsMap(root = document) {
|
||||||
|
root.querySelectorAll("[data-translate-lang]").forEach(el => {
|
||||||
|
const key = el.dataset.translateLang;
|
||||||
|
const val = mapTranslations[key];
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
if (el.tagName === "INPUT" && el.placeholder !== undefined) {
|
||||||
|
el.placeholder = val;
|
||||||
|
} else {
|
||||||
|
el.textContent = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
EXISTING MAP LOGIC
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
|
var map = L.map('map');
|
||||||
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
|
{ maxZoom:19, attribution:'© OpenStreetMap' }).addTo(map);
|
||||||
|
|
||||||
|
// Data structures
|
||||||
|
var nodes = [], markers = {}, markerById = {}, nodeMap = new Map();
|
||||||
|
var edgeLayer = L.layerGroup().addTo(map), selectedNodeId = null;
|
||||||
var activeBlinks = new Map(), lastImportTime = null;
|
var activeBlinks = new Map(), lastImportTime = null;
|
||||||
var mapInterval = 0;
|
var mapInterval = 0;
|
||||||
const portMap = {1:"Text",67:"Telemetry",3:"Position",70:"Traceroute",4:"Node Info",71:"Neighbour Info",73:"Map Report"};
|
|
||||||
const palette = ["#e6194b","#4363d8","#f58231","#911eb4","#46f0f0","#f032e6","#bcf60c","#fabebe","#008080","#e6beff","#9a6324","#fffac8","#800000","#aaffc3","#808000","#ffd8b1","#000075","#808080"];
|
const portMap = {
|
||||||
|
1:"Text",
|
||||||
|
67:"Telemetry",
|
||||||
|
3:"Position",
|
||||||
|
70:"Traceroute",
|
||||||
|
4:"Node Info",
|
||||||
|
71:"Neighbour Info",
|
||||||
|
73:"Map Report"
|
||||||
|
};
|
||||||
|
|
||||||
|
const palette = ["#e6194b","#4363d8","#f58231","#911eb4","#46f0f0","#f032e6","#bcf60c","#fabebe",
|
||||||
|
"#008080","#e6beff","#9a6324","#fffac8","#800000","#aaffc3","#808000","#ffd8b1",
|
||||||
|
"#000075","#808080"];
|
||||||
|
|
||||||
const colorMap = new Map(); let nextColorIndex = 0;
|
const colorMap = new Map(); let nextColorIndex = 0;
|
||||||
const channelSet = new Set();
|
const channelSet = new Set();
|
||||||
|
|
||||||
// ---------------------- Helpers ----------------------
|
map.on("popupopen", function (e) {
|
||||||
function timeAgo(date){ const diff=Date.now()-new Date(date), s=Math.floor(diff/1000), m=Math.floor(s/60), h=Math.floor(m/60), d=Math.floor(h/24); return d>0?d+"d":h>0?h+"h":m>0?m+"m":s+"s"; }
|
const popupEl = e.popup.getElement();
|
||||||
function hashToColor(str){ if(colorMap.has(str)) return colorMap.get(str); const c=palette[nextColorIndex++%palette.length]; colorMap.set(str,c); return c; }
|
if (popupEl) applyTranslationsMap(popupEl);
|
||||||
function isInvalidCoord(n){ return !n||!n.lat||!n.long||n.lat===0||n.long===0||Number.isNaN(n.lat)||Number.isNaN(n.long); }
|
});
|
||||||
|
|
||||||
|
function timeAgo(date){
|
||||||
|
const diff = Date.now() - new Date(date);
|
||||||
|
const s = Math.floor(diff/1000), m = Math.floor(s/60),
|
||||||
|
h = Math.floor(m/60), d = Math.floor(h/24);
|
||||||
|
return d>0?d+"d":h>0?h+"h":m>0?m+"m":s+"s";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashToColor(str){
|
||||||
|
if(colorMap.has(str)) return colorMap.get(str);
|
||||||
|
const c = palette[nextColorIndex++ % palette.length];
|
||||||
|
colorMap.set(str,c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInvalidCoord(n){
|
||||||
|
return !n || !n.lat || !n.long || n.lat === 0 || n.long === 0 ||
|
||||||
|
Number.isNaN(n.lat) || Number.isNaN(n.long);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
PACKET FETCHING (unchanged)
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
// ---------------------- Packet Fetching ----------------------
|
|
||||||
function fetchLatestPacket(){
|
function fetchLatestPacket(){
|
||||||
fetch(`/api/packets?limit=1`)
|
fetch(`/api/packets?limit=1`)
|
||||||
.then(r=>r.json())
|
.then(r=>r.json())
|
||||||
@@ -72,6 +175,7 @@ function fetchLatestPacket(){
|
|||||||
function fetchNewPackets(){
|
function fetchNewPackets(){
|
||||||
if(mapInterval <= 0) return;
|
if(mapInterval <= 0) return;
|
||||||
if(lastImportTime===null) return;
|
if(lastImportTime===null) return;
|
||||||
|
|
||||||
const url = new URL(`/api/packets`, window.location.origin);
|
const url = new URL(`/api/packets`, window.location.origin);
|
||||||
url.searchParams.set("since", lastImportTime);
|
url.searchParams.set("since", lastImportTime);
|
||||||
url.searchParams.set("limit", 50);
|
url.searchParams.set("limit", 50);
|
||||||
@@ -81,19 +185,22 @@ function fetchNewPackets(){
|
|||||||
.then(data=>{
|
.then(data=>{
|
||||||
if(!data.packets || data.packets.length===0) return;
|
if(!data.packets || data.packets.length===0) return;
|
||||||
let latest = lastImportTime;
|
let latest = lastImportTime;
|
||||||
|
|
||||||
data.packets.forEach(pkt=>{
|
data.packets.forEach(pkt=>{
|
||||||
if(pkt.import_time_us > latest) latest = pkt.import_time_us;
|
if(pkt.import_time_us > latest) latest = pkt.import_time_us;
|
||||||
|
|
||||||
const marker = markerById[pkt.from_node_id];
|
const marker = markerById[pkt.from_node_id];
|
||||||
const nodeData = nodeMap.get(pkt.from_node_id);
|
const nodeData = nodeMap.get(pkt.from_node_id);
|
||||||
if(marker && nodeData) blinkNode(marker,nodeData.long_name,pkt.portnum);
|
if(marker && nodeData) blinkNode(marker,nodeData.long_name,pkt.portnum);
|
||||||
});
|
});
|
||||||
|
|
||||||
lastImportTime = latest;
|
lastImportTime = latest;
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------- Polling ----------------------
|
|
||||||
let packetInterval=null;
|
let packetInterval=null;
|
||||||
|
|
||||||
function startPacketFetcher(){
|
function startPacketFetcher(){
|
||||||
if(mapInterval<=0) return;
|
if(mapInterval<=0) return;
|
||||||
if(!packetInterval){
|
if(!packetInterval){
|
||||||
@@ -101,65 +208,58 @@ function startPacketFetcher(){
|
|||||||
packetInterval=setInterval(fetchNewPackets,mapInterval*1000);
|
packetInterval=setInterval(fetchNewPackets,mapInterval*1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPacketFetcher(){
|
function stopPacketFetcher(){
|
||||||
if(packetInterval){
|
if(packetInterval){
|
||||||
clearInterval(packetInterval);
|
clearInterval(packetInterval);
|
||||||
packetInterval=null;
|
packetInterval=null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("visibilitychange",()=>{
|
document.addEventListener("visibilitychange",()=>{
|
||||||
document.hidden?stopPacketFetcher():startPacketFetcher();
|
document.hidden?stopPacketFetcher():startPacketFetcher();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------- WAIT FOR CONFIG ----------------------
|
|
||||||
async function waitForConfig() {
|
async function waitForConfig() {
|
||||||
while (typeof window._siteConfigPromise === "undefined") {
|
while (typeof window._siteConfigPromise === "undefined") {
|
||||||
console.log("Waiting for _siteConfigPromise...");
|
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cfg = await window._siteConfigPromise;
|
const cfg = await window._siteConfigPromise;
|
||||||
if (!cfg || !cfg.site) throw new Error("Config missing site object");
|
return cfg.site || {};
|
||||||
return cfg.site;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading site config:", err);
|
console.error("Error loading site config:", err);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------- Load Config & Start Polling ----------------------
|
|
||||||
async function initMapPolling() {
|
async function initMapPolling() {
|
||||||
try {
|
try {
|
||||||
const site = await waitForConfig();
|
const site = await waitForConfig();
|
||||||
mapInterval = parseInt(site.map_interval, 10) || 0;
|
mapInterval = parseInt(site.map_interval, 10) || 0;
|
||||||
|
|
||||||
// ---- Check URL params ----
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const lat = parseFloat(params.get('lat'));
|
const lat = parseFloat(params.get('lat'));
|
||||||
const lng = parseFloat(params.get('lng'));
|
const lng = parseFloat(params.get('lng'));
|
||||||
const zoom = parseInt(params.get('zoom'), 10);
|
const zoom = parseInt(params.get('zoom'), 10);
|
||||||
|
|
||||||
if (!isNaN(lat) && !isNaN(lng) && !isNaN(zoom)) {
|
if (!isNaN(lat) && !isNaN(lng) && !isNaN(zoom)) {
|
||||||
map.setView([lat, lng], zoom);
|
map.setView([lat, lng], zoom);
|
||||||
window.configBoundsApplied = true;
|
window.configBoundsApplied = true;
|
||||||
setTimeout(() => map.invalidateSize(), 100);
|
setTimeout(() => map.invalidateSize(), 100);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const topLeft = [parseFloat(site.map_top_left_lat), parseFloat(site.map_top_left_lon)];
|
const tl = [parseFloat(site.map_top_left_lat), parseFloat(site.map_top_left_lon)];
|
||||||
const bottomRight = [parseFloat(site.map_bottom_right_lat), parseFloat(site.map_bottom_right_lon)];
|
const br = [parseFloat(site.map_bottom_right_lat), parseFloat(site.map_bottom_right_lon)];
|
||||||
if (topLeft.every(isFinite) && bottomRight.every(isFinite)) {
|
|
||||||
map.fitBounds([topLeft, bottomRight]);
|
if (tl.every(isFinite) && br.every(isFinite)) {
|
||||||
|
map.fitBounds([tl, br]);
|
||||||
window.configBoundsApplied = true;
|
window.configBoundsApplied = true;
|
||||||
setTimeout(() => map.invalidateSize(), 100);
|
setTimeout(() => map.invalidateSize(), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapInterval > 0) {
|
if (mapInterval > 0) startPacketFetcher();
|
||||||
console.log(`Starting map polling every ${mapInterval}s`);
|
|
||||||
startPacketFetcher();
|
|
||||||
} else {
|
|
||||||
console.log("Map polling disabled (map_interval=0)");
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load /api/config:", err);
|
console.error("Failed to load /api/config:", err);
|
||||||
@@ -168,116 +268,222 @@ async function initMapPolling() {
|
|||||||
|
|
||||||
initMapPolling();
|
initMapPolling();
|
||||||
|
|
||||||
// ---------------------- Load Nodes + Edges ----------------------
|
/* ======================================================
|
||||||
fetch('/api/nodes?days_active=3').then(r=>r.json()).then(data=>{
|
LOAD NODES
|
||||||
if(!data.nodes) return;
|
====================================================== */
|
||||||
nodes = data.nodes.map(n=>({
|
|
||||||
key: n.node_id!==null?n.node_id:n.id,
|
fetch('/api/nodes?days_active=3')
|
||||||
id: n.id,
|
.then(r=>r.json())
|
||||||
node_id: n.node_id,
|
.then(data=>{
|
||||||
lat: n.last_lat?n.last_lat/1e7:null,
|
if(!data.nodes) return;
|
||||||
long: n.last_long?n.last_long/1e7:null,
|
|
||||||
long_name: n.long_name||"",
|
nodes = data.nodes.map(n=>({
|
||||||
short_name: n.short_name||"",
|
key: n.node_id ?? n.id,
|
||||||
channel: n.channel||"",
|
id: n.id,
|
||||||
hw_model: n.hw_model||"",
|
node_id: n.node_id,
|
||||||
role: n.role||"",
|
lat: n.last_lat ? n.last_lat/1e7 : null,
|
||||||
firmware: n.firmware||"",
|
long: n.last_long ? n.last_long/1e7 : null,
|
||||||
last_update: n.last_update||"",
|
long_name: n.long_name || "",
|
||||||
isRouter: n.role? n.role.toLowerCase().includes("router"):false
|
short_name: n.short_name || "",
|
||||||
}));
|
channel: n.channel || "",
|
||||||
nodes.forEach(n=>{ nodeMap.set(n.key,n); if(n.channel) channelSet.add(n.channel); });
|
hw_model: n.hw_model || "",
|
||||||
renderNodesOnMap();
|
role: n.role || "",
|
||||||
createChannelFilters();
|
firmware: n.firmware || "",
|
||||||
return fetch('/api/edges');
|
last_update: n.last_update || "",
|
||||||
}).then(r=>r?r.json():null).then(data=>{
|
isRouter: (n.role||"").toLowerCase().includes("router")
|
||||||
if(data && data.edges) edgesData=data.edges;
|
}));
|
||||||
}).catch(console.error);
|
|
||||||
|
nodes.forEach(n=>{
|
||||||
|
nodeMap.set(n.key, n);
|
||||||
|
if(n.channel) channelSet.add(n.channel);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderNodesOnMap();
|
||||||
|
createChannelFilters();
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
RENDER NODES
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
// ---------------------- Render Nodes ----------------------
|
|
||||||
function renderNodesOnMap(){
|
function renderNodesOnMap(){
|
||||||
const bounds = L.latLngBounds();
|
|
||||||
nodes.forEach(node=>{
|
nodes.forEach(node=>{
|
||||||
if(isInvalidCoord(node)) return;
|
if(isInvalidCoord(node)) return;
|
||||||
|
|
||||||
const color = hashToColor(node.channel);
|
const color = hashToColor(node.channel);
|
||||||
const opts = { radius: node.isRouter?9:7, color:"white", fillColor:color, fillOpacity:1, weight:0.7 };
|
|
||||||
const marker = L.circleMarker([node.lat,node.long],opts).addTo(map);
|
const marker = L.circleMarker([node.lat,node.long], {
|
||||||
|
radius: node.isRouter ? 9 : 7,
|
||||||
|
color: "white",
|
||||||
|
fillColor: color,
|
||||||
|
fillOpacity: 1,
|
||||||
|
weight: 0.7
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
marker.nodeId = node.key;
|
marker.nodeId = node.key;
|
||||||
marker.originalColor = color;
|
marker.originalColor = color;
|
||||||
markerById[node.key] = marker;
|
markerById[node.key] = marker;
|
||||||
const popup = `<b><a href="/node/${node.node_id}">${node.long_name}</a> (${node.short_name})</b><br>
|
|
||||||
<b>Channel:</b> ${node.channel}<br>
|
const popup = `
|
||||||
<b>Model:</b> ${node.hw_model}<br>
|
<b><a href="/node/${node.node_id}">${node.long_name}</a> (${node.short_name})</b><br>
|
||||||
<b>Role:</b> ${node.role}<br>
|
|
||||||
${node.last_update? `<b>Last seen:</b> ${timeAgo(node.last_update)}<br>`:""}
|
<b data-translate-lang="channel_label"></b> ${node.channel}<br>
|
||||||
${node.firmware? `<b>Firmware:</b> ${node.firmware}<br>`:""}`;
|
<b data-translate-lang="model_label"></b> ${node.hw_model}<br>
|
||||||
marker.on('click',()=>{ onNodeClick(node); marker.bindPopup(popup).openPopup(); setTimeout(()=>marker.closePopup(),3000); });
|
<b data-translate-lang="role_label"></b> ${node.role}<br>
|
||||||
bounds.extend(marker.getLatLng());
|
|
||||||
|
${
|
||||||
|
node.last_update
|
||||||
|
? `<b data-translate-lang="last_seen"></b> ${timeAgo(node.last_update)}<br>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
${
|
||||||
|
node.firmware
|
||||||
|
? `<b data-translate-lang="firmware"></b> ${node.firmware}<br>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
marker.on('click', () => {
|
||||||
|
onNodeClick(node);
|
||||||
|
marker.bindPopup(popup).openPopup();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
if(!window.configBoundsApplied && bounds.isValid()){
|
|
||||||
map.fitBounds(bounds);
|
setTimeout(() => applyTranslationsMap(), 50);
|
||||||
setTimeout(()=>map.invalidateSize(),100);
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
⭐ NEW: DYNAMIC EDGE LOADING
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
|
async function onNodeClick(node){
|
||||||
|
selectedNodeId = node.key;
|
||||||
|
edgeLayer.clearLayers();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/edges?node_id=${node.key}`);
|
||||||
|
const data = await res.json();
|
||||||
|
const edges = data.edges || [];
|
||||||
|
|
||||||
|
edges.forEach(edge=>{
|
||||||
|
const f = nodeMap.get(edge.from);
|
||||||
|
const t = nodeMap.get(edge.to);
|
||||||
|
|
||||||
|
if(!f || !t || isInvalidCoord(f) || isInvalidCoord(t)) return;
|
||||||
|
|
||||||
|
const color = edge.type === "neighbor" ? "gray" : "orange";
|
||||||
|
const line = L.polyline([[f.lat, f.long], [t.lat, t.long]], {
|
||||||
|
color, weight: 3
|
||||||
|
}).addTo(edgeLayer);
|
||||||
|
|
||||||
|
if(edge.type === "traceroute"){
|
||||||
|
L.polylineDecorator(line, {
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
offset: '100%',
|
||||||
|
repeat: 0,
|
||||||
|
symbol: L.Symbol.arrowHead({
|
||||||
|
pixelSize:5,
|
||||||
|
polygon:false,
|
||||||
|
pathOptions:{stroke:true,color}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).addTo(edgeLayer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch(err){
|
||||||
|
console.error("Failed to load edges for node", node.key, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------- Render Edges ----------------------
|
map.on('click', e=>{
|
||||||
function onNodeClick(node){
|
if(!e.originalEvent.target.classList.contains('leaflet-interactive')){
|
||||||
selectedNodeId = node.key;
|
edgeLayer.clearLayers();
|
||||||
edgeLayer.clearLayers();
|
selectedNodeId=null;
|
||||||
edgesData.forEach(edge=>{
|
}
|
||||||
if(edge.from!==node.key && edge.to!==node.key) return;
|
});
|
||||||
const f=nodeMap.get(edge.from), t=nodeMap.get(edge.to);
|
|
||||||
if(!f||!t||isInvalidCoord(f)||isInvalidCoord(t)) return;
|
/* ======================================================
|
||||||
const color=edge.type==="neighbor"?"gray":"orange";
|
BLINKING
|
||||||
const l=L.polyline([[f.lat,f.long],[t.lat,t.long]],{color,weight:3}).addTo(edgeLayer);
|
====================================================== */
|
||||||
if(edge.type==="traceroute"){
|
|
||||||
L.polylineDecorator(l,{patterns:[{offset:'100%',repeat:0,symbol:L.Symbol.arrowHead({pixelSize:5,polygon:false,pathOptions:{stroke:true,color}})}]}).addTo(edgeLayer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
map.on('click',e=>{ if(!e.originalEvent.target.classList.contains('leaflet-interactive')){ edgeLayer.clearLayers(); selectedNodeId=null; } });
|
|
||||||
|
|
||||||
// ---------------------- Packet Blinking ----------------------
|
|
||||||
function blinkNode(marker,longName,portnum){
|
function blinkNode(marker,longName,portnum){
|
||||||
if(!map.hasLayer(marker)) return;
|
if(!map.hasLayer(marker)) return;
|
||||||
if(activeBlinks.has(marker)){ clearInterval(activeBlinks.get(marker)); marker.setStyle({fillColor:marker.originalColor}); if(marker.tooltip) map.removeLayer(marker.tooltip); }
|
|
||||||
let blinkCount=0;
|
if(activeBlinks.has(marker)){
|
||||||
const portName = portMap[portnum]||`Port ${portnum}`;
|
clearInterval(activeBlinks.get(marker));
|
||||||
const tooltip = L.tooltip({permanent:true,direction:'top',offset:[0,-marker.options.radius-5],className:'blinking-tooltip'})
|
marker.setStyle({ fillColor: marker.originalColor });
|
||||||
.setContent(`${longName} (${portName})`).setLatLng(marker.getLatLng()).addTo(map);
|
if(marker.tooltip) map.removeLayer(marker.tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
let blinkCount = 0;
|
||||||
|
const tooltip = L.tooltip({
|
||||||
|
permanent:true,
|
||||||
|
direction:'top',
|
||||||
|
offset:[0,-marker.options.radius-5],
|
||||||
|
className:'blinking-tooltip'
|
||||||
|
})
|
||||||
|
.setContent(`${longName} (${portMap[portnum] || "Port "+portnum})`)
|
||||||
|
.setLatLng(marker.getLatLng())
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
marker.tooltip = tooltip;
|
marker.tooltip = tooltip;
|
||||||
|
|
||||||
const interval = setInterval(()=>{
|
const interval = setInterval(()=>{
|
||||||
if(map.hasLayer(marker)){ marker.setStyle({fillColor: blinkCount%2===0?'yellow':marker.originalColor}); marker.bringToFront(); }
|
if(map.hasLayer(marker)){
|
||||||
|
marker.setStyle({
|
||||||
|
fillColor: blinkCount%2===0 ? 'yellow' : marker.originalColor
|
||||||
|
});
|
||||||
|
marker.bringToFront();
|
||||||
|
}
|
||||||
blinkCount++;
|
blinkCount++;
|
||||||
if(blinkCount>7){ clearInterval(interval); marker.setStyle({fillColor:marker.originalColor}); map.removeLayer(tooltip); activeBlinks.delete(marker); }
|
|
||||||
|
if(blinkCount>7){
|
||||||
|
clearInterval(interval);
|
||||||
|
marker.setStyle({ fillColor: marker.originalColor });
|
||||||
|
map.removeLayer(tooltip);
|
||||||
|
activeBlinks.delete(marker);
|
||||||
|
}
|
||||||
|
|
||||||
},500);
|
},500);
|
||||||
activeBlinks.set(marker,interval);
|
|
||||||
|
activeBlinks.set(marker, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------- Channel Filters ----------------------
|
/* ======================================================
|
||||||
|
CHANNEL FILTERS
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
function createChannelFilters(){
|
function createChannelFilters(){
|
||||||
const filterContainer = document.getElementById("filter-container");
|
const filterContainer = document.getElementById("filter-container");
|
||||||
const savedState = JSON.parse(localStorage.getItem("mapFilters") || "{}");
|
const saved = JSON.parse(localStorage.getItem("mapFilters") || "{}");
|
||||||
|
|
||||||
channelSet.forEach(channel=>{
|
channelSet.forEach(channel=>{
|
||||||
const checkbox = document.createElement("input");
|
const cb=document.createElement("input");
|
||||||
checkbox.type = "checkbox";
|
cb.type="checkbox";
|
||||||
checkbox.className = "filter-checkbox";
|
cb.className="filter-checkbox";
|
||||||
checkbox.id = `filter-channel-${channel}`;
|
cb.id=`filter-channel-${channel}`;
|
||||||
checkbox.checked = savedState[channel] !== false;
|
cb.checked = saved[channel] !== false;
|
||||||
checkbox.addEventListener("change", saveFiltersToLocalStorage);
|
|
||||||
checkbox.addEventListener("change", updateNodeVisibility);
|
|
||||||
filterContainer.appendChild(checkbox);
|
|
||||||
|
|
||||||
const label = document.createElement("label");
|
cb.addEventListener("change", saveFiltersToLocalStorage);
|
||||||
label.htmlFor = checkbox.id;
|
cb.addEventListener("change", updateNodeVisibility);
|
||||||
label.innerText = channel;
|
|
||||||
|
filterContainer.appendChild(cb);
|
||||||
|
|
||||||
|
const label=document.createElement("label");
|
||||||
|
label.htmlFor=cb.id;
|
||||||
|
label.innerText=channel;
|
||||||
label.style.color = hashToColor(channel);
|
label.style.color = hashToColor(channel);
|
||||||
filterContainer.appendChild(label);
|
filterContainer.appendChild(label);
|
||||||
});
|
});
|
||||||
|
|
||||||
const routerOnly = document.getElementById("filter-routers-only");
|
const routerOnly=document.getElementById("filter-routers-only");
|
||||||
routerOnly.checked = savedState["routersOnly"] || false;
|
routerOnly.checked = saved["routersOnly"] || false;
|
||||||
|
|
||||||
routerOnly.addEventListener("change", saveFiltersToLocalStorage);
|
routerOnly.addEventListener("change", saveFiltersToLocalStorage);
|
||||||
routerOnly.addEventListener("change", updateNodeVisibility);
|
routerOnly.addEventListener("change", updateNodeVisibility);
|
||||||
|
|
||||||
@@ -286,51 +492,69 @@ function createChannelFilters(){
|
|||||||
|
|
||||||
function saveFiltersToLocalStorage(){
|
function saveFiltersToLocalStorage(){
|
||||||
const state = {};
|
const state = {};
|
||||||
channelSet.forEach(ch => {
|
channelSet.forEach(ch=>{
|
||||||
const el = document.getElementById(`filter-channel-${ch}`);
|
state[ch] = document.getElementById(`filter-channel-${ch}`).checked;
|
||||||
state[ch] = el.checked;
|
|
||||||
});
|
});
|
||||||
state["routersOnly"] = document.getElementById("filter-routers-only").checked;
|
state["routersOnly"] = document.getElementById("filter-routers-only").checked;
|
||||||
|
|
||||||
localStorage.setItem("mapFilters", JSON.stringify(state));
|
localStorage.setItem("mapFilters", JSON.stringify(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeVisibility(){
|
function updateNodeVisibility(){
|
||||||
const showRoutersOnly = document.getElementById("filter-routers-only").checked;
|
const routerOnly = document.getElementById("filter-routers-only").checked;
|
||||||
const activeChannels = Array.from(channelSet).filter(ch=>document.getElementById(`filter-channel-${ch}`).checked);
|
const activeChannels = [...channelSet].filter(ch =>
|
||||||
|
document.getElementById(`filter-channel-${ch}`).checked
|
||||||
|
);
|
||||||
|
|
||||||
nodes.forEach(n=>{
|
nodes.forEach(n=>{
|
||||||
const marker = markerById[n.key];
|
const marker = markerById[n.key];
|
||||||
if(marker){
|
if(marker){
|
||||||
const visible = (!showRoutersOnly || n.isRouter) && activeChannels.includes(n.channel);
|
const visible =
|
||||||
if(visible) map.addLayer(marker); else map.removeLayer(marker);
|
(!routerOnly || n.isRouter) &&
|
||||||
|
activeChannels.includes(n.channel);
|
||||||
|
|
||||||
|
visible ? map.addLayer(marker) : map.removeLayer(marker);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------- Share / Reset ----------------------
|
/* ======================================================
|
||||||
function shareCurrentView() {
|
SHARE / RESET
|
||||||
const center = map.getCenter();
|
====================================================== */
|
||||||
const zoom = map.getZoom();
|
|
||||||
const lat = center.lat.toFixed(6);
|
|
||||||
const lng = center.lng.toFixed(6);
|
|
||||||
|
|
||||||
const shareUrl = `${window.location.origin}/map?lat=${lat}&lng=${lng}&zoom=${zoom}`;
|
function shareCurrentView() {
|
||||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
const c = map.getCenter();
|
||||||
const button = document.getElementById('share-button');
|
const url = `${window.location.origin}/map?lat=${c.lat.toFixed(6)}&lng=${c.lng.toFixed(6)}&zoom=${map.getZoom()}`;
|
||||||
const originalText = button.textContent;
|
|
||||||
button.textContent = '✓ Link Copied!';
|
navigator.clipboard.writeText(url).then(()=>{
|
||||||
button.style.backgroundColor = '#2196F3';
|
const btn = document.getElementById('share-button');
|
||||||
setTimeout(() => {
|
const old = btn.textContent;
|
||||||
button.textContent = originalText;
|
btn.textContent = '✓ ' + (mapTranslations.link_copied || 'Link Copied!');
|
||||||
button.style.backgroundColor = '#4CAF50';
|
btn.style.backgroundColor = '#2196F3';
|
||||||
|
|
||||||
|
setTimeout(()=>{
|
||||||
|
btn.textContent = old;
|
||||||
|
btn.style.backgroundColor = '#4CAF50';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}).catch(() => alert('Share this link:\n' + shareUrl));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFiltersToDefaults(){
|
function resetFiltersToDefaults(){
|
||||||
document.getElementById("filter-routers-only").checked = false;
|
document.getElementById("filter-routers-only").checked = false;
|
||||||
channelSet.forEach(ch=>document.getElementById(`filter-channel-${ch}`).checked = true);
|
channelSet.forEach(ch => {
|
||||||
|
document.getElementById(`filter-channel-${ch}`).checked = true;
|
||||||
|
});
|
||||||
saveFiltersToLocalStorage();
|
saveFiltersToLocalStorage();
|
||||||
updateNodeVisibility();
|
updateNodeVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
TRANSLATION LOAD
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadTranslationsMap();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+118
-69
@@ -23,20 +23,29 @@
|
|||||||
.channel { font-style: italic; color: #bbb; }
|
.channel { font-style: italic; color: #bbb; }
|
||||||
.channel a { font-style: normal; color: #999; }
|
.channel a { font-style: normal; color: #999; }
|
||||||
|
|
||||||
@keyframes flash { 0% { background-color: #ffe066; } 100% { background-color: inherit; } }
|
|
||||||
.chat-packet.flash { animation: flash 3.5s ease-out; }
|
|
||||||
|
|
||||||
.replying-to { font-size: 0.8em; color: #aaa; margin-top: 2px; padding-left: 10px; }
|
|
||||||
.replying-to .reply-preview { color: #aaa; }
|
|
||||||
|
|
||||||
#weekly-message { margin: 15px 0; font-weight: bold; color: #ffeb3b; }
|
#weekly-message { margin: 15px 0; font-weight: bold; color: #ffeb3b; }
|
||||||
#total-count { margin-bottom: 10px; font-style: italic; color: #ccc; }
|
#total-count { margin-bottom: 10px; font-style: italic; color: #ccc; }
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="weekly-message">Loading weekly message...</div>
|
<!-- ⭐ NET TITLE WITH ICON ⭐ -->
|
||||||
<div id="total-count">Total messages: 0</div>
|
<div class="container px-2">
|
||||||
|
<h2 style="color:white; margin:0 0 10px 0;">
|
||||||
|
<span class="icon">💬</span>
|
||||||
|
<span data-translate-lang="net_title"></span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Weekly network message -->
|
||||||
|
<div id="weekly-message"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Total message count -->
|
||||||
|
<div id="total-count">
|
||||||
|
<span data-translate-lang="total_messages">Total messages:</span>
|
||||||
|
<span id="total-count-value">0</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="chat-container">
|
<div id="chat-container">
|
||||||
<div class="container" id="chat-log"></div>
|
<div class="container" id="chat-log"></div>
|
||||||
@@ -45,140 +54,180 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
|
||||||
const chatContainer = document.querySelector("#chat-log");
|
const chatContainer = document.querySelector("#chat-log");
|
||||||
const totalCountEl = document.querySelector("#total-count");
|
|
||||||
const weeklyMessageEl = document.querySelector("#weekly-message");
|
const weeklyMessageEl = document.querySelector("#weekly-message");
|
||||||
if (!chatContainer || !totalCountEl || !weeklyMessageEl) {
|
const totalCountValueEl = document.querySelector("#total-count-value");
|
||||||
console.error("Required elements not found");
|
|
||||||
|
if (!chatContainer || !weeklyMessageEl || !totalCountValueEl) {
|
||||||
|
console.error("Required elements missing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderedPacketIds = new Set();
|
const renderedPacketIds = new Set();
|
||||||
const packetMap = new Map();
|
let netTranslations = {};
|
||||||
let chatTranslations = {};
|
|
||||||
let netTag = "";
|
let netTag = "";
|
||||||
|
|
||||||
function updateTotalCount() {
|
/* -----------------------------------
|
||||||
totalCountEl.textContent = `Total messages: ${renderedPacketIds.size}`;
|
Escape HTML safely
|
||||||
}
|
----------------------------------- */
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.textContent = text ?? "";
|
div.textContent = text ?? "";
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTranslations(translations, root = document) {
|
/* -----------------------------------
|
||||||
|
Apply translations
|
||||||
|
----------------------------------- */
|
||||||
|
function applyTranslations(trans, root=document) {
|
||||||
root.querySelectorAll("[data-translate-lang]").forEach(el => {
|
root.querySelectorAll("[data-translate-lang]").forEach(el => {
|
||||||
const key = el.dataset.translateLang;
|
const key = el.dataset.translateLang;
|
||||||
if (translations[key]) el.textContent = translations[key];
|
if (trans[key]) el.textContent = trans[key];
|
||||||
});
|
});
|
||||||
root.querySelectorAll("[data-translate-lang-title]").forEach(el => {
|
root.querySelectorAll("[data-translate-lang-title]").forEach(el => {
|
||||||
const key = el.dataset.translateLangTitle;
|
const key = el.dataset.translateLangTitle;
|
||||||
if (translations[key]) el.title = translations[key];
|
if (trans[key]) el.title = trans[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------
|
||||||
|
Update count
|
||||||
|
----------------------------------- */
|
||||||
|
function updateTotalCount() {
|
||||||
|
totalCountValueEl.textContent = renderedPacketIds.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------
|
||||||
|
Render single packet
|
||||||
|
----------------------------------- */
|
||||||
function renderPacket(packet) {
|
function renderPacket(packet) {
|
||||||
if (renderedPacketIds.has(packet.id)) return;
|
if (renderedPacketIds.has(packet.id)) return;
|
||||||
renderedPacketIds.add(packet.id);
|
renderedPacketIds.add(packet.id);
|
||||||
packetMap.set(packet.id, packet);
|
|
||||||
|
|
||||||
const date = new Date(packet.import_time_us / 1000);
|
const date = new Date(packet.import_time_us / 1000);
|
||||||
const formattedTime = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: true });
|
|
||||||
const formattedDate = `${(date.getMonth() + 1).toString().padStart(2, "0")}/${date.getDate().toString().padStart(2, "0")}/${date.getFullYear()}`;
|
|
||||||
const formattedTimestamp = `${formattedTime} - ${formattedDate}`;
|
|
||||||
|
|
||||||
let replyHtml = "";
|
const timeStr = date.toLocaleTimeString([], {
|
||||||
if (packet.reply_id) {
|
hour: "numeric",
|
||||||
const parent = packetMap.get(packet.reply_id);
|
minute: "2-digit",
|
||||||
if (parent) {
|
second: "2-digit",
|
||||||
replyHtml = `<div class="replying-to">
|
hour12: true
|
||||||
<div class="reply-preview">
|
});
|
||||||
<i data-translate-lang="replying_to"></i>
|
|
||||||
<strong>${escapeHtml((parent.long_name || "").trim() || `Node ${parent.from_node_id}`)}</strong>:
|
const dateStr =
|
||||||
${escapeHtml(parent.payload || "")}
|
`${String(date.getMonth()+1).padStart(2,"0")}/`+
|
||||||
</div>
|
`${String(date.getDate()).padStart(2,"0")}/`+
|
||||||
</div>`;
|
date.getFullYear();
|
||||||
} else {
|
|
||||||
replyHtml = `<div class="replying-to">
|
const timestamp = `${timeStr} - ${dateStr}`;
|
||||||
<i data-translate-lang="replying_to"></i>
|
|
||||||
<a href="/packet/${packet.reply_id}">${packet.reply_id}</a>
|
const fromName =
|
||||||
</div>`;
|
(packet.long_name || "").trim() ||
|
||||||
}
|
`${netTranslations.node_fallback} ${packet.from_node_id}`;
|
||||||
}
|
|
||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "row chat-packet";
|
div.className = "row chat-packet";
|
||||||
div.dataset.packetId = packet.id;
|
div.dataset.packetId = packet.id;
|
||||||
|
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<span class="col-2 timestamp" title="${packet.import_time_us}">${formattedTimestamp}</span>
|
<span class="col-2 timestamp" title="${packet.import_time_us}">
|
||||||
|
${timestamp}
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="col-2 channel">
|
<span class="col-2 channel">
|
||||||
<a href="/packet/${packet.id}" data-translate-lang-title="view_packet_details">✉️</a>
|
<a href="/packet/${packet.id}"
|
||||||
|
data-translate-lang-title="view_packet_details">✉️</a>
|
||||||
${escapeHtml(packet.channel || "")}
|
${escapeHtml(packet.channel || "")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="col-3 nodename">
|
<span class="col-3 nodename">
|
||||||
<a href="/packet_list/${packet.from_node_id}">
|
<a href="/packet_list/${packet.from_node_id}">
|
||||||
${escapeHtml((packet.long_name || "").trim() || `Node ${packet.from_node_id}`)}
|
${escapeHtml(fromName)}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-5 message">${escapeHtml(packet.payload)}${replyHtml}</span>
|
|
||||||
|
<span class="col-5 message">
|
||||||
|
${escapeHtml(packet.payload).replace(/\n/g,"<br>")}
|
||||||
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
chatContainer.prepend(div);
|
chatContainer.prepend(div);
|
||||||
applyTranslations(chatTranslations, div);
|
applyTranslations(netTranslations, div);
|
||||||
updateTotalCount();
|
updateTotalCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------
|
||||||
|
Sort descending by time
|
||||||
|
----------------------------------- */
|
||||||
function renderPacketsEnsureDescending(packets) {
|
function renderPacketsEnsureDescending(packets) {
|
||||||
if (!Array.isArray(packets) || packets.length === 0) return;
|
if (!packets || !packets.length) return;
|
||||||
const sortedDesc = packets.slice().sort((a, b) => b.import_time_us - a.import_time_us);
|
const sorted = packets.slice().sort((a, b) => b.import_time_us - a.import_time_us);
|
||||||
for (let i = sortedDesc.length - 1; i >= 0; i--) renderPacket(sortedDesc[i]);
|
for (let i = sorted.length - 1; i >= 0; i--) {
|
||||||
|
renderPacket(sorted[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------
|
||||||
|
Fetch initial net-tagged packets
|
||||||
|
----------------------------------- */
|
||||||
async function fetchInitialPackets(tag) {
|
async function fetchInitialPackets(tag) {
|
||||||
if (!tag) {
|
if (!tag) return;
|
||||||
console.warn("No net_tag defined, skipping packet fetch.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
console.log("Fetching packets for netTag:", tag);
|
const sixDaysAgoMs = Date.now() - 6*24*60*60*1000;
|
||||||
const sixDaysAgoMs = Date.now() - (6 * 24 * 60 * 60 * 1000);
|
|
||||||
const sinceUs = Math.floor(sixDaysAgoMs * 1000);
|
const sinceUs = Math.floor(sixDaysAgoMs * 1000);
|
||||||
const resp = await fetch(`/api/packets?portnum=1&contains=${encodeURIComponent(tag)}&since=${sinceUs}`);
|
|
||||||
|
const url =
|
||||||
|
`/api/packets?portnum=1&contains=${encodeURIComponent(tag)}&since=${sinceUs}`;
|
||||||
|
|
||||||
|
const resp = await fetch(url);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
console.log("Packets received:", data?.packets?.length);
|
|
||||||
if (data?.packets?.length) renderPacketsEnsureDescending(data.packets);
|
if (data?.packets?.length)
|
||||||
|
renderPacketsEnsureDescending(data.packets);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Initial fetch error:", err);
|
console.error("Initial fetch error:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------
|
||||||
|
Load translations from section=net
|
||||||
|
----------------------------------- */
|
||||||
async function loadTranslations(cfg) {
|
async function loadTranslations(cfg) {
|
||||||
try {
|
try {
|
||||||
const langCode = cfg?.site?.language || "en";
|
const lang = cfg?.site?.language || "en";
|
||||||
const res = await fetch(`/api/lang?lang=${langCode}§ion=chat`);
|
const res = await fetch(`/api/lang?lang=${lang}§ion=net`);
|
||||||
chatTranslations = await res.json();
|
netTranslations = await res.json();
|
||||||
applyTranslations(chatTranslations, document);
|
applyTranslations(netTranslations, document);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Chat translation load failed:", err);
|
console.error("Failed loading translations", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MAIN LOGIC ---
|
/* -----------------------------------
|
||||||
|
MAIN
|
||||||
|
----------------------------------- */
|
||||||
try {
|
try {
|
||||||
const cfg = await window._siteConfigPromise; // ✅ Already fetched by base.html
|
const cfg = await window._siteConfigPromise;
|
||||||
const site = cfg?.site || {};
|
const site = cfg?.site || {};
|
||||||
|
|
||||||
// Populate from config
|
|
||||||
netTag = site.net_tag || "";
|
netTag = site.net_tag || "";
|
||||||
weeklyMessageEl.textContent = site.weekly_net_message || "Weekly message not set.";
|
|
||||||
|
weeklyMessageEl.textContent = site.weekly_net_message || "";
|
||||||
|
|
||||||
|
|
||||||
await loadTranslations(cfg);
|
await loadTranslations(cfg);
|
||||||
await fetchInitialPackets(netTag);
|
await fetchInitialPackets(netTag);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Initialization failed:", err);
|
console.error("Initialization failed:", err);
|
||||||
weeklyMessageEl.textContent = "Failed to load site config.";
|
|
||||||
|
weeklyMessageEl.textContent =
|
||||||
|
netTranslations.failed_to_load_site_config ||
|
||||||
|
"Failed to load site config.";
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+524
-286
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,24 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
|
<style>
|
||||||
table {
|
table {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure table centered visually */
|
||||||
|
#node-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#node-list table {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid #333;
|
border: 1px solid #333;
|
||||||
@@ -85,6 +97,7 @@ select, .export-btn, .search-box, .clear-btn {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Favorite stars */
|
||||||
.favorite-star {
|
.favorite-star {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -100,6 +113,7 @@ select, .export-btn, .search-box, .clear-btn {
|
|||||||
color: #ffd700;
|
color: #ffd700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Favorite filter button */
|
||||||
.favorites-btn {
|
.favorites-btn {
|
||||||
background-color: #ffd700;
|
background-color: #ffd700;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -114,49 +128,165 @@ select, .export-btn, .search-box, .clear-btn {
|
|||||||
background-color: #ff6b6b;
|
background-color: #ff6b6b;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
/* MOBILE CARD VIEW */
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
|
/* Hide desktop table */
|
||||||
|
#node-list table {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show mobile card list */
|
||||||
|
#mobile-node-list {
|
||||||
|
display: block !important;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card {
|
||||||
|
background: #1e1e1e;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 15px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card-field {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card-field b {
|
||||||
|
color: #9fd4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.favorite-star {
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="filter-container">
|
<div class="filter-container">
|
||||||
<input type="text" id="search-box" class="search-box" placeholder="Search by name or ID..." />
|
|
||||||
|
|
||||||
<select id="role-filter"><option value="">All Roles</option></select>
|
<input
|
||||||
<select id="channel-filter"><option value="">All Channels</option></select>
|
type="text"
|
||||||
<select id="hw-filter"><option value="">All HW Models</option></select>
|
id="search-box"
|
||||||
<select id="firmware-filter"><option value="">All Firmware</option></select>
|
class="search-box"
|
||||||
|
data-translate-lang="search_placeholder"
|
||||||
|
placeholder="Search by name or ID..."
|
||||||
|
/>
|
||||||
|
|
||||||
<button class="favorites-btn" id="favorites-btn">⭐ Show Favorites</button>
|
<select id="role-filter">
|
||||||
<button class="export-btn" id="export-btn">Export CSV</button>
|
<option value="" data-translate-lang="all_roles">All Roles</option>
|
||||||
<button class="clear-btn" id="clear-btn">Clear Filters</button>
|
</select>
|
||||||
|
|
||||||
|
<select id="channel-filter">
|
||||||
|
<option value="" data-translate-lang="all_channels">All Channels</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="hw-filter">
|
||||||
|
<option value="" data-translate-lang="all_hw">All HW Models</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="firmware-filter">
|
||||||
|
<option value="" data-translate-lang="all_firmware">All Firmware</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button class="favorites-btn" id="favorites-btn" data-translate-lang="show_favorites">
|
||||||
|
⭐ Show Favorites
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="export-btn" id="export-btn" data-translate-lang="export_csv">
|
||||||
|
Export CSV
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="clear-btn" id="clear-btn" data-translate-lang="clear_filters">
|
||||||
|
Clear Filters
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="count-container">
|
<div class="count-container">
|
||||||
Showing <span id="node-count">0</span> nodes
|
<span data-translate-lang="showing_nodes">Showing</span>
|
||||||
|
<span id="node-count">0</span>
|
||||||
|
<span data-translate-lang="nodes_suffix">nodes</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop table -->
|
||||||
<div id="node-list">
|
<div id="node-list">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Short <span class="sort-icon">▲</span></th>
|
<th data-translate-lang="short_name">Short <span class="sort-icon">▲</span></th>
|
||||||
<th>Long Name <span class="sort-icon"></span></th>
|
<th data-translate-lang="long_name">Long Name <span class="sort-icon"></span></th>
|
||||||
<th>HW Model <span class="sort-icon"></span></th>
|
<th data-translate-lang="hw_model">HW Model <span class="sort-icon"></span></th>
|
||||||
<th>Firmware <span class="sort-icon"></span></th>
|
<th data-translate-lang="firmware">Firmware <span class="sort-icon"></span></th>
|
||||||
<th>Role <span class="sort-icon"></span></th>
|
<th data-translate-lang="role">Role <span class="sort-icon"></span></th>
|
||||||
<th>Last Latitude <span class="sort-icon"></span></th>
|
<th data-translate-lang="last_lat">Last Latitude <span class="sort-icon"></span></th>
|
||||||
<th>Last Longitude <span class="sort-icon"></span></th>
|
<th data-translate-lang="last_long">Last Longitude <span class="sort-icon"></span></th>
|
||||||
<th>Channel <span class="sort-icon"></span></th>
|
<th data-translate-lang="channel">Channel <span class="sort-icon"></span></th>
|
||||||
<th>Last Seen <span class="sort-icon"></span></th>
|
<th data-translate-lang="last_seen">Last Seen <span class="sort-icon"></span></th>
|
||||||
<th> </th>
|
<th data-translate-lang="favorite"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="node-table-body">
|
<tbody id="node-table-body">
|
||||||
<tr><td colspan="10" style="text-align:center; color:white;">Loading nodes...</td></tr>
|
<tr>
|
||||||
|
<td colspan="10" style="text-align:center; color:white;" data-translate-lang="loading_nodes">
|
||||||
|
Loading nodes...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Card View -->
|
||||||
|
<div id="mobile-node-list" style="display:none;"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// =====================================================
|
||||||
|
// TRANSLATIONS
|
||||||
|
// =====================================================
|
||||||
|
let nodelistTranslations = {};
|
||||||
|
|
||||||
|
function applyTranslationsNodelist() {
|
||||||
|
document.querySelectorAll("[data-translate-lang]").forEach(el => {
|
||||||
|
const key = el.dataset.translateLang;
|
||||||
|
if (nodelistTranslations[key]) {
|
||||||
|
if (el.tagName === "INPUT" && el.placeholder) {
|
||||||
|
el.placeholder = nodelistTranslations[key];
|
||||||
|
} else {
|
||||||
|
el.textContent = nodelistTranslations[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTranslationsNodelist() {
|
||||||
|
try {
|
||||||
|
const cfg = await window._siteConfigPromise;
|
||||||
|
const lang = cfg?.site?.language || "en";
|
||||||
|
const res = await fetch(`/api/lang?lang=${lang}§ion=nodelist`);
|
||||||
|
nodelistTranslations = await res.json();
|
||||||
|
applyTranslationsNodelist();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load nodelist translations:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// GLOBALS
|
// GLOBALS
|
||||||
// =====================================================
|
// =====================================================
|
||||||
@@ -171,18 +301,13 @@ const keyMap = [
|
|||||||
"last_lat","last_long","channel","last_seen_us"
|
"last_lat","last_long","channel","last_seen_us"
|
||||||
];
|
];
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// FAVORITES SYSTEM (localStorage)
|
|
||||||
// =====================================================
|
|
||||||
function getFavorites() {
|
function getFavorites() {
|
||||||
const favorites = localStorage.getItem('nodelist_favorites');
|
const favorites = localStorage.getItem('nodelist_favorites');
|
||||||
return favorites ? JSON.parse(favorites) : [];
|
return favorites ? JSON.parse(favorites) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFavorites(favs) {
|
function saveFavorites(favs) {
|
||||||
localStorage.setItem('nodelist_favorites', JSON.stringify(favs));
|
localStorage.setItem('nodelist_favorites', JSON.stringify(favs));
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFavorite(nodeId) {
|
function toggleFavorite(nodeId) {
|
||||||
let favs = getFavorites();
|
let favs = getFavorites();
|
||||||
const idx = favs.indexOf(nodeId);
|
const idx = favs.indexOf(nodeId);
|
||||||
@@ -190,14 +315,10 @@ function toggleFavorite(nodeId) {
|
|||||||
else favs.push(nodeId);
|
else favs.push(nodeId);
|
||||||
saveFavorites(favs);
|
saveFavorites(favs);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFavorite(nodeId) {
|
function isFavorite(nodeId) {
|
||||||
return getFavorites().includes(nodeId);
|
return getFavorites().includes(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// "TIME AGO" FORMATTER
|
|
||||||
// =====================================================
|
|
||||||
function timeAgo(usTimestamp) {
|
function timeAgo(usTimestamp) {
|
||||||
if (!usTimestamp) return "N/A";
|
if (!usTimestamp) return "N/A";
|
||||||
const ms = usTimestamp / 1000;
|
const ms = usTimestamp / 1000;
|
||||||
@@ -209,14 +330,19 @@ function timeAgo(usTimestamp) {
|
|||||||
const hrs = Math.floor(mins / 60);
|
const hrs = Math.floor(mins / 60);
|
||||||
if (hrs < 24) return `${hrs} hr ago`;
|
if (hrs < 24) return `${hrs} hr ago`;
|
||||||
const days = Math.floor(hrs / 24);
|
const days = Math.floor(hrs / 24);
|
||||||
return `${days} day${days > 1 ? "s" : ""} ago`;
|
return `${days} days ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// DOM LOADED: FETCH NODES
|
// DOM LOADED
|
||||||
// =====================================================
|
// =====================================================
|
||||||
document.addEventListener("DOMContentLoaded", async function() {
|
document.addEventListener("DOMContentLoaded", async function() {
|
||||||
|
|
||||||
|
await loadTranslationsNodelist();
|
||||||
|
|
||||||
const tbody = document.getElementById("node-table-body");
|
const tbody = document.getElementById("node-table-body");
|
||||||
|
const mobileList = document.getElementById("mobile-node-list");
|
||||||
|
|
||||||
const roleFilter = document.getElementById("role-filter");
|
const roleFilter = document.getElementById("role-filter");
|
||||||
const channelFilter = document.getElementById("channel-filter");
|
const channelFilter = document.getElementById("channel-filter");
|
||||||
const hwFilter = document.getElementById("hw-filter");
|
const hwFilter = document.getElementById("hw-filter");
|
||||||
@@ -232,13 +358,19 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
if (!res.ok) throw new Error("Failed to fetch nodes");
|
if (!res.ok) throw new Error("Failed to fetch nodes");
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
allNodes = data.nodes;
|
allNodes = data.nodes.map(n => ({
|
||||||
|
...n,
|
||||||
|
firmware: n.firmware || n.firmware_version || ""
|
||||||
|
}));
|
||||||
|
|
||||||
populateFilters(allNodes);
|
populateFilters(allNodes);
|
||||||
renderTable(allNodes);
|
renderTable(allNodes);
|
||||||
updateSortIcons();
|
updateSortIcons();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
tbody.innerHTML = `<tr><td colspan="10" style="text-align:center; color:red;">Error loading nodes: ${err.message}</td></tr>`;
|
tbody.innerHTML = `<tr>
|
||||||
|
<td colspan="10" style="text-align:center; color:red;">
|
||||||
|
${nodelistTranslations.error_loading_nodes || "Error loading nodes"}
|
||||||
|
</td></tr>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
roleFilter.addEventListener("change", applyFilters);
|
roleFilter.addEventListener("change", applyFilters);
|
||||||
@@ -250,8 +382,8 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
clearBtn.addEventListener("click", clearFilters);
|
clearBtn.addEventListener("click", clearFilters);
|
||||||
favoritesBtn.addEventListener("click", toggleFavoritesFilter);
|
favoritesBtn.addEventListener("click", toggleFavoritesFilter);
|
||||||
|
|
||||||
// STAR CLICK HANDLER
|
// Favorite star click handler
|
||||||
tbody.addEventListener("click", e => {
|
document.addEventListener("click", e => {
|
||||||
if (e.target.classList.contains('favorite-star')) {
|
if (e.target.classList.contains('favorite-star')) {
|
||||||
const nodeId = parseInt(e.target.dataset.nodeId);
|
const nodeId = parseInt(e.target.dataset.nodeId);
|
||||||
const isFav = isFavorite(nodeId);
|
const isFav = isFavorite(nodeId);
|
||||||
@@ -264,10 +396,10 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
e.target.textContent = "★";
|
e.target.textContent = "★";
|
||||||
}
|
}
|
||||||
toggleFavorite(nodeId);
|
toggleFavorite(nodeId);
|
||||||
|
applyFilters();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// SORTING
|
|
||||||
headers.forEach((th, index) => {
|
headers.forEach((th, index) => {
|
||||||
th.addEventListener("click", () => {
|
th.addEventListener("click", () => {
|
||||||
let key = keyMap[index];
|
let key = keyMap[index];
|
||||||
@@ -277,9 +409,6 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// FILTER POPULATION
|
|
||||||
// =====================================================
|
|
||||||
function populateFilters(nodes) {
|
function populateFilters(nodes) {
|
||||||
const roles = new Set(), channels = new Set(), hws = new Set(), fws = new Set();
|
const roles = new Set(), channels = new Set(), hws = new Set(), fws = new Set();
|
||||||
|
|
||||||
@@ -305,19 +434,15 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// FAVORITES FILTER
|
|
||||||
// =====================================================
|
|
||||||
function toggleFavoritesFilter() {
|
function toggleFavoritesFilter() {
|
||||||
showOnlyFavorites = !showOnlyFavorites;
|
showOnlyFavorites = !showOnlyFavorites;
|
||||||
favoritesBtn.textContent = showOnlyFavorites ? "⭐ Show All" : "⭐ Show Favorites";
|
favoritesBtn.textContent = showOnlyFavorites
|
||||||
|
? "Show All"
|
||||||
|
: "⭐ Show Favorites";
|
||||||
favoritesBtn.classList.toggle("active", showOnlyFavorites);
|
favoritesBtn.classList.toggle("active", showOnlyFavorites);
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// APPLY FILTERS + SORT
|
|
||||||
// =====================================================
|
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
const searchTerm = searchBox.value.trim().toLowerCase();
|
const searchTerm = searchBox.value.trim().toLowerCase();
|
||||||
|
|
||||||
@@ -326,6 +451,7 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
const channelMatch = !channelFilter.value || n.channel === channelFilter.value;
|
const channelMatch = !channelFilter.value || n.channel === channelFilter.value;
|
||||||
const hwMatch = !hwFilter.value || n.hw_model === hwFilter.value;
|
const hwMatch = !hwFilter.value || n.hw_model === hwFilter.value;
|
||||||
const fwMatch = !firmwareFilter.value || n.firmware === firmwareFilter.value;
|
const fwMatch = !firmwareFilter.value || n.firmware === firmwareFilter.value;
|
||||||
|
|
||||||
const searchMatch =
|
const searchMatch =
|
||||||
!searchTerm ||
|
!searchTerm ||
|
||||||
(n.long_name && n.long_name.toLowerCase().includes(searchTerm)) ||
|
(n.long_name && n.long_name.toLowerCase().includes(searchTerm)) ||
|
||||||
@@ -338,19 +464,24 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
filtered = sortNodes(filtered, sortColumn, sortAsc);
|
filtered = sortNodes(filtered, sortColumn, sortAsc);
|
||||||
|
|
||||||
renderTable(filtered);
|
renderTable(filtered);
|
||||||
updateSortIcons();
|
updateSortIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// RENDER TABLE
|
|
||||||
// =====================================================
|
|
||||||
function renderTable(nodes) {
|
function renderTable(nodes) {
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
|
mobileList.innerHTML = "";
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 768;
|
||||||
|
|
||||||
if (!nodes.length) {
|
if (!nodes.length) {
|
||||||
tbody.innerHTML = `<tr><td colspan="10" style="text-align:center; color:white;">No nodes found</td></tr>`;
|
tbody.innerHTML = `<tr>
|
||||||
|
<td colspan="10" style="text-align:center; color:white;">
|
||||||
|
${nodelistTranslations.no_nodes_found || "No nodes found"}
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
|
||||||
|
mobileList.innerHTML = `<div style="text-align:center; color:white;">No nodes found</div>`;
|
||||||
countSpan.textContent = 0;
|
countSpan.textContent = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -359,6 +490,7 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
const isFav = isFavorite(node.node_id);
|
const isFav = isFavorite(node.node_id);
|
||||||
const star = isFav ? "★" : "☆";
|
const star = isFav ? "★" : "☆";
|
||||||
|
|
||||||
|
// DESKTOP TABLE ROW
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${node.short_name || "N/A"}</td>
|
<td>${node.short_name || "N/A"}</td>
|
||||||
@@ -371,18 +503,53 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
<td>${node.channel || "N/A"}</td>
|
<td>${node.channel || "N/A"}</td>
|
||||||
<td>${timeAgo(node.last_seen_us)}</td>
|
<td>${timeAgo(node.last_seen_us)}</td>
|
||||||
<td style="text-align:center;">
|
<td style="text-align:center;">
|
||||||
<span class="favorite-star ${isFav ? "active" : ""}" data-node-id="${node.node_id}">${star}</span>
|
<span class="favorite-star ${isFav ? "active" : ""}" data-node-id="${node.node_id}">
|
||||||
|
${star}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
|
|
||||||
|
// MOBILE CARD VIEW
|
||||||
|
const card = document.createElement("div");
|
||||||
|
card.className = "node-card";
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="node-card-header">
|
||||||
|
<span>${node.short_name || node.long_name || node.node_id}</span>
|
||||||
|
<span class="favorite-star ${isFav ? "active" : ""}" data-node-id="${node.node_id}">
|
||||||
|
${star}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="node-card-field"><b>ID:</b> ${node.node_id}</div>
|
||||||
|
<div class="node-card-field"><b>Name:</b> ${node.long_name || "N/A"}</div>
|
||||||
|
<div class="node-card-field"><b>HW:</b> ${node.hw_model || "N/A"}</div>
|
||||||
|
<div class="node-card-field"><b>Firmware:</b> ${node.firmware || "N/A"}</div>
|
||||||
|
<div class="node-card-field"><b>Role:</b> ${node.role || "N/A"}</div>
|
||||||
|
<div class="node-card-field"><b>Location:</b>
|
||||||
|
${node.last_lat ? (node.last_lat / 1e7).toFixed(5) : "N/A"},
|
||||||
|
${node.last_long ? (node.last_long / 1e7).toFixed(5) : "N/A"}
|
||||||
|
</div>
|
||||||
|
<div class="node-card-field"><b>Channel:</b> ${node.channel}</div>
|
||||||
|
<div class="node-card-field"><b>Last Seen:</b> ${timeAgo(node.last_seen_us)}</div>
|
||||||
|
|
||||||
|
<a href="/node/${node.node_id}" style="color:#9fd4ff; text-decoration:underline; margin-top:5px; display:block;">
|
||||||
|
View Node →
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
mobileList.appendChild(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle correct view
|
||||||
|
if (isMobile) {
|
||||||
|
mobileList.style.display = "block";
|
||||||
|
} else {
|
||||||
|
mobileList.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
countSpan.textContent = nodes.length;
|
countSpan.textContent = nodes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// CLEAR FILTERS
|
|
||||||
// =====================================================
|
|
||||||
function clearFilters() {
|
function clearFilters() {
|
||||||
roleFilter.value = "";
|
roleFilter.value = "";
|
||||||
channelFilter.value = "";
|
channelFilter.value = "";
|
||||||
@@ -392,6 +559,7 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
sortColumn = "short_name";
|
sortColumn = "short_name";
|
||||||
sortAsc = true;
|
sortAsc = true;
|
||||||
showOnlyFavorites = false;
|
showOnlyFavorites = false;
|
||||||
|
|
||||||
favoritesBtn.textContent = "⭐ Show Favorites";
|
favoritesBtn.textContent = "⭐ Show Favorites";
|
||||||
favoritesBtn.classList.remove("active");
|
favoritesBtn.classList.remove("active");
|
||||||
|
|
||||||
@@ -399,17 +567,18 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
updateSortIcons();
|
updateSortIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// EXPORT CSV
|
|
||||||
// =====================================================
|
|
||||||
function exportToCSV() {
|
function exportToCSV() {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
const headerList = Array.from(headers).map(h => `"${h.innerText.replace(/▲|▼/g,'')}"`);
|
const headerList = Array.from(headers).map(h =>
|
||||||
|
`"${h.innerText.replace(/▲|▼/g,'')}"`
|
||||||
|
);
|
||||||
rows.push(headerList.join(","));
|
rows.push(headerList.join(","));
|
||||||
|
|
||||||
const trs = tbody.querySelectorAll("tr");
|
const trs = tbody.querySelectorAll("tr");
|
||||||
trs.forEach(tr => {
|
trs.forEach(tr => {
|
||||||
const cells = Array.from(tr.children).map(td => `"${td.innerText.replace(/"/g,'""')}"`);
|
const cells = Array.from(tr.children).map(td =>
|
||||||
|
`"${td.innerText.replace(/"/g,'""')}"`
|
||||||
|
);
|
||||||
rows.push(cells.join(","));
|
rows.push(cells.join(","));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -420,15 +589,11 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
a.click();
|
a.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// SORT NODES
|
|
||||||
// =====================================================
|
|
||||||
function sortNodes(nodes, key, asc) {
|
function sortNodes(nodes, key, asc) {
|
||||||
return [...nodes].sort((a, b) => {
|
return [...nodes].sort((a, b) => {
|
||||||
let A = a[key];
|
let A = a[key];
|
||||||
let B = b[key];
|
let B = b[key];
|
||||||
|
|
||||||
// special handling for timestamp
|
|
||||||
if (key === "last_seen_us") {
|
if (key === "last_seen_us") {
|
||||||
A = A || 0;
|
A = A || 0;
|
||||||
B = B || 0;
|
B = B || 0;
|
||||||
@@ -440,14 +605,12 @@ document.addEventListener("DOMContentLoaded", async function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// SORT ICONS
|
|
||||||
// =====================================================
|
|
||||||
function updateSortIcons() {
|
function updateSortIcons() {
|
||||||
headers.forEach((th, i) => {
|
headers.forEach((th, i) => {
|
||||||
const span = th.querySelector(".sort-icon");
|
const span = th.querySelector(".sort-icon");
|
||||||
if (!span) return;
|
if (!span) return;
|
||||||
span.textContent = keyMap[i] === sortColumn ? (sortAsc ? "▲" : "▼") : "";
|
span.textContent =
|
||||||
|
keyMap[i] === sortColumn ? (sortAsc ? "▲" : "▼") : "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+145
-120
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Packet Details{%endblock%}
|
{% block title %}Packet Details{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- SOURCE MARKER (slightly bigger) --- */
|
/* --- SOURCE MARKER --- */
|
||||||
.source-marker {
|
.source-marker {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -97,26 +97,27 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mt-4 mb-5 packet-container">
|
<div class="container mt-4 mb-5 packet-container">
|
||||||
|
|
||||||
<div id="loading">Loading packet information...</div>
|
<div id="loading" data-translate-lang="loading">Loading packet information...</div>
|
||||||
<div id="packet-card" class="packet-card d-none"></div>
|
<div id="packet-card" class="packet-card d-none"></div>
|
||||||
|
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
|
|
||||||
<div id="seen-container" class="mt-4 d-none">
|
<div id="seen-container" class="mt-4 d-none">
|
||||||
<h5 style="color:#ccc; margin:15px 0 10px 0;">
|
<h5 style="color:#ccc; margin:15px 0 10px 0;">
|
||||||
📡 Seen By <span id="seen-count" style="color:#4da6ff;"></span>
|
📡 <span data-translate-lang="seen_by">Seen By</span>
|
||||||
|
<span id="seen-count" style="color:#4da6ff;"></span>
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-dark table-sm seen-table">
|
<table class="table table-dark table-sm seen-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Gateway</th>
|
<th data-translate-lang="gateway">Gateway</th>
|
||||||
<th>RSSI</th>
|
<th data-translate-lang="rssi">RSSI</th>
|
||||||
<th>SNR</th>
|
<th data-translate-lang="snr">SNR</th>
|
||||||
<th>Hop</th>
|
<th data-translate-lang="hops">Hops</th>
|
||||||
<th>Channel</th>
|
<th data-translate-lang="channel">Channel</th>
|
||||||
<th>Time</th>
|
<th data-translate-lang="time">Time</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="seen-table-body"></tbody>
|
<tbody id="seen-table-body"></tbody>
|
||||||
@@ -126,8 +127,39 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
PACKET PAGE TRANSLATION
|
||||||
|
====================================================== */
|
||||||
|
let packetTranslations = {};
|
||||||
|
|
||||||
|
async function loadTranslationsPacket() {
|
||||||
|
try {
|
||||||
|
const cfg = await window._siteConfigPromise;
|
||||||
|
const lang = cfg?.site?.language || "en";
|
||||||
|
const res = await fetch(`/api/lang?lang=${lang}§ion=packet`);
|
||||||
|
packetTranslations = await res.json();
|
||||||
|
applyTranslationsPacket(packetTranslations);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Packet translations failed:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTranslationsPacket(dict, root = document) {
|
||||||
|
root.querySelectorAll("[data-translate-lang]").forEach(el => {
|
||||||
|
const key = el.dataset.translateLang;
|
||||||
|
if (dict[key]) el.textContent = dict[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
PACKET PAGE MAIN
|
||||||
|
====================================================== */
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
|
||||||
|
await loadTranslationsPacket(); // <-- IMPORTANT
|
||||||
|
|
||||||
const packetCard = document.getElementById("packet-card");
|
const packetCard = document.getElementById("packet-card");
|
||||||
const loading = document.getElementById("loading");
|
const loading = document.getElementById("loading");
|
||||||
const mapDiv = document.getElementById("map");
|
const mapDiv = document.getElementById("map");
|
||||||
@@ -140,12 +172,12 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
const match = window.location.pathname.match(/\/packet\/(\d+)/);
|
const match = window.location.pathname.match(/\/packet\/(\d+)/);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
loading.textContent = "Invalid packet URL";
|
loading.textContent = packetTranslations.invalid_url || "Invalid packet URL";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const packetId = match[1];
|
const packetId = match[1];
|
||||||
|
|
||||||
/* PORT NAME MAP */
|
/* PORT LABELS (NOT TRANSLATED) */
|
||||||
const PORT_NAMES = {
|
const PORT_NAMES = {
|
||||||
0:"UNKNOWN APP",
|
0:"UNKNOWN APP",
|
||||||
1:"Text",
|
1:"Text",
|
||||||
@@ -164,28 +196,31 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const packetRes = await fetch(`/api/packets?packet_id=${packetId}`);
|
const packetRes = await fetch(`/api/packets?packet_id=${packetId}`);
|
||||||
const packetData = await packetRes.json();
|
const packetData = await packetRes.json();
|
||||||
if (!packetData.packets.length) {
|
if (!packetData.packets.length) {
|
||||||
loading.textContent = "Packet not found.";
|
loading.textContent = packetTranslations.not_found || "Packet not found.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const p = packetData.packets[0];
|
const p = packetData.packets[0];
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Fetch all nodes
|
Load nodes for names & positions
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
const nodesRes = await fetch("/api/nodes");
|
const nodesRes = await fetch("/api/nodes");
|
||||||
const nodesData = await nodesRes.json();
|
const nodesData = await nodesRes.json();
|
||||||
const nodeLookup = {};
|
const nodeLookup = {};
|
||||||
(nodesData.nodes || []).forEach(n => nodeLookup[n.node_id] = n);
|
(nodesData.nodes || []).forEach(n => nodeLookup[n.node_id] = n);
|
||||||
|
|
||||||
const fromNodeObj = nodeLookup[p.from_node_id];
|
const fromNodeObj = nodeLookup[p.from_node_id];
|
||||||
const toNodeObj = nodeLookup[p.to_node_id];
|
const toNodeObj = nodeLookup[p.to_node_id];
|
||||||
|
|
||||||
const fromNodeLabel = fromNodeObj?.long_name || p.from_node_id;
|
const fromNodeLabel = fromNodeObj?.long_name || p.from_node_id;
|
||||||
|
|
||||||
const toNodeLabel =
|
const toNodeLabel =
|
||||||
p.to_node_id == 4294967295 ? "All" : (toNodeObj?.long_name || p.to_node_id);
|
p.to_node_id == 4294967295
|
||||||
|
? (packetTranslations.all_broadcast || "All")
|
||||||
|
: (toNodeObj?.long_name || p.to_node_id);
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Parse payload for lat/lon if this *packet* is a position packet
|
Parse payload for lat/lon
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
let lat = null, lon = null;
|
let lat = null, lon = null;
|
||||||
const parsed = {};
|
const parsed = {};
|
||||||
@@ -195,14 +230,14 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const [k, v] = line.split(":").map(x=>x.trim());
|
const [k, v] = line.split(":").map(x=>x.trim());
|
||||||
if (k && v !== undefined) {
|
if (k && v !== undefined) {
|
||||||
parsed[k] = v;
|
parsed[k] = v;
|
||||||
if (k === "latitude_i") lat = Number(v) / 1e7;
|
if (k === "latitude_i") lat = Number(v) / 1e7;
|
||||||
if (k === "longitude_i") lon = Number(v) / 1e7;
|
if (k === "longitude_i") lon = Number(v) / 1e7;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Render packet header & details
|
Render card
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
const time = p.import_time_us
|
const time = p.import_time_us
|
||||||
? new Date(p.import_time_us / 1000).toLocaleString()
|
? new Date(p.import_time_us / 1000).toLocaleString()
|
||||||
@@ -216,42 +251,47 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
packetCard.innerHTML = `
|
packetCard.innerHTML = `
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>Packet ID: <i>${p.id}</i></span>
|
<span>
|
||||||
|
<span data-translate-lang="packet_id_label">${packetTranslations.packet_id_label || "Packet ID:"}</span>
|
||||||
|
<i>${p.id}</i>
|
||||||
|
</span>
|
||||||
<small>${time}</small>
|
<small>${time}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>From Node:</dt>
|
<dt data-translate-lang="from_node">${packetTranslations.from_node || "From Node"}:</dt>
|
||||||
<dd><a href="/node/${p.from_node_id}">${fromNodeLabel}</a></dd>
|
<dd><a href="/node/${p.from_node_id}">${fromNodeLabel}</a></dd>
|
||||||
<dt>To Node:</dt>
|
|
||||||
|
<dt data-translate-lang="to_node">${packetTranslations.to_node || "To Node"}:</dt>
|
||||||
<dd>${
|
<dd>${
|
||||||
p.to_node_id === 4294967295
|
p.to_node_id === 4294967295
|
||||||
? `<i>All</i>`
|
? `<i data-translate-lang="all_broadcast">${packetTranslations.all_broadcast || "All"}</i>`
|
||||||
: p.to_node_id === 1
|
: p.to_node_id === 1
|
||||||
? `<i>Direct to MQTT</i>`
|
? `<i data-translate-lang="direct_to_mqtt">${packetTranslations.direct_to_mqtt || "Direct to MQTT"}</i>`
|
||||||
: `<a href="/node/${p.to_node_id}">${toNodeLabel}</a>`
|
: `<a href="/node/${p.to_node_id}">${toNodeLabel}</a>`
|
||||||
}</dd>
|
}</dd>
|
||||||
|
|
||||||
|
<dt data-translate-lang="channel">${packetTranslations.channel || "Channel"}:</dt>
|
||||||
|
<dd>${p.channel ?? "—"}</dd>
|
||||||
|
|
||||||
<dt>Channel:</dt><dd>${p.channel ?? "—"}</dd>
|
<dt data-translate-lang="port">${packetTranslations.port || "Port"}:</dt>
|
||||||
|
|
||||||
<dt>Port:</dt>
|
|
||||||
<dd><i>${PORT_NAMES[p.portnum] || "UNKNOWN APP"}</i> (${p.portnum})</dd>
|
<dd><i>${PORT_NAMES[p.portnum] || "UNKNOWN APP"}</i> (${p.portnum})</dd>
|
||||||
|
|
||||||
<dt>Raw Payload:</dt>
|
<dt data-translate-lang="raw_payload">${packetTranslations.raw_payload || "From Raw Payload"}:</dt>
|
||||||
<dd><pre>${escapeHtml(p.payload ?? "—")}</pre></dd>
|
<dd><pre>${escapeHtml(p.payload ?? "—")}</pre></dd>
|
||||||
|
|
||||||
${
|
${
|
||||||
telemetryExtras.length
|
telemetryExtras.length
|
||||||
? `<dt>Decoded Telemetry</dt>
|
? `<dt data-translate-lang="decoded_telemetry">${packetTranslations.decoded_telemetry || "Decoded Telemetry"}</dt>
|
||||||
<dd><pre>${telemetryExtras.join("\n")}</pre></dd>`
|
<dd><pre>${telemetryExtras.join("\n")}</pre></dd>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
${
|
${
|
||||||
lat && lon
|
lat && lon
|
||||||
? `<dt>Location:</dt><dd>${lat.toFixed(6)}, ${lon.toFixed(6)}</dd>`
|
? `<dt data-translate-lang="location">${packetTranslations.location || "Location:"}</dt>
|
||||||
|
<dd>${lat.toFixed(6)}, ${lon.toFixed(6)}</dd>`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
</dl>
|
</dl>
|
||||||
@@ -262,22 +302,18 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
packetCard.classList.remove("d-none");
|
packetCard.classList.remove("d-none");
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Map initialization
|
Map setup
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
const map = L.map("map");
|
const map = L.map("map");
|
||||||
mapDiv.style.display = "block";
|
mapDiv.style.display = "block";
|
||||||
|
|
||||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 })
|
||||||
maxZoom: 19
|
.addTo(map);
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
const allBounds = [];
|
const allBounds = [];
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
ALWAYS SHOW SOURCE POSITION
|
Determine packet source location
|
||||||
Priority:
|
|
||||||
1) position from packet payload
|
|
||||||
2) fallback: last_lat/last_long from /api/nodes
|
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
let srcLat = lat;
|
let srcLat = lat;
|
||||||
let srcLon = lon;
|
let srcLon = lon;
|
||||||
@@ -304,135 +340,123 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
sourceMarker.bindPopup(`
|
sourceMarker.bindPopup(`
|
||||||
<div style="font-size:0.9em">
|
<div style="font-size:0.9em">
|
||||||
<b>Packet Source</b><br>
|
<b data-translate-lang="packet_source">${packetTranslations.packet_source || "Packet Source"}</b><br>
|
||||||
Lat: ${srcLat.toFixed(6)}<br>
|
Lat: ${srcLat.toFixed(6)}<br>
|
||||||
Lon: ${srcLon.toFixed(6)}<br>
|
Lon: ${srcLon.toFixed(6)}<br>
|
||||||
From Node: ${fromNodeLabel}<br>
|
<span data-translate-lang="from_node">${packetTranslations.from_node || "From Node:"}</span> ${fromNodeLabel}<br>
|
||||||
Channel: ${p.channel ?? "—"}<br>
|
<span data-translate-lang="channel">${packetTranslations.channel || "Channel:"}</span> ${p.channel ?? "—"}<br>
|
||||||
Port: ${PORT_NAMES[p.portnum] || "UNKNOWN"} (${p.portnum})
|
<span data-translate-lang="port">${packetTranslations.port || "Port:"}</span> ${PORT_NAMES[p.portnum] || "UNKNOWN"} (${p.portnum})
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
map.setView([0,0], 2);
|
map.setView([0,0], 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Color for hop indicator markers (warm → cold)
|
Colors for hops (warm → cold)
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
function hopColor(hopValue){
|
function hopColor(hopValue){
|
||||||
const colors = [
|
const colors = [
|
||||||
"#ff3b30",
|
"#ff3b30","#ff6b22","#ff9f0c","#ffd60a",
|
||||||
"#ff6b22",
|
"#87d957","#57d9c4","#3db2ff","#1e63ff"
|
||||||
"#ff9f0c",
|
|
||||||
"#ffd60a",
|
|
||||||
"#87d957",
|
|
||||||
"#57d9c4",
|
|
||||||
"#3db2ff",
|
|
||||||
"#1e63ff"
|
|
||||||
];
|
];
|
||||||
let h = Number(hopValue);
|
let h = Number(hopValue);
|
||||||
if (isNaN(h)) return "#aaa";
|
if (isNaN(h)) return "#aaa";
|
||||||
if (h < 0) h = 0;
|
return colors[Math.min(Math.max(h, 0), 7)];
|
||||||
if (h > 7) h = 7;
|
|
||||||
return colors[h];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Distance helper */
|
|
||||||
function haversine(lat1,lon1,lat2,lon2){
|
|
||||||
const R=6371;
|
|
||||||
const dLat=(lat2-lat1)*Math.PI/180;
|
|
||||||
const dLon=(lon2-lon1)*Math.PI/180;
|
|
||||||
const a=Math.sin(dLat/2)**2+
|
|
||||||
Math.cos(lat1*Math.PI/180)*
|
|
||||||
Math.cos(lat2*Math.PI/180)*
|
|
||||||
Math.sin(dLon/2)**2;
|
|
||||||
return R*(2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Fetch packets_seen
|
Distance helper
|
||||||
|
----------------------------------------------*/
|
||||||
|
function haversine(lat1, lon1, lat2, lon2){
|
||||||
|
const R = 6371;
|
||||||
|
const dLat = (lat2-lat1)*Math.PI/180;
|
||||||
|
const dLon = (lon2-lon1)*Math.PI/180;
|
||||||
|
const a = Math.sin(dLat/2)**2 +
|
||||||
|
Math.cos(lat1*Math.PI/180)*
|
||||||
|
Math.cos(lat2*Math.PI/180)*
|
||||||
|
Math.sin(dLon/2)**2;
|
||||||
|
return R * (2*Math.atan2(Math.sqrt(a), Math.sqrt(1-a)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------
|
||||||
|
Load packets_seen
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
const seenRes = await fetch(`/api/packets_seen/${packetId}`);
|
const seenRes = await fetch(`/api/packets_seen/${packetId}`);
|
||||||
const seenData = await seenRes.json();
|
const seenData = await seenRes.json();
|
||||||
const seenList = seenData.seen ?? [];
|
const seenList = seenData.seen ?? [];
|
||||||
|
|
||||||
/* sort by hop_start descending (warm → cold) */
|
|
||||||
const seenSorted = seenList.slice().sort((a,b)=>{
|
const seenSorted = seenList.slice().sort((a,b)=>{
|
||||||
const A=a.hop_start??-999;
|
return (b.hop_start ?? -999) - (a.hop_start ?? -999);
|
||||||
const B=b.hop_start??-999;
|
|
||||||
return B-A;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (seenSorted.length){
|
if (seenSorted.length){
|
||||||
seenContainer.classList.remove("d-none");
|
seenContainer.classList.remove("d-none");
|
||||||
seenCountSpan.textContent=`(${seenSorted.length} gateways)`;
|
seenCountSpan.textContent = `(${seenSorted.length})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Gateway markers and seen table
|
Render gateway table + map markers
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
seenTableBody.innerHTML = seenSorted.map(s=>{
|
seenTableBody.innerHTML = seenSorted.map(s=>{
|
||||||
const node=nodeLookup[s.node_id];
|
const node = nodeLookup[s.node_id];
|
||||||
const label=node?(node.long_name||node.node_id):s.node_id;
|
const label = node?.long_name || s.node_id;
|
||||||
|
|
||||||
const timeStr = s.import_time_us
|
const timeStr = s.import_time_us
|
||||||
? new Date(s.import_time_us/1000).toLocaleTimeString()
|
? new Date(s.import_time_us/1000).toLocaleTimeString()
|
||||||
: "—";
|
: "—";
|
||||||
|
|
||||||
if(node?.last_lat && node.last_long){
|
if (node?.last_lat && node.last_long){
|
||||||
const rlat=node.last_lat/1e7;
|
const rlat = node.last_lat/1e7;
|
||||||
const rlon=node.last_long/1e7;
|
const rlon = node.last_long/1e7;
|
||||||
allBounds.push([rlat,rlon]);
|
allBounds.push([rlat, rlon]);
|
||||||
|
|
||||||
const start = Number(s.hop_start ?? 0);
|
|
||||||
const limit = Number(s.hop_limit ?? 0);
|
|
||||||
const hopValue = start - limit;
|
|
||||||
|
|
||||||
|
const hopValue = (s.hop_start ?? 0) - (s.hop_limit ?? 0);
|
||||||
const color = hopColor(hopValue);
|
const color = hopColor(hopValue);
|
||||||
|
|
||||||
const iconHtml = `
|
const marker = L.marker([rlat,rlon],{
|
||||||
<div style="
|
icon: L.divIcon({
|
||||||
background:${color};
|
html: `
|
||||||
width:24px;
|
<div style="
|
||||||
height:24px;
|
background:${color};
|
||||||
border-radius:50%;
|
width:24px; height:24px;
|
||||||
display:flex;
|
border-radius:50%;
|
||||||
align-items:center;
|
display:flex;
|
||||||
justify-content:center;
|
align-items:center;
|
||||||
color:white;
|
justify-content:center;
|
||||||
font-size:11px;
|
color:white;
|
||||||
font-weight:700;
|
font-size:11px;
|
||||||
border:2px solid rgba(0,0,0,0.35);
|
font-weight:700;
|
||||||
box-shadow:0 0 5px rgba(0,0,0,0.45);
|
border:2px solid rgba(0,0,0,0.35);
|
||||||
">${hopValue}</div>`;
|
box-shadow:0 0 5px rgba(0,0,0,0.45);
|
||||||
|
">${hopValue}</div>`,
|
||||||
const marker=L.marker([rlat,rlon],{
|
className: "",
|
||||||
icon:L.divIcon({
|
|
||||||
html:iconHtml,
|
|
||||||
className:"",
|
|
||||||
iconSize:[24,24],
|
iconSize:[24,24],
|
||||||
iconAnchor:[12,12]
|
iconAnchor:[12,12]
|
||||||
})
|
})
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
let distKm=null,distMi=null;
|
let distKm = null, distMi = null;
|
||||||
if(srcLat&&srcLon){
|
if (srcLat && srcLon){
|
||||||
distKm=haversine(srcLat,srcLon,rlat,rlon);
|
distKm = haversine(srcLat, srcLon, rlat, rlon);
|
||||||
distMi=distKm*0.621371;
|
distMi = distKm * 0.621371;
|
||||||
}
|
}
|
||||||
|
|
||||||
marker.bindPopup(`
|
marker.bindPopup(`
|
||||||
<div style="font-size:0.9em">
|
<div style="font-size:0.9em">
|
||||||
<b>${node?.long_name || s.node_id}</b><br>
|
<b>${label}</b><br>
|
||||||
Node ID: <a href="/node/${s.node_id}">${s.node_id}</a><br>
|
<span data-translate-lang="node_id_short">${packetTranslations.node_id_short || "Node ID"}</span>:
|
||||||
|
<a href="/node/${s.node_id}">${s.node_id}</a><br>
|
||||||
HW: ${node?.hw_model ?? "—"}<br>
|
HW: ${node?.hw_model ?? "—"}<br>
|
||||||
Channel: ${s.channel ?? "—"}<br><br>
|
<span data-translate-lang="channel">${packetTranslations.channel || "Channel"}</span>: ${s.channel ?? "—"}<br><br>
|
||||||
<b>Signal</b><br>
|
|
||||||
|
<b data-translate-lang="signal">${packetTranslations.signal || "Signal"}</b><br>
|
||||||
RSSI: ${s.rx_rssi ?? "—"}<br>
|
RSSI: ${s.rx_rssi ?? "—"}<br>
|
||||||
SNR: ${s.rx_snr ?? "—"}<br><br>
|
SNR: ${s.rx_snr ?? "—"}<br><br>
|
||||||
<b>Hops</b>: ${hopValue}<br>
|
|
||||||
<b>Distance</b><br>
|
<b data-translate-lang="hops">${packetTranslations.hops || "Hops"}</b>: ${hopValue}<br>
|
||||||
|
|
||||||
|
<b data-translate-lang="distance">${packetTranslations.distance || "Distance"}:</b><br>
|
||||||
${
|
${
|
||||||
distKm
|
distKm
|
||||||
? `${distKm.toFixed(2)} km (${distMi.toFixed(2)} mi)`
|
? `${distKm.toFixed(2)} km (${distMi.toFixed(2)} mi)`
|
||||||
@@ -454,17 +478,17 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
}).join("");
|
}).join("");
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Fit map to all markers
|
Fit map around all markers
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
if(allBounds.length>0){
|
if (allBounds.length > 0){
|
||||||
map.fitBounds(allBounds,{padding:[40,40]});
|
map.fitBounds(allBounds, { padding:[40,40] });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Escape HTML
|
Escape HTML helper
|
||||||
----------------------------------------------*/
|
----------------------------------------------*/
|
||||||
function escapeHtml(unsafe) {
|
function escapeHtml(unsafe) {
|
||||||
return (unsafe??"").replace(/[&<"'>]/g,m=>({
|
return (unsafe ?? "").replace(/[&<"'>]/g, m => ({
|
||||||
"&":"&",
|
"&":"&",
|
||||||
"<":"<",
|
"<":"<",
|
||||||
">":">",
|
">":">",
|
||||||
@@ -475,4 +499,5 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+141
-116
@@ -15,7 +15,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 20px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +38,6 @@
|
|||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
table th { background-color: #333; }
|
table th { background-color: #333; }
|
||||||
table tbody tr:nth-child(odd) { background-color: #272b2f; }
|
table tbody tr:nth-child(odd) { background-color: #272b2f; }
|
||||||
table tbody tr:nth-child(even) { background-color: #212529; }
|
table tbody tr:nth-child(even) { background-color: #212529; }
|
||||||
@@ -65,37 +57,42 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<h1>Top Nodes Traffic</h1>
|
<h1 data-translate-lang="top_traffic_nodes">Top Nodes Traffic</h1>
|
||||||
|
|
||||||
<div class="top-container">
|
<div class="top-container">
|
||||||
|
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<div>
|
<div>
|
||||||
<label for="channelFilter">Channel:</label>
|
<label for="channelFilter" data-translate-lang="channel">Channel:</label>
|
||||||
<select id="channelFilter" class="form-select form-select-sm" style="width:auto;"></select>
|
<select id="channelFilter" class="form-select form-select-sm" style="width:auto;"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="nodeSearch" data-translate-lang="search">Search:</label>
|
||||||
|
<input id="nodeSearch" type="text" class="form-control form-control-sm"
|
||||||
|
placeholder="Search nodes..."
|
||||||
|
data-translate-lang="search_placeholder"
|
||||||
|
style="width:180px; display:inline-block;">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<!-- ⭐ ADDED NODE COUNT ⭐ -->
|
||||||
<label for="nodeSearch">Search:</label>
|
<div id="count-container" style="margin-bottom:10px; font-weight:bold;">
|
||||||
<input id="nodeSearch" type="text" class="form-control form-control-sm"
|
<span data-translate-lang="showing_nodes">Showing</span>
|
||||||
placeholder="Search nodes..."
|
<span id="node-count">0</span>
|
||||||
style="width:180px; display:inline-block;">
|
<span data-translate-lang="nodes_suffix">nodes</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="nodesTable">
|
<table id="nodesTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Long Name</th>
|
<th data-translate-lang="long_name">Long Name</th>
|
||||||
<th>Short Name</th>
|
<th data-translate-lang="short_name">Short Name</th>
|
||||||
<th>Channel</th>
|
<th data-translate-lang="channel">Channel</th>
|
||||||
<th>Sent (24h)</th>
|
<th data-translate-lang="packets_sent">Sent (24h)</th>
|
||||||
<th>Seen (24h)</th>
|
<th data-translate-lang="times_seen">Seen (24h)</th>
|
||||||
<th>Avg Gateways</th>
|
<th data-translate-lang="avg_gateways">Avg Gateways</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
@@ -105,74 +102,105 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let allNodes = [];
|
/* ======================================================
|
||||||
|
TOP PAGE TRANSLATION (isolated from base)
|
||||||
|
====================================================== */
|
||||||
|
let topTranslations = {};
|
||||||
|
|
||||||
async function loadChannels() {
|
function applyTranslationsTop(dict, root=document) {
|
||||||
try {
|
root.querySelectorAll("[data-translate-lang]").forEach(el => {
|
||||||
const res = await fetch("/api/channels");
|
const key = el.dataset.translateLang;
|
||||||
const data = await res.json();
|
if (!dict[key]) return;
|
||||||
const channels = data.channels || [];
|
|
||||||
|
|
||||||
const select = document.getElementById("channelFilter");
|
// input placeholder support
|
||||||
|
if (el.tagName === "INPUT" && el.placeholder !== undefined) {
|
||||||
// Default LongFast first
|
el.placeholder = dict[key];
|
||||||
if (channels.includes("LongFast")) {
|
} else {
|
||||||
const opt = document.createElement("option");
|
el.textContent = dict[key];
|
||||||
opt.value = "LongFast";
|
|
||||||
opt.textContent = "LongFast";
|
|
||||||
select.appendChild(opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ch of channels) {
|
|
||||||
if (ch === "LongFast") continue;
|
|
||||||
const opt = document.createElement("option");
|
|
||||||
opt.value = ch;
|
|
||||||
opt.textContent = ch;
|
|
||||||
select.appendChild(opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
select.addEventListener("change", renderTable);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error loading channels:", err);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function loadNodes() {
|
async function loadTranslationsTop() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/nodes");
|
const cfg = await window._siteConfigPromise;
|
||||||
const data = await res.json();
|
const lang = cfg?.site?.language || "en";
|
||||||
allNodes = data.nodes || [];
|
|
||||||
} catch (err) {
|
const res = await fetch(`/api/lang?lang=${lang}§ion=top`);
|
||||||
console.error("Error loading nodes:", err);
|
topTranslations = await res.json();
|
||||||
|
|
||||||
|
applyTranslationsTop(topTranslations);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("TOP translation load failed:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
PAGE LOGIC
|
||||||
|
====================================================== */
|
||||||
|
let allNodes = [];
|
||||||
|
|
||||||
|
async function loadChannels() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/channels");
|
||||||
|
const data = await res.json();
|
||||||
|
const channels = data.channels || [];
|
||||||
|
|
||||||
|
const select = document.getElementById("channelFilter");
|
||||||
|
|
||||||
|
// LongFast first
|
||||||
|
if (channels.includes("LongFast")) {
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = "LongFast";
|
||||||
|
opt.textContent = "LongFast";
|
||||||
|
select.appendChild(opt);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchNodeStats(nodeId) {
|
for (const ch of channels) {
|
||||||
try {
|
if (ch === "LongFast") continue;
|
||||||
const url = `/api/stats/count?from_node=${nodeId}&period_type=day&length=1`;
|
const opt = document.createElement("option");
|
||||||
const res = await fetch(url);
|
opt.value = ch;
|
||||||
const data = await res.json();
|
opt.textContent = ch;
|
||||||
|
select.appendChild(opt);
|
||||||
const sent = data.total_packets || 0;
|
|
||||||
const seen = data.total_seen || 0;
|
|
||||||
const avg = seen / Math.max(sent, 1);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sent,
|
|
||||||
seen,
|
|
||||||
avg: avg
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Stat error", err);
|
|
||||||
return { sent: 0, seen: 0, avg: 0 };
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function avgClass(v) {
|
select.addEventListener("change", renderTable);
|
||||||
if (v >= 10) return "good-x"; // Very strong node
|
} catch (err) {
|
||||||
if (v >= 2) return "ok-x"; // Normal node
|
console.error("Error loading channels:", err);
|
||||||
return "bad-x"; // Weak node
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadNodes() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/nodes");
|
||||||
|
const data = await res.json();
|
||||||
|
allNodes = data.nodes || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error loading nodes:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchNodeStats(nodeId) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/stats/count?from_node=${nodeId}&period_type=day&length=1`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const sent = data.total_packets || 0;
|
||||||
|
const seen = data.total_seen || 0;
|
||||||
|
const avg = seen / Math.max(sent, 1);
|
||||||
|
|
||||||
|
return { sent, seen, avg };
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Stat error:", err);
|
||||||
|
return { sent: 0, seen: 0, avg: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function avgClass(v) {
|
||||||
|
if (v >= 10) return "good-x";
|
||||||
|
if (v >= 2) return "ok-x";
|
||||||
|
return "bad-x";
|
||||||
|
}
|
||||||
|
|
||||||
async function renderTable() {
|
async function renderTable() {
|
||||||
const tbody = document.querySelector("#nodesTable tbody");
|
const tbody = document.querySelector("#nodesTable tbody");
|
||||||
@@ -181,10 +209,10 @@ async function renderTable() {
|
|||||||
const channel = document.getElementById("channelFilter").value;
|
const channel = document.getElementById("channelFilter").value;
|
||||||
const searchText = document.getElementById("nodeSearch").value.trim().toLowerCase();
|
const searchText = document.getElementById("nodeSearch").value.trim().toLowerCase();
|
||||||
|
|
||||||
// Filter nodes by channel FIRST
|
// Filter by channel
|
||||||
let filtered = allNodes.filter(n => n.channel === channel);
|
let filtered = allNodes.filter(n => n.channel === channel);
|
||||||
|
|
||||||
// Then apply search
|
// Filter by search
|
||||||
if (searchText !== "") {
|
if (searchText !== "") {
|
||||||
filtered = filtered.filter(n =>
|
filtered = filtered.filter(n =>
|
||||||
(n.long_name && n.long_name.toLowerCase().includes(searchText)) ||
|
(n.long_name && n.long_name.toLowerCase().includes(searchText)) ||
|
||||||
@@ -193,12 +221,10 @@ async function renderTable() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Create placeholder rows ---
|
// Placeholder rows first
|
||||||
const rowRefs = filtered.map(n => {
|
const rowRefs = filtered.map(n => {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.addEventListener("click", () => {
|
tr.addEventListener("click", () => window.location.href = `/node/${n.node_id}`);
|
||||||
window.location.href = `/node/${n.node_id}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const tdLong = document.createElement("td");
|
const tdLong = document.createElement("td");
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
@@ -215,13 +241,13 @@ async function renderTable() {
|
|||||||
tdChannel.textContent = n.channel || "";
|
tdChannel.textContent = n.channel || "";
|
||||||
|
|
||||||
const tdSent = document.createElement("td");
|
const tdSent = document.createElement("td");
|
||||||
tdSent.textContent = "Loading...";
|
tdSent.textContent = "...";
|
||||||
|
|
||||||
const tdSeen = document.createElement("td");
|
const tdSeen = document.createElement("td");
|
||||||
tdSeen.textContent = "Loading...";
|
tdSeen.textContent = "...";
|
||||||
|
|
||||||
const tdAvg = document.createElement("td");
|
const tdAvg = document.createElement("td");
|
||||||
tdAvg.textContent = "Loading...";
|
tdAvg.textContent = "...";
|
||||||
|
|
||||||
tr.appendChild(tdLong);
|
tr.appendChild(tdLong);
|
||||||
tr.appendChild(tdShort);
|
tr.appendChild(tdShort);
|
||||||
@@ -235,50 +261,49 @@ async function renderTable() {
|
|||||||
return { node: n, tr, tdSent, tdSeen, tdAvg };
|
return { node: n, tr, tdSent, tdSeen, tdAvg };
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Stats fetch ---
|
// Fetch stats
|
||||||
const statsList = await Promise.all(
|
const statsList = await Promise.all(
|
||||||
rowRefs.map(ref => fetchNodeStats(ref.node.node_id))
|
rowRefs.map(ref => fetchNodeStats(ref.node.node_id))
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- Update + cleanup empty nodes ---
|
// Update rows
|
||||||
let combined = rowRefs.map((ref, i) => {
|
let combined = rowRefs.map((ref, i) => {
|
||||||
const stats = statsList[i];
|
const stats = statsList[i];
|
||||||
|
|
||||||
ref.tdSent.textContent = stats.sent;
|
ref.tdSent.textContent = stats.sent;
|
||||||
ref.tdSeen.textContent = stats.seen;
|
ref.tdSeen.textContent = stats.seen;
|
||||||
ref.tdAvg.innerHTML = `<span class="${avgClass(stats.avg)}">${stats.avg.toFixed(1)}</span>`;
|
ref.tdAvg.innerHTML =
|
||||||
|
`<span class="${avgClass(stats.avg)}">${stats.avg.toFixed(1)}</span>`;
|
||||||
|
|
||||||
return {
|
return { tr: ref.tr, sent: stats.sent, seen: stats.seen };
|
||||||
tr: ref.tr,
|
|
||||||
sent: stats.sent,
|
|
||||||
seen: stats.seen
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove nodes with no traffic
|
// Remove nodes with no activity
|
||||||
combined = combined.filter(r => !(r.sent === 0 && r.seen === 0));
|
combined = combined.filter(r => !(r.sent === 0 && r.seen === 0));
|
||||||
|
|
||||||
// Sort by traffic (seen)
|
// Sort by seen
|
||||||
combined.sort((a, b) => b.seen - a.seen);
|
combined.sort((a, b) => b.seen - a.seen);
|
||||||
|
|
||||||
// Rebuild table
|
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
for (const r of combined) {
|
for (const r of combined) tbody.appendChild(r.tr);
|
||||||
tbody.appendChild(r.tr);
|
|
||||||
}
|
// ⭐ UPDATE COUNT ⭐
|
||||||
|
document.getElementById("node-count").textContent = combined.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================================================
|
||||||
|
INITIALIZE PAGE
|
||||||
(async () => {
|
====================================================== */
|
||||||
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
|
await loadTranslationsTop(); // ⭐ MUST run first
|
||||||
await loadNodes();
|
await loadNodes();
|
||||||
await loadChannels();
|
await loadChannels();
|
||||||
document.getElementById("channelFilter").value = "LongFast";
|
|
||||||
|
|
||||||
|
document.getElementById("channelFilter").value = "LongFast";
|
||||||
document.getElementById("nodeSearch").addEventListener("input", renderTable);
|
document.getElementById("nodeSearch").addEventListener("input", renderTable);
|
||||||
|
|
||||||
renderTable();
|
renderTable();
|
||||||
})();
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+18
-26
@@ -12,7 +12,7 @@ from google.protobuf import text_format
|
|||||||
from google.protobuf.message import Message
|
from google.protobuf.message import Message
|
||||||
from jinja2 import Environment, PackageLoader, Undefined, select_autoescape
|
from jinja2 import Environment, PackageLoader, Undefined, select_autoescape
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
|
import pathlib
|
||||||
from meshtastic.protobuf.portnums_pb2 import PortNum
|
from meshtastic.protobuf.portnums_pb2 import PortNum
|
||||||
from meshview import config, database, decode_payload, migrations, models, store
|
from meshview import config, database, decode_payload, migrations, models, store
|
||||||
from meshview.__version__ import (
|
from meshview.__version__ import (
|
||||||
@@ -200,6 +200,23 @@ async def redirect_packet_list(request):
|
|||||||
packet_id = request.match_info["packet_id"]
|
packet_id = request.match_info["packet_id"]
|
||||||
raise web.HTTPFound(location=f"/node/{packet_id}")
|
raise web.HTTPFound(location=f"/node/{packet_id}")
|
||||||
|
|
||||||
|
# Generic static HTML route
|
||||||
|
@routes.get("/{page}")
|
||||||
|
async def serve_page(request):
|
||||||
|
page = request.match_info["page"]
|
||||||
|
|
||||||
|
# default to index.html if no extension
|
||||||
|
if not page.endswith(".html"):
|
||||||
|
page = f"{page}.html"
|
||||||
|
|
||||||
|
html_file = pathlib.Path(__file__).parent / "static" / page
|
||||||
|
if not html_file.exists():
|
||||||
|
raise web.HTTPNotFound(text=f"Page '{page}' not found")
|
||||||
|
|
||||||
|
content = html_file.read_text(encoding="utf-8")
|
||||||
|
return web.Response(text=content, content_type="text/html")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/net")
|
@routes.get("/net")
|
||||||
async def net(request):
|
async def net(request):
|
||||||
@@ -394,31 +411,6 @@ async def graph_traceroute(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
@routes.get("/stats")
|
|
||||||
async def stats(request):
|
|
||||||
try:
|
|
||||||
total_packets = await store.get_total_packet_count()
|
|
||||||
total_nodes = await store.get_total_node_count()
|
|
||||||
total_packets_seen = await store.get_total_packet_seen_count()
|
|
||||||
template = env.get_template("stats.html")
|
|
||||||
return web.Response(
|
|
||||||
text=template.render(
|
|
||||||
total_packets=total_packets,
|
|
||||||
total_nodes=total_nodes,
|
|
||||||
total_packets_seen=total_packets_seen,
|
|
||||||
),
|
|
||||||
content_type="text/html",
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return web.Response(
|
|
||||||
text=f"An error occurred: {str(e)}",
|
|
||||||
status=500,
|
|
||||||
content_type="text/plain",
|
|
||||||
)
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
async def run_server():
|
async def run_server():
|
||||||
# Wait for database migrations to complete before starting web server
|
# Wait for database migrations to complete before starting web server
|
||||||
logger.info("Checking database schema status...")
|
logger.info("Checking database schema status...")
|
||||||
|
|||||||
+50
-16
@@ -48,6 +48,7 @@ async def api_channels(request: web.Request):
|
|||||||
async def api_nodes(request):
|
async def api_nodes(request):
|
||||||
try:
|
try:
|
||||||
# Optional query parameters
|
# Optional query parameters
|
||||||
|
node_id = request.query.get("node_id")
|
||||||
role = request.query.get("role")
|
role = request.query.get("role")
|
||||||
channel = request.query.get("channel")
|
channel = request.query.get("channel")
|
||||||
hw_model = request.query.get("hw_model")
|
hw_model = request.query.get("hw_model")
|
||||||
@@ -61,7 +62,7 @@ async def api_nodes(request):
|
|||||||
|
|
||||||
# Fetch nodes from database
|
# Fetch nodes from database
|
||||||
nodes = await store.get_nodes(
|
nodes = await store.get_nodes(
|
||||||
role=role, channel=channel, hw_model=hw_model, days_active=days_active
|
node_id=node_id, role=role, channel=channel, hw_model=hw_model, days_active=days_active
|
||||||
)
|
)
|
||||||
|
|
||||||
# Prepare the JSON response
|
# Prepare the JSON response
|
||||||
@@ -214,6 +215,7 @@ async def api_packets(request):
|
|||||||
"portnum": int(p.portnum),
|
"portnum": int(p.portnum),
|
||||||
"long_name": getattr(p.from_node, "long_name", ""),
|
"long_name": getattr(p.from_node, "long_name", ""),
|
||||||
"payload": (p.payload or "").strip(),
|
"payload": (p.payload or "").strip(),
|
||||||
|
"to_long_name": getattr(p.to_node, "long_name", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
reply_id = getattr(
|
reply_id = getattr(
|
||||||
@@ -422,44 +424,76 @@ async def api_edges(request):
|
|||||||
since = datetime.datetime.now() - datetime.timedelta(hours=48)
|
since = datetime.datetime.now() - datetime.timedelta(hours=48)
|
||||||
filter_type = request.query.get("type")
|
filter_type = request.query.get("type")
|
||||||
|
|
||||||
edges = {}
|
# NEW → optional single-node filter
|
||||||
|
node_filter_str = request.query.get("node_id")
|
||||||
|
node_filter = None
|
||||||
|
if node_filter_str:
|
||||||
|
try:
|
||||||
|
node_filter = int(node_filter_str)
|
||||||
|
except ValueError:
|
||||||
|
return web.json_response(
|
||||||
|
{"error": "node_id must be integer"},
|
||||||
|
status=400
|
||||||
|
)
|
||||||
|
|
||||||
# Only build traceroute edges if requested
|
edges = {}
|
||||||
|
traceroute_count = 0
|
||||||
|
neighbor_packet_count = 0
|
||||||
|
edges_added_tr = 0
|
||||||
|
edges_added_neighbor = 0
|
||||||
|
|
||||||
|
# --- Traceroute edges ---
|
||||||
if filter_type in (None, "traceroute"):
|
if filter_type in (None, "traceroute"):
|
||||||
async for tr in store.get_traceroutes(since):
|
async for tr in store.get_traceroutes(since):
|
||||||
|
traceroute_count += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
|
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.error(f"Error decoding Traceroute {tr.id}: {e}")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
path = [tr.packet.from_node_id] + list(route.route)
|
path = [tr.packet.from_node_id] + list(route.route)
|
||||||
path.append(tr.packet.to_node_id if tr.done else tr.gateway_node_id)
|
path.append(tr.packet.to_node_id if tr.done else tr.gateway_node_id)
|
||||||
|
|
||||||
for a, b in zip(path, path[1:], strict=False):
|
for a, b in zip(path, path[1:], strict=False):
|
||||||
edges[(a, b)] = "traceroute"
|
if (a, b) not in edges:
|
||||||
|
edges[(a, b)] = "traceroute"
|
||||||
|
edges_added_tr += 1
|
||||||
|
|
||||||
# Only build neighbor edges if requested
|
# --- Neighbor edges ---
|
||||||
if filter_type in (None, "neighbor"):
|
if filter_type in (None, "neighbor"):
|
||||||
packets = await store.get_packets(portnum=PortNum.NEIGHBORINFO_APP, after=since)
|
packets = await store.get_packets(portnum=71)
|
||||||
|
neighbor_packet_count = len(packets)
|
||||||
|
|
||||||
for packet in packets:
|
for packet in packets:
|
||||||
try:
|
try:
|
||||||
_, neighbor_info = decode_payload.decode(packet)
|
_, neighbor_info = decode_payload.decode(packet)
|
||||||
for node in neighbor_info.neighbors:
|
except Exception:
|
||||||
edges.setdefault((node.node_id, packet.from_node_id), "neighbor")
|
continue
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error decoding NeighborInfo packet {getattr(packet, 'id', '?')}: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert edges dict to list format for JSON response
|
for node in neighbor_info.neighbors:
|
||||||
|
edge = (node.node_id, packet.from_node_id)
|
||||||
|
if edge not in edges:
|
||||||
|
edges[edge] = "neighbor"
|
||||||
|
edges_added_neighbor += 1
|
||||||
|
|
||||||
|
# Convert to list
|
||||||
edges_list = [
|
edges_list = [
|
||||||
{"from": frm, "to": to, "type": edge_type} for (frm, to), edge_type in edges.items()
|
{"from": frm, "to": to, "type": edge_type}
|
||||||
|
for (frm, to), edge_type in edges.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# NEW → apply node_id filtering
|
||||||
|
if node_filter is not None:
|
||||||
|
edges_list = [
|
||||||
|
e for e in edges_list
|
||||||
|
if e["from"] == node_filter or e["to"] == node_filter
|
||||||
|
]
|
||||||
|
|
||||||
return web.json_response({"edges": edges_list})
|
return web.json_response({"edges": edges_list})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/api/config")
|
@routes.get("/api/config")
|
||||||
async def api_config(request):
|
async def api_config(request):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user