mirror of
https://github.com/dpup/meshstream.git
synced 2026-03-28 17:42:37 +01:00
Docker set up and fixes for build
This commit is contained in:
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Version control
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist
|
||||||
|
bin
|
||||||
|
logs
|
||||||
|
node_modules
|
||||||
|
web/node_modules
|
||||||
|
web/dist
|
||||||
|
|
||||||
|
# Editor/IDE files
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.log
|
||||||
|
tmp
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
LICENSE
|
||||||
53
.env.example
53
.env.example
@@ -1,12 +1,45 @@
|
|||||||
# MQTT Broker Configuration
|
# Environment variables for docker-compose
|
||||||
MQTT_BROKER=mqtt.bayme.sh
|
# Copy this file to .env and customize with your own values
|
||||||
MQTT_PORT=1883
|
|
||||||
MQTT_USERNAME=meshdev
|
|
||||||
MQTT_PASSWORD=large4cats
|
|
||||||
|
|
||||||
# Topic Configuration
|
###########################################
|
||||||
MQTT_TOPIC_PREFIX=msh/US/CA/Motherlode
|
# Web app variables (BUILD-TIME ONLY)
|
||||||
|
# These must be set at build time and are
|
||||||
|
# compiled into the static HTML/JS files
|
||||||
|
###########################################
|
||||||
|
# Development setup - enables more detailed logging and developer features
|
||||||
|
MESHSTREAM_APP_ENV=development
|
||||||
|
# MESHSTREAM_APP_ENV=production # Uncomment for production build
|
||||||
|
|
||||||
# Application Settings
|
# Site customization
|
||||||
# Set to "true" to enable debug logging
|
MESHSTREAM_SITE_TITLE=Bay Area Mesh - Dev
|
||||||
DEBUG=false
|
MESHSTREAM_SITE_DESCRIPTION=Development instance - Meshtastic activity in Bay Area region
|
||||||
|
|
||||||
|
# For local development, point to local server
|
||||||
|
# (empty for production where API is on same domain)
|
||||||
|
MESHSTREAM_API_BASE_URL=http://localhost:8080
|
||||||
|
|
||||||
|
# Google Maps API configuration - required for maps to work
|
||||||
|
# Get keys at: https://developers.google.com/maps/documentation/javascript/get-api-key
|
||||||
|
MESHSTREAM_GOOGLE_MAPS_ID=your mapid
|
||||||
|
MESHSTREAM_GOOGLE_MAPS_API_KEY=your api
|
||||||
|
# IMPORTANT: To change these values after building, you must rebuild the image
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
# Runtime environment variables
|
||||||
|
###########################################
|
||||||
|
# MQTT connection settings
|
||||||
|
MESHSTREAM_MQTT_BROKER=mqtt.bayme.sh
|
||||||
|
MESHSTREAM_MQTT_USERNAME=meshdev
|
||||||
|
MESHSTREAM_MQTT_PASSWORD=large4cats
|
||||||
|
# Topic to monitor - customize for your region
|
||||||
|
MESHSTREAM_MQTT_TOPIC_PREFIX=msh/US/bayarea
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
MESHSTREAM_SERVER_HOST=0.0.0.0 # Listen on all interfaces
|
||||||
|
MESHSTREAM_SERVER_PORT=8080 # Standard web port
|
||||||
|
MESHSTREAM_STATIC_DIR=/app/static
|
||||||
|
|
||||||
|
# Logging and debugging
|
||||||
|
MESHSTREAM_LOG_LEVEL=debug # Options: debug, info, warn, error
|
||||||
|
MESHSTREAM_VERBOSE_LOGGING=true # Set to false in production
|
||||||
|
MESHSTREAM_CACHE_SIZE=1000 # Number of packets to cache for new subscribers
|
||||||
119
Dockerfile
Normal file
119
Dockerfile
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Multi-stage build for Meshstream
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Stage 1: Build the web application
|
||||||
|
###############################################################################
|
||||||
|
FROM node:20-alpine AS web-builder
|
||||||
|
|
||||||
|
WORKDIR /app/web
|
||||||
|
|
||||||
|
# Install pnpm globally
|
||||||
|
RUN npm install -g pnpm@latest
|
||||||
|
|
||||||
|
# Copy web app package files
|
||||||
|
COPY web/package.json web/pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy the rest of the web app source
|
||||||
|
COPY web/ ./
|
||||||
|
|
||||||
|
# Build-time environment variables for web app
|
||||||
|
ARG MESHSTREAM_API_BASE_URL=""
|
||||||
|
ARG MESHSTREAM_APP_ENV="production"
|
||||||
|
ARG MESHSTREAM_SITE_TITLE
|
||||||
|
ARG MESHSTREAM_SITE_DESCRIPTION
|
||||||
|
ARG MESHSTREAM_GOOGLE_MAPS_ID
|
||||||
|
ARG MESHSTREAM_GOOGLE_MAPS_API_KEY
|
||||||
|
|
||||||
|
# Convert MESHSTREAM_ prefixed args to VITE_ environment variables for the web build
|
||||||
|
ENV VITE_API_BASE_URL=${MESHSTREAM_API_BASE_URL} \
|
||||||
|
VITE_APP_ENV=${MESHSTREAM_APP_ENV} \
|
||||||
|
VITE_SITE_TITLE=${MESHSTREAM_SITE_TITLE} \
|
||||||
|
VITE_SITE_DESCRIPTION=${MESHSTREAM_SITE_DESCRIPTION} \
|
||||||
|
VITE_GOOGLE_MAPS_ID=${MESHSTREAM_GOOGLE_MAPS_ID} \
|
||||||
|
VITE_GOOGLE_MAPS_API_KEY=${MESHSTREAM_GOOGLE_MAPS_API_KEY}
|
||||||
|
|
||||||
|
# Build the web app
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Stage 2: Build the Go server
|
||||||
|
###############################################################################
|
||||||
|
FROM golang:1.24-alpine AS go-builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install required system dependencies
|
||||||
|
RUN apk add --no-cache git protobuf make
|
||||||
|
|
||||||
|
# Cache Go modules
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy proto files (needed for any proto generation)
|
||||||
|
COPY proto/ ./proto/
|
||||||
|
|
||||||
|
# Copy web build from previous stage
|
||||||
|
COPY --from=web-builder /app/web/dist ./dist/static
|
||||||
|
|
||||||
|
# Copy the rest of the app code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Generate protocol buffer code in case it's needed
|
||||||
|
RUN make gen-proto
|
||||||
|
|
||||||
|
# Build the Go application
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /meshstream
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Stage 3: Final lightweight runtime image
|
||||||
|
###############################################################################
|
||||||
|
FROM alpine:3.19
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Add basic runtime dependencies
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
|
# Create a non-root user to run the app
|
||||||
|
RUN addgroup -S meshstream && adduser -S meshstream -G meshstream
|
||||||
|
|
||||||
|
# Copy the binary from the build stage
|
||||||
|
COPY --from=go-builder /meshstream /app/meshstream
|
||||||
|
|
||||||
|
# Copy the static files
|
||||||
|
COPY --from=go-builder /app/dist/static /app/static
|
||||||
|
|
||||||
|
# Set ownership to the non-root user
|
||||||
|
RUN chown -R meshstream:meshstream /app
|
||||||
|
|
||||||
|
# Switch to the non-root user
|
||||||
|
USER meshstream
|
||||||
|
|
||||||
|
# Expose the application port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
ENV MESHSTREAM_SERVER_HOST=0.0.0.0
|
||||||
|
ENV MESHSTREAM_SERVER_PORT=8080
|
||||||
|
ENV MESHSTREAM_STATIC_DIR=/app/static
|
||||||
|
|
||||||
|
# Reporting configuration
|
||||||
|
ENV MESHSTREAM_STATS_INTERVAL=30s
|
||||||
|
ENV MESHSTREAM_CACHE_SIZE=1000
|
||||||
|
ENV MESHSTREAM_VERBOSE_LOGGING=false
|
||||||
|
|
||||||
|
# MQTT configuration
|
||||||
|
ENV MESHSTREAM_MQTT_BROKER=mqtt.bayme.sh
|
||||||
|
ENV MESHSTREAM_MQTT_USERNAME=meshdev
|
||||||
|
ENV MESHSTREAM_MQTT_PASSWORD=large4cats
|
||||||
|
ENV MESHSTREAM_MQTT_TOPIC_PREFIX=msh/US/bayarea
|
||||||
|
ENV MESHSTREAM_MQTT_CLIENT_ID=meshstream
|
||||||
|
|
||||||
|
# Note: Web app configuration is set at build time
|
||||||
|
# and baked into the static files
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
ENTRYPOINT ["/app/meshstream"]
|
||||||
36
Makefile
36
Makefile
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: build run gen-proto clean tools web-run web-build web-test web-lint
|
.PHONY: build run gen-proto clean tools web-run web-build web-test web-lint docker-build docker-run
|
||||||
|
|
||||||
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
@@ -67,4 +67,36 @@ web-test:
|
|||||||
|
|
||||||
# Run linting for the web application
|
# Run linting for the web application
|
||||||
web-lint:
|
web-lint:
|
||||||
cd $(WEB_DIR) && pnpm lint
|
cd $(WEB_DIR) && pnpm lint
|
||||||
|
|
||||||
|
# Docker commands
|
||||||
|
|
||||||
|
# Build a Docker image
|
||||||
|
docker-build:
|
||||||
|
docker build \
|
||||||
|
--build-arg MESHSTREAM_API_BASE_URL=$${MESHSTREAM_API_BASE_URL:-} \
|
||||||
|
--build-arg MESHSTREAM_APP_ENV=$${MESHSTREAM_APP_ENV:-production} \
|
||||||
|
--build-arg MESHSTREAM_SITE_TITLE=$${MESHSTREAM_SITE_TITLE:-Meshstream} \
|
||||||
|
--build-arg MESHSTREAM_SITE_DESCRIPTION=$${MESHSTREAM_SITE_DESCRIPTION:-"Meshtastic activity monitoring"} \
|
||||||
|
--build-arg MESHSTREAM_GOOGLE_MAPS_ID=$${MESHSTREAM_GOOGLE_MAPS_ID:-4f089fb2d9fbb3db} \
|
||||||
|
--build-arg MESHSTREAM_GOOGLE_MAPS_API_KEY=$${MESHSTREAM_GOOGLE_MAPS_API_KEY} \
|
||||||
|
-t meshstream .
|
||||||
|
|
||||||
|
# Run Docker container with environment variables
|
||||||
|
docker-run: docker-build
|
||||||
|
docker run -p 8080:8080 \
|
||||||
|
-e MESHSTREAM_MQTT_BROKER=$${MESHSTREAM_MQTT_BROKER:-mqtt.bayme.sh} \
|
||||||
|
-e MESHSTREAM_MQTT_USERNAME=$${MESHSTREAM_MQTT_USERNAME:-meshdev} \
|
||||||
|
-e MESHSTREAM_MQTT_PASSWORD=$${MESHSTREAM_MQTT_PASSWORD:-large4cats} \
|
||||||
|
-e MESHSTREAM_MQTT_TOPIC_PREFIX=$${MESHSTREAM_MQTT_TOPIC_PREFIX:-msh/US/bayarea} \
|
||||||
|
-e MESHSTREAM_SERVER_HOST=0.0.0.0 \
|
||||||
|
-e MESHSTREAM_SERVER_PORT=$${MESHSTREAM_SERVER_PORT:-8080} \
|
||||||
|
-e MESHSTREAM_STATIC_DIR=/app/static \
|
||||||
|
-e MESHSTREAM_LOG_LEVEL=$${MESHSTREAM_LOG_LEVEL:-info} \
|
||||||
|
-e MESHSTREAM_VERBOSE_LOGGING=$${MESHSTREAM_VERBOSE_LOGGING:-false} \
|
||||||
|
-e MESHSTREAM_CACHE_SIZE=$${MESHSTREAM_CACHE_SIZE:-50} \
|
||||||
|
meshstream
|
||||||
|
|
||||||
|
# Docker compose build and run with .env support
|
||||||
|
docker-compose-up:
|
||||||
|
docker-compose up --build
|
||||||
75
README.md
75
README.md
@@ -13,10 +13,85 @@ go mod tidy
|
|||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
### Using Go directly
|
||||||
|
|
||||||
```
|
```
|
||||||
go run main.go
|
go run main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using Make
|
||||||
|
|
||||||
|
```
|
||||||
|
make run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Docker
|
||||||
|
|
||||||
|
The application can be built and run in Docker using the provided Dockerfile:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Build the Docker image
|
||||||
|
make docker-build
|
||||||
|
|
||||||
|
# Run the Docker container
|
||||||
|
make docker-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Or using Docker Compose:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Environment Variables and Secrets
|
||||||
|
|
||||||
|
The application supports two types of environment variables:
|
||||||
|
|
||||||
|
1. **Build-time variables** - Used during the web application build (via Docker build args)
|
||||||
|
2. **Runtime variables** - Used when the application is running
|
||||||
|
|
||||||
|
You can set these variables in two ways:
|
||||||
|
|
||||||
|
1. **Using a `.env` file** (recommended for development and secrets):
|
||||||
|
|
||||||
|
```
|
||||||
|
# Copy the sample file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit with your values, especially for secrets like Google Maps API key
|
||||||
|
nano .env
|
||||||
|
|
||||||
|
# Build and run with variables from .env
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Passing variables directly** (useful for CI/CD or one-off runs):
|
||||||
|
|
||||||
|
```
|
||||||
|
# Example with custom settings
|
||||||
|
docker run -p 8080:8080 \
|
||||||
|
-e MESHSTREAM_MQTT_BROKER=your-mqtt-broker.com \
|
||||||
|
-e MESHSTREAM_MQTT_TOPIC_PREFIX=msh/YOUR/REGION \
|
||||||
|
-e MESHSTREAM_SERVER_HOST=0.0.0.0 \
|
||||||
|
-e MESHSTREAM_STATIC_DIR=/app/static \
|
||||||
|
meshstream
|
||||||
|
```
|
||||||
|
|
||||||
|
For build-time variables (like the Google Maps API key), use Docker build arguments with the MESHSTREAM_ prefix:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build \
|
||||||
|
--build-arg MESHSTREAM_GOOGLE_MAPS_API_KEY=your_api_key_here \
|
||||||
|
--build-arg MESHSTREAM_GOOGLE_MAPS_ID=your_maps_id_here \
|
||||||
|
-t meshstream .
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Notes:**
|
||||||
|
- All environment variables use the `MESHSTREAM_` prefix.
|
||||||
|
- The Dockerfile internally transforms build-time variables like `MESHSTREAM_GOOGLE_MAPS_API_KEY` to `VITE_GOOGLE_MAPS_API_KEY` for the web application build process.
|
||||||
|
- Web application configuration (site title, Google Maps API key, etc.) must be set at build time. These values are compiled into the static files and cannot be changed at runtime.
|
||||||
|
- To update web application configuration, you must rebuild the Docker image.
|
||||||
|
|
||||||
## Decoding Meshtastic Packets
|
## Decoding Meshtastic Packets
|
||||||
|
|
||||||
This project includes the Meshtastic protocol buffer definitions in the `proto/` directory and a decoder for parsing MQTT packets. The application will automatically decode JSON messages and extract key information from binary messages.
|
This project includes the Meshtastic protocol buffer definitions in the `proto/` directory and a decoder for parsing MQTT packets. The application will automatically decode JSON messages and extract key information from binary messages.
|
||||||
|
|||||||
43
docker-compose.yml
Normal file
43
docker-compose.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Create a .env file for overrides. At the very least, set the following:
|
||||||
|
#
|
||||||
|
# VITE_GOOGLE_MAPS_API_KEY=your_api_key_here
|
||||||
|
# VITE_GOOGLE_MAPS_ID=your_maps_id_here
|
||||||
|
|
||||||
|
services:
|
||||||
|
meshstream:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
# Web application build-time variables
|
||||||
|
- MESHSTREAM_API_BASE_URL=${MESHSTREAM_API_BASE_URL:-}
|
||||||
|
- MESHSTREAM_APP_ENV=${MESHSTREAM_APP_ENV:-production}
|
||||||
|
- MESHSTREAM_SITE_TITLE=${MESHSTREAM_SITE_TITLE:-Bay Area Mesh}
|
||||||
|
- MESHSTREAM_SITE_DESCRIPTION=${MESHSTREAM_SITE_DESCRIPTION:-Meshtastic activity in the Bay Area region, CA.}
|
||||||
|
- MESHSTREAM_GOOGLE_MAPS_ID=${MESHSTREAM_GOOGLE_MAPS_ID}
|
||||||
|
- MESHSTREAM_GOOGLE_MAPS_API_KEY=${MESHSTREAM_GOOGLE_MAPS_API_KEY}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
# Runtime configuration with defaults from .env file or inline defaults
|
||||||
|
# MQTT connection settings
|
||||||
|
- MESHSTREAM_MQTT_BROKER=${MESHSTREAM_MQTT_BROKER:-mqtt.bayme.sh}
|
||||||
|
- MESHSTREAM_MQTT_USERNAME=${MESHSTREAM_MQTT_USERNAME:-meshdev}
|
||||||
|
- MESHSTREAM_MQTT_PASSWORD=${MESHSTREAM_MQTT_PASSWORD:-large4cats}
|
||||||
|
- MESHSTREAM_MQTT_TOPIC_PREFIX=${MESHSTREAM_MQTT_TOPIC_PREFIX:-msh/US/bayarea}
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
- MESHSTREAM_SERVER_HOST=${MESHSTREAM_SERVER_HOST:-0.0.0.0}
|
||||||
|
- MESHSTREAM_SERVER_PORT=${MESHSTREAM_SERVER_PORT:-8080}
|
||||||
|
- MESHSTREAM_STATIC_DIR=${MESHSTREAM_STATIC_DIR:-/app/static}
|
||||||
|
|
||||||
|
# Logging and debugging
|
||||||
|
- MESHSTREAM_LOG_LEVEL=${MESHSTREAM_LOG_LEVEL:-info}
|
||||||
|
- MESHSTREAM_VERBOSE_LOGGING=${MESHSTREAM_VERBOSE_LOGGING:-false}
|
||||||
|
- MESHSTREAM_CACHE_SIZE=${MESHSTREAM_CACHE_SIZE:-1000}
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/api/status"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
5244
generated/google/protobuf/descriptor.pb.go
Normal file
5244
generated/google/protobuf/descriptor.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
26
main.go
26
main.go
@@ -28,10 +28,7 @@ type Config struct {
|
|||||||
// Web server configuration
|
// Web server configuration
|
||||||
ServerHost string
|
ServerHost string
|
||||||
ServerPort string
|
ServerPort string
|
||||||
|
StaticDir string
|
||||||
// Logging configuration
|
|
||||||
LogLevel string
|
|
||||||
LogFormat string
|
|
||||||
|
|
||||||
// Channel keys configuration (name:key pairs)
|
// Channel keys configuration (name:key pairs)
|
||||||
ChannelKeys []string
|
ChannelKeys []string
|
||||||
@@ -75,10 +72,7 @@ func parseConfig() *Config {
|
|||||||
// Web server configuration
|
// Web server configuration
|
||||||
flag.StringVar(&config.ServerHost, "server-host", getEnv("SERVER_HOST", "localhost"), "Web server host")
|
flag.StringVar(&config.ServerHost, "server-host", getEnv("SERVER_HOST", "localhost"), "Web server host")
|
||||||
flag.StringVar(&config.ServerPort, "server-port", getEnv("SERVER_PORT", "8080"), "Web server port")
|
flag.StringVar(&config.ServerPort, "server-port", getEnv("SERVER_PORT", "8080"), "Web server port")
|
||||||
|
flag.StringVar(&config.StaticDir, "static-dir", getEnv("STATIC_DIR", "./server/static"), "Directory containing static web files")
|
||||||
// Logging configuration
|
|
||||||
flag.StringVar(&config.LogLevel, "log-level", getEnv("LOG_LEVEL", "info"), "Log level (debug, info, warn, error)")
|
|
||||||
flag.StringVar(&config.LogFormat, "log-format", getEnv("LOG_FORMAT", "json"), "Log format (json, console)")
|
|
||||||
|
|
||||||
// Channel key configuration (comma separated list of name:key pairs)
|
// Channel key configuration (comma separated list of name:key pairs)
|
||||||
channelKeysDefault := getEnv("CHANNEL_KEYS", "LongFast:"+decoder.DefaultPrivateKey+",ERSN:VIuMtC5uDDJtC/ojdH314HLkDIHanX4LdbK5yViV9jA=")
|
channelKeysDefault := getEnv("CHANNEL_KEYS", "LongFast:"+decoder.DefaultPrivateKey+",ERSN:VIuMtC5uDDJtC/ojdH314HLkDIHanX4LdbK5yViV9jA=")
|
||||||
@@ -146,21 +140,8 @@ func boolFromEnv(key string, defaultValue bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Parse configuration from flags and environment variables
|
|
||||||
config := parseConfig()
|
config := parseConfig()
|
||||||
|
logger := logging.NewProdLogger().Named("main")
|
||||||
// Set up logging
|
|
||||||
var logger logging.Logger
|
|
||||||
|
|
||||||
// Use the production logger (JSON format)
|
|
||||||
logger = logging.NewProdLogger()
|
|
||||||
|
|
||||||
// Add main component name
|
|
||||||
logger = logger.Named("main")
|
|
||||||
|
|
||||||
// Log our configuration
|
|
||||||
logger.Infof("Logger initialized with level: %s, format: %s",
|
|
||||||
config.LogLevel, config.LogFormat)
|
|
||||||
|
|
||||||
// Initialize channel keys
|
// Initialize channel keys
|
||||||
for _, channelKeyPair := range config.ChannelKeys {
|
for _, channelKeyPair := range config.ChannelKeys {
|
||||||
@@ -232,6 +213,7 @@ func main() {
|
|||||||
Logger: logger,
|
Logger: logger,
|
||||||
MQTTServer: config.MQTTBroker,
|
MQTTServer: config.MQTTBroker,
|
||||||
MQTTTopicPath: config.MQTTTopicPrefix + "/#",
|
MQTTTopicPath: config.MQTTTopicPrefix + "/#",
|
||||||
|
StaticDir: config.StaticDir,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start the server in a goroutine
|
// Start the server in a goroutine
|
||||||
|
|||||||
1422
proto/google/protobuf/descriptor.proto
Normal file
1422
proto/google/protobuf/descriptor.proto
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ type Config struct {
|
|||||||
Broker *mqtt.Broker // The MQTT message broker
|
Broker *mqtt.Broker // The MQTT message broker
|
||||||
MQTTServer string // MQTT server hostname
|
MQTTServer string // MQTT server hostname
|
||||||
MQTTTopicPath string // MQTT topic path being subscribed to
|
MQTTTopicPath string // MQTT topic path being subscribed to
|
||||||
|
StaticDir string // Directory containing static web files
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create connection info JSON to send to the client
|
// Create connection info JSON to send to the client
|
||||||
@@ -75,7 +76,7 @@ func (s *Server) Start() error {
|
|||||||
prefab.WithPort(port),
|
prefab.WithPort(port),
|
||||||
prefab.WithHTTPHandlerFunc("/api/status", s.handleStatus),
|
prefab.WithHTTPHandlerFunc("/api/status", s.handleStatus),
|
||||||
prefab.WithHTTPHandlerFunc("/api/stream", s.handleStream),
|
prefab.WithHTTPHandlerFunc("/api/stream", s.handleStream),
|
||||||
prefab.WithStaticFiles("/", "./server/static"),
|
prefab.WithStaticFiles("/", s.config.StaticDir),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
|
|||||||
@@ -27,19 +27,6 @@ const calculateZoomFromPrecisionBits = (precisionBits?: number): number => {
|
|||||||
return Math.min(18, baseZoom + (additionalZoom / 2)); // Cap at zoom 18
|
return Math.min(18, baseZoom + (additionalZoom / 2)); // Cap at zoom 18
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to calculate accuracy in meters from precision bits
|
|
||||||
const calculateAccuracyFromPrecisionBits = (precisionBits?: number): number => {
|
|
||||||
if (!precisionBits) return 300; // Default accuracy of 300m
|
|
||||||
|
|
||||||
// Each precision bit halves the accuracy radius
|
|
||||||
// Starting with Earth's circumference (~40075km), calculate the precision
|
|
||||||
const earthCircumference = 40075000; // in meters
|
|
||||||
const accuracy = earthCircumference / (2 ** precisionBits) / 2;
|
|
||||||
|
|
||||||
// Limit to reasonable values
|
|
||||||
return Math.max(1, Math.min(accuracy, 10000));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Map: React.FC<MapProps> = ({
|
export const Map: React.FC<MapProps> = ({
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
@@ -55,11 +42,6 @@ export const Map: React.FC<MapProps> = ({
|
|||||||
// Calculate zoom level based on precision bits if zoom is not provided
|
// Calculate zoom level based on precision bits if zoom is not provided
|
||||||
const effectiveZoom = zoom || calculateZoomFromPrecisionBits(precisionBits);
|
const effectiveZoom = zoom || calculateZoomFromPrecisionBits(precisionBits);
|
||||||
|
|
||||||
// Calculate accuracy in meters if we have precision bits
|
|
||||||
const accuracyMeters = precisionBits !== undefined
|
|
||||||
? calculateAccuracyFromPrecisionBits(precisionBits)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const mapUrl = getStaticMapUrl(
|
const mapUrl = getStaticMapUrl(
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
@@ -67,8 +49,7 @@ export const Map: React.FC<MapProps> = ({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
nightMode,
|
nightMode,
|
||||||
precisionBits,
|
precisionBits
|
||||||
accuracyMeters
|
|
||||||
);
|
);
|
||||||
const googleMapsUrl = getGoogleMapsUrl(latitude, longitude);
|
const googleMapsUrl = getGoogleMapsUrl(latitude, longitude);
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Packet } from "../lib/types";
|
|
||||||
|
|
||||||
interface MessageDisplayProps {
|
|
||||||
message: Packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MessageDisplay: React.FC<MessageDisplayProps> = ({ message }) => {
|
|
||||||
const { data } = message;
|
|
||||||
|
|
||||||
const getMessageContent = () => {
|
|
||||||
if (data.text_message) {
|
|
||||||
return data.text_message;
|
|
||||||
} else if (data.position) {
|
|
||||||
return `Position: ${data.position.latitude}, ${data.position.longitude}`;
|
|
||||||
} else if (data.node_info) {
|
|
||||||
return `Node Info: ${data.node_info.longName || data.node_info.shortName}`;
|
|
||||||
} else if (data.telemetry) {
|
|
||||||
return "Telemetry data";
|
|
||||||
} else if (data.decode_error) {
|
|
||||||
return `Error: ${data.decode_error}`;
|
|
||||||
}
|
|
||||||
return "Unknown message type";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-4 border border-neutral-700 rounded bg-neutral-800 shadow-inner">
|
|
||||||
<div className="flex justify-between mb-2">
|
|
||||||
<span className="font-medium text-neutral-200">
|
|
||||||
From: {data.from || "Unknown"}
|
|
||||||
</span>
|
|
||||||
<span className="text-neutral-400 text-sm">
|
|
||||||
ID: {data.id || "No ID"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2 text-neutral-300">{getMessageContent()}</div>
|
|
||||||
<div className="mt-3 flex justify-between items-center">
|
|
||||||
<span className="text-xs text-neutral-500">
|
|
||||||
Channel: {message.info.channel}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-neutral-500">Type: {data.port_num}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -144,7 +144,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update markers and fit the map
|
// Update markers and fit the map
|
||||||
updateNodeMarkers(nodesWithPosition, navigate);
|
updateNodeMarkers(nodesWithPosition);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error initializing map:", error);
|
console.error("Error initializing map:", error);
|
||||||
@@ -153,7 +153,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
}
|
}
|
||||||
console.warn("Cannot initialize map - prerequisites not met");
|
console.warn("Cannot initialize map - prerequisites not met");
|
||||||
return false;
|
return false;
|
||||||
}, [nodesWithPosition, navigate, updateNodeMarkers, initializeMap]);
|
}, [nodesWithPosition, updateNodeMarkers, initializeMap]);
|
||||||
|
|
||||||
// Check for Google Maps API loading - make sure all required objects are available
|
// Check for Google Maps API loading - make sure all required objects are available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -274,7 +274,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to update node markers on the map
|
// Helper function to update node markers on the map
|
||||||
function updateNodeMarkers(nodes: MapNode[], navigate: ReturnType<typeof useNavigate>): void {
|
function updateNodeMarkers(nodes: MapNode[]): void {
|
||||||
if (!mapInstanceRef.current) return;
|
if (!mapInstanceRef.current) return;
|
||||||
|
|
||||||
// Clear the bounds for recalculation
|
// Clear the bounds for recalculation
|
||||||
@@ -306,7 +306,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
|
|
||||||
// Create or update marker
|
// Create or update marker
|
||||||
if (!markersRef.current[key]) {
|
if (!markersRef.current[key]) {
|
||||||
createMarker(node, position, nodeName, navigate);
|
createMarker(node, position, nodeName);
|
||||||
} else {
|
} else {
|
||||||
updateMarker(node, position);
|
updateMarker(node, position);
|
||||||
}
|
}
|
||||||
@@ -330,8 +330,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
function createMarker(
|
function createMarker(
|
||||||
node: MapNode,
|
node: MapNode,
|
||||||
position: google.maps.LatLngLiteral,
|
position: google.maps.LatLngLiteral,
|
||||||
nodeName: string,
|
nodeName: string
|
||||||
navigate: ReturnType<typeof useNavigate>
|
|
||||||
): void {
|
): void {
|
||||||
if (!mapInstanceRef.current || !infoWindowRef.current) return;
|
if (!mapInstanceRef.current || !infoWindowRef.current) return;
|
||||||
|
|
||||||
@@ -369,7 +368,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
|
|
||||||
// Add click listener to show info window
|
// Add click listener to show info window
|
||||||
marker.addListener('gmp-click', () => {
|
marker.addListener('gmp-click', () => {
|
||||||
showInfoWindow(node, marker, navigate);
|
showInfoWindow(node, marker);
|
||||||
});
|
});
|
||||||
|
|
||||||
markersRef.current[key] = marker;
|
markersRef.current[key] = marker;
|
||||||
@@ -412,8 +411,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
// Show info window for a node
|
// Show info window for a node
|
||||||
function showInfoWindow(
|
function showInfoWindow(
|
||||||
node: MapNode,
|
node: MapNode,
|
||||||
marker: google.maps.marker.AdvancedMarkerElement,
|
marker: google.maps.marker.AdvancedMarkerElement
|
||||||
navigate: ReturnType<typeof useNavigate>
|
|
||||||
): void {
|
): void {
|
||||||
if (!infoWindowRef.current || !mapInstanceRef.current) return;
|
if (!infoWindowRef.current || !mapInstanceRef.current) return;
|
||||||
|
|
||||||
@@ -445,8 +443,7 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
<div style="font-size: 12px; margin-bottom: 8px; color: #333;">
|
<div style="font-size: 12px; margin-bottom: 8px; color: #333;">
|
||||||
Packets: ${node.messageCount || 0} · Text: ${node.textMessageCount || 0}
|
Packets: ${node.messageCount || 0} · Text: ${node.textMessageCount || 0}
|
||||||
</div>
|
</div>
|
||||||
<a href="javascript:void(0);"
|
<a href="/node/${node.id.toString(16)}"
|
||||||
id="view-node-${node.id}"
|
|
||||||
style="font-size: 13px; color: #3b82f6; text-decoration: none; font-weight: 500; display: inline-block; padding: 4px 8px; background-color: #f1f5f9; border-radius: 4px;">
|
style="font-size: 13px; color: #3b82f6; text-decoration: none; font-weight: 500; display: inline-block; padding: 4px 8px; background-color: #f1f5f9; border-radius: 4px;">
|
||||||
View details →
|
View details →
|
||||||
</a>
|
</a>
|
||||||
@@ -455,16 +452,6 @@ export const NetworkMap = React.forwardRef<{ resetAutoZoom: () => void }, Networ
|
|||||||
|
|
||||||
infoWindowRef.current.setContent(infoContent);
|
infoWindowRef.current.setContent(infoContent);
|
||||||
infoWindowRef.current.open(mapInstanceRef.current, marker);
|
infoWindowRef.current.open(mapInstanceRef.current, marker);
|
||||||
|
|
||||||
// Add listener for the "View details" link with a delay to allow DOM to update
|
|
||||||
setTimeout(() => {
|
|
||||||
const link = document.getElementById(`view-node-${node.id}`);
|
|
||||||
if (link) {
|
|
||||||
link.addEventListener('gmp-click', () => {
|
|
||||||
navigate({ to: `/node/$nodeId`, params: { nodeId: node.id.toString(16) } });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the styling for the map container
|
// Prepare the styling for the map container
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import { Separator } from "../Separator";
|
|||||||
import { KeyValuePair } from "../ui/KeyValuePair";
|
import { KeyValuePair } from "../ui/KeyValuePair";
|
||||||
import { Section } from "../ui/Section";
|
import { Section } from "../ui/Section";
|
||||||
import { BatteryLevel } from "./BatteryLevel";
|
import { BatteryLevel } from "./BatteryLevel";
|
||||||
import { NetworkStrength } from "./NetworkStrength";
|
|
||||||
import { GoogleMap } from "./GoogleMap";
|
import { GoogleMap } from "./GoogleMap";
|
||||||
import { NodePositionData } from "./NodePositionData";
|
import { NodePositionData } from "./NodePositionData";
|
||||||
import { EnvironmentMetrics } from "./EnvironmentMetrics";
|
import { EnvironmentMetrics } from "./EnvironmentMetrics";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export * from './PacketList';
|
export * from './PacketList';
|
||||||
export * from './MessageDisplay';
|
|
||||||
export * from './PacketDetails';
|
export * from './PacketDetails';
|
||||||
export * from './Filter';
|
export * from './Filter';
|
||||||
export * from './InfoMessage';
|
export * from './InfoMessage';
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export const DeviceMetricsPacket: React.FC<DeviceMetricsPacketProps> = ({
|
|||||||
icon={<Gauge />}
|
icon={<Gauge />}
|
||||||
iconBgColor="bg-amber-500"
|
iconBgColor="bg-amber-500"
|
||||||
label="Device Telemetry"
|
label="Device Telemetry"
|
||||||
backgroundColor="bg-amber-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ export const EnvironmentMetricsPacket: React.FC<
|
|||||||
icon={<Thermometer />}
|
icon={<Thermometer />}
|
||||||
iconBgColor="bg-emerald-700"
|
iconBgColor="bg-emerald-700"
|
||||||
label="Environment Telemetry"
|
label="Environment Telemetry"
|
||||||
backgroundColor="bg-green-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const ErrorPacket: React.FC<ErrorPacketProps> = ({ packet }) => {
|
|||||||
icon={<AlertTriangle />}
|
icon={<AlertTriangle />}
|
||||||
iconBgColor="bg-red-500"
|
iconBgColor="bg-red-500"
|
||||||
label="Error"
|
label="Error"
|
||||||
backgroundColor="bg-red-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="max-w-md">
|
<div className="max-w-md">
|
||||||
<div className="text-red-400 mb-2 font-medium">
|
<div className="text-red-400 mb-2 font-medium">
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export const GenericPacket: React.FC<GenericPacketProps> = ({ packet }) => {
|
|||||||
icon={<Package />}
|
icon={<Package />}
|
||||||
iconBgColor="bg-slate-500"
|
iconBgColor="bg-slate-500"
|
||||||
label={portName.replace("_APP", "")}
|
label={portName.replace("_APP", "")}
|
||||||
backgroundColor="bg-slate-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="max-w-md">
|
<div className="max-w-md">
|
||||||
<KeyValueGrid>
|
<KeyValueGrid>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export const NodeInfoPacket: React.FC<NodeInfoPacketProps> = ({ packet }) => {
|
|||||||
icon={<User />}
|
icon={<User />}
|
||||||
iconBgColor="bg-purple-500"
|
iconBgColor="bg-purple-500"
|
||||||
label="Node Info"
|
label="Node Info"
|
||||||
backgroundColor="bg-purple-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
{/* First row: Long name and short name */}
|
{/* First row: Long name and short name */}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const PacketCard: React.FC<PacketCardProps> = ({
|
|||||||
>
|
>
|
||||||
{React.cloneElement(icon as React.ReactElement, {
|
{React.cloneElement(icon as React.ReactElement, {
|
||||||
className: "h-3.5 w-3.5 text-white",
|
className: "h-3.5 w-3.5 text-white",
|
||||||
})}
|
} as React.HTMLAttributes<HTMLElement>)}
|
||||||
</div>
|
</div>
|
||||||
{data.from ? (
|
{data.from ? (
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export const TelemetryPacket: React.FC<TelemetryPacketProps> = ({ packet }) => {
|
|||||||
icon={<BarChart />}
|
icon={<BarChart />}
|
||||||
iconBgColor="bg-neutral-500"
|
iconBgColor="bg-neutral-500"
|
||||||
label="Unknown Telemetry"
|
label="Unknown Telemetry"
|
||||||
backgroundColor="bg-neutral-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="text-neutral-400 text-sm">
|
<div className="text-neutral-400 text-sm">
|
||||||
Unknown telemetry data received at{' '}
|
Unknown telemetry data received at{' '}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export const TextMessagePacket: React.FC<TextMessagePacketProps> = ({ packet })
|
|||||||
icon={<MessageSquareText />}
|
icon={<MessageSquareText />}
|
||||||
iconBgColor="bg-blue-500"
|
iconBgColor="bg-blue-500"
|
||||||
label="Text Message"
|
label="Text Message"
|
||||||
backgroundColor="bg-blue-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="max-w-lg bg-neutral-800/30 p-3 rounded-md tracking-tight break-words">
|
<div className="max-w-lg bg-neutral-800/30 p-3 rounded-md tracking-tight break-words">
|
||||||
{data.textMessage || "Empty message"}
|
{data.textMessage || "Empty message"}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export const WaypointPacket: React.FC<WaypointPacketProps> = ({ packet }) => {
|
|||||||
icon={<MapPin />}
|
icon={<MapPin />}
|
||||||
iconBgColor="bg-violet-500"
|
iconBgColor="bg-violet-500"
|
||||||
label="Waypoint"
|
label="Waypoint"
|
||||||
backgroundColor="bg-violet-950/5"
|
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* API client functions for interacting with the Meshstream server
|
* API client functions for interacting with the Meshstream server
|
||||||
*/
|
*/
|
||||||
import { API_ENDPOINTS } from "./config";
|
import { getStreamEndpoint } from "./config";
|
||||||
import {
|
import {
|
||||||
Packet,
|
Packet,
|
||||||
StreamEvent,
|
StreamEvent,
|
||||||
@@ -204,8 +204,8 @@ export function streamPackets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a new EventSource connection
|
// Create a new EventSource connection using dynamic endpoint
|
||||||
source = new EventSource(API_ENDPOINTS.STREAM);
|
source = new EventSource(getStreamEndpoint());
|
||||||
|
|
||||||
// Log connection attempt
|
// Log connection attempt
|
||||||
if (reconnectAttempt === 0) {
|
if (reconnectAttempt === 0) {
|
||||||
|
|||||||
@@ -12,24 +12,18 @@ export const SITE_TITLE = import.meta.env.VITE_SITE_TITLE || "My Mesh";
|
|||||||
export const SITE_DESCRIPTION =
|
export const SITE_DESCRIPTION =
|
||||||
import.meta.env.VITE_SITE_DESCRIPTION ||
|
import.meta.env.VITE_SITE_DESCRIPTION ||
|
||||||
"Realtime Meshtastic activity via MQTT.";
|
"Realtime Meshtastic activity via MQTT.";
|
||||||
|
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "";
|
||||||
// API URL configuration
|
export const GOOGLE_MAPS_ID = import.meta.env.VITE_GOOGLE_MAPS_ID || "demo-map-id";
|
||||||
const getApiBaseUrl = (): string => {
|
export const GOOGLE_MAPS_API_KEY = import.meta.env.VITE_GOOGLE_MAPS_API_KEY || "";
|
||||||
// In production, use the same domain (empty string base URL)
|
|
||||||
if (IS_PROD) {
|
|
||||||
return import.meta.env.VITE_API_BASE_URL || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// In development, use the configured base URL with fallback
|
|
||||||
return import.meta.env.VITE_API_BASE_URL || "http://localhost:8080";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const API_BASE_URL = getApiBaseUrl();
|
|
||||||
|
|
||||||
// API endpoints
|
// API endpoints
|
||||||
export const API_ENDPOINTS = {
|
export const API_ENDPOINTS = {
|
||||||
STREAM: `${API_BASE_URL}/api/stream`,
|
STREAM: `${API_BASE_URL}/api/stream`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Google Maps configuration
|
/**
|
||||||
export const GOOGLE_MAPS_ID = import.meta.env.VITE_GOOGLE_MAPS_ID || "demo-map-id";
|
* Get the API endpoint for the stream
|
||||||
|
*/
|
||||||
|
export function getStreamEndpoint(): string {
|
||||||
|
return API_ENDPOINTS.STREAM;
|
||||||
|
}
|
||||||
|
|||||||
3
web/src/types/google-maps.d.ts
vendored
3
web/src/types/google-maps.d.ts
vendored
@@ -48,7 +48,7 @@ declare namespace google {
|
|||||||
class InfoWindow {
|
class InfoWindow {
|
||||||
constructor(opts?: InfoWindowOptions);
|
constructor(opts?: InfoWindowOptions);
|
||||||
setContent(content: string): void;
|
setContent(content: string): void;
|
||||||
open(map?: Map, anchor?: Marker): void;
|
open(map?: Map, anchor?: any): void;
|
||||||
close(): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +101,7 @@ declare namespace google {
|
|||||||
|
|
||||||
// Event-related functionality
|
// Event-related functionality
|
||||||
const event: {
|
const event: {
|
||||||
|
addListener(instance: object, event: string, listener: (Event) => void): MapsEventListener;
|
||||||
/**
|
/**
|
||||||
* Removes the given listener, which should have been returned by
|
* Removes the given listener, which should have been returned by
|
||||||
* google.maps.event.addListener.
|
* google.maps.event.addListener.
|
||||||
|
|||||||
Reference in New Issue
Block a user