16 Commits

Author SHA1 Message Date
MarekWo f72f6d418a feat(db): VACUUM after retention and an Optimize button in Backup modal
SQLite DELETE marks pages free but doesn't shrink the file, so the
new retention job would keep DBs at their bloated size forever without
a follow-up VACUUM. Add db.vacuum() that runs PRAGMA-free VACUUM and
reports size_before/size_after/elapsed so callers can surface results.

The retention job now calls vacuum() automatically when it deleted at
least 1000 rows. Threshold avoids the multi-second VACUUM cost on quiet
days. Failure is logged, not raised — a missed VACUUM never crashes
the scheduler.

Power-user override: new "Optimize now" button in the Database Backup
modal triggers VACUUM on demand via POST /api/db/vacuum, alongside a
GET /api/db/size that drives the live "Current size" label. This way
users don't have to wait until 03:30 to reclaim space after the first
big retention pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 10:55:23 +02:00
MarekWo 63204fe08d fix(retention): enable retention by default and unblock the scheduler
The retention/cleanup scheduler was throwing "Working outside of application
context" on every boot because init_*_schedule() and the APScheduler jobs
themselves call api.get_*_settings(), which reads current_app.db. Pass the
Flask app into archiver.manager via set_flask_app() and decorate every
scheduled job with @_with_app_context so the context is active when the
worker thread runs. Wrap the init calls in main.py in app.app_context() too.

Extend cleanup_old_messages() to also delete from echoes, paths and acks
(the diagnostic tables — 191k echo rows account for the bulk of the 85 MB
DB). Each diagnostic table has its own retention window, defaulting to
min(days, 30) so debug data is purged on a tighter schedule than
user-visible messages.

Switch RETENTION_DEFAULTS to enabled=True with 90/90/60/30 days for
channel_messages/DMs/adverts/diagnostics; user opted in explicitly. First
run scheduled for 03:30 local. SQLite DELETE only marks pages free —
file size won't shrink until a manual VACUUM is run separately.

Archive job stopped working the day the device was renamed to include an
emoji ("MarWoj 💡"): the meshcore library strips non-ASCII when writing
the .msgs file, but our archiver still built /data/{device_name}.msgs and
hit ENOENT every night at 00:00. Add a tolerant fallback that globs the
data dir for a single non-archive .msgs file when the expected path is
missing — mirroring how migrate_v1 already handles this.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:41:07 +02:00
MarekWo 33a71bed17 refactor(ui): rename contact type label CLI to COM (companion)
The MeshCore community uses "companion" not "client" for type 1 nodes.
Rename the CLI label to COM across all UI, API, JS, and docs to align
with official terminology. Includes cache migration for old CLI entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 14:37:30 +01:00
MarekWo c6a2444249 fix(backup): allow backup job scheduling before db reference is set
The _db reference is set by init_retention_schedule() which runs after
schedule_daily_archiving(). The backup job checks _db at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 07:29:30 +01:00
MarekWo 4ecab9b307 feat(backup): add backup API endpoints and UI
- POST /api/backup/create — trigger immediate backup
- GET /api/backup/list — list backups with sizes
- GET /api/backup/download — download backup file
- Backup modal accessible from menu with create/download buttons
- Daily automatic backup via APScheduler (configurable hour/retention)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 07:22:50 +01:00
MarekWo b034a181ce feat(retention): add message retention scheduling (Task 2.6)
- Add daily retention job that deletes old channel messages, DMs, and
  advertisements based on configurable age threshold
