Compare commits

11 Commits

Author SHA1 Message Date
JingleManSweep
c42a2deffb Merge pull request #95 from ipnet-mesh/chore/add-sponsorship-badge
Add README badges and workflow path filters
2026-02-11 00:40:57 +00:00
Louis King
dfa4157c9c Fixed funding 2026-02-11 00:36:13 +00:00
Louis King
b52fd32106 Add path filters to CI and Docker workflows
Skip unnecessary workflow runs when only non-code files change (README,
docs, etc). Docker workflow always runs on version tags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 00:32:46 +00:00
Louis King
4bbf43a078 Add CI, Docker, and sponsorship badges to README 2026-02-11 00:29:06 +00:00
JingleManSweep
deae9c67fe Add Buy Me a Coffee funding option
Added Buy Me a Coffee funding option.
2026-02-11 00:25:26 +00:00
JingleManSweep
ceee27a3af Merge pull request #94 from ipnet-mesh/chore/docs-update
Update docs and add Claude Code skills
2026-02-11 00:24:24 +00:00
Louis King
f478096bc2 Add Claude Code skills for git branching, PRs, and releases 2026-02-11 00:01:51 +00:00
Louis King
8ae94a7763 Add Claude Code skills for documentation and quality checks 2026-02-10 23:49:58 +00:00
Louis King
fb6cc6f5a9 Update docs to reflect recent features and config options
- Add contact cleanup, admin UI, content home, and webhook secret
  settings to .env.example and README
- Update AGENTS.md project structure with pages.py, example content
  dirs, and corrected receiver init steps
- Document new API endpoints (prefix lookup, members, dashboard
  activity, send-advertisement) in README
- Fix Docker Compose core profile to include db-migrate service
2026-02-10 23:49:31 +00:00
JingleManSweep
a98b295618 Merge pull request #93 from ipnet-mesh/feat/theme-improvements
Add radial glow and solid tint backgrounds to panels and filter bars
2026-02-10 20:26:50 +00:00
Louis King
da512c0d9f Add radial glow and solid tint backgrounds to panels and filter bars
- Add panel-glow CSS class with radial gradient using section colors
- Add panel-solid CSS class for neutral solid-tinted filter bars
- Apply colored glow to stat cards on home and dashboard pages
- Apply neutral grey glow to dashboard chart and data panels
- Apply neutral solid background to filter panels on list pages
- Add shadow-xl drop shadows to dashboard panels and home hero
- Limit dashboard recent adverts to 5 rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:23:19 +00:00
18 changed files with 481 additions and 27 deletions

View File

@@ -0,0 +1,43 @@
---
name: documentation
description: Audit and update project documentation to accurately reflect the current codebase. Use when documentation may be outdated, after significant code changes, or when the user asks to review or update docs.
---
# Documentation Audit
Audit and update all project documentation so it accurately reflects the current state of the codebase. Documentation must only describe features, options, configurations, and functionality that actually exist in the code.
## Files to Review
- **README.md** - Project overview, setup instructions, usage examples
- **AGENTS.md** - AI coding assistant guidelines, project structure, conventions
- **.env.example** - Example environment variables
Also check for substantial comments or inline instructions within the codebase that may be outdated.
## Process
1. **Read all documentation files** listed above in full.
2. **Cross-reference against the codebase.** For every documented item (features, env vars, CLI commands, routes, models, directory paths, conventions), search the code to verify:
- It actually exists.
- Its described behavior matches the implementation.
- File paths and directory structures are accurate.
3. **Identify and fix discrepancies:**
- **Stale/legacy content** — documented but no longer in the code. Remove it.
- **Missing content** — exists in the code but not documented. Add it.
- **Inaccurate descriptions** — documented behavior doesn't match implementation. Correct it.
4. **Apply updates** to each file. Preserve existing style and structure.
5. **Verify consistency** across all documentation files — they must not contradict each other.
## Rules
- Do NOT invent features or options that don't exist in the code.
- Do NOT remove documentation for features that DO exist.
- Do NOT change the fundamental structure or style of the docs.
- Do NOT modify CLAUDE.md.
- Focus on accuracy, not cosmetic changes.
- When in doubt, check the source code.

