Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9481b6614a | ||
|
|
cffd35fe05 | ||
|
|
a2036802cf | ||
|
|
3d88827331 | ||
|
|
2c4abbf128 | ||
|
|
8cb2f16b19 | ||
|
|
1c254b707f | ||
|
|
77bf54be2a | ||
|
|
58d09a1d47 | ||
|
|
1fdfd76f42 | ||
|
|
722766b0a4 | ||
|
|
369b852080 | ||
|
|
1db40fe80b | ||
|
|
6c691c7ecb | ||
|
|
0142a28d89 | ||
|
|
d9e928d682 | ||
|
|
d452d99ef0 | ||
|
|
ee5243a205 | ||
|
|
37c0fedeec | ||
|
|
d10b71c6f5 | ||
|
|
7de9b1c943 | ||
|
|
3f5086eb82 | ||
|
|
6d3acd85ca | ||
|
|
944de7d560 | ||
|
|
a1088b733a | ||
|
|
d2cf940df1 | ||
|
|
d920c7e044 | ||
|
|
9a474420f2 | ||
|
|
65aefbfb51 | ||
|
|
cc117b93aa | ||
|
|
e3b84abaae | ||
|
|
38119f2e2b | ||
|
|
5a98701f51 | ||
|
|
c50036ceea | ||
|
|
7b0d334626 | ||
|
|
0374317968 | ||
|
|
bddf99aef2 | ||
|
|
c6bb3cb65f | ||
|
|
87a4226c21 | ||
|
|
6ba9488f19 | ||
|
|
a574db005c | ||
|
|
ea5c033c04 | ||
|
|
68adbe4cac | ||
|
|
b968bf9908 | ||
|
|
185933e779 | ||
|
|
2e352810c3 | ||
|
|
fce9f034f5 | ||
|
|
afd8db01d2 | ||
|
|
6894b5fe9c | ||
|
|
e89814818c | ||
|
|
904c7107f3 | ||
|
|
4f0b07c403 | ||
|
|
542172f119 | ||
|
|
b1344af667 | ||
|
|
695d6cd9a8 | ||
|
|
5f4f1094ff | ||
|
|
2b752eb807 | ||
|
|
17ebb0f5bd | ||
|
|
9ef76f6d6b | ||
|
|
e1ef436de5 | ||
|
|
f32ef36223 | ||
|
|
7cd8b8eb9a | ||
|
|
4b3ccf92f9 | ||
|
|
9d6f89105a | ||
|
|
851f6324d8 | ||
|
|
88f61b7588 | ||
|
|
696bb0b839 | ||
|
|
82e6103fc1 | ||
|
|
b30974355a | ||
|
|
523784f9c7 | ||
|
|
f15f7f5be0 | ||
|
|
02e02a72df | ||
|
|
5b10d3919d | ||
|
|
1066d1ff6a | ||
|
|
8e611a6a03 | ||
|
|
12a998242a | ||
|
|
0c2bdd33c7 | ||
|
|
daa71c5516 | ||
|
|
c3e2df6672 | ||
|
|
86cb621f10 | ||
|
|
192944f48c | ||
|
|
479d9d333c | ||
|
|
b5a96dcc2e | ||
|
|
1699097683 | ||
|
|
1577f59272 | ||
|
|
1954715077 | ||
|
|
c81a481e00 | ||
|
|
50d4923ad3 | ||
|
|
0c272cdc1e | ||
|
|
9996c59535 | ||
|
|
2e121c4c3b | ||
|
|
726075b0ab | ||
|
|
46c2b5f262 | ||
|
|
5d97a27bc3 | ||
|
|
bcee361006 | ||
|
|
9ee62f0866 | ||
|
|
8ffa89a503 | ||
|
|
35fe0a261b | ||
|
|
8670760390 | ||
|
|
54cffcf702 | ||
|
|
51b5e5c583 | ||
|
|
01325180d3 | ||
|
|
40cc2f6296 | ||
|
|
862c948472 | ||
|
|
6046c0e47e | ||
|
|
6a0cfafd94 | ||
|
|
d2e465cfa0 | ||
|
|
f7f382ce29 | ||
|
|
77cfbf257d | ||
|
|
01785fc024 | ||
|
|
bfecd2a3f3 | ||
|
|
d49c80b829 | ||
|
|
d707bb1674 | ||
|
|
20f81b2554 | ||
|
|
03aa01e6e7 | ||
|
|
214bb413bc | ||
|
|
3a1a29326d | ||
|
|
ebe2b32554 | ||
|
|
aec3ad9ed3 | ||
|
|
d71be596ab | ||
|
|
1eeabeaa91 | ||
|
|
948474a18e | ||
|
|
abe54e3b31 | ||
|
|
56cfc31e0f | ||
|
|
2f9effb04c | ||
|
|
d6fcc211dc | ||
|
|
84948adfa9 | ||
|
|
8ffdcce1fa | ||
|
|
3297518ff4 | ||
|
|
2233ab3383 | ||
|
|
d7dbc7e7d3 | ||
|
|
1c746f0a83 | ||
|
|
b14cbb2ea4 | ||
|
|
ea77054d1b | ||
|
|
1818383efd | ||
|
|
1248111621 | ||
|
|
c4fd4e33ff | ||
|
|
8189d6cf76 | ||
|
|
a7d5ea7e94 | ||
|
|
b900352920 | ||
|
|
b0bde79fc9 | ||
|
|
38c7569d0f | ||
|
|
2578415b83 | ||
|
|
625ee71e48 | ||
|
|
173ad48424 | ||
|
|
c6bdd3c8b2 | ||
|
|
671aefd11c | ||
|
|
ce61f38254 | ||
|
|
4b4eb3265d | ||
|
|
4452eae48f | ||
|
|
a7f7e8f93b | ||
|
|
69f9ad8bc3 | ||
|
|
501388eace | ||
|
|
ee174e7e5f | ||
|
|
ec0c91a334 | ||
|
|
78066d545f | ||
|
|
dba3b9b10a | ||
|
|
103082fb58 | ||
|
|
858de11b3b | ||
|
|
3ec58d8827 | ||
|
|
14ab1409e0 | ||
|
|
57158e4d63 | ||
|
|
f07e04e512 | ||
|
|
fd0fcb12ab | ||
|
|
bb09381ed7 | ||
|
|
e884514637 | ||
|
|
da51652ac1 | ||
|
|
c4f48a5ce6 | ||
|
|
16e6ac6ef9 | ||
|
|
0ef3b1cbe6 | ||
|
|
822e8e06c9 | ||
|
|
1ee012b012 | ||
|
|
80e876ab9c | ||
|
|
c4f274c8ce | ||
|
|
f8ae859614 | ||
|
|
02b011351c | ||
|
|
d9750a2f65 | ||
|
|
cc912172d9 | ||
|
|
c94a53dde9 | ||
|
|
848c840008 | ||
|
|
d2475f99e8 | ||
|
|
c57b3dc1c1 | ||
|
|
454adaa5d6 | ||
|
|
86652bd45f | ||
|
|
6bbc46a3af | ||
|
|
2042299fa8 | ||
|
|
264eb48756 | ||
|
|
ccd9d552f1 | ||
|
|
16cb8b396c | ||
|
|
13171616be | ||
|
|
98b8cfafe7 | ||
|
|
af0b50c070 | ||
|
|
91267d99e2 | ||
|
|
361323c490 | ||
|
|
78476b4d47 | ||
|
|
2fd53e3fd1 | ||
|
|
5416282788 | ||
|
|
a472d8762b | ||
|
|
5d128140cb | ||
|
|
4acdd127f5 | ||
|
|
9d1a408123 | ||
|
|
139ca7125f | ||
|
|
38755cd861 | ||
|
|
25a0632f77 | ||
|
|
ad692eef74 | ||
|
|
a7aa25aa30 | ||
|
|
77cc20635b | ||
|
|
90b2cd1fc5 | ||
|
|
a5e690e277 | ||
|
|
5ab71fcd4c | ||
|
|
bd27ed93e9 | ||
|
|
16ec124fc3 | ||
|
|
c2839d04e3 | ||
|
|
251c073a42 | ||
|
|
d2cb51d489 | ||
|
|
c5690d1edb | ||
|
|
e1ea1c4f02 | ||
|
|
12c27092be | ||
|
|
c3b9443701 | ||
|
|
0d89b09b1f | ||
|
|
77206a19ac | ||
|
|
89bfb14c77 | ||
|
|
4754504191 | ||
|
|
69acc29a95 | ||
|
|
2dc5dcde5c | ||
|
|
7809758537 | ||
|
|
f32b857705 | ||
|
|
21040e8876 | ||
|
|
193f45d568 | ||
|
|
e6d25f2fd1 | ||
|
|
a8fcd81c2b | ||
|
|
039df71307 | ||
|
|
3df59aa3a1 | ||
|
|
3212026a89 | ||
|
|
3fcba32b38 | ||
|
|
1095e1c5c4 | ||
|
|
6f4ad809ca | ||
|
|
effacf7b91 | ||
|
|
5af1d042cf | ||
|
|
f848bea33f | ||
|
|
94282b634e | ||
|
|
7b8298e292 | ||
|
|
0e04d37ad1 | ||
|
|
a5e86f8823 | ||
|
|
5946833e4d | ||
|
|
598ff8d08e | ||
|
|
c710fea5fd | ||
|
|
770acaa5e0 | ||
|
|
293a963e0e | ||
|
|
72ed413cf1 | ||
|
|
a290b3cd3d | ||
|
|
dbe8953942 | ||
|
|
3dec67f623 | ||
|
|
42de8e7403 | ||
|
|
8f0db66131 | ||
|
|
bcfa1bd8cc | ||
|
|
cd3bfc3c51 | ||
|
|
c4aa168af2 | ||
|
|
afc28b6e16 | ||
|
|
1590f17689 | ||
|
|
e6cbd88a88 | ||
|
|
cffa7f28ed | ||
|
|
bb326798f9 | ||
|
|
6719211cdb | ||
|
|
00251a5003 | ||
|
|
dc9749c042 | ||
|
|
ccc02ad438 | ||
|
|
d901ec40ae | ||
|
|
1fce10a1df | ||
|
|
341f3be6d6 | ||
|
|
011ede9966 | ||
|
|
eaf6f3d593 | ||
|
|
6b298d1648 | ||
|
|
f9e6d69ae6 | ||
|
|
7f48db7ddb | ||
|
|
b0ff5f50bf | ||
|
|
a0cda099e7 | ||
|
|
a9c52b8667 | ||
|
|
bbe9a0d5cd | ||
|
|
7a8529b841 | ||
|
|
87043f35cb | ||
|
|
90f8219f8d | ||
|
|
0d08f872b9 | ||
|
|
a364c91f25 | ||
|
|
ca0355c700 | ||
|
|
4118887cda | ||
|
|
3be19bb7a4 | ||
|
|
fbbfac6e5c | ||
|
|
bdee96fe97 | ||
|
|
70edffeeae | ||
|
|
569c990575 | ||
|
|
20dbcf06c1 | ||
|
|
e0d4504bca | ||
|
|
0060a01824 | ||
|
|
dbf7f5b18d | ||
|
|
25e70512ce | ||
|
|
af499556bb | ||
|
|
dc3da4c9d8 | ||
|
|
d492e1d4f2 | ||
|
|
fa902e6fb8 | ||
|
|
d35ccdf7a6 | ||
|
|
801e70f201 | ||
|
|
24ec09dd43 | ||
|
|
22d125a750 | ||
|
|
28029ba17f | ||
|
|
79f936c310 | ||
|
|
8cd8b0bf01 | ||
|
|
d6172fd6dd | ||
|
|
cd5cb28132 | ||
|
|
78f11dd0ee | ||
|
|
379c8773dc | ||
|
|
2f78b64dfd | ||
|
|
9a4820523f | ||
|
|
356b8a2331 | ||
|
|
cf1ab96c93 | ||
|
|
28d36dde31 | ||
|
|
9998c82ba0 | ||
|
|
6535c96e81 | ||
|
|
080cce1bf0 | ||
|
|
3c1c23c1b9 | ||
|
|
c729d3b25e | ||
|
|
a90df07ebe | ||
|
|
0579ec523e | ||
|
|
9ebee7a8f4 | ||
|
|
2b03bce0d5 | ||
|
|
948146ddc8 | ||
|
|
ce06bc4128 | ||
|
|
765e4afe4d | ||
|
|
0108494bd8 | ||
|
|
3ecea59da8 | ||
|
|
0b2fdb6439 | ||
|
|
94220e729b | ||
|
|
020e9d6b63 | ||
|
|
6a7be99a6a | ||
|
|
5debdb4689 | ||
|
|
057043864f | ||
|
|
8918603f96 | ||
|
|
68cea2e858 | ||
|
|
06cc266acd | ||
|
|
295755ec4c | ||
|
|
b99057df69 | ||
|
|
390b46f026 | ||
|
|
94d51912e6 | ||
|
|
ac28fbc53d | ||
|
|
648a9c3640 | ||
|
|
7f21b3b531 | ||
|
|
d1597ce00f | ||
|
|
0c8fcec23d | ||
|
|
2e03b4a413 | ||
|
|
a00cb2098b | ||
|
|
6aeaed988e | ||
|
|
7c9013a217 | ||
|
|
ab0308701c | ||
|
|
e13d543e73 | ||
|
|
d8ad0efcdf | ||
|
|
2e60af1e29 | ||
|
|
1af1295b8f | ||
|
|
a1a646983e | ||
|
|
4386854e9d | ||
|
|
e821a65dd4 | ||
|
|
3392c9db08 | ||
|
|
e53edfb00f | ||
|
|
1df63bb84b | ||
|
|
679f7986cc | ||
|
|
f375911c4a | ||
|
|
68ad535259 | ||
|
|
0cf677c8d5 | ||
|
|
ff02b1455d | ||
|
|
cdad811295 | ||
|
|
fc1e327b74 | ||
|
|
af2fac1465 | ||
|
|
15daa0270c | ||
|
|
80c9306db3 | ||
|
|
c3f073a380 | ||
|
|
fe35376450 | ||
|
|
64d1f0f7aa | ||
|
|
a61bb2dfb6 | ||
|
|
acfa7d2269 | ||
|
|
8f62fb7877 | ||
|
|
94409dd8e1 | ||
|
|
ac25326d24 | ||
|
|
34bdbe4709 | ||
|
|
8d98aca496 | ||
|
|
ec9ad1d309 | ||
|
|
a97195c57e | ||
|
|
22a1d92191 | ||
|
|
35596249b4 | ||
|
|
566c588377 | ||
|
|
9ab49d1431 | ||
|
|
f5a7132421 | ||
|
|
c66b315623 | ||
|
|
76ba4e917e | ||
|
|
d0da79bf56 | ||
|
|
66bf3f1578 | ||
|
|
6a358883f9 | ||
|
|
86622f83d0 | ||
|
|
66c3945bc4 | ||
|
|
38cb002ce8 | ||
|
|
36443fd838 | ||
|
|
8cc451546d | ||
|
|
1bdd923365 | ||
|
|
d5610de826 | ||
|
|
aaf85943a8 | ||
|
|
37ad0ac9eb | ||
|
|
eb2791e206 | ||
|
|
119cde65c7 | ||
|
|
cb66c22974 | ||
|
|
96c5d0d86f | ||
|
|
2d98f57b65 | ||
|
|
3e2fe721d3 | ||
|
|
52fd2f712c | ||
|
|
ad6b9f470f | ||
|
|
765f672bf5 | ||
|
|
67a5d75283 | ||
|
|
4ff010e14b | ||
|
|
1214b6ecf6 | ||
|
|
aababb8075 | ||
|
|
3b0406c5af | ||
|
|
6772f066ee | ||
|
|
d8df40816f | ||
|
|
f9bd61af49 | ||
|
|
bcdda8b751 | ||
|
|
dcb44d27fe | ||
|
|
799283fd46 | ||
|
|
d0f4939a4d | ||
|
|
d66f10e715 | ||
|
|
e421eb4244 | ||
|
|
ce16bf5aa4 | ||
|
|
3582499a3c | ||
|
|
1946000d14 | ||
|
|
01fa030ef9 | ||
|
|
90cf136b8c | ||
|
|
ee131c3681 | ||
|
|
85accf7c25 | ||
|
|
91426a89e5 | ||
|
|
d7e492cdc9 | ||
|
|
c5cad1dca6 | ||
|
|
85aa7a37b9 | ||
|
|
b87fad23d8 | ||
|
|
68ec7ee5d8 | ||
|
|
e33463b371 | ||
|
|
d4b80cf90b | ||
|
|
d7e32e9b03 | ||
|
|
449fb3ac36 | ||
|
|
1de92cd2e9 | ||
|
|
fdd3e7f971 | ||
|
|
6eece7172a | ||
|
|
5b2c25d8ee | ||
|
|
40c2bfa535 | ||
|
|
a10023fad6 | ||
|
|
0a1afa988e | ||
|
|
97b884f119 | ||
|
|
eb9b7159d4 | ||
|
|
65a53388bb | ||
|
|
2ca3eb5685 | ||
|
|
a4c817cd30 | ||
|
|
d5b03c09db | ||
|
|
0b9ebade38 | ||
|
|
ee1758a548 | ||
|
|
a2a45ac898 | ||
|
|
59d172765d | ||
|
|
d453ff809a | ||
|
|
ed0a99dbd9 | ||
|
|
32f31cb502 | ||
|
|
176d554ef9 | ||
|
|
2735c37fad | ||
|
|
f04ec36faf | ||
|
|
28f0ca4337 | ||
|
|
1dbf0b07b6 | ||
|
|
27ed4e58bd | ||
|
|
a7f56c0bd5 |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "meshtastic-web",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/r3dpoint/devcontainer-features/tailwindcss-standalone-cli:1": {
|
||||||
|
"version": "latest"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers-extra/features/pnpm:2": {
|
||||||
|
"version": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
if [ "$SKIP_SIMPLE_GIT_HOOKS" = "1" ]; then
|
|
||||||
echo "[INFO] SKIP_SIMPLE_GIT_HOOKS is set to 1, skipping hook."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$SIMPLE_GIT_HOOKS_RC" ]; then
|
|
||||||
. "$SIMPLE_GIT_HOOKS_RC"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# cd packages/web && bun run lint && bun run format
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
## Design Standards
|
||||||
|
|
||||||
|
All UI must comply with the [Meshtastic Client Design Standards](https://raw.githubusercontent.com/meshtastic/design/refs/heads/master/standards/meshtastic_design_standards_latest.md). Fetch and review this document before making any UI changes.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 3 # 1 day after pnpm's cutoff, prevent a race
|
||||||
|
open-pull-requests-limit: 10 # Increase PR limit
|
||||||
|
groups:
|
||||||
|
tanstack:
|
||||||
|
patterns:
|
||||||
|
- "@tanstack/*"
|
||||||
|
radix-ui:
|
||||||
|
patterns:
|
||||||
|
- "@radix-ui/*"
|
||||||
|
react-ecosystem:
|
||||||
|
patterns:
|
||||||
|
- react
|
||||||
|
- react-dom
|
||||||
|
- "@types/react*"
|
||||||
@@ -14,53 +14,64 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
- name: Setup Deno
|
- name: Setup Node.js
|
||||||
uses: denoland/setup-deno@v2
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
deno-version: v2.x
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||||
|
|
||||||
- name: Cache Deno dependencies
|
- name: Prewarm & Install (workspace)
|
||||||
uses: actions/cache@v4
|
run: |
|
||||||
with:
|
set -euo pipefail
|
||||||
path: ~/.cache/deno
|
pnpm fetch
|
||||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock', '**/package.json') }}
|
pnpm install --frozen-lockfile --offline
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-deno-
|
|
||||||
|
|
||||||
- name: Cache pnpm dependencies
|
- name: Build All Packages (with exclusions)
|
||||||
uses: actions/cache@v4
|
shell: bash
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
packages/web/node_modules
|
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-
|
|
||||||
|
|
||||||
- name: Build All Packages
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
for pkg_dir in packages/*/; do
|
# List packages to exclude (full paths under repo root)
|
||||||
pkg_dir=${pkg_dir%/} # Remove trailing slash
|
EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno" "packages/ui" "packages/web")
|
||||||
echo "🔍 Inspecting $pkg_dir..."
|
|
||||||
|
|
||||||
if [[ -f "$pkg_dir/package.json" ]] && [[ "$pkg_dir" != "packages/web" ]]; then
|
is_excluded() {
|
||||||
echo "🔧 Building with pnpm: $pkg_dir"
|
local dir="$1"
|
||||||
(cd "$pkg_dir" && pnpm install && pnpm run build:npm)
|
for ex in "${EXCLUDED_DIRS[@]}"; do
|
||||||
else
|
if [[ "$dir" == "$ex" ]]; then
|
||||||
echo "⚠️ Skipping $pkg_dir (web package or no package.json)"
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for pkg_dir in packages/*/; do
|
||||||
|
pkg_dir="${pkg_dir%/}" # trim trailing slash
|
||||||
|
|
||||||
|
# Must be a directory with a package.json
|
||||||
|
if [[ ! -f "$pkg_dir/package.json" ]]; then
|
||||||
|
echo "⚠️ Skipping $pkg_dir (no package.json)"
|
||||||
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Allow for exclusions
|
||||||
|
if is_excluded "$pkg_dir"; then
|
||||||
|
echo "🚫 Skipping $pkg_dir (excluded)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Optionally skip Deno-first packages automatically
|
||||||
|
if [[ -f "$pkg_dir/deno.json" || -f "$pkg_dir/deno.jsonc" ]]; then
|
||||||
|
echo "🦕 Skipping $pkg_dir (deno project)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔧 Building: $pkg_dir"
|
||||||
|
# No per-package install needed; workspace install already done
|
||||||
|
pnpm -C "$pkg_dir" run build:npm
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Download translations with Crowdin
|
- name: Download translations with Crowdin
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
with:
|
with:
|
||||||
base_url: "https://meshtastic.crowdin.com/api/v2"
|
base_url: "https://meshtastic.crowdin.com/api/v2"
|
||||||
config: "./packages/web/crowdin.yml"
|
config: "./apps/web/crowdin.yml"
|
||||||
upload_sources: false
|
upload_sources: false
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ on:
|
|||||||
# Monitor all .json files within the /i18n/locales/en/ directory.
|
# Monitor all .json files within the /i18n/locales/en/ directory.
|
||||||
# This ensures the workflow triggers if any the English namespace files are modified on the main branch.
|
# This ensures the workflow triggers if any the English namespace files are modified on the main branch.
|
||||||
paths:
|
paths:
|
||||||
- "/packages/web/public/i18n/locales/en/*.json"
|
- "/apps/web/public/i18n/locales/en/*.json"
|
||||||
branches: [main]
|
branches: [main]
|
||||||
workflow_dispatch: # Allow manual triggering
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
@@ -15,13 +15,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Upload sources with Crowdin
|
- name: Upload sources with Crowdin
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
with:
|
with:
|
||||||
base_url: "https://meshtastic.crowdin.com/api/v2"
|
base_url: "https://meshtastic.crowdin.com/api/v2"
|
||||||
config: "./packages/web/crowdin.yml"
|
config: "./apps/web/crowdin.yml"
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
download_translations: false
|
download_translations: false
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Upload translations with Crowdin
|
- name: Upload translations with Crowdin
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
with:
|
with:
|
||||||
base_url: "https://meshtastic.crowdin.com/api/v2"
|
base_url: "https://meshtastic.crowdin.com/api/v2"
|
||||||
config: "./packages/web/crowdin.yml"
|
config: "./apps/web/crowdin.yml"
|
||||||
upload_sources: false
|
upload_sources: false
|
||||||
upload_translations: true
|
upload_translations: true
|
||||||
download_translations: false
|
download_translations: false
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Real-device end-to-end suite: drives the web app in Chromium against two
|
||||||
|
# simulated `meshtasticd` firmware nodes (UDP-multicast mesh, no MQTT) and
|
||||||
|
# exercises text messaging in both directions. See e2e/README.md.
|
||||||
|
name: E2E (real device)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "e2e/**"
|
||||||
|
- "apps/web/**"
|
||||||
|
- "packages/**"
|
||||||
|
- "playwright.config.ts"
|
||||||
|
- ".github/workflows/e2e.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
env:
|
||||||
|
CI: "true"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install workspace deps
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Install Chromium
|
||||||
|
run: pnpm exec playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v7
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Install Python mesh peer
|
||||||
|
run: |
|
||||||
|
python -m venv e2e/peer/.venv
|
||||||
|
e2e/peer/.venv/bin/pip install -r e2e/peer/requirements.txt
|
||||||
|
|
||||||
|
- name: Run E2E suite
|
||||||
|
run: pnpm test:e2e
|
||||||
|
|
||||||
|
- name: Upload Playwright report
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: playwright-report
|
||||||
|
path: e2e/.report
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Dump device logs on failure
|
||||||
|
if: failure()
|
||||||
|
run: docker compose -f e2e/device/docker-compose.yml logs --no-color || true
|
||||||
|
|
||||||
|
- name: Tear down mesh
|
||||||
|
if: always()
|
||||||
|
run: docker compose -f e2e/device/docker-compose.yml down -v || true
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 */4 * * *" # every 4 hours
|
||||||
|
workflow_dispatch: # allow manual runs
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v11
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-issue-stale: 60
|
||||||
|
days-before-issue-close: 14
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
only-issue-labels: "Bug"
|
||||||
|
remove-stale-when-updated: true
|
||||||
@@ -1,101 +1,118 @@
|
|||||||
name: "Nightly Release"
|
name: Nightly Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 5 * * *" # Run every day at 5am UTC
|
- cron: "0 5 * * *" # 05:00 UTC daily
|
||||||
|
workflow_dispatch: {} # allow manual runs too
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightly-build-and-push:
|
nightly-build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
steps:
|
||||||
uses: actions/setup-node@v4
|
- name: Checkout code
|
||||||
with:
|
uses: actions/checkout@v7
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
version: latest
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||||
|
|
||||||
- name: Cache pnpm dependencies
|
- name: Install dependencies (root)
|
||||||
uses: actions/cache@v4
|
run: pnpm install --frozen-lockfile
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
packages/web/node_modules
|
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('packages/web/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-
|
|
||||||
|
|
||||||
# - name: Run tests
|
- name: Run tests
|
||||||
# working-directory: packages/web
|
run: pnpm run test
|
||||||
# run: deno task test
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Build web package
|
||||||
working-directory: packages/web
|
working-directory: apps/web
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Build Package
|
|
||||||
working-directory: packages/web
|
|
||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Package Output
|
- name: Package output
|
||||||
working-directory: packages/web
|
working-directory: apps/web
|
||||||
run: pnpm run package
|
run: pnpm run package
|
||||||
|
|
||||||
- name: Archive compressed build
|
- name: Upload compressed build (artifact)
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: build
|
name: web-build-nightly
|
||||||
path: packages/web/dist/build.tar
|
path: apps/web/dist/build.tar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Get latest release version
|
- name: Compute tags and labels
|
||||||
id: get_release
|
id: meta
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
LATEST_TAG=$(curl -sL \
|
set -euo pipefail
|
||||||
-H "Accept: application/vnd.github+json" \
|
DATE="$(date -u +%Y%m%d)"
|
||||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
ISO_CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r ".tag_name")
|
SHORTSHA="$(git rev-parse --short=12 HEAD)"
|
||||||
# Fallback to a default if no release is found
|
|
||||||
if [ -z "$LATEST_TAG" ]; then
|
# Try to use latest release tag if it exists; fallback to package version; else date
|
||||||
LATEST_TAG="2.6.0"
|
LATEST_TAG="$(gh release view --json tagName --jq .tagName 2>/dev/null || true)"
|
||||||
|
if [ -z "$LATEST_TAG" ] && [ -f apps/web/package.json ]; then
|
||||||
|
LATEST_TAG="v$(jq -r .version apps/web/package.json)"
|
||||||
fi
|
fi
|
||||||
echo "tag=${LATEST_TAG}" >> $GITHUB_OUTPUT
|
|
||||||
|
if [ -n "${LATEST_TAG:-}" ] && [ "$LATEST_TAG" != "vnull" ]; then
|
||||||
|
IMMUTABLE="nightly-${LATEST_TAG}-${SHORTSHA}"
|
||||||
|
else
|
||||||
|
IMMUTABLE="nightly-${DATE}-${SHORTSHA}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Outputs
|
||||||
|
echo "moving_tag=nightly" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "immutable_tag=${IMMUTABLE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "all_tags=nightly ${IMMUTABLE}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "created=${ISO_CREATED}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Resolved tags: nightly and ${IMMUTABLE}"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Buildah Build
|
- name: Build Container Image (multi-arch)
|
||||||
id: build-container
|
id: build-container
|
||||||
uses: redhat-actions/buildah-build@v2
|
uses: redhat-actions/buildah-build@v3
|
||||||
with:
|
with:
|
||||||
containerfiles: |
|
containerfiles: |
|
||||||
./packages/web/infra/Containerfile
|
./apps/web/infra/Containerfile
|
||||||
image: ${{ github.event.repository.full_name }}
|
image: ${{ env.REGISTRY_IMAGE }}
|
||||||
tags: nightly-${{ steps.get_release.outputs.tag }}-${{ github.sha }}
|
tags: |
|
||||||
|
${{ steps.meta.outputs.moving_tag }}
|
||||||
|
${{ steps.meta.outputs.immutable_tag }}
|
||||||
oci: true
|
oci: true
|
||||||
platforms: linux/amd64, linux/arm64
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.source=${{ github.repository }}
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
|
org.opencontainers.image.created=${{ steps.meta.outputs.created }}
|
||||||
|
|
||||||
- name: Push To Registry
|
- name: Push To GHCR
|
||||||
id: push-to-registry
|
id: push-to-registry
|
||||||
uses: redhat-actions/push-to-registry@v2
|
uses: redhat-actions/push-to-registry@v3
|
||||||
with:
|
with:
|
||||||
image: ${{ steps.build-container.outputs.image }}
|
image: ${{ steps.build-container.outputs.image }}
|
||||||
tags: ${{ steps.build-container.outputs.tags }}
|
# Push the same tags used at build time:
|
||||||
|
tags: ${{ steps.meta.outputs.all_tags }}
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Print image url
|
- name: Print image URLs
|
||||||
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
|
run: |
|
||||||
|
echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}"
|
||||||
|
echo "Immutable tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.immutable_tag }}"
|
||||||
|
|||||||
@@ -1,49 +1,61 @@
|
|||||||
name: Pull Request CI
|
name: Pull Request CI
|
||||||
|
|
||||||
on: pull_request
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: read
|
||||||
packages: write
|
|
||||||
|
concurrency:
|
||||||
|
group: pr-${{ github.event.pull_request.number }}-ci
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-package:
|
build-and-package:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.pull_request.draft == false }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Setup Node.js
|
||||||
# Commands will run from 'packages/web'
|
uses: actions/setup-node@v7
|
||||||
working-directory: packages/web
|
|
||||||
run: pnpm install
|
|
||||||
|
|
||||||
- name: Cache pnpm dependencies
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
with:
|
||||||
path: |
|
node-version: 24
|
||||||
~/.pnpm-store
|
cache: pnpm
|
||||||
packages/web/node_modules
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('packages/web/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
- name: Debug dependency state
|
||||||
${{ runner.os }}-pnpm-
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "GITHUB_SHA=${GITHUB_SHA}"
|
||||||
|
git log --oneline -1
|
||||||
|
pnpm --version
|
||||||
|
node --version
|
||||||
|
grep -n "meshtastic__protobufs" pnpm-lock.yaml || true
|
||||||
|
grep -n "2.7.18" pnpm-lock.yaml || true
|
||||||
|
grep -n "2.7.20" pnpm-lock.yaml || true
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
- name: Check formatter
|
- name: Check formatter
|
||||||
run: pnpm run check
|
run: pnpm run check
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Build Package
|
- name: Run tests
|
||||||
working-directory: packages/web
|
run: pnpm run test
|
||||||
run: pnpm run build
|
|
||||||
|
- name: Build web package
|
||||||
|
run: pnpm --filter "./apps/web" run build
|
||||||
|
|||||||
@@ -4,83 +4,142 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
packages:
|
packages:
|
||||||
description: 'Packages to release (comma-separated, or "all" for all packages)'
|
description: 'Packages to release (comma-separated, or "all")'
|
||||||
required: false
|
required: false
|
||||||
default: 'all'
|
default: "all"
|
||||||
|
bump:
|
||||||
|
description: "Semver bump (patch | minor | major)"
|
||||||
|
required: false
|
||||||
|
default: "patch"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # we commit the bumped versions back
|
||||||
|
id-token: write # required for JSR OIDC
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
# --- Setup Node.js and pnpm ---
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
version: latest
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
# --- Setup Deno ---
|
- name: Install dependencies
|
||||||
- name: Setup Deno
|
run: pnpm install --frozen-lockfile
|
||||||
uses: denoland/setup-deno@v2
|
|
||||||
with:
|
|
||||||
deno-version: v2.x
|
|
||||||
|
|
||||||
# --- Cache pnpm Dependencies ---
|
- name: Resolve package list
|
||||||
- name: Cache pnpm Dependencies
|
id: pkgs
|
||||||
uses: actions/cache@v4
|
shell: bash
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
packages/web/node_modules
|
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-
|
|
||||||
|
|
||||||
# --- Cache Deno Dependencies ---
|
|
||||||
- name: Cache Deno Dependencies
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.cache/deno
|
|
||||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-deno-
|
|
||||||
|
|
||||||
- name: Configure pnpm registry
|
|
||||||
run: pnpm config set registry https://registry.npmjs.org/
|
|
||||||
|
|
||||||
- name: Configure pnpm auth
|
|
||||||
run: pnpm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Publish packages to npm and JSR
|
|
||||||
run: |
|
run: |
|
||||||
for dir in packages/*; do
|
set -euo pipefail
|
||||||
echo "Processing $dir"
|
if [ "${{ github.event.inputs.packages }}" = "all" ] || [ -z "${{ github.event.inputs.packages }}" ]; then
|
||||||
|
mapfile -t TARGETS < <(ls -d packages/*)
|
||||||
|
else
|
||||||
|
IFS=',' read -ra TARGETS <<< "${{ github.event.inputs.packages }}"
|
||||||
|
TARGETS=("${TARGETS[@]/#/packages/}")
|
||||||
|
fi
|
||||||
|
printf '%s\n' "${TARGETS[@]}" | paste -sd, - > targets.txt
|
||||||
|
echo "list=$(cat targets.txt)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Targets: $(cat targets.txt)"
|
||||||
|
|
||||||
cd $dir
|
- name: Bump package.json versions (no git tag)
|
||||||
|
shell: bash
|
||||||
# Build and publish to npm if package.json exists
|
run: |
|
||||||
if [ -f "package.json" ]; then
|
set -euo pipefail
|
||||||
echo "Building and publishing $dir to npm..."
|
BUMP="${{ github.event.inputs.bump }}"
|
||||||
pnpm run build:npm
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
pnpm run publish:npm || echo "npm publish failed for $dir"
|
for dir in "${TARGETS[@]}"; do
|
||||||
|
if [ -f "$dir/package.json" ]; then
|
||||||
|
echo "Bumping $dir -> $BUMP"
|
||||||
|
(cd "$dir" && npm version "$BUMP" --no-git-tag-version --allow-same-version)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pnpm run prepare:jsr
|
|
||||||
|
|
||||||
# Publish to JSR if jsr.json exists
|
|
||||||
if [ -f "jsr.json" ]; then
|
|
||||||
echo "Publishing $dir to jsr..."
|
|
||||||
deno publish || echo "JSR publish failed for $dir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd - > /dev/null
|
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Generate jsr.json from package.json (pkg-to-jsr)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
|
for dir in "${TARGETS[@]}"; do
|
||||||
|
if [ -f "$dir/package.json" ]; then
|
||||||
|
echo "Generating jsr.json for $dir"
|
||||||
|
pnpm dlx pkg-to-jsr --root "$dir"
|
||||||
|
# # Optional: show result
|
||||||
|
# jq -C . "$dir/jsr.json" || cat "$dir/jsr.json"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Commit version bumps
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add packages/*/package.json packages/*/jsr.json 2>/dev/null || true
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
git checkout -b version_bump
|
||||||
|
git commit -m "chore(release): bump package versions (${{ github.event.inputs.bump }})"
|
||||||
|
git push -u origin version_bump
|
||||||
|
gh pr create --title "Bump package versions" --body "Bump versions of NPM and JSR packages" --base main --head version_bump
|
||||||
|
echo "Pushed to branch version_bump"
|
||||||
|
else
|
||||||
|
echo "No changes to commit."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build selected packages
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
|
for dir in "${TARGETS[@]}"; do
|
||||||
|
echo "Building $dir"
|
||||||
|
pnpm --filter "./$dir" run build
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Publish to JSR (OIDC)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
|
for dir in "${TARGETS[@]}"; do
|
||||||
|
if [ -f "$dir/jsr.json" ]; then
|
||||||
|
echo "Publishing $dir to JSR via OIDC…"
|
||||||
|
( cd "$dir"
|
||||||
|
[ -d dist ] || pnpm run build
|
||||||
|
npx --yes jsr publish
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Configure npm auth
|
||||||
|
run: |
|
||||||
|
pnpm config set //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
||||||
|
pnpm config set registry https://registry.npmjs.org/
|
||||||
|
|
||||||
|
- name: Publish to npm
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
|
for dir in "${TARGETS[@]}"; do
|
||||||
|
if [ -f "$dir/package.json" ]; then
|
||||||
|
echo "Publishing $dir to npm…"
|
||||||
|
( cd "$dir"
|
||||||
|
[ -d dist ] || pnpm run build
|
||||||
|
npm publish --access public
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
name: Create Protobuf Release for JSR
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version to publish (e.g. v1.2.3). Used when manually dispatching."
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
dry_run:
|
||||||
|
description: "Don't actually publish to JSR (passes --dry-run)."
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
permissions: write-all
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROTOBUF_DIR: ./packages/protobufs
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codegen:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Show files exist
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
ls -la $PROTOBUF_DIR/packages/ts || true
|
||||||
|
cat $PROTOBUF_DIR/packages/ts/deno.json
|
||||||
|
|
||||||
|
- name: Determine VERSION
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -n "${{ inputs.version }}" ]; then
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
else
|
||||||
|
echo "No 'version' input. Provide inputs.version." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
STRIPPED="${VERSION#v}"
|
||||||
|
echo "VERSION=$STRIPPED" >> "$GITHUB_ENV"
|
||||||
|
echo "Resolved VERSION=$STRIPPED"
|
||||||
|
|
||||||
|
- name: Set Package Versions to current tag
|
||||||
|
working-directory: ${{ env.PROTOBUF_DIR }}/packages/ts
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
for f in deno.json; do
|
||||||
|
test -f "$f"
|
||||||
|
jq --arg version "${VERSION//\//-}" '
|
||||||
|
walk(
|
||||||
|
if type == "string" and test("__PACKAGE_VERSION__")
|
||||||
|
then sub("__PACKAGE_VERSION__"; $version)
|
||||||
|
else .
|
||||||
|
end
|
||||||
|
)
|
||||||
|
' "$f" > "$f.tmp"
|
||||||
|
mv "$f.tmp" "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Setup Buf
|
||||||
|
uses: bufbuild/buf-setup-action@main
|
||||||
|
with:
|
||||||
|
github_token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v6
|
||||||
|
|
||||||
|
- name: Install workspace dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Generate code
|
||||||
|
run: pnpm --filter @meshtastic/protobufs-ws build
|
||||||
|
|
||||||
|
- name: Move generated .ts files and clean up
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
SRC_DIR="$PROTOBUF_DIR/packages/ts/dist/meshtastic"
|
||||||
|
DEST_DIR="$PROTOBUF_DIR/packages/ts/dist"
|
||||||
|
|
||||||
|
if [ -d "$SRC_DIR" ]; then
|
||||||
|
shopt -s nullglob
|
||||||
|
ts_files=("$SRC_DIR"/*.ts)
|
||||||
|
if [ ${#ts_files[@]} -gt 0 ]; then
|
||||||
|
mv "$SRC_DIR"/*.ts "$DEST_DIR"/
|
||||||
|
fi
|
||||||
|
rm -rf "$SRC_DIR"
|
||||||
|
else
|
||||||
|
echo "Source directory not found: $SRC_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove nanopb_pb.ts if present
|
||||||
|
if [ -f "$DEST_DIR/nanopb_pb.ts" ]; then
|
||||||
|
rm "$DEST_DIR/nanopb_pb.ts"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Copy license & README
|
||||||
|
run: |
|
||||||
|
cp LICENSE $PROTOBUF_DIR/packages/ts
|
||||||
|
cp README.md $PROTOBUF_DIR/packages/ts
|
||||||
|
|
||||||
|
- name: Upload TypeScript code
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ts_code
|
||||||
|
path: ${{ env.PROTOBUF_DIR }}/packages/ts
|
||||||
|
|
||||||
|
publish-jsr:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: codegen
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
env:
|
||||||
|
DRY_RUN: ${{ inputs.dry_run }}
|
||||||
|
steps:
|
||||||
|
- name: Download TypeScript code
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
name: ts_code
|
||||||
|
|
||||||
|
- name: Set up Deno
|
||||||
|
uses: denoland/setup-deno@main
|
||||||
|
with:
|
||||||
|
deno-version: rc
|
||||||
|
|
||||||
|
- name: Publish to JSR
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
FLAGS="--unstable-sloppy-imports"
|
||||||
|
if [ "${DRY_RUN}" = "true" ]; then
|
||||||
|
FLAGS="$FLAGS --dry-run"
|
||||||
|
echo "Running publish in dry-run mode..."
|
||||||
|
fi
|
||||||
|
deno publish $FLAGS
|
||||||
@@ -3,77 +3,156 @@ name: Release Web
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [released, prereleased]
|
types: [released, prereleased]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
ref:
|
||||||
|
description: "Git ref (branch, tag, or SHA) to build"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
tag_name:
|
||||||
|
description: "Tag to use for artifacts/images (defaults to <sha>)"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
attach_to_release:
|
||||||
|
description: "Upload build.tar to an existing GitHub Release (requires tag_name)"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-web:
|
release-web:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup pnpm
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: pnpm/action-setup@v6
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||||
|
|
||||||
- name: Cache Bun Dependencies
|
- name: Install dependencies
|
||||||
uses: actions/cache@v4
|
run: pnpm install --frozen-lockfile
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.bun/install/cache
|
|
||||||
packages/web/node_modules
|
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-bun-
|
|
||||||
|
|
||||||
- name: Run Web App Tests
|
- name: Build web package
|
||||||
working-directory: packages/web
|
working-directory: apps/web
|
||||||
run: bun run test
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Create Web App Release Archive
|
- name: Create release archive
|
||||||
working-directory: packages/web
|
working-directory: apps/web
|
||||||
run: bun run package
|
run: pnpm run package
|
||||||
|
|
||||||
- name: Upload Web App Archive
|
- name: Upload archive (artifact)
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: web-build
|
name: web-build
|
||||||
|
path: apps/web/dist/build.tar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
path: packages/web/dist/build.tar
|
|
||||||
|
|
||||||
- name: Attach Web Archive to GitHub Release
|
- name: Attach archive to GitHub Release
|
||||||
run: gh release upload ${{ github.event.release.tag_name }} packages/web/dist/build.tar
|
if: ${{ github.event_name == 'release' || inputs.attach_to_release == true }}
|
||||||
|
uses: softprops/action-gh-release@v3
|
||||||
|
with:
|
||||||
|
files: apps/web/dist/build.tar
|
||||||
|
tag_name: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag_name }}
|
||||||
|
fail_on_unmatched_files: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Compute created timestamp & short SHA for labels/tags
|
||||||
|
- name: Compute meta
|
||||||
|
id: gen
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "shortsha=$(git rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Build/tag matrix:
|
||||||
|
# - For release events: vX.Y.Z, semver helpers, and latest (only if NOT prerelease)
|
||||||
|
# - For prerelease events: vX.Y.Z and semver helpers (NO latest)
|
||||||
|
# - For manual runs:
|
||||||
|
# * if tag_name set: that raw tag
|
||||||
|
# * else: adhoc-<shortsha>
|
||||||
|
- name: Docker metadata (tags & labels)
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v6
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY_IMAGE }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.source=${{ github.repository }}
|
||||||
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
|
org.opencontainers.image.created=${{ steps.gen.outputs.created }}
|
||||||
|
tags: |
|
||||||
|
# Full release (semantic tag)
|
||||||
|
type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }}
|
||||||
|
# Semver helpers (major, major.minor) for releases/prereleases
|
||||||
|
type=semver,pattern={{version}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }}
|
||||||
|
type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
|
||||||
|
type=semver,pattern={{major}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
|
||||||
|
# Latest only for full releases (not prereleases)
|
||||||
|
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
|
||||||
|
# Manual: provided tag_name
|
||||||
|
type=raw,value=${{ inputs.tag_name }},enable=${{ github.event_name == 'workflow_dispatch' && inputs.tag_name != '' }}
|
||||||
|
# Manual: fallback to adhoc-<shortsha>
|
||||||
|
type=raw,value=adhoc-${{ steps.gen.outputs.shortsha }},enable=${{ github.event_name == 'workflow_dispatch' && inputs.tag_name == '' }}
|
||||||
|
# Always add immutable sha tag for traceability
|
||||||
|
type=raw,value=sha-${{ steps.gen.outputs.shortsha }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Build Container Image
|
- name: Set up Buildx
|
||||||
id: build-container
|
uses: docker/setup-buildx-action@v4
|
||||||
uses: redhat-actions/buildah-build@v2
|
|
||||||
with:
|
|
||||||
containerfiles: |
|
|
||||||
./infra/Containerfile
|
|
||||||
image: ghcr.io/${{ github.repository }}
|
|
||||||
tags: latest, ${{ github.event.release.tag_name }}
|
|
||||||
oci: true
|
|
||||||
platforms: linux/amd64, linux/arm64
|
|
||||||
|
|
||||||
- name: Push Container to GHCR
|
- name: Login to GHCR
|
||||||
id: push-to-registry
|
uses: docker/login-action@v4.5.2
|
||||||
uses: redhat-actions/push-to-registry@v2
|
|
||||||
with:
|
with:
|
||||||
image: ${{ steps.build-container.outputs.image }}
|
|
||||||
tags: ${{ steps.build-container.outputs.tags }}
|
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Output Image URL
|
# Push images for:
|
||||||
run: echo "🖼️ Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
|
# - release (released or prereleased)
|
||||||
|
# - manual with a tag_name (adhoc builds can still be pushed by name)
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./apps/web/infra/Containerfile
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
push: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '') }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
provenance: false
|
||||||
|
|
||||||
|
- name: Output image refs
|
||||||
|
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '') }}
|
||||||
|
run: |
|
||||||
|
echo "🖼️ Pushed tags:"
|
||||||
|
echo "${{ steps.meta.outputs.tags }}"
|
||||||
|
echo
|
||||||
|
echo "Pull examples:"
|
||||||
|
if [[ "${{ github.event_name }}" == "release" && "${{ github.event.release.prerelease }}" == "false" ]]; then
|
||||||
|
echo " docker pull ${{ env.REGISTRY_IMAGE }}:latest"
|
||||||
|
fi
|
||||||
|
# Always available:
|
||||||
|
echo " docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.event.release.tag_name || inputs.tag_name || format('adhoc-{0}', steps.gen.outputs.shortsha) }}"
|
||||||
|
echo " docker pull ${{ env.REGISTRY_IMAGE }}:sha-${{ steps.gen.outputs.shortsha }}"
|
||||||
|
|
||||||
|
- name: Explain no image push
|
||||||
|
if: ${{ !(github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '')) }}
|
||||||
|
run: echo "ℹ️ No image pushed (manual run without tag_name)."
|
||||||
|
|||||||
@@ -7,44 +7,89 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: update-stable-${{ github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-stable-branch:
|
update-stable-branch:
|
||||||
name: Update Stable Branch from Main
|
name: Update stable from latest release source
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
fetch-tags: true # IMPORTANT: we need tags to resolve the release commit
|
||||||
|
|
||||||
- name: Configure Git
|
- name: Configure Git author
|
||||||
run: |
|
run: |
|
||||||
git config user.name "GitHub Actions Bot"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
- name: Fetch latest main and stable branches
|
- name: Determine source SHA (prefer tag)
|
||||||
|
id: meta
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
git fetch origin main:main
|
set -euo pipefail
|
||||||
git fetch origin stable:stable || echo "Stable branch not found remotely, will create."
|
|
||||||
|
|
||||||
- name: Get latest main commit SHA
|
TAG="${{ github.event.release.tag_name }}"
|
||||||
id: get_main_sha
|
TARGET="${{ github.event.release.target_commitish || '' }}"
|
||||||
run: echo "MAIN_SHA=$(git rev-parse main)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Check out stable branch
|
# Always ensure we have latest remote heads/tags
|
||||||
run: |
|
git fetch --tags --prune origin
|
||||||
if git show-ref --verify --quiet refs/heads/stable; then
|
|
||||||
git checkout stable
|
SHA=""
|
||||||
git pull origin stable # Sync with remote stable if it exists
|
SRC_DESC=""
|
||||||
else
|
|
||||||
echo "Creating local stable branch based on main HEAD."
|
# 1) Prefer the release tag commit
|
||||||
git checkout -b stable ${{ env.MAIN_SHA }}
|
if [ -n "${TAG}" ] && git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||||
|
SHA="$(git rev-list -n1 "${TAG}")"
|
||||||
|
SRC_DESC="tag:${TAG}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Reset stable branch to latest main
|
# 2) Fall back to target_commitish if it points to a branch on origin
|
||||||
run: git reset --hard ${{ env.MAIN_SHA }}
|
if [ -z "${SHA}" ] && [ -n "${TARGET}" ] && git ls-remote --exit-code --heads origin "${TARGET}" >/dev/null 2>&1; then
|
||||||
|
git fetch origin "${TARGET}:${TARGET}" --prune
|
||||||
|
SHA="$(git rev-parse "${TARGET}^{commit}")"
|
||||||
|
SRC_DESC="branch:${TARGET}"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Force push stable branch
|
# 3) Fall back to main if present
|
||||||
run: git push origin stable --force
|
if [ -z "${SHA}" ] && git ls-remote --exit-code --heads origin "main" >/dev/null 2>&1; then
|
||||||
|
git fetch origin "main:main" --prune
|
||||||
|
SHA="$(git rev-parse "main^{commit}")"
|
||||||
|
SRC_DESC="branch:main"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${SHA}" ]; then
|
||||||
|
echo "::error::Unable to resolve source commit from tag (${TAG}), target_commitish (${TARGET}), or main."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Using source: ${SRC_DESC}"
|
||||||
|
echo "sha=${SHA}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "source=${SRC_DESC}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Create/reset local stable to SHA
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# Make sure we know if remote stable exists (non-fatal if not)
|
||||||
|
git fetch origin stable:refs/remotes/origin/stable || true
|
||||||
|
|
||||||
|
# Create or reset local stable in one command
|
||||||
|
git checkout -B stable "${{ steps.meta.outputs.sha }}"
|
||||||
|
git status --short --branch
|
||||||
|
|
||||||
|
- name: Push stable (force-with-lease)
|
||||||
|
run: |
|
||||||
|
# Safer than --force; refuses if remote moved unexpectedly (protects against races)
|
||||||
|
REMOTE_STABLE_SHA="$(git rev-parse refs/remotes/origin/stable || echo '')"
|
||||||
|
if [ -z "$REMOTE_STABLE_SHA" ]; then
|
||||||
|
# If remote stable doesn't exist, just use --force-with-lease=stable (no SHA)
|
||||||
|
git push origin stable:stable --force-with-lease=stable
|
||||||
|
else
|
||||||
|
# Use the specific SHA for maximum safety
|
||||||
|
git push origin stable:stable --force-with-lease=stable:$REMOTE_STABLE_SHA
|
||||||
|
fi
|
||||||
|
|||||||
@@ -8,3 +8,16 @@ __screenshots__*
|
|||||||
*.diff
|
*.diff
|
||||||
npm/
|
npm/
|
||||||
.idea
|
.idea
|
||||||
|
**/LICENSE
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
packages/protobufs/packages/ts/dist
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Local dev certs
|
||||||
|
*.pem
|
||||||
|
*.crt
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# Claude Code session locks
|
||||||
|
.claude/
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pnpm lint-staged
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json",
|
||||||
|
"useTabs": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 80,
|
||||||
|
"ignorePatterns": ["*.md"]
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["bradlc.vscode-tailwindcss", "denoland.vscode-deno"]
|
"recommendations": ["bradlc.vscode-tailwindcss", "oxc.oxc-vscode"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"search.exclude": {
|
||||||
"source.fixAll.biome": "explicit",
|
"**/i18n/locales/*-*/**": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Meshtastic Web Client - Claude Code Guide
|
||||||
|
|
||||||
|
@AGENTS.md
|
||||||
|
|
||||||
|
## Claude-Specific Instructions
|
||||||
|
|
||||||
|
- **Think First:** Always outline your step-by-step reasoning inside `<thinking>` tags before writing code or shell commands. Claude models perform significantly better on complex TypeScript tasks when they "think out loud" first.
|
||||||
|
- **Skills:** The `.skills/` directory contains task-specific instruction modules. Load them as needed — only the skill relevant to your current task.
|
||||||
|
- **Plan Mode:** Use plan mode for architectural changes spanning multiple modules. Write plans to `.agent_plans/` (git-ignored). The Copilot-CLI-specific `/plan`, `/delegate`, `/research`, and `/share` guidance in `AGENTS.md` does not apply to Claude Code — skip the `<copilot_cli_workflow>` section.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Protobuf catch-up to firmware-current
|
||||||
|
|
||||||
|
> Draft PR description + the one decision for maintainers. This file is **untracked** —
|
||||||
|
> use it as the PR body, then delete it (or leave it, it won't be committed).
|
||||||
|
|
||||||
|
## What this does
|
||||||
|
|
||||||
|
- Syncs the vendored `.proto` sources in `packages/protobufs/meshtastic/` from ~v2.7.20 to
|
||||||
|
**firmware-current (v2.7.25 + 48 commits)** — +2 new files (`deviceonly_legacy`, `serial_hal`)
|
||||||
|
and additions across `config` / `module_config` / `mesh` / `admin` / `portnums` / `telemetry`.
|
||||||
|
- Regenerates the v2 TS bindings (`buf generate`).
|
||||||
|
- Makes the app consume the **workspace** protobufs (`workspace:*`) instead of the stale JSR
|
||||||
|
`@meshtastic/protobufs@2.7.20`, finishing the monorepo migration that was half-done
|
||||||
|
(`@meshtastic/core` was already `workspace:*`; protobufs was still on JSR).
|
||||||
|
- One breaking-change fix: `admin.proto` `nodedb_reset` changed `int32 → bool`, so
|
||||||
|
`MeshDevice.resetNodes()` now sends `value: true` (the oneof-case presence triggers the
|
||||||
|
reset; `true` preserves favorites through it).
|
||||||
|
|
||||||
|
## Free wins (dynamic enum dropdowns — no UI code)
|
||||||
|
|
||||||
|
New LoRa regions (ITU/EU amateur bands), modem presets (LONG_TURBO, LITE/NARROW/TINY_*),
|
||||||
|
~25 hardware names, `OLED_SH1107_ROTATED`, serial `LOG`/`LOGTEXT` — they flow into the
|
||||||
|
existing enum-bound `<select>`s automatically once the bindings regenerate.
|
||||||
|
|
||||||
|
## Breaking proto changes that DON'T break us (verified)
|
||||||
|
|
||||||
|
- `deviceonly.proto` `NodeInfoLite` restructure — core never imports `NodeInfoLite`.
|
||||||
|
- `channel.proto` `is_client_muted → is_muted` + `mute` removed — neither web nor core
|
||||||
|
references those fields yet.
|
||||||
|
|
||||||
|
## The one decision for maintainers — how to consume the protobufs
|
||||||
|
|
||||||
|
This changeset takes **Option A (workspace consumption):** rename the workspace package
|
||||||
|
`@meshtastic/protobufs-ws → @meshtastic/protobufs` (still `private`), point its `exports` at the
|
||||||
|
TS source, and depend on it via `workspace:*` from root + core.
|
||||||
|
|
||||||
|
- ✅ Monorepo is self-consuming and always firmware-current; no publish round-trip; matches
|
||||||
|
the already-migrated `@meshtastic/core`.
|
||||||
|
- ⚠️ Diverges from the JSR publish flow; the published JSR `@meshtastic/protobufs`
|
||||||
|
(latest 2.7.23) is no longer what the app builds against.
|
||||||
|
|
||||||
|
**Option B (canonical publish flow):** keep the package JSR-published — sync the `.proto`
|
||||||
|
sources here, publish a new `@meshtastic/protobufs` to JSR, then bump the root range.
|
||||||
|
|
||||||
|
- ✅ Stays on the established pipeline; one source of truth on JSR.
|
||||||
|
- ⚠️ Needs a JSR publish, and can't reach firmware-current until that release lands
|
||||||
|
(JSR tops out at 2.7.23, still behind firmware).
|
||||||
|
|
||||||
|
If you prefer **B**, everything except the 3 dependency-wiring edits still applies — the
|
||||||
|
`.proto` sync, the regen, and the `nodedbReset` fix are identical; only the root dep, the core
|
||||||
|
dep, and the protobufs `package.json` name/exports get swapped for a JSR publish + range bump.
|
||||||
|
|
||||||
|
## Regenerating bindings
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm --filter @meshtastic/protobufs gen
|
||||||
|
```
|
||||||
|
|
||||||
|
Output lands in `packages/protobufs/packages/ts/dist/` (gitignored).
|
||||||
|
|
||||||
|
## Verified in isolation (this branch only — no feature/demo code)
|
||||||
|
|
||||||
|
- `packages/core` typecheck: clean except 2 **pre-existing** `queue.ts` strict-catch errors.
|
||||||
|
- `packages/web` `vite build`: green.
|
||||||
|
- 36 config/module validation tests: pass.
|
||||||
@@ -9,104 +9,180 @@
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This monorepo consolidates the official [Meshtastic](https://meshtastic.org) web
|
This monorepo consolidates the official [Meshtastic](https://meshtastic.org) web
|
||||||
interface and its supporting JavaScript libraries. It aims to provide a unified
|
interface, the domain-driven JavaScript SDK that drives it, and a set of
|
||||||
development experience for interacting with Meshtastic devices.
|
runtime-specific transport packages. Everything you need to read state from or
|
||||||
|
send commands to a Meshtastic device lives here.
|
||||||
|
|
||||||
### Projects within this Monorepo (`packages/`)
|
> [!NOTE]
|
||||||
|
> You can find the main Meshtastic documentation at https://meshtastic.org/docs/introduction/.
|
||||||
|
|
||||||
All projects are located within the `packages/` directory:
|
## Packages
|
||||||
|
|
||||||
- **`packages/web` (Meshtastic Web Client):** The official web interface,
|
All projects live under `packages/`.
|
||||||
designed to be hosted or served directly from a Meshtastic node.
|
|
||||||
- **[Hosted version](https://client.meshtastic.org)**
|
|
||||||
- **`packages/core`:** Core functionality for Meshtastic JS.
|
|
||||||
- **`packages/transport-node`:** TCP Transport for the NodeJS runtime.
|
|
||||||
- **`packages/transport-deno`:** TCP Transport for the Deno runtime.
|
|
||||||
- **`packages/transport-http`:** HTTP Transport.
|
|
||||||
- **`packages/transport-web-bluetooth`:** Web Bluetooth Transport.
|
|
||||||
- **`packages/transport-web-serial`:** Web Serial Transport.
|
|
||||||
|
|
||||||
All `Meshtastic JS` packages (core and transports) are published both to
|
| Package | Purpose |
|
||||||
[JSR](https://jsr.io/@meshtastic). [NPM](https://www.npmjs.com/org/meshtastic)
|
| --- | --- |
|
||||||
|
| `packages/sdk` | Framework-agnostic TypeScript SDK. Domain-driven feature slices (device, chat, nodes, channels, config, telemetry, position, traceroute, files) built around a `MeshClient` orchestrator with `@preact/signals-core` reactive state. |
|
||||||
|
| `packages/sdk-react` | React hooks + `MeshProvider` on top of `@meshtastic/sdk`. Wraps signals in `useSyncExternalStore` for concurrent-safe renders. |
|
||||||
|
| `apps/web` | Reference React web client. Hosted at [client.meshtastic.org](https://client.meshtastic.org). |
|
||||||
|
| `packages/ui` | Shared Radix + Tailwind component library. |
|
||||||
|
| `packages/protobufs` | Generated TypeScript stubs from [`meshtastic/protobufs`](https://github.com/meshtastic/protobufs), produced via `buf generate`. Source of truth for every wire-level type. |
|
||||||
|
| `packages/transport-http` | HTTP transport for devices exposing a network interface. |
|
||||||
|
| `packages/transport-web-bluetooth` | Web Bluetooth transport for BLE-capable devices (browsers). |
|
||||||
|
| `packages/transport-web-serial` | Web Serial transport for USB-serial devices (browsers). |
|
||||||
|
| `packages/transport-node` | TCP transport for Node.js. |
|
||||||
|
| `packages/transport-node-serial` | Serial transport for Node.js. |
|
||||||
|
| `packages/transport-deno` | TCP transport for Deno. |
|
||||||
|
| `packages/transport-mock` | In-memory transport for tests. |
|
||||||
|
|
||||||
---
|
All publishable packages ship to both [JSR](https://jsr.io/@meshtastic) and [NPM](https://www.npmjs.com/org/meshtastic).
|
||||||
|
|
||||||
## Stats
|
## Architecture
|
||||||
|
|
||||||
| Project | Repobeats |
|
`@meshtastic/sdk` organises its source by feature slice. Each slice follows the
|
||||||
| :------------- | :-------------------------------------------------------------------------------------------------------------------- |
|
same DDD layout:
|
||||||
| Meshtastic Web |  |
|
|
||||||
|
|
||||||
---
|
```
|
||||||
|
features/<slice>/
|
||||||
|
domain/ # entities & value objects — pure TypeScript types
|
||||||
|
application/ # use-cases (SendTextUseCase, FavoriteNodeUseCase, …)
|
||||||
|
infrastructure/ # protobuf ↔ domain mappers, admin-message adapters
|
||||||
|
state/ # signals-backed reactive stores
|
||||||
|
<Slice>Client.ts # public facade exposing readable signals + command methods
|
||||||
|
index.ts
|
||||||
|
```
|
||||||
|
|
||||||
## Tech Stack
|
The shared kernel under `packages/sdk/src/core/` owns:
|
||||||
|
|
||||||
This monorepo leverages the following technologies:
|
- **`client/`** — `MeshClient`, the thin orchestrator that owns the transport, queue, event bus, and one instance of every slice client.
|
||||||
|
- **`transport/`** — the `Transport` interface every `transport-*` package implements.
|
||||||
|
- **`event-bus/`** — typed pub/sub channels populated by the packet codec.
|
||||||
|
- **`packet-codec/`** — frame parser (0x94 0xC3), `FromRadio` decoder, portnum router.
|
||||||
|
- **`queue/`**, **`xmodem/`** — packet ack/timeout pipeline and file-transfer protocol.
|
||||||
|
- **`signals/`** — signal and keyed-collection helpers consumed by every slice.
|
||||||
|
- **`logging/`** — `tslog` factory used consistently by every class.
|
||||||
|
- **`identifiers/`**, **`errors/`** — small primitives shared across slices.
|
||||||
|
|
||||||
- **Runtime:** pnpm / Deno
|
The protobuf boundary is strict: wire messages enter through the packet codec,
|
||||||
- **Web Client:** React.js
|
get mapped into domain entities inside `features/*/infrastructure/*Mapper.ts`,
|
||||||
- **Styling:** Tailwind CSS
|
and signals only ever expose the domain shape.
|
||||||
- **Bundling:** Vite
|
|
||||||
- **Language:** TypeScript
|
|
||||||
- **Testing:** Vitest, React Testing Library
|
|
||||||
|
|
||||||
---
|
```
|
||||||
|
Transport ─▶ Packet codec ─▶ EventBus ─▶ Slice infrastructure
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Signals (state) ─▶ sdk-react hooks ─▶ UI
|
||||||
|
▲
|
||||||
|
│
|
||||||
|
Slice application (use-cases) ─▶ MeshClient.sendPacket ─▶ Queue ─▶ Transport
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected domain errors are returned as `Result<T, E>` via [`better-result`](https://www.npmjs.com/package/better-result); exceptions are reserved for programmer errors and truly exceptional conditions.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
You'll need to have [pnpm](https://pnpm.io/) installed to work with this monorepo.
|
You need [pnpm](https://pnpm.io/) installed. If you plan to regenerate
|
||||||
Follow the installation instructions on their home page.
|
protobufs, also install the [Buf CLI](https://buf.build/docs/cli/installation/).
|
||||||
|
|
||||||
### Development Setup
|
### Setup
|
||||||
|
|
||||||
1. **Clone the repository:**
|
```bash
|
||||||
```bash
|
git clone https://github.com/meshtastic/web.git
|
||||||
git clone https://github.com/meshtastic/meshtastic-web.git
|
cd web
|
||||||
cd meshtastic-web
|
pnpm install
|
||||||
```
|
```
|
||||||
2. **Install dependencies for all packages:**
|
|
||||||
```bash
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
This command installs all necessary dependencies for all packages within the
|
|
||||||
monorepo.
|
|
||||||
|
|
||||||
### Running Projects
|
### Run the web client
|
||||||
|
|
||||||
#### Meshtastic Web Client
|
```bash
|
||||||
|
pnpm --filter @meshtastic/web dev
|
||||||
|
```
|
||||||
|
|
||||||
Please refer to the [Meshtastic Web README](packages/web/README.md) for setup and usage.
|
### Build everything
|
||||||
|
|
||||||
### Feedback
|
```bash
|
||||||
|
pnpm -r build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm -r test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint + format
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm check
|
||||||
|
pnpm check:fix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
### Adding a new feature slice to `@meshtastic/sdk`
|
||||||
|
|
||||||
|
1. Create `packages/sdk/src/features/<slice>/` with `domain/`, `application/`, `infrastructure/`, `state/` subdirectories plus an `index.ts` barrel.
|
||||||
|
2. Implement a signals-backed store in `state/`.
|
||||||
|
3. Subscribe to the relevant `EventBus` channel(s) inside a `<Slice>Client.ts` class and write mapped domain entities to the store.
|
||||||
|
4. Wire the new client into `MeshClient` and re-export types from `packages/sdk/mod.ts`.
|
||||||
|
5. Add vitest coverage: domain invariants, use-cases against `createFakeTransport()`, and round-trip mapper fixtures.
|
||||||
|
6. If the slice has React callers, add a matching hook under `packages/sdk-react/src/hooks/`.
|
||||||
|
|
||||||
|
### Adding a new transport
|
||||||
|
|
||||||
|
Implement the `Transport` interface exported from `@meshtastic/sdk/transport`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Transport {
|
||||||
|
toDevice: WritableStream<Uint8Array>;
|
||||||
|
fromDevice: ReadableStream<DeviceOutput>;
|
||||||
|
disconnect(): Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The SDK does the framing and decoding — transports only supply raw bytes.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Vitest is wired at the repo root and picks up `packages/*` projects. The SDK
|
||||||
|
ships `@meshtastic/sdk/testing` with `createFakeTransport()` for wiring tests
|
||||||
|
without real hardware.
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
Each publishable package has `build:npm` / `publish:npm` / `prepare:jsr` /
|
||||||
|
`publish:jsr` scripts. See each package's `package.json` for details.
|
||||||
|
|
||||||
|
## Repository activity
|
||||||
|
|
||||||
|
| Project | Repobeats |
|
||||||
|
| :------------- | :-------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Meshtastic Web |  |
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
|
||||||
If you encounter any issues, please report them in our
|
If you encounter any issues, please report them in our
|
||||||
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
|
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
|
||||||
improve the stability of future releases
|
improve the stability of future releases.
|
||||||
|
|
||||||
### Contributing
|
## Star history
|
||||||
|
|
||||||
We welcome contributions! Here’s how the deployment flow works for pull
|
<a href="https://star-history.com/#meshtastic/web&Date">
|
||||||
requests:
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=meshtastic/web&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=meshtastic/web&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=meshtastic/web&type=Date" width="100%" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|
||||||
- **Preview Deployments:**\
|
## Contributors
|
||||||
Every pull request automatically generates a preview deployment on Vercel.
|
|
||||||
This allows you and reviewers to easily preview changes before merging.
|
|
||||||
|
|
||||||
- **Staging Environment (`client-test`):**\
|
<a href="https://github.com/meshtastic/web/graphs/contributors">
|
||||||
Once your PR is merged, your changes will be available on our staging site:
|
<img src="https://contrib.rocks/image?repo=meshtastic/web" width="100%"/>
|
||||||
[client-test.meshtastic.org](https://client-test.meshtastic.org/).\
|
</a>
|
||||||
This environment supports rapid feature iteration and testing without
|
|
||||||
impacting the production site.
|
|
||||||
|
|
||||||
- **Production Releases:**\
|
## License
|
||||||
At regular intervals, stable and fully tested releases are promoted to our
|
|
||||||
production site: [client.meshtastic.org](https://client.meshtastic.org/).\
|
|
||||||
This is the primary interface used by the public to connect with their
|
|
||||||
Meshtastic nodes.
|
|
||||||
|
|
||||||
Please review our
|
GPL-3.0-only. See [LICENSE](LICENSE).
|
||||||
[Contribution Guidelines](https://github.com/meshtastic/web/blob/main/CONTRIBUTING.md)
|
|
||||||
before submitting a pull request. We appreciate your help in making the project
|
|
||||||
better!
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Testing strategy
|
||||||
|
|
||||||
|
How this monorepo proves correctness, and where coverage currently sits.
|
||||||
|
|
||||||
|
## Levels
|
||||||
|
|
||||||
|
Tests live in five tiers. Each PR should add coverage at the **lowest level that catches the regression**, and only climb tiers when that's not possible.
|
||||||
|
|
||||||
|
| Tier | Scope | Tooling | Where |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1. **Unit** | Pure functions, value objects, mappers, single classes with mocked deps | `vitest run` (Node env) | `*.test.ts` colocated with source |
|
||||||
|
| 2. **Slice integration** | Use-case + store + mapper exercised against in-memory deps; assert outbound bytes and signal state | `vitest` + `createFakeTransport()` | `*.test.ts` in slice dirs |
|
||||||
|
| 3. **Client integration** | `MeshClient` end-to-end with a fake transport feeding canned `FromRadio` packets | `vitest` + `@meshtastic/sdk/testing` | `packages/sdk/tests/integration/` |
|
||||||
|
| 4. **Storage integration** | Real Drizzle queries against sql.js in-memory; or against `@vitest/browser` for OPFS | `vitest` (Node + browser) | `packages/sdk-storage-sqlocal/{src,tests}` |
|
||||||
|
| 5. **Hook / DOM** | React hooks render under `<MeshProvider>` / `<MeshRegistryProvider>`, react to signals | `vitest` + `@testing-library/react` + `jsdom` | `packages/sdk-react/tests/` |
|
||||||
|
| 6. **E2E / simulator** | Whole stack: SDK → transport → simulator/firmware. Catches protocol drift | `@vitest/browser` for OPFS; `meshtasticd` simulator over TCP for protocol | future `tests/e2e/` |
|
||||||
|
|
||||||
|
## Per-package coverage gates
|
||||||
|
|
||||||
|
| Package | Required floor | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `packages/sdk/core` | every primitive (signals, EventBus, Queue, packet-codec, identifiers) has a unit test; lifecycle covered by `MeshClient.test.ts` | adopt `c8` thresholds once stable |
|
||||||
|
| `packages/sdk/features/*` | each slice ships: 1 domain invariant test, 1 use-case test against fake transport, 1 mapper round-trip (where mappers exist) | Integration covered by `tests/integration/fake-transport.test.ts` |
|
||||||
|
| `packages/sdk-react` | each public hook has a `renderHook` test that asserts initial render + re-render on signal change | uses jsdom; provider wrapper required |
|
||||||
|
| `packages/sdk-storage-sqlocal` | every repository method tested against sql.js in-memory; **at least one OPFS-real test per repo** runs in browser mode | sql.js validates SQL correctness; OPFS validates VFS / Worker plumbing |
|
||||||
|
| `packages/transport-*` | minimum: framing round-trip, disconnect cleans up streams, error path emits status | low-level; ship as is |
|
||||||
|
| `packages/web` | component tests at `34` baseline; new SDK-driven UI components must add a hook-mock test | currently all green |
|
||||||
|
|
||||||
|
## Current state (audit)
|
||||||
|
|
||||||
|
| Package | Test files | Tests pass | Gaps |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `packages/sdk` | 7 | 25 ✅ | nodes/channels/config/telemetry/position/traceroute/files slices have **no tests**; no `MeshClient` lifecycle test (only fake-transport integration); no schema migration test |
|
||||||
|
| `packages/sdk-react` | 1 | 2 ✅ | only `useMeshDevice` + a stubbed `useChat` test; missing `useNodes`, `useChannels`, `useConfig`, `useConnection`, `useTraceroute`, `useTelemetry`, `usePosition`, `useFileTransfer`, `useFavoriteNode`, `useIgnoreNode`, `useMeshRegistry`, `useClientById`, `useActiveClient`. No registry-aware re-render coverage. |
|
||||||
|
| `packages/sdk-storage-sqlocal` | 2 | 8 ✅ | sql.js only — **no real OPFS test**, no Worker boot test, no cross-tab BroadcastChannel test (mocked away), no migration v1→v2 test |
|
||||||
|
| `packages/web` | 34 | 294 ✅ | no `useConnections` test; new `meshRegistry` + `sdkStorage` modules untested; chat persistence end-to-end not exercised |
|
||||||
|
| `packages/transport-*` | 1 each (5 of 7) | varies | `transport-deno` + `transport-mock` have no tests; transports likely lack disconnect/error coverage |
|
||||||
|
| `packages/core` | 0 | n/a | legacy, slated for deletion in Phase C; tolerable |
|
||||||
|
| `packages/ui` | 0 | n/a | pure presentational; visual regression only |
|
||||||
|
| `packages/protobufs` | 0 | n/a | generated code; upstream's responsibility |
|
||||||
|
|
||||||
|
## Concrete additions queued (priority order)
|
||||||
|
|
||||||
|
1. **`packages/sdk` slice tests** — one Use-case + one Mapper test per slice (`nodes`, `channels`, `config`, `telemetry`, `position`, `traceroute`). Pattern: build a stub `MeshClient`, dispatch a synthetic event, assert signal value or outbound bytes.
|
||||||
|
2. **`packages/sdk-react` hook tests** — for every hook listed above, mount under `<MeshProvider>`, drive a signal change, assert `result.current`. One file, ~15 cases.
|
||||||
|
3. **`packages/sdk` `ChatClient` persistence test** — wire `InMemoryMessageRepository`, append messages, re-construct `ChatClient`, assert hydration. Validates the lazy-load contract.
|
||||||
|
4. **`packages/sdk-storage-sqlocal` migration test** — bootstrap empty DB, run `MIGRATIONS[]`, assert `_schema.version`. Then add a fake `version: 2` migration and prove it's applied idempotently.
|
||||||
|
5. **`packages/sdk-storage-sqlocal` browser mode** — add a second `vitest.browser.config.ts` using `@vitest/browser` (Playwright provider) so we exercise real OPFS + Worker. CI runs both modes.
|
||||||
|
6. **`packages/sdk-storage-sqlocal` BroadcastChannel test** — instantiate two `MultiTabCoordinator` instances in same process; one broadcasts, the other observes. (Node has no `BroadcastChannel` global; use `worker_threads`'s `BroadcastChannel` polyfill or jsdom env.)
|
||||||
|
7. **`packages/web` `meshRegistry` + `sdkStorage` lazy-init test** — assert `getStorageDb()` returns the same promise on repeated calls and only opens the DB once.
|
||||||
|
8. **`packages/sdk-react` registry test** — mount `<MeshRegistryProvider>` with two clients, switch active, confirm a hook re-renders against the new client.
|
||||||
|
|
||||||
|
## E2E / simulator (Tier 6) — scope only
|
||||||
|
|
||||||
|
Out of immediate scope; documenting for a follow-up PR.
|
||||||
|
|
||||||
|
- Run `meshtasticd` (firmware simulator) in CI Docker via `services:` block.
|
||||||
|
- Spin up `MeshClient` with `TransportHTTP` pointed at the simulator's HTTP endpoint.
|
||||||
|
- Drive scripted scenarios: configure → send text → expect ack; channel update; node info exchange; traceroute.
|
||||||
|
- Use `@vitest/browser` so we also exercise the real OPFS persistence path during E2E.
|
||||||
|
- Run on `main` only (cost). Smoke subset on PR.
|
||||||
|
|
||||||
|
Until that lands, `createFakeTransport()` covers the protocol layer at unit/integration speed.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Test file colocated with source: `Foo.ts` → `Foo.test.ts`.
|
||||||
|
- Integration tests under `tests/integration/`.
|
||||||
|
- Browser-mode tests use the suffix `.browser.test.ts` so they can be filtered.
|
||||||
|
- Protobuf fixtures live in `__fixtures__/*.fixtures.ts` next to mappers; binary data committed as base64, not raw bytes.
|
||||||
|
- Each test imports concrete classes from the source path (`./Foo.ts`), not the package barrel — fast type-check, zero re-export drift.
|
||||||
|
- No mocked SDK from inside SDK tests. Use `createFakeTransport()` and real `MeshClient` instances.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm -r test # all packages, Node env
|
||||||
|
pnpm --filter @meshtastic/sdk test
|
||||||
|
pnpm --filter @meshtastic/sdk-storage-sqlocal test
|
||||||
|
pnpm --filter meshtastic-web test
|
||||||
|
# future:
|
||||||
|
pnpm --filter @meshtastic/sdk-storage-sqlocal test:browser
|
||||||
|
```
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json",
|
||||||
|
"useTabs": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 80,
|
||||||
|
"ignorePatterns": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"protobufs",
|
||||||
|
"**/locales/*-*/*.json",
|
||||||
|
"**/packages/ui/",
|
||||||
|
"*.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["react", "react-perf", "jsx-a11y", "typescript", "import"],
|
||||||
|
"rules": {
|
||||||
|
"no-debugger": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"@typescript-eslint/no-unused-vars": "error"
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"protobufs",
|
||||||
|
"**/locales/*-*/*.json",
|
||||||
|
"**/packages/ui/"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.test.ts", "**/*.test.tsx", "**/__tests__/**/*.{ts,tsx}"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# Contributing to Meshtastic Web
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to **Meshtastic Web**! 🎉
|
||||||
|
We welcome all contributions—whether it’s fixing a typo, improving documentation, adding new features, or reporting bugs. This document outlines how to get started and the conventions we follow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Code of Conduct
|
||||||
|
We follow the [Meshtastic Code of Conduct](https://meshtastic.org/docs/legal/conduct/).
|
||||||
|
Please make sure you are familiar with it before contributing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
Before making changes, please take some time to explore the repository and its monorepo structure.
|
||||||
|
Understanding how the packages are organized will make it much easier to contribute effectively.
|
||||||
|
|
||||||
|
[Meshtastic Web](https://github.com/meshtastic/web/)
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- [Node.js](https://nodejs.org/) (v22 or later)
|
||||||
|
- [pnpm](https://pnpm.io/) (v10.14.x or later)
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
Clone the repo and install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/meshtastic/web.git meshtastic-web
|
||||||
|
cd meshtastic-web
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
Start the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @meshtastic/web dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Once running, the site will be available at:
|
||||||
|
👉 **http://localhost:3000**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗂 Repository Structure
|
||||||
|
Meshtastic Web uses a **monorepo** setup managed with **pnpm workspaces**:
|
||||||
|
|
||||||
|
```
|
||||||
|
/packages
|
||||||
|
├─ web # React frontend
|
||||||
|
├─ core # Shared types & logic
|
||||||
|
├─ transport-* # Transport layer packages
|
||||||
|
└─ ...other packages
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Contribution Workflow
|
||||||
|
|
||||||
|
1. **Fork the repo** and create your branch from `main`.
|
||||||
|
|
||||||
|
### Branch Naming
|
||||||
|
- Use [Conventional Commit](https://www.conventionalcommits.org/) style for your branch names:
|
||||||
|
```
|
||||||
|
feat/add-project-filter
|
||||||
|
fix/storage-service
|
||||||
|
chore/update-ci-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Make your changes locally** and verify that the app runs as expected at `http://localhost:3000`.
|
||||||
|
|
||||||
|
3. **Commit your changes** with a descriptive commit message that follows the [Conventional Commits](https://www.conventionalcommits.org/) style.
|
||||||
|
|
||||||
|
4. **Open a Pull Request (PR)** from your fork's branch to the main repository's `main` branch on GitHub:
|
||||||
|
- Clearly describe the problem and solution.
|
||||||
|
- Reference related issues (e.g., `Fixes #123`).
|
||||||
|
- Keep PRs focused on a single feature or fix.
|
||||||
|
- Complete all fields in the PR template.
|
||||||
|
- Tag a **Meshtastic Web developer** in the PR for review.
|
||||||
|
|
||||||
|
5. **CI/CD**:
|
||||||
|
- Our GitHub Actions workflows handle builds, linting, and packaging automatically.
|
||||||
|
- All checks must pass before merge.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌍 Internationalization (i18n)
|
||||||
|
|
||||||
|
Meshtastic Web supports multiple languages. If your changes introduce **new user-facing strings**:
|
||||||
|
|
||||||
|
- Add them to the **`en.json`** file.
|
||||||
|
- Do **not** hardcode English strings directly in components.
|
||||||
|
- This ensures they can be translated into other languages.
|
||||||
|
|
||||||
|
🔗 See these guides for more details:
|
||||||
|
- [i18n Developer Guide](https://github.com/meshtastic/web/blob/main/packages/web/CONTRIBUTING_I18N_DEVELOPER_GUIDE.md)
|
||||||
|
- [Translation Contribution Guide](https://github.com/meshtastic/web/blob/main/packages/web/CONTRIBUTING_TRANSLATIONS.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
Tests are written with [Vitest](https://vitest.dev/).
|
||||||
|
|
||||||
|
Run all tests locally with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @meshtastic/web test
|
||||||
|
```
|
||||||
|
|
||||||
|
Please include tests for new features and bug fixes whenever possible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Commit Messages
|
||||||
|
We use **Conventional Commits**:
|
||||||
|
|
||||||
|
- `feat:` – a new feature
|
||||||
|
- `fix:` – a bug fix
|
||||||
|
- `docs:` – documentation changes
|
||||||
|
- `chore:` – maintenance, dependencies, build scripts
|
||||||
|
- `refactor:` – code restructuring without feature changes
|
||||||
|
- `test:` – adding or updating tests
|
||||||
|
- `ci:` – CI/CD changes
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
feat: add toast notification system
|
||||||
|
fix: correct caching issue in storage service
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Tips for Contributors
|
||||||
|
- Keep PRs **small, focused, and atomic**.
|
||||||
|
- Discuss larger changes with the team on [Discord](https://discord.gg/meshtastic) before starting work.
|
||||||
|
- If unsure, open a draft PR for early feedback.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🙌 Community
|
||||||
|
Contributors are the heart of Meshtastic ❤️.
|
||||||
|
Join the conversation:
|
||||||
|
- [Discord](https://discord.gg/meshtastic)
|
||||||
|
- [GitHub Discussions](https://github.com/meshtastic/web/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 License
|
||||||
|
By contributing, you agree that your contributions will be licensed under the [GPL-3.0-only License](../../LICENSE).
|
||||||
@@ -37,14 +37,14 @@ podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshta
|
|||||||
|
|
||||||
Our release process follows these guidelines:
|
Our release process follows these guidelines:
|
||||||
|
|
||||||
- **Versioning:** We use Semantic Versioning (`Major.Minor.Patch`).
|
- **Versioning:** We use Semantic Versioning (`Major.Minor.Patch`).
|
||||||
- **Stable Releases:** Published around the beginning of each month (e.g.,
|
- **Stable Releases:** Published around the beginning of each month (e.g.,
|
||||||
`v2.6.1`).
|
`v2.6.1`).
|
||||||
- **Pre-releases:** A pre-release is typically issued mid-month for testing and
|
- **Pre-releases:** A pre-release is typically issued mid-month for testing and
|
||||||
early adoption.
|
early adoption.
|
||||||
- **Nightly Builds:** An experimental Docker image containing the latest
|
- **Nightly Builds:** An experimental Docker image containing the latest
|
||||||
cutting-edge features and fixes is automatically built nightly from the `main`
|
cutting-edge features and fixes is automatically built nightly from the `main`
|
||||||
branch.
|
branch.
|
||||||
|
|
||||||
### Nightly Builds
|
### Nightly Builds
|
||||||
|
|
||||||
@@ -57,20 +57,20 @@ podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshta
|
|||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
>
|
>
|
||||||
> - Nightly builds represent the latest development state and may contain
|
> - Nightly builds represent the latest development state and may contain
|
||||||
> breaking changes
|
> breaking changes
|
||||||
> - These builds undergo automated testing but may be less stable than tagged
|
> - These builds undergo automated testing but may be less stable than tagged
|
||||||
> release versions
|
> release versions
|
||||||
> - Not recommended for production environments unless you are actively testing
|
> - Not recommended for production environments unless you are actively testing
|
||||||
> new features
|
> new features
|
||||||
> - No guarantee of backward compatibility between nightly builds
|
> - No guarantee of backward compatibility between nightly builds
|
||||||
|
|
||||||
#### Version Information
|
#### Version Information
|
||||||
|
|
||||||
Each nightly build is tagged with:
|
Each nightly build is tagged with:
|
||||||
|
|
||||||
- The nightly tag for the latest build
|
- The nightly tag for the latest build
|
||||||
- A specific SHA for build reproducibility
|
- A specific SHA for build reproducibility
|
||||||
|
|
||||||
### Feedback
|
### Feedback
|
||||||
|
|
||||||
@@ -118,39 +118,39 @@ pnpm run package
|
|||||||
Meshtastic Web uses pnpm as its package manager for several compelling
|
Meshtastic Web uses pnpm as its package manager for several compelling
|
||||||
reasons:
|
reasons:
|
||||||
|
|
||||||
- **Efficient Storage**: pnpm uses content-addressable storage, avoiding duplication
|
- **Efficient Storage**: pnpm uses content-addressable storage, avoiding duplication
|
||||||
of packages across projects and saving significant disk space.
|
of packages across projects and saving significant disk space.
|
||||||
- **Fast Performance**: Faster package installation compared to other package
|
- **Fast Performance**: Faster package installation compared to other package
|
||||||
managers through symlinks and efficient dependency resolution.
|
managers through symlinks and efficient dependency resolution.
|
||||||
- **Strict Dependency Management**: Prevents access to unlisted dependencies,
|
- **Strict Dependency Management**: Prevents access to unlisted dependencies,
|
||||||
ensuring better project reliability and security.
|
ensuring better project reliability and security.
|
||||||
- **Workspace Support**: Excellent monorepo support with workspaces for managing
|
- **Workspace Support**: Excellent monorepo support with workspaces for managing
|
||||||
multiple packages efficiently.
|
multiple packages efficiently.
|
||||||
- **Reproducible Builds**: Lockfile ensures consistent builds across all
|
- **Reproducible Builds**: Lockfile ensures consistent builds across all
|
||||||
environments.
|
environments.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
We welcome contributions! Here’s how the deployment flow works for pull
|
We welcome contributions! Here’s how the deployment flow works for pull
|
||||||
requests:
|
requests:
|
||||||
|
|
||||||
- **Preview Deployments:**\
|
- **Preview Deployments:**\
|
||||||
Every pull request automatically generates a preview deployment on Vercel.
|
Every pull request automatically generates a preview deployment on Vercel.
|
||||||
This allows you and reviewers to easily preview changes before merging.
|
This allows you and reviewers to easily preview changes before merging.
|
||||||
|
|
||||||
- **Staging Environment (`client-test`):**\
|
- **Staging Environment (`client-test`):**\
|
||||||
Once your PR is merged, your changes will be available on our staging site:
|
Once your PR is merged, your changes will be available on our staging site:
|
||||||
[client-test.meshtastic.org](https://client-test.meshtastic.org/).\
|
[client-test.meshtastic.org](https://client-test.meshtastic.org/).\
|
||||||
This environment supports rapid feature iteration and testing without
|
This environment supports rapid feature iteration and testing without
|
||||||
impacting the production site.
|
impacting the production site.
|
||||||
|
|
||||||
- **Production Releases:**\
|
- **Production Releases:**\
|
||||||
At regular intervals, stable and fully tested releases are promoted to our
|
At regular intervals, stable and fully tested releases are promoted to our
|
||||||
production site: [client.meshtastic.org](https://client.meshtastic.org/).\
|
production site: [client.meshtastic.org](https://client.meshtastic.org/).\
|
||||||
This is the primary interface used by the public to connect with their
|
This is the primary interface used by the public to connect with their
|
||||||
Meshtastic nodes.
|
Meshtastic nodes.
|
||||||
|
|
||||||
Please review our
|
Please review our
|
||||||
[Contribution Guidelines](https://github.com/meshtastic/web/blob/main/CONTRIBUTING.md)
|
[Contribution Guidelines](https://github.com/meshtastic/web/blob/main/packages/web/CONTRIBUTING.md)
|
||||||
before submitting a pull request. We appreciate your help in making the project
|
before submitting a pull request. We appreciate your help in making the project
|
||||||
better!
|
better!
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en" data-theme="system">
|
<html lang="en" data-theme="system">
|
||||||
<head>
|
<head>
|
||||||
<!-- CookieYes banner -->
|
<!-- CookieYes banner -->
|
||||||
@@ -11,21 +11,7 @@
|
|||||||
|
|
||||||
<link rel="manifest" href="site.webmanifest" />
|
<link rel="manifest" href="site.webmanifest" />
|
||||||
<link rel="mask-icon" href="logo_black.svg" color="#67ea94" />
|
<link rel="mask-icon" href="logo_black.svg" color="#67ea94" />
|
||||||
<link
|
<link rel="stylesheet" href="src/index.css" crossorigin="anonymous" />
|
||||||
rel="stylesheet"
|
|
||||||
href="https://rsms.me/inter/inter.css"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@xz/fonts@1/serve/cascadia-code.min.css"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="src/index.css"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
<meta name="theme-color" content="#67ea94" />
|
<meta name="theme-color" content="#67ea94" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="description" content="Meshtastic Web Client" />
|
<meta name="description" content="Meshtastic Web Client" />
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
FROM nginx:1.29.1-alpine-slim
|
||||||
|
|
||||||
|
RUN rm -r /usr/share/nginx/html \
|
||||||
|
&& mkdir -p /usr/share/nginx/html \
|
||||||
|
&& mkdir -p /etc/nginx/conf.d
|
||||||
|
|
||||||
|
WORKDIR /usr/share/nginx/html
|
||||||
|
|
||||||
|
ADD ./apps/web/dist .
|
||||||
|
|
||||||
|
COPY ./apps/web/infra/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location ~* \.(?:js|jsx|mjs|ts|tsx|css|png|jpg|jpeg|gif|ico|webp|avif|svg|ttf|otf|woff|woff2|map)$ {
|
||||||
|
access_log off;
|
||||||
|
etag on;
|
||||||
|
|
||||||
|
expires 3M;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
etag on;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html { internal; }
|
||||||
|
|
||||||
|
location ~ /\.ht { deny all; }
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_types
|
||||||
|
text/plain
|
||||||
|
text/css
|
||||||
|
text/xml
|
||||||
|
text/javascript
|
||||||
|
application/javascript
|
||||||
|
application/x-javascript
|
||||||
|
application/json
|
||||||
|
application/xml
|
||||||
|
application/xml+rss
|
||||||
|
font/ttf
|
||||||
|
font/otf
|
||||||
|
image/svg+xml;
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
{
|
||||||
|
"name": "meshtastic-web",
|
||||||
|
"version": "2.6.6-0",
|
||||||
|
"description": "Meshtastic web client",
|
||||||
|
"homepage": "https://meshtastic.org",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/meshtastic/web/issues"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0-only",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/meshtastic/web.git"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
"setup:certs": "mkcert localhost 127.0.0.1 ::1",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:analyze": "BUNDLE_ANALYZE=true pnpm run build",
|
||||||
|
"lint": "oxlint",
|
||||||
|
"lint:fix": "oxlint --fix",
|
||||||
|
"format": "oxfmt --check .",
|
||||||
|
"format:fix": "oxfmt .",
|
||||||
|
"check": "pnpm lint && pnpm format",
|
||||||
|
"check:fix": "pnpm lint:fix && pnpm format:fix",
|
||||||
|
"dev": "vite",
|
||||||
|
"dev:https": "USE_HTTPS=true vite",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"docker:build": "docker build -t meshtastic-web:latest -f ./infra/Containerfile .",
|
||||||
|
"package": "gzipper c -i html,js,css,png,ico,svg,json,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.5.7",
|
||||||
|
"@meshtastic/sdk": "workspace:*",
|
||||||
|
"@meshtastic/sdk-react": "workspace:*",
|
||||||
|
"@meshtastic/sdk-storage-sqlocal": "workspace:*",
|
||||||
|
"@meshtastic/transport-http": "workspace:*",
|
||||||
|
"@meshtastic/transport-web-bluetooth": "workspace:*",
|
||||||
|
"@meshtastic/transport-web-serial": "workspace:*",
|
||||||
|
"@noble/curves": "^2.2.0",
|
||||||
|
"@preact/signals-core": "^1.14.4",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.20",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.1.23",
|
||||||
|
"@radix-ui/react-checkbox": "^1.3.11",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.23",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
||||||
|
"@radix-ui/react-label": "^2.1.15",
|
||||||
|
"@radix-ui/react-menubar": "^1.1.24",
|
||||||
|
"@radix-ui/react-popover": "^1.1.23",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.18",
|
||||||
|
"@radix-ui/react-select": "^2.3.7",
|
||||||
|
"@radix-ui/react-separator": "^1.1.15",
|
||||||
|
"@radix-ui/react-slider": "^1.4.7",
|
||||||
|
"@radix-ui/react-switch": "^1.3.7",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.21",
|
||||||
|
"@radix-ui/react-toast": "^1.2.23",
|
||||||
|
"@radix-ui/react-toggle-group": "^1.1.19",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.16",
|
||||||
|
"@radix-ui/react-visually-hidden": "^1.2.11",
|
||||||
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
|
"@tanstack/react-router": "^1.170.18",
|
||||||
|
"@tanstack/react-router-devtools": "^1.167.0",
|
||||||
|
"@tanstack/router-cli": "^1.167.21",
|
||||||
|
"@tanstack/router-devtools": "^1.167.0",
|
||||||
|
"@turf/turf": "^7.3.5",
|
||||||
|
"@types/node": "^26.1.2",
|
||||||
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
|
"base64-js": "^1.5.1",
|
||||||
|
"better-result": "^2.10.0",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
|
"crypto-random-string": "^6.0.0",
|
||||||
|
"i18next": "^26.3.6",
|
||||||
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
|
"i18next-http-backend": "^4.0.1",
|
||||||
|
"idb-keyval": "^6.3.0",
|
||||||
|
"immer": "^11.1.15",
|
||||||
|
"js-cookie": "^3.0.8",
|
||||||
|
"lucide-react": "^1.28.0",
|
||||||
|
"maplibre-gl": "6.3.0",
|
||||||
|
"react": "^19.2.8",
|
||||||
|
"react-dom": "^19.2.8",
|
||||||
|
"react-error-boundary": "^6.1.2",
|
||||||
|
"react-hook-form": "^7.82.0",
|
||||||
|
"react-i18next": "^17.0.11",
|
||||||
|
"react-map-gl": "8.1.2",
|
||||||
|
"react-qrcode-logo": "^4.1.0",
|
||||||
|
"rfc4648": "^1.5.4",
|
||||||
|
"vite": "^8.1.5",
|
||||||
|
"vite-plugin-html": "^3.2.2",
|
||||||
|
"vite-plugin-pwa": "^1.3.0",
|
||||||
|
"zod": "^4.1.12",
|
||||||
|
"zustand": "5.0.14"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tanstack/router-plugin": "^1.168.22",
|
||||||
|
"@testing-library/jest-dom": "^7.0.0",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@testing-library/user-event": "^14.6.4",
|
||||||
|
"@types/chrome": "^0.2.2",
|
||||||
|
"@types/geojson": "^7946.0.16",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/react": "^19.2.17",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@types/serviceworker": "^0.0.200",
|
||||||
|
"@types/w3c-web-serial": "^1.0.8",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||||
|
"@vitejs/plugin-react": "^6.0.4",
|
||||||
|
"autoprefixer": "^10.5.4",
|
||||||
|
"gzipper": "^8.3.0",
|
||||||
|
"happy-dom": "^20.11.1",
|
||||||
|
"oxfmt": "^0.63.0",
|
||||||
|
"oxlint": "^1.75.0",
|
||||||
|
"tailwind-merge": "^3.6.0",
|
||||||
|
"tailwindcss": "^4.3.3",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"tar": "^7.5.22",
|
||||||
|
"testing-library": "^0.0.2",
|
||||||
|
"typescript": "^7.0.2",
|
||||||
|
"vitest": "^4.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"sectionLabel": "Channels",
|
||||||
|
"channelName": "Channel: {{channelName}}",
|
||||||
|
"broadcastLabel": "Primary",
|
||||||
|
"channelIndex": "Ch {{index}}",
|
||||||
|
"import": "Import",
|
||||||
|
"export": "Export"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"pskInvalid": "Please enter a valid {{bits}} bit PSK."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"label": "Channel Settings",
|
||||||
|
"description": "Crypto, MQTT & misc settings"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"label": "Role",
|
||||||
|
"description": "Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
|
||||||
|
"options": {
|
||||||
|
"primary": "PRIMARY",
|
||||||
|
"disabled": "DISABLED",
|
||||||
|
"secondary": "SECONDARY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"psk": {
|
||||||
|
"label": "Pre-Shared Key",
|
||||||
|
"description": "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)",
|
||||||
|
"generate": "Generate"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Name",
|
||||||
|
"description": "A unique name for the channel <12 bytes, leave blank for default"
|
||||||
|
},
|
||||||
|
"uplinkEnabled": {
|
||||||
|
"label": "Uplink Enabled",
|
||||||
|
"description": "Send messages from the local mesh to MQTT"
|
||||||
|
},
|
||||||
|
"downlinkEnabled": {
|
||||||
|
"label": "Downlink Enabled",
|
||||||
|
"description": "Send messages from MQTT to the local mesh"
|
||||||
|
},
|
||||||
|
"positionPrecision": {
|
||||||
|
"label": "Location",
|
||||||
|
"description": "The precision of the location to share with the channel. Can be disabled.",
|
||||||
|
"options": {
|
||||||
|
"none": "Do not share location",
|
||||||
|
"precise": "Precise Location",
|
||||||
|
"metric_km23": "Within 23 kilometers",
|
||||||
|
"metric_km12": "Within 12 kilometers",
|
||||||
|
"metric_km5_8": "Within 5.8 kilometers",
|
||||||
|
"metric_km2_9": "Within 2.9 kilometers",
|
||||||
|
"metric_km1_5": "Within 1.5 kilometers",
|
||||||
|
"metric_m700": "Within 700 meters",
|
||||||
|
"metric_m350": "Within 350 meters",
|
||||||
|
"metric_m200": "Within 200 meters",
|
||||||
|
"metric_m90": "Within 90 meters",
|
||||||
|
"metric_m50": "Within 50 meters",
|
||||||
|
"imperial_mi15": "Within 15 miles",
|
||||||
|
"imperial_mi7_3": "Within 7.3 miles",
|
||||||
|
"imperial_mi3_6": "Within 3.6 miles",
|
||||||
|
"imperial_mi1_8": "Within 1.8 miles",
|
||||||
|
"imperial_mi0_9": "Within 0.9 miles",
|
||||||
|
"imperial_mi0_5": "Within 0.5 miles",
|
||||||
|
"imperial_mi0_2": "Within 0.2 miles",
|
||||||
|
"imperial_ft600": "Within 600 feet",
|
||||||
|
"imperial_ft300": "Within 300 feet",
|
||||||
|
"imperial_ft150": "Within 150 feet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
"messages": "Messages",
|
"messages": "Messages",
|
||||||
"map": "Map",
|
"map": "Map",
|
||||||
"config": "Config",
|
"config": "Config",
|
||||||
"channels": "Channels",
|
|
||||||
"nodes": "Nodes"
|
"nodes": "Nodes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -33,9 +32,9 @@
|
|||||||
"qrGenerator": "Generator",
|
"qrGenerator": "Generator",
|
||||||
"qrImport": "Import",
|
"qrImport": "Import",
|
||||||
"scheduleShutdown": "Schedule Shutdown",
|
"scheduleShutdown": "Schedule Shutdown",
|
||||||
"scheduleReboot": "Schedule Reboot",
|
"scheduleReboot": "Reboot Device",
|
||||||
"rebootToOtaMode": "Reboot To OTA Mode",
|
|
||||||
"resetNodeDb": "Reset Node DB",
|
"resetNodeDb": "Reset Node DB",
|
||||||
|
"dfuMode": "Enter DFU Mode",
|
||||||
"factoryResetDevice": "Factory Reset Device",
|
"factoryResetDevice": "Factory Reset Device",
|
||||||
"factoryResetConfig": "Factory Reset Config",
|
"factoryResetConfig": "Factory Reset Config",
|
||||||
"disconnect": "Disconnect"
|
"disconnect": "Disconnect"
|
||||||
@@ -45,7 +44,8 @@
|
|||||||
"label": "Debug",
|
"label": "Debug",
|
||||||
"command": {
|
"command": {
|
||||||
"reconfigure": "Reconfigure",
|
"reconfigure": "Reconfigure",
|
||||||
"clearAllStoredMessages": "Clear All Stored Message"
|
"clearAllStoredMessages": "Clear All Stored Message",
|
||||||
|
"clearAllStores": "Clear All Local Storage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
{
|
||||||
|
"button": {
|
||||||
|
"apply": "Apply",
|
||||||
|
"addConnection": "Add Connection",
|
||||||
|
"saveConnection": "Save connection",
|
||||||
|
"backupKey": "Backup Key",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"connect": "Connect",
|
||||||
|
"clearMessages": "Clear Messages",
|
||||||
|
"close": "Close",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"delete": "Delete",
|
||||||
|
"dismiss": "Dismiss",
|
||||||
|
"download": "Download",
|
||||||
|
"disconnect": "Disconnect",
|
||||||
|
"export": "Export",
|
||||||
|
"generate": "Generate",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"import": "Import",
|
||||||
|
"message": "Message",
|
||||||
|
"now": "Now",
|
||||||
|
"ok": "OK",
|
||||||
|
"print": "Print",
|
||||||
|
"remove": "Remove",
|
||||||
|
"requestNewKeys": "Request New Keys",
|
||||||
|
"requestPosition": "Request Position",
|
||||||
|
"reset": "Reset",
|
||||||
|
"retry": "Retry",
|
||||||
|
"save": "Save",
|
||||||
|
"setDefault": "Set as default",
|
||||||
|
"unsetDefault": "Unset default",
|
||||||
|
"scanQr": "Scan QR Code",
|
||||||
|
"traceRoute": "Trace Route",
|
||||||
|
"submit": "Submit"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"title": "Meshtastic",
|
||||||
|
"fullTitle": "Meshtastic Web Client"
|
||||||
|
},
|
||||||
|
"loading": "Loading...",
|
||||||
|
"unit": {
|
||||||
|
"cps": "CPS",
|
||||||
|
"dbm": "dBm",
|
||||||
|
"hertz": "Hz",
|
||||||
|
"hop": {
|
||||||
|
"one": "Hop",
|
||||||
|
"plural": "Hops"
|
||||||
|
},
|
||||||
|
"hopsAway": {
|
||||||
|
"one": "{{count}} hop away",
|
||||||
|
"plural": "{{count}} hops away",
|
||||||
|
"unknown": "Unknown hops away"
|
||||||
|
},
|
||||||
|
"megahertz": "MHz",
|
||||||
|
"kilohertz": "kHz",
|
||||||
|
"raw": "raw",
|
||||||
|
"meter": {
|
||||||
|
"one": "Meter",
|
||||||
|
"plural": "Meters",
|
||||||
|
"suffix": "m"
|
||||||
|
},
|
||||||
|
"kilometer": {
|
||||||
|
"one": "Kilometer",
|
||||||
|
"plural": "Kilometers",
|
||||||
|
"suffix": "km"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "Minute",
|
||||||
|
"plural": "Minutes"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "Hour",
|
||||||
|
"plural": "Hours"
|
||||||
|
},
|
||||||
|
"millisecond": {
|
||||||
|
"one": "Millisecond",
|
||||||
|
"plural": "Milliseconds",
|
||||||
|
"suffix": "ms"
|
||||||
|
},
|
||||||
|
"second": {
|
||||||
|
"one": "Second",
|
||||||
|
"plural": "Seconds"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "Day",
|
||||||
|
"plural": "Days",
|
||||||
|
"today": "Today",
|
||||||
|
"yesterday": "Yesterday"
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"one": "Month",
|
||||||
|
"plural": "Months"
|
||||||
|
},
|
||||||
|
"year": {
|
||||||
|
"one": "Year",
|
||||||
|
"plural": "Years"
|
||||||
|
},
|
||||||
|
"snr": "SNR",
|
||||||
|
"volt": {
|
||||||
|
"one": "Volt",
|
||||||
|
"plural": "Volts",
|
||||||
|
"suffix": "V"
|
||||||
|
},
|
||||||
|
"record": {
|
||||||
|
"one": "Records",
|
||||||
|
"plural": "Records"
|
||||||
|
},
|
||||||
|
"degree": {
|
||||||
|
"one": "Degree",
|
||||||
|
"plural": "Degrees",
|
||||||
|
"suffix": "°"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"0bit": "Empty",
|
||||||
|
"8bit": "8 bit",
|
||||||
|
"128bit": "128 bit",
|
||||||
|
"256bit": "256 bit"
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
"longName": "Unknown",
|
||||||
|
"shortName": "UNK",
|
||||||
|
"notAvailable": "N/A",
|
||||||
|
"num": "??"
|
||||||
|
},
|
||||||
|
"nodeUnknownPrefix": "!",
|
||||||
|
"unset": "UNSET",
|
||||||
|
"fallbackName": "Meshtastic {{last4}}",
|
||||||
|
"node": "Node",
|
||||||
|
"formValidation": {
|
||||||
|
"unsavedChanges": "Unsaved changes",
|
||||||
|
"tooBig": {
|
||||||
|
"string": "Too long, expected less than or equal to {{maximum}} characters.",
|
||||||
|
"number": "Too big, expected a number smaller than or equal to {{maximum}}.",
|
||||||
|
"bytes": "Too big, expected less than or equal to {{params.maximum}} bytes."
|
||||||
|
},
|
||||||
|
"tooSmall": {
|
||||||
|
"string": "Too short, expected more than or equal to {{minimum}} characters.",
|
||||||
|
"number": "Too small, expected a number larger than or equal to {{minimum}}."
|
||||||
|
},
|
||||||
|
"invalidFormat": {
|
||||||
|
"ipv4": "Invalid format, expected an IPv4 address.",
|
||||||
|
"key": "Invalid format, expected a Base64 encoded pre-shared key (PSK)."
|
||||||
|
},
|
||||||
|
"invalidType": {
|
||||||
|
"number": "Invalid type, expected a number."
|
||||||
|
},
|
||||||
|
"pskLength": {
|
||||||
|
"0bit": "Key is required to be empty.",
|
||||||
|
"8bit": "Key is required to be an 8 bit pre-shared key (PSK).",
|
||||||
|
"128bit": "Key is required to be a 128 bit pre-shared key (PSK).",
|
||||||
|
"256bit": "Key is required to be a 256 bit pre-shared key (PSK)."
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"generic": "This field is required.",
|
||||||
|
"managed": "At least one admin key is requred if the node is managed.",
|
||||||
|
"key": "Key is required."
|
||||||
|
},
|
||||||
|
"invalidOverrideFreq": {
|
||||||
|
"number": "Invalid format, expected a value in the range 410-930 MHz or 0 (use default)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"title": "Settings",
|
||||||
|
"tabUser": "Карыстальнік",
|
||||||
|
"tabChannels": "Каналы",
|
||||||
|
"tabBluetooth": "Bluetooth",
|
||||||
|
"tabDevice": "Прылада",
|
||||||
|
"tabDisplay": "Экран",
|
||||||
|
"tabLora": "LoRa",
|
||||||
|
"tabNetwork": "Сетка",
|
||||||
|
"tabPosition": "Месцазнаходжанне",
|
||||||
|
"tabPower": "Power",
|
||||||
|
"tabSecurity": "Бяспека"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"label": "Configuration"
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"title": "Device Settings",
|
||||||
|
"description": "Settings for the device",
|
||||||
|
"buttonPin": {
|
||||||
|
"description": "Button pin override",
|
||||||
|
"label": "Button Pin"
|
||||||
|
},
|
||||||
|
"buzzerPin": {
|
||||||
|
"description": "Buzzer pin override",
|
||||||
|
"label": "Buzzer Pin"
|
||||||
|
},
|
||||||
|
"disableTripleClick": {
|
||||||
|
"description": "Disable triple click",
|
||||||
|
"label": "Disable Triple Click"
|
||||||
|
},
|
||||||
|
"doubleTapAsButtonPress": {
|
||||||
|
"description": "Treat double tap as button press",
|
||||||
|
"label": "Double Tap as Button Press"
|
||||||
|
},
|
||||||
|
"ledHeartbeatDisabled": {
|
||||||
|
"description": "Disable default blinking LED",
|
||||||
|
"label": "LED Heartbeat Disabled"
|
||||||
|
},
|
||||||
|
"nodeInfoBroadcastInterval": {
|
||||||
|
"description": "How often to broadcast node info",
|
||||||
|
"label": "Node Info Broadcast Interval"
|
||||||
|
},
|
||||||
|
"posixTimezone": {
|
||||||
|
"description": "The POSIX timezone string for the device",
|
||||||
|
"label": "POSIX Timezone"
|
||||||
|
},
|
||||||
|
"rebroadcastMode": {
|
||||||
|
"description": "How to handle rebroadcasting",
|
||||||
|
"label": "Rebroadcast Mode"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"description": "What role the device performs on the mesh",
|
||||||
|
"label": "Role"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bluetooth": {
|
||||||
|
"title": "Bluetooth Settings",
|
||||||
|
"description": "Settings for the Bluetooth module",
|
||||||
|
"note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.",
|
||||||
|
"enabled": {
|
||||||
|
"description": "Enable or disable Bluetooth",
|
||||||
|
"label": "Уключана"
|
||||||
|
},
|
||||||
|
"pairingMode": {
|
||||||
|
"description": "Pin selection behaviour.",
|
||||||
|
"label": "Pairing mode"
|
||||||
|
},
|
||||||
|
"pin": {
|
||||||
|
"description": "Pin to use when pairing",
|
||||||
|
"label": "Pin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"description": "Settings for the device display",
|
||||||
|
"title": "Display Settings",
|
||||||
|
"headingBold": {
|
||||||
|
"description": "Bolden the heading text",
|
||||||
|
"label": "Bold Heading"
|
||||||
|
},
|
||||||
|
"carouselDelay": {
|
||||||
|
"description": "How fast to cycle through windows",
|
||||||
|
"label": "Carousel Delay"
|
||||||
|
},
|
||||||
|
"compassNorthTop": {
|
||||||
|
"description": "Fix north to the top of compass",
|
||||||
|
"label": "Compass North Top"
|
||||||
|
},
|
||||||
|
"displayMode": {
|
||||||
|
"description": "Screen layout variant",
|
||||||
|
"label": "Display Mode"
|
||||||
|
},
|
||||||
|
"displayUnits": {
|
||||||
|
"description": "Display metric or imperial units",
|
||||||
|
"label": "Display Units"
|
||||||
|
},
|
||||||
|
"flipScreen": {
|
||||||
|
"description": "Flip display 180 degrees",
|
||||||
|
"label": "Flip Screen"
|
||||||
|
},
|
||||||
|
"gpsDisplayUnits": {
|
||||||
|
"description": "Coordinate display format",
|
||||||
|
"label": "GPS Display Units"
|
||||||
|
},
|
||||||
|
"oledType": {
|
||||||
|
"description": "Type of OLED screen attached to the device",
|
||||||
|
"label": "OLED Type"
|
||||||
|
},
|
||||||
|
"screenTimeout": {
|
||||||
|
"description": "Turn off the display after this long",
|
||||||
|
"label": "Screen Timeout"
|
||||||
|
},
|
||||||
|
"twelveHourClock": {
|
||||||
|
"description": "Use 12-hour clock format",
|
||||||
|
"label": "12-Hour Clock"
|
||||||
|
},
|
||||||
|
"wakeOnTapOrMotion": {
|
||||||
|
"description": "Wake the device on tap or motion",
|
||||||
|
"label": "Wake on Tap or Motion"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lora": {
|
||||||
|
"title": "Mesh Settings",
|
||||||
|
"description": "Settings for the LoRa mesh",
|
||||||
|
"bandwidth": {
|
||||||
|
"description": "Channel bandwidth in kHz",
|
||||||
|
"label": "Bandwidth"
|
||||||
|
},
|
||||||
|
"boostedRxGain": {
|
||||||
|
"description": "Boosted RX gain",
|
||||||
|
"label": "Boosted RX Gain"
|
||||||
|
},
|
||||||
|
"codingRate": {
|
||||||
|
"description": "The denominator of the coding rate",
|
||||||
|
"label": "Coding Rate"
|
||||||
|
},
|
||||||
|
"frequencyOffset": {
|
||||||
|
"description": "Frequency offset to correct for crystal calibration errors",
|
||||||
|
"label": "Frequency Offset"
|
||||||
|
},
|
||||||
|
"frequencySlot": {
|
||||||
|
"description": "LoRa frequency channel number",
|
||||||
|
"label": "Frequency Slot"
|
||||||
|
},
|
||||||
|
"hopLimit": {
|
||||||
|
"description": "Maximum number of hops",
|
||||||
|
"label": "Hop Limit"
|
||||||
|
},
|
||||||
|
"ignoreMqtt": {
|
||||||
|
"description": "Don't forward MQTT messages over the mesh",
|
||||||
|
"label": "Ignore MQTT"
|
||||||
|
},
|
||||||
|
"modemPreset": {
|
||||||
|
"description": "Modem preset to use",
|
||||||
|
"label": "Modem Preset"
|
||||||
|
},
|
||||||
|
"okToMqtt": {
|
||||||
|
"description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT",
|
||||||
|
"label": "OK to MQTT"
|
||||||
|
},
|
||||||
|
"overrideDutyCycle": {
|
||||||
|
"description": "Override Duty Cycle",
|
||||||
|
"label": "Override Duty Cycle"
|
||||||
|
},
|
||||||
|
"overrideFrequency": {
|
||||||
|
"description": "Override frequency",
|
||||||
|
"label": "Override Frequency"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"description": "Sets the region for your node",
|
||||||
|
"label": "Рэгіён"
|
||||||
|
},
|
||||||
|
"spreadingFactor": {
|
||||||
|
"description": "Indicates the number of chirps per symbol",
|
||||||
|
"label": "Spreading Factor"
|
||||||
|
},
|
||||||
|
"transmitEnabled": {
|
||||||
|
"description": "Enable/Disable transmit (TX) from the LoRa radio",
|
||||||
|
"label": "Transmit Enabled"
|
||||||
|
},
|
||||||
|
"transmitPower": {
|
||||||
|
"description": "Max transmit power",
|
||||||
|
"label": "Transmit Power"
|
||||||
|
},
|
||||||
|
"usePreset": {
|
||||||
|
"description": "Use one of the predefined modem presets",
|
||||||
|
"label": "Use Preset"
|
||||||
|
},
|
||||||
|
"meshSettings": {
|
||||||
|
"description": "Settings for the LoRa mesh",
|
||||||
|
"label": "Mesh Settings"
|
||||||
|
},
|
||||||
|
"waveformSettings": {
|
||||||
|
"description": "Settings for the LoRa waveform",
|
||||||
|
"label": "Waveform Settings"
|
||||||
|
},
|
||||||
|
"radioSettings": {
|
||||||
|
"label": "Radio Settings",
|
||||||
|
"description": "Settings for the LoRa radio"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"title": "WiFi Config",
|
||||||
|
"description": "WiFi radio configuration",
|
||||||
|
"note": "Note: Some devices (ESP32) cannot use both Bluetooth and WiFi at the same time.",
|
||||||
|
"addressMode": {
|
||||||
|
"description": "Address assignment selection",
|
||||||
|
"label": "Address Mode"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"description": "DNS Server",
|
||||||
|
"label": "DNS"
|
||||||
|
},
|
||||||
|
"ethernetEnabled": {
|
||||||
|
"description": "Enable or disable the Ethernet port",
|
||||||
|
"label": "Уключана"
|
||||||
|
},
|
||||||
|
"gateway": {
|
||||||
|
"description": "Default Gateway",
|
||||||
|
"label": "Gateway"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"description": "IP Address",
|
||||||
|
"label": "IP"
|
||||||
|
},
|
||||||
|
"psk": {
|
||||||
|
"description": "Network password",
|
||||||
|
"label": "PSK"
|
||||||
|
},
|
||||||
|
"ssid": {
|
||||||
|
"description": "Network name",
|
||||||
|
"label": "SSID"
|
||||||
|
},
|
||||||
|
"subnet": {
|
||||||
|
"description": "Subnet Mask",
|
||||||
|
"label": "Subnet"
|
||||||
|
},
|
||||||
|
"wifiEnabled": {
|
||||||
|
"description": "Enable or disable the WiFi radio",
|
||||||
|
"label": "Уключана"
|
||||||
|
},
|
||||||
|
"meshViaUdp": {
|
||||||
|
"label": "Mesh via UDP"
|
||||||
|
},
|
||||||
|
"ntpServer": {
|
||||||
|
"label": "NTP Server"
|
||||||
|
},
|
||||||
|
"rsyslogServer": {
|
||||||
|
"label": "Rsyslog Server"
|
||||||
|
},
|
||||||
|
"ethernetConfigSettings": {
|
||||||
|
"description": "Ethernet port configuration",
|
||||||
|
"label": "Ethernet Config"
|
||||||
|
},
|
||||||
|
"ipConfigSettings": {
|
||||||
|
"description": "IP configuration",
|
||||||
|
"label": "IP Config"
|
||||||
|
},
|
||||||
|
"ntpConfigSettings": {
|
||||||
|
"description": "NTP configuration",
|
||||||
|
"label": "NTP Config"
|
||||||
|
},
|
||||||
|
"rsyslogConfigSettings": {
|
||||||
|
"description": "Rsyslog configuration",
|
||||||
|
"label": "Rsyslog Config"
|
||||||
|
},
|
||||||
|
"udpConfigSettings": {
|
||||||
|
"description": "UDP over Mesh configuration",
|
||||||
|
"label": "UDP Config"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"title": "Position Settings",
|
||||||
|
"description": "Settings for the position module",
|
||||||
|
"broadcastInterval": {
|
||||||
|
"description": "How often your position is sent out over the mesh",
|
||||||
|
"label": "Broadcast Interval"
|
||||||
|
},
|
||||||
|
"enablePin": {
|
||||||
|
"description": "GPS module enable pin override",
|
||||||
|
"label": "Enable Pin"
|
||||||
|
},
|
||||||
|
"fixedPosition": {
|
||||||
|
"description": "Don't report GPS position, but a manually-specified one",
|
||||||
|
"label": "Fixed Position"
|
||||||
|
},
|
||||||
|
"gpsMode": {
|
||||||
|
"description": "Configure whether device GPS is Enabled, Disabled, or Not Present",
|
||||||
|
"label": "GPS Mode"
|
||||||
|
},
|
||||||
|
"gpsUpdateInterval": {
|
||||||
|
"description": "How often a GPS fix should be acquired",
|
||||||
|
"label": "GPS Update Interval"
|
||||||
|
},
|
||||||
|
"positionFlags": {
|
||||||
|
"description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.",
|
||||||
|
"label": "Position Flags"
|
||||||
|
},
|
||||||
|
"receivePin": {
|
||||||
|
"description": "GPS module RX pin override",
|
||||||
|
"label": "Receive Pin"
|
||||||
|
},
|
||||||
|
"smartPositionEnabled": {
|
||||||
|
"description": "Only send position when there has been a meaningful change in location",
|
||||||
|
"label": "Enable Smart Position"
|
||||||
|
},
|
||||||
|
"smartPositionMinDistance": {
|
||||||
|
"description": "Minimum distance (in meters) that must be traveled before a position update is sent",
|
||||||
|
"label": "Smart Position Minimum Distance"
|
||||||
|
},
|
||||||
|
"smartPositionMinInterval": {
|
||||||
|
"description": "Minimum interval (in seconds) that must pass before a position update is sent",
|
||||||
|
"label": "Smart Position Minimum Interval"
|
||||||
|
},
|
||||||
|
"transmitPin": {
|
||||||
|
"description": "GPS module TX pin override",
|
||||||
|
"label": "Transmit Pin"
|
||||||
|
},
|
||||||
|
"intervalsSettings": {
|
||||||
|
"description": "How often to send position updates",
|
||||||
|
"label": "Intervals"
|
||||||
|
},
|
||||||
|
"flags": {
|
||||||
|
"placeholder": "Select position flags...",
|
||||||
|
"altitude": "Altitude",
|
||||||
|
"altitudeGeoidalSeparation": "Altitude Geoidal Separation",
|
||||||
|
"altitudeMsl": "Altitude is Mean Sea Level",
|
||||||
|
"dop": "Dilution of precision (DOP) PDOP used by default",
|
||||||
|
"hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP",
|
||||||
|
"numSatellites": "Number of satellites",
|
||||||
|
"sequenceNumber": "Sequence number",
|
||||||
|
"timestamp": "Timestamp",
|
||||||
|
"unset": "Unset",
|
||||||
|
"vehicleHeading": "Vehicle heading",
|
||||||
|
"vehicleSpeed": "Vehicle speed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power": {
|
||||||
|
"adcMultiplierOverride": {
|
||||||
|
"description": "Used for tweaking battery voltage reading",
|
||||||
|
"label": "ADC Multiplier Override ratio"
|
||||||
|
},
|
||||||
|
"ina219Address": {
|
||||||
|
"description": "Address of the INA219 battery monitor",
|
||||||
|
"label": "INA219 Address"
|
||||||
|
},
|
||||||
|
"lightSleepDuration": {
|
||||||
|
"description": "How long the device will be in light sleep for",
|
||||||
|
"label": "Light Sleep Duration"
|
||||||
|
},
|
||||||
|
"minimumWakeTime": {
|
||||||
|
"description": "Minimum amount of time the device will stay awake for after receiving a packet",
|
||||||
|
"label": "Minimum Wake Time"
|
||||||
|
},
|
||||||
|
"noConnectionBluetoothDisabled": {
|
||||||
|
"description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long",
|
||||||
|
"label": "No Connection Bluetooth Disabled"
|
||||||
|
},
|
||||||
|
"powerSavingEnabled": {
|
||||||
|
"description": "Select if powered from a low-current source (i.e. solar), to minimize power consumption as much as possible.",
|
||||||
|
"label": "Enable power saving mode"
|
||||||
|
},
|
||||||
|
"shutdownOnBatteryDelay": {
|
||||||
|
"description": "Automatically shutdown node after this long when on battery, 0 for indefinite",
|
||||||
|
"label": "Shutdown on battery delay"
|
||||||
|
},
|
||||||
|
"superDeepSleepDuration": {
|
||||||
|
"description": "How long the device will be in super deep sleep for",
|
||||||
|
"label": "Super Deep Sleep Duration"
|
||||||
|
},
|
||||||
|
"powerConfigSettings": {
|
||||||
|
"description": "Settings for the power module",
|
||||||
|
"label": "Power Config"
|
||||||
|
},
|
||||||
|
"sleepSettings": {
|
||||||
|
"description": "Sleep settings for the power module",
|
||||||
|
"label": "Sleep Settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"description": "Settings for the Security configuration",
|
||||||
|
"title": "Security Settings",
|
||||||
|
"button_backupKey": "Backup Key",
|
||||||
|
"adminChannelEnabled": {
|
||||||
|
"description": "Allow incoming device control over the insecure legacy admin channel",
|
||||||
|
"label": "Allow Legacy Admin"
|
||||||
|
},
|
||||||
|
"enableDebugLogApi": {
|
||||||
|
"description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth",
|
||||||
|
"label": "Enable Debug Log API"
|
||||||
|
},
|
||||||
|
"managed": {
|
||||||
|
"description": "If enabled, device configuration options are only able to be changed remotely by a Remote Admin node via admin messages. Do not enable this option unless at least one suitable Remote Admin node has been setup, and the public key is stored in one of the fields above.",
|
||||||
|
"label": "Managed"
|
||||||
|
},
|
||||||
|
"privateKey": {
|
||||||
|
"description": "Used to create a shared key with a remote device",
|
||||||
|
"label": "Private Key"
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"description": "Sent out to other nodes on the mesh to allow them to compute a shared secret key",
|
||||||
|
"label": "Public Key"
|
||||||
|
},
|
||||||
|
"primaryAdminKey": {
|
||||||
|
"description": "The primary public key authorized to send admin messages to this node",
|
||||||
|
"label": "Primary Admin Key"
|
||||||
|
},
|
||||||
|
"secondaryAdminKey": {
|
||||||
|
"description": "The secondary public key authorized to send admin messages to this node",
|
||||||
|
"label": "Secondary Admin Key"
|
||||||
|
},
|
||||||
|
"serialOutputEnabled": {
|
||||||
|
"description": "Serial Console over the Stream API",
|
||||||
|
"label": "Serial Output Enabled"
|
||||||
|
},
|
||||||
|
"tertiaryAdminKey": {
|
||||||
|
"description": "The tertiary public key authorized to send admin messages to this node",
|
||||||
|
"label": "Tertiary Admin Key"
|
||||||
|
},
|
||||||
|
"adminSettings": {
|
||||||
|
"description": "Settings for Admin",
|
||||||
|
"label": "Admin Settings"
|
||||||
|
},
|
||||||
|
"loggingSettings": {
|
||||||
|
"description": "Settings for Logging",
|
||||||
|
"label": "Logging Settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"title": "User Settings",
|
||||||
|
"description": "Configure your device name and identity settings",
|
||||||
|
"longName": {
|
||||||
|
"label": "Long Name",
|
||||||
|
"description": "Your full display name (1-40 characters)",
|
||||||
|
"validation": {
|
||||||
|
"min": "Long name must be at least 1 character",
|
||||||
|
"max": "Long name must be at most 40 characters"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortName": {
|
||||||
|
"label": "Short Name",
|
||||||
|
"description": "Your abbreviated name (2-4 characters)",
|
||||||
|
"validation": {
|
||||||
|
"min": "Short name must be at least 2 characters",
|
||||||
|
"max": "Short name must be at most 4 characters"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isUnmessageable": {
|
||||||
|
"label": "Unmessageable",
|
||||||
|
"description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond."
|
||||||
|
},
|
||||||
|
"isLicensed": {
|
||||||
|
"label": "Licensed amateur radio (HAM)",
|
||||||
|
"description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"title": "Connect to a Meshtastic device",
|
||||||
|
"description": "Add a device connection via HTTP, Bluetooth, or Serial. Your saved connections will be saved in your browser."
|
||||||
|
},
|
||||||
|
"connectionType_ble": "BLE",
|
||||||
|
"connectionType_serial": "Serial",
|
||||||
|
"connectionType_network": "Сетка",
|
||||||
|
"deleteConnection": "Delete connection",
|
||||||
|
"areYouSure": "This will remove {{name}}. You canot undo this action.",
|
||||||
|
"moreActions": "More actions",
|
||||||
|
"noConnections": {
|
||||||
|
"title": "No connections yet.",
|
||||||
|
"description": "Create your first connection. It will connect immediately and be saved for later."
|
||||||
|
},
|
||||||
|
"lastConnectedAt": "Last connected: {{date}}",
|
||||||
|
"neverConnected": "Never connected",
|
||||||
|
"toasts": {
|
||||||
|
"connected": "Злучаны",
|
||||||
|
"nowConnected": "{{name}} is now connected",
|
||||||
|
"nowDisconnected": "{{name}} are now disconnecte",
|
||||||
|
"disconnected": "Адлучана",
|
||||||
|
"failed": "Failed to connect",
|
||||||
|
"checkConnetion": "Check your device or settings and try again",
|
||||||
|
"defaultSet": "Default set",
|
||||||
|
"defaultConnection": "Default connection is now {{nameisconnected}}",
|
||||||
|
"deleted": "Deleted",
|
||||||
|
"deletedByName": "{{name}} was removed",
|
||||||
|
"pickConnectionAgain": "Could not connect. You may need to reselect the device/port.",
|
||||||
|
"added": "Connection added",
|
||||||
|
"savedByName": "{{name}} saved.",
|
||||||
|
"savedCantConnect": "The connection was saved but could not connect."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
"title": "Mesh Settings",
|
"title": "Mesh Settings",
|
||||||
"description": "Settings for the LoRa mesh",
|
"description": "Settings for the LoRa mesh",
|
||||||
"bandwidth": {
|
"bandwidth": {
|
||||||
"description": "Channel bandwidth in MHz",
|
"description": "Channel bandwidth in kHz",
|
||||||
"label": "Bandwidth"
|
"label": "Bandwidth"
|
||||||
},
|
},
|
||||||
"boostedRxGain": {
|
"boostedRxGain": {
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
{
|
||||||
|
"deleteMessages": {
|
||||||
|
"description": "This action will clear all message history. This cannot be undone. Are you sure you want to continue?",
|
||||||
|
"title": "Clear All Messages"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"description": "The current LoRa configuration will be overridden.",
|
||||||
|
"error": {
|
||||||
|
"invalidUrl": "Invalid Meshtastic URL"
|
||||||
|
},
|
||||||
|
"channelPrefix": "Channel: ",
|
||||||
|
"primary": "Primary ",
|
||||||
|
"doNotImport": "No not import",
|
||||||
|
"channelName": "Назва",
|
||||||
|
"channelSlot": "Slot",
|
||||||
|
"channelSetUrl": "Channel Set/QR Code URL",
|
||||||
|
"useLoraConfig": "Import LoRa Config",
|
||||||
|
"presetDescription": "The current LoRa Config will be replaced.",
|
||||||
|
"title": "Import Channel Set"
|
||||||
|
},
|
||||||
|
"locationResponse": {
|
||||||
|
"title": "Location: {{identifier}}",
|
||||||
|
"altitude": "Altitude: ",
|
||||||
|
"coordinates": "Coordinates: ",
|
||||||
|
"noCoordinates": "No Coordinates"
|
||||||
|
},
|
||||||
|
"pkiRegenerateDialog": {
|
||||||
|
"title": "Regenerate Pre-Shared Key?",
|
||||||
|
"description": "Are you sure you want to regenerate the pre-shared key?",
|
||||||
|
"regenerate": "Regenerate"
|
||||||
|
},
|
||||||
|
"addConnection": {
|
||||||
|
"title": "Add connection",
|
||||||
|
"description": "Choose a connection type and fill in the details",
|
||||||
|
"validation": {
|
||||||
|
"requiresWebBluetooth": "This connection type requires <0>Web Bluetooth</0>. Please use a supported browser, like Chrome or Edge.",
|
||||||
|
"requiresWebSerial": "This connection type requires <0>Web Serial</0>. Please use a supported browser, like Chrome or Edge.",
|
||||||
|
"requiresSecureContext": "This application requires a <0>secure context</0>. Please connect using HTTPS or localhost.",
|
||||||
|
"additionallyRequiresSecureContext": "Additionally, it requires a <0>secure context</0>. Please connect using HTTPS or localhost."
|
||||||
|
},
|
||||||
|
"bluetoothConnection": {
|
||||||
|
"namePlaceholder": "My Bluetooth Node",
|
||||||
|
"supported": {
|
||||||
|
"title": "Web Bluetooth supported"
|
||||||
|
},
|
||||||
|
"notSupported": {
|
||||||
|
"title": "Web Bluetooth not supported",
|
||||||
|
"description": "Your browser or device does not support Web Bluetooth"
|
||||||
|
},
|
||||||
|
"short": "BT: {{deviceName}}",
|
||||||
|
"long": "Bluetooth Device",
|
||||||
|
"device": "Прылада",
|
||||||
|
"selectDevice": "Select device",
|
||||||
|
"selected": "Bluetooth device selected",
|
||||||
|
"notSelected": "No device selected",
|
||||||
|
"helperText": "Uses the Meshtastic Bluetooth service for discovery."
|
||||||
|
},
|
||||||
|
"serialConnection": {
|
||||||
|
"namePlaceholder": "My Serial Node",
|
||||||
|
"helperText": "Selecting a port grants permission so the app can open it to connect.",
|
||||||
|
"supported": {
|
||||||
|
"title": "Web Serial supported"
|
||||||
|
},
|
||||||
|
"notSupported": {
|
||||||
|
"title": "Web Serial not supported",
|
||||||
|
"description": "Your browser or device does not support Web Serial"
|
||||||
|
},
|
||||||
|
"portSelected": {
|
||||||
|
"title": "Serial port selected",
|
||||||
|
"description": "Port permissions granted."
|
||||||
|
},
|
||||||
|
"port": "Port",
|
||||||
|
"selectPort": "Select port",
|
||||||
|
"deviceName": "USB {{vendorId}}:{{productId}}",
|
||||||
|
"notSelected": "No port selected"
|
||||||
|
},
|
||||||
|
"httpConnection": {
|
||||||
|
"namePlaceholder": "My HTTP Node",
|
||||||
|
"inputPlaceholder": "192.168.1.10 or meshtastic.local",
|
||||||
|
"heading": "URL or IP",
|
||||||
|
"useHttps": "Use HTTPS",
|
||||||
|
"invalidUrl": {
|
||||||
|
"title": "Invalid URL",
|
||||||
|
"description": "Please enter a valid HTTP or HTTPS URL."
|
||||||
|
},
|
||||||
|
"connectionTest": {
|
||||||
|
"description": "Test the connetion before saving to verify the device is reachable.",
|
||||||
|
"button": {
|
||||||
|
"loading": "Testing...",
|
||||||
|
"label": "Test connection"
|
||||||
|
},
|
||||||
|
"reachable": "Reachable",
|
||||||
|
"notReachable": "Not reachable",
|
||||||
|
"success": {
|
||||||
|
"title": "Connection test successful",
|
||||||
|
"description": "The device appears to be reachable."
|
||||||
|
},
|
||||||
|
"failure": {
|
||||||
|
"title": "Connection test failed",
|
||||||
|
"description": "Could not reach the device. Check the URL and try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nodeDetails": {
|
||||||
|
"message": "Message",
|
||||||
|
"requestPosition": "Request Position",
|
||||||
|
"traceRoute": "Trace Route",
|
||||||
|
"airTxUtilization": "Air TX utilization",
|
||||||
|
"allRawMetrics": "All Raw Metrics:",
|
||||||
|
"batteryLevel": "Battery level",
|
||||||
|
"channelUtilization": "Channel utilization",
|
||||||
|
"details": "Details:",
|
||||||
|
"deviceMetrics": "Device Metrics:",
|
||||||
|
"hardware": "Hardware: ",
|
||||||
|
"lastHeard": "Last Heard: ",
|
||||||
|
"nodeHexPrefix": "Node Hex: ",
|
||||||
|
"nodeNumber": "Node Number: ",
|
||||||
|
"position": "Position:",
|
||||||
|
"role": "Role: ",
|
||||||
|
"uptime": "Uptime: ",
|
||||||
|
"voltage": "Voltage",
|
||||||
|
"title": "Node Details for {{identifier}}",
|
||||||
|
"ignoreNode": "Ignore node",
|
||||||
|
"removeNode": "Remove node",
|
||||||
|
"unignoreNode": "Unignore node",
|
||||||
|
"security": "Security:",
|
||||||
|
"publicKey": "Public Key: ",
|
||||||
|
"messageable": "Messageable: ",
|
||||||
|
"KeyManuallyVerifiedTrue": "Public Key has been manually verified",
|
||||||
|
"KeyManuallyVerifiedFalse": "Public Key is not manually verified"
|
||||||
|
},
|
||||||
|
"pkiBackup": {
|
||||||
|
"loseKeysWarning": "If you lose your keys, you will need to reset your device.",
|
||||||
|
"secureBackup": "Its important to backup your public and private keys and store your backup securely!",
|
||||||
|
"footer": "=== END OF KEYS ===",
|
||||||
|
"header": "=== MESHTASTIC KEYS FOR {{longName}} ({{shortName}}) ===",
|
||||||
|
"privateKey": "Private Key:",
|
||||||
|
"publicKey": "Public Key:",
|
||||||
|
"fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt",
|
||||||
|
"title": "Backup Keys"
|
||||||
|
},
|
||||||
|
"pkiBackupReminder": {
|
||||||
|
"description": "We recommend backing up your key data regularly. Would you like to back up now?",
|
||||||
|
"title": "Backup Reminder",
|
||||||
|
"remindLaterPrefix": "Remind me in",
|
||||||
|
"remindNever": "Never remind me",
|
||||||
|
"backupNow": "Back up now"
|
||||||
|
},
|
||||||
|
"pkiRegenerate": {
|
||||||
|
"description": "Are you sure you want to regenerate key pair?",
|
||||||
|
"title": "Regenerate Key Pair"
|
||||||
|
},
|
||||||
|
"qr": {
|
||||||
|
"addChannels": "Add Channels",
|
||||||
|
"replaceChannels": "Replace Channels",
|
||||||
|
"description": "The current LoRa configuration will also be shared.",
|
||||||
|
"sharableUrl": "Sharable URL",
|
||||||
|
"title": "Generate QR Code"
|
||||||
|
},
|
||||||
|
"reboot": {
|
||||||
|
"title": "Reboot device",
|
||||||
|
"description": "Reboot now or schedule a reboot of the connected node. Optionally, you can choose to reboot into OTA (Over-the-Air) mode.",
|
||||||
|
"ota": "Reboot into OTA mode",
|
||||||
|
"enterDelay": "Enter delay",
|
||||||
|
"scheduled": "Reboot has been scheduled",
|
||||||
|
"schedule": "Schedule reboot",
|
||||||
|
"now": "Reboot now",
|
||||||
|
"cancel": "Cancel scheduled reboot"
|
||||||
|
},
|
||||||
|
"refreshKeys": {
|
||||||
|
"description": {
|
||||||
|
"acceptNewKeys": "This will remove the node from device and request new keys.",
|
||||||
|
"keyMismatchReasonSuffix": ". This is due to the remote node's current public key does not match the previously stored key for this node.",
|
||||||
|
"unableToSendDmPrefix": "Your node is unable to send a direct message to node: "
|
||||||
|
},
|
||||||
|
"acceptNewKeys": "Accept New Keys",
|
||||||
|
"title": "Keys Mismatch - {{identifier}}"
|
||||||
|
},
|
||||||
|
"removeNode": {
|
||||||
|
"description": "Are you sure you want to remove this Node?",
|
||||||
|
"title": "Remove Node?"
|
||||||
|
},
|
||||||
|
"shutdown": {
|
||||||
|
"title": "Schedule Shutdown",
|
||||||
|
"description": "Turn off the connected node after x minutes."
|
||||||
|
},
|
||||||
|
"traceRoute": {
|
||||||
|
"routeToDestination": "Route to destination:",
|
||||||
|
"routeBack": "Route back:"
|
||||||
|
},
|
||||||
|
"tracerouteResponse": {
|
||||||
|
"title": "Traceroute: {{identifier}}"
|
||||||
|
},
|
||||||
|
"unsafeRoles": {
|
||||||
|
"confirmUnderstanding": "Yes, I know what I'm doing",
|
||||||
|
"conjunction": " and the blog post about ",
|
||||||
|
"postamble": " and understand the implications of changing the role.",
|
||||||
|
"preamble": "I have read the ",
|
||||||
|
"choosingRightDeviceRole": "Choosing The Right Device Role",
|
||||||
|
"deviceRoleDocumentation": "Device Role Documentation",
|
||||||
|
"title": "Are you sure?"
|
||||||
|
},
|
||||||
|
"managedMode": {
|
||||||
|
"confirmUnderstanding": "Yes, I know what I'm doing",
|
||||||
|
"title": "Are you sure?",
|
||||||
|
"description": "Enabling Managed Mode blocks client applications (including the web client) from writing configurations to a radio. Once enabled, radio configurations can only be changed through Remote Admin messages. This setting is not required for remote node administration."
|
||||||
|
},
|
||||||
|
"clientNotification": {
|
||||||
|
"title": "Client Notification",
|
||||||
|
"TraceRoute can only be sent once every 30 seconds": "TraceRoute can only be sent once every 30 seconds",
|
||||||
|
"Compromised keys were detected and regenerated.": "Compromised keys were detected and regenerated."
|
||||||
|
},
|
||||||
|
"resetNodeDb": {
|
||||||
|
"title": "Reset Node Database",
|
||||||
|
"description": "This will clear all nodes from the connected device's node database and clear all message history in the client. This cannot be undone. Are you sure you want to continue?",
|
||||||
|
"confirm": "Reset Node Database",
|
||||||
|
"failedTitle": "There was an error resetting the Node DB. Please try again."
|
||||||
|
},
|
||||||
|
"clearAllStores": {
|
||||||
|
"title": "Clear All Local Storage",
|
||||||
|
"description": "This will clear all locally stored data, including message history and node databases for all previously connected devices. This will require you to reconnect to your node once complete and cannot be undone. Are you sure you want to continue?",
|
||||||
|
"confirm": "Clear all local storage",
|
||||||
|
"failedTitle": "There was an error clearing local storage. Please try again."
|
||||||
|
},
|
||||||
|
"factoryResetDevice": {
|
||||||
|
"title": "Factory Reset Device",
|
||||||
|
"description": "This will factory reset the connected device, erasing all configurations and data on the device as well as all nodes and messages saved in the client. This cannot be undone. Are you sure you want to continue?",
|
||||||
|
"confirm": "Factory Reset Device",
|
||||||
|
"failedTitle": "There was an error performing the factory reset. Please try again."
|
||||||
|
},
|
||||||
|
"factoryResetConfig": {
|
||||||
|
"title": "Factory Reset Config",
|
||||||
|
"description": "This will factory reset the configuration on the connected device, erasing all configurations on the device. This cannot be undone. Are you sure you want to continue?",
|
||||||
|
"confirm": "Factory Reset Config",
|
||||||
|
"failedTitle": "There was an error performing the factory reset. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"maplibre": {
|
||||||
|
"GeolocateControl.FindMyLocation": "Find my location",
|
||||||
|
"NavigationControl.ZoomIn": "Zoom in",
|
||||||
|
"NavigationControl.ZoomOut": "Zoom out",
|
||||||
|
"CooperativeGesturesHandler.WindowsHelpText": "Use Ctrl + scroll to zoom the map",
|
||||||
|
"CooperativeGesturesHandler.MacHelpText": "Use ⌘ + scroll to zoom the map",
|
||||||
|
"CooperativeGesturesHandler.MobileHelpText": "Use two fingers to move the map"
|
||||||
|
},
|
||||||
|
"layerTool": {
|
||||||
|
"nodeMarkers": "Show nodes",
|
||||||
|
"directNeighbors": "Show direct connections",
|
||||||
|
"remoteNeighbors": "Show remote connections",
|
||||||
|
"positionPrecision": "Show position precision",
|
||||||
|
"traceroutes": "Show traceroutes",
|
||||||
|
"waypoints": "Show waypoints"
|
||||||
|
},
|
||||||
|
"mapMenu": {
|
||||||
|
"locateAria": "Locate my node",
|
||||||
|
"layersAria": "Change map style"
|
||||||
|
},
|
||||||
|
"waypointDetail": {
|
||||||
|
"edit": "Змяніць",
|
||||||
|
"description": "Description:",
|
||||||
|
"createdBy": "Edited by:",
|
||||||
|
"createdDate": "Created:",
|
||||||
|
"updated": "Updated:",
|
||||||
|
"expires": "Expires:",
|
||||||
|
"distance": "Distance:",
|
||||||
|
"bearing": "Absolute bearing:",
|
||||||
|
"lockedTo": "Locked by:",
|
||||||
|
"latitude": "Latitude:",
|
||||||
|
"longitude": "Longitude:"
|
||||||
|
},
|
||||||
|
"myNode": {
|
||||||
|
"tooltip": "This device"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"title": "Messages: {{chatName}}",
|
||||||
|
"placeholder": "Enter Message"
|
||||||
|
},
|
||||||
|
"emptyState": {
|
||||||
|
"title": "Select a Chat",
|
||||||
|
"text": "No messages yet."
|
||||||
|
},
|
||||||
|
"selectChatPrompt": {
|
||||||
|
"text": "Select a channel or node to start messaging."
|
||||||
|
},
|
||||||
|
"sendMessage": {
|
||||||
|
"placeholder": "Enter your message here...",
|
||||||
|
"sendButton": "Send"
|
||||||
|
},
|
||||||
|
"actionsMenu": {
|
||||||
|
"addReactionLabel": "Add Reaction",
|
||||||
|
"replyLabel": "Reply"
|
||||||
|
},
|
||||||
|
"deliveryStatus": {
|
||||||
|
"delivered": {
|
||||||
|
"label": "Message delivered",
|
||||||
|
"displayText": "Message delivered"
|
||||||
|
},
|
||||||
|
"failed": {
|
||||||
|
"label": "Message delivery failed",
|
||||||
|
"displayText": "Delivery failed"
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
"label": "Message status unknown",
|
||||||
|
"displayText": "Unknown state"
|
||||||
|
},
|
||||||
|
"waiting": {
|
||||||
|
"label": "Sending message",
|
||||||
|
"displayText": "Waiting for delivery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,534 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"tabAmbientLighting": "Ambient Lighting",
|
||||||
|
"tabAudio": "Audio",
|
||||||
|
"tabCannedMessage": "Canned",
|
||||||
|
"tabDetectionSensor": "Detection Sensor",
|
||||||
|
"tabExternalNotification": "Ext Notif",
|
||||||
|
"tabMqtt": "MQTT",
|
||||||
|
"tabNeighborInfo": "Neighbor Info",
|
||||||
|
"tabPaxcounter": "Paxcounter",
|
||||||
|
"tabRangeTest": "Range Test",
|
||||||
|
"tabSerial": "Serial",
|
||||||
|
"tabStoreAndForward": "S&F",
|
||||||
|
"tabTelemetry": "Telemetry"
|
||||||
|
},
|
||||||
|
"ambientLighting": {
|
||||||
|
"title": "Ambient Lighting",
|
||||||
|
"description": "Settings for the Ambient Lighting module",
|
||||||
|
"ledState": {
|
||||||
|
"label": "LED State",
|
||||||
|
"description": "Sets LED to on or off"
|
||||||
|
},
|
||||||
|
"current": {
|
||||||
|
"label": "Current",
|
||||||
|
"description": "Sets the current for the LED output. Default is 10"
|
||||||
|
},
|
||||||
|
"red": {
|
||||||
|
"label": "Red",
|
||||||
|
"description": "Sets the red LED level. Values are 0-255"
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"label": "Green",
|
||||||
|
"description": "Sets the green LED level. Values are 0-255"
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"label": "Blue",
|
||||||
|
"description": "Sets the blue LED level. Values are 0-255"
|
||||||
|
},
|
||||||
|
"ambientLightingConfig": {
|
||||||
|
"label": "Ambient Lighting Config",
|
||||||
|
"description": "Ambient Lighting configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"title": "Audio",
|
||||||
|
"description": "Settings for the Audio module",
|
||||||
|
"codec2Enabled": {
|
||||||
|
"label": "CODEC 2 enabled",
|
||||||
|
"description": "Enable Codec 2 audio encoding"
|
||||||
|
},
|
||||||
|
"pttPin": {
|
||||||
|
"label": "PTT pin",
|
||||||
|
"description": "GPIO pin to use for PTT"
|
||||||
|
},
|
||||||
|
"bitrate": {
|
||||||
|
"label": "CODEC2 sample rate",
|
||||||
|
"description": "Bitrate to use for audio encoding"
|
||||||
|
},
|
||||||
|
"i2sWs": {
|
||||||
|
"label": "I2S word select",
|
||||||
|
"description": "GPIO pin to use for i2S WS"
|
||||||
|
},
|
||||||
|
"i2sSd": {
|
||||||
|
"label": "I2S data in",
|
||||||
|
"description": "GPIO pin to use for i2S SD"
|
||||||
|
},
|
||||||
|
"i2sDin": {
|
||||||
|
"label": "I2S data out",
|
||||||
|
"description": "GPIO pin to use for i2S DIN"
|
||||||
|
},
|
||||||
|
"i2sSck": {
|
||||||
|
"label": "I2S clock",
|
||||||
|
"description": "GPIO pin to use for i2S SCK"
|
||||||
|
},
|
||||||
|
"audioConfig": {
|
||||||
|
"label": "Audio Config",
|
||||||
|
"description": "Audio configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cannedMessage": {
|
||||||
|
"title": "Canned Message",
|
||||||
|
"description": "Settings for the Canned Message module",
|
||||||
|
"moduleEnabled": {
|
||||||
|
"label": "Module Enabled",
|
||||||
|
"description": "Enable Canned Message"
|
||||||
|
},
|
||||||
|
"rotary1Enabled": {
|
||||||
|
"label": "Rotary encoder #1 enabled",
|
||||||
|
"description": "Enable the rotary encoder"
|
||||||
|
},
|
||||||
|
"inputbrokerPinA": {
|
||||||
|
"label": "GPIO pin for rotary encoder A port",
|
||||||
|
"description": "GPIO Pin Value (1-39) For encoder port A"
|
||||||
|
},
|
||||||
|
"inputbrokerPinB": {
|
||||||
|
"label": "GPIO pin for rotary encoder B port",
|
||||||
|
"description": "GPIO Pin Value (1-39) For encoder port B"
|
||||||
|
},
|
||||||
|
"inputbrokerPinPress": {
|
||||||
|
"label": "GPIO pin for rotary encoder Press port",
|
||||||
|
"description": "GPIO Pin Value (1-39) For encoder Press"
|
||||||
|
},
|
||||||
|
"inputbrokerEventCw": {
|
||||||
|
"label": "Generate input event on CW",
|
||||||
|
"description": "Select input event."
|
||||||
|
},
|
||||||
|
"inputbrokerEventCcw": {
|
||||||
|
"label": "Generate input event on CCW",
|
||||||
|
"description": "Select input event."
|
||||||
|
},
|
||||||
|
"inputbrokerEventPress": {
|
||||||
|
"label": "Generate input event on Press",
|
||||||
|
"description": "Select input event"
|
||||||
|
},
|
||||||
|
"updown1Enabled": {
|
||||||
|
"label": "Up/Down/Select input enabled",
|
||||||
|
"description": "Enable the up / down encoder"
|
||||||
|
},
|
||||||
|
"allowInputSource": {
|
||||||
|
"label": "Allow input source",
|
||||||
|
"description": "Select from: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'"
|
||||||
|
},
|
||||||
|
"sendBell": {
|
||||||
|
"label": "Send bell",
|
||||||
|
"description": "Sends a bell character with each message"
|
||||||
|
},
|
||||||
|
"cannedMessageConfig": {
|
||||||
|
"label": "Canned Message Config",
|
||||||
|
"description": "Canned Message configuration"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"label": "Canned message enabled",
|
||||||
|
"description": "Enable Canned Message"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"detectionSensor": {
|
||||||
|
"title": "Detection Sensor",
|
||||||
|
"description": "Settings for the Detection Sensor module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "Detection Sensor enabled",
|
||||||
|
"description": "Enable or disable Detection Sensor Module"
|
||||||
|
},
|
||||||
|
"minimumBroadcastSecs": {
|
||||||
|
"label": "Minimum broadcast (seconds)",
|
||||||
|
"description": "Minimum interval between detection broadcasts"
|
||||||
|
},
|
||||||
|
"stateBroadcastSecs": {
|
||||||
|
"label": "State broadcast (seconds)",
|
||||||
|
"description": "How often to broadcast detection state"
|
||||||
|
},
|
||||||
|
"sendBell": {
|
||||||
|
"label": "Send bell with alert message",
|
||||||
|
"description": "Send a bell character with the alert"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Friendly name",
|
||||||
|
"description": "Friendly name for the detected event"
|
||||||
|
},
|
||||||
|
"monitorPin": {
|
||||||
|
"label": "GPIO pin to monitor",
|
||||||
|
"description": "GPIO pin to monitor for detection"
|
||||||
|
},
|
||||||
|
"detectionTriggerType": {
|
||||||
|
"label": "Detection trigger type",
|
||||||
|
"description": "Trigger type for detection"
|
||||||
|
},
|
||||||
|
"usePullup": {
|
||||||
|
"label": "Use INPUT_PULLUP mode",
|
||||||
|
"description": "Enable internal pull-up on the monitor pin"
|
||||||
|
},
|
||||||
|
"detectionSensorConfig": {
|
||||||
|
"label": "Detection Sensor Config",
|
||||||
|
"description": "Detection Sensor configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externalNotification": {
|
||||||
|
"title": "External Notification",
|
||||||
|
"description": "Configure the external notification module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "External notification enabled",
|
||||||
|
"description": "Enable External Notification"
|
||||||
|
},
|
||||||
|
"outputMs": {
|
||||||
|
"label": "Output duration (milliseconds)",
|
||||||
|
"description": "Output MS"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"label": "Output LED (GPIO)",
|
||||||
|
"description": "Output"
|
||||||
|
},
|
||||||
|
"outputVibra": {
|
||||||
|
"label": "Output vibra (GPIO)",
|
||||||
|
"description": "Output Vibrate"
|
||||||
|
},
|
||||||
|
"outputBuzzer": {
|
||||||
|
"label": "Output buzzer (GPIO)",
|
||||||
|
"description": "Output Buzzer"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"label": "Output LED active high",
|
||||||
|
"description": "Active"
|
||||||
|
},
|
||||||
|
"alertMessage": {
|
||||||
|
"label": "Alert message LED",
|
||||||
|
"description": "Alert Message"
|
||||||
|
},
|
||||||
|
"alertMessageVibra": {
|
||||||
|
"label": "Alert message vibra",
|
||||||
|
"description": "Alert Message Vibrate"
|
||||||
|
},
|
||||||
|
"alertMessageBuzzer": {
|
||||||
|
"label": "Alert message buzzer",
|
||||||
|
"description": "Alert Message Buzzer"
|
||||||
|
},
|
||||||
|
"alertBell": {
|
||||||
|
"label": "Alert bell LED",
|
||||||
|
"description": "Should an alert be triggered when receiving an incoming bell?"
|
||||||
|
},
|
||||||
|
"alertBellVibra": {
|
||||||
|
"label": "Alert bell vibra",
|
||||||
|
"description": "Alert Bell Vibrate"
|
||||||
|
},
|
||||||
|
"alertBellBuzzer": {
|
||||||
|
"label": "Alert bell buzzer",
|
||||||
|
"description": "Alert Bell Buzzer"
|
||||||
|
},
|
||||||
|
"usePwm": {
|
||||||
|
"label": "Use PWM buzzer",
|
||||||
|
"description": "Use PWM"
|
||||||
|
},
|
||||||
|
"nagTimeout": {
|
||||||
|
"label": "Nag timeout (seconds)",
|
||||||
|
"description": "Nag Timeout"
|
||||||
|
},
|
||||||
|
"useI2sAsBuzzer": {
|
||||||
|
"label": "Use I2S as buzzer",
|
||||||
|
"description": "Use I2S output as buzzer driver"
|
||||||
|
},
|
||||||
|
"externalNotificationConfig": {
|
||||||
|
"label": "External Notification Config",
|
||||||
|
"description": "External notification configuration"
|
||||||
|
},
|
||||||
|
"notificationsOnMessage": {
|
||||||
|
"label": "Notifications on message receipt",
|
||||||
|
"description": "Alerts on incoming text messages"
|
||||||
|
},
|
||||||
|
"notificationsOnAlert": {
|
||||||
|
"label": "Notifications on alert/bell receipt",
|
||||||
|
"description": "Alerts on incoming bell/alert packets"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"label": "Advanced",
|
||||||
|
"description": "Pin assignments and timing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mqtt": {
|
||||||
|
"title": "MQTT",
|
||||||
|
"description": "MQTT module configuration",
|
||||||
|
"enabled": {
|
||||||
|
"label": "MQTT enabled",
|
||||||
|
"description": "Enable or disable MQTT"
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"label": "Address",
|
||||||
|
"description": "MQTT server address to use for default/custom servers"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"label": "Username",
|
||||||
|
"description": "MQTT username to use for default/custom servers"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Password",
|
||||||
|
"description": "MQTT password to use for default/custom servers"
|
||||||
|
},
|
||||||
|
"encryptionEnabled": {
|
||||||
|
"label": "Encryption enabled",
|
||||||
|
"description": "Enable or disable MQTT encryption. Note: All messages are sent to the MQTT broker unencrypted if this option is not enabled, even when your uplink channels have encryption keys set. This includes position data."
|
||||||
|
},
|
||||||
|
"jsonEnabled": {
|
||||||
|
"label": "JSON output enabled",
|
||||||
|
"description": "Whether to send/consume JSON packets on MQTT"
|
||||||
|
},
|
||||||
|
"tlsEnabled": {
|
||||||
|
"label": "TLS enabled",
|
||||||
|
"description": "Enable or disable TLS"
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"label": "Root topic",
|
||||||
|
"description": "MQTT root topic to use for default/custom servers"
|
||||||
|
},
|
||||||
|
"proxyToClientEnabled": {
|
||||||
|
"label": "Proxy to client enabled",
|
||||||
|
"description": "Utilizes the network connection to proxy MQTT messages to the client."
|
||||||
|
},
|
||||||
|
"mapReportingEnabled": {
|
||||||
|
"label": "Map reporting",
|
||||||
|
"description": "Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, long and short name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name."
|
||||||
|
},
|
||||||
|
"mapReportSettings": {
|
||||||
|
"publishIntervalSecs": {
|
||||||
|
"label": "Map reporting interval (seconds)",
|
||||||
|
"description": "Interval in seconds to publish map reports"
|
||||||
|
},
|
||||||
|
"positionPrecision": {
|
||||||
|
"label": "Approximate location",
|
||||||
|
"description": "Position shared will be accurate within this distance",
|
||||||
|
"options": {
|
||||||
|
"metric_km23": "Within 23 km",
|
||||||
|
"metric_km12": "Within 12 km",
|
||||||
|
"metric_km5_8": "Within 5.8 km",
|
||||||
|
"metric_km2_9": "Within 2.9 km",
|
||||||
|
"metric_km1_5": "Within 1.5 km",
|
||||||
|
"metric_m700": "Within 700 m",
|
||||||
|
"metric_m350": "Within 350 m",
|
||||||
|
"metric_m200": "Within 200 m",
|
||||||
|
"metric_m90": "Within 90 m",
|
||||||
|
"metric_m50": "Within 50 m",
|
||||||
|
"imperial_mi15": "Within 15 miles",
|
||||||
|
"imperial_mi7_3": "Within 7.3 miles",
|
||||||
|
"imperial_mi3_6": "Within 3.6 miles",
|
||||||
|
"imperial_mi1_8": "Within 1.8 miles",
|
||||||
|
"imperial_mi0_9": "Within 0.9 miles",
|
||||||
|
"imperial_mi0_5": "Within 0.5 miles",
|
||||||
|
"imperial_mi0_2": "Within 0.2 miles",
|
||||||
|
"imperial_ft600": "Within 600 feet",
|
||||||
|
"imperial_ft300": "Within 300 feet",
|
||||||
|
"imperial_ft150": "Within 150 feet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shouldReportLocation": {
|
||||||
|
"label": "I agree.",
|
||||||
|
"description": "I have read and understand the above. I voluntarily consent to the unencrypted transmission of my node data via MQTT.",
|
||||||
|
"consentHeader": "Consent to Share Unencrypted Node Data via MQTT",
|
||||||
|
"consentText": "By enabling this feature, you acknowledge and expressly consent to the transmission of your device's real-time geographic location over the MQTT protocol without encryption. This location data may be used for purposes such as live map reporting, device tracking, and related telemetry functions."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mqttConfigCard": {
|
||||||
|
"label": "MQTT Config",
|
||||||
|
"description": "MQTT broker connection"
|
||||||
|
},
|
||||||
|
"mapReportingCard": {
|
||||||
|
"label": "Map reporting",
|
||||||
|
"description": "Periodic unencrypted map report"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"neighborInfo": {
|
||||||
|
"title": "Neighbor Info",
|
||||||
|
"description": "Settings for the Neighbor Info module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "Neighbor Info enabled",
|
||||||
|
"description": "Enable or disable Neighbor Info Module"
|
||||||
|
},
|
||||||
|
"updateInterval": {
|
||||||
|
"label": "Update interval (seconds)",
|
||||||
|
"description": "Interval in seconds of how often we should try to send our Neighbor Info to the mesh"
|
||||||
|
},
|
||||||
|
"neighborInfoConfig": {
|
||||||
|
"label": "Neighbor Info Config",
|
||||||
|
"description": "Neighbor Info configuration"
|
||||||
|
},
|
||||||
|
"transmitOverLora": {
|
||||||
|
"label": "Transmit over LoRa",
|
||||||
|
"description": "Whether NeighborInfo should be transmitted over LoRa. Not available on a channel with default key and name."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paxcounter": {
|
||||||
|
"title": "Paxcounter",
|
||||||
|
"description": "Settings for the Paxcounter module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "Paxcounter enabled",
|
||||||
|
"description": "Enable Paxcounter"
|
||||||
|
},
|
||||||
|
"paxcounterUpdateInterval": {
|
||||||
|
"label": "Update interval (seconds)",
|
||||||
|
"description": "How long to wait between sending paxcounter packets"
|
||||||
|
},
|
||||||
|
"wifiThreshold": {
|
||||||
|
"label": "WiFi RSSI threshold (defaults to -80)",
|
||||||
|
"description": "At what WiFi RSSI level should the counter increase. Defaults to -80."
|
||||||
|
},
|
||||||
|
"bleThreshold": {
|
||||||
|
"label": "BLE RSSI threshold (defaults to -80)",
|
||||||
|
"description": "At what BLE RSSI level should the counter increase. Defaults to -80."
|
||||||
|
},
|
||||||
|
"paxcounterConfig": {
|
||||||
|
"label": "Paxcounter Config",
|
||||||
|
"description": "Paxcounter configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rangeTest": {
|
||||||
|
"title": "Range Test",
|
||||||
|
"description": "Settings for the Range Test module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "Range test enabled",
|
||||||
|
"description": "Enable Range Test"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"label": "Sender message interval (seconds)",
|
||||||
|
"description": "How long to wait between sending test packets"
|
||||||
|
},
|
||||||
|
"save": {
|
||||||
|
"label": "Save .CSV in storage (ESP32 only)",
|
||||||
|
"description": "ESP32 Only"
|
||||||
|
},
|
||||||
|
"rangeTestConfig": {
|
||||||
|
"label": "Range Test Config",
|
||||||
|
"description": "Range Test configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serial": {
|
||||||
|
"title": "Serial",
|
||||||
|
"description": "Settings for the Serial module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "Serial enabled",
|
||||||
|
"description": "Enable Serial output"
|
||||||
|
},
|
||||||
|
"echo": {
|
||||||
|
"label": "Echo enabled",
|
||||||
|
"description": "Any packets you send will be echoed back to your device"
|
||||||
|
},
|
||||||
|
"rxd": {
|
||||||
|
"label": "RX",
|
||||||
|
"description": "Set the GPIO pin to the RXD pin you have set up."
|
||||||
|
},
|
||||||
|
"txd": {
|
||||||
|
"label": "TX",
|
||||||
|
"description": "Set the GPIO pin to the TXD pin you have set up."
|
||||||
|
},
|
||||||
|
"baud": {
|
||||||
|
"label": "Serial baud rate",
|
||||||
|
"description": "The serial baud rate"
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"label": "Timeout",
|
||||||
|
"description": "Seconds to wait before we consider your packet as 'done'"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"label": "Serial mode",
|
||||||
|
"description": "Select Mode"
|
||||||
|
},
|
||||||
|
"overrideConsoleSerialPort": {
|
||||||
|
"label": "Override console serial port",
|
||||||
|
"description": "If you have a serial port connected to the console, this will override it."
|
||||||
|
},
|
||||||
|
"serialConfig": {
|
||||||
|
"label": "Serial Config",
|
||||||
|
"description": "Serial module configuration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeForward": {
|
||||||
|
"title": "Store & Forward",
|
||||||
|
"description": "Settings for the Store & Forward module",
|
||||||
|
"enabled": {
|
||||||
|
"label": "Store & Forward enabled",
|
||||||
|
"description": "Enable Store & Forward"
|
||||||
|
},
|
||||||
|
"heartbeat": {
|
||||||
|
"label": "Heartbeat",
|
||||||
|
"description": "Enable Store & Forward heartbeat"
|
||||||
|
},
|
||||||
|
"records": {
|
||||||
|
"label": "Number of records",
|
||||||
|
"description": "Number of records to store"
|
||||||
|
},
|
||||||
|
"historyReturnMax": {
|
||||||
|
"label": "History return max",
|
||||||
|
"description": "Max number of records to return"
|
||||||
|
},
|
||||||
|
"historyReturnWindow": {
|
||||||
|
"label": "History return window",
|
||||||
|
"description": "Return records from this time window (minutes)"
|
||||||
|
},
|
||||||
|
"storeForwardConfig": {
|
||||||
|
"label": "Store & Forward Config",
|
||||||
|
"description": "Store & Forward configuration"
|
||||||
|
},
|
||||||
|
"isServer": {
|
||||||
|
"label": "Server",
|
||||||
|
"description": "Run as a Store & Forward server"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"telemetry": {
|
||||||
|
"title": "Telemetry",
|
||||||
|
"description": "Settings for the Telemetry module",
|
||||||
|
"deviceUpdateInterval": {
|
||||||
|
"label": "Device metrics update interval",
|
||||||
|
"description": "Device metrics update interval (seconds)"
|
||||||
|
},
|
||||||
|
"environmentUpdateInterval": {
|
||||||
|
"label": "Environment metrics update interval",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"environmentMeasurementEnabled": {
|
||||||
|
"label": "Environment metrics module enabled",
|
||||||
|
"description": "Enable the Environment Telemetry"
|
||||||
|
},
|
||||||
|
"environmentScreenEnabled": {
|
||||||
|
"label": "Environment metrics on-screen enabled",
|
||||||
|
"description": "Show the Telemetry Module on the OLED"
|
||||||
|
},
|
||||||
|
"environmentDisplayFahrenheit": {
|
||||||
|
"label": "Environment metrics use Fahrenheit",
|
||||||
|
"description": "Display temp in Fahrenheit"
|
||||||
|
},
|
||||||
|
"airQualityEnabled": {
|
||||||
|
"label": "Air quality metrics module enabled",
|
||||||
|
"description": "Enable the Air Quality Telemetry"
|
||||||
|
},
|
||||||
|
"airQualityInterval": {
|
||||||
|
"label": "Air quality metrics update interval",
|
||||||
|
"description": "How often to send Air Quality data over the mesh"
|
||||||
|
},
|
||||||
|
"powerMeasurementEnabled": {
|
||||||
|
"label": "Power metrics module enabled",
|
||||||
|
"description": "Enable the Power Measurement Telemetry"
|
||||||
|
},
|
||||||
|
"powerUpdateInterval": {
|
||||||
|
"label": "Power metrics update interval",
|
||||||
|
"description": "How often to send Power data over the mesh"
|
||||||
|
},
|
||||||
|
"powerScreenEnabled": {
|
||||||
|
"label": "Power metrics on-screen enabled",
|
||||||
|
"description": "Show power metrics on the device screen"
|
||||||
|
},
|
||||||
|
"telemetryConfig": {
|
||||||
|
"label": "Telemetry Config",
|
||||||
|
"description": "Telemetry configuration"
|
||||||
|
},
|
||||||
|
"deviceTelemetryEnabled": {
|
||||||
|
"label": "Send Device Telemetry",
|
||||||
|
"description": "Enable/Disable the device telemetry module to send metrics to the mesh."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"nodeDetail": {
|
||||||
|
"publicKeyEnabled": {
|
||||||
|
"label": "Public Key Enabled"
|
||||||
|
},
|
||||||
|
"noPublicKey": {
|
||||||
|
"label": "No Public Key"
|
||||||
|
},
|
||||||
|
"directMessage": {
|
||||||
|
"label": "Direct Message {{shortName}}"
|
||||||
|
},
|
||||||
|
"favorite": {
|
||||||
|
"label": "Favorite",
|
||||||
|
"tooltip": "Add or remove this node from your favorites"
|
||||||
|
},
|
||||||
|
"notFavorite": {
|
||||||
|
"label": "Not a Favorite"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"label": "Error",
|
||||||
|
"text": "An error occurred while fetching node details. Please try again later."
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"heard": "Heard",
|
||||||
|
"mqtt": "MQTT"
|
||||||
|
},
|
||||||
|
"elevation": {
|
||||||
|
"label": "Elevation"
|
||||||
|
},
|
||||||
|
"channelUtil": {
|
||||||
|
"label": "Channel Util"
|
||||||
|
},
|
||||||
|
"airtimeUtil": {
|
||||||
|
"label": "Airtime Util"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nodesTable": {
|
||||||
|
"headings": {
|
||||||
|
"longName": "Long Name",
|
||||||
|
"connection": "Connection",
|
||||||
|
"lastHeard": "Last Heard",
|
||||||
|
"encryption": "Encryption",
|
||||||
|
"model": "Model",
|
||||||
|
"macAddress": "MAC Address"
|
||||||
|
},
|
||||||
|
"connectionStatus": {
|
||||||
|
"direct": "Direct",
|
||||||
|
"away": "away",
|
||||||
|
"viaMqtt": ", via MQTT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"added": "Added",
|
||||||
|
"removed": "Removed",
|
||||||
|
"ignoreNode": "Ignore Node",
|
||||||
|
"unignoreNode": "Unignore Node",
|
||||||
|
"requestPosition": "Request Position"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
{
|
||||||
|
"navigation": {
|
||||||
|
"title": "Navigation",
|
||||||
|
"messages": "Messages",
|
||||||
|
"map": "Map",
|
||||||
|
"settings": "Settings",
|
||||||
|
"channels": "Channels",
|
||||||
|
"radioConfig": "Radio Config",
|
||||||
|
"deviceConfig": "Device Config",
|
||||||
|
"moduleConfig": "Module Config",
|
||||||
|
"manageConnections": "Manage Connections",
|
||||||
|
"nodes": "Nodes"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"title": "Meshtastic",
|
||||||
|
"logo": "Meshtastic Logo"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"collapseToggle": {
|
||||||
|
"button": {
|
||||||
|
"open": "Open sidebar",
|
||||||
|
"close": "Close sidebar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deviceInfo": {
|
||||||
|
"volts": "{{voltage}} volts",
|
||||||
|
"firmware": {
|
||||||
|
"title": "Firmware",
|
||||||
|
"version": "v{{version}}",
|
||||||
|
"buildDate": "Build date: {{date}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"batteryStatus": {
|
||||||
|
"charging": "{{level}}% charging",
|
||||||
|
"pluggedIn": "Plugged in",
|
||||||
|
"title": "Battery"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"nodes": "Search nodes...",
|
||||||
|
"channels": "Search channels...",
|
||||||
|
"commandPalette": "Search commands..."
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"positionRequestSent": {
|
||||||
|
"title": "Position request sent."
|
||||||
|
},
|
||||||
|
"requestingPosition": {
|
||||||
|
"title": "Requesting position, please wait..."
|
||||||
|
},
|
||||||
|
"sendingTraceroute": {
|
||||||
|
"title": "Sending Traceroute, please wait..."
|
||||||
|
},
|
||||||
|
"tracerouteSent": {
|
||||||
|
"title": "Traceroute sent."
|
||||||
|
},
|
||||||
|
"savedChannel": {
|
||||||
|
"title": "Saved Channel: {{channelName}}"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"pkiEncryption": {
|
||||||
|
"title": "Chat is using PKI encryption."
|
||||||
|
},
|
||||||
|
"pskEncryption": {
|
||||||
|
"title": "Chat is using PSK encryption."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configSaveError": {
|
||||||
|
"title": "Error Saving Config",
|
||||||
|
"description": "An error occurred while saving the configuration."
|
||||||
|
},
|
||||||
|
"validationError": {
|
||||||
|
"title": "Config Errors Exist",
|
||||||
|
"description": "Please fix the configuration errors before saving."
|
||||||
|
},
|
||||||
|
"saveSuccess": {
|
||||||
|
"title": "Saving Config",
|
||||||
|
"description": "The configuration change {{case}} has been saved."
|
||||||
|
},
|
||||||
|
"saveAllSuccess": {
|
||||||
|
"title": "Saved",
|
||||||
|
"description": "All configuration changes have been saved."
|
||||||
|
},
|
||||||
|
"favoriteNode": {
|
||||||
|
"title": "{{action}} {{nodeName}} {{direction}} favorites.",
|
||||||
|
"action": {
|
||||||
|
"added": "Added",
|
||||||
|
"removed": "Removed",
|
||||||
|
"to": "to",
|
||||||
|
"from": "from"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignoreNode": {
|
||||||
|
"title": "{{action}} {{nodeName}} {{direction}} ignore list",
|
||||||
|
"action": {
|
||||||
|
"added": "Added",
|
||||||
|
"removed": "Removed",
|
||||||
|
"to": "to",
|
||||||
|
"from": "from"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"copied": {
|
||||||
|
"label": "Copied!"
|
||||||
|
},
|
||||||
|
"copyToClipboard": {
|
||||||
|
"label": "Copy to clipboard"
|
||||||
|
},
|
||||||
|
"hidePassword": {
|
||||||
|
"label": "Hide password"
|
||||||
|
},
|
||||||
|
"showPassword": {
|
||||||
|
"label": "Show password"
|
||||||
|
},
|
||||||
|
"deliveryStatus": {
|
||||||
|
"delivered": "Delivered",
|
||||||
|
"failed": "Delivery Failed",
|
||||||
|
"waiting": "Waiting",
|
||||||
|
"unknown": "Unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"label": "General"
|
||||||
|
},
|
||||||
|
"hardware": {
|
||||||
|
"label": "Hardware"
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"label": "Metrics"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"label": "Role"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"label": "Filter"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"label": "Advanced"
|
||||||
|
},
|
||||||
|
"clearInput": {
|
||||||
|
"label": "Clear input"
|
||||||
|
},
|
||||||
|
"resetFilters": {
|
||||||
|
"label": "Reset Filters"
|
||||||
|
},
|
||||||
|
"nodeName": {
|
||||||
|
"label": "Node name/number",
|
||||||
|
"placeholder": "Meshtastic 1234"
|
||||||
|
},
|
||||||
|
"airtimeUtilization": {
|
||||||
|
"label": "Airtime Utilization (%)",
|
||||||
|
"short": "Airtime Util. (%)"
|
||||||
|
},
|
||||||
|
"batteryLevel": {
|
||||||
|
"label": "Battery level (%)",
|
||||||
|
"labelText": "Battery level (%): {{value}}"
|
||||||
|
},
|
||||||
|
"batteryVoltage": {
|
||||||
|
"label": "Battery voltage (V)",
|
||||||
|
"title": "Voltage"
|
||||||
|
},
|
||||||
|
"channelUtilization": {
|
||||||
|
"label": "Channel Utilization (%)",
|
||||||
|
"short": "Channel Util. (%)"
|
||||||
|
},
|
||||||
|
"hops": {
|
||||||
|
"direct": "Direct",
|
||||||
|
"label": "Number of hops",
|
||||||
|
"text": "Number of hops: {{value}}"
|
||||||
|
},
|
||||||
|
"lastHeard": {
|
||||||
|
"label": "Last heard",
|
||||||
|
"labelText": "Last heard: {{value}}",
|
||||||
|
"nowLabel": "Now"
|
||||||
|
},
|
||||||
|
"snr": {
|
||||||
|
"label": "SNR (db)"
|
||||||
|
},
|
||||||
|
"favorites": {
|
||||||
|
"label": "Favorites"
|
||||||
|
},
|
||||||
|
"hide": {
|
||||||
|
"label": "Hide"
|
||||||
|
},
|
||||||
|
"showOnly": {
|
||||||
|
"label": "Show Only"
|
||||||
|
},
|
||||||
|
"viaMqtt": {
|
||||||
|
"label": "Connected via MQTT"
|
||||||
|
},
|
||||||
|
"hopsUnknown": {
|
||||||
|
"label": "Unknown number of hops"
|
||||||
|
},
|
||||||
|
"showUnheard": {
|
||||||
|
"label": "Never heard"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"label": "Language",
|
||||||
|
"changeLanguage": "Change Language"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"dark": "Dark",
|
||||||
|
"light": "Light",
|
||||||
|
"system": "Automatic",
|
||||||
|
"changeTheme": "Change Color Scheme"
|
||||||
|
},
|
||||||
|
"errorPage": {
|
||||||
|
"title": "This is a little embarrassing...",
|
||||||
|
"description1": "We are really sorry but an error occurred in the web client that caused it to crash. <br /> This is not supposed to happen, and we are working hard to fix it.",
|
||||||
|
"description2": "The best way to prevent this from happening again to you or anyone else is to report the issue to us.",
|
||||||
|
"reportInstructions": "Please include the following information in your report:",
|
||||||
|
"reportSteps": {
|
||||||
|
"step1": "What you were doing when the error occurred",
|
||||||
|
"step2": "What you expected to happen",
|
||||||
|
"step3": "What actually happened",
|
||||||
|
"step4": "Any other relevant information"
|
||||||
|
},
|
||||||
|
"reportLink": "You can report the issue to our <0>GitHub</0>",
|
||||||
|
"connectionsLink": "Return to the <0>connections</0>",
|
||||||
|
"detailsSummary": "Error Details",
|
||||||
|
"errorMessageLabel": "Error message:",
|
||||||
|
"stackTraceLabel": "Stack trace:",
|
||||||
|
"fallbackError": "{{error}}"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"text": "Powered by <0>▲ Vercel</0> | Meshtastic® is a registered trademark of Meshtastic LLC. | <1>Legal Information</1>",
|
||||||
|
"commitSha": "Commit SHA: {{sha}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"sectionLabel": "Канали",
|
||||||
|
"channelName": "Канал: {{channelName}}",
|
||||||
|
"broadcastLabel": "Първичен",
|
||||||
|
"channelIndex": "К {{index}}",
|
||||||
|
"import": "Импортиране",
|
||||||
|
"export": "Експортиране"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"pskInvalid": "Моля, въведете валиден {{bits}} bit PSK."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"label": "Настройки на канала",
|
||||||
|
"description": "Крипто, MQTT и други настройки"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"label": "Роля",
|
||||||
|
"description": "Телеметрията на устройството се изпраща през ПЪРВИЧЕН. Разрешен е само един ПЪРВИЧЕН.",
|
||||||
|
"options": {
|
||||||
|
"primary": "ПЪРВИЧЕН",
|
||||||
|
"disabled": "ДЕЗАКТИВИРАН",
|
||||||
|
"secondary": "ВТОРИЧЕН"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"psk": {
|
||||||
|
"label": "Предварително споделен ключ",
|
||||||
|
"description": "Поддържани дължини на PSK: 256-битова, 128-битова, 8-битова, празна (0-битова)",
|
||||||
|
"generate": "Генериране"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Име",
|
||||||
|
"description": "Уникално име за канала <12 байта, оставете празно за подразбиране"
|
||||||
|
},
|
||||||
|
"uplinkEnabled": {
|
||||||
|
"label": "Uplink е активиран",
|
||||||
|
"description": "Изпращане на съобщения от локалната mesh към MQTT"
|
||||||
|
},
|
||||||
|
"downlinkEnabled": {
|
||||||
|
"label": "Downlink е активиран",
|
||||||
|
"description": "Изпращане на съобщения от MQTT към локалната mesh"
|
||||||
|
},
|
||||||
|
"positionPrecision": {
|
||||||
|
"label": "Местоположение",
|
||||||
|
"description": "Точността на местоположението, което да се споделя с канала. Може да бъде дезактивирано.",
|
||||||
|
"options": {
|
||||||
|
"none": "Да не се споделя местоположението",
|
||||||
|
"precise": "Точно местоположение",
|
||||||
|
"metric_km23": "В рамките на 23 километра",
|
||||||
|
"metric_km12": "В рамките на 12 километра",
|
||||||
|
"metric_km5_8": "В рамките на 5.8 километра",
|
||||||
|
"metric_km2_9": "В рамките на 2.9 километра",
|
||||||
|
"metric_km1_5": "В рамките на 1.5 километра",
|
||||||
|
"metric_m700": "В рамките на 700 метра",
|
||||||
|
"metric_m350": "В рамките на 350 метра",
|
||||||
|
"metric_m200": "В рамките на 200 метра",
|
||||||
|
"metric_m90": "В рамките на 90 метра",
|
||||||
|
"metric_m50": "В рамките на 50 метра",
|
||||||
|
"imperial_mi15": "В рамките на 15 мили",
|
||||||
|
"imperial_mi7_3": "В рамките на 7.3 мили",
|
||||||
|
"imperial_mi3_6": "В рамките на 3.6 мили",
|
||||||
|
"imperial_mi1_8": "В рамките на 1.8 мили",
|
||||||
|
"imperial_mi0_9": "В рамките на 0.9 мили",
|
||||||
|
"imperial_mi0_5": "В рамките на 0.5 мили",
|
||||||
|
"imperial_mi0_2": "В рамките на 0.2 мили",
|
||||||
|
"imperial_ft600": "В рамките на 600 фута",
|
||||||
|
"imperial_ft300": "В рамките на 300 фута",
|
||||||
|
"imperial_ft150": "В рамките на 150 фута"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
"messages": "Съобщения",
|
"messages": "Съобщения",
|
||||||
"map": "Карта",
|
"map": "Карта",
|
||||||
"config": "Конфигурация",
|
"config": "Конфигурация",
|
||||||
"channels": "Канали",
|
|
||||||
"nodes": "Възли"
|
"nodes": "Възли"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -33,9 +32,9 @@
|
|||||||
"qrGenerator": "Генератор",
|
"qrGenerator": "Генератор",
|
||||||
"qrImport": "Импортиране",
|
"qrImport": "Импортиране",
|
||||||
"scheduleShutdown": "Планирано изключване",
|
"scheduleShutdown": "Планирано изключване",
|
||||||
"scheduleReboot": "Планирано рестартиране",
|
"scheduleReboot": "Рестартиране на устройството",
|
||||||
"rebootToOtaMode": "Рестартиране в режим OTA",
|
|
||||||
"resetNodeDb": "Нулиране на базата данни с възли",
|
"resetNodeDb": "Нулиране на базата данни с възли",
|
||||||
|
"dfuMode": "Влизане в DFU режим",
|
||||||
"factoryResetDevice": "Фабрично нулиране на устройството",
|
"factoryResetDevice": "Фабрично нулиране на устройството",
|
||||||
"factoryResetConfig": "Фабрично нулиране на конфигурацията",
|
"factoryResetConfig": "Фабрично нулиране на конфигурацията",
|
||||||
"disconnect": "Прекъсване"
|
"disconnect": "Прекъсване"
|
||||||
@@ -45,7 +44,8 @@
|
|||||||
"label": "Отстраняване на грешки",
|
"label": "Отстраняване на грешки",
|
||||||
"command": {
|
"command": {
|
||||||
"reconfigure": "Преконфигуриране",
|
"reconfigure": "Преконфигуриране",
|
||||||
"clearAllStoredMessages": "Изчистване на всички съхранени съобщения"
|
"clearAllStoredMessages": "Изчистване на всички съхранени съобщения",
|
||||||
|
"clearAllStores": "Clear All Local Storage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
{
|
||||||
|
"button": {
|
||||||
|
"apply": "Приложи",
|
||||||
|
"addConnection": "Добавяне на връзка",
|
||||||
|
"saveConnection": "Запазване на връзката",
|
||||||
|
"backupKey": "Резервно копие на ключа",
|
||||||
|
"cancel": "Отказ",
|
||||||
|
"connect": "Свързване",
|
||||||
|
"clearMessages": "Изчистване на съобщенията",
|
||||||
|
"close": "Затвори",
|
||||||
|
"confirm": "Потвърждаване",
|
||||||
|
"delete": "Изтриване",
|
||||||
|
"dismiss": "Отхвърляне",
|
||||||
|
"download": "Изтегляне",
|
||||||
|
"disconnect": "Прекъсване на връзката",
|
||||||
|
"export": "Експортиране",
|
||||||
|
"generate": "Генериране",
|
||||||
|
"regenerate": "Регенериране",
|
||||||
|
"import": "Импортиране",
|
||||||
|
"message": "Съобщение",
|
||||||
|
"now": "Сега",
|
||||||
|
"ok": "Добре",
|
||||||
|
"print": "Отпечатване",
|
||||||
|
"remove": "Изтрий",
|
||||||
|
"requestNewKeys": "Заявка за нови ключове",
|
||||||
|
"requestPosition": "Request Position",
|
||||||
|
"reset": "Нулиране",
|
||||||
|
"retry": "Retry",
|
||||||
|
"save": "Запис",
|
||||||
|
"setDefault": "Задаване по подразбиране",
|
||||||
|
"unsetDefault": "Unset default",
|
||||||
|
"scanQr": "Сканиране на QR кода",
|
||||||
|
"traceRoute": "Trace Route",
|
||||||
|
"submit": "Изпращане"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"title": "Meshtastic",
|
||||||
|
"fullTitle": "Meshtastic Web клиент"
|
||||||
|
},
|
||||||
|
"loading": "Зареждане...",
|
||||||
|
"unit": {
|
||||||
|
"cps": "CPS",
|
||||||
|
"dbm": "dBm",
|
||||||
|
"hertz": "Hz",
|
||||||
|
"hop": {
|
||||||
|
"one": "Хоп",
|
||||||
|
"plural": "Хопа"
|
||||||
|
},
|
||||||
|
"hopsAway": {
|
||||||
|
"one": "{{count}} hop away",
|
||||||
|
"plural": "{{count}} hops away",
|
||||||
|
"unknown": "Unknown hops away"
|
||||||
|
},
|
||||||
|
"megahertz": "MHz",
|
||||||
|
"kilohertz": "kHz",
|
||||||
|
"raw": "raw",
|
||||||
|
"meter": {
|
||||||
|
"one": "Метър",
|
||||||
|
"plural": "Метри",
|
||||||
|
"suffix": "m"
|
||||||
|
},
|
||||||
|
"kilometer": {
|
||||||
|
"one": "Километър",
|
||||||
|
"plural": "Километри",
|
||||||
|
"suffix": "км"
|
||||||
|
},
|
||||||
|
"minute": {
|
||||||
|
"one": "Минута",
|
||||||
|
"plural": "Минути"
|
||||||
|
},
|
||||||
|
"hour": {
|
||||||
|
"one": "Час",
|
||||||
|
"plural": "Часа"
|
||||||
|
},
|
||||||
|
"millisecond": {
|
||||||
|
"one": "Милисекунда",
|
||||||
|
"plural": "Милисекунди",
|
||||||
|
"suffix": "ms"
|
||||||
|
},
|
||||||
|
"second": {
|
||||||
|
"one": "Секунда",
|
||||||
|
"plural": "Секунди"
|
||||||
|
},
|
||||||
|
"day": {
|
||||||
|
"one": "Ден",
|
||||||
|
"plural": "Дни",
|
||||||
|
"today": "Днес",
|
||||||
|
"yesterday": "Вчера"
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"one": "Месец",
|
||||||
|
"plural": "Месеца"
|
||||||
|
},
|
||||||
|
"year": {
|
||||||
|
"one": "Година",
|
||||||
|
"plural": "Години"
|
||||||
|
},
|
||||||
|
"snr": "SNR",
|
||||||
|
"volt": {
|
||||||
|
"one": "Волт",
|
||||||
|
"plural": "Волта",
|
||||||
|
"suffix": "V"
|
||||||
|
},
|
||||||
|
"record": {
|
||||||
|
"one": "Записи",
|
||||||
|
"plural": "Записи"
|
||||||
|
},
|
||||||
|
"degree": {
|
||||||
|
"one": "Градус",
|
||||||
|
"plural": "Градуса",
|
||||||
|
"suffix": "°"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"0bit": "Празен",
|
||||||
|
"8bit": "8 bit",
|
||||||
|
"128bit": "128 bit",
|
||||||
|
"256bit": "256 bit"
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
"longName": "Неизвестно",
|
||||||
|
"shortName": "НЕИЗВ.",
|
||||||
|
"notAvailable": "N/A",
|
||||||
|
"num": "??"
|
||||||
|
},
|
||||||
|
"nodeUnknownPrefix": "!",
|
||||||
|
"unset": "НЕЗАДАДЕН",
|
||||||
|
"fallbackName": "Meshtastic {{last4}}",
|
||||||
|
"node": "Възел",
|
||||||
|
"formValidation": {
|
||||||
|
"unsavedChanges": "Незапазени промени",
|
||||||
|
"tooBig": {
|
||||||
|
"string": "Твърде дълъг, очаква се по-малко или равно на {{maximum}} знака.",
|
||||||
|
"number": "Твърде голям, очаква се число по-малко или равно на {{maximum}}.",
|
||||||
|
"bytes": "Твърде голям, очаква се по-малък или равен на {{params.maximum}} байта."
|
||||||
|
},
|
||||||
|
"tooSmall": {
|
||||||
|
"string": "Твърде кратък, очаква се повече или равно на {{minimum}} знака.",
|
||||||
|
"number": "Твърде малък, очаква се число, по-голямо или равно на {{minimum}}."
|
||||||
|
},
|
||||||
|
"invalidFormat": {
|
||||||
|
"ipv4": "Невалиден формат, очаква се IPv4 адрес.",
|
||||||
|
"key": "Невалиден формат, очаква се предварително споделен ключ (PSK), кодиран с Base64."
|
||||||
|
},
|
||||||
|
"invalidType": {
|
||||||
|
"number": "Невалиден тип, очаква се число."
|
||||||
|
},
|
||||||
|
"pskLength": {
|
||||||
|
"0bit": "Ключът трябва да е празен.",
|
||||||
|
"8bit": "Ключът трябва да бъде 8-битов предварително споделен ключ (PSK).",
|
||||||
|
"128bit": "Ключът трябва да бъде 128-битов предварително споделен ключ (PSK).",
|
||||||
|
"256bit": "Ключът трябва да бъде 256-битов предварително споделен ключ (PSK)."
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"generic": "Това поле е задължително.",
|
||||||
|
"managed": "Изисква се поне един администраторски ключ, ако възелът е управляван.",
|
||||||
|
"key": "Ключът е задължителен."
|
||||||
|
},
|
||||||
|
"invalidOverrideFreq": {
|
||||||
|
"number": "Невалиден формат, очаквана е стойност в диапазона 410-930 MHz или 0 (използвайте стойността по подразбиране)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yes": "Да",
|
||||||
|
"no": "Не"
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"title": "Настройки",
|
||||||
|
"tabUser": "Потребител",
|
||||||
|
"tabChannels": "Канали",
|
||||||
|
"tabBluetooth": "Bluetooth",
|
||||||
|
"tabDevice": "Устройство",
|
||||||
|
"tabDisplay": "Дисплей",
|
||||||
|
"tabLora": "LoRa",
|
||||||
|
"tabNetwork": "Мрежа",
|
||||||
|
"tabPosition": "Позиция",
|
||||||
|
"tabPower": "Захранване",
|
||||||
|
"tabSecurity": "Сигурност"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"label": "Конфигурация"
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"title": "Настройки на устройството",
|
||||||
|
"description": "Настройки за устройството",
|
||||||
|
"buttonPin": {
|
||||||
|
"description": "Button pin override",
|
||||||
|
"label": "Пин за бутон"
|
||||||
|
},
|
||||||
|
"buzzerPin": {
|
||||||
|
"description": "Buzzer pin override",
|
||||||
|
"label": "Пин за зумер"
|
||||||
|
},
|
||||||
|
"disableTripleClick": {
|
||||||
|
"description": "Дезактивиране на трикратното щракване",
|
||||||
|
"label": "Дезактивиране на трикратното щракване"
|
||||||
|
},
|
||||||
|
"doubleTapAsButtonPress": {
|
||||||
|
"description": "Treat double tap as button press",
|
||||||
|
"label": "Double Tap as Button Press"
|
||||||
|
},
|
||||||
|
"ledHeartbeatDisabled": {
|
||||||
|
"description": "Дезактивиране на мигащия светодиод по подразбиране",
|
||||||
|
"label": "LED Heartbeat Disabled"
|
||||||
|
},
|
||||||
|
"nodeInfoBroadcastInterval": {
|
||||||
|
"description": "How often to broadcast node info",
|
||||||
|
"label": "Node Info Broadcast Interval"
|
||||||
|
},
|
||||||
|
"posixTimezone": {
|
||||||
|
"description": "The POSIX timezone string for the device",
|
||||||
|
"label": "POSIX часова зона"
|
||||||
|
},
|
||||||
|
"rebroadcastMode": {
|
||||||
|
"description": "How to handle rebroadcasting",
|
||||||
|
"label": "Режим на препредаване"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"description": "Каква роля изпълнява устройството в mesh",
|
||||||
|
"label": "Роля"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bluetooth": {
|
||||||
|
"title": "Настройки за Bluetooth",
|
||||||
|
"description": "Настройки за Bluetooth модула",
|
||||||
|
"note": "Забележка: Някои устройства (ESP32) не могат да използват едновременно Bluetooth и WiFi.",
|
||||||
|
"enabled": {
|
||||||
|
"description": "Активиране или дезактивиране на Bluetooth",
|
||||||
|
"label": "Активиран"
|
||||||
|
},
|
||||||
|
"pairingMode": {
|
||||||
|
"description": "Поведение при избор на ПИН.",
|
||||||
|
"label": "Режим на сдвояване"
|
||||||
|
},
|
||||||
|
"pin": {
|
||||||
|
"description": "Pin to use when pairing",
|
||||||
|
"label": "ПИН"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"description": "Настройки за дисплея на устройството",
|
||||||
|
"title": "Настройки на дисплея",
|
||||||
|
"headingBold": {
|
||||||
|
"description": "Bolden the heading text",
|
||||||
|
"label": "Удебелен заглавен шрифт"
|
||||||
|
},
|
||||||
|
"carouselDelay": {
|
||||||
|
"description": "Колко бързо да се превключва между прозорците",
|
||||||
|
"label": "Carousel Delay"
|
||||||
|
},
|
||||||
|
"compassNorthTop": {
|
||||||
|
"description": "Фиксиране на север към горната част на компаса",
|
||||||
|
"label": "Север на компаса отгоре"
|
||||||
|
},
|
||||||
|
"displayMode": {
|
||||||
|
"description": "Screen layout variant",
|
||||||
|
"label": "Display Mode"
|
||||||
|
},
|
||||||
|
"displayUnits": {
|
||||||
|
"description": "Показване на метрични или имперски мерни единици",
|
||||||
|
"label": "Display Units"
|
||||||
|
},
|
||||||
|
"flipScreen": {
|
||||||
|
"description": "Обръщане на дисплея на 180 градуса",
|
||||||
|
"label": "Обръщане на дисплея"
|
||||||
|
},
|
||||||
|
"gpsDisplayUnits": {
|
||||||
|
"description": "Coordinate display format",
|
||||||
|
"label": "GPS Display Units"
|
||||||
|
},
|
||||||
|
"oledType": {
|
||||||
|
"description": "Тип на OLED екрана, прикрепен към устройството",
|
||||||
|
"label": "Тип OLED"
|
||||||
|
},
|
||||||
|
"screenTimeout": {
|
||||||
|
"description": "Turn off the display after this long",
|
||||||
|
"label": "Screen Timeout"
|
||||||
|
},
|
||||||
|
"twelveHourClock": {
|
||||||
|
"description": "Използване на 12-часов формат на часовника",
|
||||||
|
"label": "12-часов часовник"
|
||||||
|
},
|
||||||
|
"wakeOnTapOrMotion": {
|
||||||
|
"description": "Събуждане на устройството при докосване или движение",
|
||||||
|
"label": "Събуждане при докосване или движение"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lora": {
|
||||||
|
"title": "Настройки на Mesh",
|
||||||
|
"description": "Настройки за LoRa mesh",
|
||||||
|
"bandwidth": {
|
||||||
|
"description": "Широчина на канала в kHz",
|
||||||
|
"label": "Широчина на честотната лента"
|
||||||
|
},
|
||||||
|
"boostedRxGain": {
|
||||||
|
"description": "Boosted RX gain",
|
||||||
|
"label": "Boosted RX Gain"
|
||||||
|
},
|
||||||
|
"codingRate": {
|
||||||
|
"description": "The denominator of the coding rate",
|
||||||
|
"label": "Coding Rate"
|
||||||
|
},
|
||||||
|
"frequencyOffset": {
|
||||||
|
"description": "Frequency offset to correct for crystal calibration errors",
|
||||||
|
"label": "Отместване на честотата"
|
||||||
|
},
|
||||||
|
"frequencySlot": {
|
||||||
|
"description": "Номер на LoRa честотен канал",
|
||||||
|
"label": "Честотен слот"
|
||||||
|
},
|
||||||
|
"hopLimit": {
|
||||||
|
"description": "Максимален брой хопове",
|
||||||
|
"label": "Лимит на хопове"
|
||||||
|
},
|
||||||
|
"ignoreMqtt": {
|
||||||
|
"description": "Да не се препращат MQTT съобщения през mesh",
|
||||||
|
"label": "Игнориране на MQTT"
|
||||||
|
},
|
||||||
|
"modemPreset": {
|
||||||
|
"description": "Използване на предварително настроен модем",
|
||||||
|
"label": "Предварително настроен модем"
|
||||||
|
},
|
||||||
|
"okToMqtt": {
|
||||||
|
"description": "When set to true, this configuration indicates that the user approves the packet to be uploaded to MQTT. If set to false, remote nodes are requested not to forward packets to MQTT",
|
||||||
|
"label": "OK to MQTT"
|
||||||
|
},
|
||||||
|
"overrideDutyCycle": {
|
||||||
|
"description": "Override Duty Cycle",
|
||||||
|
"label": "Override Duty Cycle"
|
||||||
|
},
|
||||||
|
"overrideFrequency": {
|
||||||
|
"description": "Override frequency",
|
||||||
|
"label": "Override Frequency"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"description": "Задаване на региона за вашия възел",
|
||||||
|
"label": "Регион"
|
||||||
|
},
|
||||||
|
"spreadingFactor": {
|
||||||
|
"description": "Indicates the number of chirps per symbol",
|
||||||
|
"label": "Spreading Factor"
|
||||||
|
},
|
||||||
|
"transmitEnabled": {
|
||||||
|
"description": "Активиране/дезактивиране на предаването (TX) от LoRa радиото",
|
||||||
|
"label": "Предаването е активирано"
|
||||||
|
},
|
||||||
|
"transmitPower": {
|
||||||
|
"description": "Максимална мощност на предаване",
|
||||||
|
"label": "Мощност на предаване"
|
||||||
|
},
|
||||||
|
"usePreset": {
|
||||||
|
"description": "Използване на една от предварително зададените настройки на модема",
|
||||||
|
"label": "Използване на предварително зададени настройки"
|
||||||
|
},
|
||||||
|
"meshSettings": {
|
||||||
|
"description": "Настройки за LoRa mesh",
|
||||||
|
"label": "Настройки на Mesh"
|
||||||
|
},
|
||||||
|
"waveformSettings": {
|
||||||
|
"description": "Settings for the LoRa waveform",
|
||||||
|
"label": "Waveform Settings"
|
||||||
|
},
|
||||||
|
"radioSettings": {
|
||||||
|
"label": "Настройки на радиото",
|
||||||
|
"description": "Настройки за LoRa радиото"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"title": "Конфигурация на WiFi",
|
||||||
|
"description": "Конфигурация на WiFi радиото",
|
||||||
|
"note": "Забележка: Някои устройства (ESP32) не могат да използват едновременно Bluetooth и WiFi.",
|
||||||
|
"addressMode": {
|
||||||
|
"description": "Address assignment selection",
|
||||||
|
"label": "Режим на адреса"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"description": "DNS сървър",
|
||||||
|
"label": "DNS"
|
||||||
|
},
|
||||||
|
"ethernetEnabled": {
|
||||||
|
"description": "Активиране или дезактивиране на Ethernet порта",
|
||||||
|
"label": "Активиран"
|
||||||
|
},
|
||||||
|
"gateway": {
|
||||||
|
"description": "Шлюз по подразбиране",
|
||||||
|
"label": "Шлюз"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"description": "IP адрес",
|
||||||
|
"label": "IP"
|
||||||
|
},
|
||||||
|
"psk": {
|
||||||
|
"description": "Парола за мрежата",
|
||||||
|
"label": "PSK"
|
||||||
|
},
|
||||||
|
"ssid": {
|
||||||
|
"description": "Име на мрежата",
|
||||||
|
"label": "SSID"
|
||||||
|
},
|
||||||
|
"subnet": {
|
||||||
|
"description": "Подмрежова маска",
|
||||||
|
"label": "Подмрежа"
|
||||||
|
},
|
||||||
|
"wifiEnabled": {
|
||||||
|
"description": "Активиране или дезактивиране на WiFi радиото",
|
||||||
|
"label": "Активиран"
|
||||||
|
},
|
||||||
|
"meshViaUdp": {
|
||||||
|
"label": "Mesh чрез UDP"
|
||||||
|
},
|
||||||
|
"ntpServer": {
|
||||||
|
"label": "NTP сървър"
|
||||||
|
},
|
||||||
|
"rsyslogServer": {
|
||||||
|
"label": "Сървър Rsyslog"
|
||||||
|
},
|
||||||
|
"ethernetConfigSettings": {
|
||||||
|
"description": "Конфигуриране на Ethernet порта",
|
||||||
|
"label": "Конфигурация на Ethernet "
|
||||||
|
},
|
||||||
|
"ipConfigSettings": {
|
||||||
|
"description": "Конфигуриране на IP",
|
||||||
|
"label": "Конфигурация на IP"
|
||||||
|
},
|
||||||
|
"ntpConfigSettings": {
|
||||||
|
"description": "Конфигуриране на NTP",
|
||||||
|
"label": "Конфигурация на NTP"
|
||||||
|
},
|
||||||
|
"rsyslogConfigSettings": {
|
||||||
|
"description": "Конфигуриране на Rsyslog",
|
||||||
|
"label": "Конфиг. на Rsyslog"
|
||||||
|
},
|
||||||
|
"udpConfigSettings": {
|
||||||
|
"description": "Конфигуриране на UDP през Mesh",
|
||||||
|
"label": "Конфигуриране на UDP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"title": "Настройки на позицията",
|
||||||
|
"description": "Настройки за модула за позиция",
|
||||||
|
"broadcastInterval": {
|
||||||
|
"description": "How often your position is sent out over the mesh",
|
||||||
|
"label": "Интервал на излъчване"
|
||||||
|
},
|
||||||
|
"enablePin": {
|
||||||
|
"description": "GPS module enable pin override",
|
||||||
|
"label": "Активиране на пин"
|
||||||
|
},
|
||||||
|
"fixedPosition": {
|
||||||
|
"description": "Да не се докладва GPS позиция, а ръчно зададена такава",
|
||||||
|
"label": "Фиксирана позиция"
|
||||||
|
},
|
||||||
|
"gpsMode": {
|
||||||
|
"description": "Конфигуриране дали GPS на устройството е активиран, деактивиран или липсва",
|
||||||
|
"label": "Режим на GPS"
|
||||||
|
},
|
||||||
|
"gpsUpdateInterval": {
|
||||||
|
"description": "Колко често трябва да се получава GPS сигнал",
|
||||||
|
"label": "Интервал на актуализиране на GPS"
|
||||||
|
},
|
||||||
|
"positionFlags": {
|
||||||
|
"description": "Optional fields to include when assembling position messages. The more fields are selected, the larger the message will be leading to longer airtime usage and a higher risk of packet loss.",
|
||||||
|
"label": "Position Flags"
|
||||||
|
},
|
||||||
|
"receivePin": {
|
||||||
|
"description": "GPS module RX pin override",
|
||||||
|
"label": "Receive Pin"
|
||||||
|
},
|
||||||
|
"smartPositionEnabled": {
|
||||||
|
"description": "Only send position when there has been a meaningful change in location",
|
||||||
|
"label": "Enable Smart Position"
|
||||||
|
},
|
||||||
|
"smartPositionMinDistance": {
|
||||||
|
"description": "Минималното разстояние (в метри), което трябва да се измине, преди да се изпрати актуализация на местоположението",
|
||||||
|
"label": "Минимално разстояние за интелигентна позиция"
|
||||||
|
},
|
||||||
|
"smartPositionMinInterval": {
|
||||||
|
"description": "Minimum interval (in seconds) that must pass before a position update is sent",
|
||||||
|
"label": "Smart Position Minimum Interval"
|
||||||
|
},
|
||||||
|
"transmitPin": {
|
||||||
|
"description": "GPS module TX pin override",
|
||||||
|
"label": "Transmit Pin"
|
||||||
|
},
|
||||||
|
"intervalsSettings": {
|
||||||
|
"description": "Колко често да се изпращат актуализации на позицията",
|
||||||
|
"label": "Интервали"
|
||||||
|
},
|
||||||
|
"flags": {
|
||||||
|
"placeholder": "Select position flags...",
|
||||||
|
"altitude": "Надморска височина",
|
||||||
|
"altitudeGeoidalSeparation": "Altitude Geoidal Separation",
|
||||||
|
"altitudeMsl": "Altitude is Mean Sea Level",
|
||||||
|
"dop": "Dilution of precision (DOP) PDOP used by default",
|
||||||
|
"hdopVdop": "If DOP is set, use HDOP / VDOP values instead of PDOP",
|
||||||
|
"numSatellites": "Брой сателити",
|
||||||
|
"sequenceNumber": "Пореден номер",
|
||||||
|
"timestamp": "Времево клеймо",
|
||||||
|
"unset": "Не е зададен",
|
||||||
|
"vehicleHeading": "Посока на движение на превозното средство",
|
||||||
|
"vehicleSpeed": "Скорост на превозното средство"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power": {
|
||||||
|
"adcMultiplierOverride": {
|
||||||
|
"description": "Използва се за настройване на отчитането на напрежението на батерията",
|
||||||
|
"label": "ADC Multiplier Override ratio"
|
||||||
|
},
|
||||||
|
"ina219Address": {
|
||||||
|
"description": "Address of the INA219 battery monitor",
|
||||||
|
"label": "INA219 адрес"
|
||||||
|
},
|
||||||
|
"lightSleepDuration": {
|
||||||
|
"description": "Колко дълго устройството ще бъде в лек сън",
|
||||||
|
"label": "Продължителност на лекия сън"
|
||||||
|
},
|
||||||
|
"minimumWakeTime": {
|
||||||
|
"description": "Минималното време, през което устройството ще остане активно след получаване на пакет",
|
||||||
|
"label": "Минимално време за събуждане"
|
||||||
|
},
|
||||||
|
"noConnectionBluetoothDisabled": {
|
||||||
|
"description": "If the device does not receive a Bluetooth connection, the BLE radio will be disabled after this long",
|
||||||
|
"label": "Няма връзка Bluetooth е дезактивиран"
|
||||||
|
},
|
||||||
|
"powerSavingEnabled": {
|
||||||
|
"description": "Изберете, ако се захранва от източник с малък ток (напр. слънчева енергия), за да се намали максимално консумацията на енергия.",
|
||||||
|
"label": "Активиране на енергоспестяващ режим"
|
||||||
|
},
|
||||||
|
"shutdownOnBatteryDelay": {
|
||||||
|
"description": "Automatically shutdown node after this long when on battery, 0 for indefinite",
|
||||||
|
"label": "Shutdown on battery delay"
|
||||||
|
},
|
||||||
|
"superDeepSleepDuration": {
|
||||||
|
"description": "Колко дълго устройството ще бъде в супер дълбок сън",
|
||||||
|
"label": "Продължителност на супер дълбокия сън"
|
||||||
|
},
|
||||||
|
"powerConfigSettings": {
|
||||||
|
"description": "Настройки за захранващия модул",
|
||||||
|
"label": "Конфигуриране на захранването"
|
||||||
|
},
|
||||||
|
"sleepSettings": {
|
||||||
|
"description": "Настройки за спящ режим за захранващия модул",
|
||||||
|
"label": "Настройки за сън"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"description": "Настройки за конфигурацията за сигурност",
|
||||||
|
"title": "Насртойки на сигурността",
|
||||||
|
"button_backupKey": "Резервно копие на ключа",
|
||||||
|
"adminChannelEnabled": {
|
||||||
|
"description": "Allow incoming device control over the insecure legacy admin channel",
|
||||||
|
"label": "Allow Legacy Admin"
|
||||||
|
},
|
||||||
|
"enableDebugLogApi": {
|
||||||
|
"description": "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth",
|
||||||
|
"label": "Enable Debug Log API"
|
||||||
|
},
|
||||||
|
"managed": {
|
||||||
|
"description": "Ако е активирана, опциите за конфигурация на устройството могат да се променят дистанционно само от възел за отдалечено администриране чрез администраторски съобщения. Не активирайте тази опция, освен ако не е настроен поне един подходящ възел за отдалечено администриране и публичният ключ е съхранен в едно от полетата по-горе.",
|
||||||
|
"label": "Управлява се"
|
||||||
|
},
|
||||||
|
"privateKey": {
|
||||||
|
"description": "Използва се за създаване на споделен ключ с отдалечено устройство",
|
||||||
|
"label": "Частен ключ"
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"description": "Изпраща се до други възли в mesh, за да им позволи да изчислят споделения секретен ключ",
|
||||||
|
"label": "Публичен ключ"
|
||||||
|
},
|
||||||
|
"primaryAdminKey": {
|
||||||
|
"description": "Основният публичен ключ, оторизиран за изпращане на администраторски съобщения до този възел",
|
||||||
|
"label": "Основен администраторски ключ"
|
||||||
|
},
|
||||||
|
"secondaryAdminKey": {
|
||||||
|
"description": "Вторичният публичен ключ, оторизиран за изпращане на администраторски съобщения до този възел.",
|
||||||
|
"label": "Вторичен администраторски ключ"
|
||||||
|
},
|
||||||
|
"serialOutputEnabled": {
|
||||||
|
"description": "Serial Console over the Stream API",
|
||||||
|
"label": "Serial Output Enabled"
|
||||||
|
},
|
||||||
|
"tertiaryAdminKey": {
|
||||||
|
"description": "Третичният публичен ключ, оторизиран за изпращане на администраторски съобщения до този възел",
|
||||||
|
"label": "Третичен администраторски ключ"
|
||||||
|
},
|
||||||
|
"adminSettings": {
|
||||||
|
"description": "Настройки за Admin",
|
||||||
|
"label": "Администраторски настройки"
|
||||||
|
},
|
||||||
|
"loggingSettings": {
|
||||||
|
"description": "Settings for Logging",
|
||||||
|
"label": "Logging Settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"title": "User Settings",
|
||||||
|
"description": "Configure your device name and identity settings",
|
||||||
|
"longName": {
|
||||||
|
"label": "Дълго име",
|
||||||
|
"description": "Your full display name (1-40 characters)",
|
||||||
|
"validation": {
|
||||||
|
"min": "Long name must be at least 1 character",
|
||||||
|
"max": "Long name must be at most 40 characters"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shortName": {
|
||||||
|
"label": "Кратко име",
|
||||||
|
"description": "Your abbreviated name (2-4 characters)",
|
||||||
|
"validation": {
|
||||||
|
"min": "Short name must be at least 2 characters",
|
||||||
|
"max": "Short name must be at most 4 characters"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isUnmessageable": {
|
||||||
|
"label": "Без съобщения",
|
||||||
|
"description": "Used to identify unmonitored or infrastructure nodes so that messaging is not available to nodes that will never respond."
|
||||||
|
},
|
||||||
|
"isLicensed": {
|
||||||
|
"label": "Лицензиран радиолюбител (HAM)",
|
||||||
|
"description": "Enable if you are a licensed amateur radio operator, enabling this option disables encryption and is not compatible with the default Meshtastic network."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||