- Add GET/POST /api/retention-settings endpoints
- Extend cleanup_old_messages() to optionally include DMs and adverts
- Wire up APScheduler in create_app() (also enables existing archiving
  and contact cleanup schedulers that were never started in v2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:28:54 +01:00
MarekWo 2a187133ec fix: Add throttling and retry logic to cleanup job
Fixes "Broken pipe" errors on slower hardware (single CPU) by:
- Adding 0.5s delay between contact deletions
- Retrying failed deletions up to 2 times with longer delay
- Special handling for Broken pipe errors

This prevents overwhelming meshcore-bridge on resource-constrained systems.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 08:26:29 +01:00
MarekWo 98e4482054 fix: Remove extra argument from _filter_contacts_by_criteria call
The function internally fetches protected contacts, so passing it
as a third argument was incorrect and caused TypeError.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:54:13 +01:00
MarekWo f65b35709f debug: Add detailed logging to cleanup job
Added more logging to track:
- When fetching contacts starts
- Result from get_contacts_with_last_seen (success, count, error)
- Filter criteria being used
- Protected contacts count

This will help diagnose why cleanup job is not finding contacts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 17:46:42 +01:00
MarekWo 0ce7d676cd fix: Use cli module functions in cleanup job
Changed _cleanup_job() to use:
- cli.get_contacts_with_last_seen() instead of direct bridge API call
- cli.delete_contact() for reliable contact deletion

This matches the same approach used in preview-cleanup endpoint.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 17:40:40 +01:00
MarekWo ecf3b42751 fix: Use correct bridge URL in cleanup job
Changed config.BRIDGE_URL to config.MC_BRIDGE_URL and GET to POST
for bridge API calls in _cleanup_job().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 15:03:34 +01:00
MarekWo a4ef0fd497 feat: Use local timezone for scheduled cleanup instead of UTC
The scheduler now uses the timezone configured in .env (TZ variable)
instead of hardcoded UTC:
- Add get_local_timezone_name() helper to manager.py
- BackgroundScheduler uses system local timezone
- API returns timezone field in cleanup-settings response
- Frontend displays timezone next to hour selector and in status text
- Updated documentation to reflect timezone behavior

This makes the cleanup hour more intuitive for users in non-UTC timezones.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:46:08 +01:00
MarekWo da31ab8794 feat: Add configurable hour for scheduled contact cleanup
Allow users to select the hour (0-23 UTC) when automatic contact
cleanup runs:
- Add hour selector dropdown in Advanced Filters (disabled until enabled)
- Hour field saved to .webui_settings.json with cleanup_settings
- API validates hour (0-23), scheduler uses CronTrigger with hour param
- Status text shows configured hour (e.g., "Enabled (runs daily at 03:00 UTC)")
- Documentation updated in user-guide.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:22:02 +01:00
MarekWo a23eb2a5f4 feat: Add automatic scheduled contact cleanup
Add auto-cleanup feature that runs daily at 01:00 UTC using APScheduler:
- New cleanup settings stored in .webui_settings.json (enabled, types, date_field, days, name_filter)
- API endpoints: GET/POST /api/contacts/cleanup-settings
- Scheduler functions: _cleanup_job(), schedule_cleanup(), init_cleanup_schedule()
- UI toggle in Advanced Filters with validation (requires days > 0)
- Debounced auto-save for filter criteria changes
- Protected contacts are excluded from auto-cleanup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 21:49:27 +01:00
MarekWo c7163aa035 feat: Auto-detect device name from meshcli prompt
Bridge now detects device name from meshcli prompt ("DeviceName|*")
and exposes it via /health endpoint. mc-webui fetches this at startup
and uses RuntimeConfig for dynamic device name throughout the app.

Fallback chain: prompt detection → .infos command → MC_DEVICE_NAME env var

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 07:48:10 +01:00
MarekWo f5fedbc96c Feature: Add message archiving system with browse-by-date selector
Implements automatic daily archiving of messages to improve performance
and enable browsing historical chat by date.

Backend changes:
- Add APScheduler for daily archiving at midnight (00:00 UTC)
- Create app/archiver/manager.py with archive logic and scheduler
- Extend parser.py to read from archive files and filter by days
- Add archive configuration to config.py (MC_ARCHIVE_*)

API changes:
- Extend GET /api/messages with archive_date and days parameters
- Add GET /api/archives endpoint to list available archives
- Add POST /api/archive/trigger for manual archiving

Frontend changes:
- Add date selector dropdown in navbar for archive browsing
- Implement archive list loading and date selection
- Update formatTime() to show full dates in archive view
- Live view now shows only last 7 days (configurable)

Docker & Config:
- Add archive volume mount in docker-compose.yml
- Add MC_ARCHIVE_DIR, MC_ARCHIVE_ENABLED, MC_ARCHIVE_RETENTION_DAYS env vars
- Update .env.example with archive configuration section

Documentation:
- Update README.md with archive feature and usage instructions
- Update .claude/instructions.md with archive endpoints

Key features:
- Automatic daily archiving (midnight UTC)
- Live view filtered to last 7 days for better performance
- Browse historical messages by date via dropdown selector
- Archives stored as dated files: {device}.YYYY-MM-DD.msgs
- Original .msgs file never modified (safe, read-only approach)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 20:21:33 +01:00