View File

@@ -0,0 +1,49 @@
---
name: git-branch
description: Create a new branch from latest main with the project's naming convention (feat/fix/chore). Use when starting new work on a feature, bug fix, or chore.
---
# Git Branch
Create a new branch from the latest `main` branch using the project's naming convention.
## Arguments
The user may provide arguments in the format: `<type>/<description>`
- `type` — one of `feat`, `fix`, or `chore`
- `description` — short kebab-case description (e.g., `add-map-clustering`)
If not provided, ask the user for the branch type and description.
## Process
1. **Fetch latest main:**
```bash
git fetch origin main
```
2. **Determine branch name:**
- If the user provided arguments (e.g., `/git-branch feat/add-map-clustering`), use them directly.
- Otherwise, ask the user for:
- **Branch type**: `feat`, `fix`, or `chore`
- **Short description**: a brief kebab-case slug describing the work
- Construct the branch name as `{type}/{slug}` (e.g., `feat/add-map-clustering`).
3. **Create and switch to the new branch:**
```bash
git checkout -b {branch_name} origin/main
```
4. **Confirm** by reporting the new branch name to the user.
## Rules
- Branch names MUST follow the `{type}/{slug}` convention.
- Valid types are `feat`, `fix`, and `chore` only.
- The slug MUST be kebab-case (lowercase, hyphens, no spaces or underscores).
- Always branch from `origin/main`, never from the current branch.
- Do NOT push the branch — just create it locally.

View File

@@ -0,0 +1,94 @@
---
name: git-pr
description: Create a pull request to main from the current branch. Runs quality checks, commits changes, pushes, and opens a PR via gh CLI. Use when ready to submit work for review.
---
# Git PR
Create a pull request to `main` from the current feature branch.
## Process
### Phase 1: Pre-flight Checks
1. **Verify branch:**
```bash
git branch --show-current
```
- The current branch must NOT be `main`. If on `main`, tell the user to create a feature branch first (e.g., `/git-branch`).
2. **Check for uncommitted changes:**
```bash
git status
```
- If there are uncommitted changes, ask the user for a commit message and commit them using the `/git-commit` skill conventions (no Claude authoring details).
### Phase 2: Quality Checks
1. **Determine changed components** by comparing against `main`:
```bash
git diff --name-only main...HEAD
```
2. **Run targeted tests** based on changed files:
- `tests/test_web/` for web-only changes (templates, static JS, web routes)
- `tests/test_api/` for API changes
- `tests/test_collector/` for collector changes
- `tests/test_interface/` for interface/sender/receiver changes
- `tests/test_common/` for common models/schemas/config changes
- Run the full `pytest` if changes span multiple components
3. **Run pre-commit checks:**
```bash
pre-commit run --all-files
```
- If checks fail and auto-fix files, commit the fixes and re-run until clean.
4. If tests or checks fail and cannot be auto-fixed, report the issues to the user and stop.
### Phase 3: Push and Create PR
1. **Push the branch to origin:**
```bash
git push -u origin HEAD
```
2. **Generate PR content:**
- **Title**: Derive from the branch name. Convert `feat/add-map-clustering` to `Add map clustering`, `fix/login-error` to `Fix login error`, etc. Keep under 70 characters.
- **Body**: Generate a summary from the commit history:
```bash
git log main..HEAD --oneline
```
3. **Create the PR:**
```bash
gh pr create --title "{title}" --body "$(cat <<'EOF'
## Summary
{bullet points summarizing the changes}
## Test plan
{checklist of testing steps}
EOF
)"
```
4. **Return the PR URL** to the user.
## Rules
- Do NOT create a PR from `main`.
- Do NOT skip quality checks — tests and pre-commit must pass.
- Do NOT force-push.
- Always target `main` as the base branch.
- Keep the PR title concise (under 70 characters).
- If quality checks fail, fix issues or report to the user — do NOT create the PR with failing checks.

