mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-10-15 02:29:55 +00:00
2526 lines
77 KiB
HTML
2526 lines
77 KiB
HTML
|
||
<!doctype html>
|
||
<html lang="en" class="no-js">
|
||
<head>
|
||
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
|
||
<meta name="description" content="Documentation for the MeshCore bot and its services">
|
||
|
||
|
||
|
||
<link rel="canonical" href="https://agessaman.github.io/meshcore-bot/web-viewer/">
|
||
|
||
|
||
<link rel="prev" href="../region-warnings/">
|
||
|
||
|
||
<link rel="next" href="../command-reference/">
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="icon" href="../assets/images/favicon.png">
|
||
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
|
||
|
||
|
||
|
||
<title>Web Viewer - Meshcore Bot Documentation</title>
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="../assets/stylesheets/main.ec1eaa64.min.css">
|
||
|
||
|
||
<link rel="stylesheet" href="../assets/stylesheets/palette.ab4e12ef.min.css">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
|
||
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
|
||
|
||
|
||
|
||
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
|
||
|
||
|
||
|
||
|
||
|
||
</head>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="cyan">
|
||
|
||
|
||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||
<label class="md-overlay" for="__drawer"></label>
|
||
<div data-md-component="skip">
|
||
|
||
|
||
<a href="#meshcore-bot-data-viewer" class="md-skip">
|
||
Skip to content
|
||
</a>
|
||
|
||
</div>
|
||
<div data-md-component="announce">
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<header class="md-header" data-md-component="header">
|
||
<nav class="md-header__inner md-grid" aria-label="Header">
|
||
<a href=".." title="Meshcore Bot Documentation" class="md-header__button md-logo" aria-label="Meshcore Bot Documentation" data-md-component="logo">
|
||
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
|
||
|
||
</a>
|
||
<label class="md-header__button md-icon" for="__drawer">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
|
||
</label>
|
||
<div class="md-header__title" data-md-component="header-title">
|
||
<div class="md-header__ellipsis">
|
||
<div class="md-header__topic">
|
||
<span class="md-ellipsis">
|
||
Meshcore Bot Documentation
|
||
</span>
|
||
</div>
|
||
<div class="md-header__topic" data-md-component="header-topic">
|
||
<span class="md-ellipsis">
|
||
|
||
Web Viewer
|
||
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<form class="md-header__option" data-md-component="palette">
|
||
|
||
|
||
|
||
|
||
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="cyan" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_0">
|
||
|
||
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_1" hidden>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
|
||
</label>
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="indigo" data-md-color-accent="cyan" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_1">
|
||
|
||
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_0" hidden>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
|
||
</label>
|
||
|
||
|
||
</form>
|
||
|
||
|
||
|
||
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-header__button md-icon" for="__search">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
|
||
</label>
|
||
<div class="md-search" data-md-component="search" role="dialog">
|
||
<label class="md-search__overlay" for="__search"></label>
|
||
<div class="md-search__inner" role="search">
|
||
<form class="md-search__form" name="search">
|
||
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
|
||
<label class="md-search__icon md-icon" for="__search">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
|
||
</label>
|
||
<nav class="md-search__options" aria-label="Search">
|
||
|
||
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||
</button>
|
||
</nav>
|
||
|
||
</form>
|
||
<div class="md-search__output">
|
||
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
|
||
<div class="md-search-result" data-md-component="search-result">
|
||
<div class="md-search-result__meta">
|
||
Initializing search
|
||
</div>
|
||
<ol class="md-search-result__list" role="presentation"></ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="md-header__source">
|
||
<a href="https://github.com/agessaman/meshcore-bot" title="Go to repository" class="md-source" data-md-component="source">
|
||
<div class="md-source__icon md-icon">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
|
||
</div>
|
||
<div class="md-source__repository">
|
||
meshcore-bot
|
||
</div>
|
||
</a>
|
||
</div>
|
||
|
||
</nav>
|
||
|
||
</header>
|
||
|
||
<div class="md-container" data-md-component="container">
|
||
|
||
|
||
|
||
|
||
|
||
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
|
||
<div class="md-grid">
|
||
<ul class="md-tabs__list">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href=".." class="md-tabs__link">
|
||
|
||
|
||
|
||
|
||
|
||
Home
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../getting-started/" class="md-tabs__link">
|
||
|
||
|
||
|
||
|
||
|
||
Quick Start
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../installation/" class="md-tabs__link">
|
||
|
||
|
||
|
||
Installation
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../configuration/" class="md-tabs__link">
|
||
|
||
|
||
|
||
Configuration
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item md-tabs__item--active">
|
||
<a href="./" class="md-tabs__link">
|
||
|
||
|
||
|
||
|
||
|
||
Web Viewer
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../command-reference/" class="md-tabs__link">
|
||
|
||
|
||
|
||
Command Reference
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../service-plugins/" class="md-tabs__link">
|
||
|
||
|
||
|
||
Service Plugins
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../faq/" class="md-tabs__link">
|
||
|
||
|
||
|
||
|
||
|
||
FAQ
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../upgrade/" class="md-tabs__link">
|
||
|
||
|
||
|
||
|
||
|
||
Upgrade
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-tabs__item">
|
||
<a href="../solar-conditions-provenance/" class="md-tabs__link">
|
||
|
||
|
||
|
||
Project
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
|
||
|
||
|
||
<main class="md-main" data-md-component="main">
|
||
<div class="md-main__inner md-grid">
|
||
|
||
|
||
|
||
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
|
||
<div class="md-sidebar__scrollwrap">
|
||
<div class="md-sidebar__inner">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<nav class="md-nav md-nav--primary md-nav--lifted md-nav--integrated" aria-label="Navigation" data-md-level="0">
|
||
<label class="md-nav__title" for="__drawer">
|
||
<a href=".." title="Meshcore Bot Documentation" class="md-nav__button md-logo" aria-label="Meshcore Bot Documentation" data-md-component="logo">
|
||
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
|
||
|
||
</a>
|
||
Meshcore Bot Documentation
|
||
</label>
|
||
|
||
<div class="md-nav__source">
|
||
<a href="https://github.com/agessaman/meshcore-bot" title="Go to repository" class="md-source" data-md-component="source">
|
||
<div class="md-source__icon md-icon">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
|
||
</div>
|
||
<div class="md-source__repository">
|
||
meshcore-bot
|
||
</div>
|
||
</a>
|
||
</div>
|
||
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href=".." class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Home
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../getting-started/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Quick Start
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" >
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Installation
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_3">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Installation
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../installation/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Overview
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../docker/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Docker
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../service-installation/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Service
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" >
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Configuration
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_4">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Configuration
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../configuration/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Overview
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../data-retention/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Data retention
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../local-plugins/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Local plugins and services
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../checkin-api/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Check-in API
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../path-command-config/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Path Command
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../config-validation/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Config validation
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../region-warnings/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Region warnings
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--active">
|
||
|
||
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__link md-nav__link--active" for="__toc">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Web Viewer
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<a href="./" class="md-nav__link md-nav__link--active">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Web Viewer
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
|
||
|
||
|
||
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__title" for="__toc">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
Table of contents
|
||
</label>
|
||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#features" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Features
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#quick-start" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Quick Start
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Quick Start">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#option-1-standalone-mode" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Option 1: Standalone Mode
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#option-2-integrated-with-bot" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Option 2: Integrated with Bot
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#configuration" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Configuration
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#accessing-the-viewer" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Accessing the Viewer
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#reverse-proxy-with-nginx-basic-auth" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Reverse Proxy With Nginx Basic Auth
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#pages-overview" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Pages Overview
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Pages Overview">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#dashboard" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Dashboard
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#repeater-contacts" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Repeater Contacts
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#contact-tracking" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Contact Tracking
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#config" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Config
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#radio" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Radio
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#purging-log" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Purging Log
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#api-endpoints" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
API Endpoints
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#database-requirements" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Database Requirements
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#migrating-from-a-separate-web-viewer-database" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Migrating from a separate web viewer database
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#troubleshooting" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Troubleshooting
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Troubleshooting">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#web-viewer-not-accessible-eg-orange-pi-sbc" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Web viewer not accessible (e.g. Orange Pi / SBC)
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#flask-not-found" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Flask Not Found
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#database-not-found" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Database Not Found
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#port-already-in-use" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Port Already in Use
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#permission-denied" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Permission Denied
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#security-notes" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Security Notes
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#future-enhancements" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Future Enhancements
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_6" >
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Command Reference
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_6_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_6">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Command Reference
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../command-reference/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Overview
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../repeater-commands/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Repeater Commands
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../command-reference-website/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Custom Command Website
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../develop-command-scripts/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Developing Commands
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_7" >
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_7" id="__nav_7_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Service Plugins
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_7_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_7">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Service Plugins
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../service-plugins/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Overview
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../discord-bridge/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Discord Bridge
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../telegram-bridge/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Telegram Bridge
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../packet-capture/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Packet Capture
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../map-uploader/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Map Uploader
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../weather-service/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Weather Service
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../earthquake-service/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Earthquake Service
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../repeater-prefix-collision-service/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Repeater Prefix Collision
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../worldcup/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
World Cup
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../FEEDS/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Feed Management
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../faq/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
FAQ
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../upgrade/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Upgrade
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_10" >
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_10" id="__nav_10_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Project
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_10_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_10">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Project
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../solar-conditions-provenance/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Solar conditions provenance
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<div class="md-content" data-md-component="content">
|
||
|
||
<article class="md-content__inner md-typeset">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<h1 id="meshcore-bot-data-viewer">MeshCore Bot Data Viewer</h1>
|
||
<p>A web-based interface for viewing and analyzing data from your MeshCore Bot.</p>
|
||
<h2 id="features">Features</h2>
|
||
<ul>
|
||
<li><strong>Dashboard</strong>: Overview of database statistics and bot status</li>
|
||
<li><strong>Repeater Contacts</strong>: View active repeater contacts with location and status information</li>
|
||
<li><strong>Contact Tracking</strong>: Complete history of all heard contacts with signal strength and routing data</li>
|
||
<li><strong>Config panel</strong>: Structured settings with categorized topics and database tools</li>
|
||
<li><strong>Plugins page</strong>: Toggle every command and service on/off and edit their settings
|
||
from the browser. Changes are validated, written to <code>config.ini</code> with comments
|
||
preserved and a timestamped backup, and the bot hot-reloads command settings
|
||
within a few seconds (service on/off still needs a restart)</li>
|
||
<li><strong>Purging Log</strong>: Audit trail of contact purging operations</li>
|
||
<li><strong>Real-time Updates</strong>: Auto-refreshes every 30 seconds</li>
|
||
<li><strong>API Endpoints</strong>: JSON API for programmatic access</li>
|
||
</ul>
|
||
<h2 id="quick-start">Quick Start</h2>
|
||
<h3 id="option-1-standalone-mode">Option 1: Standalone Mode</h3>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1"># Install Flask if not already installed</span>
|
||
<a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>pip3<span class="w"> </span>install<span class="w"> </span>flask
|
||
<a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>
|
||
<a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="c1"># Start the web viewer (reads config from config.ini)</span>
|
||
<a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>python3<span class="w"> </span>-m<span class="w"> </span>modules.web_viewer.app
|
||
<a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a>
|
||
<a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a><span class="c1"># Or use the restart script for standalone mode</span>
|
||
<a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a>./restart_viewer.sh
|
||
<a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a>
|
||
<a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a><span class="c1"># Override configuration with command line arguments</span>
|
||
<a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a>python3<span class="w"> </span>-m<span class="w"> </span>modules.web_viewer.app<span class="w"> </span>--port<span class="w"> </span><span class="m">8080</span><span class="w"> </span>--host<span class="w"> </span><span class="m">0</span>.0.0.0
|
||
</code></pre></div>
|
||
<h3 id="option-2-integrated-with-bot">Option 2: Integrated with Bot</h3>
|
||
<ol>
|
||
<li>
|
||
<p>Edit <code>config.ini</code> and set:
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="k">[Web_Viewer]</span>
|
||
<a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a><span class="na">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
|
||
<a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a><span class="na">auto_start</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
|
||
<a id="__codelineno-1-4" name="__codelineno-1-4" href="#__codelineno-1-4"></a><span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">127.0.0.1</span>
|
||
<a id="__codelineno-1-5" name="__codelineno-1-5" href="#__codelineno-1-5"></a><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5000</span>
|
||
</code></pre></div></p>
|
||
</li>
|
||
<li>
|
||
<p>The web viewer will start automatically with the bot</p>
|
||
</li>
|
||
</ol>
|
||
<h2 id="configuration">Configuration</h2>
|
||
<p>The web viewer can be configured in the <code>[Web_Viewer]</code> section of <code>config.ini</code>:</p>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a><span class="k">[Web_Viewer]</span>
|
||
<a id="__codelineno-2-2" name="__codelineno-2-2" href="#__codelineno-2-2"></a><span class="c1"># Enable or disable the web data viewer</span>
|
||
<a id="__codelineno-2-3" name="__codelineno-2-3" href="#__codelineno-2-3"></a><span class="na">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
|
||
<a id="__codelineno-2-4" name="__codelineno-2-4" href="#__codelineno-2-4"></a>
|
||
<a id="__codelineno-2-5" name="__codelineno-2-5" href="#__codelineno-2-5"></a><span class="c1"># Web viewer host address</span>
|
||
<a id="__codelineno-2-6" name="__codelineno-2-6" href="#__codelineno-2-6"></a><span class="c1"># 127.0.0.1: Only accessible from localhost</span>
|
||
<a id="__codelineno-2-7" name="__codelineno-2-7" href="#__codelineno-2-7"></a><span class="c1"># 0.0.0.0: Accessible from any network interface</span>
|
||
<a id="__codelineno-2-8" name="__codelineno-2-8" href="#__codelineno-2-8"></a><span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">127.0.0.1</span>
|
||
<a id="__codelineno-2-9" name="__codelineno-2-9" href="#__codelineno-2-9"></a>
|
||
<a id="__codelineno-2-10" name="__codelineno-2-10" href="#__codelineno-2-10"></a><span class="c1"># Web viewer port</span>
|
||
<a id="__codelineno-2-11" name="__codelineno-2-11" href="#__codelineno-2-11"></a><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5000</span>
|
||
<a id="__codelineno-2-12" name="__codelineno-2-12" href="#__codelineno-2-12"></a>
|
||
<a id="__codelineno-2-13" name="__codelineno-2-13" href="#__codelineno-2-13"></a><span class="c1"># Enable debug mode for the web viewer</span>
|
||
<a id="__codelineno-2-14" name="__codelineno-2-14" href="#__codelineno-2-14"></a><span class="na">debug</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">false</span>
|
||
<a id="__codelineno-2-15" name="__codelineno-2-15" href="#__codelineno-2-15"></a>
|
||
<a id="__codelineno-2-16" name="__codelineno-2-16" href="#__codelineno-2-16"></a><span class="c1"># Auto-start web viewer with bot</span>
|
||
<a id="__codelineno-2-17" name="__codelineno-2-17" href="#__codelineno-2-17"></a><span class="na">auto_start</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">false</span>
|
||
<a id="__codelineno-2-18" name="__codelineno-2-18" href="#__codelineno-2-18"></a>
|
||
<a id="__codelineno-2-19" name="__codelineno-2-19" href="#__codelineno-2-19"></a><span class="c1"># Optional: enable the multibyte monitor page and API</span>
|
||
<a id="__codelineno-2-20" name="__codelineno-2-20" href="#__codelineno-2-20"></a><span class="na">multibyte_monitor_enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">false</span>
|
||
</code></pre></div>
|
||
<h2 id="accessing-the-viewer">Accessing the Viewer</h2>
|
||
<p>Once started, open your web browser and navigate to:
|
||
- <strong>Local access</strong>: http://localhost:5005 (or your configured port)
|
||
- <strong>Network access</strong>: http://YOUR_BOT_IP:5005 (if host is set to 0.0.0.0)</p>
|
||
<h2 id="reverse-proxy-with-nginx-basic-auth">Reverse Proxy With Nginx Basic Auth</h2>
|
||
<p>If you expose the web viewer outside your local network, run it behind HTTPS and authentication. One option is to bind the viewer locally, then put Nginx in front with basic auth:</p>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="k">[Web_Viewer]</span>
|
||
<a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a><span class="na">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
|
||
<a id="__codelineno-3-3" name="__codelineno-3-3" href="#__codelineno-3-3"></a><span class="na">auto_start</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
|
||
<a id="__codelineno-3-4" name="__codelineno-3-4" href="#__codelineno-3-4"></a><span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">127.0.0.1</span>
|
||
<a id="__codelineno-3-5" name="__codelineno-3-5" href="#__codelineno-3-5"></a><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">8080</span>
|
||
</code></pre></div>
|
||
<p>Example Nginx server block:</p>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="k">server</span><span class="w"> </span><span class="p">{</span>
|
||
<a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a><span class="w"> </span><span class="c1"># [...]</span>
|
||
<a id="__codelineno-4-3" name="__codelineno-4-3" href="#__codelineno-4-3"></a><span class="w"> </span><span class="kn">auth_basic</span><span class="w"> </span><span class="s">"Login</span><span class="w"> </span><span class="s">required"</span><span class="p">;</span>
|
||
<a id="__codelineno-4-4" name="__codelineno-4-4" href="#__codelineno-4-4"></a><span class="w"> </span><span class="kn">auth_basic_user_file</span><span class="w"> </span><span class="s">/etc/nginx/.meshcore-bot.htpasswd</span><span class="p">;</span>
|
||
<a id="__codelineno-4-5" name="__codelineno-4-5" href="#__codelineno-4-5"></a>
|
||
<a id="__codelineno-4-6" name="__codelineno-4-6" href="#__codelineno-4-6"></a><span class="w"> </span><span class="kn">location</span><span class="w"> </span><span class="s">/</span><span class="w"> </span><span class="p">{</span>
|
||
<a id="__codelineno-4-7" name="__codelineno-4-7" href="#__codelineno-4-7"></a><span class="w"> </span><span class="c1"># Local web viewer instance</span>
|
||
<a id="__codelineno-4-8" name="__codelineno-4-8" href="#__codelineno-4-8"></a><span class="w"> </span><span class="kn">proxy_pass</span><span class="w"> </span><span class="s">http://127.0.0.1:8080</span><span class="p">;</span>
|
||
<a id="__codelineno-4-9" name="__codelineno-4-9" href="#__codelineno-4-9"></a><span class="w"> </span><span class="kn">proxy_buffering</span><span class="w"> </span><span class="no">off</span><span class="p">;</span>
|
||
<a id="__codelineno-4-10" name="__codelineno-4-10" href="#__codelineno-4-10"></a><span class="w"> </span><span class="kn">include</span><span class="w"> </span><span class="s">/etc/nginx/proxy_params</span><span class="p">;</span>
|
||
<a id="__codelineno-4-11" name="__codelineno-4-11" href="#__codelineno-4-11"></a><span class="w"> </span><span class="p">}</span>
|
||
<a id="__codelineno-4-12" name="__codelineno-4-12" href="#__codelineno-4-12"></a>
|
||
<a id="__codelineno-4-13" name="__codelineno-4-13" href="#__codelineno-4-13"></a><span class="w"> </span><span class="c1"># Socket.IO websocket path for live updates</span>
|
||
<a id="__codelineno-4-14" name="__codelineno-4-14" href="#__codelineno-4-14"></a><span class="w"> </span><span class="kn">location</span><span class="w"> </span><span class="s">/socket.io/</span><span class="w"> </span><span class="p">{</span>
|
||
<a id="__codelineno-4-15" name="__codelineno-4-15" href="#__codelineno-4-15"></a><span class="w"> </span><span class="kn">if</span><span class="w"> </span><span class="s">(</span><span class="nv">$http_connection</span><span class="w"> </span><span class="s">!~*</span><span class="w"> </span><span class="s">"upgrade")</span><span class="w"> </span><span class="p">{</span>
|
||
<a id="__codelineno-4-16" name="__codelineno-4-16" href="#__codelineno-4-16"></a><span class="w"> </span><span class="kn">return</span><span class="w"> </span><span class="mi">403</span><span class="p">;</span>
|
||
<a id="__codelineno-4-17" name="__codelineno-4-17" href="#__codelineno-4-17"></a><span class="w"> </span><span class="p">}</span>
|
||
<a id="__codelineno-4-18" name="__codelineno-4-18" href="#__codelineno-4-18"></a><span class="w"> </span><span class="kn">if</span><span class="w"> </span><span class="s">(</span><span class="nv">$http_upgrade</span><span class="w"> </span><span class="s">!~*</span><span class="w"> </span><span class="s">"websocket")</span><span class="w"> </span><span class="p">{</span>
|
||
<a id="__codelineno-4-19" name="__codelineno-4-19" href="#__codelineno-4-19"></a><span class="w"> </span><span class="kn">return</span><span class="w"> </span><span class="mi">403</span><span class="p">;</span>
|
||
<a id="__codelineno-4-20" name="__codelineno-4-20" href="#__codelineno-4-20"></a><span class="w"> </span><span class="p">}</span>
|
||
<a id="__codelineno-4-21" name="__codelineno-4-21" href="#__codelineno-4-21"></a>
|
||
<a id="__codelineno-4-22" name="__codelineno-4-22" href="#__codelineno-4-22"></a><span class="w"> </span><span class="kn">proxy_pass</span><span class="w"> </span><span class="s">http://127.0.0.1:8080</span><span class="p">;</span>
|
||
<a id="__codelineno-4-23" name="__codelineno-4-23" href="#__codelineno-4-23"></a><span class="w"> </span><span class="kn">include</span><span class="w"> </span><span class="s">/etc/nginx/proxy_params</span><span class="p">;</span>
|
||
<a id="__codelineno-4-24" name="__codelineno-4-24" href="#__codelineno-4-24"></a>
|
||
<a id="__codelineno-4-25" name="__codelineno-4-25" href="#__codelineno-4-25"></a><span class="w"> </span><span class="kn">proxy_http_version</span><span class="w"> </span><span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
|
||
<a id="__codelineno-4-26" name="__codelineno-4-26" href="#__codelineno-4-26"></a><span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Upgrade</span><span class="w"> </span><span class="nv">$http_upgrade</span><span class="p">;</span>
|
||
<a id="__codelineno-4-27" name="__codelineno-4-27" href="#__codelineno-4-27"></a><span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Connection</span><span class="w"> </span><span class="s">"upgrade"</span><span class="p">;</span>
|
||
<a id="__codelineno-4-28" name="__codelineno-4-28" href="#__codelineno-4-28"></a><span class="w"> </span><span class="kn">proxy_read_timeout</span><span class="w"> </span><span class="mi">86400</span><span class="p">;</span>
|
||
<a id="__codelineno-4-29" name="__codelineno-4-29" href="#__codelineno-4-29"></a><span class="w"> </span><span class="p">}</span>
|
||
<a id="__codelineno-4-30" name="__codelineno-4-30" href="#__codelineno-4-30"></a><span class="p">}</span>
|
||
</code></pre></div>
|
||
<p>With the config above, <code>/etc/nginx/proxy_params</code> should include the standard forwarded headers:</p>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="k">proxy_set_header</span><span class="w"> </span><span class="s">Host</span><span class="w"> </span><span class="nv">$http_host</span><span class="p">;</span>
|
||
<a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a><span class="k">proxy_set_header</span><span class="w"> </span><span class="s">X-Real-IP</span><span class="w"> </span><span class="nv">$remote_addr</span><span class="p">;</span>
|
||
<a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="k">proxy_set_header</span><span class="w"> </span><span class="s">X-Forwarded-For</span><span class="w"> </span><span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
|
||
<a id="__codelineno-5-4" name="__codelineno-5-4" href="#__codelineno-5-4"></a><span class="k">proxy_set_header</span><span class="w"> </span><span class="s">X-Forwarded-Proto</span><span class="w"> </span><span class="nv">$scheme</span><span class="p">;</span>
|
||
</code></pre></div>
|
||
<h2 id="pages-overview">Pages Overview</h2>
|
||
<h3 id="dashboard">Dashboard</h3>
|
||
<ul>
|
||
<li>Health strip: bot status, database size, uptime, connected clients, radio state,
|
||
and snapshot age with a manual refresh control</li>
|
||
<li><strong>Mesh</strong>: nodes heard, adverts, new nodes, nodes gone quiet, and geographic
|
||
coverage — the count tiles carry a 30-day sparkline and a change chip</li>
|
||
<li>Routing mix (flood vs direct), a hop-distance chart, and the role mix</li>
|
||
<li>Path encoding: multibyte share among contacts and among incoming packets,
|
||
plus a 30-day stacked bar whose height is the share of each day's packets that
|
||
took a multibyte path, split by the payload type carrying them (<code>GRP_TXT</code>,
|
||
<code>RESPONSE</code>, <code>REQ</code>, <code>PATH</code>, <code>TXT_MSG</code>, <code>ANON_REQ</code>, <code>GRP_DATA</code>, <code>ADVERT</code>, and
|
||
<code>Other</code> for the rest). Its y-axis is the tallest bar rounded up to the next
|
||
5%, so it rescales as the mesh changes</li>
|
||
<li>Busiest repeaters, and <strong>one-hop neighbours</strong> (radios heard directly; 24-hour
|
||
or 7-day window)</li>
|
||
</ul>
|
||
<p>The live packet feed lives on the <strong>Real-time</strong> page rather than here; the
|
||
dashboard reads a single snapshot per poll and holds no streaming
|
||
subscriptions.</p>
|
||
<p>Two measurement notes for the mesh charts:</p>
|
||
<p><strong>Hops are derived, and the two path tables disagree on units.</strong>
|
||
<code>observed_paths.path_length</code> is a <em>byte</em> count, so hops are
|
||
<code>path_length / bytes_per_hop</code> — with 2- or 3-byte encoding a three-hop path is
|
||
six or nine bytes long, and charting the raw value would overstate distance two-
|
||
to threefold on a mesh that is ~95% multibyte. <code>packet_stream.path_len</code>, by
|
||
contrast, is <em>already a hop count</em>, with its byte length carried separately as
|
||
<code>path_byte_length</code>. Applying either table's rule to the other silently rescales
|
||
the axis, so both conventions are pinned by tests.</p>
|
||
<p>The hops chart shows both distributions: nodes by their closest advert path (7
|
||
days) and arriving flood packets by distance travelled (whatever
|
||
<code>packet_stream</code> retains, typically 3 days). One counts nodes and the other
|
||
packets, so each is drawn as a share of its own total. Flood packets carry no
|
||
sender identity, which is why they cannot be reduced to a shortest path per
|
||
node the way adverts can.</p>
|
||
<p>Flood hop buckets holding under 0.1% of the series are omitted, because that
|
||
tail decays over roughly twenty hops in bars thinner than a pixel. The number of
|
||
packets withheld is printed beneath the chart, and percentages stay shares of
|
||
the full series so that hiding the tail cannot inflate the remaining bars. The
|
||
node series is shown in full.</p>
|
||
<p><strong>Neighbour membership is direct RF (MeshCore hop count 0).</strong>
|
||
An empty path means this radio heard the originator on the air. A path whose
|
||
byte length equals <code>bytes_per_hop</code> already contains one hop hash — that
|
||
originator is one repeater away, not a neighbour. The dashboard lists empty-path
|
||
adverts in <code>observed_paths</code> plus in-window rows from <code>neighbor_links</code> (zero-hop
|
||
node-discover). <code>complete_contact_tracking.hop_count</code> is not used for
|
||
membership: on a representative database it claims 800 zero-hop contacts while
|
||
only a few dozen have any empty-path advert to corroborate it, and the SNR
|
||
stored against them clusters in a ~1.5 dB band with RSSI near -45 dBm — one
|
||
strong local link recorded against every node whose traffic arrived through it.
|
||
SNR/RSSI on the panel come from the zero-hop path row or from discover (SNR
|
||
only). A relayed packet's SNR measures the last hop into this radio, never the
|
||
link to whoever sent it.
|
||
- <strong>Bot</strong>: messages, commands, reply rate, and unique users, plus the top
|
||
commands/users/channels and longest paths
|
||
- Live activity feed</p>
|
||
<p>The dashboard is served from a <strong>snapshot</strong>, not from live aggregation. A
|
||
background thread in the viewer process recomputes it every
|
||
<code>dashboard_snapshot_interval_seconds</code> (default 60) and writes two tables:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Table</th>
|
||
<th>Contents</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>daily_rollup</code></td>
|
||
<td>One row per local date. Retains counts whose raw sources are pruned long before the dashboard's 30-day window. Signal metrics are stored as sums and counts, never means, so any window re-aggregates correctly.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>dashboard_snapshot</code></td>
|
||
<td>A single JSON row describing current state.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Two consequences worth knowing:</p>
|
||
<ul>
|
||
<li><strong>Gaps are not zeros.</strong> A day with no source data stores NULL and renders as a
|
||
break in the line. Days seeded when the feature was first enabled have real
|
||
advert counts but no message, command, path, or packet figures — those raw
|
||
rows were already pruned and cannot be recovered.</li>
|
||
<li><strong>Window labels come from retention.</strong> The time-window selectors are built
|
||
from each source's configured retention, so the list cannot offer "30 days"
|
||
against a table pruned at 7.</li>
|
||
</ul>
|
||
<p>The <strong>multibyte share</strong> trends are accumulated forward, not derived.</p>
|
||
<p>The per-payload-type figures come from <code>packet_stream</code>, which is pruned within
|
||
days while the chart spans thirty, so each day's split is written into
|
||
<code>daily_rollup.packet_type_encoding</code> as that day is rolled up and cannot be
|
||
recomputed afterwards. Enabling the feature therefore starts an empty chart that
|
||
fills in over the following month. Packets whose denormalized dimensions have
|
||
not been backfilled yet are excluded from both sides of the ratio rather than
|
||
counted as single-byte — counting them would invent a dip in whichever type the
|
||
backfill has not reached.</p>
|
||
<p><strong>The chart measures the day, and the API serves per-type adoption.</strong> A bar's
|
||
height is the share of that day's packets that went multibyte, and its segments
|
||
are each type's multibyte packets over that same day-wide denominator — so the
|
||
segments sum to the bar and the bar equals the figure the packet doughnut
|
||
reports for its own window. Every payload type is counted, with the uncharted
|
||
tail (<code>ACK</code>, <code>TRACE</code>, unmapped ordinals — about 0.8% of live traffic) summed into
|
||
<code>Other</code>; omitting it would leave bar heights a share of the charted types rather
|
||
than of the day.</p>
|
||
<p>The <code>multibyte_share_*</code> metrics answer the different question "how much of <em>this
|
||
type</em> went multibyte?", each a ratio over its own denominator. Eight such ratios
|
||
share no denominator and cannot be stacked, which is why the dashboard payload
|
||
carries the raw counts in <code>packet_encoding</code> rather than the eight percentage
|
||
series; the tooltip quotes both readings per segment.</p>
|
||
<p><strong>Two different advert shares exist, and they do not agree.</strong> The charted
|
||
<code>multibyte_share_advert</code> counts advert <em>packets</em> off the packet stream, the same
|
||
way as every other line. The older <code>multibyte_share</code> counts a day's adverts
|
||
against a classification of the <em>node</em> that sent them — one multibyte path ever
|
||
observed marks that node multibyte for every advert it sends. Neither is wrong;
|
||
they answer different questions, and the gap between them is roughly the set of
|
||
nodes that can do multibyte but mostly do not.</p>
|
||
<p>That older share is also frozen for a different reason. <code>observed_paths</code> is
|
||
deduplicated with a lifetime <code>observation_count</code> and a <code>last_seen</code> that is
|
||
bumped on every re-observation, so historical per-day shares cannot be
|
||
reconstructed from it — nearly half the observation volume would be attributed
|
||
to the wrong day. Each refresh recomputes today plus a three-day trailing
|
||
window; older days stay frozen at the value recorded then.</p>
|
||
<h3 id="repeater-contacts">Repeater Contacts</h3>
|
||
<ul>
|
||
<li>Active repeater contacts</li>
|
||
<li>Location information (city/coordinates)</li>
|
||
<li>Device types and status</li>
|
||
<li>First/last seen timestamps</li>
|
||
<li>Purge count tracking</li>
|
||
</ul>
|
||
<h3 id="contact-tracking">Contact Tracking</h3>
|
||
<ul>
|
||
<li>Complete history of all heard contacts</li>
|
||
<li>Signal strength indicators</li>
|
||
<li>Hop count and routing information</li>
|
||
<li>Advertisement data</li>
|
||
<li>Currently tracked status</li>
|
||
</ul>
|
||
<h3 id="config">Config</h3>
|
||
<ul>
|
||
<li>Categorized configuration topics in a left navigation column</li>
|
||
<li>Core settings such as notifications, log rotation, backup, and maintenance status</li>
|
||
<li>Database operations and database information views in the same tab</li>
|
||
</ul>
|
||
<h3 id="radio">Radio</h3>
|
||
<ul>
|
||
<li>Radio connect/disconnect and channel management (create, inspect, delete)</li>
|
||
<li><strong>Radio Parameters</strong>: read/write frequency, bandwidth, spreading factor, coding
|
||
rate, and TX power on the device</li>
|
||
<li><strong>Node Settings</strong>: read/write companion firmware settings on the device</li>
|
||
<li><em>Response Path Hashing</em>: the path hash size the firmware uses for each hop
|
||
when building outgoing/response paths (mode 0–2 = 1–3 bytes per hop; larger
|
||
hashes avoid relay collisions but need firmware 1.14+ mesh-wide)</li>
|
||
<li><em>Default Region Scope</em>: the radio's own default region, stored in firmware
|
||
(<code>NodePrefs.default_scope_name</code> / <code>default_scope_key</code>) and used for any send
|
||
the bot does not scope itself. Max 30 ASCII characters including the <code>#</code>,
|
||
which is added if you omit it; blank clears it and the radio floods
|
||
unscoped. Firmware without the setting is reported as such rather than
|
||
shown as an empty field, and a stored key that is not the stored name's
|
||
hash is flagged, because the radio routes by the key. Note that once the
|
||
bot sends a scoped message it leaves the radio in forced-unscoped mode, so
|
||
this default stops applying until the bot scopes another send — to have the
|
||
bot post under a region, use Region Scopes below</li>
|
||
<li><em>Identity & Adverts</em>: node name, advertised latitude/longitude, advert
|
||
location policy, and buttons to send a zero-hop or flood advert. The name
|
||
field is locked when the bot manages it (<code>[Bot] bot_name</code> with
|
||
<code>auto_update_device_name</code> on)</li>
|
||
<li><em>Mesh Behavior</em>: extra ACK count and telemetry permissions
|
||
(base/location/environment, each deny / per-contact flags / allow all).
|
||
New-contact handling is shown read-only — it is owned by
|
||
<code>[Bot] auto_manage_contacts</code> in config.ini, which the bot applies to the
|
||
device itself</li>
|
||
<li><em>Advanced Tuning</em>: RX delay base and airtime factor (write-only; the device
|
||
does not report current values)</li>
|
||
<li><strong>Region Scopes</strong>: the bot's own regional flood scopes, edited here rather
|
||
than by hand in <code>config.ini</code>. Unlike the cards above it, this writes
|
||
<code>[Channels]</code> in <code>config.ini</code> and queues a hot config reload — nothing is read
|
||
from or written to the radio. This is a different setting from the radio's
|
||
Default Region Scope above: this one decides what the bot sends and answers,
|
||
that one is the radio's fallback for anything the bot leaves unscoped</li>
|
||
<li><em>Which scopes the bot replies to</em>: <code>flood_scopes</code>. Either "reply whatever
|
||
the scope" (the key is left empty, which is also the default) or an
|
||
allowlist of named regions, optionally including unscoped FLOOD via the
|
||
"also reply to unscoped messages" box, which is the <code>*</code> entry</li>
|
||
<li><em>Default outgoing scope</em>: <code>outgoing_flood_scope_override</code>, used for sends
|
||
that carry no scope of their own (scheduled messages, feeds, webhooks) and
|
||
for replies whose incoming scope could not be matched. Blank means global
|
||
flood. A mirrored reply scope still wins over it</li>
|
||
<li>Per-channel <code>flood_scope.<channel></code> entries are listed read-only, because
|
||
they override the default on their own channel; edit those in <code>config.ini</code></li>
|
||
<li>Names are normalized the way the bot normalizes them, so <code>west</code> is stored
|
||
as <code>#west</code>. A name containing <code>,</code>, <code>%</code> or an inner <code>#</code> is refused: the
|
||
first separates the list, the second breaks every later read of the section
|
||
(config.ini is parsed with interpolation on), and the third is the scope
|
||
marker itself</li>
|
||
<li>The page reports what the bot did with the edit, not just that the file was
|
||
written — it polls the queued reload and says so plainly when nothing
|
||
picked it up</li>
|
||
<li>Device writes are queued through the bot process (<code>channel_operations</code> table),
|
||
so the bot must be running and connected to the radio for reads/writes to
|
||
complete</li>
|
||
</ul>
|
||
<h3 id="purging-log">Purging Log</h3>
|
||
<ul>
|
||
<li>Audit trail of contact purging operations</li>
|
||
<li>Timestamps and reasons</li>
|
||
<li>Contact names and public keys</li>
|
||
</ul>
|
||
<h2 id="api-endpoints">API Endpoints</h2>
|
||
<p>The viewer also provides JSON API endpoints:</p>
|
||
<ul>
|
||
<li><code>GET /api/region-warnings</code> - Region-code settings, per-channel scope tallies,
|
||
the daily series, today's warning budget, and recent warning decisions</li>
|
||
<li><code>POST /api/region-warnings/settings</code> - Save <code>[Region_Warnings]</code> and queue a
|
||
hot config reload</li>
|
||
<li><code>GET /api/region-scopes</code> - Regional flood scopes as the bot resolves them:
|
||
the <code>[Channels] flood_scopes</code> allowlist split into named scopes and the
|
||
global flag, the default outgoing scope, any per-channel overrides, and which
|
||
file a save would write</li>
|
||
<li><code>POST /api/radio/firmware/config/write</code> - Queue a firmware write.
|
||
<code>path_hash_mode</code> (0-2) and <code>default_flood_scope</code> (a region name, or empty to
|
||
clear the radio's default) may be sent together or alone.
|
||
<code>GET</code>-side values come back from <code>POST /api/radio/firmware/config/read</code></li>
|
||
<li><code>POST /api/region-scopes</code> - Save <code>[Channels] flood_scopes</code> and
|
||
<code>outgoing_flood_scope_override</code>, and queue a hot config reload. Returns the
|
||
reload's <code>channel_operations</code> id so the caller can poll
|
||
<code>/api/channel-operations/<id></code> for what the bot actually did</li>
|
||
<li><code>GET /api/dashboard/summary</code> - Snapshot-backed dashboard payload, including
|
||
30-day sparkline series and change figures, plus <code>packet_encoding</code>: 30 days of
|
||
raw per-payload-type multibyte/total counts for the stacked encoding chart.
|
||
Sends a strong <code>ETag</code>; poll with <code>If-None-Match</code> to get a bodyless <code>304</code> while
|
||
the snapshot is unchanged.</li>
|
||
<li><code>GET /api/dashboard/series?metric=<m>&days=<n></code> - Full-history points for one
|
||
metric. <code>metric</code> is one of <code>messages</code>, <code>commands</code>, <code>adverts</code>, <code>nodes</code>,
|
||
<code>new_nodes</code>, <code>packets</code>, <code>multibyte_share</code> (adverts), or the per-payload-type
|
||
packet shares <code>multibyte_share_grp_txt</code>, <code>multibyte_share_response</code>,
|
||
<code>multibyte_share_req</code>, <code>multibyte_share_path</code>, <code>multibyte_share_txt_msg</code>,
|
||
<code>multibyte_share_anon_req</code>, <code>multibyte_share_grp_data</code>,
|
||
<code>multibyte_share_advert</code>.</li>
|
||
<li><code>GET /api/dashboard/top?kind=<k>&window=<w>&limit=<n></code> - One leaderboard.
|
||
<code>kind</code> is one of <code>users</code>, <code>commands</code>, <code>channels</code>, <code>paths</code>, <code>repeaters</code>. The
|
||
response carries <code>window_label</code>, <code>retention_days</code>, and
|
||
<code>truncated_by_retention</code>.</li>
|
||
<li><code>GET /api/dashboard/windows</code> - Selector options derived from each source's
|
||
retention.</li>
|
||
<li><code>POST /api/dashboard/refresh</code> - Force a snapshot recomputation.</li>
|
||
<li><code>GET /api/stats</code> - <strong>Deprecated.</strong> The whole-database statistics payload the
|
||
dashboard used to call five times per page load. Every key name is preserved
|
||
for external consumers, and the response carries <code>Deprecation</code> and <code>Sunset</code>
|
||
headers. Use the <code>/api/dashboard/*</code> endpoints instead; this one is removed at
|
||
the next major version.</li>
|
||
<li><code>GET /api/contacts</code> - Repeater contacts data. The contacts page uses optional
|
||
<code>page</code>, <code>page_size</code> (maximum 200), <code>search</code>, <code>sort</code>, and <code>direction</code> parameters;
|
||
callers that omit pagination retain the legacy full-list response.</li>
|
||
<li><code>GET /api/tracking</code> - Contact tracking data</li>
|
||
</ul>
|
||
<p>Example usage:
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a>curl<span class="w"> </span>http://localhost:5000/api/dashboard/summary
|
||
</code></pre></div></p>
|
||
<h2 id="database-requirements">Database Requirements</h2>
|
||
<p>The viewer uses the same database as the bot by default (<code>[Bot] db_path</code>, typically <code>meshcore_bot.db</code>). That single file holds repeater contacts, mesh graph, packet stream, and other data so the viewer can show everything.</p>
|
||
<p><strong>Dashboard stats</strong> (message/command counts, top users, etc.) come from the stats tables (<code>message_stats</code>, <code>command_stats</code>, <code>path_stats</code>). Stats collection is enabled by default with <code>[Stats_Command] collect_stats = true</code>, even if the user-facing <code>stats</code> chat command is disabled with <code>enabled = false</code>. Set <code>collect_stats = false</code> only if you want to stop writing those dashboard stats tables.</p>
|
||
<h2 id="migrating-from-a-separate-web-viewer-database">Migrating from a separate web viewer database</h2>
|
||
<p>If you previously had the web viewer using a <strong>separate</strong> database (e.g. <code>[Web_Viewer] db_path = bot_data.db</code>), you can switch to the shared database so the viewer shows repeater/graph data and uses one file.</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Stop the bot and web viewer</strong> so neither has the databases open.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Optionally preserve packet stream history</strong> from the old viewer DB into the main DB:</p>
|
||
</li>
|
||
<li>From the project root, run:
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a>python3<span class="w"> </span>migrate_webviewer_db.py<span class="w"> </span>bot_data.db<span class="w"> </span>meshcore_bot.db
|
||
</code></pre></div>
|
||
Use your actual paths if they differ (e.g. full paths or different filenames). The script copies the <code>packet_stream</code> table from the first file into the second and skips rows that would duplicate IDs.</li>
|
||
<li>
|
||
<p>If you don’t care about old packet stream data, skip this step; the viewer will create a new <code>packet_stream</code> table in the main DB.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Point the viewer at the main database</strong> in <code>config.ini</code>:
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a><span class="k">[Web_Viewer]</span>
|
||
<a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a><span class="na">db_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">meshcore_bot.db</span>
|
||
</code></pre></div>
|
||
(Or the same value as <code>[Bot] db_path</code> if you use a different path.)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Start the bot (and viewer as usual)</strong>. The viewer will now read and write to the same database as the bot.</p>
|
||
</li>
|
||
</ol>
|
||
<p>You can keep or remove the old <code>bot_data.db</code> file after verifying the viewer works with the shared DB.</p>
|
||
<h2 id="troubleshooting">Troubleshooting</h2>
|
||
<h3 id="web-viewer-not-accessible-eg-orange-pi-sbc">Web viewer not accessible (e.g. Orange Pi / SBC)</h3>
|
||
<p>If the viewer does not load from another device (e.g. from your phone or PC while the bot runs on an Orange Pi), work through these steps on the Pi.</p>
|
||
<ol>
|
||
<li><strong>Confirm config</strong></li>
|
||
<li>In <code>config.ini</code> under <code>[Web_Viewer]</code>:<ul>
|
||
<li><code>enabled = true</code></li>
|
||
<li><code>auto_start = true</code> (if you want it to start with the bot)</li>
|
||
<li><code>host = 0.0.0.0</code> (required for access from other devices; <code>127.0.0.1</code> is localhost only)</li>
|
||
<li><code>port = 8080</code> (or another port 1024–65535)</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p>Restart the bot after changing config.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Check that the viewer process is running</strong>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a><span class="c1"># From project root on the Pi</span>
|
||
<a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a>ss<span class="w"> </span>-tlnp<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="m">8080</span>
|
||
<a id="__codelineno-9-3" name="__codelineno-9-3" href="#__codelineno-9-3"></a><span class="c1"># or</span>
|
||
<a id="__codelineno-9-4" name="__codelineno-9-4" href="#__codelineno-9-4"></a>netstat<span class="w"> </span>-tlnp<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="m">8080</span>
|
||
</code></pre></div>
|
||
If nothing listens on your port, the viewer did not start or has exited.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Inspect viewer logs</strong></p>
|
||
</li>
|
||
<li>When run by the bot, the viewer writes to:<ul>
|
||
<li><code>logs/web_viewer_stdout.log</code></li>
|
||
<li><code>logs/web_viewer_stderr.log</code></li>
|
||
</ul>
|
||
</li>
|
||
<li>Look for Python tracebacks, "Address already in use", or missing dependencies (e.g. Flask, flask-socketio).</li>
|
||
<li>
|
||
<p>Optional: run the viewer manually to see errors in the terminal:
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a><span class="nb">cd</span><span class="w"> </span>/path/to/meshcore-bot
|
||
<a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a>python3<span class="w"> </span>modules/web_viewer/app.py<span class="w"> </span>--config<span class="w"> </span>config.ini<span class="w"> </span>--host<span class="w"> </span><span class="m">0</span>.0.0.0<span class="w"> </span>--port<span class="w"> </span><span class="m">8080</span>
|
||
</code></pre></div></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Check integration startup</strong></p>
|
||
</li>
|
||
<li>Bot logs may show: <code>Web viewer integration failed: ...</code> or <code>Web viewer integration initialized</code>.</li>
|
||
<li>
|
||
<p>If integration failed, the viewer subprocess is never started; fix the error shown (e.g. invalid <code>host</code> or <code>port</code> in config).</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Firewall</strong></p>
|
||
</li>
|
||
<li>Many SBC images (e.g. Orange Pi, Armbian minimal) do <strong>not</strong> ship with a firewall; if <code>curl</code> to localhost works and <code>host = 0.0.0.0</code>, the blocker may be network (Wi‑Fi client isolation, different subnet, or router). Check from a device on the same LAN using <code>http://<PI_IP>:8080</code>.</li>
|
||
<li>If your system uses <strong>ufw</strong>:
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a>sudo<span class="w"> </span>ufw<span class="w"> </span>status
|
||
<a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a>sudo<span class="w"> </span>ufw<span class="w"> </span>allow<span class="w"> </span><span class="m">8080</span>/tcp
|
||
<a id="__codelineno-11-3" name="__codelineno-11-3" href="#__codelineno-11-3"></a>sudo<span class="w"> </span>ufw<span class="w"> </span>reload
|
||
</code></pre></div></li>
|
||
<li>If <code>ufw</code> is not installed (e.g. <code>sudo: ufw: command not found</code>), you may have no host firewall—that’s common on embedded images. To allow the port with <strong>iptables</strong> (often available when ufw is not):
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a>sudo<span class="w"> </span>iptables<span class="w"> </span>-I<span class="w"> </span>INPUT<span class="w"> </span>-p<span class="w"> </span>tcp<span class="w"> </span>--dport<span class="w"> </span><span class="m">8080</span><span class="w"> </span>-j<span class="w"> </span>ACCEPT
|
||
</code></pre></div>
|
||
(Rules may not persist across reboots unless you use a persistence method for your distro.)</li>
|
||
<li>
|
||
<p>If you prefer ufw, install it (e.g. <code>sudo apt install ufw</code>) and use the ufw commands above.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Test from the Pi first</strong>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a>curl<span class="w"> </span>-s<span class="w"> </span>-o<span class="w"> </span>/dev/null<span class="w"> </span>-w<span class="w"> </span><span class="s2">"%{http_code}"</span><span class="w"> </span>http://127.0.0.1:8080/
|
||
</code></pre></div>
|
||
If this returns <code>200</code>, the viewer is running and the issue is binding or firewall. If you use <code>host = 0.0.0.0</code>, then try from another device: <code>http://<PI_IP>:8080</code>.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Standalone run (no bot)</strong></p>
|
||
</li>
|
||
<li>To rule out bot integration issues, start the viewer by itself (same config path so it finds the DB):
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-14-1" name="__codelineno-14-1" href="#__codelineno-14-1"></a>python3<span class="w"> </span>modules/web_viewer/app.py<span class="w"> </span>--config<span class="w"> </span>config.ini<span class="w"> </span>--host<span class="w"> </span><span class="m">0</span>.0.0.0<span class="w"> </span>--port<span class="w"> </span><span class="m">8080</span>
|
||
</code></pre></div></li>
|
||
<li>If <code>restart_viewer.sh</code> is used, note it binds to <code>127.0.0.1</code> by default; for network access run the command above with <code>--host 0.0.0.0</code> or edit the script.</li>
|
||
</ol>
|
||
<h3 id="flask-not-found">Flask Not Found</h3>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-15-1" name="__codelineno-15-1" href="#__codelineno-15-1"></a>pip3<span class="w"> </span>install<span class="w"> </span>flask<span class="w"> </span>flask-socketio
|
||
</code></pre></div>
|
||
<h3 id="database-not-found">Database Not Found</h3>
|
||
<ul>
|
||
<li>Ensure the bot has been run at least once to create the databases</li>
|
||
<li>Check file permissions on database files</li>
|
||
</ul>
|
||
<h3 id="port-already-in-use">Port Already in Use</h3>
|
||
<ul>
|
||
<li>Change the port in <code>config.ini</code> or stop the conflicting service</li>
|
||
<li>Use <code>ss -tlnp | grep 8080</code> or <code>lsof -i :8080</code> (if available) to find what's using the port</li>
|
||
</ul>
|
||
<h3 id="permission-denied">Permission Denied</h3>
|
||
<div class="highlight"><pre><span></span><code><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a>chmod<span class="w"> </span>+x<span class="w"> </span>restart_viewer.sh
|
||
</code></pre></div>
|
||
<h2 id="security-notes">Security Notes</h2>
|
||
<ul>
|
||
<li>The web viewer is designed for local network use</li>
|
||
<li>Set <code>host = 127.0.0.1</code> for localhost-only access</li>
|
||
<li>Set <code>host = 0.0.0.0</code> for network access (use with caution)</li>
|
||
<li>For network access, set <code>web_viewer_password</code> or use a reverse proxy with authentication and firewall rules</li>
|
||
</ul>
|
||
<h2 id="future-enhancements">Future Enhancements</h2>
|
||
<ul>
|
||
<li>Live packet streaming</li>
|
||
<li>Real-time message monitoring</li>
|
||
<li>Interactive contact management</li>
|
||
<li>Export functionality</li>
|
||
<li>Additional authentication options</li>
|
||
<li>Mobile-responsive design improvements</li>
|
||
</ul>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</article>
|
||
</div>
|
||
|
||
|
||
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<footer class="md-footer">
|
||
|
||
<div class="md-footer-meta md-typeset">
|
||
<div class="md-footer-meta__inner md-grid">
|
||
<div class="md-copyright">
|
||
|
||
|
||
Made with
|
||
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
|
||
Material for MkDocs
|
||
</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div class="md-dialog" data-md-component="dialog">
|
||
<div class="md-dialog__inner md-typeset"></div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.sections", "toc.integrate", "content.code.copy"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||
|
||
|
||
<script src="../assets/javascripts/bundle.d7400e89.min.js"></script>
|
||
|
||
|
||
</body>
|
||
</html> |