611 Commits

Author SHA1 Message Date
Dan Ditomaso
e2f03aaf81 feat: add dependency injection using tanstack context (#680)
* feat: add dep injection using tanstack context

* fixed small typo
2025-06-24 18:19:26 -04:00
Dan Ditomaso
6c676fa8da add git sub module (#678) 2025-06-23 22:26:01 -04:00
github-actions[bot]
183b3ae8cc chore(i18n): New Crowdin Translations by GitHub Action (#677)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-22 13:57:29 -04:00
Dan Ditomaso
43143bfdf6 Device Name dialog validation (#676)
* fix: style on config page

* feat: added device name validation

* Update src/i18n/locales/en/dialog.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/components/Dialog/DeviceNameDialog.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/components/Dialog/DeviceNameDialog.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fixed typo

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-21 23:08:25 -04:00
Dan Ditomaso
c6a564f7e4 fix: style on config page (#674) 2025-06-20 12:39:11 -04:00
Dan Ditomaso
762aed50b7 Updated button styling on config header (#673)
* fix: updated button in config

* Update src/pages/Config/index.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-18 21:08:56 -04:00
Jeremy Gallant
bb91350ef5 fix: Crash when navigator.serial is undefined (#670)
* Fix crash when navigator.serial is undefined

* Change value

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-06-18 16:39:14 -04:00
Dan Ditomaso
ec9b299b37 fix: add missing i18n strings (#671)
* fix: added required i18n labels to UI

* added node i18n

* updated tests

* updated path to match github action
2025-06-18 16:25:14 -04:00
Jeremy Gallant
0e6a4818ea fix: Crowdin upload sources action (#672)
* Fix Crowdin upload sources action

* Replace biome with deno in vscode extension recommendations

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-06-18 15:48:41 -04:00
Jeremy Gallant
181c984b27 Config form improvements (#652)
* Config reset work WIP

* Config reset WIP

* Fix tests, tsc, linting

* Form reset adjustments

* Add ManagedModeDialog

* Remove debug logging

* Add Suspense

* Review fixes

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-06-18 10:04:17 -04:00
Jeremy Gallant
7adbe01723 Add advanced filters (#655)
* Add advanced filters

* Review edits

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-06-18 10:02:42 -04:00
Dan Ditomaso
a5339af0dd Fix language default in picker. Misc i18n fixes (#664)
* fix: fix language default in picker. Misc i18n fixes

* Update src/i18n/config.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* PR fixes

* duplicate key

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-17 16:23:57 -04:00
Dan Ditomaso
c91e5e6b7b fix: add rewrites to vercel.json (#662) 2025-06-17 11:09:43 -04:00
Jeremy Gallant
ccc4202aa4 Minor i18n fixes (#663)
* i18n fixes

Add PKI Backup Reminder dialog + ensure en-US is UI default

* Revert edits to i18n components
2025-06-17 10:24:58 -04:00
Dan Ditomaso
118f848308 feat: add support for 3 languages (#661) 2025-06-16 16:22:45 -04:00
github-actions[bot]
c36ff60778 chore(i18n): New Crowdin Translations by GitHub Action (#660)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-16 15:59:08 -04:00
Dan Ditomaso
474e610c3d fix: revert crowdin config (#659) 2025-06-16 15:55:29 -04:00
Dan Ditomaso
0b6ae0ce32 fix: update crowdin config (#658) 2025-06-16 15:14:59 -04:00
github-actions[bot]
fad1b984bf chore(i18n): New Crowdin Translations by GitHub Action (#653)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-16 09:41:50 -04:00
Dan Ditomaso
6cc6986904 fix: remove service worker (#654) 2025-06-16 09:39:13 -04:00
Dan Ditomaso
26d5c0a08a feat: added i18n translator & developer guides (#646) 2025-06-12 20:15:43 -04:00
Dan Ditomaso
78e1d1f81a fix: updated mqtt description (#647) 2025-06-12 20:14:59 -04:00
dependabot[bot]
48862141dc chore(deps): bump tj-actions/changed-files in /.github/workflows (#650)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 44 to 46.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v44...v46)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-version: '46'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-12 20:10:08 -04:00
Dan Ditomaso
47f8264c31 Fix tsc errors (#649)
* fixed tsc errors

* fixed tsc errors

* fixed tsc errors

* fixing tsc errors

* fixing more tsc errors

* fixing more tsc errors

* fixed tsc errors

* fixing tsc errors

* fixing PR issues

* commented out tsc check

* completing tsc fixes

* updating lockfile

* removed react-hooks
2025-06-12 19:00:30 -04:00
Dan Ditomaso
851da0707c Fix broken tests (#648) 2025-06-12 16:08:08 -04:00
Jeremy Gallant
4275bdd0c0 Remove deprecated meshtastic/js dependency (#638)
* Remove deprecated meshtastic/js dependency

* Bump dependency version

* Fix linting

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-06-11 10:09:29 -04:00
Dan Ditomaso
828e5d0903 Replace zustand state management with URL-based routing (#640)
* feat: added router

* feat: added params to messages page

* fixing tests, added translation labels

* Update src/i18n/locales/en/ui.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/components/PageComponents/Map/NodeDetail.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/pages/Messages.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* updated dev tools

* fixing tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-06 18:23:49 -04:00
Jeremy Gallant
1cbd98ec53 Zod config validation (#635)
* Zod WIP

* Zod form validation

* DynamicForm testing

* Fix linting

* Delete rasterSource.ts

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-06-04 08:10:59 -04:00
Dan Ditomaso
df036d3904 Add commit sha to footer. (#636)
* feat: added commit sha to footer.

* added translation

* added padding

* updated styling
2025-06-01 20:41:25 -04:00
Kimberly Graham
08dbe94679 fix i18n files missing and Toast messages (#634)
* fix Toast messages

* Copy i18n files to dist using
vite-plugin-static-copy
2025-05-28 13:55:45 -04:00
Dan Ditomaso
24e9764fcb fix: updated namespace module (#632)
N33d this merged.
2025-05-27 11:13:51 -04:00
Dan Ditomaso
eb2a2717b1 Internationalization (i18n) (#627)
* `feat: added internationalization lib, added english labels`
* `fixes from code review`
* `feat: add crowdin github actions`
* `fix: added support for i18n in testing. Fixed broken tests`
* `fix: missing translations`
* `removed unneded import`
* `more components updated with missing translations`
* `fixed lint issue`
* `Refactor: updated how translations are scoped, updated all references`
* `fixing broken tests`
* `feat: added language switcher, updated some translations`
* `fixed linting issues`
* `reverting vite config`
* `add english group id's to command palette`
* `updated PR template for i18n`
2025-05-27 08:33:43 -04:00
jamon
9d74fe2d6e Remove autocomplete from message box (#630)
* Remove autocomplete from message box

The message box has autocomplete turned on, which causes some browsers to suggest previously written messages in a really annoying way.   This fixes that issue.

* Update src/components/PageComponents/Messages/MessageInput.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-27 07:36:16 -04:00
philon-
8413f6345c Fix #624 (#631) 2025-05-27 07:35:47 -04:00
philon-
37a53b747c Fix NodeInfoDialog initiation (#626) 2025-05-27 07:14:57 -04:00
philon-
a642080b90 Filter rework (#623)
* Rework filtering

Created common FilterComponents
Created common FilterControl
Abstracted common map logic into Map component
Reworked Node filtering for map page
Added Node filtering for nodes list
Added test for node filtering
Added toggle group UI package

* Debounce filterState change

* UI adjustments

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-05-22 15:11:32 -04:00
Dan Ditomaso
9cb5cffdb1 Update README.md
Remove references to `master` replace with `main`
2025-05-21 11:58:03 -04:00
philon-
57b9942cce Add RSSI fields for Paxcounter module (#620)
Co-authored-by: philon- <philon-@users.noreply.github.com>
2025-05-19 12:36:39 -04:00
philon-
1274584497 Feature: Admin PKI fields (#619)
* Add admin PKI fields

* Renaming state ref

* WIP: Refactoring

* Refactoring Security Config

* Ensure admin keys have been set before allowing managed mode
2025-05-16 10:24:41 -04:00
philon-
513a285fee Feature: Node favourites (and ignores) (#618)
* Add favourite icon to node Avatar

* Favourites WIP

* Save isFavorite and isIgnored to device

* Fix spelling

* Clean up

* Always sort favorites first

* Add unread count to "Messages" top level menu

* Renaming, UI tweaks

* Add hook tests

* Handle undefined node better
2025-05-13 08:45:24 -04:00
philon-
b2bb3baa01 Add check for undefined hopsAway (#616)
* Add check for undefined hopsAway

Better UI for already "fixed" #351

* Change conditional
2025-05-12 11:54:06 -04:00
Aria Stewart
3f933dd166 Remove case-duplicated sidebarButton.tsx (#617) 2025-05-11 20:01:43 -04:00
Dan Ditomaso
1180b9afb0 fix: moved all svgs into public / dist folder (#615) 2025-05-09 20:57:38 -04:00
philon-
b4ce6efd7b Fix node list sorting, dark mode text colors (#613)
* Fix node list sorting, dark mode text colors

* Update tests

* Remove unused import

* Fix warnings for missing DialogDescription
2025-05-08 12:54:18 -04:00
Dan Ditomaso
da0ada925f fix: removed overly complex scrolling logic in channel chat (#612) 2025-05-06 12:40:07 -04:00
Dan Ditomaso
f9346931f8 update readme with deployment schedule (#611) 2025-05-06 10:10:24 -04:00
Dan Ditomaso
1d18abf6c1 fix: connect new node no longer crashes. (#610) 2025-05-06 10:08:42 -04:00
Dan Ditomaso
a644d30228 Add github action to release to stable (#590)
* feat: added github action workflow to release to stable

* updated trigger logic
2025-05-05 13:20:49 -04:00
Dan Ditomaso
1c453e2981 fix: couple of UI updates, fix: add pre release support to release workflow (#609) 2025-05-05 11:47:36 -04:00
philon-
170abfcc2f fix: Adjust table odd/even colors (#606) 2025-05-05 11:23:37 -04:00
philon-
0539b15ddc Fix #607 (#608) 2025-05-04 20:08:16 -04:00
Dan Ditomaso
bbadb1a917 chore: optimize deno workflow for CI/CD (#605)
* chore: optimize deno workflow for CI/CD

* add install step
2025-05-04 16:39:19 -04:00
Dan Ditomaso
480ca46a95 chore: lint/format all files (#604)
* chore: lint/format all files

* Fix config sidebar button state (#602)

* chore: Update deno.lock version and add Radix UI slider component (#601)

* fix: improve how table addresses even/odd rows

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
Co-authored-by: Kamil Dzieniszewski <kamil.dzieniszewski@gmail.com>
2025-05-04 15:55:04 -04:00
Kamil Dzieniszewski
b7bdb1a502 chore: Update deno.lock version and add Radix UI slider component (#601) 2025-05-04 15:29:34 -04:00
philon-
3be528d03a Fix config sidebar button state (#602) 2025-05-04 15:28:47 -04:00
Dan Ditomaso
ebc386cfa5 Merge pull request #599 from danditomaso/fix/update-telemetry-labels
fix: updated telemetry field labels
2025-05-04 11:50:58 -04:00
Dan Ditomaso
35f3a347ba fix: updated telemtry field labels 2025-05-03 21:27:26 -04:00
Dan Ditomaso
cfcc9f82d8 Merge pull request #596 from philon-/style-fixes
Minor style fixes
2025-05-03 13:28:22 -04:00
Dan Ditomaso
7ab4254cd0 Merge pull request #597 from danditomaso/fix/content-overflow-issue
fix: defined height to html/body elements
2025-05-03 13:27:54 -04:00
Dan Ditomaso
e3857e61c6 fix: defined height to html/body elements 2025-05-03 13:24:00 -04:00
philon-
a02579d6dd Minor style fixes 2025-05-03 00:27:12 +02:00
Dan Ditomaso
79910dfec7 fix: removed console logs in code (#592) 2025-05-02 11:48:18 -04:00
Austin
b40540b118 Fix upload build.tar (#591) 2025-05-02 10:57:17 -04:00
Dan Ditomaso
99711fc44e Remove duplicate node logic, UI Update, flatten build output (#586)
* refactor nodes to getNodes fn. ui updates

* fixed several styling issues

* fix: message specific styling/overflow

* added footer, fixed tests. styling

* fix: added theme support back to app component

* fix: hide emojis/reactions

* fix: added more padding to content element

* fix: fixed padding in content element

* updated color scheme

* fix: more dark mode styling improvements

* fix: padding and alignment fixes

* fix: prevent left sidebar collapse, added battery component

* fix: change scrollbars to "tiny" style, improved message scrolling, fixed bug with message input

* message store fixes, ui fixes

* fix: disabled message persistance until after release
2025-05-02 09:00:24 -04:00
Dan Ditomaso
3eafad7261 Merge pull request #585 from philon-/feature/map-filtering
Map filtering improvements
2025-04-29 15:29:06 -04:00
Jeremy Gallant
80905d9d29 Change colors to slate 2025-04-29 15:35:34 +02:00
Jeremy Gallant
34db0da87c Add map filter groups / more filters / update UI 2025-04-28 17:38:58 +02:00
Jeremy Gallant
ff33554716 Add hover: and active: styling 2025-04-24 20:30:31 +02:00
Jeremy Gallant
f399d17721 Added map filter indication
Added map filter indication
+ some more type fixes...
2025-04-24 20:12:41 +02:00
Dan Ditomaso
ce71f22316 Merge pull request #580 from philon-/feature/map-filtering
Add filters to node map
2025-04-24 08:52:55 -04:00
Dan Ditomaso
1f3f76373d Merge pull request #583 from danditomaso/fix-remove-assets-output-dir
fix: keep js and css assets in same output directory
2025-04-24 07:53:39 -04:00
Dan Ditomaso
c5fe2f5e68 fix: dont put js and css assets in sub-directory in output 2025-04-23 23:12:54 -04:00
Dan Ditomaso
c050998f3d Merge pull request #582 from meshtastic/danditomaso-patch-1
Update ci.yml
2025-04-23 15:48:37 -04:00
Dan Ditomaso
4802a8f6e6 Update ci.yml 2025-04-23 14:14:00 -04:00
Jeremy Gallant
03e516e568 Slider - additional props 2025-04-23 16:41:20 +02:00
Jeremy Gallant
ef37397969 Remove ! 2025-04-23 07:10:04 +02:00
Jeremy Gallant
c6d122008b Unique id 2025-04-22 23:26:49 +02:00
Jeremy Gallant
3dce031f8e Stricter typing, adjuster colors, mandatory props 2025-04-22 23:18:26 +02:00
philon-
91d8776637 Merge branch 'meshtastic:master' into feature/map-filtering 2025-04-22 20:28:05 +02:00
Dan Ditomaso
01f242b7c3 Merge pull request #577 from philon-/fix/495
Fix #495 - Rescale traceroute SNR
2025-04-22 14:09:45 -04:00
Jeremy Gallant
d5cf71c840 Add filters to node map 2025-04-22 19:04:42 +02:00
Jeremy Gallant
5ba70d9764 Fix #495 - Rescale traceroute SNR 2025-04-19 22:14:03 +02:00
Dan Ditomaso
673476d773 Merge pull request #573 from danditomaso/feat/add-copy-text-to-input
Add Copy to Clipboard option for input fields.
2025-04-13 17:09:40 -04:00
Dan Ditomaso
5eb9fda015 Merge pull request #574 from danditomaso/issue-557-transparent-background
fix: removed transparent toast background
2025-04-13 11:39:28 -04:00
Dan Ditomaso
81586caea0 fix: removed transparent toast background 2025-04-13 08:07:17 -04:00
Dan Ditomaso
a195126df1 fixed when grid collapose's to single column 2025-04-12 22:05:42 -04:00
Dan Ditomaso
b3783bab40 added longer field length 2025-04-12 21:59:58 -04:00
Dan Ditomaso
33d0f93e68 Merge branch 'master' into feat/add-copy-text-to-input 2025-04-12 21:36:11 -04:00
Dan Ditomaso
2050b05d6a feat: add copy text option to input fields. 2025-04-12 21:11:52 -04:00
Dan Ditomaso
38754b9d1a Merge pull request #572 from danditomaso/fix/improve-responsiveness
Improve form responsiveness
2025-04-12 20:26:44 -04:00
Dan Ditomaso
1867484032 fix: improve-responsiveness 2025-04-12 12:46:01 -04:00
Dan Ditomaso
b52ed19649 Merge pull request #571 from danditomaso/fix/improve-refresh-keys-dialog
fix: improvements to refresh dialog
2025-04-10 23:09:41 -04:00
Dan Ditomaso
d53ababf7d fix: improvements to refresh dialog 2025-04-10 16:51:29 -04:00
Hunter Thornsberry
ff43763721 Merge pull request #568 from Hunter275/clock-settings 2025-04-10 13:30:52 -04:00
Hunter Thornsberry
c44d7633f2 Merge pull request #570 from danditomaso/fix/minus-node-num 2025-04-10 13:30:09 -04:00
Dan Ditomaso
08d641eb42 fix: ensured node number on loading screen cant be negitive. 2025-04-10 12:19:15 -04:00
Hunter275
a243a044b9 add use12hClock support 2025-04-10 01:48:32 -04:00
Dan Ditomaso
c95a819eaf Merge pull request #566 from danditomaso/issue-564-multiple-connect-clicks-on-connect-dialog
Multiple clicks on "Connect" button, creates multiple nodes.
2025-04-08 08:20:36 -04:00
Dan Ditomaso
ce5ae675ea fix: hoisted state to parent in order to coordinate connection 2025-04-07 20:27:51 -04:00
Dan Ditomaso
0828618c0d Merge pull request #563 from danditomaso/issue-550-incorrect-text-in-dialog
Incorrect text in PKI dialog
2025-04-07 20:19:33 -04:00
Dan Ditomaso
7267101021 fix: extended dialog to allow for dynamic title/description 2025-04-07 15:02:12 -04:00
Dan Ditomaso
0e868cef58 renamed dialog, added reactions menu 2025-04-07 11:42:15 -04:00
Dan Ditomaso
e410ccb2f4 Merge pull request #562 from danditomaso/fix/missing-import
fix: added missing imports
2025-04-05 13:01:19 -04:00
Dan Ditomaso
c5b3f2ece6 fix: added missing imports 2025-04-05 12:57:42 -04:00
Dan Ditomaso
35353c58cb Merge pull request #561 from danditomaso/fix/failing-test
fix: refactor to fix merge issues with  messageStore and unread counts
2025-04-05 11:49:11 -04:00
Dan Ditomaso
e80d8e73ae fix: refactor to fix merge issues with messageStore and unread counts 2025-04-05 11:46:32 -04:00
Dan Ditomaso
494a35a0c3 Merge pull request #497 from Hunter275/unread-counts
Unread Counts
2025-04-05 08:40:54 -04:00
Dan Ditomaso
818bbb4a30 fix broken test 2025-04-05 08:39:27 -04:00
Dan Ditomaso
4755c0eeb9 refactor to integrate messageStore and unreadCounts 2025-04-04 22:22:35 -04:00
Dan Ditomaso
c8c89fdc95 Merge branch 'master' into unread-counts 2025-04-04 08:58:13 -04:00
Dan Ditomaso
52e0924f1c Merge pull request #560 from danditomaso/fix/node-detail-use-message-store
fix: update node details page to use message store
2025-04-03 22:42:48 -04:00
Dan Ditomaso
645c758b42 fixed: removed unneeded prop 2025-04-03 22:41:10 -04:00
Dan Ditomaso
4dc7788981 fixed typo 2025-04-03 22:40:30 -04:00
Dan Ditomaso
9fa945a863 fix: update node details page to use message store 2025-04-03 22:37:50 -04:00
Dan Ditomaso
38b8695441 Merge pull request #536 from danditomaso/add-message-persistance
Add message persistence using IndexedDB
2025-04-03 22:28:25 -04:00
Dan Ditomaso
eadadb5d1d keyed conversations against from/to, updated tests 2025-04-03 17:03:08 -04:00
Dan Ditomaso
5f424e2e0b Merge branch 'master' into add-message-persistance 2025-04-02 18:02:57 -04:00
James Thomas
d807cd2de7 Using existing Link component and standardizing colors 2025-04-02 18:00:44 -04:00
James Thomas
0b4e3a8da9 Cleanup and add link to official docs 2025-04-02 18:00:44 -04:00
James Thomas
31be5e9a25 Adding connection failure warning 2025-04-02 18:00:44 -04:00
vidplace7
367538eeea GHA: Attach build.tar to release 2025-04-02 18:00:33 -04:00
Dan Ditomaso
442c1cb5f1 fix: moved meshtastic packages into package.json 2025-04-02 18:00:33 -04:00
Dan Ditomaso
a333e4524f updated deps 2025-04-02 18:00:12 -04:00
Hunter275
1e54f7d99b fix for non-Primary channels 2025-04-02 17:59:59 -04:00
Dan Ditomaso
9f2aa8282d adding tests 2025-04-02 17:59:59 -04:00
Dan Ditomaso
8d5dc440d0 feat: add udp over mesh toggle 2025-04-02 17:59:59 -04:00
Dan Ditomaso
8fffde0165 wip 2025-04-02 17:59:59 -04:00
James Thomas
1a6e99971a Extended loading 2025-04-02 17:58:40 -04:00
Dan Ditomaso
4de88c3add fixed: import issue 2025-04-02 17:58:40 -04:00
Dan Ditomaso
76374893e3 added tests 2025-04-02 17:58:40 -04:00
Dan Ditomaso
edc17b304a feat: added reboot to OTA in command menu 2025-04-02 17:58:40 -04:00
Dan Ditomaso
ec7b4528f6 added reboot to command menu 2025-04-02 17:58:30 -04:00
Dan Ditomaso
8d75c4afb1 fix: docker build process 2025-04-02 17:58:30 -04:00
James Thomas
b30fbf90b9 Prevent tooltip from appearing by default 2025-04-02 17:58:30 -04:00
James Thomas
8fb95e1b06 Lint 2025-04-02 17:58:30 -04:00
James Thomas
f5e1a0569f Lint 2025-04-02 17:58:30 -04:00
James Thomas
929f87b411 Adding DM from Map function 2025-04-02 17:58:30 -04:00
Dan Ditomaso
59d97008f2 feat: added tzdef to device config 2025-04-02 17:58:30 -04:00
Dan Ditomaso
540b8ebb4d Merge pull request #548 from James9074/tls-warning
Adding UX Feedback For Failed Device Connections Over HTTP/s
2025-04-02 13:12:39 -04:00
Dan Ditomaso
109d4afce2 Merge pull request #556 from vidplace7/release-buildtar
GHA: Attach build.tar to release
2025-04-02 10:09:17 -04:00
vidplace7
aab8bce78e GHA: Attach build.tar to release 2025-04-02 09:18:20 -04:00
James Thomas
d2c33b4caf Merge branch 'meshtastic:master' into tls-warning 2025-04-02 08:32:23 -04:00
Dan Ditomaso
633b99d6b2 Merge pull request #554 from danditomaso/fix-pkg-import-issue
fix: moved meshtastic packages into package.json
2025-04-01 20:48:17 -04:00
Dan Ditomaso
87159b4eee fix: moved meshtastic packages into package.json 2025-04-01 20:45:36 -04:00
Dan Ditomaso
6d39ecc7b9 Merge pull request #553 from danditomaso/update-deps-to-latest
updated deps to latest
2025-04-01 16:51:29 -04:00
Dan Ditomaso
7738661b7c updated deps 2025-04-01 16:50:39 -04:00
Dan Ditomaso
6443544a6b added dialog to warn before clearing all messages 2025-04-01 14:50:41 -04:00
Dan Ditomaso
4689ebe3ce Merge pull request #551 from Hunter275/position-precision-v2
Fix Position Precision for non-Primary channels
2025-04-01 11:19:33 -04:00
Hunter275
6cd8ce5102 fix for non-Primary channels 2025-03-31 23:14:20 -04:00
Dan Ditomaso
a56ac84186 add enums, improve tests, add styling 2025-03-31 21:28:58 -04:00
James Thomas
443a9ea101 Merge branch 'meshtastic:master' into tls-warning 2025-03-31 09:29:45 -04:00
James Thomas
0faafe8bc4 Using existing Link component and standardizing colors 2025-03-30 21:43:25 -04:00
James Thomas
9948701127 Cleanup and add link to official docs 2025-03-30 21:01:25 -04:00
James Thomas
ffae92d233 Adding connection failure warning 2025-03-30 20:52:04 -04:00
Dan Ditomaso
fed65d9c8b Merge pull request #547 from danditomaso/issue-523-add-udp-toggle
feat: Add udp over mesh toggle
2025-03-30 20:13:03 -04:00
Dan Ditomaso
8f225f4d28 Merge pull request #544 from James9074/loading-device
Cleaner Device Loading UX
2025-03-30 17:26:10 -04:00
Dan Ditomaso
11e820d1d0 Merge pull request #543 from danditomaso/issue-542-add-reboot-to-command-menu
added reboot to command menu
2025-03-30 17:25:30 -04:00
Dan Ditomaso
95fc72173f adding tests 2025-03-29 22:59:46 -04:00
Dan Ditomaso
03b5c639fb feat: add udp over mesh toggle 2025-03-29 22:19:53 -04:00
Dan Ditomaso
4d30558aca Merge pull request #546 from danditomaso/issue-545-nightly-docker-not-starting
Fixed Docker Builds Not Serving Site on 8080
2025-03-29 17:02:06 -04:00
Dan Ditomaso
7f376186b4 Merge pull request #540 from James9074/dm-from-map
Allow users to DM nodes directly from the map
2025-03-29 16:00:56 -04:00
Dan Ditomaso
0de24c41ed fix: docker build process 2025-03-29 15:54:15 -04:00
James Thomas
88c4f84edb Extended loading 2025-03-28 22:49:21 -04:00
Dan Ditomaso
74db087d7d updated node details with new messaging features. 2025-03-28 21:22:10 -04:00
Dan Ditomaso
e00239562c Update src/components/PageComponents/Config/Position.tsx
Co-authored-by: James Thomas <james9074@gmail.com>
2025-03-28 21:14:42 -04:00
Dan Ditomaso
bf9557040f fixed: import issue 2025-03-28 21:12:13 -04:00
Dan Ditomaso
6d9a44a0e3 added tests 2025-03-28 21:11:01 -04:00
Dan Ditomaso
35aabdc900 feat: added reboot to OTA in command menu 2025-03-28 21:06:37 -04:00
Dan Ditomaso
163502156d Merge pull request #541 from danditomaso/add-tzdef
feat: added tzdef to device config
2025-03-28 14:27:12 -04:00
Dan Ditomaso
8baa5d84b9 added reboot to command menu 2025-03-28 12:12:56 -04:00
Dan Ditomaso
c55fdbd982 wip 2025-03-28 11:45:48 -04:00
James Thomas
8da38ab2e4 Prevent tooltip from appearing by default 2025-03-28 09:57:35 -04:00
Dan Ditomaso
dddb781627 feat: added tzdef to device config 2025-03-27 20:50:27 -04:00
James Thomas
77b3a7ac85 Lint 2025-03-27 17:35:18 -04:00
James Thomas
626970865f Lint 2025-03-27 17:34:42 -04:00
James Thomas
c0308532a1 Adding DM from Map function 2025-03-27 17:21:40 -04:00
Dan Ditomaso
8df67bf76a Merge branch 'master' into add-message-persistance 2025-03-26 16:09:12 -04:00
Dan Ditomaso
80d4670204 state store cleanup, added tests 2025-03-26 15:22:14 -04:00
Dan Ditomaso
a378cce0be Merge pull request #538 from danditomaso/issue-537-position-flags-undefined
fix: ensured undefined position flags are handled
2025-03-26 13:08:55 -04:00
Dan Ditomaso
488fd61558 fix: ensured undefined position flags are handled 2025-03-25 15:49:44 -04:00
Dan Ditomaso
ed2ab36ed4 feat: added message persistance 2025-03-25 15:23:24 -04:00
Dan Ditomaso
0d6c5878fc WIP 2025-03-24 15:37:51 -04:00
Dan Ditomaso
dcbfb08f26 Merge pull request #528 from danditomaso/add-dismiss-to-key-reminder
Refactor useBackupReminder hook
2025-03-23 22:20:57 -04:00
Hunter275
dab76df131 reorder tests so they don't step on each other 2025-03-21 23:43:57 -04:00
Dan Ditomaso
a7a448cbcd refactor: improved how reminder expiry dates are handled. 2025-03-21 23:34:20 -04:00
Dan Ditomaso
1780c6fb2a refactor: updated how expiry dates are handled. 2025-03-21 22:39:40 -04:00
Hunter Thornsberry
2d54df7dba Merge pull request #532 from Hunter275/node-count-off-by-one
Subtract one from node count
2025-03-21 14:39:24 -04:00
Hunter Thornsberry
890674eea3 subtract one from node count 2025-03-21 14:26:19 -04:00
Hunter Thornsberry
d1c19d9d3e add hasNodeError for tests 2025-03-21 14:20:27 -04:00
Hunter Thornsberry
11b052e5bb Merge branch 'master' into unread-counts 2025-03-21 10:36:09 -04:00
Dan Ditomaso
93a70dfd47 Merge pull request #529 from danditomaso/feat/update-readme
Updated repo readme
2025-03-21 07:53:24 -04:00
Dan Ditomaso
6ac8646323 Merge pull request #500 from Hunter275/browser-feature-rework
Update style and wording of browser support for connection types
2025-03-20 21:40:12 -04:00
Dan Ditomaso
a215da1ebe Merge pull request #526 from bkimmel/bkimmel/reorder-nodes-columns
reorder columns in Nodes page
2025-03-20 16:54:51 -04:00
Dan Ditomaso
22dbfbcc09 feat: added never remind me to key reminder. 2025-03-20 14:40:15 -04:00
Dan Ditomaso
6341d564d3 feat: update readme with domain changes 2025-03-20 12:06:21 -04:00
bkimmel
28cc7b9800 reorder columns in Nodes page 2025-03-19 17:50:20 -04:00
Dan Ditomaso
5a142e671d Merge pull request #525 from danditomaso/fix/remove-react-scan
Revert: Remove react scan
2025-03-19 14:55:33 -04:00
Dan Ditomaso
ba3d45584d adding lock file 2025-03-19 13:52:25 -04:00
Dan Ditomaso
f54c0dd836 fix: removed react-scan due to issues with bluetooth 2025-03-19 13:46:56 -04:00
Dan Ditomaso
a6427a9ed1 Merge pull request #520 from bkimmel/bkimmel/labels-on-icons
add a label to theme icon
2025-03-19 13:26:40 -04:00
Dan Ditomaso
11058dbf3b Merge pull request #516 from danditomaso/feat/add-key-mismatch-error-handling
feat: add error handling for key mismatch
2025-03-19 12:42:38 -04:00
Dan Ditomaso
d062c2f1ab Merge pull request #522 from danditomaso/fix-node-details-styling
fix: styling issues in NodeDialog & TraceRoute components
2025-03-19 08:11:08 -04:00
bkimmel
1f109d161f deno format 2025-03-18 23:28:44 -04:00
bkimmel
f2d6daa9fc safety coalesce 2025-03-18 22:57:22 -04:00
bkimmel
9634e1ce39 PR Feedback: h/t Dan & Hunter 2025-03-18 22:52:58 -04:00
Dan Ditomaso
64055a5aeb fixed spacing and updated wording on dialog 2025-03-18 22:09:20 -04:00
Dan Ditomaso
ad366e6bab added additional routing packet error handler 2025-03-18 19:44:09 -04:00
Dan Ditomaso
9399104914 fix: resolved issues with styling 2025-03-18 15:21:58 -04:00
bkimmel
f82bc660b0 add a label to theme icon 2025-03-18 00:06:38 -04:00
Dan Ditomaso
ed13af2382 Merge pull request #514 from bkimmel/bkimmel/nodespage-fixes-1
small-scale Nodes page fixes
2025-03-17 22:49:53 -04:00
bkimmel
e4c2952e49 dark mode adjustments 2025-03-17 18:15:43 -04:00
Dan Ditomaso
0830eb9971 Merge pull request #518 from danditomaso/issue-515-add-node-count
feat: added node count to sidebar
2025-03-17 17:10:06 -04:00
Dan Ditomaso
be9b61ec0c feat: added node count to sidebar 2025-03-17 13:20:21 -04:00
Hunter Thornsberry
be0fe08f2f Merge pull request #513 from Hunter275/position-precision-fix
Position Precision Rework
2025-03-16 23:06:55 -04:00
Dan Ditomaso
3f8d3389d5 feat: add error handling for key mismatch 2025-03-16 22:56:58 -04:00
Hunter Thornsberry
7e1ba42873 remove defined css class and just use tailwind 2025-03-16 19:45:27 -04:00
Hunter Thornsberry
20af1b4d34 change submit to be outlined 2025-03-16 19:35:26 -04:00
bkimmel
207061e9d8 small-scale Nodes page fixes 2025-03-16 09:41:50 -04:00
Hunter Thornsberry
6633fc9c55 Merge branch 'master' into position-precision-fix 2025-03-16 01:34:57 -04:00
Hunter275
52b80613f8 position precision rework 2025-03-16 01:33:05 -04:00
Hunter Thornsberry
0bef82ec32 don't update unred if the channel/dm is active 2025-03-14 15:44:02 -04:00
Hunter Thornsberry
f80bb6c42d DM vs Channel message detection 2025-03-14 15:28:45 -04:00
Hunter275
db2cb8cb42 update style and wording of browser support for connection types 2025-03-13 01:29:03 -04:00
Dan Ditomaso
c320d7d173 Merge pull request #499 from Hunter275/position_precision_stop_gap
Stop Gap: Remove Position Precision
2025-03-12 23:42:44 -04:00
Hunter Thornsberry
db50bb5c1b stop gap for channel position precision until fix is worked out 2025-03-12 22:32:01 -04:00
Hunter Thornsberry
01a74829fc Merge branch 'master' into unread-counts 2025-03-11 16:25:58 -04:00
Hunter Thornsberry
7b77b7f5e9 remove test values 2025-03-11 16:21:44 -04:00
Hunter Thornsberry
ce8fcd2269 simplify device list and tests 2025-03-11 16:20:22 -04:00
Hunter Thornsberry
f6f64eca10 tests 2025-03-10 23:33:39 -04:00
Dan Ditomaso
3240ac57f7 Merge pull request #492 from bkimmel/issue459/direct_nodes
Fix: issue 459 / sort direct nodes
2025-03-10 21:57:08 -04:00
Dan Ditomaso
2008b09ca3 Merge pull request #494 from danditomaso/issue-486-are-you-sure-dialog
Issue 486 are you sure dialog
2025-03-10 21:06:30 -04:00
Dan Ditomaso
491f72b426 fix for broken test 2025-03-10 21:00:01 -04:00
Dan Ditomaso
a6f46bd38a commit lock 2025-03-10 20:56:32 -04:00
Hunter275
c103d7012b tests 2025-03-10 20:54:42 -04:00
Dan Ditomaso
2cebb8eee2 refactor: added close button back to dialog 2025-03-10 20:53:09 -04:00
Dan Ditomaso
33ad9f989c fix: fixed vitest file after merge conflict 2025-03-10 20:46:46 -04:00
bkimmel
c590ab2ff5 Fix: issue 459 / sort direct nodes 2025-03-10 20:31:52 -04:00
Dan Ditomaso
9da949d27a Merge branch 'master' into issue-486-are-you-sure-dialog 2025-03-10 20:31:33 -04:00
Dan Ditomaso
f1a58f0434 refactor: fixed unsafe roles dialog and hook logic, added tests 2025-03-10 20:30:04 -04:00
Dan Ditomaso
0296b241e4 Merge pull request #487 from danditomaso/issue-455-cant-scroll-up-in-chat
fix: resolved issue with being unable to scroll up in the input field
2025-03-10 15:11:15 -04:00
Dan Ditomaso
344ad48858 fix: improved style of the message input field 2025-03-10 09:26:01 -04:00
Dan Ditomaso
97f2abb582 fix: added tests to branch 2025-03-09 12:57:51 -04:00
Dan Ditomaso
eca5d780c1 feat: added are you sure dialog 2025-03-09 12:57:37 -04:00
Hunter275
1f1a3c5de8 spread on the map 2025-03-09 00:57:52 -05:00
Dan Ditomaso
844a6316f6 fix: remove unneeded role 2025-03-08 21:15:34 -05:00
Dan Ditomaso
d39c5ed079 Merge pull request #490 from danditomaso/issue-489-bluetooth-uuid-not-set
fix: restored correct BLE service uuid to BLE devices filter
2025-03-08 21:01:22 -05:00
Dan Ditomaso
09bb0bc43a fix: added ble header to vercel config 2025-03-08 16:28:20 -05:00
Dan Ditomaso
266e27bfe9 fix: added BLE uuid back to BLE connection component 2025-03-08 16:26:26 -05:00
Dan Ditomaso
5b11131e08 fix: remove unneeded role 2025-03-08 12:19:26 -05:00
Hunter Thornsberry
3bfd96defe wip 2025-03-07 23:59:52 -05:00
Hunter275
cad590f993 wip 2025-03-07 22:01:34 -05:00
Dan Ditomaso
d70b14b12b fix: failing tests 2025-03-06 16:09:22 -05:00
Dan Ditomaso
c115ac0749 fix: improved hover state for message input button 2025-03-06 14:39:09 -05:00
Dan Ditomaso
d54a612e0b fix: restore aliased paths to vite config 2025-03-06 14:28:07 -05:00
Dan Ditomaso
d379769672 fix: resolved issue with being unable to scroll up in the input field 2025-03-06 14:09:47 -05:00
Dan Ditomaso
b670ffe407 Merge pull request #484 from varanauskas/patch-1
Update HTTP.test.tsx to ensure "https://" prefix is used if needed
2025-03-05 20:28:54 -05:00
Tadas Varanauskas
4ffbe03b22 Update HTTP.test.tsx to ensure "https://" prefix is used if needed
Small test update to prevent regression of #481 

#482 fixed the issue, this new test would have failed before, and will prevent reoccurrence of the issue
2025-03-05 20:09:11 +02:00
Dan Ditomaso
6a438470cf Merge pull request #483 from danditomaso/fix/restore-window-to-http
fix: remove GlobalThis and use window instead
2025-03-05 10:53:52 -05:00
Dan Ditomaso
4d0d1da691 fix: remove GlobalThis and use window instead 2025-03-05 08:54:26 -05:00
Dan Ditomaso
39f26f475b Merge pull request #482 from danditomaso/issue-481-node-connecting-on-https
fix: update TLS setting if URL is using HTTPS

Fixes #481
2025-03-05 08:30:18 -05:00
Dan Ditomaso
35fed173af fix: update TLS setting if URL is using HTTPS 2025-03-05 08:14:59 -05:00
Dan Ditomaso
a8b0515949 Merge pull request #480 from danditomaso/issue-479-node-connecting-on-https
fix: restored https toggle functionality. added tests
2025-03-04 14:38:29 -05:00
Dan Ditomaso
bd9d599934 fix: if url is already https, toggle should be checked 2025-03-04 14:10:46 -05:00
Dan Ditomaso
b40079cdc9 fix: restored https toggle functionality. added tests 2025-03-04 13:43:02 -05:00
Dan Ditomaso
b1cf4ef645 Merge pull request #477 from danditomaso/refactor/use-deno
Switch from Bun to Deno for dev environment
2025-03-03 20:56:00 -05:00
Dan Ditomaso
237c7bec3e Merge branch 'master' into refactor/use-deno 2025-03-03 20:55:16 -05:00
Dan Ditomaso
75596e7153 chore: fixes from deno linting/formatting 2025-03-03 20:42:46 -05:00
Hunter275
7968994090 fix for 'never' as well 2025-03-03 20:33:23 -05:00
Hunter275
7c84a582ba fix padding on last heard 2025-03-03 20:33:23 -05:00
Hunter275
9ad6c049f2 fix nodes page error, more cleanup is probably required for "undefined" hops away 2025-03-03 20:33:14 -05:00
Dan Ditomaso
04ecdd55fe fix overflow issue with stack trace 2025-03-03 20:33:05 -05:00
Dan Ditomaso
dbad25814c Reduced code duplication of ErrorBoundary, wrapped PageLayout with ErrorBoundary. 2025-03-03 20:33:05 -05:00
Dan Ditomaso
5af5364668 feat: add chirpy to error page 2025-03-03 20:32:56 -05:00
Dan Ditomaso
d28b4ce6d9 feat: add error boundary 2025-03-03 20:32:47 -05:00
Dan Ditomaso
f9066eced0 fix: added a notes fild to Dynamic form allowing for notes to inform users about using BLE and wifi at the same time. (#474) 2025-03-03 20:32:01 -05:00
Hunter Thornsberry
a60db5521d Merge pull request #478 from Hunter275/fix-nodes-page
Fix Nodes page
2025-03-03 09:39:47 -05:00
Hunter275
e1d4490d62 fix for 'never' as well 2025-03-02 22:44:59 -05:00
Hunter275
cc24605cdb fix padding on last heard 2025-03-02 22:42:55 -05:00
Hunter275
2647604798 fix nodes page error, more cleanup is probably required for "undefined" hops away 2025-03-02 22:22:02 -05:00
Hunter Thornsberry
6e3d326abb Merge pull request #458 from danditomaso/feat/add-error-boundary
feat: add error boundary
2025-03-02 22:12:17 -05:00
Dan Ditomaso
fc713f55fe fix: update package json scripts commands 2025-03-02 15:54:30 -05:00
Dan Ditomaso
81a1e0e550 fix: update process.env to import.meta 2025-03-02 15:23:57 -05:00
Dan Ditomaso
88587f358d refactor: move tsconfig into deno.json 2025-03-02 15:23:22 -05:00
Dan Ditomaso
99a66bfdc7 chore: updated readme 2025-03-02 14:52:46 -05:00
Dan Ditomaso
50eb2a827f chore: fixes from deno linting/formatting 2025-03-02 14:46:02 -05:00
Dan Ditomaso
db08542b39 refactor: switch to deno 2025-03-02 14:25:29 -05:00
Dan Ditomaso
cbcbafc4a3 fix: added a notes fild to Dynamic form allowing for notes to inform users about using BLE and wifi at the same time. (#474) 2025-03-02 14:43:07 +08:00
Tilen Komel
7e66dc7cac Merge pull request #476 from meshtastic/chore/removal-of-mapbox
Chore: Removal of mapbox-gl
2025-03-01 22:52:13 +01:00
Tilen Komel
62fa8df8d2 bun remove mapbox-gl 2025-03-01 22:08:37 +01:00
Dan Ditomaso
b888d8f4cf Merge branch 'master' into feat/add-error-boundary 2025-03-01 09:09:57 -05:00
Sacha Weatherstone
e224a4ebdf migrate to new transport-http 2025-03-01 16:12:56 +08:00
Sacha Weatherstone
977b5647f6 format 2025-03-01 14:14:21 +08:00
Sacha Weatherstone
5a62b67e79 use new packages for imports & new webserial transport 2025-03-01 14:00:06 +08:00
Sacha Weatherstone
f31ac24707 fix formatting 2025-03-01 13:34:05 +08:00
Sacha Weatherstone
a5589e232b update dependencies 2025-03-01 13:33:35 +08:00
Hunter Thornsberry
f65f750b7f Merge pull request #475 from danditomaso/issue-473-label-inputs-not-connected
fix: connected labels to inputs, improving accessibility and testability
2025-02-28 21:38:46 -05:00
Dan Ditomaso
fa85e83817 fix: connected labels to inputs, improving accessibility and testability 2025-02-28 14:59:17 -05:00
Dan Ditomaso
d978978677 Merge branch 'master' into feat/add-error-boundary 2025-02-28 09:05:03 -05:00
Dan Ditomaso
593c08f3e0 Merge pull request #469 from danditomaso/feat/add-react-scan-debugging
feat: add react scan debugging
2025-02-26 21:15:33 -05:00
Dan Ditomaso
801f8f38d2 Merge branch 'master' into feat/add-react-scan-debugging 2025-02-26 21:10:02 -05:00
Dan Ditomaso
8be849d982 Merge pull request #468 from danditomaso/issue-182-pwa-funcionality
feat: add PWA functionality to vite
2025-02-26 20:51:30 -05:00
Dan Ditomaso
8bfa58540b updated readme to discuss react scan 2025-02-26 20:47:47 -05:00
Dan Ditomaso
4a6eb0d3f8 feat: add react scan debugging 2025-02-26 14:24:56 -05:00
Dan Ditomaso
5c6ba38655 updated readme for pwa changes 2025-02-26 09:33:10 -05:00
Dan Ditomaso
5831967603 feat: added pwa functionality to vite 2025-02-26 09:27:08 -05:00
Dan Ditomaso
fe2360baf6 Merge pull request #466 from danditomaso/fix/tailwind-4-styling-updates
dark mode styling updates
2025-02-25 20:01:53 -05:00
Dan Ditomaso
3db2ede9d6 Merge pull request #462 from danditomaso/feat/add-support-for-unit-testing
feat: adding unit/ui testing support
2025-02-25 20:01:37 -05:00
Dan Ditomaso
25cd448d69 Merge pull request #460 from danditomaso/issue-389-admin-key-not-taking
fix: admin key can be saved/restored inside of web ui.
2025-02-25 20:01:22 -05:00
Tilen Komel
c6bb11ccc8 Merge pull request #467 from KomelT/fix/issue-465
Fix/issue 465
2025-02-25 21:44:41 +01:00
Dan Ditomaso
d8bf2f7d8e fix: updates to overall site styling in dark mode 2025-02-25 15:42:14 -05:00
Tilen Komel
079e60677e Biome 2025-02-25 20:55:15 +01:00
Tilen Komel
379ac46ec8 Add fix 2025-02-25 20:54:53 +01:00
Dan Ditomaso
f4417f984b fixed unsafe array access in component 2025-02-25 11:53:32 -05:00
Dan Ditomaso
9f8d88bb4e fix overflow issue with stack trace 2025-02-25 11:47:51 -05:00
Dan Ditomaso
cf4c3c1376 fixed unsafe array access in component 2025-02-25 09:40:59 -05:00
Dan Ditomaso
c39ef5916f running tests in CI/CD pipeline 2025-02-25 08:00:40 -05:00
Tilen Komel
edee3571be Merge pull request #464 from meshtastic/fix-readme-update
Update README.md
2025-02-25 13:48:08 +01:00
Tilen Komel
d039f7fd79 Fix readme warning section 2025-02-25 13:37:36 +01:00
Tilen Komel
34abbcb467 Update README.md 2025-02-25 13:22:13 +01:00
Dan Ditomaso
1d93e358ed Merge pull request #461 from danditomaso/fix/improve-github-issue-templates
fix: improved github feature request and bug templates
2025-02-24 19:42:40 -05:00
Dan Ditomaso
b4ada0449c Merge pull request #463 from meshtastic/revert-436-feat-add-formatting-github-action
Revert "feat: apply formatting as github action"
2025-02-24 15:17:16 -05:00
Dan Ditomaso
4490d178d0 Revert "feat: apply formatting as github action" 2025-02-24 15:14:57 -05:00
Dan Ditomaso
c9536c9ffd added some tests to utils folder 2025-02-24 12:39:25 -05:00
Dan Ditomaso
14e9bd304a feat: adding unit/ui testing support 2025-02-24 11:46:35 -05:00
Dan Ditomaso
5df409475e feat: adding unit/ui testing support 2025-02-24 11:45:34 -05:00
Dan Ditomaso
7427623c6e feat: added PR template 2025-02-24 11:25:14 -05:00
Dan Ditomaso
16d04bb878 fix: improved github feature request and bug templates 2025-02-24 10:05:58 -05:00
Dan Ditomaso
9eda22b5db Reduced code duplication of ErrorBoundary, wrapped PageLayout with ErrorBoundary. 2025-02-24 09:38:49 -05:00
Dan Ditomaso
ebf64b5bcb fix: admin key can be saved/restored inside of web ui. 2025-02-23 22:17:08 -05:00
Dan Ditomaso
fb2a057c05 feat: add chirpy to error page 2025-02-22 15:14:44 -05:00
Dan Ditomaso
635d0673bf feat: add error boundary 2025-02-22 13:51:50 -05:00
Hunter Thornsberry
864f8075d9 Merge pull request #454 from danditomaso/feat/add-nightly-gh-action 2025-02-22 11:42:49 -05:00
Dan Ditomaso
92a84af454 feat: added nightly github action. updated docs 2025-02-21 16:30:48 -05:00
Tilen Komel
1560d1e18c Merge pull request #452 from danditomaso/fix/switch-pnpm-to-bun
fix: changed pnpm to bun
2025-02-21 18:50:11 +01:00
Dan Ditomaso
bbb8384d66 fix: changed pnpm to bun 2025-02-21 12:39:46 -05:00
Dan Ditomaso
b23e197178 Merge pull request #451 from meshtastic/revert-446-fix/update-lock-file-version
Revert "fix: update pnpm lock file version"
2025-02-21 12:24:16 -05:00
Dan Ditomaso
ff6515269b Revert "fix: update pnpm lock file version" 2025-02-21 12:24:00 -05:00
Dan Ditomaso
d6f2d3a73f Merge pull request #450 from meshtastic/revert-448-fix/try-bun-package-manager
Revert "fix: Try using bun package manager to fix lock files issues in vercel"
2025-02-21 12:23:30 -05:00
Dan Ditomaso
9b598aa3d7 Revert "fix: Try using bun package manager to fix lock files issues in vercel" 2025-02-21 12:23:00 -05:00
Dan Ditomaso
88e0c6deda Merge pull request #448 from danditomaso/fix/try-bun-package-manager
fix: Try using bun package manager to fix lock files issues in vercel
2025-02-21 08:20:44 -05:00
Dan Ditomaso
1f3ff3dc03 fix: try using bun package manager with vercel 2025-02-21 08:17:30 -05:00
Dan Ditomaso
9cb449aa31 Merge pull request #446 from danditomaso/fix/update-lock-file-version
fix: update pnpm lock file version
2025-02-20 22:07:58 -05:00
Dan Ditomaso
3afbc9a693 manually specify pnpm version in gh workflows 2025-02-20 22:06:06 -05:00
Dan Ditomaso
4ce84549c7 fix: update pnpm lock file version 2025-02-20 22:03:23 -05:00
Dan Ditomaso
ac550d3b44 Merge pull request #445 from danditomaso/fix/switch-style-issue
fix: improve switch dark mode style, fixed merge conflict bug in HTTP component.
2025-02-20 21:23:13 -05:00
Dan Ditomaso
c9dab8d83c fix: improved dark mode styles, fixed bug in http. 2025-02-20 21:21:33 -05:00
Dan Ditomaso
5e18c8d256 Merge pull request #417 from danditomaso/switch-rsbuild-to-vite
feat: switch to Vite 6.x
2025-02-20 21:02:30 -05:00
Dan Ditomaso
349a2bf855 Merge branch 'master' into switch-rsbuild-to-vite 2025-02-20 21:01:58 -05:00
Dan Ditomaso
3928d378f5 feat: updated app to tailwind 4.x 2025-02-20 20:54:19 -05:00
Dan Ditomaso
36d4f22007 feat: add required engines to package.json 2025-02-20 20:54:09 -05:00
Dan Ditomaso
cbbe9be819 fix: validate bluetooth pin is 6 characters. 2025-02-20 20:52:08 -05:00
Dan Ditomaso
ede6523678 feat: updated app to tailwind 4.x 2025-02-20 20:51:08 -05:00
Dan Ditomaso
ce086ffa82 fix: fixing styling issues 2025-02-20 19:00:47 -05:00
Dan Ditomaso
a1715ef686 feat: switch to vite 6.1 2025-02-20 15:59:00 -05:00
Justin
2c8b206903 fixed tls switch for http connections 2025-02-20 15:57:54 -05:00
Dan Ditomaso
5938f91861 fix: validate bluetooth pin is 6 characters. 2025-02-20 15:57:38 -05:00
Dan Ditomaso
b959a59e7b feat: updated app to tailwind 4.x 2025-02-20 15:56:51 -05:00
Dan Ditomaso
39c1176311 feat: add required engines to package.json 2025-02-20 15:40:08 -05:00
Dan Ditomaso
a10230b412 Merge pull request #442 from justinforlenza/tls-switch-fix
fix: tls switch for http connections
2025-02-20 15:34:47 -05:00
Dan Ditomaso
c9416a31a2 Merge pull request #444 from danditomaso/hotfix/remove-unneeded-import
fix: removed hook from device config
2025-02-20 14:12:36 -05:00
Dan Ditomaso
f7fe56535d fix: removed hook from device config 2025-02-20 14:12:01 -05:00
Justin
ec10b63d11 fixed tls switch for http connections 2025-02-20 18:09:48 +00:00
Dan Ditomaso
a5131352c7 Merge pull request #441 from danditomaso/fix/refresh-lock-file
fix: refreshed lock file
2025-02-20 13:09:37 -05:00
Dan Ditomaso
9f0a794f22 fix: refreshed lock file 2025-02-20 13:09:03 -05:00
Dan Ditomaso
63ecb12fcf Merge pull request #432 from danditomaso/feat/update-tailwind
feat: update to tailwind 4.x
2025-02-20 13:03:53 -05:00
Dan Ditomaso
5a4774e9f3 Merge branch 'master' into feat/update-tailwind 2025-02-20 13:03:40 -05:00
Dan Ditomaso
35be1bee59 Merge pull request #439 from danditomaso/fix/validate-ble-pin
fix: Add 6-digit BLE PIN validation and error management
2025-02-19 22:45:10 -05:00
Dan Ditomaso
a7a9ba0463 fix: validate bluetooth pin is 6 characters. 2025-02-19 22:29:42 -05:00
Tom Fifield
0b7bdda4bf Merge branch 'master' into switch-rsbuild-to-vite 2025-02-20 08:40:29 +08:00
Tom Fifield
2612997355 Merge pull request #436 from danditomaso/feat-add-formatting-github-action
feat: apply formatting as github action
2025-02-20 08:24:16 +08:00
Dan Ditomaso
84ca90ae97 fix: further action changes. 2025-02-19 11:47:07 -05:00
Dan Ditomaso
8c693f1956 fix: improve action to fix checkout issue 2025-02-19 11:44:36 -05:00
Dan Ditomaso
764328593d changed name of github action 2025-02-19 11:33:20 -05:00
Dan Ditomaso
e9b6c2495c feat: apply biome formatting as github action 2025-02-19 11:28:23 -05:00
Hunter Thornsberry
f4bddefd33 Merge pull request #435 from danditomaso/fix/allow-building-without-formatting 2025-02-19 10:29:45 -05:00
Dan Ditomaso
033409351e fix: allow app to build without require formatting 2025-02-19 10:22:21 -05:00
Dan Ditomaso
a6a66a7672 Merge pull request #434 from danditomaso/chore/fix-formatting-issue
chore: format http file
2025-02-19 10:13:50 -05:00
Dan Ditomaso
1f55d08adf Merge branch 'master' into chore/fix-formatting-issue 2025-02-19 10:13:12 -05:00
Dan Ditomaso
664fd1c2d0 chore: format file 2025-02-19 10:11:42 -05:00
Dan Ditomaso
c9572af445 Merge branch 'master' into feat/update-tailwind 2025-02-19 09:24:08 -05:00
Dan Ditomaso
98edd30360 Merge pull request #250 from Hunter275/issue-249-cors
add ability to enable/disable HTTPS connections
2025-02-19 09:09:18 -05:00
Dan Ditomaso
ab311bfa86 Merge branch 'master' into issue-249-cors 2025-02-19 09:07:08 -05:00
Dan Ditomaso
f4d58a9ea9 feat: updated app to tailwind 4.x 2025-02-19 07:47:02 -05:00
Dan Ditomaso
e769143e58 Merge pull request #430 from PhotoNomad0/bugfix-fixMapCrashHwModel
bugfix for Map crash
2025-02-18 13:03:14 -05:00
Dan Ditomaso
3c37899fe4 Merge pull request #307 from KomelT/feature/node-quick-options
[Feature request]: Nodes quick options
2025-02-18 13:02:08 -05:00
Tilen Komel
30d36789c1 Biome 2025-02-18 16:51:29 +01:00
Tilen Komel
971541a516 Merge branch 'master' into feature/node-quick-options 2025-02-18 16:50:56 +01:00
Dan Ditomaso
ca9e7d5c73 Merge pull request #429 from skeemer/add-avatar-to-node-table
Add Avator to nodes table
2025-02-18 10:21:26 -05:00
PhotoNomad0
61529675ec fix for Map crash on unsupported hwModel in Node. 2025-02-18 07:17:29 -05:00
Leo Lutz
22887b4dd6 Add avator to nodes table 2025-02-17 23:36:21 -07:00
Dan Ditomaso
c4383f4bd2 Merge pull request #423 from danditomaso/issue-407-text-style-messages
feat: added text style chat messages
2025-02-17 23:20:31 -05:00
Dan Ditomaso
0f0751e4d2 Merge pull request #409 from danditomaso/fix/map-zooms-on-click
Fixed map zooming when clicking on map marker. Fixed popup alignment to Map Marker
2025-02-17 23:20:13 -05:00
Dan Ditomaso
2a460dfdba Merge branch 'master' into issue-407-text-style-messages 2025-02-17 13:00:27 -05:00
Dan Ditomaso
2e42620d67 Merge pull request #426 from danditomaso/issue-425-toasts-in-light-mode
fix: fixed issue with toast background opacity in light mode
2025-02-16 20:05:50 -05:00
Dan Ditomaso
5cc24fd6ab fix: added border around markers 2025-02-14 11:41:06 -05:00
Dan Ditomaso
1c59d0451a fix: lighted border of outline button in dark mode 2025-02-14 11:30:28 -05:00
Dan Ditomaso
6d26996d65 fix: fixed issue with toast background opacity in light mode 2025-02-14 11:26:12 -05:00
Hunter Thornsberry
5f2f929af8 Merge pull request #416 from danditomaso/issue-415-message-input-allows-empty-messages
fix: prevent empty/blank messages from being sent.
2025-02-13 23:25:27 -05:00
Hunter Thornsberry
e129b7b469 Merge pull request #411 from danditomaso/issue-399-wrong-measurement-unit-in-map
feat: adding pluralizer util. fix: measurement unit in marker popup
2025-02-13 23:22:56 -05:00
Dan Ditomaso
c6f70a7b77 feat: added text style chat messages 2025-02-13 15:36:44 -05:00
Tom Fifield
f2a2e5ddf2 Merge pull request #285 from sgtwilko/patch-1
Use window.location.host so that websites run on non-standard ports i…
2025-02-12 08:48:17 +08:00
Dan Ditomaso
b7f92388c5 fix: added dark modifer to message input (#414) 2025-02-11 21:03:20 +01:00
Dan Ditomaso
8105f89c55 feat: switch to vite 6.1 2025-02-10 13:31:33 -05:00
Dan Ditomaso
467effa62e fix: prevent empty/blank messages from being sent. fix: count chars during copy/paste action 2025-02-10 12:24:57 -05:00
Dan Ditomaso
2627b9035d feat: adding pluralizer util. fix: measurement unit in marker popup 2025-02-08 23:11:54 -05:00
Dan Ditomaso
d9aff93993 fix: removed map zoom on marker click. feat: lined up popup with map marker 2025-02-08 21:49:17 -05:00
Dan Ditomaso
1fc72aa7be fix: keep existing zoom on map ndoe click 2025-02-08 08:32:29 -05:00
Hunter Thornsberry
b7067923c0 Merge pull request #394 from danditomaso/feat/add-multi-select
Feat: Add MultiSelect component
2025-02-07 23:42:35 -05:00
Hunter Thornsberry
ed3ae2622e Merge pull request #390 from danditomaso/fix/improve-styling-of-chat
fix: improve styling of messages
2025-02-07 23:34:43 -05:00
Hunter275
e3cc95cfd8 add missing maxBytes to MessageInput 2025-02-07 23:28:55 -05:00
Dan Ditomaso
9456495a3d Merge pull request #406 from danditomaso/feat--update-required-pnpm-version
fix: Updated required pnpm version to use latest 10.x version
2025-02-07 20:10:03 -05:00
Dan Ditomaso
53fe300fe9 fix: updating position flags to be more readable 2025-02-07 11:08:59 -05:00
Dan Ditomaso
76aea1a038 fix: updated required package manager to use latest vrsion 2025-02-07 10:45:40 -05:00
Dan Ditomaso
921f9b21a2 fix: added overflow scroll back to page layout 2025-02-07 10:39:23 -05:00
Dan Ditomaso
d552dcd137 Merge branch 'master' into fix/improve-styling-of-chat 2025-02-07 10:25:15 -05:00
Tilen Komel
9505284e61 Merge remote-tracking branch 'origin/HEAD' into feature/node-quick-options 2025-02-06 07:33:48 +01:00
Tom Fifield
d53acf204c Merge pull request #405 from Hunter275/issue-402-message-length-limit
Limit the length of messages to 200 bytes
2025-02-06 08:23:26 +08:00
Tilen Komel
175e98a080 Add location respone dialog 2025-02-05 21:45:31 +01:00
Tilen Komel
b8c1096568 Add short name row to nodes page 2025-02-05 21:06:55 +01:00
Hunter275
430e0cbd46 Limit the length of messages to 200 bytes 2025-02-04 23:30:39 -05:00
Tilen Komel
c2a2e0ac19 Add direct message button 2025-02-04 22:52:14 +01:00
Tilen Komel
5a1c207ffc Remove useEffect 2025-02-04 21:50:11 +01:00
Dan Ditomaso
1c7b466e64 fix: styling fixes from code review 2025-02-04 14:46:46 -05:00
Hunter Thornsberry
30158ca5c5 Merge pull request #401 from danditomaso/feat--add-formatting-linting-on-staging 2025-02-03 21:04:06 -05:00
Dan Ditomaso
e896555694 added: added lint-staged so linting runs when a file is staged. 2025-02-03 20:35:31 -05:00
Dan Ditomaso
87ddaad966 fix: styling updates, chat conversation moved to bottom of chat window. 2025-02-03 20:28:01 -05:00
Dan Ditomaso
4736fa6b50 fix: increasing size of clickable area for navigation items 2025-02-03 16:06:24 -05:00
Tilen Komel
44b8dd308a Update tooltip 2025-02-03 21:23:08 +01:00
Dan Ditomaso
a4e21ed343 feat: added hover tooltip on message status 2025-02-03 13:17:57 -05:00
Tilen Komel
48eb931c37 Add time toltip to timeAgo 2025-02-03 19:07:27 +01:00
Dan Ditomaso
794d214636 feat: removed traceroute from message screen. 2025-02-03 12:36:33 -05:00
Tilen Komel
fb1b4c6cc5 Update according to change request 2025-02-03 18:27:22 +01:00
Tilen Komel
76de3e0e02 Biome 2025-02-03 17:19:27 +01:00
Thomas Göttgens
3f49dc2595 Merge branch 'master' into feature/node-quick-options 2025-02-03 13:55:33 +01:00
Tilen Komel
28c5fd64fe Add overflow-x-scroll to Node details dialog raw metrics 2025-02-02 16:05:35 +01:00
Tilen Komel
9d9c46f732 Remove unused ariables from Nodes.tsx 2025-02-02 14:10:48 +01:00
Tilen Komel
358f8a94d0 Update timeAgo to use Intl.RelativeTimeFormat 2025-02-02 14:02:47 +01:00
Tilen Komel
0c8901b5b2 Make all raw data accordion in node details dialog 2025-02-02 12:58:35 +01:00
Tilen Komel
4abc78fff3 Add basic accordion element 2025-02-02 12:56:07 +01:00
Hunter Thornsberry
095f1fde27 Merge pull request #349 from cmorg3/add_search_nodes
feat: Add search functionality for nodes in Messages and Nodes pages
2025-02-02 00:12:55 -05:00
Hunter Thornsberry
c2c7510dc4 Merge branch 'master' into add_search_nodes 2025-02-01 13:26:02 -05:00
Hunter Thornsberry
1c70fb8606 Merge pull request #391 from dzienisz/map-key
fix: Map Action Buttons key
2025-02-01 13:17:10 -05:00
Dan Ditomaso
75d6817012 chore: linting fix 2025-01-31 16:17:59 -05:00
Dan Ditomaso
3d3b59686c feat: add multi select component. feat: add multi select to position flags section 2025-01-31 16:11:10 -05:00
Hunter Thornsberry
963deeca75 Merge pull request #393 from danditomaso/fix/fix-tak-tracker-role-value
fix: update value for tak tracker device role
2025-01-30 16:02:07 -05:00
Dan Ditomaso
991405c7aa fix: update value for tak tracker device role 2025-01-30 14:21:24 -05:00
Kamil Dzieniszewski
ce66e55196 fix: Map Action Buttons key 2025-01-30 19:53:04 +01:00
Tilen Komel
7142f0f8d5 Add dialog Node Details 2025-01-30 18:03:56 +01:00
Tilen Komel
8ac714a5b5 Update TimeAgo 2025-01-30 18:03:28 +01:00
Tilen Komel
97d206a9d4 Add Uptime.tsx 2025-01-30 15:25:54 +01:00
Tilen Komel
fad6f72dd1 Add DeviceImage.tsx and hardware svgs 2025-01-30 15:17:05 +01:00
Tilen Komel
3f09e6de93 Remove timeago-react 2025-01-30 15:04:07 +01:00
Hunter Thornsberry
547b86f98e Merge pull request #366 from kylewistrand/dashboard-node-links
feat: Make nodes on dashboard clickable
2025-01-29 23:51:42 -05:00
Hunter275
2bdfbedeea add dark mode classes and biome fixes 2025-01-29 23:50:38 -05:00
Hunter Thornsberry
68da810a85 Merge pull request #387 from danditomaso/feat/add-build-analyze-script
feat: add bundle analyzer script
2025-01-29 23:23:17 -05:00
Dan Ditomaso
d9ad044ecd fix: improve styling of messsges 2025-01-29 16:08:55 -05:00
Dan Ditomaso
5b7b770aee feat: add bundle analyzer script 2025-01-29 09:07:57 -05:00
Tilen Komel
b3cde1bcd7 Move remove button to quick options 2025-01-29 13:56:48 +01:00
Tilen Komel
9eeed9630b Fix 2025-01-29 13:15:52 +01:00
Tilen Komel
617b452da5 Merge branch 'master' into feature/node-quick-options 2025-01-29 11:50:33 +01:00
Dan Ditomaso
eb0ea4ea24 Merge branch 'master' into issue-249-cors 2025-01-28 21:32:34 -05:00
Hunter Thornsberry
c7e2baea1b Merge pull request #385 from danditomaso/fix/update-project-deps-to-latest
fix: update packages to latest version
2025-01-28 15:53:52 -05:00
Dan Ditomaso
172e0c70c8 Merge pull request #383 from danditomaso/feat/debounce-messages-input
Add debounce to message input field
2025-01-28 15:36:53 -05:00
Dan Ditomaso
9b843f6483 chore: update packages to latest version. feat: replace hashicon lib with vanilla component 2025-01-28 12:15:04 -05:00
Dan Ditomaso
e405a91ba8 Merge branch 'master' into feat/debounce-messages-input 2025-01-27 13:01:34 -05:00
Dan Ditomaso
cdc2554af6 fix: update lock file 2025-01-27 08:31:31 -05:00
Hunter Thornsberry
2808b6f26a Merge pull request #384 from danditomaso/feat/force-format-lint-before-commit
Added simple-git-hooks to run linting/formatting script prior to commit
2025-01-25 22:47:58 -05:00
Hunter Thornsberry
41ff10c653 Merge pull request #374 from tdhawk/fix-readme
update container port
2025-01-25 22:43:42 -05:00
Dan Ditomaso
4508428160 feat: add simple-git-hooks to run linting/formatting prior to commit 2025-01-25 21:49:37 -05:00
Dan Ditomaso
f0dd426055 chore: linting 2025-01-25 21:24:43 -05:00
Dan Ditomaso
d0ca24ae6f feat: debounce message draft state updates to reduce zustand/immer store writes on Messages page 2025-01-25 16:41:42 -05:00
Dan Ditomaso
fe2b76eeb9 fix: moving tailwind libs to dev dependency, removed unused deps 2025-01-25 16:09:59 -05:00
Tilen Komel
2fbf2a1173 Fix show ?? if null 2025-01-24 12:26:28 +01:00
Tilen Komel
c9377295db Merge branch 'master' into feature/node-quick-options 2025-01-24 12:18:10 +01:00
Tilen Komel
54c73a8c0d Add snr to traceroute 2025-01-24 12:15:57 +01:00
Hunter Thornsberry
5d0f2e4403 Merge pull request #381 from KomelT/fix/biome
Fix Biome
2025-01-23 22:54:52 -05:00
Tilen Komel
47150c649f Add back pnpm check into build command 2025-01-24 01:50:41 +01:00
Tilen Komel
7930f44109 Try to fix old stuff 2025-01-24 01:42:01 +01:00
Tilen Komel
ec6906a5c3 Fixes by hand 2025-01-24 01:27:33 +01:00
Tilen Komel
891a1a9503 pnpm check:fix 2025-01-24 01:26:15 +01:00
Tilen Komel
965e3247b0 Added 2way traceroute 2025-01-24 00:49:27 +01:00
Tilen Komel
f24041651c Fix legacy import 2025-01-24 00:03:55 +01:00
Tilen Komel
3f88373dd8 Merge branch 'master' into feature/node-quick-options 2025-01-23 23:57:05 +01:00
Tilen Komel
aa66e1f73c Biome 2025-01-23 23:45:24 +01:00
Hunter Thornsberry
cf79f3b07e Merge pull request #379 from KomelT/add-package-manager
Add packageManager in package.json
2025-01-23 16:25:58 -05:00
Tilen Komel
9493649a69 This works now 2025-01-23 14:02:21 +01:00
Tilen Komel
c8cd5b0eaa Define package_json_file: package.json in CI 2025-01-23 13:49:44 +01:00
Tilen Komel
c16b3a3ce0 Add version 2025-01-23 13:35:12 +01:00
Tilen Komel
2967a74480 Blank version 2025-01-23 13:33:13 +01:00
Tilen Komel
ea648ca887 Just for joke without actions/checkout@v4 2025-01-23 13:30:16 +01:00
Tilen Komel
8d35b57d19 Add packageManager in package.json 2025-01-23 12:59:57 +01:00
Hunter Thornsberry
be9a07bc4b Merge pull request #360 from danditomaso/add_pki_backup_dialog
Add PKI backup dialog, add reminder toast to suggest backing up private key.
2025-01-22 22:35:41 -05:00
Tom Fifield
7d37c6e728 Merge pull request #378 from Hunter275/ci-pnpm-version
remove version spec
2025-01-21 13:54:11 +11:00
Hunter275
42ccd953c0 remove version spec 2025-01-20 20:20:50 -05:00
Hunter Thornsberry
578405d5d3 Merge pull request #362 from danditomaso/update_ble_serial_wording_https
fix: update connect dialog messaging to describe requirement for https
2025-01-20 14:59:51 -05:00
Dan Ditomaso
ebb32f0893 fix: changed position of error message, disabled buttons when error is showing 2025-01-19 22:42:38 -05:00
Hunter Thornsberry
9cef18b82a Merge pull request #370 from PhotoNomad0/feature-addDateToDisplayedMessages
Feature / Added date to messages displayed
2025-01-19 02:25:14 -05:00
Hunter Thornsberry
88510a6ffe Merge pull request #367 from danielhogstrom/sidebar-close-button
feat: Add sidebar close button
2025-01-19 02:21:10 -05:00
Hunter275
be6acc5ef2 add right border 2025-01-19 00:50:23 -05:00
Anthony Hawk
4e35cf326e updated container port 2025-01-18 14:10:30 -06:00
Dan Ditomaso
225d6055d4 Improved error messaging based on feature missing from browser 2025-01-15 20:11:46 -05:00
Dan Ditomaso
7884991ac6 Refactor based on code review. Improved typing of useCookie hook. Added duration/delay to control length of toast. 2025-01-14 21:51:06 -05:00
PhotoNomad0
2d041ab6d0 Added date to messages displayed 2025-01-13 06:37:18 -05:00
Daniel
c2cdc92ae9 feat: Add sidebar close button 2025-01-12 18:59:12 +01:00
Tom Fifield
6b10d35e1a Merge pull request #365 from kylewistrand/fix-node-detail-dark-mode
fix: Node map detail dark mode
2025-01-12 13:38:45 +08:00
Kyle Wistrand
790f93322e feat: Make nodes on dashboard clickable 2025-01-11 20:56:03 -08:00
Kyle Wistrand
b50edb2762 fix: Node map detail dark mode 2025-01-11 20:31:33 -08:00
Hunter Thornsberry
88e06a1bea Merge pull request #350 from kylewistrand/add_map_node_detail
feat: Add Node detail popup in Map view
2025-01-10 14:17:48 -05:00
Dan Ditomaso
07d4204e86 Merge branch 'add_pki_backup_dialog' of https://github.com/danditomaso/meshtastic-web into add_pki_backup_dialog 2025-01-07 15:11:58 -05:00
Dan Ditomaso
0e78d0bd50 feat: added delay to toast appearing to avoid conflicting with messages/node loading. 2025-01-07 15:09:00 -05:00
Dan Ditomaso
f3a3741216 feat: Add key backup reminder. Refactor Toast component to handle dark mode better 2025-01-07 15:08:01 -05:00
Dan Ditomaso
74e33d09b1 feat: Add pki backup dialog, refactor Channels pre-shared key to support regenerate dialog 2025-01-07 15:08:01 -05:00
Tom Fifield
ecd50148de Merge pull request #363 from danditomaso/add_empty_psk_size
Added empty value to pre-shared key size. Feature parity with IOS app
2025-01-07 13:58:20 +08:00
Hunter Thornsberry
08a28eeb68 Merge branch 'master' into add_map_node_detail 2025-01-07 00:05:45 -05:00
Dan Ditomaso
cbabcd4782 feat: added delay to toast appearing to avoid conflicting with messages/node loading. 2025-01-06 16:00:13 -05:00
Dan Ditomaso
63be65a487 feat: added empty value to pre-shared key size. 2025-01-06 13:45:45 -05:00
Dan Ditomaso
1c8476df53 refactor: consolidate browser feature detection into typed hook. Update connect dialog messaging to describe requirement for https when conneecting 2025-01-06 11:43:36 -05:00
Dan Ditomaso
7cd03c6a52 feat: Add key backup reminder. Refactor Toast component to handle dark mode better 2025-01-03 21:40:56 -05:00
Dan Ditomaso
db09711be5 feat: Add pki backup dialog, refactor Channels pre-shared key to support regenerate dialog 2025-01-03 12:05:29 -05:00
Hunter Thornsberry
94c6eea20b Merge pull request #317 from jangrewe/master
Update Containerfile to use plain Nginx
2024-12-30 14:46:52 -05:00
Jan Grewe
1ec3aa07d3 Merge branch 'meshtastic:master' into master 2024-12-30 16:19:16 +01:00
Tom Fifield
1087c68541 Merge pull request #356 from danditomaso/add_attribution_control_to_map
feat: Add AttributionControl to Map to comply with OSM license requirements
2024-12-29 13:01:53 +11:00
Dan Ditomaso
856556c12b feat: Add AttributionControl to Map to comply with OSM license requirements 2024-12-28 20:21:40 -05:00
Tom Fifield
fc24a389c0 Merge pull request #353 from meshtastic/dependabot/npm_and_yarn/nanoid-3.3.8
Bump nanoid from 3.3.7 to 3.3.8
2024-12-14 22:16:31 +11:00
dependabot[bot]
5687485154 Bump nanoid from 3.3.7 to 3.3.8
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-14 09:37:41 +00:00
Jan Grewe
654d6c64c1 Merge remote-tracking branch 'upstream/master' 2024-12-02 11:28:43 +01:00
Kyle Wistrand
fed6b2a6da feat: Add Node detail popup in Map view 2024-11-26 18:45:59 -08:00
Connor Morgan
79806cc6a2 feat: Add search functionality for nodes in Messages and Nodes pages 2024-11-24 22:17:17 -06:00
Hunter Thornsberry
e78aa2df61 Merge pull request #332 from Nestpebble/patch-1
Improve Managed Node description
2024-11-24 17:16:25 -05:00
Hunter Thornsberry
03173f6a37 Merge pull request #347 from meshtastic/dependabot/npm_and_yarn/cross-spawn-7.0.6
Bump cross-spawn from 7.0.3 to 7.0.6
2024-11-24 17:11:10 -05:00
dependabot[bot]
3fa73894ed Bump cross-spawn from 7.0.3 to 7.0.6
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-20 15:01:05 +00:00
Tom Fifield
a147324913 Merge pull request #344 from meshtastic/CVE-2024-45812
Update vite to 5.3.6
2024-11-12 10:42:30 +08:00
Tom Fifield
0fc4211c13 Update vite to 5.3.6
Fixes CVE-2024-45812
2024-11-12 10:40:47 +08:00
Tom Fifield
6df0b287ef Update vite to 5.3.6
Fixes CVE-2024-45812
2024-11-12 10:33:02 +08:00
Tom Fifield
2f9af111c8 Update vite to 5.3.6
Fixes CVE-2024-45812
2024-11-12 10:29:27 +08:00
Hunter Thornsberry
615045be9f Merge pull request #339 from hectorthemonk/patch-1
Add ledHeartbeatDisabled to Device Settings in Radio Config
2024-11-10 15:05:51 -05:00
Tom
6fc183ff15 Update Security.tsx 2024-11-10 12:58:54 +00:00
hectorthemonk
9aafa681e8 Add ledHeartbeatDisabled to Device Settings in Radio Config
`led_heartbeat_disabled` in https://buf.build/meshtastic/protobufs/docs/main:meshtastic#meshtastic.Config.DeviceConfig
2024-11-10 21:34:33 +13:00
Tom Fifield
f48f9ccd62 Merge pull request #338 from Hunter275/bluetooth-pin-validation
Add bluetooth pin validation
2024-11-10 11:54:22 +08:00
Hunter275
2bd80bb5b4 Add pin validation 2024-11-09 22:39:57 -05:00
Tom Fifield
de597d2c28 Merge pull request #337 from Hunter275/issue-335-charging
If the batteryLevel is over 100, its charging
2024-11-10 10:14:15 +08:00
Hunter275
94cca88e2e If the batteryLevel is over 100, its charging 2024-11-09 21:10:55 -05:00
Hunter Thornsberry
443c7fcd48 Merge pull request #331 from fmoessbauer/master
document scalar clamping of curve25519 keys
2024-11-09 20:25:38 -05:00
Hunter Thornsberry
6138d9f8c8 Merge pull request #314 from meshtastic/dependabot/npm_and_yarn/micromatch-4.0.8
Bump micromatch from 4.0.7 to 4.0.8
2024-11-09 20:19:00 -05:00
Hunter Thornsberry
37547b54e9 Merge pull request #330 from meshtastic/dependabot/npm_and_yarn/elliptic-6.6.0
Bump elliptic from 6.5.7 to 6.6.0
2024-11-09 20:18:45 -05:00
Hunter Thornsberry
618e2f619b Merge pull request #313 from meshtastic/dependabot/npm_and_yarn/rollup-4.24.0
Bump rollup from 4.18.0 to 4.24.0
2024-11-09 20:18:34 -05:00
rcarteraz
72fc3ea337 Merge pull request #336 from rcarteraz/master
Fix Title
2024-11-09 10:43:00 -07:00
rcarteraz
82f4784107 Merge pull request #2 from rcarteraz/fix-title
Fix Title
2024-11-09 10:38:35 -07:00
rcarteraz
4f9fb9976d fix title 2024-11-09 10:37:06 -07:00
Tom
2cf7655562 Update Security.tsx 2024-11-02 01:37:28 +00:00
Felix Moessbauer
79c5638e10 document scalar clamping of curve25519 keys
The "scalars" are just random bytes. To make them secure curve25519
keys, they need to be clamped according to rfc7748 section 5.
As this is not obvious, we need to add a reference to the RFC.

No functional change.

Closes: #324
2024-11-01 12:55:54 +01:00
dependabot[bot]
f53d53ea20 Bump elliptic from 6.5.7 to 6.6.0
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-31 02:19:04 +00:00
Jan Grewe
f2760a941b Update Containerfile to use plain Nginx
Only add required files to container
Fix HTML title
2024-10-14 16:38:30 +02:00
dependabot[bot]
1e26eed861 Bump micromatch from 4.0.7 to 4.0.8
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-06 11:35:29 +00:00
dependabot[bot]
2da4a44505 Bump rollup from 4.18.0 to 4.24.0
Bumps [rollup](https://github.com/rollup/rollup) from 4.18.0 to 4.24.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.18.0...v4.24.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-06 11:35:24 +00:00
Sacha Weatherstone
42068ad3d8 move to RsBuild 2024-10-06 21:24:12 +10:00
Hunter Thornsberry
62f8c4509e Merge pull request #311 from medentem/master
Added Password Visibility Toggle
2024-10-02 23:07:11 -04:00
Hunter Thornsberry
d699764546 biome 2024-10-02 22:07:45 -04:00
medentem
8549d56c21 biome 2024-10-02 19:33:09 -05:00
medentem
4b532fc7f8 added password visibility toggle 2024-10-02 15:54:53 -05:00
Hunter Thornsberry
06d2c393ce Merge pull request #310 from Hunter275/remove-client-router
Remove router-client
2024-09-22 19:54:57 -04:00
Hunter Thornsberry
cecdf9758b remove router-client 2024-09-22 18:25:35 -04:00
Tilen Komel
a2b9a33f6a Cursor pointer 2024-09-19 10:37:48 +02:00
Hunter Thornsberry
02cb4f2584 Merge pull request #303 from Hunter275/gps-precision 2024-09-18 13:45:17 -04:00
Tilen Komel
56ac1d55f4 Implement on nodes page 2024-09-18 11:34:47 +02:00
Tilen Komel
2a5acb8771 Add node dialog 2024-09-18 11:34:27 +02:00
Tilen Komel
93e04e1b69 Add traceroute dialog 2024-09-18 11:21:15 +02:00
Tilen Komel
bd48b02ef3 Updated traceroute response 2024-09-18 11:17:18 +02:00
Hunter Thornsberry
8cfcd7b1af Merge pull request #306 from KomelT/feature/better-unknown
Unknown nodes
2024-09-17 16:26:56 -04:00
Tilen Komel
c0cb059f52 Merge branch 'meshtastic:master' into feature/better-unknown 2024-09-17 21:10:29 +02:00
Tilen Komel
a2a9b37238 Unknown to node hex on other missing places 2024-09-17 21:08:35 +02:00
Hunter Thornsberry
57d0d27bbb Merge pull request #304 from KomelT/feature/better-unknown
Unknown nodes
2024-09-17 14:54:05 -04:00
Hunter Thornsberry
0e92dd9bea There is no longer a setting here 2024-09-17 13:53:36 -04:00
Tilen Komel
c16ebf3917 Show hex on map instead of empty 2024-09-17 19:50:27 +02:00
Hunter Thornsberry
3d3a08a23f replace with select 2024-09-17 13:47:07 -04:00
Hunter Thornsberry
4d1227a942 Merge pull request #269 from KomelT/fix/static-ip-display
Fix/static ip display
2024-09-17 13:27:12 -04:00
Hunter Thornsberry
a8ee273b24 biome 2024-09-17 12:36:49 -04:00
Hunter Thornsberry
3ee7a57480 rewrite convertIpAddressToInt 2024-09-17 12:35:51 -04:00
Tilen Komel
2f2c777c56 Optimize 2024-09-17 07:32:12 +02:00
Tilen Komel
2f36118e9d Merge branch 'meshtastic:master' into fix/static-ip-display 2024-09-17 07:16:54 +02:00
Hunter Thornsberry
a6d161581f Merge pull request #300 from Hunter275/primary-channel
Only allow channel index 0 to be PRIMARY
2024-09-15 19:55:24 -04:00
Hunter Thornsberry
d05ea5a2cc Merge remote-tracking branch 'meshtastic-remote/master' into fix/static-ip-display 2024-09-15 19:39:13 -04:00
Hunter Thornsberry
471db94242 Merge branch 'master' into fix/static-ip-display 2024-09-15 19:38:03 -04:00
Hunter Thornsberry
2654e4fbc9 biome manual fixes 2024-09-15 19:24:16 -04:00
Hunter Thornsberry
f2aa5bfbee biome 2024-09-15 19:23:44 -04:00
Hunter Thornsberry
3b018b0c70 Only allow channel index 0 to be PRIMARY 2024-09-15 19:23:07 -04:00
Hunter Thornsberry
921db10d91 Merge pull request #297 from Hunter275/js-version-bump
js version bump
2024-09-12 17:20:09 -04:00
Hunter Thornsberry
bf4f593e3a use new js and remove polyfills 2024-09-12 14:20:32 -04:00
Hunter Thornsberry
1e061a1e19 polyfill 2024-09-11 23:07:52 -04:00
Hunter Thornsberry
9b9f537e2c js version bump 2024-09-11 22:13:46 -04:00
Hunter Thornsberry
985cce0b0d Merge pull request #294 from meshtastic/pki
PKI
2024-09-11 17:51:31 -04:00
Hunter Thornsberry
3fe38eb506 Merge pull request #295 from meshtastic/master
Update release.yml
2024-09-10 20:53:31 -04:00
Hunter Thornsberry
51081d3052 Update release.yml
Create a build artifact on release
2024-09-10 20:51:50 -04:00
Hunter Thornsberry
c08f6d16bb Merge branch 'master' into pki 2024-09-09 18:55:02 -04:00
Hunter Thornsberry
62ad4c49f8 Merge pull request #293 from Hunter275/pki-nodelist
Node List & DMs
2024-09-09 18:50:29 -04:00
Hunter Thornsberry
3b0a1e6108 biome 2024-09-09 18:37:22 -04:00
Hunter Thornsberry
c2f2205626 cleanup 2024-09-09 18:25:58 -04:00
Hunter Thornsberry
87c729d694 Merge branch 'pki' into pki-nodelist 2024-09-09 18:01:17 -04:00
Hunter Thornsberry
8e4f60edf3 biome 2024-09-09 16:36:56 -04:00
Hunter Thornsberry
8811eee9f5 Remove bluetooth debugging and reword generic debug 2024-09-09 16:36:17 -04:00
Hunter Thornsberry
2af93f1acd Fix protobufs, add configOkToMqtt, add PKI icons 2024-09-09 16:22:48 -04:00
Hunter Thornsberry
78a35544c7 Node list and DMs now show icons 2024-09-08 19:48:42 -04:00
Hunter Thornsberry
3ad2d650b0 update protobufs 2024-09-08 18:59:57 -04:00
Hunter Thornsberry
bf425a8ec7 Merge pull request #291 from Kongduino/patch-1 2024-09-07 12:21:47 -04:00
Kongduino
a7d0d36086 Update index.tsx
"at least", two words. Thanks...
2024-09-07 13:32:16 +08:00
sgtwilko
fd9e327c85 Use window.location.host so that websites run on non-standard ports include the port 2024-08-30 01:01:44 +01:00
Tilen Komel
8ed3ce8203 Error & Format fixing 2024-08-21 23:13:35 +02:00
Tilen Komel
ebd5a3d3a6 Implemented IP utils 2024-08-21 21:53:07 +02:00
Tilen Komel
1cdf18747d Added ip utils 2024-08-21 21:53:07 +02:00
Hunter Thornsberry
d8261a649b add disabled 2024-06-21 15:09:29 -04:00
Hunter Thornsberry
c4565d97b0 add ability to enable/disable HTTPS connections 2024-06-21 15:06:19 -04:00
487 changed files with 62204 additions and 11783 deletions

12
.githooks/_/pre-commit Executable file
View File

@@ -0,0 +1,12 @@
#!/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
deno task lint:fix && deno task format

View File

@@ -6,8 +6,20 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
# Bug Report
Thanks for taking the time to fill out this bug report! The more information you provide, the faster we can diagnose and fix the issue.
- type: checkboxes
id: prerequisites
attributes:
label: Before submitting
description: Please confirm you've completed the following steps
options:
- label: I have searched existing issues to make sure this bug hasn't already been reported
required: true
- label: I have updated to the latest version of the software to verify the issue still exists
required: true
- label: I have cleared cache/cookies/storage or tried in a private/incognito window (if applicable)
required: false
- type: dropdown
id: hardware
attributes:
@@ -41,7 +53,6 @@ body:
- Other
validations:
required: true
- type: dropdown
id: category
attributes:
@@ -54,7 +65,6 @@ body:
- Serial
validations:
required: true
- type: dropdown
id: local
attributes:
@@ -66,7 +76,6 @@ body:
- https://client.meshtastic.org
validations:
required: true
- type: input
id: version
attributes:
@@ -75,15 +84,50 @@ body:
placeholder: x.x.x.yyyyyyy
validations:
required: true
- type: textarea
id: body
- type: input
id: os
attributes:
label: Description
description: Please provide details on what steps you performed for this to happen.
label: Operating System
description: What OS are you running? Include version if possible.
placeholder: e.g., Windows 11, macOS 13.1, Android 13, iOS 16.2
validations:
required: true
- type: input
id: browser
attributes:
label: Browser
description: What browser are you using? Include version if possible.
placeholder: e.g., Chrome 108, Firefox 107, Safari 16.2
validations:
required: false
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
placeholder: Describe what you expected to occur...
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: What actually happened?
placeholder: Describe what occurred instead...
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: Provide clear steps to reproduce the issue
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: logs
attributes:
@@ -92,3 +136,21 @@ body:
render: Shell
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other context about the problem here.
validations:
required: false
- type: markdown
attributes:
value: |
Thank you for helping improve our project by reporting this bug!

View File

@@ -6,12 +6,60 @@ body:
- type: markdown
attributes:
value: |
Thanks for your request this will not gurantee that we will implement it, but it will be reviewed.
- type: textarea
id: body
Thanks for your request. While we can't guarantee implementation, all requests will be carefully reviewed.
- type: checkboxes
id: prerequisites
attributes:
label: Description
description: Please provide details about your enhancement.
label: Prerequisites
description: Please confirm the following before submitting your feature request
options:
- label: I have searched existing issues to ensure this feature hasn't already been requested
required: true
- label: I have checked the documentation to verify this feature doesn't already exist
required: true
- type: textarea
id: problem
attributes:
label: Problem Statement
description: What problem are you trying to solve? Describe the challenge or limitation you're facing.
placeholder: I'm frustrated when...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe your idea for solving the problem. What would you like to see implemented?
placeholder: It would be great if...
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Current Alternatives
description: Are there any workarounds or alternative solutions you're currently using?
placeholder: Currently, I'm working around this by...
validations:
required: false
- type: dropdown
id: importance
attributes:
label: Importance
description: How important is this feature to you?
options:
- Nice to have
- Important
- Critical
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any other context, screenshots, mockups, or examples that might help us understand your request better.
validations:
required: false
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out this feature request!

50
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,50 @@
<!--
Thank you for your contribution to our project!
-->
## Description
<!--
Provide a clear and concise description of what this PR does. Explain the problem it solves or the feature it adds.
-->
## Related Issues
<!--
Link any related issues here using the GitHub syntax: "Fixes #123" or "Relates to #456".
If there are no related issues, you can remove this section.
-->
## Changes Made
<!--
List the key changes you've made. Focus on the most important aspects that reviewers should understand.
-->
-
-
-
## Testing Done
<!--
Describe how you tested these changes (added new tests, etc).
-->
## Screenshots (if applicable)
<!--
If your changes affect the UI, include screenshots or screencasts showing the before and after.
-->
## Checklist
<!--
Check all that apply. If an item doesn't apply to your PR, you can leave it unchecked or remove it.
-->
- [ ] Code follows project style guidelines
- [ ] Documentation has been updated or added
- [ ] Tests have been added or updated
- [ ] All i18n translation labels have been added (read
CONTRIBUTING_I18N_DEVELOPER_GUIDE.md for more details)

View File

@@ -1,9 +1,9 @@
name: CI
name: Push to Main CI
on:
push:
branches:
- master
- main
permissions:
contents: write
@@ -13,14 +13,38 @@ jobs:
build-and-package:
runs-on: ubuntu-latest
steps:
- name: Checkout
- name: Checkout code
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
version: latest
deno-version: v2.x
- name: Install Dependencies
run: pnpm install
run: deno install
- name: Cache Deno dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/deno
./deno.lock
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }}
restore-keys: |
${{ runner.os }}-deno-
- name: Cache Dependencies
run: deno cache src/index.tsx
- name: Run linter
run: deno task lint
- name: Check formatter
run: deno task format --check
- name: Run tests
run: deno task test
- name: Build Package
run: pnpm build
run: deno task build

35
.github/workflows/crowdin-download.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Crowdin Download Translations Action
on:
schedule: # Every Sunday at midnight
- cron: '0 0 * * 0'
workflow_dispatch: # Allow manual triggering
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download translations with Crowdin
uses: crowdin/github-action@v2
with:
base_url: 'https://meshtastic.crowdin.com/api/v2'
config: 'crowdin.yml'
upload_sources: false
upload_translations: false
download_translations: true
localization_branch_name: i18n_crowdin_translations
commit_message: 'chore(i18n): New Crowdin Translations by GitHub Action'
create_pull_request: true
pull_request_title: 'chore(i18n): New Crowdin Translations'
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)'
pull_request_base_branch_name: 'main'
pull_request_labels: 'i18n'
crowdin_branch_name: 'main'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -0,0 +1,32 @@
name: Crowdin Upload Sources Action
on:
push:
# Monitor all .json files within the /src/i18n/locales/en/ directory.
# This ensures the workflow triggers if any the English namespace files are modified on the main branch.
paths:
- "/src/i18n/locales/en/*.json"
branches: [main]
workflow_dispatch: # Allow manual triggering
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Upload sources with Crowdin
uses: crowdin/github-action@v2
with:
base_url: "https://meshtastic.crowdin.com/api/v2"
config: "crowdin.yml"
upload_sources: true
upload_translations: false
download_translations: false
crowdin_branch_name: "main"
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -0,0 +1,25 @@
name: Crowdin Upload Translations Action
on:
workflow_dispatch: # Allow manual triggering
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Upload translations with Crowdin
uses: crowdin/github-action@v2
with:
base_url: "https://meshtastic.crowdin.com/api/v2"
config: "crowdin.yml"
upload_sources: false
upload_translations: true
download_translations: false
crowdin_branch_name: "main"
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

66
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: 'Nightly Release'
on:
schedule:
- cron: "0 5 * * *" # Run every day at 5am UTC
permissions:
contents: write
packages: write
jobs:
build-and-package:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install Dependencies
run: deno install
- name: Run tests
run: deno task test
- name: Build Package
run: deno task build
- name: Package Output
run: deno task package
- name: Archive compressed build
uses: actions/upload-artifact@v4
with:
name: build
path: dist/build.tar
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Buildah Build
id: build-container
uses: redhat-actions/buildah-build@v2
with:
containerfiles: |
./infra/Containerfile
image: ${{github.event.repository.full_name}}
tags: nightly ${{ github.sha }}
oci: true
platforms: linux/amd64, linux/arm64
- name: Push To Registry
id: push-to-registry
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.build-container.outputs.image }}
tags: ${{ steps.build-container.outputs.tags }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Print image url
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"

View File

@@ -1,6 +1,7 @@
name: Pull Request
name: Pull Request CI
on: pull_request
on:
pull_request:
jobs:
build:
@@ -9,19 +10,56 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
version: latest
deno-version: v2.x
- name: Cache Deno dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/deno
./deno.lock
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }}
restore-keys: |
${{ runner.os }}-deno-
- name: Install Dependencies
run: pnpm install
run: deno install
- name: Cache Dependencies
run: deno cache src/index.tsx
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: |
**/*.ts
**/*.tsx
# Uncomment the following lines when you have figured out how to ignore files
# - name: Type check changed files
# if: steps.changed-files.outputs.all_changed_files != ''
# run: deno check ${{ steps.changed-files.outputs.all_changed_files }}
- name: Run linter on changed files
if: steps.changed-files.outputs.all_changed_files != ''
run: deno task lint ${{ steps.changed-files.outputs.all_changed_files }}
- name: Check format on changed files
if: steps.changed-files.outputs.all_changed_files != ''
run: deno task format --check ${{ steps.changed-files.outputs.all_changed_files }}
- name: Run tests
run: deno task test
- name: Build Package
run: pnpm build
run: deno task build
- name: Compress build
run: pnpm package
run: deno task package
- name: Archive compressed build
uses: actions/upload-artifact@v4

View File

@@ -1,8 +1,8 @@
name: 'Release'
name: Release
on:
release:
types: [released]
types: [released, prereleased]
permissions:
contents: write
@@ -14,18 +14,35 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
version: latest
deno-version: v2.x
- name: Install Dependencies
run: pnpm install
run: deno install
- name: Run tests
run: deno task test
- name: Build Package
run: pnpm build
run: deno task build
- name: Package Output
run: pnpm package
run: deno task package
- name: Archive compressed build
uses: actions/upload-artifact@v4
with:
name: build
path: dist/build.tar
- name: Attach build.tar to release
run: |
gh release upload ${{ github.event.release.tag_name }} dist/build.tar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -35,7 +52,7 @@ jobs:
uses: redhat-actions/buildah-build@v2
with:
containerfiles: |
./Containerfile
./infra/Containerfile
image: ${{github.event.repository.full_name}}
tags: latest ${{ github.sha }}
oci: true

View File

@@ -0,0 +1,50 @@
name: Update Stable Branch from Main on Latest Release
on:
release:
types: [released]
permissions:
contents: write
jobs:
update-stable-branch:
name: Update Stable Branch from Main
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
git config user.name "GitHub Actions Bot"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Fetch latest main and stable branches
run: |
git fetch origin main:main
git fetch origin stable:stable || echo "Stable branch not found remotely, will create."
- name: Get latest main commit SHA
id: get_main_sha
run: echo "MAIN_SHA=$(git rev-parse main)" >> $GITHUB_ENV
- name: Check out stable branch
run: |
if git show-ref --verify --quiet refs/heads/stable; then
git checkout stable
git pull origin stable # Sync with remote stable if it exists
else
echo "Creating local stable branch based on main HEAD."
git checkout -b stable ${{ env.MAIN_SHA }}
fi
- name: Reset stable branch to latest main
run: git reset --hard ${{ env.MAIN_SHA }}
- name: Force push stable branch
run: git push origin stable --force

5
.gitignore vendored
View File

@@ -2,4 +2,7 @@ dist
node_modules
stats.html
.vercel
dev-dist
.vite
dev-dist
__screenshots__*
*.diff

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "src/core/connection"]
path = src/core/connection
url = https://github.com/meshtastic/js.git

2
.npmrc
View File

@@ -1 +1 @@
@buf:registry=https://buf.build/gen/npm/v1
@jsr:registry=https://npm.jsr.io

View File

@@ -1,3 +1,3 @@
{
"recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"]
"recommendations": ["bradlc.vscode-tailwindcss", "denoland.vscode-deno"]
}

View File

@@ -1,7 +1,6 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit"
},
"editor.formatOnSave": true
"deno.enable": true,
"deno.suggest.imports.autoDiscover": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "denoland.vscode-deno"
}

View File

@@ -0,0 +1,112 @@
# i18n Developer Guide
When developing new components, all user-facing text must be added as an i18n
key and rendered using our translation functions. This ensures your UI can be
translated into multiple languages.
## Adding New i18n Keys
### Search Before Creating
Before adding a new key, please perform a quick search to see if one that fits
your needs already exists. Many common labels like "Save," "Cancel," "Name,"
"Description," "Loading...," or "Error" are likely already present, especially
in the common.json namespace. Reusing existing keys prevents duplication and
ensures consistency across the application. Using your code editor's search
function across the /src/i18n/locales/en/ directory is an effective way to do
this.
### Key Naming and Structure Rules
To maintain consistency and ease of use, please adhere to the following rules
when creating new keys in the JSON files.
- **Keys are camelCase:** `exampleKey`, `anotherExampleKey`.
- **Avoid Deep Nesting:** One or two levels of nesting are acceptable for
grouping related keys (e.g., all labels for a specific menu). However, nesting
deeper than two levels should be avoided to maintain readability and ease of
use.
- **Good (1 level):**
```json
"buttons": {
"save": "Save",
"cancel": "Cancel"
}
```
- **Acceptable (2 levels):**
```json
"userMenu": {
"items": {
"profile": "Profile",
"settings": "Settings"
}
}
```
- **Avoid (3+ levels):**
```json
"userMenu": {
"items": {
"actions": {
"viewProfile": "View Profile"
}
}
}
```
- **Organize for Retrieval, Not UI Layout:** Keys should be named logically for
easy retrieval, not to mirror the layout of your component.
### Namespace Rules
We use namespaces to organize keys. All source keys are added to the English
(`en`) files located at `/src/i18n/locales/en/`. Place your new keys in the
appropriate file based on these rules:
- `common.json`:
- All button labels (`save`, `cancel`, `submit`, etc.).
- Any text that is repeated and used throughout the application (e.g.,
"Loading...", "Error").
- `ui.json`:
- Labels and text specific to a distinct UI element or view that isn't a
dialog or a config page.
- `dialog.json`:
- All text specific to modal dialogs (titles, body text, prompts).
- `messages.json`:
- Text specifically related to the messaging interface.
- `deviceConfig.json` & `moduleConfig.json`:
- Labels and descriptions for the settings on the Device and Module
configuration pages.
## Using i18n Keys in Components
We use the `useTranslation` hook from `react-i18next` to access the translation
function, `t`.
### Default Namespaces
Our i18next configuration has fallback namespaces configured which includes
`common`, `ui`, and `dialog`. This means you **do not** need to explicitly
specify these namespaces when calling the hook. The system will automatically
check these files for your key.
For any keys in `common.json`, `ui.json`, or `dialog.json`, you can instantiate
the hook simply:
```typescript
import { useTranslation } from "react-i18next";
// In your component
const { t } = useTranslation(["messages"]);
// Usage
return <p>{t("someMessageLabel")}</p>;
```
You can also specify the namespace on a per-call basis using the options object.
This is useful if a component primarily uses a default namespace but needs a
single key from another.
```typescript
const { t } = useTranslation();
return <p>{t("someMessageLabel", { ns: "messages" })}</p>;
```

View File

@@ -0,0 +1,31 @@
# Contributing Translations
Thank you for your interest in making the Meshtastic Web Client accessible to a
global audience! Your translation efforts are greatly appreciated.
## Our Translation Platform: Crowdin
We manage all our translations through a platform called
[Crowdin](https://crowdin.com/). This allows for a collaborative and streamlined
translation process. All translation work should be done on our Crowdin project,
not directly in the code repository via Pull Requests.
### How to Get Started
1. **Create a Crowdin Account:** If you don't already have one, sign up for a
free account on Crowdin.
2. **Join Our Project:** Please ask for a link to our specific Crowdin project
on the Meshtastic Discord.
3. **Request Translator Role:** Once you have an account, join the Meshtastic
Discord and notify an admin in the `#web` channel. They will grant you the
necessary permissions to start translating.
4. **Start Translating:** Once you have your role, you can begin translating the
source labels into your native language directly on the Crowdin platform.
### Language Activation
A new language will only be added to the web client and appear in the language
picker once its translation is 100% complete on Crowdin. The repository
maintainers will handle this process once the milestone is reached.
Thank you for helping us bring Meshtastic to more users around the world!

View File

@@ -1,5 +0,0 @@
FROM registry.access.redhat.com/ubi9/nginx-122:1-45
ADD dist .
CMD nginx -g "daemon off;"

156
README.md
View File

@@ -2,14 +2,15 @@
<!--Project specific badges here-->
[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/web/ci.yml?branch=master&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/web/actions/workflows/ci.yml)
[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/web/ci.yml?branch=main&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/web/actions/workflows/ci.yml)
[![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/web)](https://cla-assistant.io/meshtastic/web)
[![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/)
[![Vercel](https://img.shields.io/static/v1?label=Powered%20by&message=Vercel&style=flat&logo=vercel&color=000000)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
## Overview
Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted or served from a node
Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
or served from a node
**[Hosted version](https://client.meshtastic.org)**
@@ -17,45 +18,158 @@ Official [Meshtastic](https://meshtastic.org) web interface, that can be hosted
![Alt](https://repobeats.axiom.co/api/embed/e5b062db986cb005d83e81724c00cb2b9cce8e4c.svg "Repobeats analytics image")
## Progress Web App Support (PWA)
Meshtastic Web Client now includes Progressive Web App (PWA) functionality,
allowing users to:
- Install the app on desktop and mobile devices
- Access the interface offline
- Receive updates automatically
- Experience faster load times with caching
To install as a PWA:
- On desktop: Look for the install icon in your browser's address bar
- On mobile: Use "Add to Home Screen" option in your browser menu
PWA functionality works with both the hosted version and self-hosted instances.
## Self-host
The client can be self hosted using the precompiled container images with an OCI compatible runtime such as [Docker](https://www.docker.com/) or [Podman](https://podman.io/).
The base image used is [UBI9 Nginx 1.22](https://catalog.redhat.com/software/containers/ubi9/nginx-122/63f7653b9b0ca19f84f7e9a1)
The client can be self hosted using the precompiled container images with an OCI
compatible runtime such as [Docker](https://www.docker.com/) or
[Podman](https://podman.io/). The base image used is
[Nginx 1.27](https://hub.docker.com/_/nginx)
```bash
# With Docker
docker run -d -p 8080:8080 -p 8443:8443 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web
docker run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web
#With Podman
podman run -d -p 8080:8080 -p 8443:8443 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web
podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web
```
## Release Schedule
Our release process follows these guidelines:
- **Versioning:** We use Semantic Versioning (`Major.Minor.Patch`).
- **Stable Releases:** Published around the beginning of each month (e.g.,
`v2.3.4`).
- **Pre-releases:** A pre-release is typically issued mid-month for testing and
early adoption.
- **Nightly Builds:** An experimental Docker image containing the latest
cutting-edge features and fixes is automatically built nightly from the
`main` branch.
### Nightly Builds
```bash
# With Docker
docker run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web:nightly
#With Podman
podman run -d -p 8080:8080 --restart always --name Meshtastic-Web ghcr.io/meshtastic/web:nightly
```
> [!WARNING]
>
> - Nightly builds represent the latest development state and may contain
> breaking changes
> - These builds undergo automated testing but may be less stable than tagged
> release versions
> - Not recommended for production environments unless you are actively testing
> new features
> - No guarantee of backward compatibility between nightly builds
#### Version Information
Each nightly build is tagged with:
- The nightly tag for the latest build
- A specific SHA for build reproducibility
### Feedback
If you encounter any issues with nightly builds, please report them in our
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
improve the stability of future releases
## Development & Building
### Building and Packaging
Build the project:
```bash
pnpm build
```
GZip the output:
```bash
pnpm package
```
You'll need to download the package manager used with this repo. You can install
it by visiting [deno.com](https://deno.com/) and following the installation
instructions listed on the home page.
### Development
Install the dependencies.
```bash
pnpm i
deno i
```
Start the development server:
```bash
pnpm dev
deno task dev
```
### Building and Packaging
Build the project:
```bash
deno task build
```
GZip the output:
```bash
deno task package
```
### Why Deno?
Meshtastic Web uses Deno as its development platform for several compelling
reasons:
- **Built-in Security**: Deno's security-first approach requires explicit
permissions for file, network, and environment access, reducing vulnerability
risks.
- **TypeScript Support**: Native TypeScript support without additional
configuration, enhancing code quality and developer experience.
- **Modern JavaScript**: First-class support for ESM imports, top-level await,
and other modern JavaScript features.
- **Simplified Tooling**: Built-in formatter, linter, test runner, and bundler
eliminate the need for multiple third-party tools.
- **Reproducible Builds**: Lockfile ensures consistent builds across all
environments.
- **Web Standard APIs**: Uses browser-compatible APIs, making code more portable
between server and client environments.
### Contributing
We welcome contributions! Heres how the deployment flow works for pull
requests:
- **Preview Deployments:**\
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`):**\
Once your PR is merged, your changes will be available on our staging site:
[client-test.meshtastic.org](https://client-test.meshtastic.org/).\
This environment supports rapid feature iteration and testing without
impacting the production site.
- **Production Releases:**\
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
[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!

View File

@@ -1,27 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
"organizeImports": {
"enabled": true
},
"files": {
"ignoreUnknown": true,
"ignore": ["vercel.json"]
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "master"
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}

10
crowdin.yml Normal file
View File

@@ -0,0 +1,10 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
base_path: "."
base_url: "https://meshtastic.crowdin.com/api/v2"
preserve_hierarchy: true
files:
- source: "/src/i18n/locales/en/*.json"
translation: "/src/i18n/locales/%locale%/%original_file_name%"

60
deno.json Normal file
View File

@@ -0,0 +1,60 @@
{
"imports": {
"@app/": "./src/",
"@pages/": "./src/pages/",
"@components/": "./src/components/",
"@core/": "./src/core/",
"@layouts/": "./src/layouts/",
"@std/path": "jsr:@std/path@^1.1.0"
},
"include": ["src", "./vite-env.d.ts"],
"compilerOptions": {
"lib": [
"DOM",
"DOM.Iterable",
"ESNext",
"deno.window",
"deno.ns"
],
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": true,
"types": [
"vite/client",
"node",
"npm:@types/w3c-web-serial",
"npm:@types/web-bluetooth"
],
"strictPropertyInitialization": false
},
"fmt": {
"exclude": [
"src/routeTree.gen.ts",
"*.test.ts",
"*.test.tsx"
]
},
"lint": {
"exclude": [
"src/routeTree.gen.ts",
"*.test.ts",
"*.test.tsx"
],
"report": "pretty"
},
"exclude": [
"routeTree.gen.ts",
"node_modules/",
"dist",
"build",
"coverage",
"out",
".vscode-test"
],
"unstable": [
"sloppy-imports"
]
}

7031
deno.lock generated Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="system">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
@@ -18,7 +18,7 @@
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
/>
<meta name="description" content="Meshtastic Web App" />
<meta name="description" content="Meshtastic Web Client" />
<title>Meshtastic Web</title>
</head>
<body>

2
infra/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
../dist/build.tar
../dist/output

15
infra/Containerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM nginx:1.27-alpine
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 dist .
COPY ./infra/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]

42
infra/default.conf Normal file
View File

@@ -0,0 +1,42 @@
server {
listen 8080;
server_name localhost;
root /usr/share/nginx/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
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;
}

View File

@@ -1,16 +1,21 @@
{
"name": "meshtastic-web",
"version": "2.3.3-0",
"version": "2.6.0-0",
"type": "module",
"description": "Meshtastic web client",
"license": "GPL-3.0-only",
"scripts": {
"dev": "vite --host",
"build": "tsc && pnpm check && vite build ",
"check": "biome check .",
"check:fix": "pnpm check --write",
"preview": "vite preview",
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ $(ls ./dist/output/)"
"build": "vite build",
"build:analyze": "BUNDLE_ANALYZE=true deno task build",
"lint": "deno lint src/",
"lint:fix": "deno lint --fix src/",
"format": "deno fmt src/",
"dev": "deno task dev:ui",
"dev:ui": "VITE_APP_VERSION=development deno run -A npm:vite dev",
"test": "deno run -A npm:vitest",
"check": "deno check",
"preview": "deno run -A npm:vite preview",
"package": "gzipper c -i html,js,css,png,ico,svg,webmanifest,txt dist dist/output && tar -cvf dist/build.tar -C ./dist/output/ ."
},
"repository": {
"type": "git",
@@ -19,68 +24,98 @@
"bugs": {
"url": "https://github.com/meshtastic/web/issues"
},
"simple-git-hooks": {
"pre-commit": "deno task lint:fix && deno task format"
},
"lint-staged": {
"*.{ts,tsx}": [
"deno task lint:fix",
"deno task format"
]
},
"homepage": "https://meshtastic.org",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0",
"@emeraldpay/hashicon-react": "^0.5.2",
"@meshtastic/js": "2.3.7-1",
"@noble/curves": "^1.5.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-menubar": "^1.1.1",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.1",
"@turf/turf": "^6.5.0",
"@bufbuild/protobuf": "^2.2.5",
"@hookform/resolvers": "^5.1.1",
"@meshtastic/core": "npm:@jsr/meshtastic__core@2.6.4",
"@meshtastic/js": "npm:@jsr/meshtastic__js@2.6.0-0",
"@meshtastic/transport-http": "npm:@jsr/meshtastic__transport-http",
"@meshtastic/transport-web-bluetooth": "npm:@jsr/meshtastic__transport-web-bluetooth",
"@meshtastic/transport-web-serial": "npm:@jsr/meshtastic__transport-web-serial",
"@noble/curves": "^1.9.0",
"@radix-ui/react-accordion": "^1.2.8",
"@radix-ui/react-checkbox": "^1.2.3",
"@radix-ui/react-dialog": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-menubar": "^1.1.12",
"@radix-ui/react-popover": "^1.1.11",
"@radix-ui/react-scroll-area": "^1.2.6",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-slider": "^1.3.2",
"@radix-ui/react-switch": "^1.2.2",
"@radix-ui/react-tabs": "^1.1.9",
"@radix-ui/react-toast": "^1.2.11",
"@radix-ui/react-toggle-group": "^1.1.9",
"@radix-ui/react-tooltip": "^1.2.4",
"@tanstack/react-router": "^1.120.15",
"@tanstack/react-router-devtools": "^1.120.16",
"@tanstack/router-devtools": "^1.120.15",
"@turf/turf": "^7.2.0",
"@types/web-bluetooth": "^0.0.21",
"base64-js": "^1.5.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"class-variance-authority": "^0.7.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"cmdk": "^1.1.1",
"crypto-random-string": "^5.0.0",
"i18next": "^25.2.0",
"i18next-browser-languagedetector": "^8.1.0",
"i18next-http-backend": "^3.0.2",
"idb-keyval": "^6.2.1",
"immer": "^10.1.1",
"lucide-react": "^0.363.0",
"mapbox-gl": "npm:empty-npm-package@^1.0.0",
"maplibre-gl": "4.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.0",
"react-map-gl": "7.1.7",
"react-qrcode-logo": "^2.10.0",
"rfc4648": "^1.5.3",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"timeago-react": "^3.0.6",
"zustand": "4.5.2"
"js-cookie": "^3.0.5",
"lucide-react": "^0.507.0",
"maplibre-gl": "5.4.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-error-boundary": "^6.0.0",
"react-hook-form": "^7.56.2",
"react-i18next": "^15.5.1",
"react-map-gl": "8.0.4",
"react-qrcode-logo": "^3.0.0",
"rfc4648": "^1.5.4",
"zod": "^3.25.67",
"zustand": "5.0.5"
},
"devDependencies": {
"@biomejs/biome": "^1.8.2",
"@buf/meshtastic_protobufs.bufbuild_es": "1.10.0-20240820152623-fac6975bbc78.1",
"@types/chrome": "^0.0.263",
"@types/node": "^20.14.9",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/w3c-web-serial": "^1.0.6",
"@types/web-bluetooth": "^0.0.20",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"gzipper": "^7.2.0",
"postcss": "^8.4.38",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.4",
"tar": "^6.2.1",
"tslib": "^2.6.3",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vite-plugin-environment": "^1.1.3"
"@tailwindcss/postcss": "^4.1.5",
"@tanstack/router-plugin": "^1.120.15",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/chrome": "^0.0.318",
"@types/node": "^22.15.3",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@types/serviceworker": "^0.0.133",
"@types/js-cookie": "^3.0.6",
"@types/w3c-web-serial": "^1.0.8",
"@vitejs/plugin-react": "^4.4.1",
"autoprefixer": "^10.4.21",
"gzipper": "^8.2.1",
"happy-dom": "^17.4.6",
"postcss": "^8.5.3",
"simple-git-hooks": "^2.13.0",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.5",
"tailwindcss-animate": "^1.0.7",
"tar": "^7.4.3",
"testing-library": "^0.0.2",
"typescript": "^5.8.3",
"vite": "^6.3.4",
"vite-plugin-pwa": "^1.0.0",
"vite-plugin-static-copy": "^3.0.0",
"vitest": "^3.1.2"
}
}

6543
pnpm-lock.yaml generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
};

16
public/Logo.svg Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="512" height="512" viewBox="0 0 512 512" xml:space="preserve">
<desc>Created with Fabric.js 4.6.0</desc>
<defs>
</defs>
<g transform="matrix(1 0 0 1 256 256)" id="xYQ9Gk9Jwpgj_HMOXB3F_" >
<path style="stroke: rgb(213,130,139); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(103,234,148); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-256, -256)" d="M 0 0 L 512 0 L 512 512 L 0 512 z" stroke-linecap="round" />
</g>
<g transform="matrix(1.79 0 0 1.79 313.74 258.36)" id="1xBsk2n9FZp60Rz1O-ceJ" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: round; stroke-miterlimit: 2; fill: rgb(44,45,60); fill-rule: evenodd; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-250.97, -362.41)" d="M 250.908 330.267 L 193.126 415.005 L 180.938 406.694 L 244.802 313.037 C 246.174 311.024 248.453 309.819 250.889 309.816 C 253.326 309.814 255.606 311.015 256.982 313.026 L 320.994 406.536 L 308.821 414.869 L 250.908 330.267 Z" stroke-linecap="round" />
</g>
<g transform="matrix(1.81 0 0 1.81 145 256.15)" id="KxN7E9YpbyPgz0S4z4Cl6" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: round; stroke-miterlimit: 2; fill: rgb(44,45,60); fill-rule: evenodd; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-115.14, -528.06)" d="M 87.642 581.398 L 154.757 482.977 L 142.638 474.713 L 75.523 573.134 L 87.642 581.398 Z" stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
public/chirpy.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

1
public/diy.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 89 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="795.27 277.13 409.46 1319.35"><defs><style>.cls-1{fill:#353535;}.cls-2{fill:#1e1e1d;}.cls-3{fill:#b1a368;}.cls-10,.cls-11,.cls-4,.cls-6,.cls-8,.cls-9{fill:none;}.cls-4,.cls-6{stroke:#050606;}.cls-10,.cls-11,.cls-4,.cls-6,.cls-8{stroke-miterlimit:10;}.cls-4{stroke-width:2.41px;}.cls-5{fill:#30c2db;}.cls-6{stroke-width:3.91px;}.cls-7{fill:#dcf0f2;}.cls-10,.cls-11,.cls-8{stroke:#dcf0f2;}.cls-8{stroke-width:1.81px;}.cls-9{stroke:#17afbf;stroke-linecap:round;stroke-linejoin:round;stroke-width:7.23px;}.cls-10{stroke-width:1.78px;}.cls-11{stroke-width:1.81px;}</style></defs><g id="Layer_7" data-name="Layer 7"><path class="cls-1" d="M915.62,278.34h22.61a35,35,0,0,1,35,35V715.74a0,0,0,0,1,0,0H880.6a0,0,0,0,1,0,0V313.36A35,35,0,0,1,915.62,278.34Z"></path><rect class="cls-2" x="880.6" y="340.15" width="92.65" height="7.54"></rect><rect class="cls-2" x="880.6" y="356.68" width="92.65" height="7.54"></rect><rect class="cls-3" x="885.8" y="844.3" width="84.14" height="19.02"></rect><rect class="cls-3" x="880.6" y="819.07" width="92.65" height="25.23"></rect><rect class="cls-3" x="885.8" y="790.65" width="84.14" height="28.41"></rect><rect class="cls-3" x="880.6" y="723.02" width="92.65" height="67.63"></rect><rect class="cls-3" x="885.8" y="715.74" width="84.14" height="7.28"></rect><rect class="cls-4" x="885.8" y="844.3" width="84.14" height="19.02"></rect><rect class="cls-4" x="880.6" y="819.07" width="92.65" height="25.23"></rect><rect class="cls-4" x="885.8" y="790.65" width="84.14" height="28.41"></rect><rect class="cls-4" x="880.6" y="723.02" width="92.65" height="67.63"></rect><rect class="cls-4" x="885.8" y="715.74" width="84.14" height="7.28"></rect><path class="cls-4" d="M915.62,278.34h22.61a35,35,0,0,1,35,35V715.74a0,0,0,0,1,0,0H880.6a0,0,0,0,1,0,0V313.36A35,35,0,0,1,915.62,278.34Z"></path><rect class="cls-4" x="880.6" y="340.15" width="92.65" height="7.54"></rect><rect class="cls-4" x="880.6" y="356.68" width="92.65" height="7.54"></rect><rect class="cls-5" x="796.48" y="856.3" width="407.05" height="738.98" rx="47.74"></rect><rect class="cls-1" x="900.05" y="973.19" width="202.03" height="354.65" rx="16.4"></rect><rect class="cls-6" x="900.05" y="973.19" width="202.03" height="354.65" rx="16.4"></rect><rect class="cls-7" x="871.51" y="890.41" width="55.42" height="31.12" rx="15.56"></rect><rect class="cls-7" x="1070.16" y="890.41" width="55.42" height="31.12" rx="15.56"></rect><rect class="cls-4" x="871.51" y="890.41" width="55.42" height="31.12" rx="15.56"></rect><rect class="cls-4" x="1070.16" y="890.41" width="55.42" height="31.12" rx="15.56"></rect><circle class="cls-8" cx="841.7" cy="1537.01" r="16.25"></circle><circle class="cls-8" cx="841.7" cy="913.26" r="16.25"></circle><circle class="cls-8" cx="1157.32" cy="913.26" r="16.25"></circle><circle class="cls-8" cx="1157.32" cy="1504.51" r="16.25"></circle><line class="cls-9" x1="942.51" y1="1592.42" x2="942.51" y2="1381.55"></line><line class="cls-9" x1="966.52" y1="1592.42" x2="966.52" y2="1381.55"></line><line class="cls-9" x1="990.57" y1="1592.42" x2="990.57" y2="1381.55"></line><line class="cls-9" x1="1014.59" y1="1592.42" x2="1014.59" y2="1381.55"></line><line class="cls-9" x1="1038.63" y1="1592.42" x2="1038.63" y2="1381.55"></line><line class="cls-9" x1="1062.65" y1="1592.42" x2="1062.65" y2="1381.55"></line><rect class="cls-4" x="796.48" y="856.3" width="407.05" height="738.98" rx="47.74"></rect><path class="cls-10" d="M1040.1,947.74H960.65A13.93,13.93,0,0,1,947,936.64l-10.23-49.2a13.93,13.93,0,0,1,13.64-16.77h97.72a13.93,13.93,0,0,1,13.75,16.18l-8,49.2A13.94,13.94,0,0,1,1040.1,947.74Z"></path><rect class="cls-11" x="816.35" y="870.67" width="365.51" height="703.12" rx="32.37"></rect><rect class="cls-11" x="888.77" y="963.84" width="223.2" height="374.66" rx="25.21"></rect></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="404.68 390.65 1217.15 959.26"><defs><style>.cls-1{fill:#dfeaf7;}.cls-2{fill:#17907f;}.cls-3{fill:#2b2b2b;}.cls-4,.cls-5{fill:none;stroke:#050606;stroke-miterlimit:10;}.cls-4{stroke-width:2.25px;}.cls-5{stroke-width:4px;}.cls-6{fill:#050606;}</style></defs><g id="Layer_5" data-name="Layer 5"><path class="cls-1" d="M1517.73,392.65h0a102.1,102.1,0,0,0-102.1,102.1V770.37A40.62,40.62,0,0,1,1375,811H455.16a48.49,48.49,0,0,0-48.48,48.48v126a11.85,11.85,0,0,0,3.46,8.37l15.34,15.34a11.81,11.81,0,0,1,3.47,8.37v137.16a11.81,11.81,0,0,1-3.47,8.37l-15.34,15.34a11.85,11.85,0,0,0-3.46,8.37v112.67a48.49,48.49,0,0,0,48.48,48.49H1571.34a48.51,48.51,0,0,0,48.49-48.5V494.75A102.1,102.1,0,0,0,1517.73,392.65Zm-110.61,815V954a33.14,33.14,0,0,1,66.27,0v253.65a33.14,33.14,0,0,1-66.27,0Z"></path><path class="cls-2" d="M1516,439.16c-30.23.91-53.92,26.54-53.92,56.79V770.37A87.11,87.11,0,0,1,1375,857.48H732.31A27.51,27.51,0,0,0,704.8,885v388.93a27.51,27.51,0,0,0,27.51,27.51h828.38a12.7,12.7,0,0,0,12.65-12.65v-794A55.69,55.69,0,0,0,1516,439.16Zm-108.9,768.47V954a33.14,33.14,0,0,1,66.27,0v253.65a33.14,33.14,0,0,1-66.27,0Z"></path><rect class="cls-3" x="787.14" y="943.38" width="429.45" height="224.42"></rect><path class="cls-1" d="M1478.6,915.35A54.23,54.23,0,0,0,1386,953.69v254.23a54.23,54.23,0,1,0,108.45,0V953.69A54,54,0,0,0,1478.6,915.35Zm-5.21,292.28a33.14,33.14,0,0,1-66.27,0V954a33.14,33.14,0,0,1,66.27,0Z"></path></g><g id="Layer_2" data-name="Layer 2"><path class="cls-4" d="M1573.34,494.75v794a12.68,12.68,0,0,1-12.65,12.65H732.31a27.51,27.51,0,0,1-27.51-27.51V885a27.51,27.51,0,0,1,27.51-27.51H1375a87.11,87.11,0,0,0,87.11-87.11V496c0-30.25,23.69-55.88,53.92-56.79A55.69,55.69,0,0,1,1573.34,494.75Z"></path><path class="cls-5" d="M410.14,1178.39,425.49,1163a11.78,11.78,0,0,0,3.46-8.35V1017.5a11.8,11.8,0,0,0-3.46-8.35l-15.35-15.36a11.77,11.77,0,0,1-3.46-8.35v-126A48.47,48.47,0,0,1,455.16,811H1375a40.63,40.63,0,0,0,40.63-40.63V494.75a102.1,102.1,0,0,1,102.1-102.1h0a102.1,102.1,0,0,1,102.1,102.1v804.66a48.51,48.51,0,0,1-48.49,48.5H455.16a48.48,48.48,0,0,1-48.48-48.49V1186.74A11.78,11.78,0,0,1,410.14,1178.39Z"></path><rect class="cls-4" x="1407.12" y="920.85" width="66.26" height="319.9" rx="33.13"></rect><rect class="cls-4" x="1386.03" y="899.46" width="108.46" height="362.69" rx="54.23"></rect><path class="cls-6" d="M639.76,1070.55a2.91,2.91,0,0,1-2.91-2.91v-30.53a5.42,5.42,0,0,0-1.6-3.86l-32.44-32.44a11.86,11.86,0,0,1-3.5-8.44V901a12.52,12.52,0,0,0-12.51-12.51H483.92a12.7,12.7,0,0,0-12.68,12.69v76.78a24.13,24.13,0,0,0,7.11,17.18l14.33,14.33a24.13,24.13,0,0,0,17.18,7.11h50.75a11.86,11.86,0,0,1,8.44,3.5l24.26,24.26a12.47,12.47,0,0,1,3.68,8.88v14.46a2.91,2.91,0,1,1-5.81,0v-14.46a6.72,6.72,0,0,0-2-4.77l-24.26-24.26a6.09,6.09,0,0,0-4.33-1.8H509.86a29.91,29.91,0,0,1-21.29-8.81l-14.33-14.33a29.87,29.87,0,0,1-8.81-21.29V901.14a18.51,18.51,0,0,1,18.49-18.5H586.8A18.34,18.34,0,0,1,605.12,901v91.41a6.09,6.09,0,0,0,1.8,4.33l32.44,32.44a11.19,11.19,0,0,1,3.3,8v30.53A2.9,2.9,0,0,1,639.76,1070.55Z"></path><path class="cls-6" d="M586.8,1289.46H483.92a18.51,18.51,0,0,1-18.49-18.5v-76.78a29.87,29.87,0,0,1,8.81-21.29l14.33-14.34a29.91,29.91,0,0,1,21.29-8.81h50.75a6.09,6.09,0,0,0,4.33-1.8l24.26-24.25a6.74,6.74,0,0,0,2-4.78v-14.46a2.91,2.91,0,0,1,5.81,0v14.46a12.51,12.51,0,0,1-3.68,8.89l-24.26,24.25a11.86,11.86,0,0,1-8.44,3.5H509.86a24.17,24.17,0,0,0-17.18,7.11L478.35,1177a24.09,24.09,0,0,0-7.11,17.18V1271a12.71,12.71,0,0,0,12.68,12.69H586.8a12.53,12.53,0,0,0,12.51-12.52v-91.4a11.83,11.83,0,0,1,3.5-8.44l32.44-32.44a5.46,5.46,0,0,0,1.6-3.87v-30.53a2.91,2.91,0,0,1,5.81,0V1135a11.19,11.19,0,0,1-3.3,8l-32.44,32.45a6.06,6.06,0,0,0-1.8,4.33v91.4A18.35,18.35,0,0,1,586.8,1289.46Z"></path><rect class="cls-4" x="787.14" y="943.38" width="429.45" height="224.42"></rect></g></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

1
public/heltec-v3.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="528.89 806.04 942.22 446.84"><defs><style>.cls-1{fill:#e8eae8;}.cls-2{fill:#dbdddb;}.cls-3{fill:#c6842a;}.cls-4,.cls-5,.cls-7,.cls-9{fill:none;stroke-miterlimit:10;}.cls-4,.cls-5{stroke:#050606;}.cls-4,.cls-9{stroke-width:2.44px;}.cls-5,.cls-7{stroke-width:1.22px;}.cls-6{fill:#b7b7b7;}.cls-7,.cls-9{stroke:#b7b7b7;}.cls-8{fill:#cbcccb;}.cls-10{fill:#434543;}</style></defs><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M1469.9,826.68v390.94a19.43,19.43,0,0,1-19.43,19.43H549.53a19.43,19.43,0,0,1-19.43-19.43V826.68a19.42,19.42,0,0,1,19.43-19.42h900.94A19.42,19.42,0,0,1,1469.9,826.68Z"></path><path class="cls-2" d="M574.23,807.26v429.79h-24.7a19.43,19.43,0,0,1-19.43-19.43V826.68a19.42,19.42,0,0,1,19.43-19.42Z"></path><path class="cls-2" d="M1469.9,826.68v390.94a19.43,19.43,0,0,1-19.43,19.43h-37.56V807.26h37.56A19.42,19.42,0,0,1,1469.9,826.68Z"></path><path class="cls-3" d="M574.23,1129.8h-7.47a4.55,4.55,0,0,1-4.55-4.54V919.05a4.55,4.55,0,0,1,4.55-4.55h7.47"></path><rect class="cls-4" x="530.11" y="807.26" width="939.78" height="429.79" rx="19.42"></rect><line class="cls-5" x1="574.23" y1="807.26" x2="574.23" y2="1237.05"></line><path class="cls-5" d="M574.23,1129.8h-7.47a4.55,4.55,0,0,1-4.55-4.54V919.05a4.55,4.55,0,0,1,4.55-4.55h7.47"></path><rect class="cls-6" x="599.01" y="970.1" width="11.52" height="104.11"></rect><rect class="cls-5" x="599.01" y="970.1" width="11.52" height="104.11"></rect><path class="cls-2" d="M610.53,816.78V935.32l38.37,11.55a7.85,7.85,0,0,1,5.6,7.53V1107a7.87,7.87,0,0,1-7.87,7.87h-36.1v110.58H1406V816.78Zm775.41,384.75H631.43a3.66,3.66,0,0,1-3.66-3.66V1138a3.65,3.65,0,0,1,3.66-3.65h28.3a14.7,14.7,0,0,0,14.71-14.71V931a14.7,14.7,0,0,0-14.71-14.71h-28.3a3.65,3.65,0,0,1-3.66-3.66V844.11a3.66,3.66,0,0,1,3.66-3.66h754.51Z"></path><path class="cls-1" d="M1385.94,840.45v361.08H631.43a3.66,3.66,0,0,1-3.66-3.66V1138a3.65,3.65,0,0,1,3.66-3.65h28.3a14.7,14.7,0,0,0,14.71-14.71V931a14.7,14.7,0,0,0-14.71-14.71h-28.3a3.65,3.65,0,0,1-3.66-3.66V844.11a3.66,3.66,0,0,1,3.66-3.66Z"></path><path class="cls-7" d="M610.53,816.78V935.32l38.37,11.55a7.85,7.85,0,0,1,5.6,7.53V1107a7.87,7.87,0,0,1-7.87,7.87h-36.1v110.58H1406V816.78Zm775.41,384.75H631.43a3.66,3.66,0,0,1-3.66-3.66V1138a3.65,3.65,0,0,1,3.66-3.65h28.3a14.7,14.7,0,0,0,14.71-14.71V931a14.7,14.7,0,0,0-14.71-14.71h-28.3a3.65,3.65,0,0,1-3.66-3.66V844.11a3.66,3.66,0,0,1,3.66-3.66h754.51Z"></path><path class="cls-7" d="M1385.94,840.45v361.08H631.43a3.66,3.66,0,0,1-3.66-3.66V1138a3.65,3.65,0,0,1,3.66-3.65h28.3a14.7,14.7,0,0,0,14.71-14.71V931a14.7,14.7,0,0,0-14.71-14.71h-28.3a3.65,3.65,0,0,1-3.66-3.66V844.11a3.66,3.66,0,0,1,3.66-3.66Z"></path><path class="cls-7" d="M1385.94,840.45v361.08H631.43a3.66,3.66,0,0,1-3.66-3.66V1138a3.65,3.65,0,0,1,3.66-3.65h28.3a14.7,14.7,0,0,0,14.71-14.71V931a14.7,14.7,0,0,0-14.71-14.71h-28.3a3.65,3.65,0,0,1-3.66-3.66V844.11a3.66,3.66,0,0,1,3.66-3.66Z"></path><line class="cls-7" x1="666.4" y1="1132.79" x2="666.4" y2="1201.53"></line><line class="cls-7" x1="663.66" y1="916.86" x2="663.66" y2="840.45"></line><circle class="cls-8" cx="644.99" cy="878.66" r="10.7"></circle><circle class="cls-8" cx="644.99" cy="1171.55" r="10.7"></circle><circle class="cls-9" cx="644.99" cy="878.66" r="10.7"></circle><circle class="cls-9" cx="644.99" cy="1171.55" r="10.7"></circle><path class="cls-10" d="M1225,1237.05l2.23,11.19a4.25,4.25,0,0,0,4.17,3.42H1249a4.26,4.26,0,0,0,4.18-3.42l2.22-11.19"></path><path class="cls-10" d="M1306.87,1237.05l2.22,11.19a4.26,4.26,0,0,0,4.18,3.42h17.62a4.25,4.25,0,0,0,4.17-3.42l2.22-11.19"></path><path class="cls-10" d="M1388.77,1237.05l2.23,11.19a4.24,4.24,0,0,0,4.17,3.42h17.62a4.25,4.25,0,0,0,4.17-3.42l2.23-11.19"></path><path class="cls-4" d="M1225,1237.05l2.23,11.19a4.25,4.25,0,0,0,4.17,3.42H1249a4.26,4.26,0,0,0,4.18-3.42l2.22-11.19"></path><path class="cls-4" d="M1306.87,1237.05l2.22,11.19a4.26,4.26,0,0,0,4.18,3.42h17.62a4.25,4.25,0,0,0,4.17-3.42l2.22-11.19"></path><path class="cls-4" d="M1388.77,1237.05l2.23,11.19a4.24,4.24,0,0,0,4.17,3.42h17.62a4.25,4.25,0,0,0,4.17-3.42l2.23-11.19"></path><line class="cls-4" x1="1412.9" y1="807.26" x2="1412.9" y2="1237.05"></line></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="479.57 786.58 1040.84 433.17"><defs><style>.cls-1{fill:#cccccb;}.cls-2{fill:#2b2b2b;}.cls-3,.cls-6,.cls-7,.cls-8{fill:none;stroke-miterlimit:10;}.cls-3,.cls-6,.cls-7{stroke:#050606;}.cls-3,.cls-8{stroke-width:1.65px;}.cls-4{fill:#40403f;}.cls-5{fill:#ddd;}.cls-6{stroke-width:1.62px;}.cls-7{stroke-width:1.64px;}.cls-8{stroke:#fff;}.cls-9{fill:#353535;}.cls-10{fill:#c08c2d;}</style></defs><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M595,923.44H519.6A10.46,10.46,0,0,1,509.14,913V802.7a15.28,15.28,0,0,1,15.28-15.28h979.89a15.29,15.29,0,0,1,15.29,15.29v400.93a15.3,15.3,0,0,1-15.29,15.29H524.42a15.28,15.28,0,0,1-15.28-15.28V1102.1a10.46,10.46,0,0,1,10.46-10.46H595"></path><rect class="cls-2" x="611.37" y="796.48" width="819.83" height="411.47"></rect><line class="cls-3" x1="1441.99" y1="787.41" x2="1441.99" y2="1218.92"></line><path class="cls-4" d="M620.91,851.7v302.78a1.87,1.87,0,0,1-1.87,1.87h-13.8a8.7,8.7,0,0,1-.89,0,10.23,10.23,0,0,1-9.35-10.2v-286a10.24,10.24,0,0,1,9.35-10.2,8.7,8.7,0,0,1,.89,0H619A1.87,1.87,0,0,1,620.91,851.7Z"></path><rect class="cls-5" x="480.4" y="942.42" width="114.6" height="127.58"></rect><rect class="cls-3" x="480.4" y="942.42" width="114.6" height="127.58"></rect><path class="cls-6" d="M595,923.44H519.6A10.46,10.46,0,0,1,509.14,913V802.7a15.28,15.28,0,0,1,15.28-15.28h979.89a15.29,15.29,0,0,1,15.29,15.29v400.93a15.3,15.3,0,0,1-15.29,15.29H524.42a15.28,15.28,0,0,1-15.28-15.28V1102.1a10.46,10.46,0,0,1,10.46-10.46H595"></path><path class="cls-2" d="M584.65,970.14H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3V973.11A3,3,0,0,1,584.65,970.14Z"></path><rect class="cls-2" x="560.58" y="976.5" width="9.04" height="16.58" rx="2.72"></rect><path class="cls-2" d="M584.65,1026.63H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3v-18.49A3,3,0,0,1,584.65,1026.63Z"></path><rect class="cls-2" x="560.58" y="1028.91" width="9.04" height="16.58" rx="2.72"></rect><path class="cls-3" d="M584.65,970.14H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3V973.11A3,3,0,0,1,584.65,970.14Z"></path><rect class="cls-3" x="560.58" y="976.5" width="9.04" height="16.58" rx="2.72"></rect><path class="cls-3" d="M584.65,1026.63H595a0,0,0,0,1,0,0v24.44a0,0,0,0,1,0,0H584.65a3,3,0,0,1-3-3v-18.49A3,3,0,0,1,584.65,1026.63Z"></path><rect class="cls-3" x="560.58" y="1028.91" width="9.04" height="16.58" rx="2.72"></rect><polyline class="cls-7" points="611.37 1156.35 611.37 1207.95 1431.2 1207.95 1431.2 796.48 611.37 796.48 611.37 849.83"></polyline><line class="cls-3" x1="611.37" y1="1207.95" x2="611.37" y2="1218.93"></line><line class="cls-3" x1="611.37" y1="796.48" x2="611.37" y2="787.42"></line><rect class="cls-8" x="560.58" y="1107.17" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="1107.17" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="560.58" y="1166.28" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="1166.28" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="560.58" y="804.46" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="804.46" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="560.58" y="863.57" width="18.37" height="41.67" rx="4.91"></rect><rect class="cls-8" x="528.51" y="863.57" width="18.37" height="41.67" rx="4.91"></rect><circle class="cls-8" cx="1476.63" cy="831.74" r="27.15"></circle><circle class="cls-8" cx="1476.63" cy="1173.49" r="27.15"></circle><rect class="cls-9" x="676.15" y="804.6" width="742.79" height="396.03"></rect><path class="cls-10" d="M604.35,849.87v306.44a10.23,10.23,0,0,1-9.35-10.2v-286A10.24,10.24,0,0,1,604.35,849.87Z"></path><path class="cls-3" d="M605.24,849.83H619a1.87,1.87,0,0,1,1.87,1.87v302.78a1.87,1.87,0,0,1-1.87,1.87h-13.8A10.24,10.24,0,0,1,595,1146.11v-286A10.24,10.24,0,0,1,605.24,849.83Z"></path><rect class="cls-6" x="676.15" y="804.6" width="742.79" height="396.03"></rect></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

1
public/heltec-wsl-v3.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

1
public/nano-g2-ultra.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

2956
public/pico.svg Normal file
View File

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 102 KiB

1
public/promicro.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 71 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

2339
public/rak11310.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 164 KiB

1
public/rak2560.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

3514
public/rak4631.svg Normal file
View File

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 128 KiB

1
public/rak4631_case.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

1
public/rpipicow.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 76 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

1
public/seeed-xiao-s3.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,11 +1,11 @@
{
"name": "Meshtastic",
"short_name": "Meshtastic",
"short_name": "Web Client",
"start_url": ".",
"description": "Meshtastic web app",
"description": "Meshtastic Web App",
"icons": [
{
"src": "/icon.svg",
"src": "/Logo.svg",
"sizes": "any",
"type": "image/svg+xml"
}

1
public/station-g2.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

1
public/t-deck.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

1
public/t-echo.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

1
public/t-watch-s3.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="733.42 451.82 573.87 931.48"><defs><style>.cls-1{fill:#8e8d8e;}.cls-2{fill:#383839;}.cls-3{fill:#cccccb;}.cls-4{fill:#222226;}.cls-5,.cls-6{fill:none;stroke:#050606;stroke-miterlimit:10;}.cls-5{stroke-width:1.87px;}.cls-6{stroke-width:3.77px;}.cls-7{fill:#4c4c4d;}</style></defs><g id="Layer_4" data-name="Layer 4"><path class="cls-1" d="M1277.27,847.59h4.35a8.09,8.09,0,0,1,8.09,8.08v138a8.09,8.09,0,0,1-8.09,8.09h-4.35"></path><path class="cls-1" d="M1277.27,732.73h18a10.14,10.14,0,0,1,10.14,10.14v43A10.14,10.14,0,0,1,1295.26,796h-18a0,0,0,0,1,0,0V732.73A0,0,0,0,1,1277.27,732.73Z"></path><path class="cls-2" d="M1256.49,1200.6h0a14.19,14.19,0,0,1-2.83,12.5c-8.13,9.86-19.94,18.58-46,30.75-19.15,9-28.65,16-38.35,29.6a93.15,93.15,0,0,0-8.7,14.61c-6.95,15.17-11.77,44.44-11.77,65.66v3.61a24.09,24.09,0,0,1-24.1,24.09H887.83a24.09,24.09,0,0,1-24.1-24.09v-3.61c0-21.22-4.82-50.49-11.77-65.66a93.15,93.15,0,0,0-8.7-14.61c-9.7-13.63-19.2-20.65-38.35-29.6-26.06-12.17-37.87-20.89-46-30.75a14.22,14.22,0,0,1-2.82-12.5h0"></path><path class="cls-2" d="M756.09,634.53h0a14.19,14.19,0,0,1,2.83-12.5c8.12-9.86,19.93-18.58,46-30.75,19.15-8.95,28.65-16,38.35-29.6a93.15,93.15,0,0,0,8.7-14.61c6.95-15.17,11.77-44.44,11.77-65.66V477.8a24.09,24.09,0,0,1,24.1-24.09h236.92a24.09,24.09,0,0,1,24.1,24.09v3.61c0,21.22,4.82,50.49,11.77,65.66a93.15,93.15,0,0,0,8.7,14.61c9.7,13.63,19.2,20.65,38.35,29.6,26,12.17,37.86,20.89,46,30.75a14.19,14.19,0,0,1,2.83,12.5h0"></path><rect class="cls-3" x="735.31" y="598.25" width="541.96" height="638.99" rx="96.44"></rect><path class="cls-2" d="M1247.38,694.68v446.11a66.63,66.63,0,0,1-66.54,66.56H831.75a66.62,66.62,0,0,1-66.56-66.56V694.68a66.63,66.63,0,0,1,66.56-66.55h349.09A66.64,66.64,0,0,1,1247.38,694.68Z"></path><rect class="cls-4" x="817.71" y="721.76" width="379.03" height="388.6"></rect><path class="cls-5" d="M1247.38,694.68v446.11a66.63,66.63,0,0,1-66.54,66.56H831.75a66.62,66.62,0,0,1-66.56-66.56V694.68a66.63,66.63,0,0,1,66.56-66.55h349.09A66.64,66.64,0,0,1,1247.38,694.68Z"></path><rect class="cls-6" x="735.31" y="598.25" width="541.96" height="638.99" rx="96.44"></rect><path class="cls-6" d="M1256.49,1200.6h0a14.19,14.19,0,0,1-2.83,12.5c-8.13,9.86-19.94,18.58-46,30.75-19.15,9-28.65,16-38.35,29.6a93.15,93.15,0,0,0-8.7,14.61c-6.95,15.17-11.77,44.44-11.77,65.66v3.61a24.09,24.09,0,0,1-24.1,24.09H887.83a24.09,24.09,0,0,1-24.1-24.09v-3.61c0-21.22-4.82-50.49-11.77-65.66a93.15,93.15,0,0,0-8.7-14.61c-9.7-13.63-19.2-20.65-38.35-29.6-26.06-12.17-37.87-20.89-46-30.75a14.22,14.22,0,0,1-2.82-12.5h0"></path><path class="cls-6" d="M756.09,634.53h0a14.19,14.19,0,0,1,2.83-12.5c8.12-9.86,19.93-18.58,46-30.75,19.15-8.95,28.65-16,38.35-29.6a93.15,93.15,0,0,0,8.7-14.61c6.95-15.17,11.77-44.44,11.77-65.66V477.8a24.09,24.09,0,0,1,24.1-24.09h236.92a24.09,24.09,0,0,1,24.1,24.09v3.61c0,21.22,4.82,50.49,11.77,65.66a93.15,93.15,0,0,0,8.7,14.61c9.7,13.63,19.2,20.65,38.35,29.6,26,12.17,37.86,20.89,46,30.75a14.19,14.19,0,0,1,2.83,12.5h0"></path><rect class="cls-5" x="817.71" y="721.76" width="379.03" height="388.6"></rect><path class="cls-6" d="M1277.27,847.59h4.35a8.09,8.09,0,0,1,8.09,8.08v138a8.09,8.09,0,0,1-8.09,8.09h-4.35"></path><path class="cls-6" d="M1277.27,732.73h18a10.14,10.14,0,0,1,10.14,10.14v43A10.14,10.14,0,0,1,1295.26,796h-18a0,0,0,0,1,0,0V732.73A0,0,0,0,1,1277.27,732.73Z"></path><circle class="cls-7" cx="1083.08" cy="1177.35" r="16.6"></circle><rect class="cls-2" x="1280.24" y="739.77" width="16.77" height="4.59" rx="2.29"></rect><rect class="cls-2" x="1280.24" y="750.91" width="16.77" height="4.59" rx="2.29"></rect><rect class="cls-2" x="1280.24" y="762.06" width="16.77" height="4.59" rx="2.29"></rect><rect class="cls-2" x="1280.24" y="773.2" width="16.77" height="4.59" rx="2.29"></rect><rect class="cls-2" x="1280.24" y="784.34" width="16.77" height="4.59" rx="2.29"></rect></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

1
public/tbeam-s3-core.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

1
public/tbeam.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 112 KiB

1
public/tlora-c6.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

1
public/tlora-t3s3-v1.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.9 KiB

160
public/unknown.svg Normal file
View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
class="svg-icon"
style="overflow:hidden;fill:currentColor"
viewBox="0 0 909.87988 546.85529"
version="1.1"
id="svg3"
xml:space="preserve"
width="909.87988"
height="546.85529"
sodipodi:docname="unknown.svg"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.57169944"
inkscape:cx="291.23695"
inkscape:cy="107.57401"
inkscape:window-width="1472"
inkscape:window-height="890"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_7" /><defs
id="defs3"><style
id="style1">.cls-1{fill:#383838;}.cls-2{fill:#9f9f9e;}.cls-3{fill:#cbcccb;}.cls-4{fill:#b7b7b7;}.cls-5{fill:#353535;}.cls-6{fill:#b1a368;}.cls-7{fill:#2c2d2d;}.cls-10,.cls-11,.cls-8,.cls-9{fill:none;stroke:#050606;}.cls-10,.cls-11,.cls-8{stroke-miterlimit:10;}.cls-8,.cls-9{stroke-width:2px;}.cls-9{stroke-linecap:round;stroke-linejoin:round;}.cls-10{stroke-width:2.04px;}.cls-11{stroke-width:1.99px;}.cls-12{fill:#c08c2d;}.cls-13{fill:#af7a2b;}</style></defs><g
id="Layer_7"
data-name="Layer 7"
transform="translate(-646.6554,-758.05941)"><path
class="cls-2"
d="m 1545.1753,893.49468 h 4.69 a 5.67,5.67 0 0 1 5.67,5.67 v 84.64998 a 5.67,5.67 0 0 1 -5.67,5.67 h -4.69"
id="path1-4" /><rect
class="cls-3"
x="647.6554"
y="862.80469"
width="897.52002"
height="441.10999"
rx="11.7"
id="rect2" /><path
class="cls-2"
d="m 681.12532,862.80468 v 113.47998 a 3.67,3.67 0 0 0 3.67,3.67 h 41 a 2.35,2.35 0 0 1 2.35,2.35 V 1303.9147 H 1517.6053 V 862.80468 Z M 1492.6453,1278.9147 H 753.18532 V 972.01466 a 17.06,17.06 0 0 0 -17.06,-17.06 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 h 783.99998 z"
id="path2-7" /><path
class="cls-3"
d="M 1492.6453,887.80468 V 1278.9147 H 753.18532 V 972.01466 a 17,17 0 0 0 -7.2,-13.92 v -70.28998 z"
id="path3-7"
style="fill:#ffffff" /><path
class="cls-4"
d="m 745.98532,887.80468 v 70.28998 a 17,17 0 0 0 -9.86,-3.14 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 z"
id="path4" /><rect
class="cls-2"
x="672.10535"
y="1011.4448"
width="13.53"
height="148.39999"
id="rect4" /><path
class="cls-6"
d="m 1077.2923,853.76468 h 71.71 a 2.55,2.55 0 0 1 2.55,2.55 v 6.48 h -76.8 v -6.48 a 2.55,2.55 0 0 1 2.54,-2.55 z"
id="path7" /><path
class="cls-8"
d="m 1082.9205,761.22647 h 60.8838 a 6.1958134,4.8451518 0 0 1 6.1958,4.84516 v 77.32638 h -73.2754 v -77.32638 a 6.1958134,4.8451518 0 0 1 6.1958,-4.84516 z"
id="path39"
style="fill:#b1a368" /><rect
class="cls-8"
x="1066.9833"
y="778.19855"
width="91.504646"
height="55.957298"
rx="5.5511622"
id="rect39"
style="fill:#b1a368" /><path
class="cls-2"
d="m 1158.4522,782.53954 v 47.24724 a 5.5153484,4.3130254 0 0 1 -5.5512,4.34102 h -80.3665 a 5.5511623,4.341032 0 0 1 -5.587,-4.34102 v -47.24724 a 5.5511623,4.341032 0 0 1 5.587,-4.34103 h 80.5098 a 5.5153484,4.3130254 0 0 1 5.4079,4.34103 z"
id="path41-4"
style="fill:none;stroke:#050606;stroke-width:3.16706;stroke-miterlimit:10" /><rect
class="cls-6"
x="1079.9424"
y="843.73468"
width="65.989998"
height="10.03"
id="rect8" /><path
class="cls-8"
d="M 1492.6453,887.80468 V 1278.9147 H 753.18532 V 972.01466 a 17.06,17.06 0 0 0 -17.06,-17.06 h -27.5 a 2.5,2.5 0 0 1 -2.5,-2.5 v -62.14998 a 2.5,2.5 0 0 1 2.5,-2.5 h 783.99998 m 25,-25 H 681.12532 v 113.47998 a 3.68,3.68 0 0 0 3.67,3.67 h 41 a 2.35,2.35 0 0 1 2.35,2.35 V 1303.9147 H 1517.6053 V 862.80468 Z"
id="path10" /><line
class="cls-8"
x1="745.99536"
y1="958.09467"
x2="745.99536"
y2="887.80469"
id="line10" /><rect
class="cls-8"
x="672.10535"
y="1011.4448"
width="13.53"
height="148.39999"
id="rect11" /><path
class="cls-8"
d="m 1545.1753,893.49468 h 4.69 a 5.67,5.67 0 0 1 5.67,5.67 v 84.64998 a 5.67,5.67 0 0 1 -5.67,5.67 h -4.69"
id="path14" /><path
class="cls-10"
d="m 1077.2923,853.76468 h 71.71 a 2.55,2.55 0 0 1 2.55,2.55 v 6.48 h -76.8 v -6.48 a 2.55,2.55 0 0 1 2.54,-2.55 z"
id="path16" /><rect
class="cls-11"
x="1079.9424"
y="843.73468"
width="65.989998"
height="10.03"
id="rect17" /><path
class="cls-2"
d="m 725.27532,910.38466 a 14,14 0 1 0 14,14 13.95,13.95 0 0 0 -14,-14 z m 0,21.5 a 7.55,7.55 0 1 1 7.54,-7.55 7.55,7.55 0 0 1 -7.54,7.55 z"
id="path19" /><circle
class="cls-8"
cx="725.27539"
cy="924.33466"
r="7.5500002"
id="circle19" /><circle
class="cls-8"
cx="725.27539"
cy="924.33466"
r="13.95"
id="circle20" /><path
d="m 445.36309,440.05365 c 0,11.52004 10.38375,20.85861 23.19309,20.85861 12.80937,0 23.19311,-9.33857 23.19311,-20.85861 0,-11.52005 -10.38374,-20.85861 -23.19311,-20.85861 -12.80934,0 -23.19309,9.33856 -23.19309,20.85861 z"
fill="#ccc"
id="path1"
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.458227"
transform="translate(646.6554,758.05941)" /><path
d="m 469.40305,538.40107 c -119.83415,0 -217.31582,-93.40624 -217.31582,-208.23067 0,-114.82425 97.48167,-208.23058 217.31582,-208.23058 119.83417,0 217.31585,93.40633 217.31585,208.23058 0,114.82443 -97.48168,208.23067 -217.31585,208.23067 z m 0,-386.58065 c -102.63515,0 -186.13149,80.00572 -186.13149,178.34998 0,98.32948 83.49634,178.34997 186.13149,178.34997 102.61966,0 186.13151,-80.01997 186.13151,-178.34997 0,-98.34426 -83.51185,-178.34998 -186.13151,-178.34998 z"
fill="#ccc"
id="path2"
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.474832"
transform="translate(646.6554,758.05941)" /><path
d="m 468.55618,391.96713 c -8.53552,0 -15.46205,-6.22977 -15.46205,-13.90533 v -23.51468 c 0,-22.75028 19.32709,-40.13201 36.39722,-55.47009 12.50833,-11.26363 25.45056,-22.88885 25.45056,-32.16398 0,-23.18095 -20.81195,-42.03718 -46.38573,-42.03718 -26.0067,0 -46.38619,18.0497 -46.38619,41.09158 0,7.67594 -6.92654,13.90533 -15.46208,13.90533 -8.53554,0 -15.46207,-6.22977 -15.46207,-13.9058 0,-37.99002 34.68046,-68.90262 77.31034,-68.90262 42.62989,0 77.31034,31.32967 77.31034,69.84869 0,20.81694 -17.54944,36.5856 -34.51132,51.84064 -13.452,12.07016 -27.33645,24.55758 -27.33645,35.77907 v 23.51468 c 0,7.6764 -6.92702,13.91969 -15.46257,13.91969 z"
fill="#ccc"
id="path3"
style="overflow:hidden;fill:#4d4d4d;stroke-width:0.458227;stroke:#000000;stroke-opacity:1"
transform="translate(646.6554,758.05941)" /><rect
class="cls-8"
x="647.6554"
y="862.80469"
width="897.52002"
height="441.10999"
rx="11.7"
id="rect28" /></g><path
style="fill:#ffffff;fill-opacity:0;stroke-width:0.92"
d="m 107.41785,363.03448 -0.23786,-156.30546 -2.99,-3.72057 -2.99,-3.72058 v -34.09394 -34.09394 l 150.64998,0.048 150.64999,0.048 -8.28,3.06943 c -19.31509,7.16019 -34.46167,14.82453 -50.21721,25.41044 -50.57644,33.98158 -84.35747,88.86991 -91.06203,147.96009 -1.43336,12.63279 -0.63536,44.7022 1.3876,55.76392 7.76201,42.44321 25.98398,77.92651 55.67763,108.41989 17.37837,17.84644 33.98994,30.29944 55.42867,41.55255 l 11.30534,5.93414 -134.54212,0.0167 -134.54213,0.0167 z"
id="path11" /><path
style="fill:#ffffff;fill-opacity:0;stroke-width:0.92"
d="m 107.41785,363.03448 -0.23786,-156.30546 -2.99,-3.72057 -2.99,-3.72058 v -34.09394 -34.09394 l 150.64998,0.048 150.64999,0.048 -8.28,3.06943 c -19.31509,7.16019 -34.46167,14.82453 -50.21721,25.41044 -50.57644,33.98158 -84.35747,88.86991 -91.06203,147.96009 -1.43336,12.63279 -0.63536,44.7022 1.3876,55.76392 7.76201,42.44321 25.98398,77.92651 55.67763,108.41989 17.37837,17.84644 33.98994,30.29944 55.42867,41.55255 l 11.30534,5.93414 -134.54212,0.0167 -134.54213,0.0167 z"
id="path12" /><path
style="fill:#ffffff;fill-opacity:0;stroke-width:0.92"
d="m 107.41785,363.03448 -0.23786,-156.30546 -2.99,-3.72057 -2.99,-3.72058 v -34.09394 -34.09394 l 150.64998,0.048 150.64999,0.048 -8.28,3.06943 c -19.31509,7.16019 -34.46167,14.82453 -50.21721,25.41044 -50.57644,33.98158 -84.35747,88.86991 -91.06203,147.96009 -1.43336,12.63279 -0.63536,44.7022 1.3876,55.76392 7.76201,42.44321 25.98398,77.92651 55.67763,108.41989 17.37837,17.84644 33.98994,30.29944 55.42867,41.55255 l 11.30534,5.93414 -134.54212,0.0167 -134.54213,0.0167 z"
id="path13" /></svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 91 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -1,26 +1,33 @@
import { DeviceWrapper } from "@app/DeviceWrapper.js";
import { PageRouter } from "@app/PageRouter.js";
import { CommandPalette } from "@components/CommandPalette.js";
import { DeviceSelector } from "@components/DeviceSelector.js";
import { DialogManager } from "@components/Dialog/DialogManager.js";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.js";
import { Toaster } from "@components/Toaster.js";
import Footer from "@components/UI/Footer.js";
import { ThemeController } from "@components/generic/ThemeController.js";
import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
import { Dashboard } from "@pages/Dashboard/index.js";
import { MapProvider } from "react-map-gl";
import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
import { DialogManager } from "@components/Dialog/DialogManager.tsx";
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx";
import { Toaster } from "@components/Toaster.tsx";
import Footer from "@components/UI/Footer.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { Dashboard } from "@pages/Dashboard/index.tsx";
import { ErrorBoundary } from "react-error-boundary";
import { ErrorPage } from "@components/UI/ErrorPage.tsx";
import { MapProvider } from "react-map-gl/maplibre";
import { CommandPalette } from "@components/CommandPalette/index.tsx";
import { SidebarProvider } from "@core/stores/sidebarStore.tsx";
import { useTheme } from "@core/hooks/useTheme.ts";
import { Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
export const App = (): JSX.Element => {
export function App() {
const { getDevice } = useDeviceStore();
const { selectedDevice, setConnectDialogOpen, connectDialogOpen } =
useAppStore();
const device = getDevice(selectedDevice);
// Sets up light/dark mode based on user preferences or system settings
useTheme();
return (
<ThemeController>
<ErrorBoundary FallbackComponent={ErrorPage}>
<NewDeviceDialog
open={connectDialogOpen}
onOpenChange={(open) => {
@@ -28,30 +35,35 @@ export const App = (): JSX.Element => {
}}
/>
<Toaster />
<MapProvider>
<DeviceWrapper device={device}>
<div className="flex h-screen flex-col overflow-hidden bg-backgroundPrimary text-textPrimary">
<div className="flex flex-grow">
<DeviceSelector />
<div className="flex flex-grow flex-col">
{device ? (
<div className="flex h-screen">
<TanStackRouterDevtools position="bottom-right" />
<DeviceWrapper device={device}>
<div
className="flex h-screen flex-col bg-background-primary text-text-primary"
style={{ scrollbarWidth: "thin" }}
>
<SidebarProvider>
<div className="h-full flex flex-col">
{device
? (
<div className="h-full flex w-full">
<DialogManager />
<KeyBackupReminder />
<CommandPalette />
<PageRouter />
<MapProvider>
<Outlet />
</MapProvider>
</div>
) : (
)
: (
<>
<Dashboard />
<div className="flex flex-grow" />
<Footer />
</>
)}
</div>
</div>
</div>
</DeviceWrapper>
</MapProvider>
</ThemeController>
</SidebarProvider>
</div>
</DeviceWrapper>
</ErrorBoundary>
);
};
}

View File

@@ -1,5 +1,5 @@
import { DeviceContext } from "@core/stores/deviceStore.js";
import type { Device } from "@core/stores/deviceStore.js";
import { DeviceContext } from "@core/stores/deviceStore.ts";
import type { Device } from "@core/stores/deviceStore.ts";
import type { ReactNode } from "react";
export interface DeviceWrapperProps {
@@ -7,10 +7,7 @@ export interface DeviceWrapperProps {
device?: Device;
}
export const DeviceWrapper = ({
children,
device,
}: DeviceWrapperProps): JSX.Element => {
export const DeviceWrapper = ({ children, device }: DeviceWrapperProps) => {
return (
<DeviceContext.Provider value={device}>{children}</DeviceContext.Provider>
);

View File

@@ -1,19 +0,0 @@
import { useDevice } from "@core/stores/deviceStore.js";
import { ChannelsPage } from "@pages/Channels.js";
import { ConfigPage } from "@pages/Config/index.js";
import { MapPage } from "@pages/Map.js";
import { MessagesPage } from "@pages/Messages.js";
import { NodesPage } from "@pages/Nodes.js";
export const PageRouter = (): JSX.Element => {
const { activePage } = useDevice();
return (
<>
{activePage === "messages" && <MessagesPage />}
{activePage === "map" && <MapPage />}
{activePage === "config" && <ConfigPage />}
{activePage === "channels" && <ChannelsPage />}
{activePage === "nodes" && <NodesPage />}
</>
);
};

43
src/__mocks__/README.md Normal file
View File

@@ -0,0 +1,43 @@
# Mocks Directory
This directory contains mock implementations used by Vitest for testing.
## Structure
The directory structure mirrors the actual project structure to make mocking
more intuitive:
```
__mocks__/
├── components/
│ └── UI/
│ ├── Dialog.tsx
│ ├── Button.tsx
│ ├── Checkbox.tsx
│ └── ...
├── core/
│ └── ...
└── ...
```
## Auto-mocking
Vitest will automatically use the mock files in this directory when the
corresponding module is imported in tests. For example, when a test imports
`@components/UI/Dialog.tsx`, Vitest will use
`__mocks__/components/UI/Dialog.tsx` instead.
## Creating New Mocks
To create a new mock:
1. Create a file in the same relative path as the original module
2. Export the mocked functionality with the same names as the original
3. Add a `vi.mock()` statement to `vitest.setup.ts` if needed
## Mock Guidelines
- Keep mocks as simple as possible
- Use `data-testid` attributes for easy querying in tests
- Implement just enough functionality to test the component
- Use TypeScript types to ensure compatibility with the original module

View File

@@ -0,0 +1,21 @@
import { vi } from "vitest";
vi.mock("@components/UI/Button.tsx", () => ({
Button: ({ children, name, disabled, onClick }: {
children: React.ReactNode;
variant: string;
name: string;
disabled?: boolean;
onClick: () => void;
}) => (
<button
type="button"
name={name}
data-testid={`button-${name}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
),
}));

View File

@@ -0,0 +1,19 @@
import { vi } from "vitest";
vi.mock("@components/UI/Checkbox.tsx", () => ({
Checkbox: (
{ id, checked, onChange }: {
id: string;
checked: boolean;
onChange: () => void;
},
) => (
<input
data-testid="checkbox"
type="checkbox"
id={id}
checked={checked}
onChange={onChange}
/>
),
}));

View File

@@ -0,0 +1,45 @@
import React from "react";
export const Dialog = ({ children, open }: {
children: React.ReactNode;
open: boolean;
onOpenChange?: (open: boolean) => void;
}) => open ? <div data-testid="dialog">{children}</div> : null;
export const DialogContent = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => <div data-testid="dialog-content" className={className}>{children}</div>;
export const DialogHeader = ({
children,
}: {
children: React.ReactNode;
}) => <div data-testid="dialog-header">{children}</div>;
export const DialogTitle = ({
children,
}: {
children: React.ReactNode;
}) => <div data-testid="dialog-title">{children}</div>;
export const DialogDescription = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => (
<div data-testid="dialog-description" className={className}>{children}</div>
);
export const DialogFooter = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => <div data-testid="dialog-footer" className={className}>{children}</div>;

View File

@@ -0,0 +1,15 @@
import { vi } from "vitest";
vi.mock("@components/UI/Label.tsx", () => ({
Label: (
{ children, htmlFor, className }: {
children: React.ReactNode;
htmlFor: string;
className?: string;
},
) => (
<label data-testid="label" htmlFor={htmlFor} className={className}>
{children}
</label>
),
}));

View File

@@ -0,0 +1,11 @@
import { vi } from "vitest";
vi.mock("@components/UI/Typography/Link.tsx", () => ({
Link: (
{ children, href, className }: {
children: React.ReactNode;
href: string;
className?: string;
},
) => <a data-testid="link" href={href} className={className}>{children}</a>,
}));

View File

@@ -0,0 +1,102 @@
import React from "react";
import {
BatteryFullIcon,
BatteryLowIcon,
BatteryMediumIcon,
PlugZapIcon,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { DeviceMetrics } from "./types.ts";
type BatteryStatusKey = keyof typeof BATTERY_STATUS;
interface BatteryStatusProps {
deviceMetrics?: DeviceMetrics | null;
}
interface BatteryStatusProps {
deviceMetrics?: DeviceMetrics | null;
}
interface StatusConfig {
Icon: React.ElementType;
className: string;
text: string;
}
const BATTERY_STATUS = {
PLUGGED_IN: "PLUGGED_IN",
FULL: "FULL",
MEDIUM: "MEDIUM",
LOW: "LOW",
} as const;
export const getBatteryStatus = (level: number): BatteryStatusKey => {
if (level > 100) {
return BATTERY_STATUS.PLUGGED_IN;
}
if (level > 80) {
return BATTERY_STATUS.FULL;
}
if (level > 20) {
return BATTERY_STATUS.MEDIUM;
}
return BATTERY_STATUS.LOW;
};
const BatteryStatus: React.FC<BatteryStatusProps> = ({ deviceMetrics }) => {
const { t } = useTranslation();
if (
deviceMetrics?.batteryLevel === undefined ||
deviceMetrics?.batteryLevel === null
) {
return null;
}
const { batteryLevel } = deviceMetrics;
const statusKey = getBatteryStatus(batteryLevel);
const statusConfigMap: Record<BatteryStatusKey, StatusConfig> = {
[BATTERY_STATUS.PLUGGED_IN]: {
Icon: PlugZapIcon,
className: "text-gray-500",
text: t("batteryStatus.pluggedIn"),
},
[BATTERY_STATUS.FULL]: {
Icon: BatteryFullIcon,
className: "text-green-500",
text: t("batteryStatus.charging", { level: batteryLevel }),
},
[BATTERY_STATUS.MEDIUM]: {
Icon: BatteryMediumIcon,
className: "text-yellow-500",
text: t("batteryStatus.charging", { level: batteryLevel }),
},
[BATTERY_STATUS.LOW]: {
Icon: BatteryLowIcon,
className: "text-red-500",
text: t("batteryStatus.charging", { level: batteryLevel }),
},
};
// 3. Use the key to get the current state configuration
const {
Icon: BatteryIcon,
className: iconClassName,
text: statusText,
} = statusConfigMap[statusKey];
return (
<div
className="flex items-center gap-1 mt-0.5 "
aria-label={t("batteryStatus.title")}
>
<BatteryIcon size={22} className={iconClassName} />
{statusText}
</div>
);
};
export default BatteryStatus;

View File

@@ -1,418 +0,0 @@
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@components/UI/Command.js";
import { useAppStore } from "@core/stores/appStore.js";
import { useDevice, useDeviceStore } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import { useCommandState } from "cmdk";
import {
ArrowLeftRightIcon,
BoxSelectIcon,
BugIcon,
EraserIcon,
FactoryIcon,
LayersIcon,
LayoutIcon,
LinkIcon,
type LucideIcon,
MapIcon,
MessageSquareIcon,
MoonIcon,
PaletteIcon,
PlusIcon,
PowerIcon,
QrCodeIcon,
RefreshCwIcon,
SettingsIcon,
SmartphoneIcon,
TrashIcon,
UsersIcon,
XCircleIcon,
} from "lucide-react";
import { useEffect } from "react";
export interface Group {
label: string;
icon: LucideIcon;
commands: Command[];
}
export interface Command {
label: string;
icon: LucideIcon;
action?: () => void;
subItems?: SubItem[];
tags?: string[];
}
export interface SubItem {
label: string;
icon: JSX.Element;
action: () => void;
}
export const CommandPalette = (): JSX.Element => {
const {
commandPaletteOpen,
setCommandPaletteOpen,
setSelectedDevice,
removeDevice,
selectedDevice,
darkMode,
setDarkMode,
setAccent,
} = useAppStore();
const { getDevices } = useDeviceStore();
const { setDialogOpen, setActivePage, connection } = useDevice();
const groups: Group[] = [
{
label: "Goto",
icon: LinkIcon,
commands: [
{
label: "Messages",
icon: MessageSquareIcon,
action() {
setActivePage("messages");
},
},
{
label: "Map",
icon: MapIcon,
action() {
setActivePage("map");
},
},
{
label: "Config",
icon: SettingsIcon,
action() {
setActivePage("config");
},
tags: ["settings"],
},
{
label: "Channels",
icon: LayersIcon,
action() {
setActivePage("channels");
},
},
{
label: "Nodes",
icon: UsersIcon,
action() {
setActivePage("nodes");
},
},
],
},
{
label: "Manage",
icon: SmartphoneIcon,
commands: [
{
label: "Switch Node",
icon: ArrowLeftRightIcon,
subItems: getDevices().map((device) => {
return {
label:
device.nodes.get(device.hardware.myNodeNum)?.user?.longName ??
device.hardware.myNodeNum.toString(),
icon: (
<Hashicon
size={16}
value={device.hardware.myNodeNum.toString()}
/>
),
action() {
setSelectedDevice(device.id);
},
};
}),
},
{
label: "Connect New Node",
icon: PlusIcon,
action() {
setSelectedDevice(0);
},
},
],
},
{
label: "Contextual",
icon: BoxSelectIcon,
commands: [
{
label: "QR Code",
icon: QrCodeIcon,
subItems: [
{
label: "Generator",
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("QR", true);
},
},
{
label: "Import",
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("import", true);
},
},
],
},
{
label: "Disconnect",
icon: XCircleIcon,
action() {
void connection?.disconnect();
setSelectedDevice(0);
removeDevice(selectedDevice ?? 0);
},
},
{
label: "Schedule Shutdown",
icon: PowerIcon,
action() {
setDialogOpen("shutdown", true);
},
},
{
label: "Schedule Reboot",
icon: RefreshCwIcon,
action() {
setDialogOpen("reboot", true);
},
},
{
label: "Reset Nodes",
icon: TrashIcon,
action() {
connection?.resetNodes();
},
},
{
label: "Factory Reset Device",
icon: FactoryIcon,
action() {
connection?.factoryResetDevice();
},
},
{
label: "Factory Reset Config",
icon: FactoryIcon,
action() {
connection?.factoryResetConfig();
},
},
],
},
{
label: "Debug",
icon: BugIcon,
commands: [
{
label: "Reconfigure",
icon: RefreshCwIcon,
action() {
void connection?.configure();
},
},
{
label: "[WIP] Clear Messages",
icon: EraserIcon,
action() {
alert("This feature is not implemented");
},
},
],
},
{
label: "Application",
icon: LayoutIcon,
commands: [
{
label: "Toggle Dark Mode",
icon: MoonIcon,
action() {
setDarkMode(!darkMode);
},
},
{
label: "Accent Color",
icon: PaletteIcon,
subItems: [
{
label: "Red",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#f25555]" : "bg-[#f28585]"
}`}
/>
),
action() {
setAccent("red");
},
},
{
label: "Orange",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#e1720b]" : "bg-[#edb17a]"
}`}
/>
),
action() {
setAccent("orange");
},
},
{
label: "Yellow",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#ac8c1a]" : "bg-[#e0cc87]"
}`}
/>
),
action() {
setAccent("yellow");
},
},
{
label: "Green",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#27a341]" : "bg-[#8bc9c5]"
}`}
/>
),
action() {
setAccent("green");
},
},
{
label: "Blue",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#2093fe]" : "bg-[#70afea]"
}`}
/>
),
action() {
setAccent("blue");
},
},
{
label: "Purple",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#926bff]" : "bg-[#a09eef]"
}`}
/>
),
action() {
setAccent("purple");
},
},
{
label: "Pink",
icon: (
<span
className={`h-3 w-3 rounded-full ${
darkMode ? "bg-[#e454c4]" : "bg-[#dba0c7]"
}`}
/>
),
action() {
setAccent("pink");
},
},
],
},
],
},
];
useEffect(() => {
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setCommandPaletteOpen(true);
}
};
window.addEventListener("keydown", handleKeydown);
return () => window.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]);
return (
<CommandDialog
open={commandPaletteOpen}
onOpenChange={setCommandPaletteOpen}
>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{groups.map((group) => (
<CommandGroup key={group.label} heading={group.label}>
{group.commands.map((command) => (
<div key={command.label}>
<CommandItem
onSelect={() => {
command.action?.();
setCommandPaletteOpen(false);
}}
>
<command.icon size={16} className="mr-2" />
{command.label}
</CommandItem>
{command.subItems?.map((subItem) => (
<SubItem
key={subItem.label}
label={subItem.label}
icon={subItem.icon}
action={subItem.action}
/>
))}
</div>
))}
</CommandGroup>
))}
</CommandList>
</CommandDialog>
);
};
const SubItem = ({
label,
icon,
action,
}: {
label: string;
icon: React.ReactNode;
action: () => void;
}) => {
const search = useCommandState((state) => state.search);
if (!search) return null;
return (
<CommandItem onSelect={action}>
{icon}
{label}
</CommandItem>
);
};

View File

@@ -0,0 +1,346 @@
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@components/UI/Command.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts";
import { useCommandState } from "cmdk";
import {
ArrowLeftRightIcon,
BoxSelectIcon,
BugIcon,
EraserIcon,
FactoryIcon,
LayersIcon,
LinkIcon,
type LucideIcon,
MapIcon,
MessageSquareIcon,
Pin,
PlusIcon,
PowerIcon,
QrCodeIcon,
RefreshCwIcon,
SettingsIcon,
SmartphoneIcon,
TrashIcon,
UsersIcon,
} from "lucide-react";
import { useEffect } from "react";
import { Avatar } from "@components/UI/Avatar.tsx";
import { cn } from "@core/utils/cn.ts";
import { useTranslation } from "react-i18next";
import { usePinnedItems } from "@core/hooks/usePinnedItems.ts";
import { useNavigate } from "@tanstack/react-router";
export interface Group {
id: string;
label: string;
icon: LucideIcon;
commands: Command[];
}
export interface Command {
label: string;
icon: LucideIcon;
action?: () => void;
subItems?: SubItem[];
tags?: string[];
}
export interface SubItem {
label: string;
icon: React.ReactNode;
action: () => void;
}
export const CommandPalette = () => {
const {
commandPaletteOpen,
setCommandPaletteOpen,
setConnectDialogOpen,
setSelectedDevice,
} = useAppStore();
const { getDevices } = useDeviceStore();
const { setDialogOpen, getNode, connection } = useDevice();
const { pinnedItems, togglePinnedItem } = usePinnedItems({
storageName: "pinnedCommandMenuGroups",
});
const { t } = useTranslation("commandPalette");
const navigate = useNavigate({ from: "/" });
const groups: Group[] = [
{
id: "gotoGroup",
label: t("goto.label"),
icon: LinkIcon,
commands: [
{
label: t("goto.command.messages"),
icon: MessageSquareIcon,
action() {
navigate({ to: "/messages" });
},
},
{
label: t("goto.command.map"),
icon: MapIcon,
action() {
navigate({ to: "/map" });
},
},
{
label: t("goto.command.config"),
icon: SettingsIcon,
action() {
navigate({ to: "/config" });
},
tags: ["settings"],
},
{
label: t("goto.command.channels"),
icon: LayersIcon,
action() {
navigate({ to: "/channels" });
},
},
{
label: t("goto.command.nodes"),
icon: UsersIcon,
action() {
navigate({ to: "/nodes" });
},
},
],
},
{
id: "manageGroup",
label: t("manage.label"),
icon: SmartphoneIcon,
commands: [
{
label: t("manage.command.switchNode"),
icon: ArrowLeftRightIcon,
subItems: getDevices().map((device) => ({
label: getNode(device.hardware.myNodeNum)?.user?.longName ??
t("unknown.shortName"),
icon: (
<Avatar
text={getNode(device.hardware.myNodeNum)?.user?.shortName ??
t("unknown.shortName")}
/>
),
action() {
setSelectedDevice(device.id);
},
})),
},
{
label: t("manage.command.connectNewNode"),
icon: PlusIcon,
action() {
setConnectDialogOpen(true);
},
},
],
},
{
id: "contextualGroup",
label: t("contextual.label"),
icon: BoxSelectIcon,
commands: [
{
label: t("contextual.command.qrCode"),
icon: QrCodeIcon,
subItems: [
{
label: t("contextual.command.qrGenerator"),
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("QR", true);
},
},
{
label: t("contextual.command.qrImport"),
icon: <QrCodeIcon size={16} />,
action() {
setDialogOpen("import", true);
},
},
],
},
{
label: t("contextual.command.scheduleShutdown"),
icon: PowerIcon,
action() {
setDialogOpen("shutdown", true);
},
},
{
label: t("contextual.command.scheduleReboot"),
icon: RefreshCwIcon,
action() {
setDialogOpen("reboot", true);
},
},
{
label: t("contextual.command.rebootToOtaMode"),
icon: RefreshCwIcon,
action() {
setDialogOpen("rebootOTA", true);
},
},
{
label: t("contextual.command.resetNodeDb"),
icon: TrashIcon,
action() {
connection?.resetNodes();
},
},
{
label: t("contextual.command.factoryResetDevice"),
icon: FactoryIcon,
action() {
connection?.factoryResetDevice();
},
},
{
label: t("contextual.command.factoryResetConfig"),
icon: FactoryIcon,
action() {
connection?.factoryResetConfig();
},
},
],
},
{
id: "debugGroup",
label: t("debug.label"),
icon: BugIcon,
commands: [
{
label: t("debug.command.reconfigure"),
icon: RefreshCwIcon,
action() {
void connection?.configure();
},
},
{
label: t("debug.command.clearAllStoredMessages"),
icon: EraserIcon,
action() {
setDialogOpen("deleteMessages", true);
},
},
],
},
];
const sortedGroups = [...groups].sort((a, b) => {
const aPinned = pinnedItems.includes(a.id) ? 1 : 0;
const bPinned = pinnedItems.includes(b.id) ? 1 : 0;
return bPinned - aPinned;
});
useEffect(() => {
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setCommandPaletteOpen(true);
}
};
globalThis.addEventListener("keydown", handleKeydown);
return () => globalThis.removeEventListener("keydown", handleKeydown);
}, [setCommandPaletteOpen]);
return (
<CommandDialog
open={commandPaletteOpen}
onOpenChange={setCommandPaletteOpen}
>
<CommandInput placeholder={t("search.commandPalette")} />
<CommandList>
<CommandEmpty>{t("emptyState")}</CommandEmpty>
{sortedGroups.map((group) => (
<CommandGroup
key={group.label}
heading={
<div className="flex items-center justify-between">
<span>{group.label}</span>
<button
type="button"
onClick={() => togglePinnedItem(group.id)}
className={cn(
"transition-all duration-300 scale-100 cursor-pointer p-2 focus:*:data-label:opacity-100",
)}
aria-description={pinnedItems.includes(group.label)
? t("unpinGroup.label")
: t("pinGroup.label")}
>
<span
data-label
className="transition-all block absolute w-full mb-auto mt-auto ml-0 mr-0 text-xs left-0 -top-5 opacity-0 rounded-lg"
/>
<Pin
size={16}
className={cn(
"transition-opacity",
pinnedItems.includes(group.id)
? "opacity-100 text-red-500"
: "opacity-40 hover:opacity-70",
)}
/>
</button>
</div>
}
>
{group.commands.map((command) => (
<div key={command.label}>
<CommandItem
onSelect={() => {
command.action?.();
setCommandPaletteOpen(false);
}}
>
<command.icon size={16} className="mr-2" />
{command.label}
</CommandItem>
{command.subItems?.map((subItem) => (
<SubItem
key={subItem.label}
label={subItem.label}
icon={subItem.icon}
action={subItem.action}
/>
))}
</div>
))}
</CommandGroup>
))}
</CommandList>
</CommandDialog>
);
};
const SubItem = ({
label,
icon,
action,
}: {
label: string;
icon: React.ReactNode;
action: () => void;
}) => {
const search = useCommandState((state) => state.search);
if (!search) return null;
return (
<CommandItem onSelect={action}>
{icon}
{label}
</CommandItem>
);
};

View File

@@ -0,0 +1,239 @@
import { cn } from "@core/utils/cn.ts";
import {
CpuIcon,
Languages,
type LucideIcon,
Palette,
PenLine,
Search as SearchIcon,
ZapIcon,
} from "lucide-react";
import BatteryStatus from "./BatteryStatus.tsx";
import { Subtle } from "./UI/Typography/Subtle.tsx";
import { Avatar } from "./UI/Avatar.tsx";
import type { DeviceMetrics } from "./types.ts";
import { Button } from "./UI/Button.tsx";
import React, { Fragment } from "react";
import { useTranslation } from "react-i18next";
import ThemeSwitcher from "./ThemeSwitcher.tsx";
import LanguageSwitcher from "./LanguageSwitcher.tsx";
interface DeviceInfoPanelProps {
isCollapsed: boolean;
deviceMetrics: DeviceMetrics;
firmwareVersion: string;
user: {
shortName: string;
longName: string;
};
setDialogOpen: () => void;
setCommandPaletteOpen: () => void;
disableHover?: boolean;
}
interface InfoDisplayItem {
id: string;
label: string;
icon?: LucideIcon;
customComponent?: React.ReactNode;
value?: string | number | null;
}
interface ActionButtonConfig {
id: string;
label: string;
icon: LucideIcon;
onClick?: () => void;
render?: () => React.ReactNode;
}
export const DeviceInfoPanel = ({
deviceMetrics,
firmwareVersion,
user,
isCollapsed,
setDialogOpen,
setCommandPaletteOpen,
disableHover = false,
}: DeviceInfoPanelProps) => {
const { t } = useTranslation();
const { batteryLevel, voltage } = deviceMetrics;
const deviceInfoItems: InfoDisplayItem[] = [
{
id: "battery",
label: t("batteryStatus.title"),
customComponent: <BatteryStatus deviceMetrics={deviceMetrics} />,
value: batteryLevel !== undefined ? `${batteryLevel}%` : "N/A",
},
{
id: "voltage",
label: t("batteryVoltage.title"),
icon: ZapIcon,
value: voltage !== undefined
? `${voltage?.toPrecision(3)} V`
: t("unknown.notAvailable", "N/A"),
},
{
id: "firmware",
label: t("sidebar.deviceInfo.firmware.title"),
icon: CpuIcon,
value: firmwareVersion ?? t("unknown.notAvailable", "N/A"),
},
];
const actionButtons: ActionButtonConfig[] = [
{
id: "changeName",
label: t("sidebar.deviceInfo.deviceName.changeName"),
icon: PenLine,
onClick: setDialogOpen,
},
{
id: "commandMenu",
label: t("page.title", { ns: "commandPalette" }),
icon: SearchIcon,
onClick: setCommandPaletteOpen,
},
{
id: "theme",
label: t("theme.changeTheme"),
icon: Palette,
render: () => <ThemeSwitcher />,
},
{
id: "language",
label: t("language.changeLanguage"),
icon: Languages,
render: () => <LanguageSwitcher />,
},
];
return (
<>
<div
className={cn(
"flex items-center gap-3 p-1 flex-shrink-0",
isCollapsed && "justify-center",
)}
>
<Avatar
text={user.shortName}
className={cn("flex-shrink-0", isCollapsed && "")}
size="sm"
/>
{!isCollapsed && (
<p
className={cn(
"text-sm font-medium text-gray-800 dark:text-gray-200",
"transition-opacity duration-300 ease-in-out truncate",
)}
>
{user.longName}
</p>
)}
</div>
{!isCollapsed && (
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700 flex-shrink-0">
</div>
)}
<div
className={cn(
"flex flex-col gap-2 mt-1",
"transition-all duration-300 ease-in-out",
isCollapsed
? "opacity-0 max-w-0 h-0 invisible pointer-events-none"
: "opacity-100 max-w-xs h-auto visible",
)}
>
{deviceInfoItems.map((item) => {
const IconComponent = item.icon;
return (
<div
key={item.id}
className="flex items-center gap-2.5 text-sm"
>
{IconComponent && (
<IconComponent
size={16}
className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0"
/>
)}
{item.customComponent}
{item.id !== "battery" && (
<Subtle className="text-gray-600 dark:text-gray-300">
{item.label}: {item.value}
</Subtle>
)}
</div>
);
})}
</div>
{!isCollapsed && (
<div className="my-2 h-px bg-gray-200 dark:bg-gray-700 flex-shrink-0">
</div>
)}
<div
className={cn(
"flex flex-col gap-1 mt-1",
"transition-all duration-300 ease-in-out",
isCollapsed
? "opacity-0 max-w-0 h-0 invisible pointer-events-none"
: "opacity-100 max-w-xs visible",
)}
>
{actionButtons.map((buttonItem) => {
const Icon = buttonItem.icon;
if (buttonItem.render) {
return (
<Fragment key={buttonItem.id}>
{buttonItem.render()}
</Fragment>
);
}
return (
<Button
key={buttonItem.id}
variant="ghost"
aria-label={buttonItem.label}
onClick={buttonItem.onClick}
className={cn(
"group",
"flex w-full items-center justify-start text-sm p-1.5 rounded-md",
"gap-2.5",
"transition-colors duration-150",
!disableHover && "hover:bg-gray-100 dark:hover:bg-gray-700",
)}
>
<Icon
size={16}
className={cn(
"flex-shrink-0 w-4",
"text-gray-500 dark:text-gray-400",
"transition-colors duration-150",
!disableHover &&
"group-hover:text-gray-700 dark:group-hover:text-gray-200",
)}
/>
<Subtle
className={cn(
"text-sm",
"text-gray-600 dark:text-gray-300",
"transition-colors duration-150",
!disableHover &&
"group-hover:text-gray-800 dark:group-hover:text-gray-100",
)}
>
{buttonItem.label}
</Subtle>
</Button>
);
})}
</div>
</>
);
};

View File

@@ -1,86 +0,0 @@
import { DeviceSelectorButton } from "@components/DeviceSelectorButton.js";
import { Separator } from "@components/UI/Seperator.js";
import { Code } from "@components/UI/Typography/Code.js";
import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react";
import {
HomeIcon,
LanguagesIcon,
MoonIcon,
PlusIcon,
SearchIcon,
SunIcon,
} from "lucide-react";
export const DeviceSelector = (): JSX.Element => {
const { getDevices } = useDeviceStore();
const {
selectedDevice,
setSelectedDevice,
darkMode,
setDarkMode,
setCommandPaletteOpen,
setConnectDialogOpen,
} = useAppStore();
return (
<nav className="flex flex-col justify-between border-r-[0.5px] border-slate-300 bg-transparent pt-2 dark:border-slate-700">
<div className="flex flex-col overflow-y-hidden">
<ul className="flex w-20 grow flex-col items-center space-y-4 bg-transparent py-4 px-5">
<DeviceSelectorButton
active={selectedDevice === 0}
onClick={() => {
setSelectedDevice(0);
}}
>
<HomeIcon />
</DeviceSelectorButton>
{getDevices().map((device) => (
<DeviceSelectorButton
key={device.id}
onClick={() => {
setSelectedDevice(device.id);
}}
active={selectedDevice === device.id}
>
<Hashicon
size={24}
value={device.hardware.myNodeNum.toString()}
/>
</DeviceSelectorButton>
))}
<Separator />
<button
type="button"
onClick={() => setConnectDialogOpen(true)}
className="transition-all duration-300 hover:text-accent"
>
<PlusIcon />
</button>
</ul>
</div>
<div className="flex w-20 flex-col items-center space-y-5 bg-transparent px-5 pb-5">
<button
type="button"
className="transition-all hover:text-accent"
onClick={() => setDarkMode(!darkMode)}
>
{darkMode ? <SunIcon /> : <MoonIcon />}
</button>
<button
type="button"
className="transition-all hover:text-accent"
onClick={() => setCommandPaletteOpen(true)}
>
<SearchIcon />
</button>
<button type="button" className="transition-all hover:text-accent">
<LanguagesIcon />
</button>
<Separator />
<Code>{process.env.COMMIT_HASH}</Code>
</div>
</nav>
);
};

View File

@@ -1,24 +0,0 @@
export interface DeviceSelectorButtonProps {
active: boolean;
onClick: () => void;
children?: React.ReactNode;
}
export const DeviceSelectorButton = ({
active,
onClick,
children,
}: DeviceSelectorButtonProps): JSX.Element => (
<li
className="aspect-w-1 aspect-h-1 relative w-full"
onClick={onClick}
onKeyDown={onClick}
>
{active && (
<div className="absolute -left-2 h-10 w-1.5 rounded-full bg-accent" />
)}
<div className="flex aspect-square cursor-pointer flex-col items-center justify-center">
{children}
</div>
</li>
);

View File

@@ -0,0 +1,80 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
// Ensure the path is correct for import
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
vi.mock("@core/stores/messageStore", () => ({
useMessageStore: vi.fn(() => ({
deleteAllMessages: vi.fn(),
})),
}));
describe("DeleteMessagesDialog", () => {
const mockOnOpenChange = vi.fn();
const mockClearAllMessages = vi.fn();
beforeEach(() => {
mockOnOpenChange.mockClear();
mockClearAllMessages.mockClear();
const mockedUseMessageStore = vi.mocked(useMessageStore);
mockedUseMessageStore.mockImplementation(() => ({
deleteAllMessages: mockClearAllMessages,
}));
mockedUseMessageStore.mockClear();
});
it("calls onOpenChange with false when the close button (X) is clicked", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
const closeButton = screen.queryByTestId("dialog-close-button");
if (!closeButton) {
throw new Error(
"Dialog close button with data-testid='dialog-close-button' not found. Did you add it to the component?",
);
}
fireEvent.click(closeButton);
expect(mockOnOpenChange).toHaveBeenCalledTimes(1);
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
});
it("renders the dialog when open is true", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
expect(screen.getByText("Clear All Messages")).toBeInTheDocument();
expect(screen.getByText(/This action will clear all message history./))
.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Dismiss" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Clear Messages" }))
.toBeInTheDocument();
});
it("does not render the dialog when open is false", () => {
render(
<DeleteMessagesDialog open={false} onOpenChange={mockOnOpenChange} />,
);
expect(screen.queryByText("Clear All Messages")).toBeNull();
});
it("calls onOpenChange with false when the dismiss button is clicked", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
expect(mockOnOpenChange).toHaveBeenCalledTimes(1); // Add count check
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
});
it("calls deleteAllMessages and onOpenChange with false when the clear messages button is clicked", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
fireEvent.click(screen.getByRole("button", { name: "Clear Messages" }));
expect(mockClearAllMessages).toHaveBeenCalledTimes(1);
expect(mockOnOpenChange).toHaveBeenCalledTimes(1); // Add count check
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
});
});

View File

@@ -0,0 +1,65 @@
import { Button } from "@components/UI/Button.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { AlertTriangleIcon } from "lucide-react";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { useTranslation } from "react-i18next";
export interface DeleteMessagesDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const DeleteMessagesDialog = ({
open,
onOpenChange,
}: DeleteMessagesDialogProps) => {
const { t } = useTranslation("dialog");
const { deleteAllMessages } = useMessageStore();
const handleCloseDialog = () => {
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogClose data-testid="dialog-close-button" />
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<AlertTriangleIcon className="h-5 w-5 text-warning" />
{t("deleteMessages.title")}
</DialogTitle>
<DialogDescription>
{t("deleteMessages.description")}
</DialogDescription>
</DialogHeader>
<DialogFooter className="mt-4">
<Button
variant="outline"
onClick={handleCloseDialog}
name="dismiss"
>
{t("button.dismiss")}
</Button>
<Button
variant="destructive"
onClick={() => {
deleteAllMessages();
handleCloseDialog();
}}
name="clearMessages"
>
{t("button.clearMessages")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,17 +1,22 @@
import { useDevice } from "@app/core/stores/deviceStore.js";
import { Button } from "@components/UI/Button.js";
import { useDevice } from "@core/stores/deviceStore.ts";
import { create } from "@bufbuild/protobuf";
import { Button } from "@components/UI/Button.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.js";
import { Input } from "@components/UI/Input.js";
import { Label } from "@components/UI/Label.js";
import { Protobuf } from "@meshtastic/js";
} from "@components/UI/Dialog.tsx";
import { Protobuf } from "@meshtastic/core";
import { useForm } from "react-hook-form";
import { GenericInput } from "@components/Form/FormInput.tsx";
import { useTranslation } from "react-i18next";
import { Label } from "../UI/Label.tsx";
import z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
export interface User {
longName: string;
@@ -26,48 +31,117 @@ export interface DeviceNameDialogProps {
export const DeviceNameDialog = ({
open,
onOpenChange,
}: DeviceNameDialogProps): JSX.Element => {
const { hardware, nodes, connection } = useDevice();
}: DeviceNameDialogProps) => {
const { t } = useTranslation("dialog");
const { hardware, getNode, connection } = useDevice();
const myNode = getNode(hardware.myNodeNum);
const myNode = nodes.get(hardware.myNodeNum);
const defaultValues = {
shortName: myNode?.user?.shortName ?? "",
longName: myNode?.user?.longName ?? "",
};
const { register, handleSubmit } = useForm<User>({
values: {
longName: myNode?.user?.longName ?? "Unknown",
shortName: myNode?.user?.shortName ?? "Unknown",
},
const deviceNameSchema = z.object({
longName: z
.string()
.min(1, t("deviceName.validation.longNameMin"))
.max(40, t("deviceName.validation.longNameMax")),
shortName: z
.string()
.min(2, t("deviceName.validation.shortNameMin"))
.max(4, t("deviceName.validation.shortNameMax")),
});
const { getValues, reset, control, handleSubmit } = useForm<User>({
values: defaultValues,
resolver: zodResolver(deviceNameSchema),
});
const onSubmit = handleSubmit((data) => {
connection?.setOwner(
new Protobuf.Mesh.User({
...myNode?.user,
create(Protobuf.Mesh.UserSchema, {
...data,
}),
);
onOpenChange(false);
});
const handleReset = () => {
reset(defaultValues);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogClose />
<DialogHeader>
<DialogTitle>Change Device Name</DialogTitle>
<DialogTitle>{t("deviceName.title")}</DialogTitle>
<DialogDescription>
The Device will restart once the config is saved.
{t("deviceName.description")}
</DialogDescription>
</DialogHeader>
<div className="gap-4">
<form onSubmit={onSubmit}>
<Label>Long Name</Label>
<Input {...register("longName")} />
<Label>Short Name</Label>
<Input maxLength={4} {...register("shortName")} />
</form>
</div>
<DialogFooter>
<Button onClick={() => onSubmit()}>Save</Button>
</DialogFooter>
<form onSubmit={onSubmit} className="flex flex-col gap-4">
<div>
<Label htmlFor="longName">
{t("deviceName.longName")}
</Label>
<GenericInput
control={control}
field={{
name: "longName",
label: t("deviceName.longName"),
type: "text",
properties: {
className: "text-slate-900 dark:text-slate-200",
fieldLength: {
currentValueLength: getValues("longName").length,
max: 40,
min: 1,
showCharacterCount: true,
},
},
}}
/>
</div>
<div>
<Label htmlFor="shortName">
{t("deviceName.shortName")}
</Label>
<GenericInput
control={control}
field={{
name: "shortName",
label: t("deviceName.shortName"),
type: "text",
properties: {
fieldLength: {
currentValueLength: getValues("shortName").length,
max: 4,
min: 1,
showCharacterCount: true,
},
},
}}
/>
</div>
<DialogFooter>
<Button
type="button"
variant="destructive"
name="reset"
onClick={handleReset}
>
{t("button.reset")}
</Button>
<Button
type="submit"
name="save"
>
{t("button.save")}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);

View File

@@ -1,12 +1,18 @@
import { RemoveNodeDialog } from "@app/components/Dialog/RemoveNodeDialog.js";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.js";
import { ImportDialog } from "@components/Dialog/ImportDialog.js";
import { QRDialog } from "@components/Dialog/QRDialog.js";
import { RebootDialog } from "@components/Dialog/RebootDialog.js";
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { useDevice } from "@core/stores/deviceStore.ts";
import { RemoveNodeDialog } from "@components/Dialog/RemoveNodeDialog.tsx";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx";
import { ImportDialog } from "@components/Dialog/ImportDialog.tsx";
import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog.tsx";
import { QRDialog } from "@components/Dialog/QRDialog.tsx";
import { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx";
import { UnsafeRolesDialog } from "@components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx";
import { RefreshKeysDialog } from "@components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx";
import { RebootOTADialog } from "@components/Dialog/RebootOTADialog.tsx";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
export const DialogManager = (): JSX.Element => {
export const DialogManager = () => {
const { channels, config, dialog, setDialogOpen } = useDevice();
return (
<>
@@ -49,6 +55,42 @@ export const DialogManager = (): JSX.Element => {
setDialogOpen("nodeRemoval", open);
}}
/>
<PkiBackupDialog
open={dialog.pkiBackup}
onOpenChange={(open) => {
setDialogOpen("pkiBackup", open);
}}
/>
<NodeDetailsDialog
open={dialog.nodeDetails}
onOpenChange={(open) => {
setDialogOpen("nodeDetails", open);
}}
/>
<UnsafeRolesDialog
open={dialog.unsafeRoles}
onOpenChange={(open) => {
setDialogOpen("unsafeRoles", open);
}}
/>
<RefreshKeysDialog
open={dialog.refreshKeys}
onOpenChange={(open) => {
setDialogOpen("refreshKeys", open);
}}
/>
<RebootOTADialog
open={dialog.rebootOTA}
onOpenChange={(open) => {
setDialogOpen("rebootOTA", open);
}}
/>
<DeleteMessagesDialog
open={dialog.deleteMessages}
onOpenChange={(open) => {
setDialogOpen("deleteMessages", open);
}}
/>
</>
);
};

View File

@@ -1,20 +1,23 @@
import { Button } from "@components/UI/Button.js";
import { Checkbox } from "@components/UI/Checkbox.js";
import { create, fromBinary } from "@bufbuild/protobuf";
import { Button } from "@components/UI/Button.tsx";
import { Checkbox } from "../UI/Checkbox/index.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.js";
import { Input } from "@components/UI/Input.js";
import { Label } from "@components/UI/Label.js";
import { Switch } from "@components/UI/Switch.js";
import { useDevice } from "@core/stores/deviceStore.js";
import { Protobuf } from "@meshtastic/js";
} from "@components/UI/Dialog.tsx";
import { Input } from "@components/UI/Input.tsx";
import { Label } from "@components/UI/Label.tsx";
import { Switch } from "@components/UI/Switch.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core";
import { toByteArray } from "base64-js";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
export interface ImportDialogProps {
open: boolean;
@@ -25,7 +28,8 @@ export interface ImportDialogProps {
export const ImportDialog = ({
open,
onOpenChange,
}: ImportDialogProps): JSX.Element => {
}: ImportDialogProps) => {
const { t } = useTranslation("dialog");
const [importDialogInput, setImportDialogInput] = useState<string>("");
const [channelSet, setChannelSet] = useState<Protobuf.AppOnly.ChannelSet>();
const [validUrl, setValidUrl] = useState<boolean>(false);
@@ -42,7 +46,7 @@ export const ImportDialog = ({
channelsUrl.pathname !== "/e/") ||
!channelsUrl.hash
) {
throw "Invalid Meshtastic URL";
throw t("import.error.invalidUrl");
}
const encodedChannelConfig = channelsUrl.hash.substring(1);
@@ -55,32 +59,36 @@ export const ImportDialog = ({
.replace(/-/g, "+")
.replace(/_/g, "/");
setChannelSet(
Protobuf.AppOnly.ChannelSet.fromBinary(toByteArray(paddedString)),
fromBinary(
Protobuf.AppOnly.ChannelSetSchema,
toByteArray(paddedString),
),
);
setValidUrl(true);
} catch (error) {
} catch (_error) {
setValidUrl(false);
setChannelSet(undefined);
}
}, [importDialogInput]);
const apply = () => {
channelSet?.settings.map((ch, index) => {
connection?.setChannel(
new Protobuf.Channel.Channel({
index,
role:
index === 0
channelSet?.settings.map(
(ch: Protobuf.Channel.ChannelSettings, index: number) => {
connection?.setChannel(
create(Protobuf.Channel.ChannelSchema, {
index,
role: index === 0
? Protobuf.Channel.Channel_Role.PRIMARY
: Protobuf.Channel.Channel_Role.SECONDARY,
settings: ch,
}),
);
});
settings: ch,
}),
);
},
);
if (channelSet?.loraConfig) {
connection?.setConfig(
new Protobuf.Config.Config({
create(Protobuf.Config.ConfigSchema, {
payloadVariant: {
case: "lora",
value: channelSet.loraConfig,
@@ -93,14 +101,15 @@ export const ImportDialog = ({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogClose />
<DialogHeader>
<DialogTitle>Import Channel Set</DialogTitle>
<DialogTitle>{t("import.title")}</DialogTitle>
<DialogDescription>
The current LoRa configuration will be overridden.
{t("import.description")}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-3">
<Label>Channel Set/QR Code URL</Label>
<Label>{t("import.channelSetUrl")}</Label>
<Input
value={importDialogInput}
suffix={validUrl ? "✅" : "❌"}
@@ -112,30 +121,34 @@ export const ImportDialog = ({
<div className="flex flex-col gap-3">
<div className="flex w-full gap-2">
<div className="w-36">
<Label>Use Preset?</Label>
<Label>{t("import.usePreset")}</Label>
<Switch
disabled={true}
disabled
checked={channelSet?.loraConfig?.usePreset ?? true}
/>
</div>
{/* <Select
{
/* <Select
label="Modem Preset"
disabled
value={channelSet?.loraConfig?.modemPreset}
>
{renderOptions(Protobuf.Config_LoRaConfig_ModemPreset)}
</Select> */}
</Select> */
}
</div>
{/* <Select
{
/* <Select
label="Region"
disabled
value={channelSet?.loraConfig?.region}
>
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */}
</Select> */
}
<span className="text-md block font-medium text-textPrimary">
Channels:
<span className="text-md block font-medium text-text-primary">
{t("import.channels")}
</span>
<div className="flex w-40 flex-col gap-1">
{channelSet?.settings.map((channel) => (
@@ -143,7 +156,7 @@ export const ImportDialog = ({
<Label>
{channel.name.length
? channel.name
: `Channel: ${channel.id}`}
: `${t("import.channelPrefix")}${channel.id}`}
</Label>
<Checkbox key={channel.id} />
</div>
@@ -153,8 +166,8 @@ export const ImportDialog = ({
)}
</div>
<DialogFooter>
<Button onClick={apply} disabled={!validUrl}>
Apply
<Button onClick={apply} disabled={!validUrl} name="apply">
{t("button.apply")}
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -0,0 +1,93 @@
import { useDevice } from "@core/stores/deviceStore.ts";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../UI/Dialog.tsx";
import type { Protobuf, Types } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { useTranslation } from "react-i18next";
export interface LocationResponseDialogProps {
location: Types.PacketMetadata<Protobuf.Mesh.Position> | undefined;
open: boolean;
onOpenChange: () => void;
}
export const LocationResponseDialog = ({
location,
open,
onOpenChange,
}: LocationResponseDialogProps) => {
const { t } = useTranslation("dialog");
const { getNode } = useDevice();
const from = getNode(location?.from ?? 0);
const longName = from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : t("unknown.shortName"));
const shortName = from?.user?.shortName ??
(from
? `${numberToHexUnpadded(from?.num).substring(0, 4)}`
: t("unknown.shortName"));
const position = location?.data;
const hasCoordinates = position &&
typeof position.latitudeI === "number" &&
typeof position.longitudeI === "number" &&
typeof position.altitude === "number";
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogClose />
<DialogHeader>
<DialogTitle>
{t("locationResponse.title", {
identifier: `${longName} (${shortName})`,
})}
</DialogTitle>
</DialogHeader>
<DialogDescription>
{hasCoordinates
? (
<div className="ml-5 flex">
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
<p>
{t("locationResponse.coordinates")}
<a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${
position.latitudeI ?? 0 / 1e7
}&mlon=${position.longitudeI ?? 0 / 1e7}&layers=N`}
target="_blank"
rel="noreferrer"
>
{" "}
{position.latitudeI ?? 0 / 1e7},{" "}
{position.longitudeI ?? 0 / 1e7}
</a>
</p>
<p>
{t("locationResponse.altitude")} {position.altitude}
{(position.altitude ?? 0) < 1
? t("unit.meter.one")
: t("unit.meter.plural")}
</p>
</span>
</div>
)
: (
// Optional: Show a message if coordinates are not available
<p className="text-textPrimary">
{t("locationResponse.noCoordinates")}
</p>
)}
</DialogDescription>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,72 @@
import { Button } from "@components/UI/Button.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { Trans, useTranslation } from "react-i18next";
import { Checkbox } from "@components/UI/Checkbox/index.tsx";
import { useState } from "react";
export interface ManagedModeDialogProps {
open: boolean;
onOpenChange: () => void;
onSubmit: () => void;
}
export const ManagedModeDialog = ({
open,
onOpenChange,
onSubmit,
}: ManagedModeDialogProps) => {
const { t } = useTranslation("dialog");
const [confirmState, setConfirmState] = useState(false);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogClose />
<DialogHeader>
<DialogTitle>{t("managedMode.title")}</DialogTitle>
<DialogDescription>
<Trans
i18nKey="managedMode.description"
components={{
"bold": <p className="font-bold inline" />,
}}
/>
</DialogDescription>
</DialogHeader>
<div className="flex items-center gap-2">
<Checkbox
id="managedMode"
checked={confirmState}
onChange={() => setConfirmState(!confirmState)}
name="confirmUnderstanding"
>
<p className="dark:text-white pt-1">
{t("managedMode.confirmUnderstanding")}
</p>
</Checkbox>
</div>
<DialogFooter>
<Button
variant="destructive"
name="regenerate"
disabled={!confirmState}
onClick={() => {
setConfirmState(false);
onSubmit();
}}
>
{t("button.confirm")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,115 +1,187 @@
import { BLE } from "@components/PageComponents/Connect/BLE.js";
import { HTTP } from "@components/PageComponents/Connect/HTTP.js";
import { Serial } from "@components/PageComponents/Connect/Serial.js";
import {
type BrowserFeature,
useBrowserFeatureDetection,
} from "@core/hooks/useBrowserFeatureDetection.ts";
import { BLE } from "@components/PageComponents/Connect/BLE.tsx";
import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx";
import { Serial } from "@components/PageComponents/Connect/Serial.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.js";
} from "@components/UI/Dialog.tsx";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@components/UI/Tabs.js";
import { Link } from "@components/UI/Typography/Link.js";
import { Subtle } from "@components/UI/Typography/Subtle.js";
} from "@components/UI/Tabs.tsx";
import { AlertCircle } from "lucide-react";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "../UI/Typography/Link.tsx";
export interface TabElementProps {
closeDialog: () => void;
}
export interface TabManifest {
id: "HTTP" | "BLE" | "Serial";
label: string;
element: React.FC<TabElementProps>;
disabled: boolean;
disabledMessage: string;
disabledLink?: string;
isDisabled: boolean;
}
const tabs: TabManifest[] = [
{
label: "HTTP",
element: HTTP,
disabled: false,
disabledMessage: "Unsuported connection method",
},
{
label: "Bluetooth",
element: BLE,
disabled: !navigator.bluetooth,
disabledMessage:
"Web Bluetooth is currently only supported by Chromium-based browsers",
disabledLink:
"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility",
},
{
label: "Serial",
element: Serial,
disabled: !navigator.serial,
disabledMessage:
"WebSerial is currently only supported by Chromium based browsers: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility",
},
];
export interface NewDeviceProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
interface FeatureErrorProps {
missingFeatures: BrowserFeature[];
tabId: "HTTP" | "BLE" | "Serial";
}
const errors: Record<BrowserFeature, { href: string; i18nKey: string }> = {
"Web Bluetooth": {
href:
"https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility",
i18nKey: "newDeviceDialog.validation.requiresWebBluetooth",
},
"Web Serial": {
href:
"https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility",
i18nKey: "newDeviceDialog.validation.requiresWebSerial",
},
"Secure Context": {
href:
"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts",
i18nKey: "newDeviceDialog.validation.requiresSecureContext",
},
};
const ErrorMessage = ({ missingFeatures, tabId }: FeatureErrorProps) => {
if (missingFeatures.length === 0) return null;
const browserFeatures = missingFeatures.filter(
(feature) => feature !== "Secure Context",
);
const needsSecureContext = missingFeatures.includes("Secure Context");
const needsFeature =
(tabId === "BLE" && browserFeatures.includes("Web Bluetooth"))
? "Web Bluetooth"
: (tabId === "Serial" && browserFeatures.includes("Web Serial"))
? "Web Serial"
: undefined;
return (
<div className="flex flex-col items-start gap-2 bg-red-500 p-4 rounded-md text-sm text-slate-500 dark:text-slate-400">
<div className="flex items-center gap-2 w-full">
<AlertCircle size={40} className="mr-2 shrink-0 text-white" />
<div className="flex flex-col gap-3">
<div className="text-sm text-white">
{needsFeature && (
<Trans
i18nKey={errors[needsFeature].i18nKey}
components={[
<Link
key="0"
href={errors[needsFeature].href}
className="underline hover:text-slate-200 text-white dark:text-white dark:hover:text-slate-300"
/>,
]}
/>
)}
{needsFeature && needsSecureContext && " "}
{needsSecureContext && (
<Trans
i18nKey={browserFeatures.length > 0
? "newDeviceDialog.validation.additionallyRequiresSecureContext"
: "newDeviceDialog.validation.requiresSecureContext"}
components={{
"0": (
<Link
href={errors["Secure Context"].href}
className="underline hover:text-slate-200"
/>
),
}}
/>
)}
</div>
</div>
</div>
</div>
);
};
export const NewDeviceDialog = ({
open,
onOpenChange,
}: NewDeviceProps): JSX.Element => {
}: NewDeviceProps) => {
const { t } = useTranslation("dialog");
const { unsupported } = useBrowserFeatureDetection();
const tabs: TabManifest[] = [
{
id: "HTTP",
label: t("newDeviceDialog.tabHttp"),
element: HTTP,
isDisabled: false,
},
{
id: "BLE",
label: t("newDeviceDialog.tabBluetooth"),
element: BLE,
isDisabled: unsupported.includes("Web Bluetooth") ||
unsupported.includes("Secure Context"),
},
{
id: "Serial",
label: t("newDeviceDialog.tabSerial"),
element: Serial,
isDisabled: unsupported.includes("Web Serial") ||
unsupported.includes("Secure Context"),
},
];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogContent aria-describedby={undefined}>
<DialogClose />
<DialogHeader>
<DialogTitle>Connect New Device</DialogTitle>
<DialogTitle>{t("newDeviceDialog.title")}</DialogTitle>
</DialogHeader>
<Tabs defaultValue="HTTP">
<TabsList>
{tabs.map((tab) => (
<TabsTrigger
key={tab.label}
value={tab.label}
disabled={tab.disabled}
>
<TabsTrigger key={tab.id} value={tab.id}>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent key={tab.label} value={tab.label}>
{tab.disabled ? (
<p className="text-sm text-slate-500 dark:text-slate-400">
{tab.disabledMessage}
</p>
) : (
<tab.element closeDialog={() => onOpenChange(false)} />
)}
<TabsContent key={tab.id} value={tab.id}>
<fieldset disabled={tab.isDisabled}>
{(tab.id !== "HTTP" &&
tab.isDisabled)
? (
<ErrorMessage
missingFeatures={unsupported}
tabId={tab.id}
/>
)
: (
<tab.element
closeDialog={() => onOpenChange(false)}
/>
)}
</fieldset>
</TabsContent>
))}
</Tabs>
{(!navigator.bluetooth || !navigator.serial) && (
<>
<Subtle>
Web Bluetooth and Web Serial are currently only supported by
Chromium-based browsers.
</Subtle>
<Subtle>
Read more:&nbsp;
<Link href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility">
Web Bluetooth
</Link>
&nbsp;
<Link href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#browser_compatibility">
Web Serial
</Link>
</Subtle>
</>
)}
</DialogContent>
</Dialog>
);

View File

@@ -0,0 +1,379 @@
import { useEffect, useState } from "react";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { DeviceImage } from "@components/generic/DeviceImage.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
import { Uptime } from "@components/generic/Uptime.tsx";
import { toast } from "@core/hooks/useToast.ts";
import { useFavoriteNode } from "@core/hooks/useFavoriteNode.ts";
import { useIgnoreNode } from "@core/hooks/useIgnoreNode.ts";
import { cn } from "@core/utils/cn.ts";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@components/UI/Accordion.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { Button } from "@components/UI/Button.tsx";
import {
BellIcon,
BellOffIcon,
MapPinnedIcon,
MessageSquareIcon,
StarIcon,
TrashIcon,
WaypointsIcon,
} from "lucide-react";
import {
Tooltip,
TooltipArrow,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@components/UI/Tooltip.tsx";
import { Separator } from "@components/UI/Seperator.tsx";
import { useTranslation } from "react-i18next";
import { useNavigate } from "@tanstack/react-router";
export interface NodeDetailsDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const NodeDetailsDialog = ({
open,
onOpenChange,
}: NodeDetailsDialogProps) => {
const { t } = useTranslation("dialog");
const { setDialogOpen, connection, getNode } = useDevice();
const navigate = useNavigate();
const { setNodeNumToBeRemoved, nodeNumDetails } = useAppStore();
const { updateFavorite } = useFavoriteNode();
const { updateIgnored } = useIgnoreNode();
const node = getNode(nodeNumDetails);
const [isFavoriteState, setIsFavoriteState] = useState<boolean>(
node?.isFavorite ?? false,
);
const [isIgnoredState, setIsIgnoredState] = useState<boolean>(
node?.isIgnored ?? false,
);
useEffect(() => {
if (!node) return;
setIsFavoriteState(node?.isFavorite);
setIsIgnoredState(node?.isIgnored);
}, [node]);
if (!node) return;
function handleDirectMessage() {
if (!node) return;
navigate({ to: `/messages/direct/${node.num}` });
setDialogOpen("nodeDetails", false);
}
function handleRequestPosition() {
if (!node) return;
toast({
title: t("toast.requestingPosition.title", { ns: "ui" }),
});
connection?.requestPosition(node.num).then(() =>
toast({
title: t("toast.positionRequestSent.title", { ns: "ui" }),
})
);
onOpenChange(false);
}
function handleTraceroute() {
if (!node) return;
toast({
title: t("toast.sendingTraceroute.title", { ns: "ui" }),
});
connection?.traceRoute(node.num).then(() =>
toast({
title: t("toast.tracerouteSent.title", { ns: "ui" }),
})
);
onOpenChange(false);
}
function handleNodeRemove() {
if (!node) return;
setNodeNumToBeRemoved(node?.num);
setDialogOpen("nodeRemoval", true);
onOpenChange(false);
}
function handleToggleFavorite() {
if (!node) return;
updateFavorite({ nodeNum: node.num, isFavorite: !isFavoriteState });
setIsFavoriteState(!isFavoriteState);
}
function handleToggleIgnored() {
if (!node) return;
updateIgnored({ nodeNum: node.num, isIgnored: !isIgnoredState });
setIsIgnoredState(!isIgnoredState);
}
const deviceMetricsMap = [
{
key: "airUtilTx",
label: t("nodeDetails.airTxUtilization"),
value: node.deviceMetrics?.airUtilTx,
format: (val: number) => `${val.toFixed(2)}%`,
},
{
key: "channelUtilization",
label: t("nodeDetails.channelUtilization"),
value: node.deviceMetrics?.channelUtilization,
format: (val: number) => `${val.toFixed(2)}%`,
},
{
key: "batteryLevel",
label: t("nodeDetails.batteryLevel"),
value: node.deviceMetrics?.batteryLevel,
format: (val: number) => `${val.toFixed(2)}%`,
},
{
key: "voltage",
label: t("nodeDetails.voltage"),
value: node.deviceMetrics?.voltage,
format: (val: number) => `${val.toFixed(2)}V`,
},
];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent aria-describedby={undefined}>
<DialogClose />
<DialogHeader>
<DialogTitle>
{t("nodeDetails.title", {
identifier: `${node.user?.longName ?? t("unknown.shortName")} (${
node.user?.shortName ?? t("unknown.shortName")
})`,
})}
</DialogTitle>
</DialogHeader>
<DialogFooter>
<div className="w-full">
<div className="flex flex-row flex-wrap space-y-1">
<Button
className="mr-1"
name="message"
onClick={handleDirectMessage}
>
<MessageSquareIcon className="mr-2" />
{t("nodeDetails.message")}
</Button>
<Button
className="mr-1"
name="traceRoute"
onClick={handleTraceroute}
>
<WaypointsIcon className="mr-2" />
{t("nodeDetails.traceRoute")}
</Button>
<Button className="mr-1" onClick={handleToggleFavorite}>
<StarIcon
className={cn(
isFavoriteState ? " fill-yellow-400 stroke-yellow-400" : "",
)}
/>
</Button>
<div className="flex flex-1 justify-start"></div>
<TooltipProvider delayDuration={300}>
<Tooltip>
<TooltipTrigger asChild>
<Button
className={cn(
"flex justify-end mr-1 text-white",
isIgnoredState
? "bg-red-500 dark:bg-red-500 hover:bg-red-600 hover:dark:bg-red-600 text-white dark:text-white"
: "",
)}
onClick={handleToggleIgnored}
>
{isIgnoredState ? <BellIcon /> : <BellOffIcon />}
</Button>
</TooltipTrigger>
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
{isIgnoredState
? t("nodeDetails.unignoreNode")
: t("nodeDetails.ignoreNode")}
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider delayDuration={300}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="destructive"
className="flex justify-end"
onClick={handleNodeRemove}
>
<TrashIcon />
</Button>
</TooltipTrigger>
<TooltipContent className="bg-slate-800 dark:bg-slate-600 text-white px-4 py-1 rounded text-xs">
{t("nodeDetails.removeNode")}
<TooltipArrow className="fill-slate-800 dark:fill-slate-600" />
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Separator className="mt-5 mb-2" />
<div className="flex flex-col flex-wrap space-x-1 space-y-1">
<div className="flex flex-row space-x-2">
<div className="w-full bg-slate-100 text-slate-900 dark:text-slate-100 dark:bg-slate-800 p-3 rounded-lg">
<p className="text-lg font-semibold">
{t("nodeDetails.details")}
</p>
<p>{t("nodeDetails.nodeNumber")}{node.num}</p>
<p>
{t("nodeDetails.nodeHexPrefix")}
{numberToHexUnpadded(node.num)}
</p>
<p>
{t("nodeDetails.role")}
{Protobuf.Config.Config_DeviceConfig_Role[
node.user?.role ?? 0
].replace(/_/g, " ")}
</p>
<p>
{t("nodeDetails.lastHeard")}
{node.lastHeard === 0
? t("nodesTable.lastHeardStatus.never", { ns: "nodes" })
: <TimeAgo timestamp={node.lastHeard * 1000} />}
</p>
<p>
{t("nodeDetails.hardware")}
{(Protobuf.Mesh.HardwareModel[node.user?.hwModel ?? 0] ??
t("unknown.shortName"))
.replace(/_/g, " ")}
</p>
</div>
<DeviceImage
className="h-45 w-45 p-2 rounded-lg border-4 border-slate-200 dark:border-slate-800"
deviceType={Protobuf.Mesh
.HardwareModel[node.user?.hwModel ?? 0]}
/>
</div>
</div>
<div>
<div className="text-slate-900 dark:text-slate-100 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold">
{t("nodeDetails.position")}
</p>
{node.position
? (
<>
{node.position.latitudeI &&
node.position.longitudeI && (
<p>
{t("locationResponse.coordinates")}
<a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${
node.position.latitudeI / 1e7
}&mlon=${node.position.longitudeI / 1e7}&layers=N`}
target="_blank"
rel="noreferrer"
>
{node.position.latitudeI / 1e7},{" "}
{node.position.longitudeI / 1e7}
</a>
</p>
)}
{node.position.altitude && (
<p>
{t("locationResponse.altitude")}
{node.position.altitude}
{t("unit.meter.one")}
</p>
)}
</>
)
: <p>{t("unknown.shortName")}</p>}
<Button
onClick={handleRequestPosition}
name="requestPosition"
className="mt-2"
>
<MapPinnedIcon className="mr-2" />
{t("nodeDetails.requestPosition")}
</Button>
</div>
{node.deviceMetrics && (
<div className="text-slate-900 dark:text-slate-100 bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold text-slate-900 dark:text-slate-100">
{t("nodeDetails.deviceMetrics")}
</p>
{deviceMetricsMap.map(
(metric) =>
metric.value !== undefined && (
<p key={metric.key}>
{metric.label}: {metric.format(metric.value)}
</p>
),
)}
{node.deviceMetrics.uptimeSeconds && (
<p>
{t("nodeDetails.uptime")}
<Uptime seconds={node.deviceMetrics.uptimeSeconds} />
</p>
)}
</div>
)}
</div>
<div className="text-slate-900 dark:text-slate-100 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<Accordion className="AccordionRoot" type="single" collapsible>
<AccordionItem className="AccordionItem" value="item-1">
<AccordionTrigger>
<p className="text-lg font-semibold text-slate-900 dark:text-slate-50">
{t("nodeDetails.allRawMetrics")}
</p>
</AccordionTrigger>
<AccordionContent className="overflow-x-scroll">
<pre className="text-xs w-full">
{JSON.stringify(node, null, 2)}
</pre>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,151 @@
import { useDevice } from "../../core/stores/deviceStore.ts";
import { Button } from "../UI/Button.tsx";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { fromByteArray } from "base64-js";
import { DownloadIcon, PrinterIcon } from "lucide-react";
import React from "react";
import { useTranslation } from "react-i18next";
export interface PkiBackupDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const PkiBackupDialog = ({
open,
onOpenChange,
}: PkiBackupDialogProps) => {
const { t } = useTranslation("dialog");
const { config, setDialogOpen, getMyNode } = useDevice();
const privateKey = config.security?.privateKey;
const publicKey = config.security?.publicKey;
const decodeKeyData = React.useCallback(
(key: Uint8Array<ArrayBufferLike>) => {
if (!key) return "";
return fromByteArray(key ?? new Uint8Array(0));
},
[],
);
const closeDialog = React.useCallback(() => {
setDialogOpen("pkiBackup", false);
}, [setDialogOpen]);
const renderPrintWindow = React.useCallback(() => {
if (!privateKey || !publicKey) return;
const printWindow = globalThis.open("", "_blank");
if (printWindow) {
printWindow.document.write(`
<html>
<head>
<title>${
t("pkiBackup.header", {
shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"),
longName: getMyNode()?.user?.longName ?? t("unknown.longName"),
})
}</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
h1 { font-size: 18px; }
p { font-size: 14px; word-break: break-all; }
</style>
</head>
<body>
<h1>${
t("pkiBackup.header", {
shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"),
longName: getMyNode()?.user?.longName ?? t("unknown.longName"),
})
}</h1>
<h3>${t("pkiBackup.secureBackup")}</h3>
<h3>${t("pkiBackup.publicKey")}</h3>
<p>${decodeKeyData(publicKey)}</p>
<h3>${t("pkiBackup.privateKey")}</h3>
<p>${decodeKeyData(privateKey)}</p>
<p>${t("pkiBackup.footer")}</p>
</body>
</html>
`);
printWindow.document.close();
printWindow.print();
closeDialog();
}
}, [decodeKeyData, privateKey, publicKey, closeDialog, t]);
const createDownloadKeyFile = React.useCallback(() => {
if (!privateKey || !publicKey) return;
const decodedPrivateKey = decodeKeyData(privateKey);
const decodedPublicKey = decodeKeyData(publicKey);
const formattedContent = [
`${t("pkiBackup.header")}\n\n`,
`${t("pkiBackup.privateKey")}\n`,
decodedPrivateKey,
`\n\n${t("pkiBackup.publicKey")}\n`,
decodedPublicKey,
`\n\n${t("pkiBackup.footer")}`,
].join("");
const blob = new Blob([formattedContent], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = t("pkiBackup.fileName", {
shortName: getMyNode()?.user?.shortName ?? t("unknown.shortName"),
longName: getMyNode()?.user?.longName ?? t("unknown.longName"),
});
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
closeDialog();
URL.revokeObjectURL(url);
}, [decodeKeyData, privateKey, publicKey, closeDialog, t]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogClose />
<DialogHeader>
<DialogTitle>{t("pkiBackup.title")}</DialogTitle>
<DialogDescription>
{t("pkiBackup.secureBackup")}
</DialogDescription>
<DialogDescription>
<span className="font-bold break-before-auto">
{t("pkiBackup.loseKeysWarning")}
</span>
</DialogDescription>
</DialogHeader>
<DialogFooter className="mt-6">
<Button
variant="default"
name="download"
onClick={() => createDownloadKeyFile()}
className=""
>
<DownloadIcon size={20} className="mr-2" />
{t("button.download")}
</Button>
<Button variant="default" onClick={() => renderPrintWindow()}>
<PrinterIcon size={20} className="mr-2" />
{t("button.print")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

Some files were not shown because too many files have changed in this diff Show More