View File

@@ -0,0 +1,66 @@
---
name: quality
description: Run the full test suite, pre-commit checks, and re-run tests to ensure code quality. Fixes any issues found. Use after code changes, before commits, or when the user asks to check quality.
---
# Quality Check
Run the full quality pipeline: tests, pre-commit checks, and a verification test run. Fix any issues discovered at each stage.
## Prerequisites
Before running checks, ensure the environment is ready:
1. Check for `.venv` directory — create with `python -m venv .venv` if missing.
2. Activate the virtual environment: `source .venv/bin/activate`
3. Install dependencies: `pip install -e ".[dev]"`
## Process
### Phase 1: Initial Test Run
Run the full test suite to establish a baseline:
```bash
pytest
```
- If tests **pass**, proceed to Phase 2.
- If tests **fail**, investigate and fix the failures before continuing. Re-run the failing tests to confirm fixes. Then proceed to Phase 2.
### Phase 2: Pre-commit Checks
Run all pre-commit hooks against the entire codebase:
```bash
pre-commit run --all-files
```
- If all checks **pass**, proceed to Phase 3.
- If checks **fail**:
- Many hooks (black, trailing whitespace, end-of-file) auto-fix issues. Re-run `pre-commit run --all-files` to confirm auto-fixes resolved the issues.
- For remaining failures (flake8, mypy, etc.), investigate and fix manually.
- Re-run `pre-commit run --all-files` until all checks pass.
- Then proceed to Phase 3.
### Phase 3: Verification Test Run
Run the full test suite again to ensure pre-commit fixes (formatting, import sorting, etc.) haven't broken any functionality:
```bash
pytest
```
- If tests **pass**, the quality check is complete.
- If tests **fail**, the pre-commit fixes introduced a regression. Investigate and fix, then re-run both `pre-commit run --all-files` and `pytest` until both pass cleanly.
## Rules
- Always run the FULL test suite (`pytest`), not targeted tests.
- Always run pre-commit against ALL files (`--all-files`).
- Do NOT skip or ignore failing tests — investigate and fix them.
- Do NOT skip or ignore pre-commit failures — investigate and fix them.
- Do NOT modify test assertions to make tests pass unless the test is genuinely wrong.
- Do NOT disable pre-commit hooks or add noqa/type:ignore unless truly justified.
- Fix root causes, not symptoms.
- If a fix requires changes outside the scope of a simple quality fix (e.g., a design change), report it to the user rather than making the change silently.

View File

@@ -0,0 +1,114 @@
---
name: release
description: Full release workflow — quality gate, semantic version tag, push, and GitHub release. Use when ready to cut a new release from main.
---
# Release
Run the full release workflow: quality checks, version tagging, push, and GitHub release creation.
## Arguments
The user may optionally provide a version number (e.g., `/release 1.2.0`). If not provided, one will be suggested based on commit history.
## Process
### Phase 1: Pre-flight Checks
1. **Verify on `main` branch:**
```bash
git branch --show-current
```
- Must be on `main`. If not, tell the user to switch to `main` first.
2. **Verify working tree is clean:**
```bash
git status --porcelain
```
- If there are uncommitted changes, tell the user to commit or stash them first.
3. **Pull latest:**
```bash
git pull origin main
```
### Phase 2: Quality Gate
1. **Run full test suite:**
```bash
pytest
```
2. **Run pre-commit checks:**
```bash
pre-commit run --all-files
```
3. If either fails, report the issues and stop. Do NOT proceed with a release that has failing checks.
### Phase 3: Determine Version
1. **Get the latest tag:**
```bash
git describe --tags --abbrev=0 2>/dev/null || echo "none"
```
2. **List commits since last tag:**
```bash
git log {last_tag}..HEAD --oneline
```
If no previous tag exists, list the last 20 commits:
```bash
git log --oneline -20
```
3. **Determine next version:**
- If the user provided a version, use it.
- Otherwise, suggest a version based on commit prefixes:
- Any commit starting with `feat` or `Add`**minor** bump
- Only `fix` or `Fix` commits → **patch** bump
- If no previous tag, suggest `0.1.0`
- Present the suggestion and ask the user to confirm or provide a different version.
### Phase 4: Tag and Release
1. **Create annotated tag:**
```bash
git tag -a v{version} -m "Release v{version}"
```
2. **Push tag to origin:**
```bash
git push origin v{version}
```
3. **Create GitHub release:**
```bash
gh release create v{version} --title "v{version}" --generate-notes
```
4. **Report** the release URL to the user.
## Rules
- MUST be on `main` branch with a clean working tree.
- MUST pass all quality checks before tagging.
- Tags MUST follow the `v{major}.{minor}.{patch}` format (e.g., `v1.2.0`).
- Always create an annotated tag, not a lightweight tag.
- Always confirm the version with the user before tagging.
- Do NOT skip quality checks under any circumstances.
- Do NOT force-push tags.

