diff --git a/AGENTS.md b/AGENTS.md
index dafcd79..762f36c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -455,7 +455,7 @@ See [PLAN.md](PLAN.md#configuration-environment-variables) for complete list.
Key variables:
- `DATA_HOME` - Base directory for runtime data (default: `./data`)
- `SEED_HOME` - Directory containing seed data files (default: `./seed`)
-- `PAGES_HOME` - Directory containing custom markdown pages (default: `./pages`)
+- `CONTENT_HOME` - Directory containing custom content (pages, media) (default: `./content`)
- `MQTT_HOST`, `MQTT_PORT`, `MQTT_PREFIX` - MQTT broker connection
- `MQTT_TLS` - Enable TLS/SSL for MQTT (default: `false`)
- `API_READ_KEY`, `API_ADMIN_KEY` - API authentication keys
@@ -473,12 +473,16 @@ ${SEED_HOME}/
└── members.yaml # Network members list
```
-**Custom Pages (`PAGES_HOME`)** - Contains custom markdown pages for the web dashboard:
+**Custom Content (`CONTENT_HOME`)** - Contains custom pages and media for the web dashboard:
```
-${PAGES_HOME}/
-├── about.md # Example: About page (/pages/about)
-├── faq.md # Example: FAQ page (/pages/faq)
-└── getting-started.md # Example: Getting Started (/pages/getting-started)
+${CONTENT_HOME}/
+├── pages/ # Custom markdown pages
+│ ├── about.md # Example: About page (/pages/about)
+│ ├── faq.md # Example: FAQ page (/pages/faq)
+│ └── getting-started.md # Example: Getting Started (/pages/getting-started)
+└── media/ # Custom media files
+ └── images/
+ └── logo.svg # Custom logo (replaces default favicon and navbar/home logo)
```
Pages use YAML frontmatter for metadata:
diff --git a/README.md b/README.md
index 594d306..560be11 100644
--- a/README.md
+++ b/README.md
@@ -338,19 +338,28 @@ The collector automatically cleans up old event data and inactive nodes:
| `NETWORK_CONTACT_EMAIL` | *(none)* | Contact email address |
| `NETWORK_CONTACT_DISCORD` | *(none)* | Discord server link |
| `NETWORK_CONTACT_GITHUB` | *(none)* | GitHub repository URL |
-| `PAGES_HOME` | `./pages` | Directory containing custom markdown pages |
+| `CONTENT_HOME` | `./content` | Directory containing custom content (pages/, media/) |
-### Custom Pages
+### Custom Content
-The web dashboard supports custom markdown pages for adding static content like "About Us", "Getting Started", or "FAQ" pages. Pages are stored as markdown files with YAML frontmatter.
+The web dashboard supports custom content including markdown pages and media files. Content is organized in subdirectories:
+
+```
+content/
+├── pages/ # Custom markdown pages
+│ └── about.md
+└── media/ # Custom media files
+ └── images/
+ └── logo.svg # Custom logo (replaces favicon and navbar/home logo)
+```
**Setup:**
```bash
-# Create pages directory
-mkdir -p pages
+# Create content directory structure
+mkdir -p content/pages content/media
# Create a custom page
-cat > pages/about.md << 'EOF'
+cat > content/pages/about.md << 'EOF'
---
title: About Us
slug: about
@@ -378,14 +387,14 @@ EOF
The markdown content is rendered as-is, so include your own `# Heading` if desired.
-Pages automatically appear in the navigation menu and sitemap. With Docker, mount the pages directory:
+Pages automatically appear in the navigation menu and sitemap. With Docker, mount the content directory:
```yaml
# docker-compose.yml (already configured)
volumes:
- - ${PAGES_HOME:-./pages}:/pages:ro
+ - ${CONTENT_HOME:-./content}:/content:ro
environment:
- - PAGES_HOME=/pages
+ - CONTENT_HOME=/content
```
## Seed Data
@@ -594,10 +603,16 @@ meshcore-hub/
│ ├── seed/ # Example seed data files
│ │ ├── node_tags.yaml # Example node tags
│ │ └── members.yaml # Example network members
-│ └── pages/ # Example custom pages
-│ └── about.md # Example about page
+│ └── content/ # Example custom content
+│ ├── pages/ # Example custom pages
+│ │ └── about.md # Example about page
+│ └── media/ # Example media files
+│ └── images/ # Custom images
├── seed/ # Seed data directory (SEED_HOME, copy from example/seed/)
-├── pages/ # Custom pages directory (PAGES_HOME, optional)
+├── content/ # Custom content directory (CONTENT_HOME, optional)
+│ ├── pages/ # Custom markdown pages
+│ └── media/ # Custom media files
+│ └── images/ # Custom images (logo.svg replaces default logo)
├── data/ # Runtime data directory (DATA_HOME, created at runtime)
├── Dockerfile # Docker build configuration
├── docker-compose.yml # Docker Compose services
diff --git a/docker-compose.yml b/docker-compose.yml
index 88c7e30..7eaf78b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -242,7 +242,7 @@ services:
ports:
- "${WEB_PORT:-8080}:8080"
volumes:
- - ${PAGES_HOME:-./pages}:/pages:ro
+ - ${CONTENT_HOME:-./content}:/content:ro
environment:
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- API_BASE_URL=http://api:8000
@@ -260,7 +260,7 @@ services:
- NETWORK_CONTACT_DISCORD=${NETWORK_CONTACT_DISCORD:-}
- NETWORK_CONTACT_GITHUB=${NETWORK_CONTACT_GITHUB:-}
- NETWORK_WELCOME_TEXT=${NETWORK_WELCOME_TEXT:-}
- - PAGES_HOME=/pages
+ - CONTENT_HOME=/content
command: ["web"]
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
diff --git a/example/content/media/images/logo_ipnet.svg b/example/content/media/images/logo_ipnet.svg
new file mode 100644
index 0000000..3bf6bf4
--- /dev/null
+++ b/example/content/media/images/logo_ipnet.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/example/pages/join.md b/example/content/pages/join.md
similarity index 100%
rename from example/pages/join.md
rename to example/content/pages/join.md
diff --git a/src/meshcore_hub/common/config.py b/src/meshcore_hub/common/config.py
index 9022ca9..bf1b919 100644
--- a/src/meshcore_hub/common/config.py
+++ b/src/meshcore_hub/common/config.py
@@ -295,18 +295,32 @@ class WebSettings(CommonSettings):
default=None, description="Welcome text for homepage"
)
- # Custom pages directory
- pages_home: Optional[str] = Field(
+ # Content directory (contains pages/ and media/ subdirectories)
+ content_home: Optional[str] = Field(
default=None,
- description="Directory containing custom markdown pages (default: ./pages)",
+ description="Directory containing custom content (pages/, media/) (default: ./content)",
)
@property
- def effective_pages_home(self) -> str:
- """Get the effective pages home directory."""
+ def effective_content_home(self) -> str:
+ """Get the effective content home directory."""
from pathlib import Path
- return str(Path(self.pages_home or "./pages"))
+ return str(Path(self.content_home or "./content"))
+
+ @property
+ def effective_pages_home(self) -> str:
+ """Get the effective pages directory (content_home/pages)."""
+ from pathlib import Path
+
+ return str(Path(self.effective_content_home) / "pages")
+
+ @property
+ def effective_media_home(self) -> str:
+ """Get the effective media directory (content_home/media)."""
+ from pathlib import Path
+
+ return str(Path(self.effective_content_home) / "media")
@property
def web_data_dir(self) -> str:
diff --git a/src/meshcore_hub/web/app.py b/src/meshcore_hub/web/app.py
index f3e9d0f..258c544 100644
--- a/src/meshcore_hub/web/app.py
+++ b/src/meshcore_hub/web/app.py
@@ -132,10 +132,23 @@ def create_app(
page_loader.load_pages()
app.state.page_loader = page_loader
+ # Check for custom logo and store media path
+ media_home = Path(settings.effective_media_home)
+ custom_logo_path = media_home / "images" / "logo.svg"
+ if custom_logo_path.exists():
+ app.state.logo_url = "/media/images/logo.svg"
+ logger.info(f"Using custom logo from {custom_logo_path}")
+ else:
+ app.state.logo_url = "/static/img/logo.svg"
+
# Mount static files
if STATIC_DIR.exists():
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
+ # Mount custom media files if directory exists
+ if media_home.exists() and media_home.is_dir():
+ app.mount("/media", StaticFiles(directory=str(media_home)), name="media")
+
# Include routers
from meshcore_hub.web.routes import web_router
@@ -292,5 +305,6 @@ def get_network_context(request: Request) -> dict:
"network_welcome_text": request.app.state.network_welcome_text,
"admin_enabled": request.app.state.admin_enabled,
"custom_pages": custom_pages,
+ "logo_url": request.app.state.logo_url,
"version": __version__,
}
diff --git a/src/meshcore_hub/web/static/img/logo.svg b/src/meshcore_hub/web/static/img/logo.svg
index 3bf6bf4..1709a2b 100644
--- a/src/meshcore_hub/web/static/img/logo.svg
+++ b/src/meshcore_hub/web/static/img/logo.svg
@@ -1,61 +1,21 @@
diff --git a/src/meshcore_hub/web/templates/base.html b/src/meshcore_hub/web/templates/base.html
index 8136eb0..b508ac9 100644
--- a/src/meshcore_hub/web/templates/base.html
+++ b/src/meshcore_hub/web/templates/base.html
@@ -25,7 +25,7 @@
-
+
@@ -112,7 +112,7 @@
-
+
{{ network_name }}
diff --git a/src/meshcore_hub/web/templates/home.html b/src/meshcore_hub/web/templates/home.html
index b02e89e..1e6a8b1 100644
--- a/src/meshcore_hub/web/templates/home.html
+++ b/src/meshcore_hub/web/templates/home.html
@@ -10,7 +10,7 @@