Commit Graph

84 Commits

Author SHA1 Message Date
alexei.dolgolyov 645331d320 chore: release v0.2.1
Release / release (push) Successful in 1m27s
2026-04-22 02:35:38 +03:00
alexei.dolgolyov 6c3dd67c1b feat(tracking): per-config quiet hours with app-level IANA timezone
Add quiet_hours_enabled/start/end to TrackingConfig (HH:MM strings
interpreted in the app-level timezone AppSetting). The dispatch path
loads the app timezone once per run and passes it through
event_allowed_by_config -> in_quiet_hours, so overnight windows like
22:00-07:00 work correctly in any IANA tz.

Frontend exposes a Timezone field under Settings and a Quiet Hours
section on the Immich tracking-config form with time-picker inputs.
2026-04-22 02:31:48 +03:00
alexei.dolgolyov 56993d2ca3 fix(security,perf): harden restore, CSRF, token_version + perf pass
Security
- Sign pending_restore.json (SHA256 stored in AppSetting, verified on
  startup apply) + refuse path outside data_dir, tighten to 0600.
- Require same-origin Origin/Referer on POST /api/backup/apply-restart —
  Bearer-in-localStorage is CSRF-reachable from any XSS'd admin tab.
- Bump token_version on role/username change and admin password reset so
  demoted admins lose admin in already-issued JWTs.  Guard last-admin
  TOCTOU via COUNT + post-commit re-check that rolls back a race.
- SSRF guard (validate_outbound_url) in ImmichClient.__init__ and the
  external_domain setter — admin-mutable URLs were bypassing the check
  that webhook/slack/discord paths already used.  Dev restart script now
  sets NOTIFY_BRIDGE_ALLOW_PRIVATE_URLS=1 so homelab Immich still works.
- Redact + cap Immich error bodies to ~120 chars before they flow into
  ActionExecution.error / EventLog.details (both UI-visible).
- Deny-list sensitive keys (api_key / token / secret / password /
  authorization / cookie / ...) in template-context merges so a rogue
  template can't exfiltrate provider creds via {{ api_key }}.
- Cap user-controlled Immich search params (query ≤256, person_ids ≤50,
  size ≤100) so a Telegram listener can't DoS upstream.
- Stream upload reads with running byte counter + content-length precheck
  instead of buffering the full body and then rejecting.
- Log Telegram parse_mode fallbacks instead of swallowing silently;
  template escape bugs now surface in server logs.
- Rollback partial imports on pending-restore failure (error recorded on
  a fresh session).

Performance
- Fix N+1 in _refresh_telegram_chat_titles: single IN query instead of
  session.get per chat.
- Parallelize album + shared-link fetches in test_dispatch (asyncio.gather)
  and per-receiver Telegram test sends in notifier (semaphore 5).
- Early-exit collect_scheduled_assets(limit=0) so the periodic-summary
  test path skips full per-album filter/sample (was O(album_assets)).
- Emit explicit CREATE INDEX IF NOT EXISTS for event_log user_id /
  action_id / provider_id so the first boot after upgrade isn't left
  unindexed for the dashboard query.
- Add AbortController timeout (120s) to fetchAuth so uploads/downloads
  don't hang indefinitely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 02:28:55 +03:00