View File

@@ -107,6 +107,17 @@ MESHCORE_DEVICE_NAME=
NODE_ADDRESS=
NODE_ADDRESS_SENDER=
# -------------------
# Contact Cleanup Settings (RECEIVER mode only)
# -------------------
# Automatic removal of stale contacts from the MeshCore companion node
# Enable automatic removal of stale contacts from companion node
CONTACT_CLEANUP_ENABLED=true
# Remove contacts not advertised for this many days
CONTACT_CLEANUP_DAYS=7
# =============================================================================
# COLLECTOR SETTINGS
# =============================================================================
@@ -192,11 +203,19 @@ WEB_PORT=8080
# Default: dark
# WEB_THEME=dark
# Enable admin interface at /a/ (requires auth proxy in front)
# Default: false
# WEB_ADMIN_ENABLED=false
# Timezone for displaying dates/times on the web dashboard
# Uses standard IANA timezone names (e.g., America/New_York, Europe/London)
# Default: UTC
TZ=UTC
# Directory containing custom content (pages/, media/)
# Default: ./content
# CONTENT_HOME=./content
# -------------------
# Network Information
# -------------------

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
buy_me_a_coffee: jinglemansweep

View File

@@ -3,6 +3,13 @@ name: CI
on:
pull_request:
branches: [main]
paths:
- "src/**"
- "tests/**"
- "alembic/**"
- "pyproject.toml"
- ".pre-commit-config.yaml"
- ".github/workflows/ci.yml"
jobs:
lint:

View File

@@ -3,6 +3,14 @@ name: Docker
on:
push:
branches: [main]
paths:
- "src/**"
- "alembic/**"
- "alembic.ini"
- "pyproject.toml"
- "Dockerfile"
- "docker-compose.yml"
- ".github/workflows/docker.yml"
tags:
- "v*"

View File

@@ -287,7 +287,8 @@ meshcore-hub/
│ └── web/
│ ├── cli.py
│ ├── app.py # FastAPI app
│ ├── templates/ # Jinja2 templates (spa.html shell, base.html)
│ ├── pages.py # Custom markdown page loader
│ ├── templates/ # Jinja2 templates (spa.html shell)
│ └── static/
│ ├── css/app.css # Custom styles
│ └── js/spa/ # SPA frontend (ES modules)
@@ -312,9 +313,12 @@ meshcore-hub/
├── etc/
│ └── mosquitto.conf # MQTT broker configuration
├── example/
── seed/ # Example seed data files
├── node_tags.yaml # Example node tags
└── members.yaml # Example network members
── seed/ # Example seed data files
├── node_tags.yaml # Example node tags
└── members.yaml # Example network members
│ └── content/ # Example custom content
│ ├── pages/ # Example custom pages
│ └── media/ # Example media files
├── seed/ # Seed data directory (SEED_HOME)
│ ├── node_tags.yaml # Node tags for import
│ └── members.yaml # Network members for import
@@ -715,9 +719,10 @@ await mc.start_auto_message_fetching()
On startup, the receiver performs these initialization steps:
1. Set device clock to current Unix timestamp
2. Send a local (non-flood) advertisement
3. Start automatic message fetching
4. Sync the device's contact database
2. Optionally set the device name (if `MESHCORE_DEVICE_NAME` is configured)
3. Send a flood advertisement (broadcasts device name to the mesh)
4. Start automatic message fetching
5. Sync the device's contact database
### Contact Sync Behavior

View File

@@ -1,5 +1,9 @@
# MeshCore Hub
[![CI](https://github.com/ipnet-mesh/meshcore-hub/actions/workflows/ci.yml/badge.svg)](https://github.com/ipnet-mesh/meshcore-hub/actions/workflows/ci.yml)
[![Docker](https://github.com/ipnet-mesh/meshcore-hub/actions/workflows/docker.yml/badge.svg)](https://github.com/ipnet-mesh/meshcore-hub/actions/workflows/docker.yml)
[![BuyMeACoffee](https://raw.githubusercontent.com/pachadotdev/buymeacoffee-badges/main/bmc-donate-yellow.svg)](https://www.buymeacoffee.com/jinglemansweep)
Python 3.13+ platform for managing and orchestrating MeshCore mesh networks.
![MeshCore Hub Web Dashboard](docs/images/web.png)
@@ -168,7 +172,7 @@ Docker Compose uses **profiles** to select which services to run:
| Profile | Services | Use Case |
|---------|----------|----------|
| `core` | collector, api, web | Central server infrastructure |
| `core` | db-migrate, collector, api, web | Central server infrastructure |
| `receiver` | interface-receiver | Receiver node (events to MQTT) |
| `sender` | interface-sender | Sender node (MQTT to device) |
| `mqtt` | mosquitto broker | Local MQTT broker (optional) |
@@ -275,6 +279,10 @@ All components are configured via environment variables. Create a `.env` file or
| `SERIAL_PORT` | `/dev/ttyUSB0` | Serial port for MeshCore device |
| `SERIAL_BAUD` | `115200` | Serial baud rate |
| `MESHCORE_DEVICE_NAME` | *(none)* | Device/node name set on startup (broadcast in advertisements) |
| `NODE_ADDRESS` | *(none)* | Override for device public key (64-char hex string) |
| `NODE_ADDRESS_SENDER` | *(none)* | Override for sender device public key |
| `CONTACT_CLEANUP_ENABLED` | `true` | Enable automatic removal of stale contacts from companion node |
| `CONTACT_CLEANUP_DAYS` | `7` | Remove contacts not advertised for this many days |
### Webhooks
@@ -287,7 +295,9 @@ The collector can forward certain events to external HTTP endpoints:
| `WEBHOOK_MESSAGE_URL` | *(none)* | Webhook URL for all message events |
| `WEBHOOK_MESSAGE_SECRET` | *(none)* | Secret for message webhook |
| `WEBHOOK_CHANNEL_MESSAGE_URL` | *(none)* | Override URL for channel messages only |
| `WEBHOOK_CHANNEL_MESSAGE_SECRET` | *(none)* | Secret for channel message webhook |
| `WEBHOOK_DIRECT_MESSAGE_URL` | *(none)* | Override URL for direct messages only |
| `WEBHOOK_DIRECT_MESSAGE_SECRET` | *(none)* | Secret for direct message webhook |
| `WEBHOOK_TIMEOUT` | `10.0` | Request timeout in seconds |
| `WEBHOOK_MAX_RETRIES` | `3` | Max retry attempts on failure |
| `WEBHOOK_RETRY_BACKOFF` | `2.0` | Exponential backoff multiplier |
@@ -329,6 +339,7 @@ The collector automatically cleans up old event data and inactive nodes:
| `WEB_HOST` | `0.0.0.0` | Web server bind address |
| `WEB_PORT` | `8080` | Web server port |
| `API_BASE_URL` | `http://localhost:8000` | API endpoint URL |
| `WEB_THEME` | `dark` | Default theme (`dark` or `light`). Users can override via theme toggle in navbar. |
| `WEB_ADMIN_ENABLED` | `false` | Enable admin interface at /a/ (requires auth proxy) |
| `TZ` | `UTC` | Timezone for displaying dates/times (e.g., `America/New_York`, `Europe/London`) |
| `NETWORK_NAME` | `MeshCore Network` | Display name for the network |
@@ -339,6 +350,7 @@ 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 |
| `NETWORK_CONTACT_YOUTUBE` | *(none)* | YouTube channel URL |
| `CONTENT_HOME` | `./content` | Directory containing custom content (pages/, media/) |
#### Feature Flags
@@ -541,15 +553,21 @@ curl -X POST \
|--------|----------|-------------|
| GET | `/api/v1/nodes` | List all known nodes |
| GET | `/api/v1/nodes/{public_key}` | Get node details |
| GET | `/api/v1/nodes/prefix/{prefix}` | Get node by public key prefix |
| GET | `/api/v1/nodes/{public_key}/tags` | Get node tags |
| POST | `/api/v1/nodes/{public_key}/tags` | Create node tag |
| GET | `/api/v1/messages` | List messages with filters |
| GET | `/api/v1/advertisements` | List advertisements |
| GET | `/api/v1/telemetry` | List telemetry data |
| GET | `/api/v1/trace-paths` | List trace paths |
| GET | `/api/v1/members` | List network members |
| POST | `/api/v1/commands/send-message` | Send direct message |
| POST | `/api/v1/commands/send-channel-message` | Send channel message |
| POST | `/api/v1/commands/send-advertisement` | Send advertisement |
| GET | `/api/v1/dashboard/stats` | Get network statistics |
| GET | `/api/v1/dashboard/activity` | Get daily advertisement activity |
| GET | `/api/v1/dashboard/message-activity` | Get daily message activity |
| GET | `/api/v1/dashboard/node-count` | Get cumulative node count history |
## Development
@@ -618,13 +636,13 @@ meshcore-hub/
├── tests/ # Test suite
├── alembic/ # Database migrations
├── etc/ # Configuration files (mosquitto.conf)
├── example/ # Example files for testing
├── example/ # Example files for reference
│ ├── seed/ # Example seed data files
│ │ ├── node_tags.yaml # Example node tags
│ │ └── members.yaml # Example network members
│ └── content/ # Example custom content
│ ├── pages/ # Example custom pages
│ │ └── about.md # Example about page
│ │ └── join.md # Example join page
│ └── media/ # Example media files
│ └── images/ # Custom images
├── seed/ # Seed data directory (SEED_HOME, copy from example/seed/)

View File

@@ -25,6 +25,7 @@
--color-messages: oklch(0.75 0.18 180); /* teal */
--color-map: oklch(0.8471 0.199 83.87); /* yellow (matches btn-warning) */
--color-members: oklch(0.72 0.17 50); /* orange */
--color-neutral: oklch(0.3 0.01 250); /* subtle dark grey */
}
/* Light mode: darker section colors for contrast on light backgrounds */
@@ -35,6 +36,7 @@
--color-messages: oklch(0.55 0.18 180);
--color-map: oklch(0.58 0.16 45);
--color-members: oklch(0.55 0.18 25);
--color-neutral: oklch(0.85 0.01 250);
}
/* ==========================================================================
@@ -90,6 +92,34 @@
color: #fff !important;
}
/* ==========================================================================
Panel Glow
Radial color glow from bottom-right corner.
Set --panel-color on the element for a section-tinted glow.
========================================================================== */
.panel-glow {
background-image:
radial-gradient(
ellipse at 80% 80%,
color-mix(in oklch, var(--panel-color, transparent) 15%, transparent),
transparent 70%
);
}
.panel-glow.panel-glow-tl {
background-image:
radial-gradient(
ellipse at 20% 20%,
color-mix(in oklch, var(--panel-color, transparent) 15%, transparent),
transparent 70%
);
}
.panel-solid {
background-color: color-mix(in oklch, var(--panel-color, transparent) 10%, oklch(var(--b1)));
}
/* ==========================================================================
Scrollbar Styling
========================================================================== */

View File

@@ -160,7 +160,7 @@ ${content}`, container);
});
renderPage(html`
<div class="card bg-base-100 shadow mb-6">
<div class="card shadow mb-6 panel-solid" style="--panel-color: var(--color-neutral)">
<div class="card-body py-4">
<form method="GET" action="/advertisements" class="flex gap-4 flex-wrap items-end" @submit=${createFilterHandler('/advertisements', navigate)}>
<div class="form-control">

View File

@@ -45,7 +45,7 @@ function renderRecentAds(ads) {
if (!ads || ads.length === 0) {
return html`<p class="text-sm opacity-70">No advertisements recorded yet.</p>`;
}
const rows = ads.map(ad => {
const rows = ads.slice(0, 5).map(ad => {
const friendlyName = ad.tag_name || ad.name;
const displayName = friendlyName || (ad.public_key.slice(0, 12) + '...');
const keyLine = friendlyName
@@ -98,7 +98,7 @@ function renderChannelMessages(channelMessages) {
</div>`;
});
return html`<div class="card bg-base-100 shadow-xl">
return html`<div class="card bg-base-100 shadow-xl panel-glow" style="--panel-color: var(--color-neutral)">
<div class="card-body">
<h2 class="card-title">
${iconChannel('h-6 w-6')}
@@ -148,7 +148,7 @@ export async function render(container, params, router) {
${topCount > 0 ? html`
<div class="grid grid-cols-1 ${topGrid} gap-6 mb-6">
${showNodes ? html`
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat bg-base-100 rounded-box shadow-xl panel-glow" style="--panel-color: ${pageColors.nodes}">
<div class="stat-figure" style="color: ${pageColors.nodes}">
${iconNodes('h-8 w-8')}
</div>
@@ -158,7 +158,7 @@ ${topCount > 0 ? html`
</div>` : nothing}
${showAdverts ? html`
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat bg-base-100 rounded-box shadow-xl panel-glow" style="--panel-color: ${pageColors.adverts}">
<div class="stat-figure" style="color: ${pageColors.adverts}">
${iconAdvertisements('h-8 w-8')}
</div>
@@ -168,7 +168,7 @@ ${topCount > 0 ? html`
</div>` : nothing}
${showMessages ? html`
<div class="stat bg-base-100 rounded-box shadow">
<div class="stat bg-base-100 rounded-box shadow-xl panel-glow" style="--panel-color: ${pageColors.messages}">
<div class="stat-figure" style="color: ${pageColors.messages}">
${iconMessages('h-8 w-8')}
</div>
@@ -180,7 +180,7 @@ ${topCount > 0 ? html`
<div class="grid grid-cols-1 ${topGrid} gap-6 mb-8">
${showNodes ? html`
<div class="card bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow-xl panel-glow" style="--panel-color: var(--color-neutral)">
<div class="card-body">
<h2 class="card-title text-base">
${iconNodes('h-5 w-5')}
@@ -194,7 +194,7 @@ ${topCount > 0 ? html`
</div>` : nothing}
${showAdverts ? html`
<div class="card bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow-xl panel-glow" style="--panel-color: var(--color-neutral)">
<div class="card-body">
<h2 class="card-title text-base">
${iconAdvertisements('h-5 w-5')}
@@ -208,7 +208,7 @@ ${topCount > 0 ? html`
</div>` : nothing}
${showMessages ? html`
<div class="card bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow-xl panel-glow" style="--panel-color: var(--color-neutral)">
<div class="card-body">
<h2 class="card-title text-base">
${iconMessages('h-5 w-5')}
@@ -225,7 +225,7 @@ ${topCount > 0 ? html`
${bottomCount > 0 ? html`
<div class="grid grid-cols-1 ${bottomGrid} gap-6">
${showAdverts ? html`
<div class="card bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow-xl panel-glow" style="--panel-color: var(--color-neutral)">
<div class="card-body">
<h2 class="card-title">
${iconAdvertisements('h-6 w-6')}

View File

@@ -67,7 +67,7 @@ export async function render(container, params, router) {
const showActivityChart = showAdvertSeries || showMessageSeries;
litRender(html`
<div class="${showStats ? 'grid grid-cols-1 lg:grid-cols-3 gap-6' : ''} bg-base-100 rounded-box p-6">
<div class="${showStats ? 'grid grid-cols-1 lg:grid-cols-3 gap-6' : ''} bg-base-100 rounded-box shadow-xl p-6">
<div class="${showStats ? 'lg:col-span-2' : ''} flex flex-col items-center text-center">
<div class="flex flex-col sm:flex-row items-center gap-4 sm:gap-8 mb-4">
<img src="${logoUrl}" alt="${networkName}" class="theme-logo h-24 w-24 sm:h-36 sm:w-36" />
@@ -111,7 +111,7 @@ export async function render(container, params, router) {
${showStats ? html`
<div class="flex flex-col gap-4">
${features.nodes !== false ? html`
<div class="stat bg-base-200 rounded-box">
<div class="stat bg-base-200 rounded-box shadow panel-glow" style="--panel-color: ${pageColors.nodes}">
<div class="stat-figure" style="color: ${pageColors.nodes}">
${iconNodes('h-8 w-8')}
</div>
@@ -121,7 +121,7 @@ export async function render(container, params, router) {
</div>` : nothing}
${features.advertisements !== false ? html`
<div class="stat bg-base-200 rounded-box">
<div class="stat bg-base-200 rounded-box shadow panel-glow" style="--panel-color: ${pageColors.adverts}">
<div class="stat-figure" style="color: ${pageColors.adverts}">
${iconAdvertisements('h-8 w-8')}
</div>
@@ -131,7 +131,7 @@ export async function render(container, params, router) {
</div>` : nothing}
${features.messages !== false ? html`
<div class="stat bg-base-200 rounded-box">
<div class="stat bg-base-200 rounded-box shadow panel-glow" style="--panel-color: ${pageColors.messages}">
<div class="stat-figure" style="color: ${pageColors.messages}">
${iconMessages('h-8 w-8')}
</div>

View File

@@ -174,7 +174,7 @@ export async function render(container, params, router) {
</div>
</div>
<div class="card bg-base-100 shadow mb-6">
<div class="card shadow mb-6 panel-solid" style="--panel-color: var(--color-neutral)">
<div class="card-body py-4">
<div class="flex gap-4 flex-wrap items-end">
<div class="form-control">

View File

@@ -139,7 +139,7 @@ ${content}`, container);
});
renderPage(html`
<div class="card bg-base-100 shadow mb-6">
<div class="card shadow mb-6 panel-solid" style="--panel-color: var(--color-neutral)">
<div class="card-body py-4">
<form method="GET" action="/messages" class="flex gap-4 flex-wrap items-end" @submit=${createFilterHandler('/messages', navigate)}>
<div class="form-control">

View File

@@ -133,7 +133,7 @@ ${content}`, container);
});
renderPage(html`
<div class="card bg-base-100 shadow mb-6">
<div class="card shadow mb-6 panel-solid" style="--panel-color: var(--color-neutral)">
<div class="card-body py-4">
<form method="GET" action="/nodes" class="flex gap-4 flex-wrap items-end" @submit=${createFilterHandler('/nodes', navigate)}>
<div class="form-control">