alexei.dolgolyov fe92b206b7 chore: release v0.2.0
Release / release (push) Successful in 1m20s
2026-04-22 01:35:24 +03:00
alexei.dolgolyov cf4976da2f fix(telegram): load chats/listeners before expanding to fix slide animation height 2026-04-22 01:29:44 +03:00
alexei.dolgolyov 80c034d2af fix(test-dispatch): fall back to tracker defaults, surface soft errors
- dispatch_test_notification now resolves tracking_config / template_config
  from the tracker's default_* fields when the per-link override is unset,
  matching what load_link_data does for the real watcher. Previously
  periodic/scheduled/memory tests silently failed with "no template
  defined" whenever the user configured the template config at the
  tracker level instead of on each link (the UI's normal default).
- Distinguish the two missing-template cases in the returned error
  ("no template config linked" vs. "slot missing in linked config").
- Frontend testTrackerTarget now treats {success:false,error:"..."} in a
  2xx body as a failure — previously any 2xx flashed a success snack so
  users never saw the real reason their test didn't deliver.
2026-04-22 01:25:35 +03:00
alexei.dolgolyov a7a2b4efa4 feat: large polish pass — UX fixes, per-chat scope, restore/backup, action events
Backend
- Per-chat album scope for Immich commands (search/latest/memory/...): new
  allowed_album_ids on CommandTrackerListener, threaded listener/page kwargs
  through ProviderCommandHandler.handle; PATCH listener-scope endpoint.
- /search and /find accept a trailing page number; Immich client search_smart
  / search_metadata take a page param.
- Immich person-asset lookup switched from removed GET /api/people/{id}/assets
  to POST /api/search/metadata with personIds (fixes /person command and
  auto_organize rules silently returning zero candidates on Immich 1.106+).
- Auto_organize rule now sets the target album's thumbnail to the first added
  image when missing (falls back to any asset type); failures do not fail the
  rule. add_assets_to_album surfaces the Immich error body on non-2xx.
- EventLog.user_id / action_id / action_name columns with defensive migration
  + backfill. Status query filters by user_id directly; Immich/webhook paths
  emit user_id explicitly. action_runner writes an action_success/partial/
  failed event on each non-dry-run.
- Dashboard DELETE /api/status/events (scoped to user_id) + rendering live
  tracker/provider/action names via FK join with snapshot fallback.
- PATCH /api/users/{id} for username/role change with last-admin guard.
- Deletion protection returns structured {message, entity, blocked_by}
  (ApiError carries .blockedBy; frontend opens BlockedByModal).
- Backup prepare-restore → AppSetting markers + atomic write of
  pending_restore.json; lifespan hook applies on next startup and archives
  under data/applied_restores/. apply-restart sends SIGTERM so the lifespan
  shutdown runs; NOTIFY_BRIDGE_SUPERVISED env override gates the button.
  Manual POST /api/backup/files (same format as scheduled).
- New periodic-summary test path reuses shared collect_scheduled_assets
  (limit=0) so test and future production code go through one primitive.
- Per-receiver locale for Telegram test messages (resolves
  TelegramChat.language_override per chat instead of applying the first
  receiver's locale to everyone).
- Bounded concurrency (semaphores) in NotificationDispatcher._preload_asset_data
  and _refresh_telegram_chat_titles; chat title sweep extended to 24h since
  save_chat_from_webhook covers active chats opportunistically.
- Telegram poller detects the \"webhook is active\" 409 and auto-calls
  deleteWebhook for bots whose DB update_mode is polling (throttled per bot).
- TelegramClient.get_chat added (CLAUDE.md rule 6); set_album_thumbnail added.
- Seeds: rename \"Default Commands\" → \"Default Immich Commands\";
  track_assets_removed default False.

Frontend
- Global provider selector visible when there is only one provider.
- Clear-events button + i18n + ConfirmModal on the dashboard; new icons/
  labels/filters/colors for action_success / action_partial / action_failed.
- Auto-select first available tracking/template/command/config + bot on
  create forms (trackers, command-trackers, targets, template/command
  configs).
- Telegram target disable_url_preview defaults to true.
- BlockedByModal wired into 8 deletion flows; fetchAuth helper for
  multipart/binary calls (reuses api()'s refresh + ApiError mapping).
- Immich tracker 'Checking links' parallelised (concurrency cap 6).
- Backup page: pending-restore banner + Apply-now / Apply-later modal,
  restarting overlay polling /api/health, manual 'Create backup' button.
- Command-trackers listener row gets an 'Edit album scope' modal with
  inherit/explicit multiselect.
- Users page: Edit user modal (username + role).
- parseDate helper for consistent UTC date rendering.

Migrations / schema
- event_log: + user_id, action_id, action_name (+ backfill user_id from
  notification_tracker).
- command_tracker_listener: + allowed_album_ids.
2026-04-22 01:13:11 +03:00
alexei.dolgolyov 90bc3ccdc2 chore: pre-release cleanup
- Skip token clear/redirect on 401 for unauthenticated requests
- Fix typo in test secret key in restart-backend script
- Remove completed plan documents (entity-relationship-refactor, ux-notification-improvements)
2026-04-21 19:39:33 +03:00
alexei.dolgolyov f0739ca949 feat: security hardening — SSRF guard, template sandbox timeout, webhook log prune, auth & backup polish
- Add outbound URL validation (SSRF) for webhook/Discord/Slack/ntfy/Matrix dispatch
- Template renderer: input/output caps and thread-based render timeout
- Webhook log filter: strip Authorization/signature/token-like headers; atomic prune
- Auth/JWT/backup/config tightening; misc frontend UX fixes
2026-04-16 03:21:45 +03:00
alexei.dolgolyov 734e5c9340 feat: UX improvements — secure webhooks, locale fixes, dynamic languages, UI polish
- Remove top paginator from dashboard events, keep only bottom
- Fix test message locale: pass UI locale to email/matrix bot tests
- Convert webhook auth mode from text input to icon grid selector
- Generate secure UUID tokens for webhook URLs instead of sequential IDs
- Move Recent Payloads into per-provider expandable container (lazy-loaded)
- Make template config languages dynamic via app settings instead of hardcoded
- Change default dev port to 5175
2026-04-11 02:14:15 +03:00
alexei.dolgolyov 6b2211353d feat: person excludes for auto-organize rules, backup & restore system
Add person exclude criteria to Immich auto-organize — assets containing
excluded persons are filtered out after candidate gathering. Also adds
full backup/restore system with export, import, scheduled backups, and
retention management.
2026-04-02 14:13:42 +03:00
alexei.dolgolyov 6e51164f8e refactor: comprehensive consistency review — UI/UX, code quality, functional parity
Fix 19 issues across 3 priority tiers found during full-codebase review:

CRITICAL:
- Fix undefined --color-secondary CSS variable causing invisible UI elements
- Fix Google Photos command templates using nonexistent asset.originalFileName
- Fix scheduler template variable docs (tracker_name → schedule_name)
- Add missing admin guard on notification template update endpoint

HIGH:
- Fix 5 hardcoded English strings missing i18n (MultiEntitySelect, actions,
  settings, TelegramBotTab, users)
- Replace 17 raw <button> elements with shared <Button> component
- Replace 5 raw error divs with shared <ErrorBanner> component
- Refactor webhook handler duplication into shared _dispatch_webhook_event()
- Add 30+ provider-specific fields to TrackingConfig TypeScript type
- Add default TrackingConfig seeds for immich and google_photos
- Add provider-specific command variable docs (Gitea, Planka, NUT, GP, Webhook)

MEDIUM:
- Replace hardcoded hex colors and Tailwind classes with CSS variable tokens
- Remove dead code (unused imports, orphaned check_notification_tracker)
- Fix Svelte 5 patterns ($state for _prevProviderId, remove unnecessary as any)
- Fix inconsistent POST response shape (targets now returns full response)
- Fix Google Photos template dead asset.year branches, clarify album_url docs
2026-03-31 23:27:35 +03:00
alexei.dolgolyov 6113a0039c feat: webhook payload history — store and display recent incoming payloads
Backend:
- WebhookPayloadLog model (provider_id, method, headers, body, status, extracted_fields, error_message)
- Auto-log payloads in generic_webhook() with matched/unmatched/error status
- Auto-prune beyond max_stored_payloads per provider
- Header filtering (only Content-Type, User-Agent, X-* stored; no Authorization)
- GET/DELETE /api/providers/{id}/webhook-logs endpoints
- store_payloads + max_stored_payloads in WebhookProviderConfig

Frontend:
- WebhookPayloadHistory.svelte — expandable log viewer with status badges, JSON body, headers, extracted fields
- payloadHistory flag on webhook provider descriptor
- max_stored_payloads config field (0 = disabled)
- Password confirmation field on change password modal
- i18n keys for webhook logs (en + ru)
2026-03-28 13:54:54 +03:00
alexei.dolgolyov b803d004e1 refactor: comprehensive codebase review — security, performance, quality, UX
Security:
- Fix NUT protocol command injection (validate names against safe regex)
- Enable Jinja2 autoescape=True to prevent HTML injection via external data
- Add WebhookProviderConfig validation model

Performance:
- Shared aiohttp.ClientSession singleton (replaces 40+ per-request sessions)
- Fix 4 N+1 queries with batch IN loads (poller, scheduler, memory, broadcast)
- asyncio.gather for Gitea commands and notification dispatcher
- Add DB indexes on NotificationTrackerState.tracker_id, CommandTrackerListener
- LRU cache for compiled Jinja2 templates
- Daily EventLog cleanup job (90-day retention)
- 30s HTTP timeout on all external calls
- GROUP BY for target type counts (replaces 7 sequential queries)

Code quality:
- Extract get_owned_entity() helper (replaces 11 duplicate functions)
- Extract slot_helpers.py (load_slots, save_slots, render_template_preview)
- Extract command_utils.py (tracker lookup, last event, collection IDs)
- Extract http_session.py (shared session lifecycle)
- Provider connection validation dedup (3x → 1 helper)
- Command dispatch tables replacing if/elif chains
- Album+links fetch helper (fetch_albums_with_links)
- Provider dispatch polymorphism (list_provider_collections)
- Immutable _enrich_assets (no longer mutates in-place)
- Fix _format_assets return type + handler unpacking

Frontend:
- Fix 18+ hardcoded English strings → t() with new i18n keys (en + ru)
- Mobile "More" nav panel with provider filter and search
- Shared Button.svelte component (4 variants, 2 sizes)
- Shared ErrorBanner.svelte component (8 pages updated)
- SvelteKit goto() replacing window.location.href
- Dashboard grid fixed for 4 cards, paginator opacity consistency

Functionality:
- max_instances=1 on scheduler jobs (prevents duplicate events)
- Webhook provider in watcher (prevents error spam)
- Fix stale SQLModel reference in poller
- Gitea get_repo() direct API call
2026-03-28 13:22:26 +03:00
alexei.dolgolyov 616b221c92 feat: generic webhook provider with JSONPath payload extraction
Add a new "webhook" provider type that accepts arbitrary HTTP POST payloads,
extracts template variables via user-defined JSONPath mappings, and dispatches
notifications through the existing pipeline. Supports three auth modes
(HMAC-SHA256, Bearer token, none), bounded JSONPath cache, and 1MB payload limit.

Full stack: core provider + event parser, API endpoint, DB migration,
capabilities, seeds, default templates (EN/RU), frontend descriptor, i18n.
2026-03-27 23:51:14 +03:00
alexei.dolgolyov 307871cae5 feat: Google Photos provider backend + API hardening
- Add Google Photos provider: client, models, change detector, capabilities
- Add notification templates (en/ru) for all GP event slots
- Add command templates (en/ru) for GP bot commands
- Register GP in slot/command loaders, capabilities, and seeds
- Harden provider API: validate OAuth credentials on create/update
- Add internal URL rewriting for asset fetches (LAN optimization)
- Fix template renderer to handle missing variables gracefully
- Improve webhook command routing for multi-provider support
- Add provider health check endpoint and watcher improvements
2026-03-25 22:07:03 +03:00
alexei.dolgolyov 337276113d feat: collapsible chart, paginator controls, localized template slots
- Dashboard chart collapsible with state persisted in localStorage
- Events per page user-controlled (5/10/20/50) via select, persisted
- Paginator rendered both above and below event list (shared snippet)
- Removed viewport-based page size calculation
- Template slot descriptions localized (templateSlot.* i18n keys)
- Preview As target selector expanded: email, discord, slack added
- Tighter event item spacing
2026-03-24 23:36:41 +03:00
alexei.dolgolyov 21d8ef712a fix: simplify add-target UX — single EntitySelect click to add 2026-03-24 22:50:02 +03:00
alexei.dolgolyov 6e35926772 feat: default tracker configs, email validation, expandable target links
- Tracker now has default_tracking_config_id and default_template_config_id
  that apply to all linked targets unless overridden per-target
- Dispatch falls back to tracker defaults when per-link configs are null
- Email bot creation validates SMTP connection before saving
- Email notifications sent as HTML (links render properly)
- Linked target items are expandable: collapsed shows config CrossLinks,
  expanded shows config selectors; action buttons always visible
- Fix email bot test button icon (mdiEmailSend → mdiSend)
- Fix target type icons in LinkedTargetsSection for all types
- Provider filter moved above search in sidebar
2026-03-24 22:32:37 +03:00
alexei.dolgolyov 1a8c95e942 refactor: replace favorites checkbox with toggle switch in grid layout
Move "Favorites only" from a separate checkboxes array into the regular
fields grid as a toggle switch, aligning Scheduled Assets and Memory Mode
sections visually. Memory source moved to last position.
2026-03-24 17:26:02 +03:00
alexei.dolgolyov b1ab5b884f feat: collapsible accordion slots for template editing UX
Template slot editors (notification + command) now use collapsible
accordion rows instead of showing all editors at once. Each slot
displays a compact header with status pill (empty/valid/warning/error).
Adds slot name filtering and a preview toggle button that swaps
between editor and rendered preview views.
2026-03-24 17:06:03 +03:00
alexei.dolgolyov d0bc767e98 feat: rich command templates with public links + media text-first flow
- Command templates now match notification template style: type icons,
  linked filenames via album shared links, location, favorite status
- Media mode sends text message first, then media as reply (was media-only)
- Search/find/person/place resolve asset public URLs from tracked albums'
  shared links (share/{key}/photos/{id})
- Albums/summary commands include album public_url in context
- Enriched command template preview sample context with public_url, city,
  country, is_favorite
- Extract sanitizePreview to shared lib/sanitize.ts
- Command template preview now renders HTML links (was raw text)
- Global provider filter moved above search in sidebar
- CLAUDE.md: template consistency + context variable sync rules
2026-03-24 16:48:57 +03:00
alexei.dolgolyov f90cc36ebd feat: add API docs link button in sidebar footer
Opens Swagger UI (/docs) in a new tab. Proxies /docs and /openapi.json
in dev mode so the link works from the Vite dev server.
2026-03-24 16:00:22 +03:00
alexei.dolgolyov d8ecb60073 feat: broadcast notification target + UX improvements
Add broadcast target type that fans out notifications to multiple
child targets. Dispatch expands broadcast into children in
load_link_data() — dispatcher stays unaware. Children can be
toggled on/off via disabled_child_ids in config.

Also: dashboard provider card smaller font for names, scroll-to-form
on target edit, broadcast nav tab with counter, flag_modified fix
for JSON column updates, CLAUDE.md nav tree docs.
2026-03-24 15:15:41 +03:00
alexei.dolgolyov 8cb836e16c refactor: provider descriptor registry — eliminate provider-specific hardcoding
Replace all if/else chains keyed on provider type strings with a
descriptor-driven architecture. Each provider type (immich, gitea,
planka, scheduler, nut, google_photos) has a descriptor in
frontend/src/lib/providers/ that declares config fields, event
tracking fields, collection metadata, validation, and hooks.

Components now use getDescriptor(type) and render dynamically.
Dashboard provider card shows provider name + type when global
filter is active. Grid-items derived from registry.
2026-03-24 12:40:33 +03:00
alexei.dolgolyov c6bb2b5b51 fix: provider-aware collection count labels in tracker list
"1 album(s)" now shows "1 device(s)" for NUT, "1 repo(s)" for Gitea,
"1 board(s)" for Planka instead of hardcoded album label.
2026-03-24 11:48:26 +03:00
alexei.dolgolyov 2cc4bf699a fix: NUT template preview + tracking config event checkboxes
- Add NUT variables to _SAMPLE_CONTEXT (fixes ups_name undefined in preview)
- Add NUT event tracking checkboxes to tracking config form
- Add NUT event i18n keys (EN + RU)
2026-03-24 00:09:11 +03:00
alexei.dolgolyov 68ac13b452 feat: NUT (Network UPS Tools) service provider + provider-agnostic UI
Add full NUT support as a polling-based service provider:
- Async TCP client for upsd protocol (port 3493, configurable)
- 8 event types: online, on_battery, low_battery, battery_restored,
  comms_lost, comms_restored, replace_battery, overload
- 3 bot commands: /status, /devices, /battery
- 38 Jinja2 templates (EN+RU notification + command templates)
- Database: tracking config fields, migration, seeds
- Frontend: provider form with host/port/credentials, grid items, i18n

Provider-agnostic UI improvements:
- Remove hardcoded 'immich' defaults from all config forms
- Dynamic collection labels per provider type (Albums/Repos/Boards/UPS Devices)
- Capability-driven test types instead of provider type checks
- Template variable helpers for all providers (was Immich-only)
- Guard Immich-only shared link check to Immich providers
- Auto-clear stale global provider filter from localStorage
- EntitySelect search placeholder shows current selection
- Fix noneLabel in linked target config selectors

New CLAUDE.md rule #8: no provider-specific hardcoding
2026-03-23 23:23:58 +03:00
alexei.dolgolyov c451f3dd72 feat: filter entity selectors by global provider filter
Provider selectors in notification tracker, command tracker, and actions
forms now only show providers matching the global provider type filter.
Command config selector in command trackers also filters by provider type.
2026-03-23 21:54:13 +03:00
alexei.dolgolyov 0702ec72af fix: dashboard provider card shows filtered count, fix provider update 400
- Dashboard providers card now shows count of providers matching the
  global provider type filter instead of special name/type display
- Fix provider update sending empty config when only name/icon changed,
  causing 400 validation error (api_key required)
2026-03-23 21:30:25 +03:00
alexei.dolgolyov 4049efe186 fix: UI polish — overflow, placeholders, dashboard provider card
- Fix bot card header overflow by replacing "Sync with Telegram" text
  button with icon button, add flex-wrap
- Rename sync button label to "Sync Commands"
- Remove decorative dashes from selector placeholders (— X — → X)
- Show selected provider name/icon in dashboard stat card when global
  provider filter is active
- Add selector placeholder convention to frontend-architecture.md
2026-03-23 21:26:49 +03:00
alexei.dolgolyov 1cfa72888c feat: Receiver OOP hierarchy with per-receiver locale resolution
- Introduce Receiver base class + typed subclasses (TelegramReceiver,
  WebhookReceiver, EmailReceiver, etc.) in core/notifications/receiver.py
- Dispatcher uses typed Receiver objects instead of raw dicts, with
  per-receiver locale-aware template rendering
- load_link_data resolves locale from TelegramChat.language_override at
  load time: TargetReceiver.locale || chat.language_override || chat.language_code
- Add language_override field to TelegramChat (separate from auto-detected
  language_code), with per-chat commands toggle and command dispatch using
  override language
- Add locale field to TargetReceiver for explicit per-receiver overrides
2026-03-23 21:20:31 +03:00
alexei.dolgolyov b3b6c31c4d feat: per-chat command toggle, listener name + toggle in bot tab
- Add commands_enabled field to TelegramChat (default off) with
  migration, gating command dispatch in both poller and webhook
- Show toggle switch per chat in bot tab for enabling/disabling commands
- Fix listener response to include bot name instead of just type
- Replace listener "Enabled" label + "Edit" link with toggle switch
  and crosslink to command-trackers page
2026-03-23 19:23:37 +03:00
alexei.dolgolyov 37388c430c feat: locale-aware notification templates + UX improvements
- Add locale support to notification templates (matching command template
  pattern): TemplateSlot now has locale field with (config_id, slot_name,
  locale) uniqueness, nested API format {slot: {locale: template}}
- Migration merges separate EN/RU system configs into unified per-provider
  configs; seeds create one config per provider with multi-locale slots
- Locale-aware dispatch with EN fallback in NotificationDispatcher
- Frontend locale tabs (EN/RU) on template config editor
- Fix tracking config cards not showing default provider icons
- Global provider filter, search palette, and various UX polish
2026-03-23 19:08:48 +03:00
alexei.dolgolyov 6a559bfcd2 feat: Actions system — scheduled mutations on external services
Full-stack implementation of provider-scoped Actions with extensible
executor architecture. First action type: Immich auto_organize (sort
assets into albums by person, CLIP search, date range, favorites).

Core:
- ActionTypeDefinition registry + ActionExecutor ABC with execute/validate/dry-run
- ImmichActionExecutor with multi-album support and client-side filtering
- ImmichClient write methods: add/remove assets, create album, paginated search

Server:
- Action, ActionRule, ActionExecution DB models
- Full CRUD API + manual execute + dry-run + execution history endpoints
- APScheduler integration (interval + cron) for automated execution
- Action type discovery API + provider people endpoint

Frontend:
- Actions page with CRUD, execute/dry-run buttons, inline rule editor
- RuleEditor: person/album MultiEntitySelect pickers, criteria config
- ExecutionHistory: expandable per-rule result details
- MultiEntitySelect reusable component (searchable multi-pick palette)
- Notification tracker album picker migrated to MultiEntitySelect
- Fixed MdiIcon race condition (icons missing after cache-clearing reload)
2026-03-23 16:59:20 +03:00
alexei.dolgolyov 0fde3c6b3d feat: add Planka service provider with full notification and command support
Webhook-based provider for Planka (self-hosted Kanban board) with:
- 15 event types (cards, boards, lists, comments, tasks, attachments, labels)
- Bearer token webhook authentication
- Async API client for boards/cards/lists
- 30 notification templates (en/ru) + 26 command templates (en/ru)
- Bot commands: /status, /boards, /cards, /lists
- Default tracking config, template config, command config seeded on startup
- DB migration for 15 new tracking_config columns
- Frontend: provider config UI with auto-name, Planka-specific hints
- Frontend: tracking config event toggles for all 15 Planka events
2026-03-23 15:54:00 +03:00
alexei.dolgolyov 39bac828fd feat: smart video size warnings + Jinja2 template autocomplete
Video size warnings:
- Add file_size field to ImmichAssetInfo from exifInfo.fileSizeInByte
- Expose per-target max_video_size (50 MB for Telegram, none for others)
- Compute has_oversized_videos and per-asset oversized flag in template context
- Default templates show warning only when videos actually exceed the limit
- Templates no longer hardcode Telegram-specific logic

Template autocomplete:
- New jinja-autocomplete.ts engine with contextual completions
- Top-level variables ({{ }}), asset/album fields (dot access in loops),
  Jinja2 filters (|), block tags ({% %}), and loop.* special vars
- JinjaEditor accepts optional variables prop via CodeMirror Compartment
- Wired into template-configs and command-template-configs pages

Also: fix template emoji (📷📎) and sync sample_context with new vars.
2026-03-23 15:03:35 +03:00
alexei.dolgolyov e0bae394ee feat: comprehensive code review fixes — security, performance, quality
Backend security:
- Reject Gitea webhooks when webhook_secret is empty (was silently skipping)
- Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints
- Add CORS middleware with configurable origins
- Mask telegram_webhook_secret in settings API response
- Protect system-owned command template configs from regular user modification
- Increase minimum password length to 8 characters

Backend performance:
- Batch queries in _resolve_command_context (3 queries instead of 3N)
- Concurrent album fetching with asyncio.gather in immich commands
- Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation)
- TTLCache for rate limits (bounded memory, auto-eviction)
- Optional aiohttp session reuse in send_reply/send_media_group

Backend code quality:
- Extract dispatch_helpers.py (shared link_data loading + event filtering)
- Extract database/seeds.py from main.py (490 lines → dedicated module)
- Split immich_handler.py (415 lines) into commands/immich/ subpackage
- Replace bare except blocks with logged warnings
- Add per-provider config validation (Pydantic models)
- Truncate command input to 512 chars
- Expose usage_* and desc_* slots in capabilities and variables API

Frontend security:
- CSS.escape() for user-controlled querySelector in highlight.ts
- Client-side password min 8 chars validation on setup and password change

Frontend code quality:
- Replace any types with proper interfaces across top files
- Decompose targets/+page.svelte into TargetForm + ReceiverSection
- Fix $derived.by usage, $state mutation patterns
- Add console.warn to empty catch blocks

Frontend UX:
- Auth redirect via goto() with "Redirecting..." state
- Platform-aware Ctrl/Cmd K keyboard hint
- Remove stat-card hover transform

Frontend accessibility:
- Modal: role=dialog, aria-modal, focus trap, restore focus
- EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
2026-03-23 01:59:51 +03:00
alexei.dolgolyov 31584c5d31 feat: consistent IconGridSelect sizing + descriptions + filter upgrades
- Added desc text to all 40+ grid items (EN + RU)
- compact prop on all IconGridSelect in compact form sections
- Fixed compact width to fill grid cells (removed width:auto)
- Replaced <select> filter dropdowns with IconGridSelect on config pages
- Replaced <select> provider filters with EntitySelect on tracker pages
- Dashboard filters constrained to fixed widths (not full row)
- Added filtering to command-template-configs and providers pages
- providerTypeFilterItems() with "All" option for filter contexts
2026-03-23 01:05:59 +03:00
alexei.dolgolyov 82e400ddcd feat: chat language display, disabled EntitySelect items, dev scripts
Chat language:
- Added language_code field to TelegramChat model + migration
- Saved from message.from.language_code on webhook/polling
- Displayed as badge on bot chat cards and target receiver items
- Resolved from DB in target API response (works for existing receivers)
- Shown in chat picker dropdown (desc includes language)

EntitySelect improvements:
- Tracker-target link selector shows all targets, already-linked ones
  appear disabled with "Already linked" hint
- Receiver chat picker shows already-added chats as disabled

Dev scripts:
- scripts/restart-backend.sh and restart-frontend.sh
- Updated .claude/docs/dev-servers.md to reference scripts
2026-03-22 23:39:52 +03:00
alexei.dolgolyov d8a1af0c9e fix: remove all transform from stagger/fade animations
Any transform (even transform:none) in animation keyframes with
fill-mode creates a containing block that traps position:fixed
overlays. Removed transform entirely — fade-in only with opacity.
2026-03-22 20:55:19 +03:00
alexei.dolgolyov f9a4ccf725 fix: stagger animation breaking position:fixed overlays
The fadeSlideIn animation used transform:translateY which creates a
new containing block, trapping position:fixed children (EntitySelect).
Switched to the CSS translate property which doesn't create a
containing block.
2026-03-22 20:49:33 +03:00
alexei.dolgolyov bd254de7a9 fix: remove Card hover transform that breaks fixed-position overlays
The translateY(-2px) transform on Card hover created a new containing
block, trapping position:fixed EntitySelect overlays inside the Card
instead of rendering relative to the viewport.
2026-03-22 20:46:56 +03:00
alexei.dolgolyov c26b71db85 fix: clipboard copy fallback for non-HTTPS contexts
navigator.clipboard.writeText is undefined on HTTP. Added textarea
fallback using execCommand('copy').
2026-03-22 20:24:17 +03:00
alexei.dolgolyov 7cbba9d3fd feat: add filtering to all entity list pages
- Tracking configs: filter by name + provider type
- Template configs: filter by name + provider type
- Command configs: filter by name + provider type
- Notification trackers: filter by name + provider
- Command trackers: filter by name + provider
- Targets: filter by name (type filtering already existed)
- Nav badge counts include system-owned entities (user_id=0)
- Shows "no items match filter" vs "no items yet" empty states
2026-03-22 20:22:53 +03:00
alexei.dolgolyov 63437c1841 refactor: provider-agnostic bot command system + Gitea commands
Refactored the monolithic command handler (707 lines) into a pluggable
provider-handler architecture:

- Abstract ProviderCommandHandler interface (base.py)
- Handler dispatch registry routes commands by provider type
- Extracted all Immich logic into ImmichCommandHandler
- New GiteaCommandHandler with /status, /repos, /issues, /prs, /commits
- Multi-provider routing: groups context by provider type, finds handler
- handler.py reduced to ~280 line thin orchestrator

Gitea commands:
- Extended GiteaClient with get_repo_issues, get_repo_pulls, get_repo_commits
- 30 Jinja2 command templates (15 EN + 15 RU)
- Gitea capabilities updated with 6 commands + 15 command_slots
- Default command config + command template config seeded on startup
- Rate limiting: Gitea API commands share "api" category (15s cooldown)

Also:
- Command configs API accepts "gitea" provider type
- System command configs (user_id=0) visible to all users
- Webhook URL shown on Gitea provider card and edit form
- Scan interval hidden for webhook-based providers
2026-03-22 17:44:47 +03:00
alexei.dolgolyov 0562f78b35 feat: add Scheduler provider + multi-provider UX fixes
Scheduler provider:
- Virtual provider (no external service) that emits SCHEDULED_MESSAGE
  events on user-defined intervals or cron expressions
- Custom variables stored in tracker filters, flattened into template context
- fire_count persists across triggers via tracker state
- APScheduler CronTrigger support for cron-mode schedules
- Default templates (EN+RU), seeded on startup

Multi-provider UX fixes:
- Tracking config hides Immich-specific sections (periodic, scheduled,
  memory, asset display) for non-Immich providers
- Command config driven by provider capabilities — hides commands/settings
  for providers without bot commands
- Template config hides empty "Scheduled Messages" group
- Test menu on tracker targets is provider-aware (Immich shows all 4 test
  types, others show only basic)
- Removed redundant Test button from tracker card
- System-owned tracking configs (user_id=0) seeded for Gitea + Scheduler
- Fixed ownership checks to allow system configs in tracker-target links
- Capabilities cache shared across template-configs and command-configs
- Command tracker bot selector uses EntitySelect instead of raw select
- Sample context includes Gitea + Scheduler variables for template preview
2026-03-22 15:50:51 +03:00
alexei.dolgolyov 6d28cfb8d8 feat: add Gitea as webhook-based service provider
First webhook-based provider integration (Immich uses polling).
Gitea pushes events via POST /api/webhooks/gitea/{provider_id} with
HMAC-SHA256 signature validation.

- 9 event types: push, issue opened/closed/commented, PR opened/closed/merged/commented, release published
- Generic filters system on NotificationTracker (collections, senders, exclude_senders)
- Provider capabilities include supported_filters and webhook_based flag
- Gitea API client for connection testing and repository listing
- 18 default Jinja2 notification templates (EN + RU)
- Frontend: conditional provider forms, Gitea event toggles in tracking config
- Auto-migration for filters column and Gitea tracking flags
2026-03-22 12:58:35 +03:00
alexei.dolgolyov 1167d138a3 feat: locale-aware command templates, debounced auto-sync, entity pickers
- Locale-aware templates: CommandTemplateSlot now has a locale column,
  allowing each slot to have per-language variants (EN/RU). Templates
  are resolved at runtime from the Telegram user's language_code.

- Merged system configs: "Default Commands (EN)" and "(RU)" merged
  into a single "Default Commands" config with locale-aware slots.
  Migration handles existing data automatically.

- Configurable command descriptions: hardcoded COMMAND_DESCRIPTIONS
  replaced with desc_* template slots (desc_status, desc_help, etc.)
  that users can customize per locale. setMyCommands registers all
  locales explicitly.

- Removed locale from CommandConfig: no longer needed since locale
  is derived from the Telegram user's language at runtime.

- Debounced command auto-sync: after command config/tracker changes,
  affected bots are marked dirty and synced after a 30s debounce
  window. Manual "Sync with Telegram" button still works.

- Entity pickers in LinkedTargetsSection: replaced 6 plain <select>
  elements with EntitySelect components (search, icons, keyboard nav).
  Added onselect callback and size="sm" props to EntitySelect.
2026-03-22 03:14:51 +03:00
alexei.dolgolyov 751097b347 feat: comprehensive code review fixes + receivers-only architecture
Security:
- Refuse startup with default secret_key in production (was just logging)
- Settings endpoint now requires admin role
- Password validation on initial setup
- DOM-based HTML sanitizer replaces regex in template previews
- Add *.log to .gitignore

Performance & reliability:
- Token refresh deduplication prevents race condition on concurrent 401s
- Theme media query listener registered once (no leak)
- IconPicker uses $derived instead of function call per render
- Snackbar uses single-batch state update instead of while loop
- Replace 11 inline hover handlers with CSS :hover in layout

Architecture - receivers-only:
- Delivery endpoints (chat_id, email, url, room_id, topic) now stored
  exclusively in TargetReceiver rows, never in target.config
- Migration extracts existing delivery fields to receiver rows
- Notifier and dispatcher remove all config fallbacks
- Frontend targets page shows receivers list per target with
  add/remove/toggle/test per receiver
- Single-receiver test endpoint: POST /targets/{id}/receivers/{id}/test

Code quality:
- Extract AuthLayout.svelte from login/setup (150 lines CSS dedup)
- Split telegram-bots page (754→51 lines + 3 tab components)
- Split notification-trackers page (547→432 lines + 4 components)
- Deduplicate _send_reply into shared handler.send_reply()
- Add locale column to template models, replace name-based detection
- Fix delete_notification_tracker dead protection check
- Fix check_telegram_bot query (filter by type, remove bogus OR)
- Add graceful scheduler shutdown in lifespan
- Consistent /bots?tab=telegram URLs across all nav links

i18n:
- Error page, chat actions, target types, provider types internationalized
- All new receiver UI strings in EN + RU
2026-03-22 02:19:31 +03:00