From 38b2de54fff9669d2c1f7f5311152eb75a740471 Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 24 Jun 2026 13:28:23 +0000 Subject: [PATCH] chore: archive observability-service-registry, track pi-map artifacts - Archive the completed observability-service-registry SDD change into openspec/changes/archive/ (delivered across 5 slices; only jellyfin-service-registry remains active). - Stop ignoring .pi-map.md / .pi-map.index.md so the navigation maps are versioned alongside the code, and add the regenerated map pairs repo-wide. --- .claude/.pi-map.index.md | 20 +++++ .claude/.pi-map.md | 18 +++++ .claude/skills/.pi-map.index.md | 20 +++++ .claude/skills/.pi-map.md | 18 +++++ .claude/skills/sift-backlog/.pi-map.index.md | 19 +++++ .claude/skills/sift-backlog/.pi-map.md | 19 +++++ .gitignore | 2 - .opencode/.pi-map.index.md | 23 ++++++ .opencode/.pi-map.md | 18 +++++ .opencode/commands/.pi-map.index.md | 22 ++++++ .opencode/commands/.pi-map.md | 22 ++++++ .opencode/skills/.pi-map.index.md | 29 +++++++ .opencode/skills/.pi-map.md | 18 +++++ .../openspec-apply-change/.pi-map.index.md | 19 +++++ .../skills/openspec-apply-change/.pi-map.md | 19 +++++ .../openspec-archive-change/.pi-map.index.md | 19 +++++ .../skills/openspec-archive-change/.pi-map.md | 19 +++++ .../skills/openspec-explore/.pi-map.index.md | 19 +++++ .opencode/skills/openspec-explore/.pi-map.md | 19 +++++ .../skills/openspec-propose/.pi-map.index.md | 19 +++++ .opencode/skills/openspec-propose/.pi-map.md | 19 +++++ .pi-map.index.md | 76 +++++++++++++++++++ .pi-map.md | 45 +++++++++++ archive/.pi-map.index.md | 31 ++++++++ archive/.pi-map.md | 26 +++++++ archive/src/.pi-map.index.md | 20 +++++ archive/src/.pi-map.md | 18 +++++ .../src/media_library_viewer/.pi-map.index.md | 39 ++++++++++ archive/src/media_library_viewer/.pi-map.md | 35 +++++++++ .../clients/.pi-map.index.md | 23 ++++++ .../media_library_viewer/clients/.pi-map.md | 30 ++++++++ .../domain/.pi-map.index.md | 21 +++++ .../media_library_viewer/domain/.pi-map.md | 28 +++++++ .../services/.pi-map.index.md | 21 +++++ .../media_library_viewer/services/.pi-map.md | 28 +++++++ .../media_library_viewer/ui/.pi-map.index.md | 24 ++++++ .../src/media_library_viewer/ui/.pi-map.md | 31 ++++++++ archive/tests/.pi-map.index.md | 19 +++++ archive/tests/.pi-map.md | 19 +++++ backend/.pi-map.index.md | 32 ++++++++ backend/.pi-map.md | 21 +++++ backend/src/.pi-map.index.md | 20 +++++ backend/src/.pi-map.md | 18 +++++ .../media_library_viewer_api/.pi-map.index.md | 57 ++++++++++++++ .../src/media_library_viewer_api/.pi-map.md | 41 ++++++++++ .../clients/.pi-map.index.md | 24 ++++++ .../clients/.pi-map.md | 31 ++++++++ .../domain/.pi-map.index.md | 22 ++++++ .../domain/.pi-map.md | 29 +++++++ .../integrations/.pi-map.index.md | 29 +++++++ .../integrations/.pi-map.md | 36 +++++++++ .../models/.pi-map.index.md | 22 ++++++ .../models/.pi-map.md | 29 +++++++ .../routers/.pi-map.index.md | 32 ++++++++ .../routers/.pi-map.md | 39 ++++++++++ .../services/.pi-map.index.md | 33 ++++++++ .../services/.pi-map.md | 40 ++++++++++ .../widgets/.pi-map.index.md | 22 ++++++ .../widgets/.pi-map.md | 29 +++++++ .../workers/.pi-map.index.md | 21 +++++ .../workers/.pi-map.md | 28 +++++++ backend/tests/.pi-map.index.md | 39 ++++++++++ backend/tests/.pi-map.md | 46 +++++++++++ docs/.pi-map.index.md | 24 ++++++ docs/.pi-map.md | 22 ++++++ docs/superpowers/.pi-map.index.md | 23 ++++++ docs/superpowers/.pi-map.md | 18 +++++ docs/superpowers/plans/.pi-map.index.md | 19 +++++ docs/superpowers/plans/.pi-map.md | 19 +++++ docs/superpowers/specs/.pi-map.index.md | 20 +++++ docs/superpowers/specs/.pi-map.md | 20 +++++ frontend/.pi-map.index.md | 47 ++++++++++++ frontend/.pi-map.md | 39 ++++++++++ frontend/public/.pi-map.index.md | 20 +++++ frontend/public/.pi-map.md | 20 +++++ frontend/src/.pi-map.index.md | 56 ++++++++++++++ frontend/src/.pi-map.md | 37 +++++++++ frontend/src/api/.pi-map.index.md | 25 ++++++ frontend/src/api/.pi-map.md | 32 ++++++++ frontend/src/components/.pi-map.index.md | 44 +++++++++++ frontend/src/components/.pi-map.md | 46 +++++++++++ .../src/components/__tests__/.pi-map.index.md | 32 ++++++++ frontend/src/components/__tests__/.pi-map.md | 32 ++++++++ frontend/src/components/ui/.pi-map.index.md | 46 +++++++++++ frontend/src/components/ui/.pi-map.md | 51 +++++++++++++ .../components/ui/__tests__/.pi-map.index.md | 21 +++++ .../src/components/ui/__tests__/.pi-map.md | 21 +++++ frontend/src/hooks/.pi-map.index.md | 31 ++++++++ frontend/src/hooks/.pi-map.md | 38 ++++++++++ frontend/src/integrations/.pi-map.index.md | 23 ++++++ frontend/src/integrations/.pi-map.md | 30 ++++++++ frontend/src/lib/.pi-map.index.md | 20 +++++ frontend/src/lib/.pi-map.md | 22 ++++++ frontend/src/pages/.pi-map.index.md | 34 +++++++++ frontend/src/pages/.pi-map.md | 39 ++++++++++ frontend/src/pages/__tests__/.pi-map.index.md | 26 +++++++ frontend/src/pages/__tests__/.pi-map.md | 26 +++++++ frontend/src/test/.pi-map.index.md | 20 +++++ frontend/src/test/.pi-map.md | 23 ++++++ frontend/src/types/.pi-map.index.md | 21 +++++ frontend/src/types/.pi-map.md | 28 +++++++ frontend/src/widgets/.pi-map.index.md | 27 +++++++ frontend/src/widgets/.pi-map.md | 34 +++++++++ frontend/tests/.pi-map.index.md | 20 +++++ frontend/tests/.pi-map.md | 20 +++++ monitoring/.pi-map.index.md | 32 ++++++++ monitoring/.pi-map.md | 18 +++++ monitoring/alertmanager/.pi-map.index.md | 19 +++++ monitoring/alertmanager/.pi-map.md | 19 +++++ monitoring/alloy/.pi-map.index.md | 19 +++++ monitoring/alloy/.pi-map.md | 19 +++++ monitoring/grafana/.pi-map.index.md | 21 +++++ monitoring/grafana/.pi-map.md | 19 +++++ .../grafana/provisioning/.pi-map.index.md | 23 ++++++ monitoring/grafana/provisioning/.pi-map.md | 18 +++++ .../dashboards-json/.pi-map.index.md | 21 +++++ .../provisioning/dashboards-json/.pi-map.md | 19 +++++ .../dashboards/.pi-map.index.md | 20 +++++ .../dashboards-json/dashboards/.pi-map.md | 20 +++++ .../provisioning/datasources/.pi-map.index.md | 19 +++++ .../provisioning/datasources/.pi-map.md | 19 +++++ monitoring/loki/.pi-map.index.md | 19 +++++ monitoring/loki/.pi-map.md | 19 +++++ monitoring/prometheus/.pi-map.index.md | 24 ++++++ monitoring/prometheus/.pi-map.md | 19 +++++ .../prometheus/file-sd/.pi-map.index.md | 19 +++++ monitoring/prometheus/file-sd/.pi-map.md | 19 +++++ monitoring/prometheus/rules/.pi-map.index.md | 19 +++++ monitoring/prometheus/rules/.pi-map.md | 19 +++++ openspec/.pi-map.index.md | 24 ++++++ openspec/.pi-map.md | 19 +++++ openspec/changes/.pi-map.index.md | 23 ++++++ openspec/changes/.pi-map.md | 18 +++++ openspec/changes/archive/.pi-map.index.md | 35 +++++++++ openspec/changes/archive/.pi-map.md | 18 +++++ .../2026-06-17-web-ui-rework/.pi-map.index.md | 29 +++++++ .../2026-06-17-web-ui-rework/.pi-map.md | 27 +++++++ .../specs/.pi-map.index.md | 20 +++++ .../2026-06-17-web-ui-rework/specs/.pi-map.md | 18 +++++ .../specs/web-ui/.pi-map.index.md | 19 +++++ .../specs/web-ui/.pi-map.md | 19 +++++ .../.pi-map.index.md | 25 ++++++ .../configurable-dashboard-widgets/.pi-map.md | 23 ++++++ .../specs/.pi-map.index.md | 20 +++++ .../specs/.pi-map.md | 18 +++++ .../specs/dashboard-widgets/.pi-map.index.md | 19 +++++ .../specs/dashboard-widgets/.pi-map.md | 19 +++++ .../.pi-map.index.md | 19 +++++ .../decommission-monitoring-poller/.pi-map.md | 19 +++++ .../.pi-map.index.md | 21 +++++ .../observability-service-registry/.pi-map.md | 21 +++++ .../observability-service-registry/design.md | 0 .../proposal.md | 0 .../observability-service-registry/tasks.md | 0 .../archive/service-registry/.pi-map.index.md | 22 ++++++ .../archive/service-registry/.pi-map.md | 22 ++++++ .../unify-tasks-on-services/.pi-map.index.md | 21 +++++ .../unify-tasks-on-services/.pi-map.md | 21 +++++ .../.pi-map.index.md | 21 +++++ .../jellyfin-service-registry/.pi-map.md | 21 +++++ openspec/specs/.pi-map.index.md | 20 +++++ openspec/specs/.pi-map.md | 18 +++++ openspec/specs/web-ui/.pi-map.index.md | 19 +++++ openspec/specs/web-ui/.pi-map.md | 19 +++++ 164 files changed, 4047 insertions(+), 2 deletions(-) create mode 100644 .claude/.pi-map.index.md create mode 100644 .claude/.pi-map.md create mode 100644 .claude/skills/.pi-map.index.md create mode 100644 .claude/skills/.pi-map.md create mode 100644 .claude/skills/sift-backlog/.pi-map.index.md create mode 100644 .claude/skills/sift-backlog/.pi-map.md create mode 100644 .opencode/.pi-map.index.md create mode 100644 .opencode/.pi-map.md create mode 100644 .opencode/commands/.pi-map.index.md create mode 100644 .opencode/commands/.pi-map.md create mode 100644 .opencode/skills/.pi-map.index.md create mode 100644 .opencode/skills/.pi-map.md create mode 100644 .opencode/skills/openspec-apply-change/.pi-map.index.md create mode 100644 .opencode/skills/openspec-apply-change/.pi-map.md create mode 100644 .opencode/skills/openspec-archive-change/.pi-map.index.md create mode 100644 .opencode/skills/openspec-archive-change/.pi-map.md create mode 100644 .opencode/skills/openspec-explore/.pi-map.index.md create mode 100644 .opencode/skills/openspec-explore/.pi-map.md create mode 100644 .opencode/skills/openspec-propose/.pi-map.index.md create mode 100644 .opencode/skills/openspec-propose/.pi-map.md create mode 100644 .pi-map.index.md create mode 100644 .pi-map.md create mode 100644 archive/.pi-map.index.md create mode 100644 archive/.pi-map.md create mode 100644 archive/src/.pi-map.index.md create mode 100644 archive/src/.pi-map.md create mode 100644 archive/src/media_library_viewer/.pi-map.index.md create mode 100644 archive/src/media_library_viewer/.pi-map.md create mode 100644 archive/src/media_library_viewer/clients/.pi-map.index.md create mode 100644 archive/src/media_library_viewer/clients/.pi-map.md create mode 100644 archive/src/media_library_viewer/domain/.pi-map.index.md create mode 100644 archive/src/media_library_viewer/domain/.pi-map.md create mode 100644 archive/src/media_library_viewer/services/.pi-map.index.md create mode 100644 archive/src/media_library_viewer/services/.pi-map.md create mode 100644 archive/src/media_library_viewer/ui/.pi-map.index.md create mode 100644 archive/src/media_library_viewer/ui/.pi-map.md create mode 100644 archive/tests/.pi-map.index.md create mode 100644 archive/tests/.pi-map.md create mode 100644 backend/.pi-map.index.md create mode 100644 backend/.pi-map.md create mode 100644 backend/src/.pi-map.index.md create mode 100644 backend/src/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/clients/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/clients/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/domain/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/domain/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/integrations/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/integrations/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/models/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/models/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/routers/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/routers/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/services/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/services/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/widgets/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/widgets/.pi-map.md create mode 100644 backend/src/media_library_viewer_api/workers/.pi-map.index.md create mode 100644 backend/src/media_library_viewer_api/workers/.pi-map.md create mode 100644 backend/tests/.pi-map.index.md create mode 100644 backend/tests/.pi-map.md create mode 100644 docs/.pi-map.index.md create mode 100644 docs/.pi-map.md create mode 100644 docs/superpowers/.pi-map.index.md create mode 100644 docs/superpowers/.pi-map.md create mode 100644 docs/superpowers/plans/.pi-map.index.md create mode 100644 docs/superpowers/plans/.pi-map.md create mode 100644 docs/superpowers/specs/.pi-map.index.md create mode 100644 docs/superpowers/specs/.pi-map.md create mode 100644 frontend/.pi-map.index.md create mode 100644 frontend/.pi-map.md create mode 100644 frontend/public/.pi-map.index.md create mode 100644 frontend/public/.pi-map.md create mode 100644 frontend/src/.pi-map.index.md create mode 100644 frontend/src/.pi-map.md create mode 100644 frontend/src/api/.pi-map.index.md create mode 100644 frontend/src/api/.pi-map.md create mode 100644 frontend/src/components/.pi-map.index.md create mode 100644 frontend/src/components/.pi-map.md create mode 100644 frontend/src/components/__tests__/.pi-map.index.md create mode 100644 frontend/src/components/__tests__/.pi-map.md create mode 100644 frontend/src/components/ui/.pi-map.index.md create mode 100644 frontend/src/components/ui/.pi-map.md create mode 100644 frontend/src/components/ui/__tests__/.pi-map.index.md create mode 100644 frontend/src/components/ui/__tests__/.pi-map.md create mode 100644 frontend/src/hooks/.pi-map.index.md create mode 100644 frontend/src/hooks/.pi-map.md create mode 100644 frontend/src/integrations/.pi-map.index.md create mode 100644 frontend/src/integrations/.pi-map.md create mode 100644 frontend/src/lib/.pi-map.index.md create mode 100644 frontend/src/lib/.pi-map.md create mode 100644 frontend/src/pages/.pi-map.index.md create mode 100644 frontend/src/pages/.pi-map.md create mode 100644 frontend/src/pages/__tests__/.pi-map.index.md create mode 100644 frontend/src/pages/__tests__/.pi-map.md create mode 100644 frontend/src/test/.pi-map.index.md create mode 100644 frontend/src/test/.pi-map.md create mode 100644 frontend/src/types/.pi-map.index.md create mode 100644 frontend/src/types/.pi-map.md create mode 100644 frontend/src/widgets/.pi-map.index.md create mode 100644 frontend/src/widgets/.pi-map.md create mode 100644 frontend/tests/.pi-map.index.md create mode 100644 frontend/tests/.pi-map.md create mode 100644 monitoring/.pi-map.index.md create mode 100644 monitoring/.pi-map.md create mode 100644 monitoring/alertmanager/.pi-map.index.md create mode 100644 monitoring/alertmanager/.pi-map.md create mode 100644 monitoring/alloy/.pi-map.index.md create mode 100644 monitoring/alloy/.pi-map.md create mode 100644 monitoring/grafana/.pi-map.index.md create mode 100644 monitoring/grafana/.pi-map.md create mode 100644 monitoring/grafana/provisioning/.pi-map.index.md create mode 100644 monitoring/grafana/provisioning/.pi-map.md create mode 100644 monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md create mode 100644 monitoring/grafana/provisioning/dashboards-json/.pi-map.md create mode 100644 monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md create mode 100644 monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md create mode 100644 monitoring/grafana/provisioning/datasources/.pi-map.index.md create mode 100644 monitoring/grafana/provisioning/datasources/.pi-map.md create mode 100644 monitoring/loki/.pi-map.index.md create mode 100644 monitoring/loki/.pi-map.md create mode 100644 monitoring/prometheus/.pi-map.index.md create mode 100644 monitoring/prometheus/.pi-map.md create mode 100644 monitoring/prometheus/file-sd/.pi-map.index.md create mode 100644 monitoring/prometheus/file-sd/.pi-map.md create mode 100644 monitoring/prometheus/rules/.pi-map.index.md create mode 100644 monitoring/prometheus/rules/.pi-map.md create mode 100644 openspec/.pi-map.index.md create mode 100644 openspec/.pi-map.md create mode 100644 openspec/changes/.pi-map.index.md create mode 100644 openspec/changes/.pi-map.md create mode 100644 openspec/changes/archive/.pi-map.index.md create mode 100644 openspec/changes/archive/.pi-map.md create mode 100644 openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md create mode 100644 openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md create mode 100644 openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md create mode 100644 openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md create mode 100644 openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md create mode 100644 openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.md create mode 100644 openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md create mode 100644 openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md create mode 100644 openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md create mode 100644 openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md create mode 100644 openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md create mode 100644 openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.md create mode 100644 openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md create mode 100644 openspec/changes/archive/decommission-monitoring-poller/.pi-map.md create mode 100644 openspec/changes/archive/observability-service-registry/.pi-map.index.md create mode 100644 openspec/changes/archive/observability-service-registry/.pi-map.md rename openspec/changes/{ => archive}/observability-service-registry/design.md (100%) rename openspec/changes/{ => archive}/observability-service-registry/proposal.md (100%) rename openspec/changes/{ => archive}/observability-service-registry/tasks.md (100%) create mode 100644 openspec/changes/archive/service-registry/.pi-map.index.md create mode 100644 openspec/changes/archive/service-registry/.pi-map.md create mode 100644 openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md create mode 100644 openspec/changes/archive/unify-tasks-on-services/.pi-map.md create mode 100644 openspec/changes/jellyfin-service-registry/.pi-map.index.md create mode 100644 openspec/changes/jellyfin-service-registry/.pi-map.md create mode 100644 openspec/specs/.pi-map.index.md create mode 100644 openspec/specs/.pi-map.md create mode 100644 openspec/specs/web-ui/.pi-map.index.md create mode 100644 openspec/specs/web-ui/.pi-map.md diff --git a/.claude/.pi-map.index.md b/.claude/.pi-map.index.md new file mode 100644 index 0000000..7243ac7 --- /dev/null +++ b/.claude/.pi-map.index.md @@ -0,0 +1,20 @@ +# .claude (index) +dir: .claude + +## role +Configuration directory for the Claude AI assistant, storing project-specific settings, instructions, and behavioral guidelines. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- .claude/skills + index: .claude/skills/.pi-map.index.md + map: .claude/skills/.pi-map.md +## files +## links +index: .claude/.pi-map.index.md +map: .claude/.pi-map.md +## workflows +- +## dirty +- diff --git a/.claude/.pi-map.md b/.claude/.pi-map.md new file mode 100644 index 0000000..c8f0814 --- /dev/null +++ b/.claude/.pi-map.md @@ -0,0 +1,18 @@ +# .claude +dir: .claude + +index: .claude/.pi-map.index.md + +## role +Configuration directory for the Claude AI assistant, storing project-specific settings, instructions, and behavioral guidelines. +## files +## arch +Flat configuration structure containing markdown/YAML files that define custom commands, project context, and operational rules for Claude's interactions with the codebase. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/.claude/skills/.pi-map.index.md b/.claude/skills/.pi-map.index.md new file mode 100644 index 0000000..17d4237 --- /dev/null +++ b/.claude/skills/.pi-map.index.md @@ -0,0 +1,20 @@ +# .claude/skills (index) +dir: .claude/skills + +## role +Directory containing custom skill definitions and capability instructions for the Claude AI assistant integration. +## parent +index: .claude/.pi-map.index.md +map: .claude/.pi-map.md +## children +- .claude/skills/sift-backlog + index: .claude/skills/sift-backlog/.pi-map.index.md + map: .claude/skills/sift-backlog/.pi-map.md +## files +## links +index: .claude/skills/.pi-map.index.md +map: .claude/skills/.pi-map.md +## workflows +- +## dirty +- diff --git a/.claude/skills/.pi-map.md b/.claude/skills/.pi-map.md new file mode 100644 index 0000000..b2b2fae --- /dev/null +++ b/.claude/skills/.pi-map.md @@ -0,0 +1,18 @@ +# .claude/skills +dir: .claude/skills + +index: .claude/skills/.pi-map.index.md + +## role +Directory containing custom skill definitions and capability instructions for the Claude AI assistant integration. +## files +## arch +Flat configuration file structure defining modular skill behaviors and prompts used to extend Claude's domain-specific abilities. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/.claude/skills/sift-backlog/.pi-map.index.md b/.claude/skills/sift-backlog/.pi-map.index.md new file mode 100644 index 0000000..0cf077f --- /dev/null +++ b/.claude/skills/sift-backlog/.pi-map.index.md @@ -0,0 +1,19 @@ +# .claude/skills/sift-backlog (index) +dir: .claude/skills/sift-backlog + +## role +Defines a Claude skill workflow for triaging, organizing, and activating backlog tasks into actionable plans using the `sf` CLI tool. +## parent +index: .claude/skills/.pi-map.index.md +map: .claude/skills/.pi-map.md +## children +- +## files +- SKILL.md +## links +index: .claude/skills/sift-backlog/.pi-map.index.md +map: .claude/skills/sift-backlog/.pi-map.md +## workflows +- +## dirty +- diff --git a/.claude/skills/sift-backlog/.pi-map.md b/.claude/skills/sift-backlog/.pi-map.md new file mode 100644 index 0000000..ea55d18 --- /dev/null +++ b/.claude/skills/sift-backlog/.pi-map.md @@ -0,0 +1,19 @@ +# .claude/skills/sift-backlog +dir: .claude/skills/sift-backlog + +index: .claude/skills/sift-backlog/.pi-map.index.md + +## role +Defines a Claude skill workflow for triaging, organizing, and activating backlog tasks into actionable plans using the `sf` CLI tool. +## files +- SKILL.md | Defines a workflow skill for triaging, organizing, and activating backlog tasks into actionable plans using the `sf` CLI tool. | dep: sf CLI (task, plan, dependency, update subcommands) +## arch +Single-file declarative skill definition following a prompt-driven workflow pattern with structured triage and activation instructions for Claude to execute. +## tags +skill, defines, workflow, triaging, organizing, activating, backlog, tasks +## symbols +- +## workflows +- +## dirty +- diff --git a/.gitignore b/.gitignore index 16fbd21..bcecffd 100644 --- a/.gitignore +++ b/.gitignore @@ -55,5 +55,3 @@ frontend/dist/ .superpowers/ # Local Pi runtime state .atl/ -.pi-map.md -.pi-map.index.md diff --git a/.opencode/.pi-map.index.md b/.opencode/.pi-map.index.md new file mode 100644 index 0000000..af64611 --- /dev/null +++ b/.opencode/.pi-map.index.md @@ -0,0 +1,23 @@ +# .opencode (index) +dir: .opencode + +## role +Configuration directory for the opencode tool, managing project-specific settings and preferences. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- .opencode/commands + index: .opencode/commands/.pi-map.index.md + map: .opencode/commands/.pi-map.md +- .opencode/skills + index: .opencode/skills/.pi-map.index.md + map: .opencode/skills/.pi-map.md +## files +## links +index: .opencode/.pi-map.index.md +map: .opencode/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/.pi-map.md b/.opencode/.pi-map.md new file mode 100644 index 0000000..a7ae063 --- /dev/null +++ b/.opencode/.pi-map.md @@ -0,0 +1,18 @@ +# .opencode +dir: .opencode + +index: .opencode/.pi-map.index.md + +## role +Configuration directory for the opencode tool, managing project-specific settings and preferences. +## files +## arch +Flat directory structure containing configuration files that define opencode behavior for the associated project. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/.opencode/commands/.pi-map.index.md b/.opencode/commands/.pi-map.index.md new file mode 100644 index 0000000..1a79ea4 --- /dev/null +++ b/.opencode/commands/.pi-map.index.md @@ -0,0 +1,22 @@ +# .opencode/commands (index) +dir: .opencode/commands + +## role +Defines slash-command workflows and assistant personas for an OpenSpec-based development process (explore, propose, apply, archive). +## parent +index: .opencode/.pi-map.index.md +map: .opencode/.pi-map.md +## children +- +## files +- opsx-apply.md +- opsx-archive.md +- opsx-explore.md +- opsx-propose.md +## links +index: .opencode/commands/.pi-map.index.md +map: .opencode/commands/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/commands/.pi-map.md b/.opencode/commands/.pi-map.md new file mode 100644 index 0000000..70c69d6 --- /dev/null +++ b/.opencode/commands/.pi-map.md @@ -0,0 +1,22 @@ +# .opencode/commands +dir: .opencode/commands + +index: .opencode/commands/.pi-map.index.md + +## role +Defines slash-command workflows and assistant personas for an OpenSpec-based development process (explore, propose, apply, archive). +## files +- opsx-apply.md | Defines a workflow for implementing tasks from an OpenSpec change in a structured, iterative manner with pause points for blockers and ambiguity. | dep: openspec CLI, AskUserQuestion tool, filesystem access +- opsx-archive.md | Defines a workflow for archiving completed changes in an experimental openspec-based development process, including validation, spec sync assessment, and user confirmation steps. | dep: openspec CLI, AskUserQuestion tool, Task tool, Skill tool, filesystem (mkdir, mv), tasks.md +- opsx-explore.md | Defines the explore mode stance for a thinking/discussion assistant that investigates problems and clarifies requirements without implementing code | dep: OpenSpec system (openspec CLI, change artifacts like proposal.md/design.md/tasks.md/spec.md) +- opsx-propose.md | Defines a workflow for creating a new change with all required planning artifacts (proposal, design, tasks) in a single step using the openspec CLI tool. | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool, file system +## arch +Markdown-based declarative templates serving as structured prompts/playbooks that guide an AI assistant through specific operational phases of a spec-driven lifecycle. +## tags +opsx, defines, tasks, md, workflow, openspec, openspec cli, askuserquestion tool +## symbols +- +## workflows +- +## dirty +- diff --git a/.opencode/skills/.pi-map.index.md b/.opencode/skills/.pi-map.index.md new file mode 100644 index 0000000..f4abd43 --- /dev/null +++ b/.opencode/skills/.pi-map.index.md @@ -0,0 +1,29 @@ +# .opencode/skills (index) +dir: .opencode/skills + +## role +Directory for defining custom agent skills, capabilities, and behavioral instructions within the opencode configuration framework. +## parent +index: .opencode/.pi-map.index.md +map: .opencode/.pi-map.md +## children +- .opencode/skills/openspec-apply-change + index: .opencode/skills/openspec-apply-change/.pi-map.index.md + map: .opencode/skills/openspec-apply-change/.pi-map.md +- .opencode/skills/openspec-archive-change + index: .opencode/skills/openspec-archive-change/.pi-map.index.md + map: .opencode/skills/openspec-archive-change/.pi-map.md +- .opencode/skills/openspec-explore + index: .opencode/skills/openspec-explore/.pi-map.index.md + map: .opencode/skills/openspec-explore/.pi-map.md +- .opencode/skills/openspec-propose + index: .opencode/skills/openspec-propose/.pi-map.index.md + map: .opencode/skills/openspec-propose/.pi-map.md +## files +## links +index: .opencode/skills/.pi-map.index.md +map: .opencode/skills/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/skills/.pi-map.md b/.opencode/skills/.pi-map.md new file mode 100644 index 0000000..009338d --- /dev/null +++ b/.opencode/skills/.pi-map.md @@ -0,0 +1,18 @@ +# .opencode/skills +dir: .opencode/skills + +index: .opencode/skills/.pi-map.index.md + +## role +Directory for defining custom agent skills, capabilities, and behavioral instructions within the opencode configuration framework. +## files +## arch +Configuration-based skill definition directory; skills are declared as individual files consumed by the opencode agent runtime to extend or specialize assistant behavior. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-apply-change/.pi-map.index.md b/.opencode/skills/openspec-apply-change/.pi-map.index.md new file mode 100644 index 0000000..0048ab3 --- /dev/null +++ b/.opencode/skills/openspec-apply-change/.pi-map.index.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-apply-change (index) +dir: .opencode/skills/openspec-apply-change + +## role +Provides a structured skill definition for implementing OpenSpec changes through a schema-driven workflow with progress tracking. +## parent +index: .opencode/skills/.pi-map.index.md +map: .opencode/skills/.pi-map.md +## children +- +## files +- SKILL.md +## links +index: .opencode/skills/openspec-apply-change/.pi-map.index.md +map: .opencode/skills/openspec-apply-change/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-apply-change/.pi-map.md b/.opencode/skills/openspec-apply-change/.pi-map.md new file mode 100644 index 0000000..a06be2f --- /dev/null +++ b/.opencode/skills/openspec-apply-change/.pi-map.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-apply-change +dir: .opencode/skills/openspec-apply-change + +index: .opencode/skills/openspec-apply-change/.pi-map.index.md + +## role +Provides a structured skill definition for implementing OpenSpec changes through a schema-driven workflow with progress tracking. +## files +- SKILL.md | Defines a skill for implementing tasks from an OpenSpec change using a schema-driven workflow with progress tracking and contextual file reading. | dep: openspec CLI, AskUserQuestion tool +## arch +Documentation-based skill specification using markdown with defined workflow steps, schema references, and contextual file reading rules. +## tags +skill, defines, implementing, tasks, openspec, change, schema, driven +## symbols +- +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-archive-change/.pi-map.index.md b/.opencode/skills/openspec-archive-change/.pi-map.index.md new file mode 100644 index 0000000..6eb5e3b --- /dev/null +++ b/.opencode/skills/openspec-archive-change/.pi-map.index.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-archive-change (index) +dir: .opencode/skills/openspec-archive-change + +## role +Provides a structured skill definition for archiving completed changes in the openspec experimental workflow with validation and user confirmation steps. +## parent +index: .opencode/skills/.pi-map.index.md +map: .opencode/skills/.pi-map.md +## children +- +## files +- SKILL.md +## links +index: .opencode/skills/openspec-archive-change/.pi-map.index.md +map: .opencode/skills/openspec-archive-change/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-archive-change/.pi-map.md b/.opencode/skills/openspec-archive-change/.pi-map.md new file mode 100644 index 0000000..2899d09 --- /dev/null +++ b/.opencode/skills/openspec-archive-change/.pi-map.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-archive-change +dir: .opencode/skills/openspec-archive-change + +index: .opencode/skills/openspec-archive-change/.pi-map.index.md + +## role +Provides a structured skill definition for archiving completed changes in the openspec experimental workflow with validation and user confirmation steps. +## files +- SKILL.md | Defines a skill for archiving a completed change in the openspec experimental workflow, including validation, sync assessment, and user confirmation steps. | dep: openspec CLI, AskUserQuestion tool, Task tool (subagent_type: general-purpose), openspec-sync-specs skill +## arch +Single-document declarative skill specification following a procedural checklist pattern (validate, assess sync, confirm) designed for an AI agent to execute. +## tags +skill, openspec, sync, defines, archiving, completed, change, experimental +## symbols +- +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-explore/.pi-map.index.md b/.opencode/skills/openspec-explore/.pi-map.index.md new file mode 100644 index 0000000..ad93ef9 --- /dev/null +++ b/.opencode/skills/openspec-explore/.pi-map.index.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-explore (index) +dir: .opencode/skills/openspec-explore + +## role +Provides a conversational "explore mode" skill for the OpenSpec CLI that serves as a thinking partner for brainstorming ideas, investigating problems, and clarifying requirements. +## parent +index: .opencode/skills/.pi-map.index.md +map: .opencode/skills/.pi-map.md +## children +- +## files +- SKILL.md +## links +index: .opencode/skills/openspec-explore/.pi-map.index.md +map: .opencode/skills/openspec-explore/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-explore/.pi-map.md b/.opencode/skills/openspec-explore/.pi-map.md new file mode 100644 index 0000000..c7ae04d --- /dev/null +++ b/.opencode/skills/openspec-explore/.pi-map.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-explore +dir: .opencode/skills/openspec-explore + +index: .opencode/skills/openspec-explore/.pi-map.index.md + +## role +Provides a conversational "explore mode" skill for the OpenSpec CLI that serves as a thinking partner for brainstorming ideas, investigating problems, and clarifying requirements. +## files +- SKILL.md | Defines a conversational "explore mode" skill for the OpenSpec CLI that acts as a thinking partner for exploring ideas, investigating problems, and clarifying requirements without implementing code. | dep: openspec CLI +## arch +Skill-definition pattern using a single Markdown file (SKILL.md) that declaratively specifies the assistant's behavioral constraints, workflow, and operational guidelines. +## tags +skill, defines, conversational, explore, mode, openspec, cli, acts +## symbols +- +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-propose/.pi-map.index.md b/.opencode/skills/openspec-propose/.pi-map.index.md new file mode 100644 index 0000000..0e27001 --- /dev/null +++ b/.opencode/skills/openspec-propose/.pi-map.index.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-propose (index) +dir: .opencode/skills/openspec-propose + +## role +Provides an AI assistant skill that automates the openspec proposal workflow by scaffolding directories and generating structured artifacts (proposals, designs, tasks). +## parent +index: .opencode/skills/.pi-map.index.md +map: .opencode/skills/.pi-map.md +## children +- +## files +- SKILL.md +## links +index: .opencode/skills/openspec-propose/.pi-map.index.md +map: .opencode/skills/openspec-propose/.pi-map.md +## workflows +- +## dirty +- diff --git a/.opencode/skills/openspec-propose/.pi-map.md b/.opencode/skills/openspec-propose/.pi-map.md new file mode 100644 index 0000000..855779c --- /dev/null +++ b/.opencode/skills/openspec-propose/.pi-map.md @@ -0,0 +1,19 @@ +# .opencode/skills/openspec-propose +dir: .opencode/skills/openspec-propose + +index: .opencode/skills/openspec-propose/.pi-map.index.md + +## role +Provides an AI assistant skill that automates the openspec proposal workflow by scaffolding directories and generating structured artifacts (proposals, designs, tasks). +## files +- SKILL.md | Defines an AI assistant skill that automates proposing new changes by scaffolding a directory, generating dependent artifacts (proposal, design, tasks), and tracking progress through a structured workflow using the openspec CLI. | dep: openspec CLI, AskUserQuestion tool, TodoWrite tool +## arch +Skill-definition pattern using a declarative markdown document (SKILL.md) that encodes a step-by-step procedural workflow with CLI integration conventions. +## tags +skill, defines, assistant, automates, proposing, new, changes, scaffolding +## symbols +- +## workflows +- +## dirty +- diff --git a/.pi-map.index.md b/.pi-map.index.md new file mode 100644 index 0000000..554209b --- /dev/null +++ b/.pi-map.index.md @@ -0,0 +1,76 @@ +# . (index) +dir: . + +## Project Map Protocol + +1. Read this protocol and the root `.pi-map.index.md` first. +2. Use `index:` / `map:` references to open relevant directory indexes and maps. +3. Load indexes before rich maps during task-start navigation. +4. Read the local rich map and actual source before editing. +5. Treat non-empty `## dirty` sections in either artifact as stale. +6. If source and generated artifacts disagree, trust source. +7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair. +8. After editing source, run `project_map_patch` for each changed file. +9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters. + +Trust boundary: index routes, map orients, source decides. + +## role +Root project configuration and documentation hub for "Manage," a media and server operations tool with a FastAPI + React architecture and optional observability stack. +## parent +- +## children +- .atl + index: .atl/.pi-map.index.md + map: .atl/.pi-map.md +- .claude + index: .claude/.pi-map.index.md + map: .claude/.pi-map.md +- .opencode + index: .opencode/.pi-map.index.md + map: .opencode/.pi-map.md +- .pi + index: .pi/.pi-map.index.md + map: .pi/.pi-map.md +- .ruff_cache + index: .ruff_cache/.pi-map.index.md + map: .ruff_cache/.pi-map.md +- archive + index: archive/.pi-map.index.md + map: archive/.pi-map.md +- backend + index: backend/.pi-map.index.md + map: backend/.pi-map.md +- docs + index: docs/.pi-map.index.md + map: docs/.pi-map.md +- frontend + index: frontend/.pi-map.index.md + map: frontend/.pi-map.md +- monitoring + index: monitoring/.pi-map.index.md + map: monitoring/.pi-map.md +- openspec + index: openspec/.pi-map.index.md + map: openspec/.pi-map.md +## files +- .dockerignore +- .env.example +- .gitignore +- AGENTS.md +- CHANGELOG.md +- CONTRIBUTING.md +- LICENSE +- README.md +- context.md +- docker-compose.dev.yml +- docker-compose.observability.yml +- docker-compose.yml +- token-usage-output.txt +## links +index: ./.pi-map.index.md +map: ./.pi-map.md +## workflows +- +## dirty +- diff --git a/.pi-map.md b/.pi-map.md new file mode 100644 index 0000000..25c2f5a --- /dev/null +++ b/.pi-map.md @@ -0,0 +1,45 @@ +# . +dir: . + +index: ./.pi-map.index.md + +## Project Map Protocol + +1. Read this protocol and the root `.pi-map.index.md` first. +2. Use `index:` / `map:` references to open relevant directory indexes and maps. +3. Load indexes before rich maps during task-start navigation. +4. Read the local rich map and actual source before editing. +5. Treat non-empty `## dirty` sections in either artifact as stale. +6. If source and generated artifacts disagree, trust source. +7. If map and index disagree, trust neither blindly; verify from source and regenerate the pair. +8. After editing source, run `project_map_patch` for each changed file. +9. Before broad architectural claims or final handoff, run `project_map_validate` when freshness matters. + +Trust boundary: index routes, map orients, source decides. + +## role +Root project configuration and documentation hub for "Manage," a media and server operations tool with a FastAPI + React architecture and optional observability stack. +## files +- .dockerignore | Specifies files and directories to exclude from Docker build context to reduce image size and improve build performance | dep: Docker +- .env.example | Provides a template of environment variables for configuring application hosts, backend settings, OIDC authentication, SMTP, Grafana, and alerting across a Docker Compose deployment. +- .gitignore | Specifies patterns for Git to ignore across a Python/Streamlit project with frontend components and internal tooling | dep: git, python, streamlit, node/npm +- AGENTS.md | Provides project-specific guidance for AI agents working on a media library viewer application with FastAPI backend and Vite React frontend | dep: FastAPI, Vite, React, Docker Compose, uvicorn, pytest, Ruff, TypeScript, Python 3.11 +- CHANGELOG.md | Documents notable changes, breaking changes, and migration steps for the Manage project across versions. +- CONTRIBUTING.md | Provides guidelines for setting up a development environment, coding standards, validation steps, and contribution requirements for a Streamlit-based media library viewer application. | dep: python, venv, pip, streamlit, py_compile +- LICENSE | Provides the MIT open-source software license terms for the project +- README.md | This file is the project README, serving as the primary documentation and setup guide for "Manage," a media and server operations tool with a FastAPI + React architecture. | dep: FastAPI, React, Jellyfin, Docker Compose, SQLite, Prometheus, Grafana, Alertmanager +- context.md | Documentation file providing a historical and architectural overview of an observability stack (Prometheus, Grafana, Loki, Alertmanager) for a containerized media management application. | dep: Prometheus, Grafana, Loki, Alertmanager, Grafana Alloy, Node Exporter, Docker Compose, FastAPI +- docker-compose.dev.yml | Defines a development Docker Compose stack for a backend (FastAPI/Uvicorn) and frontend (Vite) application with hot-reload and disabled authentication. | dep: uvicorn, Docker +- docker-compose.observability.yml | Defines an optional standalone Docker Compose observability stack with Prometheus, Loki, Grafana, Alertmanager, Alloy, and Node Exporter for monitoring hosts without the main Manage application. | dep: prom/prometheus, grafana/loki, grafana/alloy, grafana/grafana, prom/alertmanager, prom/node-exporter, Traefik +- docker-compose.yml | Defines a Docker Compose production stack for a backend and frontend application with OIDC authentication, Traefik reverse proxy routing, and Prometheus metrics exposure. | dep: Traefik, OIDC provider, Docker, SMTP server, external observability stack (Prometheus/Grafana/Loki/Alertmanager) +- token-usage-output.txt | Displays a detailed token usage and cost analysis report for an AI coding session, including breakdowns by category, tool usage, cache efficiency, subagent costs, and pricing comparisons. +## arch +Docker Compose orchestrated multi-service architecture featuring a Python/FastAPI backend, Vite/React frontend, Traefik reverse proxy, and an optional Prometheus/Grafana/Loki monitoring stack. +## tags +docker, grafana, application, compose, fastapi, prometheus, project, alertmanager +## symbols +- +## workflows +- +## dirty +- diff --git a/archive/.pi-map.index.md b/archive/.pi-map.index.md new file mode 100644 index 0000000..8920819 --- /dev/null +++ b/archive/.pi-map.index.md @@ -0,0 +1,31 @@ +# archive (index) +dir: archive + +## role +Archive of an earlier project structure for a Streamlit-based Jellyfin media library browser with SSH remote file inspection capabilities. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- archive/src + index: archive/src/.pi-map.index.md + map: archive/src/.pi-map.md +- archive/tests + index: archive/tests/.pi-map.index.md + map: archive/tests/.pi-map.md +## files +- app.py +- pyproject.toml +- requirements.txt +## links +index: archive/.pi-map.index.md +map: archive/.pi-map.md +## workflows +- change archive behavior + read: app.py, pyproject.toml, requirements.txt +- change archive config + read: pyproject.toml +- explore archive subdirectories + index: archive/src/.pi-map.index.md, archive/tests/.pi-map.index.md +## dirty +- diff --git a/archive/.pi-map.md b/archive/.pi-map.md new file mode 100644 index 0000000..5b2be7b --- /dev/null +++ b/archive/.pi-map.md @@ -0,0 +1,26 @@ +# archive +dir: archive + +index: archive/.pi-map.index.md + +## role +Archive of an earlier project structure for a Streamlit-based Jellyfin media library browser with SSH remote file inspection capabilities. +## files +- app.py | Provides a minimal Streamlit entrypoint that adds the src directory to Python's path and delegates to the actual application in media_library_viewer.app. | dep: sys, pathlib, media_library_viewer.app +- pyproject.toml | Defines Python package metadata, dependencies, and tool configurations for a Streamlit-based Jellyfin media library browser with SSH remote file inspection. | dep: hatchling, streamlit, streamlit-aggrid, requests, paramiko, python-dotenv, pandas, ruff, pytest +- requirements.txt | Installs the current package in editable/development mode using pip | dep: pip, setuptools +## arch +Thin entrypoint pattern using a bootstrap app.py that manipulates sys.path to delegate execution to a nested media_library_viewer package, managed via standard Python packaging (pyproject.toml). +## tags +streamlit, app, python, media, library, package, pyproject, pip +## symbols +- +## workflows +- change archive behavior + read: app.py, pyproject.toml, requirements.txt +- change archive config + read: pyproject.toml +- explore archive subdirectories + index: archive/src/.pi-map.index.md, archive/tests/.pi-map.index.md +## dirty +- diff --git a/archive/src/.pi-map.index.md b/archive/src/.pi-map.index.md new file mode 100644 index 0000000..4efd58c --- /dev/null +++ b/archive/src/.pi-map.index.md @@ -0,0 +1,20 @@ +# archive/src (index) +dir: archive/src + +## role +No files provided — directory appears to be empty or contents were not included, so the package's role cannot be determined. +## parent +index: archive/.pi-map.index.md +map: archive/.pi-map.md +## children +- archive/src/media_library_viewer + index: archive/src/media_library_viewer/.pi-map.index.md + map: archive/src/media_library_viewer/.pi-map.md +## files +## links +index: archive/src/.pi-map.index.md +map: archive/src/.pi-map.md +## workflows +- +## dirty +- diff --git a/archive/src/.pi-map.md b/archive/src/.pi-map.md new file mode 100644 index 0000000..8b78186 --- /dev/null +++ b/archive/src/.pi-map.md @@ -0,0 +1,18 @@ +# archive/src +dir: archive/src + +index: archive/src/.pi-map.index.md + +## role +No files provided — directory appears to be empty or contents were not included, so the package's role cannot be determined. +## files +## arch +Cannot be assessed due to missing file contents; please provide the file listing for analysis. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/archive/src/media_library_viewer/.pi-map.index.md b/archive/src/media_library_viewer/.pi-map.index.md new file mode 100644 index 0000000..abb5fb1 --- /dev/null +++ b/archive/src/media_library_viewer/.pi-map.index.md @@ -0,0 +1,39 @@ +# archive/src/media_library_viewer (index) +dir: archive/src/media_library_viewer + +## role +Streamlit-based media library viewer that provides a unified dashboard for browsing and monitoring Jellyfin media alongside remote SSH file systems. +## parent +index: archive/src/.pi-map.index.md +map: archive/src/.pi-map.md +## children +- archive/src/media_library_viewer/clients + index: archive/src/media_library_viewer/clients/.pi-map.index.md + map: archive/src/media_library_viewer/clients/.pi-map.md +- archive/src/media_library_viewer/domain + index: archive/src/media_library_viewer/domain/.pi-map.index.md + map: archive/src/media_library_viewer/domain/.pi-map.md +- archive/src/media_library_viewer/services + index: archive/src/media_library_viewer/services/.pi-map.index.md + map: archive/src/media_library_viewer/services/.pi-map.md +- archive/src/media_library_viewer/ui + index: archive/src/media_library_viewer/ui/.pi-map.index.md + map: archive/src/media_library_viewer/ui/.pi-map.md +## files +- __init__.py +- app.py +- config.py +- jobs.py +- utils.py +## links +index: archive/src/media_library_viewer/.pi-map.index.md +map: archive/src/media_library_viewer/.pi-map.md +## workflows +- change media_library_viewer behavior + read: __init__.py, app.py, config.py +- change media_library_viewer config + read: config.py +- explore media_library_viewer subdirectories + index: archive/src/media_library_viewer/clients/.pi-map.index.md, archive/src/media_library_viewer/domain/.pi-map.index.md, archive/src/media_library_viewer/services/.pi-map.index.md +## dirty +- diff --git a/archive/src/media_library_viewer/.pi-map.md b/archive/src/media_library_viewer/.pi-map.md new file mode 100644 index 0000000..5c9a141 --- /dev/null +++ b/archive/src/media_library_viewer/.pi-map.md @@ -0,0 +1,35 @@ +# archive/src/media_library_viewer +dir: archive/src/media_library_viewer + +index: archive/src/media_library_viewer/.pi-map.index.md + +## role +Streamlit-based media library viewer that provides a unified dashboard for browsing and monitoring Jellyfin media alongside remote SSH file systems. +## files +- __init__.py | Package initialization file that defines the Media Library Viewer package metadata and exports the version string. +- app.py | Streamlit UI entrypoint for a Media Library Viewer that connects to Jellyfin and SSH backends, providing dashboard, monitoring, media browsing, and file browser tabs with cached data and path resolution between systems. | exp: func:get_jellyfin_client(base_url: str, api_key: str) → JellyfinClient, call:JellyfinClient, func:cached_users(base_url: str, api_key: str), call:get_jellyfin_client(base_url, api_key).users, func:get_ssh_client(host: str, username: str, port: int, key_filename: str, password: str) → RemoteSSHClient, call:RemoteSSHClient, call:client.connect, func:cached_libraries(base_url: str, api_key: str, user_id: str), call:get_jellyfin_client(base_url, api_key).libraries, func:cached_media_counts(base_url: str, api_key: str, user_id: str), call:get_jellyfin_client(base_url, api_key).media_counts, func:cached_library_counts(base_url: str, api_key: str, user_id: str), call:get_jellyfin_client, call:client.libraries, call:client.library_item_counts, func:cached_active_sessions(base_url: str, api_key: str), call:get_jellyfin_client(base_url, api_key).active_sessions, func:cached_dir_listing(host: str, username: str, port: int, key_filename: str, password: str, path: str), call:get_ssh_client, call:ssh.list_dir, call:json.loads, raise:RuntimeError, func:cached_ffprobe_preview(host: str, username: str, port: int, key_filename: str, password: str, path: str), call:get_ssh_client, call:ssh.ffprobe_json, func:apply_remote_path_prefix(path: str, prefix: str) → str, call:(prefix or "").strip, call:normalized_prefix.rstrip, call:path.startswith, call:posixpath.normpath, call:posixpath.join, func:map_path_to_media_root(path: str, media_root: str) → str, call:(media_root or "").strip, call:posixpath.normpath, call:str(path).split, call:"/".join, call:path_absolute.startswith, call:posixpath.basename, call:raw_parts.index, call:posixpath.join, func:resolve_remote_media_path(path: str, media_root: str, fallback_prefix: str) → str, call:map_path_to_media_root, call:apply_remote_path_prefix, func:credentials_panel(), call:load_config, call:st.header, call:st.expander, call:st.text_input, call:st.number_input, call:int, func:main(), call:st.set_page_config, call:st.title, call:st.caption, call:credentials_panel, call:st.info, call:get_jellyfin_client, call:cached_users, call:st.error, call:user.get, call:st.selectbox, call:list, call:user_options.keys, call:st.tabs, call:render_now_playing, call:st.divider, call:render_resource_dashboard, call:render_media_overview, call:cached_libraries, call:set_file_browser_path, call:resolve_remote_media_path, call:render_media_tab, call:render_file_browser, call:get_ssh_client, call:render_ssh_tools, func:set_prefixed_file_browser_path(path: str, selected_path, reset_filters) → None, call:set_file_browser_path, call:resolve_remote_media_path | dep: json, posixpath, typing, media_library_viewer.clients.jellyfin, media_library_viewer.clients.ssh, media_library_viewer.config, media_library_viewer.ui.dashboard, media_library_viewer.ui.file_browser, media_library_viewer.ui.media, media_library_viewer.ui.preview, streamlit +- config.py | Loads application configuration from environment variables and .env files using immutable dataclasses for Jellyfin and SSH settings. | exp: class:JellyfinConfig, class:SSHConfig, class:AppConfig, func:load_config() → AppConfig, call:AppConfig | dep: os, dataclasses, pathlib, dotenv +- jobs.py | Defines safe, template-based remote SSH jobs with shell-quoted parameter rendering. | exp: class:JobTemplate, method:render(self, values: Mapping[str, str]) → str, call:shlex.quote, call:values.items, call:self.command_template.format, func:run_job(ssh: RemoteSSHClient, job_key: str, path: str, timeout) → CommandResult, call:template.render, call:ssh.run | dep: shlex, dataclasses, typing, media_library_viewer.clients.ssh, typing.Mapping +- utils.py | Provides UI-independent formatting helpers and ffprobe output summarizers for video/audio/subtitle stream metadata. | exp: func:ticks_to_minutes(ticks: int | None) → int | None, call:round, func:human_size(num: int | float | None) → str, call:float, call:int, func:timestamp_to_local(ts: float | None) → str, call:datetime.fromtimestamp(ts).strftime, func:is_known_video_file(path: str | None) → bool, call:PurePosixPath(path).suffix.lower, func:format_duration(seconds: str | int | float | None) → str, call:float, call:str, call:int, func:format_bitrate(bit_rate: str | int | float | None) → str, call:float, call:str, func:_tags(stream: dict[str, Any]) → dict[str, Any], call:stream.get, func:_disposition(stream: dict[str, Any], key: str) → str, call:(stream.get("disposition") or {}).get, call:stream.get, func:_side_data_types(stream: dict[str, Any]) → str, call:stream.get, call:item.get, call:values.append, call:", ".join, func:ffprobe_format_summary(ffprobe: dict[str, Any]) → dict[str, str], call:ffprobe.get, call:fmt.get, call:format_duration, call:human_size, call:float, call:format_bitrate, call:str, func:summarize_video_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:stream.get, call:_tags, call:rows.append, call:format_bitrate, call:_side_data_types, call:tags.get, call:_disposition, func:summarize_audio_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:stream.get, call:_tags, call:rows.append, call:format_bitrate, call:tags.get, call:_disposition, func:summarize_subtitle_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:stream.get, call:_tags, call:rows.append, call:tags.get, call:_disposition, func:summarize_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:rows.append, call:format_bitrate, call:stream.get("tags", {}).get | dep: datetime, pathlib, typing +## arch +Layered Streamlit application using immutable dataclass configuration, template-based remote job execution, cached data access, and separated utility functions following a tab-based modular UI pattern. +## tags +client, path, media, call:, jellyfin, call:get, ssh, cached +## symbols +- JellyfinConfig +- SSHConfig +- AppConfig +- JobTemplate +- get_jellyfin_client +- cached_users +- get_ssh_client +- cached_libraries +## workflows +- change media_library_viewer behavior + read: __init__.py, app.py, config.py +- change media_library_viewer config + read: config.py +- explore media_library_viewer subdirectories + index: archive/src/media_library_viewer/clients/.pi-map.index.md, archive/src/media_library_viewer/domain/.pi-map.index.md, archive/src/media_library_viewer/services/.pi-map.index.md +## dirty +- diff --git a/archive/src/media_library_viewer/clients/.pi-map.index.md b/archive/src/media_library_viewer/clients/.pi-map.index.md new file mode 100644 index 0000000..11b38cb --- /dev/null +++ b/archive/src/media_library_viewer/clients/.pi-map.index.md @@ -0,0 +1,23 @@ +# archive/src/media_library_viewer/clients (index) +dir: archive/src/media_library_viewer/clients + +## role +External service and system integration layer providing HTTP API clients for Jellyfin/Emby media servers and SSH-based remote system metrics collection. +## parent +index: archive/src/media_library_viewer/.pi-map.index.md +map: archive/src/media_library_viewer/.pi-map.md +## children +- +## files +- __init__.py +- jellyfin.py +- resources.py +- ssh.py +## links +index: archive/src/media_library_viewer/clients/.pi-map.index.md +map: archive/src/media_library_viewer/clients/.pi-map.md +## workflows +- change clients behavior + read: __init__.py, jellyfin.py, resources.py +## dirty +- diff --git a/archive/src/media_library_viewer/clients/.pi-map.md b/archive/src/media_library_viewer/clients/.pi-map.md new file mode 100644 index 0000000..4476ca4 --- /dev/null +++ b/archive/src/media_library_viewer/clients/.pi-map.md @@ -0,0 +1,30 @@ +# archive/src/media_library_viewer/clients +dir: archive/src/media_library_viewer/clients + +index: archive/src/media_library_viewer/clients/.pi-map.index.md + +## role +External service and system integration layer providing HTTP API clients for Jellyfin/Emby media servers and SSH-based remote system metrics collection. +## files +- __init__.py | Package initialization file that defines external service clients module boundaries and constraints +- jellyfin.py | HTTP API client for Jellyfin/Emby media servers providing user, library, item, and session management with plain Python return types for frontend agnosticism. | exp: class:JellyfinClient, method:__init__(self, base_url: str, api_key: str, timeout), call:base_url.rstrip, call:self.base_url.endswith, call:requests.Session, call:self.session.headers.update, raise:ValueError, method:get(self, path: str, **params: Any) → dict[str, Any], call:params.items, call:self.session.get, call:response.raise_for_status, call:response.json, raise:requests.HTTPError, method:users(self) → list[dict[str, Any]], call:self.get, method:libraries(self, user_id: str) → list[dict[str, Any]], call:self.get(f"/Users/{user_id}/Views").get, method:items(self, user_id: str, parent_id, start_index, limit, search, include_item_types, recursive, sort_by, sort_order) → dict[str, Any], call:self.get, call:str(recursive).lower, method:item_count(self, user_id: str, include_item_types: str, parent_id) → int, call:self.get, call:int, call:response.get, method:media_counts(self, user_id: str) → dict[str, int], call:self.item_count, method:library_item_counts(self, user_id: str, libraries: list[dict[str, Any]]) → list[dict[str, Any]], call:lib.get, call:self.item_count, call:results.append, method:active_sessions(self, active_within_seconds) → list[dict[str, Any]], call:self.get, call:isinstance, call:session.get, method:image_url(self, item_id: str, image_type) → str | dep: typing, requests +- resources.py | Manages a lightweight POSIX shell-based remote system metrics collector that samples CPU, memory, network, and disk statistics via SSH and reads the resulting JSONL data. | exp: class:ResourceMonitorPaths, func:start_resource_collector(ssh: RemoteSSHClient, interval_seconds, retention_seconds, max_lines, paths) → str, call:shlex.quote, call:int, call:ssh.run, call:result.stdout.strip, raise:RuntimeError, func:stop_resource_collector(ssh: RemoteSSHClient, paths) → str, call:shlex.quote, call:ssh.run, call:result.stdout.strip, raise:RuntimeError, func:restart_resource_collector(ssh: RemoteSSHClient, interval_seconds, retention_seconds, max_lines, paths) → str, call:stop_resource_collector, call:start_resource_collector, func:resource_collector_status(ssh: RemoteSSHClient, paths) → str, call:shlex.quote, call:ssh.run, call:result.stdout.strip, raise:RuntimeError, func:resource_collector_debug_info(ssh: RemoteSSHClient, paths) → str, call:shlex.quote, call:ssh.run, func:read_resource_metrics(ssh: RemoteSSHClient, max_lines, paths) → list[dict[str, Any]], call:shlex.quote, call:int, call:ssh.run, call:result.stdout.splitlines, call:line.strip, call:rows.append, call:json.loads, raise:RuntimeError, func:disk_space(ssh: RemoteSSHClient, path) → dict[str, Any], call:shlex.quote, call:ssh.run, call:result.stdout.strip, call:json.loads, raise:RuntimeError | dep: json, shlex, dataclasses, typing, media_library_viewer.clients.ssh +- ssh.py | Provides an SSH client wrapper around Paramiko for remote filesystem inspection and media analysis, ensuring POSIX shell compatibility regardless of the user's login shell. | exp: class:CommandResult, class:RemoteSSHClient, method:__init__(self, host: str, username: str, port, key_filename, password, timeout), raise:ValueError, method:connect(self) → paramiko.SSHClient, call:paramiko.SSHClient, call:client.load_system_host_keys, call:client.set_missing_host_key_policy, call:paramiko.RejectPolicy, call:client.connect, method:close(self) → None, call:self._client.close, method:run(self, command: str, timeout) → CommandResult, call:self.connect, call:shlex.quote, call:client.exec_command, call:stdout.channel.recv_exit_status, call:CommandResult, call:stdout.read().decode, call:stderr.read().decode, method:list_dir(self, path: str) → CommandResult, call:shlex.quote, call:self.run, method:stat_path(self, path: str) → CommandResult, call:shlex.quote, call:self.run, method:ffprobe_json(self, path: str) → dict[str, Any], call:shlex.quote, call:self.run, call:json.loads, raise:RuntimeError | dep: json, posixpath, shlex, dataclasses, typing, paramiko +## arch +Client-wrapper pattern with each module encapsulating a specific integration concern (Jellyfin HTTP API, SSH filesystem access, remote resource monitoring), returning plain Python types for frontend agnosticism. +## tags +call:shlex.quote, resource, error, collector, call:ssh.run, raise:runtime, call:self.get, call:result.stdout.strip +## symbols +- JellyfinClient +- ResourceMonitorPaths +- CommandResult +- RemoteSSHClient +- __init__ +- get +- users +- libraries +## workflows +- change clients behavior + read: __init__.py, jellyfin.py, resources.py +## dirty +- diff --git a/archive/src/media_library_viewer/domain/.pi-map.index.md b/archive/src/media_library_viewer/domain/.pi-map.index.md new file mode 100644 index 0000000..06c017c --- /dev/null +++ b/archive/src/media_library_viewer/domain/.pi-map.index.md @@ -0,0 +1,21 @@ +# archive/src/media_library_viewer/domain (index) +dir: archive/src/media_library_viewer/domain + +## role +Provides domain-level normalization logic that transforms inconsistent Jellyfin API responses into stable, flattened data structures for storage and display. +## parent +index: archive/src/media_library_viewer/.pi-map.index.md +map: archive/src/media_library_viewer/.pi-map.md +## children +- +## files +- __init__.py +- media.py +## links +index: archive/src/media_library_viewer/domain/.pi-map.index.md +map: archive/src/media_library_viewer/domain/.pi-map.md +## workflows +- change domain behavior + read: __init__.py, media.py +## dirty +- diff --git a/archive/src/media_library_viewer/domain/.pi-map.md b/archive/src/media_library_viewer/domain/.pi-map.md new file mode 100644 index 0000000..a39cd5c --- /dev/null +++ b/archive/src/media_library_viewer/domain/.pi-map.md @@ -0,0 +1,28 @@ +# archive/src/media_library_viewer/domain +dir: archive/src/media_library_viewer/domain + +index: archive/src/media_library_viewer/domain/.pi-map.index.md + +## role +Provides domain-level normalization logic that transforms inconsistent Jellyfin API responses into stable, flattened data structures for storage and display. +## files +- __init__.py | Serves as the package docstring for a domain-level helpers/normalization module that converts external data into stable app concepts. +- media.py | Flattens inconsistent Jellyfin API item JSON into stable, normalized dictionaries for SQLite storage and frontend display. | exp: func:first_media_source(item: dict[str, Any]) → dict[str, Any], call:item.get, func:media_streams(item: dict[str, Any], stream_type) → list[dict[str, Any]], call:item.get, call:streams.extend, call:source.get, call:str(stream.get("Type") or stream.get("codec_type") or "").lower, call:stream.get, call:stream_type.lower, func:stream_value(stream: dict[str, Any], *keys: str) → Any, func:is_hdr_item(item: dict[str, Any]) → bool, call:media_streams, call:stream_value, call:" ".join, call:str(value).lower, call:any, func:format_date_added(value: str | None) → str, call:pd.to_datetime(value).strftime, call:str, func:timestamp_date_added(value: str | None) → int | None, call:int, call:pd.to_datetime(value).timestamp, func:format_rate_bits_decimal(bits_per_second: float | int | str | None) → str, call:float, call:str, func:normalize_media_item(item: dict[str, Any], library_id, library_name) → dict[str, Any], call:first_media_source, call:media_streams, call:source.get, call:item.get, call:stream_value, call:is_hdr_item, call:int, call:ticks_to_minutes, call:human_size, call:format_rate_bits_decimal, call:video.get, call:format_date_added, call:timestamp_date_added, func:display_media_row(row: dict[str, Any]) → dict[str, Any], call:row.get, call:human_size, call:format_rate_bits_decimal | dep: typing, media_library_viewer.utils, pandas, media_library_viewer.utils (human_size, ticks_to_minutes) +## arch +Functional transformation layer pattern mapping raw external API JSON directly into normalized flat dictionaries without intermediate ORM or complex object hierarchies. +## tags +media, call:str, date, added, item, call:item.get, streams, call:stream +## symbols +- first_media_source +- media_streams +- stream_value +- is_hdr_item +- format_date_added +- timestamp_date_added +- format_rate_bits_decimal +- normalize_media_item +## workflows +- change domain behavior + read: __init__.py, media.py +## dirty +- diff --git a/archive/src/media_library_viewer/services/.pi-map.index.md b/archive/src/media_library_viewer/services/.pi-map.index.md new file mode 100644 index 0000000..792a13e --- /dev/null +++ b/archive/src/media_library_viewer/services/.pi-map.index.md @@ -0,0 +1,21 @@ +# archive/src/media_library_viewer/services (index) +dir: archive/src/media_library_viewer/services + +## role +Application service layer that coordinates domain logic and external clients into reusable, UI-agnostic media library operations. +## parent +index: archive/src/media_library_viewer/.pi-map.index.md +map: archive/src/media_library_viewer/.pi-map.md +## children +- +## files +- __init__.py +- media_index.py +## links +index: archive/src/media_library_viewer/services/.pi-map.index.md +map: archive/src/media_library_viewer/services/.pi-map.md +## workflows +- change services behavior + read: __init__.py, media_index.py +## dirty +- diff --git a/archive/src/media_library_viewer/services/.pi-map.md b/archive/src/media_library_viewer/services/.pi-map.md new file mode 100644 index 0000000..2d0f493 --- /dev/null +++ b/archive/src/media_library_viewer/services/.pi-map.md @@ -0,0 +1,28 @@ +# archive/src/media_library_viewer/services +dir: archive/src/media_library_viewer/services + +index: archive/src/media_library_viewer/services/.pi-map.index.md + +## role +Application service layer that coordinates domain logic and external clients into reusable, UI-agnostic media library operations. +## files +- __init__.py | Marks the directory as a Python package and documents it as the application services layer for coordinating clients/domain logic into reusable operations. +- media_index.py | Provides a UI-agnostic SQLite-backed media inventory service that indexes, queries, and manages Jellyfin media metadata with filtering, sorting, and pagination capabilities. | exp: class:MediaIndexStatus, class:MediaIndex, method:__init__(self, db_path), call:Path, call:self.db_path.parent.mkdir, method:connect(self) → sqlite3.Connection, call:sqlite3.connect, method:init_schema(self) → None, call:self.connect, call:conn.executescript, method:set_metadata(self, key: str, value: str | int | float) → None, call:self.init_schema, call:self.connect, call:conn.execute, call:str, method:replace_items(self, rows: Iterable[dict[str, Any]]) → int, call:self.init_schema, call:list, call:",".join, call:len, call:self.connect, call:conn.execute, call:conn.executemany, call:','.join, call:row.get, call:str, call:int, call:time.time, method:status(self) → MediaIndexStatus, call:self.db_path.exists, call:MediaIndexStatus, call:self.connect, call:int, call:conn.execute("SELECT COUNT(*) FROM media_items").fetchone, call:conn.execute("SELECT value FROM index_metadata WHERE key='updated_at'").fetchone, call:conn.execute("SELECT value FROM index_metadata WHERE key='build_duration_seconds'").fetchone, call:str(updated_row[0]).isdigit, call:time.strftime, call:time.localtime, call:float, method:query(self, library_id, library_ids, media_types, search, hdr_filter, sort_key, sort_order, limit, offset) → tuple[list[dict[str, Any]], int], call:self.init_schema, call:where.append, call:",".join, call:len, call:params.extend, call:params.append, call:search.lower, call:" AND ".join, call:SORT_COLUMNS.get, call:self.connect, call:int, call:conn.execute("SELECT COUNT(*) FROM media_items" + where_sql, params).fetchone, call:conn.execute( "SELECT * FROM media_items" + where_sql + order_sql + " LIMIT ? OFFSET ?", [*params, int(limit), int(offset)], ).fetchall, call:display_media_row, call:dict, func:build_media_index(client: JellyfinClient, user_id: str, libraries: list[dict[str, Any]], index, page_size) → int, call:MediaIndex, call:time.perf_counter, call:library.get, call:client.items, call:response.get, call:normalized_rows.extend, call:normalize_media_item, call:len, call:int, call:index.replace_items, call:index.set_metadata | dep: sqlite3, time, dataclasses, pathlib, typing, media_library_viewer.clients.jellyfin, media_library_viewer.domain.media, media_library_viewer.clients.jellyfin.JellyfinClient, media_library_viewer.domain.media.display_media_row, media_library_viewer.domain.media.normalize_media_item +## arch +Service-oriented pattern with SQLite-backed indexing, query filtering, and pagination encapsulated behind a single cohesive media index service module. +## tags +media, call:conn.execute, index, call:self.connect, schema, call:int, init, status +## symbols +- MediaIndexStatus +- MediaIndex +- __init__ +- connect +- init_schema +- set_metadata +- replace_items +- status +## workflows +- change services behavior + read: __init__.py, media_index.py +## dirty +- diff --git a/archive/src/media_library_viewer/ui/.pi-map.index.md b/archive/src/media_library_viewer/ui/.pi-map.index.md new file mode 100644 index 0000000..208e34f --- /dev/null +++ b/archive/src/media_library_viewer/ui/.pi-map.index.md @@ -0,0 +1,24 @@ +# archive/src/media_library_viewer/ui (index) +dir: archive/src/media_library_viewer/ui + +## role +Streamlit UI rendering layer for the media library viewer application, providing dashboard monitoring, file browsing, media indexing, and preview capabilities. +## parent +index: archive/src/media_library_viewer/.pi-map.index.md +map: archive/src/media_library_viewer/.pi-map.md +## children +- +## files +- __init__.py +- dashboard.py +- file_browser.py +- media.py +- preview.py +## links +index: archive/src/media_library_viewer/ui/.pi-map.index.md +map: archive/src/media_library_viewer/ui/.pi-map.md +## workflows +- change ui behavior + read: __init__.py, dashboard.py, file_browser.py +## dirty +- diff --git a/archive/src/media_library_viewer/ui/.pi-map.md b/archive/src/media_library_viewer/ui/.pi-map.md new file mode 100644 index 0000000..b5db7c1 --- /dev/null +++ b/archive/src/media_library_viewer/ui/.pi-map.md @@ -0,0 +1,31 @@ +# archive/src/media_library_viewer/ui +dir: archive/src/media_library_viewer/ui + +index: archive/src/media_library_viewer/ui/.pi-map.index.md + +## role +Streamlit UI rendering layer for the media library viewer application, providing dashboard monitoring, file browsing, media indexing, and preview capabilities. +## files +- __init__.py | Package initialization file for Streamlit UI modules that documents the architectural pattern of splitting the application into separate render modules. +- dashboard.py | Implements a Streamlit dashboard for monitoring a Jellyfin media server, displaying media library statistics, active playback sessions, and server resource metrics via SSH. | exp: func:format_rate_bytes(bytes_per_second: float | int | None) → str, call:human_size, func:rate_scale(max_value: float | int | None) → tuple[float, str], call:abs, call:float, func:scaled_rate_chart_df(chart_df: pd.DataFrame, columns: list[str], labels: list[str]) → tuple[pd.DataFrame, str], call:chart_df[columns].max(numeric_only=True).max, call:rate_scale, call:chart_df[columns].copy, func:format_elapsed(seconds: float | int | None) → str, call:float, call:int, func:render_media_overview(cached_media_counts, cached_library_counts, base_url: str, api_key: str, user_id: str) → None, call:st.subheader, call:cached_media_counts, call:st.warning, call:counts.get, call:st.columns, call:top_cols[0].metric, call:top_cols[1].metric, call:top_cols[2].metric, call:top_cols[3].metric, call:cached_library_counts, call:st.caption, call:st.markdown, call:e.get, call:st.container, call:m_cols[0].metric, call:m_cols[1].metric, func:render_now_playing(cached_active_sessions, base_url: str, api_key: str) → None, call:st.subheader, call:cached_active_sessions, call:st.warning, call:st.caption, call:session.get, call:bool, call:play_state.get, call:item.get, call:transcoding.get, call:transcode_type.append, call:rows.append, call:", ".join, call:st.dataframe, call:pd.DataFrame, func:render_resource_dashboard(get_ssh_client, ssh_args: tuple, media_root: str, detailed) → None, call:st.subheader, call:get_ssh_client, call:resource_collector_status, call:st.error, call:st.columns, call:control_col.caption, call:start_col.button, call:st.success, call:start_resource_collector, call:restart_col.button, call:restart_resource_collector, call:stop_col.button, call:st.info, call:stop_resource_collector, call:refresh_col.button, call:st.rerun, call:st.caption, call:read_resource_metrics, call:disk_space, call:float, call:str(space.get("used_pct", "0")).rstrip, call:space.get, call:disk_cols[0].metric, call:human_size, call:disk_cols[1].metric, call:disk_cols[2].metric, call:disk_cols[3].metric, call:st.progress, call:min, call:max, call:st.warning, call:st.expander, call:st.code, call:resource_collector_debug_info, call:pd.DataFrame, call:pd.to_numeric, call:df.dropna, call:pd.to_datetime(df["ts"], unit="s", utc=True).dt.tz_convert, call:time.time, call:len, call:st.write, call:raw_df['ts'].astype(float).max, call:st.dataframe, call:raw_df.tail, call:df.sort_values, call:df["cpu_pct"].mean, call:df["cpu_pct"].max, call:df["iowait_pct"].mean, call:df["iowait_pct"].max, call:df["mem_pct"].mean, call:df["mem_pct"].max, call:df["net_rx_bytes_per_sec"].mean, call:df["net_rx_bytes_per_sec"].max, call:df["net_tx_bytes_per_sec"].mean, call:df["net_tx_bytes_per_sec"].max, call:df["disk_read_bps"].mean, call:df["disk_read_bps"].max, call:df["disk_write_bps"].mean, call:df["disk_write_bps"].max, call:metric_cols[0].metric, call:metric_cols[0].caption, call:metric_cols[1].metric, call:latest.get, call:metric_cols[1].caption, call:metric_cols[2].metric, call:metric_cols[2].caption, call:metric_cols[3].metric, call:format_rate_bytes, call:metric_cols[3].caption, call:metric_cols[4].metric, call:metric_cols[4].caption, call:metric_cols[5].metric, call:metric_cols[5].caption, call:metric_cols[6].metric, call:metric_cols[6].caption, call:df.set_index, call:st.markdown, call:st.line_chart, call:scaled_rate_chart_df | dep: time, typing, media_library_viewer.clients.resources, media_library_viewer.utils, pandas, streamlit +- file_browser.py | Renders an interactive SSH remote file browser UI in Streamlit with filtering, sorting, pagination, and directory navigation using ag-grid. | exp: func:reset_file_browser_filters() → None, call:st.session_state.pop, func:set_file_browser_path(path: str, selected_path, reset_filters) → None, func:aggrid_selected_rows(response: dict) → list[dict], call:response.get, call:isinstance, call:selected_rows.to_dict, call:list, func:render_file_browser(cached_dir_listing: Callable[..., list[dict]], ssh_args: tuple, initial_path: str) → str, call:st.subheader, call:st.session_state.pop, call:reset_file_browser_filters, call:st.session_state.get, call:st.columns, call:status_col.caption, call:selected_col.caption, call:path_col.text_input, call:set_file_browser_path, call:st.rerun, call:refresh_col.button, call:cached_dir_listing.clear, call:st.error, call:PurePosixPath(name).suffix.lower, call:str, call:display_rows.append, call:int, call:human_size, call:float, call:timestamp_to_local, call:len, call:sum, call:st.caption, call:st.container, call:filter_col.selectbox, call:search_col.text_input, call:sorted, call:ext_col.selectbox, call:sort_col.selectbox, call:order_col.toggle, call:page_size_col.selectbox, call:search_term.lower, call:r["name"].lower, call:filtered_rows.sort, call:max, call:page_col.number_input, call:summary_col.caption, call:min, call:visible_rows.append, call:visible_rows.extend, call:st.info, call:st.expander, call:st.write, call:pd.DataFrame, call:GridOptionsBuilder.from_dataframe, call:grid_builder.configure_default_column, call:grid_builder.configure_column, call:grid_builder.configure_selection, call:grid_builder.build, call:JsCode, call:AgGrid, call:aggrid_selected_rows, call:picked_row.get | dep: json, pathlib, typing, st_aggrid, media_library_viewer.utils, streamlit, pandas +- media.py | Renders a Streamlit UI tab for browsing and filtering a local SQLite-backed media index with ag-grid table display and automatic file browser synchronization. | exp: func:aggrid_selected_rows(response: dict[str, Any]) → list[dict[str, Any]], call:response.get, call:isinstance, call:selected_rows.to_dict, call:list, func:format_elapsed(seconds: float | int | None) → str, call:float, call:int, func:render_media_tab(client, user_id: str, libraries: list[dict[str, Any]], set_file_browser_path: Callable[[str, str | None, bool], None]) → None, call:st.subheader, call:st.caption, call:MediaIndex, call:index.status, call:st.columns, call:status_parts.append, call:format_elapsed, call:status_col.caption, call:" | ".join, call:status_col.warning, call:build_col.button, call:st.spinner, call:build_media_index, call:st.success, call:st.rerun, call:refresh_col.button, call:st.info, call:filter_col.multiselect, call:list, call:library_options.keys, call:type_col.multiselect, call:search_col.text_input, call:page_size_col.selectbox, call:page_col.number_input, call:sort_col.selectbox, call:sort_options.keys, call:order_col.selectbox, call:hdr_col.selectbox, call:index.query, call:int, call:len, call:pd.DataFrame(rows)[columns].fillna, call:st.session_state.get, call:GridOptionsBuilder.from_dataframe, call:grid_builder.configure_default_column, call:grid_builder.configure_column, call:grid_builder.configure_selection, call:grid_builder.build, call:JsCode, call:AgGrid, call:min, call:aggrid_selected_rows, call:selected_rows[0].get, call:set_file_browser_path, call:str, call:PurePosixPath, call:st.expander, call:st.write | dep: pathlib, typing, st_aggrid, media_library_viewer.services.media_index, pandas, streamlit +- preview.py | Renders a Streamlit UI for previewing selected media file metadata via ffprobe and executing remote SSH diagnostic tools/jobs. | exp: func:render_ffprobe_sections(ffprobe_data: dict[str, Any]) → None, call:ffprobe_format_summary, call:summarize_video_streams, call:summarize_audio_streams, call:summarize_subtitle_streams, call:st.markdown, call:st.dataframe, call:pd.DataFrame, call:st.caption, func:render_selected_file_preview(ssh_args: tuple, selected_path: str | None, cached_ffprobe_preview: Callable[..., dict[str, Any]]) → None, call:st.container, call:st.markdown, call:st.caption, call:is_known_video_file, call:st.columns, call:refresh_col.button, call:cached_ffprobe_preview.clear, call:st.rerun, call:st.spinner, call:status_col.error, call:status_col.success, call:render_ffprobe_sections, call:st.expander, call:st.json, func:render_ssh_tools(ssh, ssh_args: tuple, selected_path: str | None, cached_ffprobe_preview: Callable[..., dict[str, Any]]) → None, call:render_selected_file_preview, call:st.subheader, call:st.tabs, call:st.button, call:ssh.ffprobe_json, call:render_ffprobe_sections, call:st.expander, call:st.dataframe, call:pd.DataFrame, call:summarize_streams, call:st.json, call:st.error, call:str, call:ssh.stat_path, call:st.code, call:st.warning, call:st.selectbox, call:list, call:JOB_TEMPLATES.keys, call:st.caption, call:JOB_TEMPLATES[job_key].render, call:run_job, call:st.write | dep: typing, media_library_viewer.jobs, media_library_viewer.utils, pandas, streamlit +## arch +Module-based render pattern where each UI tab/view is isolated in its own module, sharing session state for cross-component synchronization (e.g., file browser auto-sync) and leveraging ag-grid for interactive data tables. +## tags +call:metric, call:grid, render, call:st.caption, col.button, browser, col.selectbox, media +## symbols +- format_rate_bytes +- rate_scale +- scaled_rate_chart_df +- format_elapsed +- render_media_overview +- render_now_playing +- render_resource_dashboard +- reset_file_browser_filters +## workflows +- change ui behavior + read: __init__.py, dashboard.py, file_browser.py +## dirty +- diff --git a/archive/tests/.pi-map.index.md b/archive/tests/.pi-map.index.md new file mode 100644 index 0000000..757ee59 --- /dev/null +++ b/archive/tests/.pi-map.index.md @@ -0,0 +1,19 @@ +# archive/tests (index) +dir: archive/tests + +## role +Legacy or archived test directory currently containing only a placeholder file with no active test code. +## parent +index: archive/.pi-map.index.md +map: archive/.pi-map.md +## children +- +## files +- .gitkeep +## links +index: archive/tests/.pi-map.index.md +map: archive/tests/.pi-map.md +## workflows +- +## dirty +- diff --git a/archive/tests/.pi-map.md b/archive/tests/.pi-map.md new file mode 100644 index 0000000..7e3e87e --- /dev/null +++ b/archive/tests/.pi-map.md @@ -0,0 +1,19 @@ +# archive/tests +dir: archive/tests + +index: archive/tests/.pi-map.index.md + +## role +Legacy or archived test directory currently containing only a placeholder file with no active test code. +## files +- .gitkeep | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +## arch +Empty placeholder structure using a `.gitkeep` file to preserve the directory in version control for potential future use. +## tags +tmux, swaps, position, two, panes, within, window, windows +## symbols +- +## workflows +- +## dirty +- diff --git a/backend/.pi-map.index.md b/backend/.pi-map.index.md new file mode 100644 index 0000000..6281045 --- /dev/null +++ b/backend/.pi-map.index.md @@ -0,0 +1,32 @@ +# backend (index) +dir: backend + +## role +FastAPI backend service providing REST API endpoints for Jellyfin media browsing, SSH file inspection, and server monitoring. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- backend/.pytest_cache + index: backend/.pytest_cache/.pi-map.index.md + map: backend/.pytest_cache/.pi-map.md +- backend/.ruff_cache + index: backend/.ruff_cache/.pi-map.index.md + map: backend/.ruff_cache/.pi-map.md +- backend/src + index: backend/src/.pi-map.index.md + map: backend/src/.pi-map.md +- backend/tests + index: backend/tests/.pi-map.index.md + map: backend/tests/.pi-map.md +## files +- Dockerfile +- README.md +- pyproject.toml +## links +index: backend/.pi-map.index.md +map: backend/.pi-map.md +## workflows +- +## dirty +- diff --git a/backend/.pi-map.md b/backend/.pi-map.md new file mode 100644 index 0000000..0d80720 --- /dev/null +++ b/backend/.pi-map.md @@ -0,0 +1,21 @@ +# backend +dir: backend + +index: backend/.pi-map.index.md + +## role +FastAPI backend service providing REST API endpoints for Jellyfin media browsing, SSH file inspection, and server monitoring. +## files +- Dockerfile | Builds a Docker container for a Python 3.11 backend API service using uvicorn | dep: python:3.11-slim, pip, uvicorn, pyproject.toml-based package +- README.md | Documentation describing the setup, configuration, Docker deployment, and API endpoints of a FastAPI backend for Jellyfin media browsing, SSH file inspection, and server monitoring. | dep: FastAPI, uvicorn, pydantic-settings, Jellyfin, Jellyseerr, SSH, Docker Compose, Alertmanager, Prometheus, Grafana, Authentik/OIDC +- pyproject.toml | Project configuration file defining dependencies, build system, linting, and testing settings for a FastAPI media library viewer backend. | dep: FastAPI, uvicorn, pydantic-settings, paramiko, requests, python-dotenv, pandas, PyJWT, prometheus-client, python-json-logger, cryptography, hatchling, ruff, pytest, httpx +## arch +Containerized Python 3.11 service using FastAPI framework with uvicorn ASGI server, following standard pyproject.toml-based project configuration with defined linting and testing pipelines. +## tags +uvicorn, fastapi, python, backend, pyproject, settings, docker, api +## symbols +- +## workflows +- +## dirty +- diff --git a/backend/src/.pi-map.index.md b/backend/src/.pi-map.index.md new file mode 100644 index 0000000..ea0ca7a --- /dev/null +++ b/backend/src/.pi-map.index.md @@ -0,0 +1,20 @@ +# backend/src (index) +dir: backend/src + +## role +Root source directory of the backend application containing server configuration, business logic, API routes, and data access layers. +## parent +index: backend/.pi-map.index.md +map: backend/.pi-map.md +## children +- backend/src/media_library_viewer_api + index: backend/src/media_library_viewer_api/.pi-map.index.md + map: backend/src/media_library_viewer_api/.pi-map.md +## files +## links +index: backend/src/.pi-map.index.md +map: backend/src/.pi-map.md +## workflows +- +## dirty +- diff --git a/backend/src/.pi-map.md b/backend/src/.pi-map.md new file mode 100644 index 0000000..edf1069 --- /dev/null +++ b/backend/src/.pi-map.md @@ -0,0 +1,18 @@ +# backend/src +dir: backend/src + +index: backend/src/.pi-map.index.md + +## role +Root source directory of the backend application containing server configuration, business logic, API routes, and data access layers. +## files +## arch +Unable to determine specific architecture/patterns without file listing; please provide directory contents for analysis. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/backend/src/media_library_viewer_api/.pi-map.index.md b/backend/src/media_library_viewer_api/.pi-map.index.md new file mode 100644 index 0000000..43ffde1 --- /dev/null +++ b/backend/src/media_library_viewer_api/.pi-map.index.md @@ -0,0 +1,57 @@ +# backend/src/media_library_viewer_api (index) +dir: backend/src/media_library_viewer_api + +## role +FastAPI backend providing authenticated media library viewing APIs with Jellyfin/Jellyseerr integration, remote SSH job execution, and observability features. +## parent +index: backend/src/.pi-map.index.md +map: backend/src/.pi-map.md +## children +- backend/src/media_library_viewer_api/clients + index: backend/src/media_library_viewer_api/clients/.pi-map.index.md + map: backend/src/media_library_viewer_api/clients/.pi-map.md +- backend/src/media_library_viewer_api/domain + index: backend/src/media_library_viewer_api/domain/.pi-map.index.md + map: backend/src/media_library_viewer_api/domain/.pi-map.md +- backend/src/media_library_viewer_api/integrations + index: backend/src/media_library_viewer_api/integrations/.pi-map.index.md + map: backend/src/media_library_viewer_api/integrations/.pi-map.md +- backend/src/media_library_viewer_api/models + index: backend/src/media_library_viewer_api/models/.pi-map.index.md + map: backend/src/media_library_viewer_api/models/.pi-map.md +- backend/src/media_library_viewer_api/routers + index: backend/src/media_library_viewer_api/routers/.pi-map.index.md + map: backend/src/media_library_viewer_api/routers/.pi-map.md +- backend/src/media_library_viewer_api/services + index: backend/src/media_library_viewer_api/services/.pi-map.index.md + map: backend/src/media_library_viewer_api/services/.pi-map.md +- backend/src/media_library_viewer_api/widgets + index: backend/src/media_library_viewer_api/widgets/.pi-map.index.md + map: backend/src/media_library_viewer_api/widgets/.pi-map.md +- backend/src/media_library_viewer_api/workers + index: backend/src/media_library_viewer_api/workers/.pi-map.index.md + map: backend/src/media_library_viewer_api/workers/.pi-map.md +## files +- __init__.py +- auth.py +- config.py +- dependencies.py +- jobs.py +- logging_utils.py +- main.py +- observability.py +- path_utils.py +- utils.py +- version.py +## links +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## workflows +- change media_library_viewer_api behavior + read: __init__.py, auth.py, config.py +- change media_library_viewer_api config + read: config.py +- explore media_library_viewer_api subdirectories + index: backend/src/media_library_viewer_api/clients/.pi-map.index.md, backend/src/media_library_viewer_api/domain/.pi-map.index.md, backend/src/media_library_viewer_api/integrations/.pi-map.index.md +## dirty +- diff --git a/backend/src/media_library_viewer_api/.pi-map.md b/backend/src/media_library_viewer_api/.pi-map.md new file mode 100644 index 0000000..cbb41bc --- /dev/null +++ b/backend/src/media_library_viewer_api/.pi-map.md @@ -0,0 +1,41 @@ +# backend/src/media_library_viewer_api +dir: backend/src/media_library_viewer_api + +index: backend/src/media_library_viewer_api/.pi-map.index.md + +## role +FastAPI backend providing authenticated media library viewing APIs with Jellyfin/Jellyseerr integration, remote SSH job execution, and observability features. +## files +- __init__.py | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +- auth.py | Implements OIDC/JWT and API key authentication for a FastAPI backend with middleware-based route protection. | exp: func:_normalize_issuer_url(issuer_url: str) → str, call:issuer_url.rstrip, func:get_oidc_metadata(issuer_url: str) → dict[str, Any], call:_normalize_issuer_url, call:urljoin, call:requests.get, call:response.raise_for_status, call:response.json, call:isinstance, raise:RuntimeError, func:get_jwk_client(jwks_url: str) → PyJWKClient, call:PyJWKClient, func:_split_audience(audience: str) → list[str], call:item.strip, call:audience.split, func:validate_auth_settings(settings: Settings) → None, raise:RuntimeError, func:validate_bearer_jwt(authorization: str | None, settings) → dict[str, Any], call:get_settings, call:validate_auth_settings, call:authorization.partition, call:scheme.lower, call:token.strip, call:_normalize_issuer_url, call:get_oidc_metadata, call:settings.oidc_jwks_url.strip, call:str, call:metadata.get, call:get_jwk_client, call:jwk_client.get_signing_key_from_jwt, call:_split_audience, call:jwt.decode, call:list, call:len, call:int, raise:PermissionError, raise:RuntimeError, func:require_jwt_auth(request: Request, call_next), call:get_settings, call:path.startswith, call:call_next, call:validate_bearer_jwt, call:request.headers.get, call:logger.warning, call:JSONResponse, call:str, call:logger.exception, call:claims.get, call:isinstance, func:get_api_key() → str, call:get_settings_store, call:store.get_settings, call:settings.get, call:secrets.token_urlsafe, call:store.update_setting, func:require_api_key(authorization) → str, call:get_api_key, call:secrets.compare_digest, raise:HTTPException | dep: logging, secrets, functools, typing, urllib.parse, jwt, requests, fastapi, fastapi.responses, jwt.exceptions, media_library_viewer_api.config, media_library_viewer_api.dependencies +- config.py | Defines a flat pydantic-settings configuration model that loads application settings from environment variables and .env files with cached access. | exp: class:Settings, func:_find_env_file() → str | None, call:Path.cwd, call:candidate.is_file, call:str, call:(directory / ".git").exists, func:get_settings() → Settings, call:_find_env_file, call:Settings, call:logger.info, call:describe_settings | dep: logging, functools, pathlib, pydantic_settings, media_library_viewer_api.logging_utils, functools.lru_cache, pathlib.Path, pydantic_settings.BaseSettings +- dependencies.py | Provides FastAPI dependency injection for Jellyfin/Jellyseerr clients and SSH/local command clients, resolving service instances from query parameters or configuration with caching and fallback logic. | exp: func:_request_machine_id(request: Request | None) → str | None, call:request.query_params.get, func:_request_jellyfin_service_id(request: Request | None) → str | None, call:request.query_params.get, func:_service_record(store: SettingsStore, service_type: str, service_id: str | None) → dict[str, Any] | None, call:store.get_service, call:candidate.get, call:store.list_services, call:s.get, call:row.get, call:decrypt_secrets, call:logger.exception, func:_jellyfin_client_for(cache_key: tuple[str, str, str]) → JellyfinClient, call:logger.info, call:url.rstrip, call:JellyfinClient, func:_ssh_client_for(cache_key: tuple[str, str, str, int, str, str | None, str | None, str | None, str | None]) → RemoteSSHClient, call:logger.info, call:RemoteSSHClient, call:client.connect, call:str, call:message.lower, call:logger.exception, raise:HTTPException, func:_resolve_machine(service: str, request) → dict[str, Any] | None, call:get_settings_store, call:_request_machine_id, call:store.get_machine, call:machine.get, call:store.list_machines_for_service, func:get_jellyfin_client(request) → JellyfinClient, call:get_settings_store, call:_request_jellyfin_service_id, call:_service_record, call:str, call:service.get("config", {}).get, call:service.get("secrets", {}).get, call:_jellyfin_client_for, raise:RuntimeError, func:get_jellyseerr_client(request) → JellyseerrClient | None, call:get_settings_store, call:_request_jellyfin_service_id, call:_service_record, call:logger.info, call:str, call:service.get("config", {}).get, call:service.get("secrets", {}).get, call:JellyseerrClient, func:_ssh_client_from_machine_config(machine: dict[str, Any], store) → RemoteSSHClient, call:get_settings_store, call:get_settings, call:str(machine.get("ssh_key_id") or "").strip, call:machine.get, call:store.get_ssh_key, call:ssh_key.get, call:int, call:_ssh_client_for, func:get_ssh_client(request), call:get_settings_store, call:_request_machine_id, call:store.get_machine_config, call:_resolve_machine, call:str(machine.get("mode") or "local").strip().lower, call:machine.get, call:logger.info, call:LocalCommandClient, call:_ssh_client_from_machine_config, call:get_settings, call:_ssh_client_for, raise:RuntimeError, func:get_mail_queue() → MailQueue, call:_get_mail_queue, func:get_settings_store() → SettingsStore, call:_get_settings_store, func:get_user_id(request) → str, call:get_settings_store, call:_request_jellyfin_service_id, call:_service_record, call:service.get("config", {}).get, call:str, call:get_jellyfin_client, call:client.users, raise:RuntimeError | dep: logging, functools, typing, fastapi, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.clients.jellyseerr, media_library_viewer_api.clients.local, media_library_viewer_api.clients.ssh, media_library_viewer_api.config, media_library_viewer_api.services.mail_queue, media_library_viewer_api.services.settings_store, media_library_viewer_api.services.secrets +- jobs.py | Defines template-based remote SSH jobs with shell-safe rendering for a media library viewer API. | exp: class:JobTemplate, method:render(self, values: Mapping[str, str]) → str, call:shlex.quote, call:values.items, call:self.command_template.format, func:run_job(ssh: RemoteSSHClient, job_key: str, path: str, timeout) → CommandResult, call:template.render, call:logger.info, call:ssh.run | dep: logging, shlex, dataclasses, typing, media_library_viewer_api.clients.ssh +- logging_utils.py | Configures structured JSON/text logging with secret-safe settings introspection and log field sanitization for a backend application. | exp: func:_json_formatter() → logging.Formatter, call:jsonlogger.JsonFormatter, func:_text_formatter() → logging.Formatter, call:logging.Formatter, func:configure_logging(level_name, log_format) → int, call:(level_name or os.getenv("LOG_LEVEL", "INFO")).upper, call:os.getenv, call:getattr, call:(log_format or os.getenv("LOG_FORMAT", "text")).lower, call:logging.StreamHandler, call:handler.setFormatter, call:_json_formatter, call:_text_formatter, call:logging.basicConfig, call:root.setLevel, call:logging.getLogger("media_library_viewer_api").setLevel, call:logging.getLogger("uvicorn").setLevel, call:logging.getLogger("uvicorn.error").setLevel, call:logging.getLogger("uvicorn.access").setLevel, call:logging.getLogger("paramiko").setLevel, call:logging.getLogger("urllib3").setLevel, func:_sanitize_url(url: str | None) → str, call:urlsplit, call:url.strip, call:url.rstrip, func:describe_settings(settings: object) → dict[str, str], call:str(getattr(settings, "log_level", "INFO") or "INFO").upper, call:getattr, call:str(getattr(settings, "log_format", "text") or "text").lower, call:bool, call:_sanitize_url, func:sanitize_log_extra(extra: dict[str, Any] | None) → dict[str, Any], call:extra.items, call:key.lower, call:any, call:lower_key.endswith | dep: logging, os, typing, urllib.parse, pythonjsonlogger +- main.py | FastAPI application entrypoint that configures middleware, registers routers, manages startup/shutdown lifecycle, and exposes health/version/metrics endpoints. | exp: func:lifespan(app: FastAPI), call:get_settings, call:configure_logging, call:validate_auth_settings, call:validate_encryption_key, call:logger.info, call:describe_settings, call:get_settings_store().ensure_defaults, call:logger.exception, call:get_mail_queue, call:get_backup_poller, call:mail_queue.start, call:backup_poller.start, call:backup_poller.stop, call:mail_queue.stop, func:enforce_jwt_auth(request: Request, call_next), call:call_next, call:require_jwt_auth, func:log_requests(request: Request, call_next), call:time.perf_counter, call:get_request_id, call:set_current_request_id, call:sanitize_log_extra, call:logger.info, call:call_next, call:logger.exception, call:record_request, call:round, func:health_check() → dict[str, str], call:logger.debug, func:version_info() → dict[str, str], call:logger.debug, call:get_version_info, func:metrics() → Response, call:metrics_payload, call:FastAPIResponse | dep: logging, time, contextlib, uvicorn, fastapi, fastapi.middleware.cors, fastapi.responses, media_library_viewer_api.auth, media_library_viewer_api.config, media_library_viewer_api.dependencies, media_library_viewer_api.logging_utils, media_library_viewer_api.observability, media_library_viewer_api.routers, media_library_viewer_api.routers.settings, .services.backup_poller, .version, media_library_viewer_api.services.secrets, media_library_viewer_api.services.backup_poller, media_library_viewer_api.version +- observability.py | Provides Prometheus metrics collection, request ID generation/correlation, and structured logging helpers for application observability. | exp: func:set_current_request_id(request_id: str | None) → None, call:_current_request_id.set, func:get_current_request_id() → str | None, call:_current_request_id.get, func:generate_request_id() → str, call:uuid.uuid4, func:get_request_id(request) → str, call:request.headers.get, call:header.strip, call:_current_request_id.get, call:generate_request_id, call:_current_request_id.set, func:metrics_payload() → tuple[bytes, str], call:generate_latest, func:record_request(request: Request, response: Response, duration_seconds: float) → None, call:str, call:REQUESTS_TOTAL.labels(method=method, path=path, status_code=status).inc, call:REQUEST_DURATION.labels(method=method, path=path).observe, func:record_ssh_command(machine_id: str, action: str, status: str, duration_seconds: float) → None, call:SSH_COMMANDS_TOTAL.labels(machine_id=machine_id or "unknown", action=action, status=status).inc, call:SSH_COMMAND_DURATION.labels(machine_id=machine_id or "unknown", action=action).observe, func:record_media_index_build(status: str, duration_seconds) → None, call:MEDIA_INDEX_BUILDS_TOTAL.labels(status=status).inc, call:MEDIA_INDEX_BUILD_DURATION.observe, func:record_backup_run(job_name: str, status: str, success) → None, call:BACKUP_RUNS_TOTAL.labels(job_name=job_name, status=status).inc, call:BACKUP_RUNS_LAST_SUCCESS.labels(job_name=job_name).set_to_current_time, func:record_mail_queue(status: str) → None, call:MAIL_QUEUE_SIZE.labels(status=status).inc, func:log_extra(request, **kwargs: Any) → dict[str, Any], call:get_request_id, call:extra.update | dep: uuid, contextvars, typing, fastapi, prometheus_client +- path_utils.py | Maps Jellyfin media paths to SSH-accessible paths using media root anchoring or fallback prefixing. | exp: func:apply_remote_path_prefix(path: str, prefix: str) → str, call:(prefix or "").strip, call:normalized_prefix.rstrip, call:path.startswith, call:posixpath.normpath, call:logger.debug, call:posixpath.join, func:map_path_to_media_root(path: str, media_root: str) → str, call:(media_root or "").strip, call:posixpath.normpath, call:str(path).split, call:"/".join, call:path_absolute.startswith, call:logger.debug, call:posixpath.basename, call:raw_parts.index, call:posixpath.join, func:resolve_remote_media_path(path: str, media_root: str, fallback_prefix: str) → str, call:map_path_to_media_root, call:logger.debug, call:apply_remote_path_prefix | dep: logging, posixpath +- utils.py | Provides UI-framework-independent formatting helpers and ffprobe output summarizers for video, audio, and subtitle streams. | exp: func:ticks_to_minutes(ticks: int | None) → int | None, call:round, func:human_size(num: int | float | None) → str, call:float, call:int, func:timestamp_to_local(ts: float | None) → str, call:datetime.fromtimestamp(ts).strftime, func:is_known_video_file(path: str | None) → bool, call:PurePosixPath(path).suffix.lower, func:format_duration(seconds: str | int | float | None) → str, call:float, call:str, call:int, func:format_bitrate(bit_rate: str | int | float | None) → str, call:float, call:str, func:_tags(stream: dict[str, Any]) → dict[str, Any], call:stream.get, func:_disposition(stream: dict[str, Any], key: str) → str, call:(stream.get("disposition") or {}).get, call:stream.get, func:_side_data_types(stream: dict[str, Any]) → str, call:stream.get, call:item.get, call:values.append, call:", ".join, func:ffprobe_format_summary(ffprobe: dict[str, Any]) → dict[str, str], call:ffprobe.get, call:fmt.get, call:format_duration, call:human_size, call:float, call:format_bitrate, call:str, func:summarize_video_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:stream.get, call:_tags, call:rows.append, call:format_bitrate, call:_side_data_types, call:tags.get, call:_disposition, func:summarize_audio_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:stream.get, call:_tags, call:rows.append, call:format_bitrate, call:tags.get, call:_disposition, func:summarize_subtitle_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:stream.get, call:_tags, call:rows.append, call:tags.get, call:_disposition, func:summarize_streams(ffprobe: dict[str, Any]) → list[dict[str, Any]], call:ffprobe.get, call:rows.append, call:format_bitrate, call:stream.get("tags", {}).get | dep: datetime, pathlib, typing +- version.py | Provides version retrieval and formatting utilities for a backend service, falling back through environment variables, package metadata, and default values. | exp: func:get_backend_version() → str, call:os.getenv("APP_VERSION", "").strip, call:package_version, func:get_backend_build_info() → str, call:os.getenv("APP_BUILD_INFO", "").strip, call:os.getenv("GIT_COMMIT", "").strip, call:os.getenv("BUILD_COMMIT", "").strip, func:format_version_label(version: str, build_info: str) → str, call:version.strip, call:build_info.strip, func:get_version_info() → dict[str, str], call:get_backend_version, call:get_backend_build_info, call:format_version_label | dep: os, importlib.metadata +## arch +Layered FastAPI architecture using dependency injection, pydantic-settings configuration, OIDC/API-key middleware authentication, template-based remote execution, and Prometheus/structured logging observability. +## tags +call:, settings, call:get, request, get, client, call:str, id +## symbols +- Settings +- JobTemplate +- _normalize_issuer_url +- get_oidc_metadata +- get_jwk_client +- _split_audience +- validate_auth_settings +- validate_bearer_jwt +## workflows +- change media_library_viewer_api behavior + read: __init__.py, auth.py, config.py +- change media_library_viewer_api config + read: config.py +- explore media_library_viewer_api subdirectories + index: backend/src/media_library_viewer_api/clients/.pi-map.index.md, backend/src/media_library_viewer_api/domain/.pi-map.index.md, backend/src/media_library_viewer_api/integrations/.pi-map.index.md +## dirty +- diff --git a/backend/src/media_library_viewer_api/clients/.pi-map.index.md b/backend/src/media_library_viewer_api/clients/.pi-map.index.md new file mode 100644 index 0000000..b8d13c9 --- /dev/null +++ b/backend/src/media_library_viewer_api/clients/.pi-map.index.md @@ -0,0 +1,24 @@ +# backend/src/media_library_viewer_api/clients (index) +dir: backend/src/media_library_viewer_api/clients + +## role +Provides HTTP and command execution client wrappers for integrating with external media services (Jellyfin, Jellyseerr) and performing remote/local filesystem inspection. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- jellyfin.py +- jellyseerr.py +- local.py +- ssh.py +## links +index: backend/src/media_library_viewer_api/clients/.pi-map.index.md +map: backend/src/media_library_viewer_api/clients/.pi-map.md +## workflows +- change clients behavior + read: __init__.py, jellyfin.py, jellyseerr.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/clients/.pi-map.md b/backend/src/media_library_viewer_api/clients/.pi-map.md new file mode 100644 index 0000000..363b14a --- /dev/null +++ b/backend/src/media_library_viewer_api/clients/.pi-map.md @@ -0,0 +1,31 @@ +# backend/src/media_library_viewer_api/clients +dir: backend/src/media_library_viewer_api/clients + +index: backend/src/media_library_viewer_api/clients/.pi-map.index.md + +## role +Provides HTTP and command execution client wrappers for integrating with external media services (Jellyfin, Jellyseerr) and performing remote/local filesystem inspection. +## files +- __init__.py | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +- jellyfin.py | Provides a reusable, framework-agnostic HTTP client wrapper for the Jellyfin/Emby API with methods for browsing users, libraries, media items, and sessions. | exp: class:JellyfinClient, method:__init__(self, base_url: str, api_key: str, timeout), call:base_url.rstrip, call:self.base_url.endswith, call:requests.Session, call:self.session.headers.update, raise:ValueError, method:get(self, path: str, **params: Any) → Any, call:params.items, call:logger.debug, call:sorted, call:clean_params.keys, call:self.session.get, call:response.raise_for_status, call:logger.warning, call:response.json, raise:requests.HTTPError, method:users(self) → list[dict[str, Any]], call:self.get, call:logger.info, call:len, method:libraries(self, user_id: str) → list[dict[str, Any]], call:self.get(f"/Users/{user_id}/Views").get, call:logger.info, call:len, method:items(self, user_id: str, parent_id, start_index, limit, search, include_item_types, recursive, sort_by, sort_order) → dict[str, Any], call:logger.debug, call:self.get, call:str(recursive).lower, method:item_count(self, user_id: str, include_item_types: str, parent_id) → int, call:self.get, call:int, call:response.get, call:logger.debug, method:media_counts(self, user_id: str) → dict[str, int], call:self.item_count, method:library_item_counts(self, user_id: str, libraries: list[dict[str, Any]]) → list[dict[str, Any]], call:lib.get, call:self.item_count, call:results.append, method:sessions(self, active_within_seconds) → list[dict[str, Any]], call:self.get, call:cast, call:isinstance, method:active_sessions(self, active_within_seconds) → list[dict[str, Any]], call:self.sessions, call:session.get, call:logger.info, call:len, method:image_url(self, item_id: str, image_type) → str | dep: logging, typing, requests +- jellyseerr.py | HTTP client wrapper for the Jellyseerr REST API to fetch user data and enrich Jellyfin user information | exp: class:JellyseerrClient, method:__init__(self, base_url: str, api_key: str, timeout), call:base_url.rstrip, call:self.base_url.endswith, call:requests.Session, call:self.session.headers.update, raise:ValueError, method:get(self, path: str, **params: Any) → Any, call:params.items, call:logger.debug, call:sorted, call:clean_params.keys, call:self.session.get, call:response.raise_for_status, call:logger.warning, call:response.json, raise:requests.HTTPError, method:absolute_url(self, path: str | None) → str, call:path.startswith, method:jellyfin_users(self) → list[dict[str, Any]], call:self.get, call:isinstance, call:logger.info, call:len, call:payload.get, method:users(self, page_size) → list[dict[str, Any]], call:max, call:int, call:self.get, call:isinstance, call:payload.get, call:results.extend, call:page_info.get, call:logger.debug, call:len, call:logger.info | dep: logging, typing, requests +- local.py | Provides a local command execution client that mirrors remote SSH helpers to run POSIX shell commands, list directories, stat paths, and run ffprobe on the API host for built-in local monitoring. | exp: class:CommandResult, class:LocalCommandClient, method:__init__(self, timeout), method:run(self, command: str, timeout) → CommandResult, call:logger.debug, call:subprocess.run, call:CommandResult, call:logger.warning, call:result.stderr.strip, call:result.stdout.strip, method:list_dir(self, path: str) → CommandResult, call:shlex.quote, call:self.run, method:stat_path(self, path: str) → CommandResult, call:shlex.quote, call:self.run, method:ffprobe_json(self, path: str) → dict[str, object], call:shlex.quote, call:self.run, call:json.loads, raise:RuntimeError | dep: json, logging, posixpath, shlex, subprocess, dataclasses +- ssh.py | Provides an SSH client wrapper for remote filesystem inspection and media analysis using paramiko, with POSIX shell command execution and host key management. | exp: class:CommandResult, class:RemoteSSHClient, method:__init__(self, host: str, username: str, port, key_filename, private_key, private_key_passphrase, password, known_hosts_path, timeout), raise:ValueError, method:connect(self) → paramiko.SSHClient, call:paramiko.SSHClient, call:client.load_system_host_keys, call:Path, call:bool, call:has_known_host, call:known_hosts_file.is_file, call:client.load_host_keys, call:client.set_missing_host_key_policy, call:paramiko.RejectPolicy, call:paramiko.AutoAddPolicy, call:self._load_private_key, call:client.connect, call:str(exc).lower, call:known_hosts_file.parent.mkdir, call:client.save_host_keys, raise:RuntimeError, method:close(self) → None, call:self._client.close, method:run(self, command: str, timeout) → CommandResult, call:self.connect, call:shlex.quote, call:logger.debug, call:client.exec_command, call:stdout.channel.recv_exit_status, call:CommandResult, call:stdout.read().decode, call:stderr.read().decode, call:logger.warning, call:result.stderr.strip, call:result.stdout.strip, method:list_dir(self, path: str) → CommandResult, call:shlex.quote, call:self.run, call:logger.info, method:stat_path(self, path: str) → CommandResult, call:shlex.quote, call:self.run, call:logger.info, method:ffprobe_json(self, path: str) → dict[str, Any], call:shlex.quote, call:self.run, call:logger.info, call:json.loads, raise:RuntimeError | dep: json, logging, posixpath, shlex, dataclasses, io, pathlib, typing, paramiko, media_library_viewer_api.services.known_hosts +## arch +Client-wrapper pattern with framework-agnostic abstractions; parallel local/remote execution strategies via paramiko SSH and local subprocess; centralized REST API communication modules. +## tags +call:logger.info, call:logger.debug, call:self.get, call:shlex.quote, error, host, call:self.run, init +## symbols +- JellyfinClient +- JellyseerrClient +- CommandResult +- LocalCommandClient +- RemoteSSHClient +- __init__ +- get +- users +## workflows +- change clients behavior + read: __init__.py, jellyfin.py, jellyseerr.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/domain/.pi-map.index.md b/backend/src/media_library_viewer_api/domain/.pi-map.index.md new file mode 100644 index 0000000..2d6ff23 --- /dev/null +++ b/backend/src/media_library_viewer_api/domain/.pi-map.index.md @@ -0,0 +1,22 @@ +# backend/src/media_library_viewer_api/domain (index) +dir: backend/src/media_library_viewer_api/domain + +## role +Domain layer providing data normalization and transformation helpers for Jellyfin media data and dashboard summaries. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- dashboard.py +- media.py +## links +index: backend/src/media_library_viewer_api/domain/.pi-map.index.md +map: backend/src/media_library_viewer_api/domain/.pi-map.md +## workflows +- change domain behavior + read: __init__.py, dashboard.py, media.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/domain/.pi-map.md b/backend/src/media_library_viewer_api/domain/.pi-map.md new file mode 100644 index 0000000..d68737f --- /dev/null +++ b/backend/src/media_library_viewer_api/domain/.pi-map.md @@ -0,0 +1,29 @@ +# backend/src/media_library_viewer_api/domain +dir: backend/src/media_library_viewer_api/domain + +index: backend/src/media_library_viewer_api/domain/.pi-map.index.md + +## role +Domain layer providing data normalization and transformation helpers for Jellyfin media data and dashboard summaries. +## files +- __init__.py | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +- dashboard.py | Provides domain helper functions for building dashboard data, specifically normalizing Jellyfin session activity rows and computing backup job summaries. | exp: func:_map_sessions_to_activity_rows(sessions: list[dict[str, Any]]) → list[dict[str, Any]], call:session.get, call:bool, call:item.get, call:play_state.get, call:transcoding.get, call:transcode_type.append, call:results.append, call:", ".join, func:build_backup_dashboard_summary(store: SettingsStore) → BackupDashboardSummary, call:store.list_backup_jobs, call:len, call:int, call:time.time, call:store.list_backup_runs, call:recent_runs.append, call:sum, call:store.list_backup_alerts, call:failed_runs.append, call:max, call:BackupDashboardSummary, call:round | dep: time, typing, media_library_viewer_api.models.backups, media_library_viewer_api.services.settings_store +- media.py | Flattens inconsistent Jellyfin API JSON into normalized dictionaries for SQLite indexing and frontend display. | exp: func:first_media_source(item: dict[str, Any]) → dict[str, Any], call:item.get, func:media_streams(item: dict[str, Any], stream_type) → list[dict[str, Any]], call:item.get, call:streams.extend, call:source.get, call:str(stream.get("Type") or stream.get("codec_type") or "").lower, call:stream.get, call:stream_type.lower, func:stream_value(stream: dict[str, Any], *keys: str) → Any, func:is_hdr_item(item: dict[str, Any]) → bool, call:media_streams, call:stream_value, call:" ".join, call:str(value).lower, call:any, func:format_date_added(value: str | None) → str, call:pd.to_datetime(value).strftime, call:str, func:timestamp_date_added(value: str | None) → int | None, call:int, call:pd.to_datetime(value).timestamp, func:format_rate_bits_decimal(bits_per_second: float | int | str | None) → str, call:float, call:str, func:normalize_media_item(item: dict[str, Any], library_id, library_name) → dict[str, Any], call:first_media_source, call:media_streams, call:source.get, call:item.get, call:stream_value, call:is_hdr_item, call:int, call:ticks_to_minutes, call:human_size, call:format_rate_bits_decimal, call:video.get, call:format_date_added, call:timestamp_date_added, func:display_media_row(row: dict[str, Any]) → dict[str, Any], call:row.get, call:human_size, call:format_rate_bits_decimal | dep: typing, media_library_viewer_api.utils, pandas +## arch +Stateless functional modules that transform inconsistent upstream API JSON into normalized dictionaries for persistence and display. +## tags +media, backup, call:item.get, call:str, date, added, dashboard, call:store.list +## symbols +- _map_sessions_to_activity_rows +- build_backup_dashboard_summary +- first_media_source +- media_streams +- stream_value +- is_hdr_item +- format_date_added +- timestamp_date_added +## workflows +- change domain behavior + read: __init__.py, dashboard.py, media.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/integrations/.pi-map.index.md b/backend/src/media_library_viewer_api/integrations/.pi-map.index.md new file mode 100644 index 0000000..22f0cc6 --- /dev/null +++ b/backend/src/media_library_viewer_api/integrations/.pi-map.index.md @@ -0,0 +1,29 @@ +# backend/src/media_library_viewer_api/integrations (index) +dir: backend/src/media_library_viewer_api/integrations + +## role +Defines and registers third-party service integrations (e.g., Alertmanager, Grafana, Jellyfin, Prometheus) with typed configs, secrets, and widget definitions for the media library viewer API. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- alertmanager.py +- base.py +- grafana.py +- jellyfin.py +- jellyseerr.py +- nextcloud.py +- prometheus.py +- registry.py +- ssh_tasks.py +## links +index: backend/src/media_library_viewer_api/integrations/.pi-map.index.md +map: backend/src/media_library_viewer_api/integrations/.pi-map.md +## workflows +- change integrations behavior + read: __init__.py, alertmanager.py, base.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/integrations/.pi-map.md b/backend/src/media_library_viewer_api/integrations/.pi-map.md new file mode 100644 index 0000000..e584a77 --- /dev/null +++ b/backend/src/media_library_viewer_api/integrations/.pi-map.md @@ -0,0 +1,36 @@ +# backend/src/media_library_viewer_api/integrations +dir: backend/src/media_library_viewer_api/integrations + +index: backend/src/media_library_viewer_api/integrations/.pi-map.index.md + +## role +Defines and registers third-party service integrations (e.g., Alertmanager, Grafana, Jellyfin, Prometheus) with typed configs, secrets, and widget definitions for the media library viewer API. +## files +- __init__.py | Defines a closed registry module for service integrations. +- alertmanager.py | Defines the Alertmanager service integration, including connection config, widget definitions, and alert summarization logic. | exp: class:AlertmanagerConfig, class:AlertmanagerAlertsWidgetConfig, func:summarize_alerts(alerts: list[dict[str, Any]], severity_filter) → dict[str, Any], call:alert.get, call:labels.get, call:by_severity.get, call:open_alerts.append, call:annotations.get, call:open_alerts.sort, call:len | dep: typing, media_library_viewer_api.integrations.base +- base.py | Defines base classes and utilities for creating compile-time service integration definitions with Pydantic-based config schemas, secret fields, and widget kinds. | exp: class:ServiceConfigBase, class:WidgetConfigBase, class:SecretField, class:WidgetKind, class:ServiceDefinition, method:widget_kind(self, kind: str) → WidgetKind | None, func:widget_kind(kind: str, name: str, description: str, model_cls: type[WidgetConfigBase], default_config, refresh_interval_ms) → WidgetKind, call:model_cls.model_json_schema, call:schema.pop, call:WidgetKind, call:dict, func:validate_config(model_cls: type[BaseModel], config: dict[str, Any] | None) → dict[str, Any], call:model_cls.model_validate, call:instance.model_dump | dep: dataclasses, typing, pydantic +- grafana.py | Defines the Grafana service configuration, secret fields, and widget types for integration with the media library viewer API. | exp: class:GrafanaConfig, class:GrafanaLinkWidgetConfig | dep: media_library_viewer_api.integrations.base +- jellyfin.py | Defines the Jellyfin media server service configuration and activity widget definition for a media library viewer API. | exp: class:JellyfinConfig, class:JellyfinActivityWidgetConfig | dep: media_library_viewer_api.integrations.base +- jellyseerr.py | Defines a service configuration and metadata for Jellyseerr, a request management companion to Jellyfin. | exp: class:JellyseerrConfig | dep: media_library_viewer_api.integrations.base +- nextcloud.py | Defines the Nextcloud service configuration model and service definition for integration into the media library viewer API. | exp: class:NextcloudConfig | dep: media_library_viewer_api.integrations.base +- prometheus.py | Defines the service configuration, widget types, and service definition for integrating Prometheus as a metrics data source. | exp: class:PrometheusConfig, class:PrometheusMetricWidgetConfig | dep: media_library_viewer_api.integrations.base +- registry.py | Provides a closed registry of service definitions with lookup and enumeration functions. | exp: func:list_service_types() → list[str], call:sorted, func:get_service_definition(service_type: str) → ServiceDefinition | None, call:SERVICE_DEFINITIONS.get, func:get_widget_kind(service_type: str, widget_kind: str) → WidgetKind | None, call:get_service_definition, call:definition.widget_kind, func:require_service_definition(service_type: str) → ServiceDefinition, call:get_service_definition, raise:ValueError | dep: media_library_viewer_api.integrations.alertmanager, media_library_viewer_api.integrations.base, media_library_viewer_api.integrations.grafana, media_library_viewer_api.integrations.jellyfin, media_library_viewer_api.integrations.jellyseerr, media_library_viewer_api.integrations.nextcloud, media_library_viewer_api.integrations.prometheus, media_library_viewer_api.integrations.ssh_tasks +- ssh_tasks.py | Defines a service configuration for an SSH task runner that executes reusable saved tasks over SSH and records run history. | exp: class:SshTasksConfig, class:SshTaskOutputWidgetConfig | dep: media_library_viewer_api.integrations.base +## arch +Plugin/registry pattern using Pydantic-based config schemas with a closed registry for compile-time service definition lookups, base class extension for per-service widgets and secrets, and a modular file-per-integration structure. +## tags +config, service, widget, integrations, media_library_viewer_api, base, definition, kind +## symbols +- AlertmanagerConfig +- AlertmanagerAlertsWidgetConfig +- ServiceConfigBase +- WidgetConfigBase +- SecretField +- WidgetKind +- ServiceDefinition +- GrafanaConfig +## workflows +- change integrations behavior + read: __init__.py, alertmanager.py, base.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/models/.pi-map.index.md b/backend/src/media_library_viewer_api/models/.pi-map.index.md new file mode 100644 index 0000000..269f1ed --- /dev/null +++ b/backend/src/media_library_viewer_api/models/.pi-map.index.md @@ -0,0 +1,22 @@ +# backend/src/media_library_viewer_api/models (index) +dir: backend/src/media_library_viewer_api/models + +## role +Defines Pydantic data models for request/response validation across backup management, service registry, and dashboard widget APIs. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- backups.py +- services.py +- widgets.py +## links +index: backend/src/media_library_viewer_api/models/.pi-map.index.md +map: backend/src/media_library_viewer_api/models/.pi-map.md +## workflows +- change models behavior + read: backups.py, services.py, widgets.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/models/.pi-map.md b/backend/src/media_library_viewer_api/models/.pi-map.md new file mode 100644 index 0000000..52aa1bb --- /dev/null +++ b/backend/src/media_library_viewer_api/models/.pi-map.md @@ -0,0 +1,29 @@ +# backend/src/media_library_viewer_api/models +dir: backend/src/media_library_viewer_api/models + +index: backend/src/media_library_viewer_api/models/.pi-map.index.md + +## role +Defines Pydantic data models for request/response validation across backup management, service registry, and dashboard widget APIs. +## files +- backups.py | Defines Pydantic data models for backup system API requests and responses including reports, jobs, runs, alerts, and dashboard summaries. | exp: class:BackupReportRequest, class:BackupJobResponse, class:BackupRunResponse, class:BackupAlertResponse, class:BackupDashboardSummary | dep: datetime, typing, pydantic +- services.py | Defines Pydantic models for a service registry API, including validation to prevent credential keys in non-secret configuration. | exp: class:ServiceInstanceInput, class:ServiceInstance, class:SecretFieldInfo, class:WidgetKindInfo, class:ServiceTypeInfo, func:_validate_config_keys(config: dict[str, Any]) → dict[str, Any], call:isinstance, call:value.items, call:key.lower, call:_check, raise:ValueError, func:_check(value: Any) → None, call:isinstance, call:value.items, call:key.lower, call:_check, raise:ValueError | dep: typing, pydantic +- widgets.py | Defines Pydantic models for a dashboard widget system with validation to prevent secrets/credentials in widget configuration. | exp: class:_WidgetInstanceBase, class:WidgetInstanceInput, class:WidgetInstance, class:BuiltinWidgetKindInfo, class:WidgetDataResponse, func:_looks_secret(value: Any) → bool, call:isinstance, call:value.strip, call:value.lower, call:value.startswith, call:len, call:lowered.isalnum, func:_validate_config_keys(config: dict[str, Any]) → dict[str, Any], call:config.items, call:key.lower, call:_looks_secret, call:isinstance, call:_validate_config_keys, raise:ValueError | dep: typing, pydantic +## arch +Pydantic-based model layer implementing data validation, serialization contracts, and custom validators enforcing security constraints (e.g., blocking credentials in non-secret configs). +## tags +widget, backup, instance, response, info, call:isinstance, call:, service +## symbols +- BackupReportRequest +- BackupJobResponse +- BackupRunResponse +- BackupAlertResponse +- BackupDashboardSummary +- ServiceInstanceInput +- ServiceInstance +- SecretFieldInfo +## workflows +- change models behavior + read: backups.py, services.py, widgets.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/routers/.pi-map.index.md b/backend/src/media_library_viewer_api/routers/.pi-map.index.md new file mode 100644 index 0000000..a0d6b97 --- /dev/null +++ b/backend/src/media_library_viewer_api/routers/.pi-map.index.md @@ -0,0 +1,32 @@ +# backend/src/media_library_viewer_api/routers (index) +dir: backend/src/media_library_viewer_api/routers + +## role +FastAPI router package that exposes all REST API endpoints for the media library viewer backend, organized by domain (backups, dashboard, files, jobs, media, monitoring, services, settings, tasks, users, widgets). +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- backups.py +- dashboard.py +- files.py +- jobs.py +- media.py +- monitoring.py +- services.py +- settings.py +- tasks.py +- users.py +- users_impl.py +- widgets.py +## links +index: backend/src/media_library_viewer_api/routers/.pi-map.index.md +map: backend/src/media_library_viewer_api/routers/.pi-map.md +## workflows +- change routers behavior + read: __init__.py, backups.py, dashboard.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/routers/.pi-map.md b/backend/src/media_library_viewer_api/routers/.pi-map.md new file mode 100644 index 0000000..8f5ef1d --- /dev/null +++ b/backend/src/media_library_viewer_api/routers/.pi-map.md @@ -0,0 +1,39 @@ +# backend/src/media_library_viewer_api/routers +dir: backend/src/media_library_viewer_api/routers + +index: backend/src/media_library_viewer_api/routers/.pi-map.index.md + +## role +FastAPI router package that exposes all REST API endpoints for the media library viewer backend, organized by domain (backups, dashboard, files, jobs, media, monitoring, services, settings, tasks, users, widgets). +## files +- __init__.py | Marks the directory as a Python package for routers. +- backups.py | FastAPI router that provides REST endpoints for reporting, tracking, and alerting on backup jobs and runs. | exp: func:_get_or_create_job(store: SettingsStore, report: BackupReportRequest) → dict[str, Any], call:store.get_backup_job_by_name, call:store.upsert_backup_job, call:store.get_backup_job, func:post_backup_report(report: BackupReportRequest, store, _auth) → BackupRunResponse, call:_get_or_create_job, call:store.list_backup_runs, call:int, call:report.started_at.timestamp, call:abs, call:BackupRunResponse, call:report.ended_at.timestamp, call:store.create_backup_run, call:record_backup_run, call:generate_alerts_for_run, call:store.create_backup_alert, call:store.resolve_backup_alerts_for_job, call:run.pop, func:post_backup_start(report: BackupReportRequest, store, _auth) → BackupRunResponse, call:_get_or_create_job, call:int, call:report.started_at.timestamp, call:store.create_backup_run, call:record_backup_run, call:run.pop, call:BackupRunResponse, func:get_backup_jobs(store) → list[dict[str, Any]], call:store.list_backup_jobs, func:get_backup_job(job_id: str, store) → dict[str, Any], call:store.get_backup_job, call:store.list_backup_runs, raise:HTTPException, func:get_backup_runs(job_id, status, limit, store) → list[BackupRunResponse], call:store.list_backup_runs, call:BackupRunResponse, func:get_backup_run(run_id: str, store) → BackupRunResponse, call:store.get_backup_run, call:BackupRunResponse, raise:HTTPException, func:get_backup_alerts(job_id, acknowledged, severity, store) → list[BackupAlertResponse], call:store.list_backup_alerts, call:BackupAlertResponse, func:acknowledge_backup_alert(alert_id: str, store) → BackupAlertResponse, call:store.acknowledge_backup_alert, call:BackupAlertResponse, raise:HTTPException | dep: typing, fastapi, ..auth, ..models.backups, ..observability, ..services.backup_alert_engine, ..services.settings_store +- dashboard.py | FastAPI router providing dashboard endpoints for media counts, library breakdowns, shortcuts CRUD, activity sessions, and backup summaries. | exp: func:get_counts(client, user_id) → dict[str, int], call:client.media_counts, call:logger.info, func:get_library_counts(client, user_id) → list[dict[str, Any]], call:client.libraries, call:logger.info, call:len, call:client.library_item_counts, func:get_shortcuts() → list[dict[str, Any]], call:store.list_shortcuts, call:logger.info, call:len, func:create_shortcut(payload: dict[str, Any]) → dict[str, Any], call:store.upsert_shortcut, call:logger.info, call:shortcut.get, func:update_shortcut(shortcut_id: str, payload: dict[str, Any]) → dict[str, Any], call:store.upsert_shortcut, call:logger.info, call:shortcut.get, func:delete_shortcut(shortcut_id: str) → dict[str, str], call:store.delete_shortcut, call:logger.info, func:get_activity(client) → list[dict[str, Any]], call:client.sessions, call:_map_sessions_to_activity_rows, call:rows.sort, call:state_rank.get, call:r.get, call:str(r.get("user", "")).lower, call:logger.info, call:len, func:get_now_playing(client) → list[dict[str, Any]], call:get_activity, func:get_backup_dashboard(store) → BackupDashboardSummary, call:build_backup_dashboard_summary | dep: logging, typing, fastapi, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.dependencies, media_library_viewer_api.domain.dashboard, media_library_viewer_api.models.backups, media_library_viewer_api.services.settings_store +- files.py | FastAPI router providing endpoints for remote file operations including directory listing, ffprobe media analysis, stat, and path resolution via SSH. | exp: func:list_directory(path, ssh) → dict[str, Any], call:ssh.list_dir, call:logger.warning, call:json.loads, call:logger.info, call:len, raise:HTTPException, func:get_ffprobe(path, ssh) → dict[str, Any], call:ssh.ffprobe_json, call:logger.warning, call:logger.info, raise:HTTPException, func:get_stat(path, ssh) → dict[str, str], call:ssh.stat_path, call:logger.warning, call:logger.info, raise:HTTPException, func:resolve_path(path) → dict[str, str], call:get_settings, call:resolve_remote_media_path, call:logger.info | dep: json, logging, typing, fastapi, media_library_viewer_api.clients.ssh, media_library_viewer_api.config, media_library_viewer_api.dependencies, media_library_viewer_api.path_utils +- jobs.py | FastAPI router that exposes endpoints to list available job templates and execute them on remote paths via SSH. | exp: class:RunJobRequest, func:get_templates() → list[dict[str, str]], call:JOB_TEMPLATES.items, call:logger.info, call:len, func:post_run_job(request: RunJobRequest, ssh) → dict[str, Any], call:logger.warning, call:logger.info, call:run_job, raise:HTTPException | dep: logging, typing, fastapi, pydantic, media_library_viewer_api.clients.ssh, media_library_viewer_api.dependencies, media_library_viewer_api.jobs +- media.py | FastAPI router that manages media index lifecycle (status, build, stop, query) with subprocess worker orchestration and cooperative/force cancellation. | exp: func:get_media_index() → MediaIndex, call:MediaIndex, func:_set_build_metadata(index: MediaIndex, state: dict[str, Any]) → None, call:state.items, call:index.set_metadata, func:_staging_db_path(index: MediaIndex) → Path, call:index.db_path.with_name, func:_pid_is_alive(pid: int | None) → bool, call:os.kill, func:_clean_stale_build_state(index: MediaIndex) → Any, call:index.status, call:_pid_is_alive, call:logger.warning, call:_set_build_metadata, func:_serialize_status(status: Any) → dict[str, Any], func:_worker_command(final_db_path: Path, staging_db_path: Path) → list[str], call:str, func:_start_worker(index: MediaIndex) → subprocess.Popen[bytes], call:_staging_db_path, call:staging_path.unlink, call:subprocess.Popen, call:_worker_command, call:os.environ.copy, func:get_index_status(index) → dict[str, Any], call:_clean_stale_build_state, call:logger.info, call:_serialize_status, func:post_build_index(client, user_id, index) → dict[str, Any], call:_clean_stale_build_state, call:_pid_is_alive, call:logger.warning, call:client.libraries, call:logger.info, call:len, call:_start_worker, call:_set_build_metadata, call:index.status, call:record_media_index_build, call:_serialize_status, raise:HTTPException, func:stop_build(index) → dict[str, Any], call:_clean_stale_build_state, call:logger.warning, call:logger.info, call:_set_build_metadata, call:index.status, call:_serialize_status, raise:HTTPException, func:force_stop_build(index) → dict[str, Any], call:_clean_stale_build_state, call:logger.warning, call:_pid_is_alive, call:_set_build_metadata, call:index.status, call:_serialize_status, call:logger.info, call:os.killpg, call:time.time, call:time.sleep, call:record_media_index_build, raise:HTTPException, func:query_media(libraries, types, search, hdr_filter, sort_key, sort_order, limit, offset, client, user_id, index) → dict[str, Any], call:lid.strip, call:libraries.split, call:client.libraries, call:t.strip, call:types.split, call:logger.info, call:len, call:",".join, call:index.query | dep: logging, os, signal, subprocess, sys, threading, time, pathlib, typing, fastapi, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.dependencies, media_library_viewer_api.observability, media_library_viewer_api.services.media_index, media_library_viewer_api.workers.media_index_worker +- monitoring.py | FastAPI router providing endpoints to check the health/status of Alertmanager, Grafana, and Prometheus services and expose Prometheus scrape targets. | exp: func:_resolve_service_record(store: SettingsStore, service_type: str, service_id) → ServiceRecord | None, call:store.get_service, call:row.get, call:build_service_record, call:store.list_services, func:_base_url(service: ServiceRecord) → str, call:str(service.config.get("base_url") or "").rstrip, call:service.config.get, func:_timeout(service: ServiceRecord, default: int) → int, call:int, call:service.config.get, func:_auth_headers(service: ServiceRecord) → dict[str, str], call:str, call:service.secrets.get, func:_status_response(service: ServiceRecord | None, version, error) → dict[str, Any], func:_summary_from_alerts(alerts: list[dict[str, Any]]) → dict[str, Any], call:summarize_alerts, func:get_machines(store) → list[dict[str, Any]], call:store.list_machines, call:m.get, func:get_prometheus_targets(store) → list[dict[str, Any]], call:build_node_exporter_targets, call:logger.info, call:len, func:get_alertmanager_alerts(service_id, store) → dict[str, Any], call:_resolve_service_record, call:requests.get, call:_base_url, call:_auth_headers, call:_timeout, call:response.raise_for_status, call:response.json, call:logger.exception, call:data.get, call:_summary_from_alerts, call:logger.info, func:get_alertmanager_status(service_id, store) → dict[str, Any], call:_resolve_service_record, call:requests.get, call:_base_url, call:_auth_headers, call:_timeout, call:response.raise_for_status, call:response.json, call:logger.exception, call:data.get("versionInfo", {}).get, call:status.get, call:p.get, call:cluster.get, func:get_grafana_status(service_id, store) → dict[str, Any], call:_resolve_service_record, call:_status_response, call:requests.get, call:_base_url, call:_auth_headers, call:_timeout, call:response.raise_for_status, call:response.json, call:logger.exception, call:data.get, func:get_prometheus_status(service_id, store) → dict[str, Any], call:_resolve_service_record, call:_status_response, call:_base_url, call:_timeout, call:_auth_headers, call:requests.get, call:health.raise_for_status, call:build_info.raise_for_status, call:build_info.json().get("data", {}).get, call:logger.exception, func:receive_alertmanager_webhook(payload) → dict[str, str], call:payload.get, call:logger.info, call:len | dep: logging, typing, requests, fastapi, media_library_viewer_api.dependencies, media_library_viewer_api.services.settings_store, media_library_viewer_api.services.targets, media_library_viewer_api.widgets.sources, media_library_viewer_api.integrations.alertmanager +- services.py | Provides REST API endpoints for managing service instances in a service registry, including listing service types and CRUD operations for instances while ensuring plaintext secrets are never exposed. | exp: func:_to_type_info(service_type: str) → ServiceTypeInfo, call:require_service_definition, call:ServiceTypeInfo, call:SecretFieldInfo, call:WidgetKindInfo, func:_to_instance(row: dict[str, Any]) → ServiceInstance, call:get_service_definition, call:set, call:row.get, call:bool, call:ServiceInstance, func:_validate_input(body: ServiceInstanceInput) → None, call:get_service_definition, call:validate_config, call:set, raise:HTTPException, func:list_types() → list[ServiceTypeInfo], call:_to_type_info, call:sorted, func:list_instances(service_type, store) → list[ServiceInstance], call:store.list_services, call:_to_instance, func:create_instance(body: ServiceInstanceInput, store) → ServiceInstance, call:_validate_input, call:store.upsert_service, call:_to_instance, func:update_instance(service_id: str, body: ServiceInstanceInput, store) → ServiceInstance, call:store.get_service, call:_validate_input, call:store.upsert_service, call:_to_instance, raise:HTTPException, func:delete_instance(service_id: str, store) → dict[str, str], call:store.get_service, call:store.delete_service, raise:HTTPException | dep: logging, typing, fastapi, media_library_viewer_api.dependencies, media_library_viewer_api.integrations.base, media_library_viewer_api.integrations.registry, media_library_viewer_api.models.services, media_library_viewer_api.services.settings_store +- settings.py | FastAPI router providing REST endpoints for managing machine configurations, SSH keys, and local database resets. | exp: class:MonitoringMachineInput, class:SSHKeyInput, class:SSHKeyGenerateInput, class:ResetLocalDatabaseInput, func:get_machines(store) → list[dict[str, Any]], call:store.list_machines, func:_resolve_ssh_client(machine: MonitoringMachineInput, store: SettingsStore) → tuple[RemoteSSHClient, str, int], call:machine.host.strip, call:machine.username.strip, call:int, call:store.get_ssh_key, call:str, call:ssh_key.get, call:get_settings, call:RemoteSSHClient, raise:HTTPException, func:_raise_ssh_validation_error(host: str, port: int, exc: Exception) → None, call:str, call:message.lower, raise:HTTPException, func:_validate_saved_machine_ssh(machine: MonitoringMachineInput, store: SettingsStore) → None, call:str(machine.mode or "").strip().lower, call:_resolve_ssh_client, call:client.connect, call:_raise_ssh_validation_error, call:client.close, func:test_machine_ssh(machine: MonitoringMachineInput, store) → dict[str, Any], call:str(machine.mode or "").strip().lower, call:_resolve_ssh_client, call:get_settings, call:has_known_host, call:client.connect, call:message.lower, call:client.close, raise:HTTPException, func:post_machine(machine: MonitoringMachineInput, store) → dict[str, Any], call:store.upsert_machine, call:machine.model_dump, call:MonitoringMachineInput.model_validate, call:_validate_saved_machine_ssh, func:put_machine(machine_id: str, machine: MonitoringMachineInput, store) → dict[str, Any], call:store.get_machine, call:store.upsert_machine, call:machine.model_dump, call:MonitoringMachineInput.model_validate, call:_validate_saved_machine_ssh, raise:HTTPException, func:delete_machine(machine_id: str, store) → dict[str, str], call:store.get_machine, call:store.delete_machine, raise:HTTPException, func:generate_ssh_key(payload: SSHKeyGenerateInput) → dict[str, Any], call:paramiko.RSAKey.generate, call:StringIO, call:key.write_private_key, call:private_buffer.getvalue, call:key.get_name, call:key.get_base64, call:":".join, call:key.get_fingerprint, func:get_ssh_keys(store) → list[dict[str, Any]], call:store.list_ssh_keys, func:post_ssh_key(key: SSHKeyInput, store) → dict[str, Any], call:store.upsert_ssh_key, call:key.model_dump, func:put_ssh_key(key_id: str, key: SSHKeyInput, store) → dict[str, Any], call:store.get_ssh_key, call:store.upsert_ssh_key, call:key.model_dump, raise:HTTPException, func:delete_ssh_key(key_id: str, store) → dict[str, str], call:store.get_ssh_key, call:store.delete_ssh_key, raise:HTTPException, func:reset_local_database(payload: ResetLocalDatabaseInput, store) → dict[str, Any], call:payload.confirm_phrase.strip().upper, call:remove_sqlite_database, call:MediaIndex, call:bool, raise:HTTPException | dep: logging, io, typing, paramiko, fastapi, pydantic, media_library_viewer_api.clients.ssh, media_library_viewer_api.config, media_library_viewer_api.dependencies, media_library_viewer_api.services.db_maintenance, media_library_viewer_api.services.known_hosts, media_library_viewer_api.services.media_index, media_library_viewer_api.services.settings_store +- tasks.py | FastAPI router providing CRUD endpoints and execution for saved server tasks with SSH service resolution | exp: class:TaskInput, class:RunTaskRequest, func:_service_label(service: dict[str, Any] | None) → str, call:str, call:service.get, func:_resolve_service_for_task(store: SettingsStore, task: dict[str, Any], service_id: str | None) → dict[str, Any] | None, call:store.get_service, call:str(task.get("default_service_id") or "").strip, call:task.get, call:store.list_services, call:svc.get, func:_service_row_to_record(service_row: dict[str, Any]) → ServiceRecord, call:build_service_record, call:get_settings_store, func:list_tasks(store) → list[dict[str, Any]], call:store.list_tasks, func:create_task(task: TaskInput, store) → dict[str, Any], call:store.upsert_task, call:task.model_dump, func:update_task(task_id: str, task: TaskInput, store) → dict[str, Any], call:store.get_task, call:store.upsert_task, call:task.model_dump, raise:HTTPException, func:delete_task(task_id: str, store) → dict[str, str], call:store.get_task, call:store.delete_task, raise:HTTPException, func:list_task_runs(task_id: str, limit, store) → dict[str, Any], call:store.get_task, call:store.list_service_task_runs, call:len, raise:HTTPException, func:run_task(request: RunTaskRequest, service_id, store) → dict[str, Any], call:store.get_task, call:task.get, call:_resolve_service_for_task, call:service_row.get, call:_service_row_to_record, call:run_saved_task, call:_service_label, raise:HTTPException | dep: logging, typing, fastapi, pydantic, media_library_viewer_api.dependencies, media_library_viewer_api.services.settings_store, media_library_viewer_api.services.task_runner, media_library_viewer_api.widgets.sources +- users.py | Re-exports all public names from the users_impl module to provide a unified public API for user-related functionality. | dep: .users_impl, users_impl +- users_impl.py | FastAPI router that fetches and merges Jellyfin users with optional Jellyseerr enrichment, plus endpoints for queueing user emails via a background mail worker. | exp: func:_safe_int(value: Any) → int, call:int, func:_permission_labels(permissions: int) → list[str], func:_role_label(permissions: int) → str, func:_account_type(user_type: Any) → str, call:_USER_TYPES.get, call:_safe_int, func:_merge_users(jellyfin_users: list[dict[str, Any]], jellyseerr_jellyfin_users: list[dict[str, Any]] | None, jellyseerr_users: list[dict[str, Any]] | None, jellyseerr_client: JellyseerrClient | None) → dict[str, Any], call:str(value or "").strip().lower, call:bool, call:_looks_like_email, call:str(value).strip, call:", ".join, call:_normalize, call:item.get, call:_lookup_keys, call:user.get, call:linked_by_jellyfin_id.get, call:(jf_link or {}).get, call:seerr_by_key.get, call:_pick_source_and_value, call:(seerr_user or {}).get, call:_first_value, call:jellyseerr_client.absolute_url, call:_safe_int, call:_role_label, call:_source_summary, call:items.append, call:_account_type, call:_permission_labels, call:logger.info, call:len, func:_normalize(value: Any) → str, call:str(value or "").strip().lower, func:_looks_like_email(value: Any) → bool, call:str(value or "").strip, call:bool, func:_pick_source_and_value(candidates: list[tuple[str, Any]]) → tuple[str, str], call:_looks_like_email, call:str(value).strip, func:_first_value(candidates: list[tuple[str, Any]]) → tuple[str, str], call:str(value or "").strip, func:_source_summary(name_source: str, email_source: str, avatar_source: str, access_source: str) → str, call:", ".join, func:_lookup_keys(item: dict[str, Any]) → list[str], call:_normalize, call:item.get, func:get_users(jellyfin, jellyseerr) → dict[str, Any], call:jellyfin.users, call:logger.info, call:len, call:jellyseerr.jellyfin_users, call:logger.exception, call:jellyseerr.users, call:_merge_users, call:bool, func:get_user_message_status() → dict[str, Any], call:mail_queue.status, func:post_user_message(recipient_ids, subject, html_body, text_body, attachments, jellyfin, jellyseerr) → dict[str, Any], call:json.loads, call:isinstance, call:str(item).strip, call:subject.strip, call:get_users, call:item.get, call:directory.get, call:users_by_id.get, call:skipped.append, call:str(item.get("email") or "").strip, call:recipients.append, call:recipient_labels.append, call:get_settings, call:validate_smtp_settings, call:mail_queue.status, call:upload.read, call:attachment_payloads.append, call:EmailAttachment, call:mail_queue.enqueue, call:str(getattr(settings, "smtp_from_address", "") or "").strip, call:getattr, call:str(getattr(settings, "smtp_username", "") or "").strip, call:logger.info, call:len, raise:HTTPException | dep: json, logging, typing, fastapi, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.clients.jellyseerr, media_library_viewer_api.config, media_library_viewer_api.dependencies, media_library_viewer_api.services.mailer +- widgets.py | Provides a FastAPI REST API for managing dashboard widget instances, including CRUD operations, built-in widget discovery, and data fetching through service or built-in adapters. | exp: func:_validate_widget_input(body: WidgetInstanceInput, store: SettingsStore) → None, call:store.get_service, call:get_service_definition, call:definition.widget_kind, call:validate_config, call:is_builtin_kind, call:validate_builtin_config, raise:HTTPException, func:list_builtin_kinds() → list[BuiltinWidgetKindInfo], call:BuiltinWidgetKindInfo, call:BUILTIN_WIDGET_KINDS.values, func:list_instances(store) → list[dict[str, Any]], call:WidgetInstance(**widget).model_dump, call:store.list_widgets, func:create_instance(body: WidgetInstanceInput, store) → dict[str, Any], call:_validate_widget_input, call:store.upsert_widget, call:body.model_dump, call:WidgetInstance(**widget).model_dump, func:update_instance(widget_id: str, body: WidgetInstanceInput, store) → dict[str, Any], call:store.get_widget, call:_validate_widget_input, call:store.upsert_widget, call:body.model_dump, call:WidgetInstance(**widget).model_dump, raise:HTTPException, func:delete_instance(widget_id: str, store) → dict[str, str], call:store.get_widget, call:store.delete_widget, raise:HTTPException, func:fetch_data(widget_id: str, store) → dict[str, Any], call:store.get_widget, call:widget.get, call:store.get_service, call:WidgetDataResponse( widget_id=widget_id, error=f"Service {service_id} not found", fetched_at=int(time.time()), ).model_dump, call:int, call:time.time, call:service_row.get, call:WidgetDataResponse( widget_id=widget_id, error="Service is disabled", fetched_at=int(time.time()), ).model_dump, call:get_service_adapter, call:WidgetDataResponse( widget_id=widget_id, error=f"No adapter for service type {service_row['service_type']}", fetched_at=int(time.time()), ).model_dump, call:build_service_record, call:get_builtin_adapter, call:WidgetDataResponse( widget_id=widget_id, error=f"Unknown built-in widget kind: {widget_kind}", fetched_at=int(time.time()), ).model_dump, call:adapter.fetch, call:logger.exception, call:WidgetDataResponse( widget_id=widget_id, data=data if "error" not in data else None, error=data.get("error"), fetched_at=int(time.time()), ).model_dump, call:data.get, raise:HTTPException | dep: logging, time, typing, fastapi, media_library_viewer_api.dependencies, media_library_viewer_api.integrations.base, media_library_viewer_api.integrations.registry, media_library_viewer_api.models.widgets, media_library_viewer_api.services.settings_store, media_library_viewer_api.widgets.builtin, media_library_viewer_api.widgets.sources +## arch +Modular APIRouter-per-domain pattern where each module defines a FastAPI router with endpoints for its concern, aggregated by the parent application; routers delegate business logic to services/workers and interact with remote systems via SSH and external APIs. +## tags +call:, service, raise:httpexception, get, backup, media_library_viewer_api, ssh, call:logger.info +## symbols +- RunJobRequest +- MonitoringMachineInput +- SSHKeyInput +- SSHKeyGenerateInput +- ResetLocalDatabaseInput +- TaskInput +- RunTaskRequest +- _get_or_create_job +## workflows +- change routers behavior + read: __init__.py, backups.py, dashboard.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/services/.pi-map.index.md b/backend/src/media_library_viewer_api/services/.pi-map.index.md new file mode 100644 index 0000000..0dd5c0e --- /dev/null +++ b/backend/src/media_library_viewer_api/services/.pi-map.index.md @@ -0,0 +1,33 @@ +# backend/src/media_library_viewer_api/services (index) +dir: backend/src/media_library_viewer_api/services + +## role +Backend service layer providing business logic for media library management, including media indexing, backup monitoring, email notifications, SSH task execution, settings persistence, and security. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- backup_alert_engine.py +- backup_poller.py +- db_maintenance.py +- known_hosts.py +- mail_queue.py +- mailer.py +- mailer_impl.py +- media_index.py +- media_index_impl.py +- secrets.py +- settings_store.py +- targets.py +- task_runner.py +## links +index: backend/src/media_library_viewer_api/services/.pi-map.index.md +map: backend/src/media_library_viewer_api/services/.pi-map.md +## workflows +- change services behavior + read: __init__.py, backup_alert_engine.py, backup_poller.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/services/.pi-map.md b/backend/src/media_library_viewer_api/services/.pi-map.md new file mode 100644 index 0000000..74b86df --- /dev/null +++ b/backend/src/media_library_viewer_api/services/.pi-map.md @@ -0,0 +1,40 @@ +# backend/src/media_library_viewer_api/services +dir: backend/src/media_library_viewer_api/services + +index: backend/src/media_library_viewer_api/services/.pi-map.index.md + +## role +Backend service layer providing business logic for media library management, including media indexing, backup monitoring, email notifications, SSH task execution, settings persistence, and security. +## files +- __init__.py | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +- backup_alert_engine.py | Generates alerts for backup job runs based on failure status, size/duration anomalies compared to historical medians, and missed schedules. | exp: func:generate_alerts_for_run(run: dict[str, Any], previous_runs: list[dict[str, Any]], job: dict[str, Any] | None) → list[dict[str, Any]], call:alerts.append, call:run.get, call:r.get, call:len, call:statistics.median, func:check_missed_schedules(jobs: list[dict[str, Any]], get_latest_run: callable, existing_alerts: list[dict[str, Any]]) → list[dict[str, Any]], call:int, call:time.time, call:job.get, call:get_latest_run, call:alerts.append, call:any | dep: statistics, typing, time +- backup_poller.py | Runs a background daemon thread that periodically checks backup jobs for missed schedules, creates alerts, and prunes old resolved alerts. | exp: class:BackupAlertPoller, method:__init__(self) → None, call:threading.Event, call:threading.Lock, method:start(self) → None, call:self._thread.is_alive, call:self._stop_event.clear, call:threading.Thread, call:self._thread.start, method:stop(self, timeout) → None, call:self._stop_event.set, call:thread.join, method:_run(self) → None, call:self._stop_event.wait, call:get_settings_store, call:self._stop_event.is_set, call:self._run_cycle, call:logger.exception, method:_run_cycle(self, store: SettingsStore) → None, call:time.perf_counter, call:store.list_backup_jobs, call:store.list_backup_alerts, call:check_missed_schedules, call:store.get_latest_backup_run, call:store.create_backup_alert, call:int, call:time.time, call:store.prune_backup_alerts, func:get_backup_poller() → BackupAlertPoller | dep: logging, threading, time, typing, .backup_alert_engine, .settings_store, backup_alert_engine, settings_store +- db_maintenance.py | Provides safe deletion helpers for SQLite databases including their WAL and SHM sidecar files. | exp: func:sqlite_sidecar_paths(db_path: Path) → list[Path], call:Path, call:db_path.with_name, func:remove_sqlite_database(db_path: Path) → list[str], call:sqlite_sidecar_paths, call:path.unlink, call:removed.append, call:str | dep: pathlib +- known_hosts.py | Synthesizes and manages SSH known_hosts entries by fetching server keys dynamically rather than mounting host configuration, enabling strict host-key checking in containerized environments. | exp: func:_host_alias(host: str, port: int) → str, call:int, func:_fetch_server_key(host: str, port: int, timeout) → paramiko.PKey, call:socket.create_connection, call:int, call:paramiko.Transport, call:transport.start_client, call:transport.get_remote_server_key, call:transport.close, call:sock.close, raise:RuntimeError, func:has_known_host(host: str, port: int, known_hosts_path: Path) → bool, call:known_hosts_path.exists, call:_host_alias, call:paramiko.HostKeys, call:host_keys.load, call:str, call:host_keys.lookup, func:ensure_known_host(host: str, port: int, known_hosts_path: Path, strict) → bool, call:known_hosts_path.parent.mkdir, call:_fetch_server_key, call:_host_alias, call:paramiko.HostKeys, call:known_hosts_path.exists, call:host_keys.load, call:str, call:host_keys.lookup, call:host_key.get_name, call:existing[key_type].get_base64, call:host_key.get_base64, call:host_keys.add, call:host_keys.save, call:logger.info, raise:RuntimeError, func:ensure_known_hosts_for_machines(machines: list[dict[str, Any]], known_hosts_path: Path, strict) → int, call:str(machine.get("mode") or "").lower, call:machine.get, call:str(machine.get("host") or "").strip, call:int, call:ensure_known_host | dep: logging, socket, pathlib, typing, paramiko +- mail_queue.py | Implements an in-process background queue with a single worker thread for asynchronous SMTP email delivery to keep API requests responsive. | exp: class:QueuedEmailMessage, class:MailQueue, method:__init__(self) → None, call:queue.Queue, call:threading.Event, call:threading.Lock, method:start(self) → None, call:self._thread.is_alive, call:self._stop_event.clear, call:threading.Thread, call:self._thread.start, call:logger.info, method:stop(self, timeout) → None, call:self._stop_event.set, call:self._queue.put, call:thread.join, call:thread.is_alive, call:logger.warning, call:logger.info, method:enqueue(self, settings: Any, recipients: list[str], subject: str, html_body: str, text_body, attachments) → str, call:uuid.uuid4, call:QueuedEmailMessage, call:list, call:time.time, call:self._queue.put, call:logger.info, call:len, method:status(self) → dict[str, Any], call:bool, call:self._thread.is_alive, call:self._stop_event.is_set, method:_run(self) → None, call:self._stop_event.is_set, call:self._queue.get, call:max, call:time.time, call:logger.info, call:len, call:send_email_message, call:(result.get("selected_mode") or {}).get, call:result.get, call:record_mail_queue, call:describe_smtp_error, call:logger.exception, call:getattr, call:self._queue.task_done, func:get_mail_queue() → MailQueue | dep: logging, queue, threading, time, uuid, dataclasses, typing, media_library_viewer_api.observability, media_library_viewer_api.services.mailer +- mailer.py | Re-exports all symbols from mailer_impl module to provide a public interface for mail functionality | dep: .mailer_impl, mailer_impl +- mailer_impl.py | SMTP email sending implementation with HTML-to-text conversion, attachment handling, multi-mode connection attempts, and sender fallback retry logic. | exp: class:EmailAttachment, class:_HTMLToTextParser, method:__init__(self) → None, call:super().__init__, method:handle_starttag(self, tag: str, attrs), call:tag.lower, call:self.parts[-1].endswith, call:self.parts.append, method:handle_endtag(self, tag: str) → None, call:tag.lower, call:self.parts[-1].endswith, call:self.parts.append, method:handle_data(self, data: str) → None, call:self.parts.append, method:text(self) → str, call:"".join, func:html_to_text(html: str) → str, call:_HTMLToTextParser, call:parser.feed, call:parser.text, call:line.rstrip, call:text.splitlines, call:"\n".join(line for line in lines if line).strip, func:_from_address(settings: object) → str, call:str(getattr(settings, "smtp_from_address", "") or "").strip, call:getattr, call:str(getattr(settings, "smtp_username", "") or "").strip, raise:ValueError, func:validate_smtp_settings(settings: object) → None, call:str(getattr(settings, "smtp_host", "") or "").strip, call:getattr, call:_from_address, raise:ValueError, func:_smtp_settings(settings: object) → dict[str, object], call:str(getattr(settings, "smtp_host", "") or "").strip, call:getattr, call:int, call:str(getattr(settings, "smtp_username", "") or "").strip, call:bool, raise:ValueError, func:_smtp_mode_candidates(settings: object) → list[dict[str, Any]], call:_smtp_settings, call:dict, call:str(base["smtp_host"]).lower, call:any, call:candidates.append, func:_probe_smtp_connection(mode: dict[str, Any]) → None, call:ssl.create_default_context, call:str, call:int, call:bool, call:smtplib.SMTP_SSL, call:smtplib.SMTP, call:smtp.ehlo, call:smtp.starttls, call:smtp.login, call:smtp.noop, func:_smtp_sender_not_authorized(error: Exception) → bool, call:getattr, call:isinstance, call:raw_error.decode, call:str, call:f"{code} {raw_error_text} {error}".lower, func:_smtp_attempt_metadata(mode: dict[str, Any]) → dict[str, Any], call:str, call:mode.get, call:int, call:bool, func:describe_smtp_error(error: Exception) → str, call:chain.append, call:isinstance, call:str(item).strip, call:text.lower, call:getattr, func:build_email_message(settings: object, recipients: list[str], subject: str, html_body: str, text_body: str, attachments, sender_address, reply_to_address) → tuple[EmailMessage, str], call:_from_address, call:str(getattr(settings, "smtp_from_name", "") or "").strip, call:getattr, call:EmailMessage, call:formataddr, call:text_body.strip, call:html_to_text, call:html_body.strip, call:msg.set_content, call:msg.add_alternative, call:mimetypes.guess_type, call:content_type.split, call:msg.add_attachment, func:_send_email_via_mode(mode: dict[str, Any], message: EmailMessage, recipients: list[str], from_address: str) → None, call:ssl.create_default_context, call:str, call:int, call:bool, call:smtplib.SMTP_SSL, call:smtplib.SMTP, call:smtp.ehlo, call:smtp.starttls, call:smtp.login, call:smtp.send_message, func:send_email_message(settings: object, recipients: list[str], subject: str, html_body: str, text_body, attachments) → dict[str, object], call:list, call:_from_address, call:str(getattr(settings, "smtp_username", "") or "").strip, call:getattr, call:build_email_message, call:logger.info, call:len, call:_smtp_mode_candidates, call:_smtp_attempt_metadata, call:_send_email_via_mode, call:describe_smtp_error, call:attempts.append, call:_smtp_sender_not_authorized, call:logger.warning, raise:ValueError, raise:RuntimeError | dep: logging, mimetypes, smtplib, socket, ssl, dataclasses, email.message, email.utils, html.parser, typing +- media_index.py | Re-exports all symbols from the media_index_impl module to provide a public API interface for media indexing services | dep: media_library_viewer_api.services.media_index_impl +- media_index_impl.py | SQLite-backed media inventory service that indexes Jellyfin media libraries with queryable, sortable, and filterable data for UI-agnostic consumption. | exp: class:MediaIndexBuildCancelled, class:MediaIndexStatus, class:MediaIndex, method:__init__(self, db_path), call:Path, call:self.db_path.parent.mkdir, method:connect(self) → sqlite3.Connection, call:sqlite3.connect, call:conn.execute, method:init_schema(self) → None, call:self.connect, call:conn.executescript, method:set_metadata(self, key: str, value: str | int | float) → None, call:self.init_schema, call:self.connect, call:conn.execute, call:str, method:replace_items(self, rows: Iterable[dict[str, Any]]) → int, call:self.init_schema, call:list, call:",".join, call:len, call:self.connect, call:conn.execute, call:conn.executemany, call:','.join, call:row.get, call:str, call:int, call:time.time, method:status(self) → MediaIndexStatus, call:self.db_path.exists, call:MediaIndexStatus, call:self.connect, call:int, call:conn.execute("SELECT COUNT(*) FROM media_items").fetchone, call:conn.execute("SELECT key, value FROM index_metadata").fetchall, call:meta.get, call:str(updated_at_raw).isdigit, call:time.strftime, call:time.localtime, call:float, call:str(meta.get(key, str(default))).strip().lower, call:_bool, call:_float, call:_int, method:query(self, library_id, library_ids, media_types, search, hdr_filter, sort_key, sort_order, limit, offset) → tuple[list[dict[str, Any]], int], call:self.init_schema, call:where.append, call:",".join, call:len, call:params.extend, call:params.append, call:search.lower, call:" AND ".join, call:SORT_COLUMNS.get, call:self.connect, call:int, call:conn.execute("SELECT COUNT(*) FROM media_items" + where_sql, params).fetchone, call:conn.execute( "SELECT * FROM media_items" + where_sql + order_sql + " LIMIT ? OFFSET ?", [*params, int(limit), int(offset)], ).fetchall, call:display_media_row, call:dict, func:_estimate_remaining_seconds(elapsed_seconds: float, progress: float | None) → float | None, call:max, call:min, func:build_media_index(client: JellyfinClient, user_id: str, libraries: list[dict[str, Any]], index, page_size, media_root, fallback_prefix, progress_callback, should_cancel) → int, call:MediaIndex, call:time.perf_counter, call:should_cancel, call:progress_callback, call:_estimate_remaining_seconds, call:len, call:ensure_not_cancelled, call:logger.info, call:emit, call:enumerate, call:library.get, call:client.items, call:response.get, call:int, call:max, call:normalized_rows.extend, call:resolve_remote_media_path, call:row.get, call:normalize_media_item, call:logger.debug, call:index.replace_items, call:index.set_metadata, raise:MediaIndexBuildCancelled, func:ensure_not_cancelled() → None, call:should_cancel, raise:MediaIndexBuildCancelled, func:emit(stage: str, message: str) → None, call:time.perf_counter, call:progress_callback, call:_estimate_remaining_seconds, call:len | dep: logging, sqlite3, time, dataclasses, pathlib, typing, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.domain.media, media_library_viewer_api.path_utils, jellyfin_client, media_domain, path_utils +- secrets.py | Provides symmetric authenticated encryption for service secrets using Fernet with a mandatory environment variable master key. | exp: class:EncryptionKeyError, func:get_encryption_key() → bytes, call:os.environ.get, call:raw.strip().encode, call:Fernet, raise:EncryptionKeyError, func:reset_encryption_key_cache() → None, call:get_encryption_key.cache_clear, func:_fernet() → Fernet, call:Fernet, call:get_encryption_key, func:encrypt_value(plaintext: str) → str, call:_fernet().encrypt(plaintext.encode()).decode, call:plaintext.encode, func:decrypt_value(ciphertext: str) → str, call:_fernet().decrypt(ciphertext.encode()).decode, call:ciphertext.encode, raise:EncryptionKeyError, func:encrypt_secrets(values: dict[str, str]) → dict[str, str], call:_fernet, call:fernet.encrypt(value.encode()).decode, call:value.encode, call:values.items, func:decrypt_secrets(blob: dict[str, str]) → dict[str, str], call:_fernet, call:blob.items, call:fernet.decrypt(ciphertext.encode()).decode, call:ciphertext.encode, raise:EncryptionKeyError, func:generate_development_key() → str, call:Fernet.generate_key().decode, func:validate_encryption_key() → None, call:get_encryption_key | dep: os, functools, cryptography.fernet, functools.lru_cache +- settings_store.py | Manages persistent application settings, machine definitions, SSH keys, dashboard widgets, saved tasks, backup jobs, and service configurations in a SQLite database with schema migrations and WAL mode. | exp: class:SettingsStore, method:__init__(self, db_path), call:Path, call:self.db_path.parent.mkdir, method:connect(self) → sqlite3.Connection, call:sqlite3.connect, call:conn.execute, method:init_schema(self) → None, call:self.connect, call:conn.execute("PRAGMA table_info(ssh_keys)").fetchall, call:conn.execute("PRAGMA table_info(saved_tasks)").fetchall, call:conn.execute("PRAGMA table_info(dashboard_widgets)").fetchall, method:_row_to_machine(self, row: sqlite3.Row) → dict[str, Any], call:json.loads, call:self._normalize_services, call:data.get, call:bool, call:int, method:_normalize_machine_payload(self, payload: dict[str, Any], machine_id) → dict[str, Any], call:self.get_machine, call:str(payload.get("id") or machine_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("mode") or (current or {}).get("mode") or "local").strip().lower, call:(current or {}).get, call:bool, call:str(payload.get("name") or (current or {}).get("name") or "").strip, call:self._normalize_services, call:str( payload.get(field) if payload.get(field) is not None else (current or {}).get(field, default) or default ).strip, call:_current_str, call:int, method:_seed_local_machine(self) → None, call:_default_local_machine, call:int, call:time.time, call:machine.get, call:self.connect, call:conn.execute, call:json.dumps, method:_seed_dashboard_widgets(self) → None, method:ensure_defaults(self) → None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT COUNT(*) FROM monitoring_machines").fetchone, call:int, call:self._seed_local_machine, method:list_machines(self) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute( "SELECT * FROM monitoring_machines ORDER BY CASE WHEN id = ? THEN 0 ELSE 1 END, name COLLATE NOCASE", (LOCAL_MACHINE_ID,), ).fetchall, call:self._row_to_machine, method:get_machine(self, machine_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM monitoring_machines WHERE id = ?", (machine_id,)).fetchone, call:self._row_to_machine, method:get_machine_config(self, machine_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM monitoring_machines WHERE id = ?", (machine_id,)).fetchone, call:json.loads, call:bool, call:self._normalize_services, call:data.get, call:int, method:list_machines_for_service(self, service: str) → list[dict[str, Any]], call:self.list_machines, call:machine.get, method:get_machine_for_service(self, service: str, machine_id) → dict[str, Any] | None, call:self.get_machine, call:machine.get, call:self.list_machines_for_service, method:upsert_machine(self, payload: dict[str, Any], machine_id) → dict[str, Any], call:self.init_schema, call:self._normalize_machine_payload, call:int, call:time.time, call:machine.get, call:self.connect, call:conn.execute( "SELECT created_at FROM monitoring_machines WHERE id = ?", (machine["id"],), ).fetchone, call:json.dumps, call:self.get_machine, method:delete_machine(self, machine_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, method:_row_to_ssh_key(self, row: sqlite3.Row, usage_count) → dict[str, Any], call:self._private_key_summary, call:str, call:bool, method:_normalize_ssh_key_payload(self, payload: dict[str, Any], key_id) → dict[str, Any], call:self.get_ssh_key, call:str(payload.get("id") or key_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("name") or (current or {}).get("name") or key_id).strip, call:(current or {}).get, call:str( payload.get("notes") if payload.get("notes") is not None else (current or {}).get("notes", "") or "" ).strip, call:self._private_key_summary, call:str( payload.get("public_key") if payload.get("public_key") is not None else (current or {}).get("public_key", "") or summary["public_key"] or "" ).strip, call:str( payload.get("fingerprint") if payload.get("fingerprint") is not None else (current or {}).get("fingerprint", "") or summary["fingerprint"] or "" ).strip, method:list_ssh_keys(self) → list[dict[str, Any]], call:self.init_schema, call:self.list_machines, call:str(machine.get("ssh_key_id") or "").strip, call:machine.get, call:usage_counts.get, call:self.connect, call:conn.execute("SELECT * FROM ssh_keys ORDER BY name COLLATE NOCASE").fetchall, call:self._row_to_ssh_key, method:get_ssh_key(self, key_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM ssh_keys WHERE id = ?", (key_id,)).fetchone, call:self._private_key_summary, call:str, method:upsert_ssh_key(self, payload: dict[str, Any], key_id) → dict[str, Any], call:self.init_schema, call:self._normalize_ssh_key_payload, call:int, call:time.time, call:self.connect, call:conn.execute("SELECT created_at FROM ssh_keys WHERE id = ?", (key["id"],)).fetchone, call:self.get_ssh_key, method:delete_ssh_key(self, key_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, method:_row_to_task(self, row: sqlite3.Row) → dict[str, Any], call:bool, method:_normalize_task_payload(self, payload: dict[str, Any], task_id) → dict[str, Any], call:self.get_task, call:str(payload.get("id") or task_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("name") or (current or {}).get("name") or task_id).strip, call:(current or {}).get, call:str(payload.get("task_type") or (current or {}).get("task_type") or "shell").strip().lower, call:bool, call:str( payload.get("default_service_id") if payload.get("default_service_id") is not None else (current or {}).get("default_service_id", "") or "" ).strip, call:str( payload.get("notes") if payload.get("notes") is not None else (current or {}).get("notes", "") or "" ).strip, method:list_tasks(self) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM saved_tasks ORDER BY name COLLATE NOCASE").fetchall, call:self._row_to_task, method:get_task(self, task_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM saved_tasks WHERE id = ?", (task_id,)).fetchone, call:self._row_to_task, method:upsert_task(self, payload: dict[str, Any], task_id) → dict[str, Any], call:self.init_schema, call:self._normalize_task_payload, call:int, call:time.time, call:self.connect, call:conn.execute("SELECT created_at FROM saved_tasks WHERE id = ?", (task["id"],)).fetchone, call:self.get_task, method:delete_task(self, task_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, method:_row_to_shortcut(self, row: sqlite3.Row) → dict[str, Any], call:json.loads, call:bool, call:target.get, method:_normalize_shortcut_payload(self, payload: dict[str, Any], shortcut_id) → dict[str, Any], call:self.get_shortcut, call:str(payload.get("id") or shortcut_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("shortcut_type") or (current or {}).get("shortcut_type") or "website").strip().lower, call:(current or {}).get, call:str(payload.get("label") or (current or {}).get("label") or "").strip, call:bool, call:str( payload.get(field) if payload.get(field) is not None else (current or {}).get(field, default) or default ).strip, call:_field, method:list_shortcuts(self) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM dashboard_shortcuts ORDER BY label COLLATE NOCASE").fetchall, call:self._row_to_shortcut, method:get_shortcut(self, shortcut_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM dashboard_shortcuts WHERE id = ?", (shortcut_id,)).fetchone, call:self._row_to_shortcut, method:upsert_shortcut(self, payload: dict[str, Any], shortcut_id) → dict[str, Any], call:self.init_schema, call:self._normalize_shortcut_payload, call:int, call:time.time, call:self.connect, call:conn.execute( "SELECT created_at FROM dashboard_shortcuts WHERE id = ?", (shortcut["id"],), ).fetchone, call:json.dumps, call:self.get_shortcut, method:delete_shortcut(self, shortcut_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, method:_row_to_job(self, row: sqlite3.Row) → dict[str, Any], method:_normalize_backup_job_payload(self, payload: dict[str, Any], job_id) → dict[str, Any], call:self.get_backup_job, call:str(payload.get("id") or job_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("name") or (current or {}).get("name") or job_id).strip, call:(current or {}).get, call:str( payload.get("source") if payload.get("source") is not None else (current or {}).get("source", "") or "" ).strip, call:str( payload.get("target") if payload.get("target") is not None else (current or {}).get("target", "") or "" ).strip, call:int, method:get_backup_job_by_name(self, name: str) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM backup_jobs WHERE name = ?", (name,)).fetchone, call:self._row_to_job, method:upsert_backup_job(self, payload: dict[str, Any]) → dict[str, Any], call:self.init_schema, call:self._normalize_backup_job_payload, call:int, call:time.time, call:self.connect, call:conn.execute("SELECT created_at FROM backup_jobs WHERE id = ?", (job["id"],)).fetchone, call:self.get_backup_job, method:get_backup_job(self, job_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM backup_jobs WHERE id = ?", (job_id,)).fetchone, call:self._row_to_job, method:list_backup_jobs(self) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM backup_jobs ORDER BY created_at DESC").fetchall, call:self._row_to_job, method:_row_to_run(self, row: sqlite3.Row) → dict[str, Any], call:json.loads, method:create_backup_run(self, payload: dict[str, Any]) → dict[str, Any], call:self.init_schema, call:str(payload.get("id") or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:int, call:time.time, call:json.dumps, call:self.connect, call:conn.execute, call:self.get_backup_run, method:get_backup_run(self, run_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM backup_runs WHERE id = ?", (run_id,)).fetchone, call:self._row_to_run, method:list_backup_runs(self, job_id, status, limit) → list[dict[str, Any]], call:self.init_schema, call:clauses.append, call:params.append, call:' AND '.join, call:max, call:min, call:int, call:self.connect, call:conn.execute(sql, params).fetchall, call:self._row_to_run, method:get_latest_backup_run(self, job_id: str) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute( "SELECT * FROM backup_runs WHERE job_id = ? ORDER BY created_at DESC LIMIT 1", (job_id,), ).fetchone, call:self._row_to_run, method:_row_to_alert(self, row: sqlite3.Row) → dict[str, Any], call:bool, method:create_backup_alert(self, payload: dict[str, Any]) → dict[str, Any], call:self.init_schema, call:str(payload.get("id") or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:int, call:time.time, call:self.connect, call:conn.execute, call:self.get_backup_alert, method:get_backup_alert(self, alert_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM backup_alerts WHERE id = ?", (alert_id,)).fetchone, call:self._row_to_alert, method:list_backup_alerts(self, job_id, acknowledged, severity) → list[dict[str, Any]], call:self.init_schema, call:clauses.append, call:params.append, call:' AND '.join, call:self.connect, call:conn.execute(sql, params).fetchall, call:self._row_to_alert, method:acknowledge_backup_alert(self, alert_id: str) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute, call:self.get_backup_alert, method:resolve_backup_alerts_for_job(self, job_id: str, alert_type: str) → int, call:self.init_schema, call:int, call:time.time, call:self.connect, call:conn.execute, method:prune_backup_alerts(self, cutoff_ts: int) → int, call:self.init_schema, call:self.connect, call:conn.execute, call:int, method:get_settings(self) → dict[str, Any], call:self.init_schema, call:self.connect, call:conn.execute("SELECT key, value FROM app_settings").fetchall, method:get_setting(self, key: str, default) → Any, call:self.init_schema, call:self.connect, call:conn.execute("SELECT value FROM app_settings WHERE key = ?", (key,)).fetchone, method:update_setting(self, key: str, value: str) → None, call:self.init_schema, call:int, call:time.time, call:self.connect, call:conn.execute, method:_row_to_widget(self, row: sqlite3.Row) → dict[str, Any], call:row.keys, call:json.loads, call:bool, call:int, method:_normalize_widget_payload(self, payload: dict[str, Any], widget_id) → dict[str, Any], call:self.get_widget, call:str(payload.get("id") or widget_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("service_id") or (current or {}).get("service_id") or "").strip, call:(current or {}).get, call:str(payload.get("widget_kind") or (current or {}).get("widget_kind", "")).strip, call:str(payload.get("title") or (current or {}).get("title", "") or "").strip, call:isinstance, call:_validate_config_keys, call:bool, call:int, method:list_widgets(self) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM dashboard_widgets ORDER BY sort_order ASC, created_at ASC").fetchall, call:self._row_to_widget, method:get_widget(self, widget_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM dashboard_widgets WHERE id = ?", (widget_id,)).fetchone, call:self._row_to_widget, method:upsert_widget(self, payload: dict[str, Any], widget_id) → dict[str, Any], call:self.init_schema, call:self._normalize_widget_payload, call:int, call:time.time, call:self.connect, call:conn.execute( "SELECT created_at FROM dashboard_widgets WHERE id = ?", (widget["id"],), ).fetchone, call:json.dumps, call:self.get_widget, method:delete_widget(self, widget_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, method:_row_to_service(self, row: sqlite3.Row) → dict[str, Any], call:json.loads, call:bool, method:list_services(self, service_type) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute( "SELECT * FROM services WHERE service_type = ? ORDER BY name ASC", (service_type,), ).fetchall, call:conn.execute("SELECT * FROM services ORDER BY name ASC").fetchall, call:self._row_to_service, method:get_service(self, service_id: str) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM services WHERE id = ?", (service_id,)).fetchone, call:self._row_to_service, method:_normalize_service_payload(self, payload: dict[str, Any], service_id) → dict[str, Any], call:self.get_service, call:str(payload.get("id") or service_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("service_type") or (current or {}).get("service_type", "")).strip, call:(current or {}).get, call:str(payload.get("name") or (current or {}).get("name", "") or "").strip, call:isinstance, call:bool, method:upsert_service(self, payload: dict[str, Any], secret_values, service_id) → dict[str, Any], call:self.init_schema, call:self._normalize_service_payload, call:int, call:time.time, call:self.get_service, call:dict, call:secret_values.items, call:secrets_blob.pop, call:encrypt_value, call:self.connect, call:conn.execute, call:json.dumps, method:delete_service(self, service_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute("PRAGMA table_info(dashboard_widgets)").fetchall, method:record_service_task_run(self, payload: dict[str, Any]) → dict[str, Any], call:self.init_schema, call:str, call:payload.get, call:uuid.uuid4, call:int, call:time.time, call:self.connect, call:conn.execute, method:list_service_task_runs(self, service_id, task_id, limit) → list[dict[str, Any]], call:self.init_schema, call:clauses.append, call:params.append, call:" AND ".join, call:int, call:self.connect, call:conn.execute( f"SELECT * FROM service_task_runs {where} ORDER BY created_at DESC LIMIT ?", params, ).fetchall, func:_default_local_machine() → dict[str, Any], call:get_settings, call:list, func:get_settings_store() → SettingsStore, call:SettingsStore | dep: json, sqlite3, time, uuid, io, pathlib, typing, paramiko, media_library_viewer_api.config, media_library_viewer_api.models.widgets +- targets.py | Builds a Prometheus HTTP service discovery target list of remote Node Exporter endpoints from configured SSH machines. | exp: func:_scrape_address(machine: dict[str, Any]) → str | None, call:machine.get, call:str(machine.get("node_exporter_scrape_host") or "").strip, call:str(machine.get("host") or "").strip, call:int, func:build_node_exporter_targets(store: SettingsStore) → list[dict[str, Any]], call:store.list_machines, call:machine.get, call:str(machine.get("mode") or "local").strip().lower, call:_scrape_address, call:targets.append | dep: logging, typing, media_library_viewer_api.services.settings_store +- task_runner.py | Provides a unified execution path for running saved tasks over SSH against ssh_tasks service instances, including client building, command rendering, execution, and run logging. | exp: class:TaskRunResult, func:build_ssh_client(store: SettingsStore, service: "ServiceRecord") → RemoteSSHClient, call:str(config.get("host") or "").strip, call:config.get, call:str(config.get("username") or "").strip, call:get_settings, call:str(config.get("ssh_key_id") or "").strip, call:store.get_ssh_key, call:ssh_key.get, call:service.secrets.get, call:RemoteSSHClient, call:int, raise:ValueError, func:_render_command(task: dict[str, Any]) → str, call:str(task.get("task_type") or "shell").lower, call:task.get, call:shlex.quote, raise:ValueError, func:run_saved_task(store: SettingsStore, task: dict[str, Any], service: "ServiceRecord", timeout) → TaskRunResult, call:int, call:service.config.get, call:build_ssh_client, call:_render_command, call:time.perf_counter, call:client.run, call:_record, call:str, call:logger.exception, call:task.get, call:TaskRunResult, func:_record(store: SettingsStore, task: dict[str, Any], service: "ServiceRecord", status: str, exit_status, duration_ms, stdout_tail, stderr_tail, error) → None, call:store.record_service_task_run, call:str, call:task.get | dep: logging, shlex, time, dataclasses, typing, media_library_viewer_api.clients.ssh, media_library_viewer_api.config, media_library_viewer_api.services.settings_store, media_library_viewer_api.widgets.sources, RemoteSSHClient, get_settings, SettingsStore, ServiceRecord +## arch +Modular service-oriented design with background daemon threads, SQLite persistence with WAL mode, implementation-module re-export pattern for public APIs, and in-process queues for asynchronous work. +## tags +call:str, call:conn.execute, call:self.connect, call:self., schema, call:self.init, call:int, call: +## symbols +- BackupAlertPoller +- QueuedEmailMessage +- MailQueue +- EmailAttachment +- _HTMLToTextParser +- MediaIndexBuildCancelled +- MediaIndexStatus +- MediaIndex +## workflows +- change services behavior + read: __init__.py, backup_alert_engine.py, backup_poller.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/widgets/.pi-map.index.md b/backend/src/media_library_viewer_api/widgets/.pi-map.index.md new file mode 100644 index 0000000..9f61fe3 --- /dev/null +++ b/backend/src/media_library_viewer_api/widgets/.pi-map.index.md @@ -0,0 +1,22 @@ +# backend/src/media_library_viewer_api/widgets (index) +dir: backend/src/media_library_viewer_api/widgets + +## role +Provides widget definitions, schemas, and data source adapters for rendering configurable dashboard widgets from both built-in and external service data. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- builtin.py +- sources.py +## links +index: backend/src/media_library_viewer_api/widgets/.pi-map.index.md +map: backend/src/media_library_viewer_api/widgets/.pi-map.md +## workflows +- change widgets behavior + read: __init__.py, builtin.py, sources.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/widgets/.pi-map.md b/backend/src/media_library_viewer_api/widgets/.pi-map.md new file mode 100644 index 0000000..66574e3 --- /dev/null +++ b/backend/src/media_library_viewer_api/widgets/.pi-map.md @@ -0,0 +1,29 @@ +# backend/src/media_library_viewer_api/widgets +dir: backend/src/media_library_viewer_api/widgets + +index: backend/src/media_library_viewer_api/widgets/.pi-map.index.md + +## role +Provides widget definitions, schemas, and data source adapters for rendering configurable dashboard widgets from both built-in and external service data. +## files +- __init__.py | Marks the directory as a Python package for the widget subsystem. +- builtin.py | Defines built-in widget kinds that don't require external services, providing their configurations, schemas, and validation. | exp: class:StaticConfig, func:get_builtin_widget_kind(kind: str) → WidgetKind | None, call:BUILTIN_WIDGET_KINDS.get, func:is_builtin_kind(kind: str) → bool, func:builtin_widget_kind_models() → dict[str, type], call:Field, func:validate_builtin_config(kind: str, config: dict[str, Any]) → dict[str, Any], call:builtin_widget_kind_models, call:models.get, call:dict, call:model_cls.model_validate(config or {}).model_dump | dep: typing, media_library_viewer_api.integrations.base, pydantic +- sources.py | Defines widget source adapters that fetch and transform data from various external services (Grafana, Prometheus, Jellyfin, etc.) into dashboard widget payloads. | exp: class:ServiceRecord, class:WidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], class:BackupsWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:get_settings_store, call:build_backup_dashboard_summary, call:summary.model_dump, call:logger.exception, class:StaticWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:config.get, class:GrafanaWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:str(service.config.get("base_url") or "").rstrip, call:service.config.get, call:config.get, call:logger.exception, class:PrometheusWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:str(service.config.get("base_url") or "").rstrip, call:service.config.get, call:int, call:config.get, call:asyncio.wait_for, call:asyncio.to_thread, call:response.raise_for_status, call:response.json, call:payload.get, call:logger.exception, class:AlertmanagerWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:str(service.config.get("base_url") or "").rstrip, call:service.config.get, call:int, call:config.get, call:service.secrets.get, call:asyncio.wait_for, call:asyncio.to_thread, call:response.raise_for_status, call:response.json, call:payload.get, call:isinstance, call:summarize_alerts, call:logger.exception, class:JellyfinWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:str, call:service.config.get, call:service.secrets.get, call:int, call:asyncio.wait_for, call:asyncio.to_thread, call:_map_sessions_to_activity_rows, call:logger.exception, class:SshTaskWidgetSource, method:fetch(self, service: ServiceRecord | None, widget_kind: str, config: dict[str, Any]) → dict[str, Any], call:get_settings_store, call:config.get, call:store.get_task, call:task.get, call:int, call:service.config.get, call:asyncio.wait_for, call:asyncio.to_thread, call:_record_timeout, call:logger.exception, func:build_service_record(store: SettingsStore, service_row: dict[str, Any]) → ServiceRecord, call:ServiceRecord, call:service_row.get, call:decrypt_secrets, call:bool, func:_record_timeout(service: ServiceRecord | None, config: dict[str, Any], timeout: int) → None, call:get_settings_store, call:store.record_service_task_run, call:str, call:config.get, call:logger.exception, func:get_service_adapter(service_type: str) → WidgetSource | None, call:SERVICE_ADAPTERS.get, func:get_builtin_adapter(kind: str) → WidgetSource | None, call:BUILTIN_ADAPTERS.get | dep: asyncio, logging, dataclasses, typing, requests, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.domain.dashboard, media_library_viewer_api.integrations.alertmanager, media_library_viewer_api.services.settings_store, media_library_viewer_api.services.task_runner, media_library_viewer_api.services.secrets +## arch +Adapter pattern with modular source integrations that normalize heterogeneous external API responses into a unified widget payload format, complemented by schema-validated built-in widget configurations. +## tags +widget, source, fetch, call:logger.exception, builtin, call:config.get, record, call:str +## symbols +- StaticConfig +- ServiceRecord +- WidgetSource +- BackupsWidgetSource +- StaticWidgetSource +- GrafanaWidgetSource +- PrometheusWidgetSource +- AlertmanagerWidgetSource +## workflows +- change widgets behavior + read: __init__.py, builtin.py, sources.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/workers/.pi-map.index.md b/backend/src/media_library_viewer_api/workers/.pi-map.index.md new file mode 100644 index 0000000..0d0b999 --- /dev/null +++ b/backend/src/media_library_viewer_api/workers/.pi-map.index.md @@ -0,0 +1,21 @@ +# backend/src/media_library_viewer_api/workers (index) +dir: backend/src/media_library_viewer_api/workers + +## role +Background worker package providing subprocess-based media index building from Jellyfin libraries with progress tracking and cancellation support. +## parent +index: backend/src/media_library_viewer_api/.pi-map.index.md +map: backend/src/media_library_viewer_api/.pi-map.md +## children +- +## files +- __init__.py +- media_index_worker.py +## links +index: backend/src/media_library_viewer_api/workers/.pi-map.index.md +map: backend/src/media_library_viewer_api/workers/.pi-map.md +## workflows +- change workers behavior + read: __init__.py, media_index_worker.py +## dirty +- diff --git a/backend/src/media_library_viewer_api/workers/.pi-map.md b/backend/src/media_library_viewer_api/workers/.pi-map.md new file mode 100644 index 0000000..d7a56f1 --- /dev/null +++ b/backend/src/media_library_viewer_api/workers/.pi-map.md @@ -0,0 +1,28 @@ +# backend/src/media_library_viewer_api/workers +dir: backend/src/media_library_viewer_api/workers + +index: backend/src/media_library_viewer_api/workers/.pi-map.index.md + +## role +Background worker package providing subprocess-based media index building from Jellyfin libraries with progress tracking and cancellation support. +## files +- __init__.py | Marks the directory as a Python package for worker entrypoints used in background task processing. +- media_index_worker.py | Subprocess worker that builds a media index from Jellyfin libraries with progress tracking and cooperative cancellation support. | exp: func:_set_build_metadata(index: MediaIndex, state: dict[str, Any]) → None, call:state.items, call:index.set_metadata, func:_cancel_requested(index: MediaIndex) → bool, call:index.status, func:_start_state(index: MediaIndex, pid: int, library_count: int) → None, call:_set_build_metadata, func:_progress_callback(index: MediaIndex, pid: int, state: dict[str, Any]) → None, call:_set_build_metadata, call:state.get, func:run_build(final_index_path: str | Path, staging_index_path: str | Path) → int, call:get_settings, call:configure_logging, call:logger.info, call:describe_settings, call:get_jellyfin_client, call:get_user_id, call:client.libraries, call:MediaIndex, call:os.getpid, call:time.perf_counter, call:Path, call:staging_path.unlink, call:len, call:_start_state, call:build_media_index, call:_progress_callback, call:_cancel_requested, call:os.replace, call:completed_index.status, call:_set_build_metadata, call:logger.exception, call:str, call:staging_path.exists, func:main() → int, call:argparse.ArgumentParser, call:parser.add_argument, call:parser.parse_args, call:run_build | dep: argparse, logging, os, time, pathlib, typing, media_library_viewer_api.config, media_library_viewer_api.dependencies, media_library_viewer_api.logging_utils, media_library_viewer_api.services.media_index +## arch +Subprocess worker pattern with cooperative cancellation, designed to be spawned and monitored by a parent process for long-running media indexing tasks. +## tags +call:, metadata, set, index, call:get, media, progress, worker +## symbols +- _set_build_metadata +- _cancel_requested +- _start_state +- _progress_callback +- run_build +- main +- call:state.items +- call:index.set_metadata +## workflows +- change workers behavior + read: __init__.py, media_index_worker.py +## dirty +- diff --git a/backend/tests/.pi-map.index.md b/backend/tests/.pi-map.index.md new file mode 100644 index 0000000..43e49cf --- /dev/null +++ b/backend/tests/.pi-map.index.md @@ -0,0 +1,39 @@ +# backend/tests (index) +dir: backend/tests + +## role +Test suite providing unit and integration coverage for the backend's API endpoints, clients, configuration, utilities, and services. +## parent +index: backend/.pi-map.index.md +map: backend/.pi-map.md +## children +- +## files +- __init__.py +- test_api.py +- test_backups.py +- test_config.py +- test_domain_media.py +- test_jellyseerr_client.py +- test_jobs.py +- test_mailer.py +- test_media_index.py +- test_observability.py +- test_path_utils.py +- test_services.py +- test_ssh_client.py +- test_targets.py +- test_utils.py +- test_widgets.py +## links +index: backend/tests/.pi-map.index.md +map: backend/tests/.pi-map.md +## workflows +- change tests behavior + read: __init__.py, test_api.py, test_backups.py +- change tests CLI + read: test_jellyseerr_client.py, test_ssh_client.py +- change tests config + read: test_config.py +## dirty +- diff --git a/backend/tests/.pi-map.md b/backend/tests/.pi-map.md new file mode 100644 index 0000000..2825a64 --- /dev/null +++ b/backend/tests/.pi-map.md @@ -0,0 +1,46 @@ +# backend/tests +dir: backend/tests + +index: backend/tests/.pi-map.index.md + +## role +Test suite providing unit and integration coverage for the backend's API endpoints, clients, configuration, utilities, and services. +## files +- __init__.py | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +- test_api.py | Integration tests for FastAPI endpoints using mocked dependencies to validate API layer behavior without real remote connections. | exp: class:TestHealth, method:test_health(self, test_client), call:test_client.get, call:response.json, method:test_metrics_endpoint(self, test_client), call:test_client.get, call:response.headers["content-type"].startswith, method:test_request_id_header(self, test_client), call:test_client.get, call:len, class:TestDashboard, method:test_counts(self, test_client), call:test_client.get, call:response.json, method:test_libraries(self, test_client), call:test_client.get, call:response.json, call:len, method:test_activity(self, test_client), call:test_client.get, call:response.json, call:len, call:next, method:test_now_playing_alias(self, test_client), call:test_client.get, call:response.json, call:len, class:TestSettingsReset, method:test_reset_local_database_requires_full_confirmation(self, test_client, tmp_path), call:SettingsStore, call:store.ensure_defaults, call:media_db.write_text, call:media_wal.write_text, call:__import__, call:SimpleNamespace, call:test_client.post, call:app.dependency_overrides.pop, method:test_reset_local_database_wipes_state_and_leaves_no_machines(self, test_client, tmp_path), call:SettingsStore, call:store.ensure_defaults, call:media_db.write_text, call:media_wal.write_text, call:__import__, call:SimpleNamespace, call:test_client.post, call:app.dependency_overrides.pop, call:response.json, call:media_db.exists, call:media_wal.exists, call:store.get_machine, call:len, call:store.list_machines, class:TestUsers, method:test_users_list_enriched(self, test_client), call:test_client.get, call:response.json, call:next, call:alex["avatar"].startswith, method:test_users_message_status(self, test_client), call:MagicMock, call:test_client.get, call:app.dependency_overrides.pop, call:response.json, method:test_users_message_is_queued(self, test_client), call:MagicMock, call:SimpleNamespace, call:patch, call:test_client.post, call:json.dumps, call:app.dependency_overrides.pop, call:response.json, call:mail_queue.enqueue.assert_called_once, class:TestFiles, method:test_list_directory(self, test_client), call:test_client.get, call:response.json, method:test_ffprobe(self, test_client), call:test_client.get, call:response.json, method:test_stat(self, test_client), call:test_client.get, call:response.json, method:test_resolve_path(self, test_client), call:test_client.get, call:response.json, method:test_list_failure(self, test_client, mock_ssh), call:CommandResult, call:test_client.get, class:TestMediaIndexApi, method:test_status_includes_build_progress(self, test_client, tmp_path), call:MediaIndex, call:index.init_schema, call:index.set_metadata, call:patch, call:test_client.get, call:response.json, method:test_build_returns_started_when_background_build_is_queued(self, test_client, tmp_path, mock_jellyfin), call:MediaIndex, call:patch, call:FakeProcess, call:test_client.post, call:response.json, call:len, call:start_worker.assert_called_once, call:app.dependency_overrides.pop, method:test_stop_requests_cancel(self, test_client, tmp_path), call:MediaIndex, call:index.init_schema, call:index.set_metadata, call:patch, call:test_client.post, call:response.json, call:test_client.get("/api/media/status").json, call:app.dependency_overrides.pop, method:test_force_stop_terminates_worker(self, test_client, tmp_path), call:MediaIndex, call:index.init_schema, call:index.set_metadata, call:patch, call:test_client.post, call:response.json, call:killpg.assert_called, call:test_client.get("/api/media/status").json, call:app.dependency_overrides.pop, method:test_force_stop_returns_conflict_when_idle(self, test_client, tmp_path), call:MediaIndex, call:index.init_schema, call:test_client.post, call:app.dependency_overrides.pop, class:TestJobs, method:test_list_templates(self, test_client), call:test_client.get, call:response.json, call:len, method:test_run_job(self, test_client, mock_ssh), call:CommandResult, call:test_client.post, call:response.json, method:test_run_unknown_job(self, test_client), call:test_client.post, class:TestMonitoring, method:test_prometheus_targets_empty(self, test_client), call:test_client.get, call:response.json, method:test_prometheus_targets_returns_enabled_ssh_node_exporter(self, test_client), call:store.upsert_machine, call:test_client.get, call:response.json, call:len, class:TestResolveServiceRecord, method:_store(self, rows), call:MagicMock, call:next, method:test_service_id_match_returns_record(self), call:self._store, call:patch, call:_resolve_service_record, call:mock_build.assert_called_once_with, method:test_service_id_type_mismatch_returns_none(self), call:self._store, call:_resolve_service_record, method:test_service_id_disabled_returns_none(self), call:self._store, call:_resolve_service_record, method:test_no_service_id_returns_first_enabled(self), call:self._store, call:patch, call:_resolve_service_record, call:mock_build.assert_called_once_with, method:test_no_service_id_and_none_enabled_returns_none(self), call:self._store, call:_resolve_service_record, class:TestSettingsMachines, method:test_machine_appears_in_prometheus_targets(self, test_client), call:store.upsert_machine, call:test_client.get("/api/monitoring/prometheus-targets").json, call:len, method:test_delete_machine_removed_from_prometheus_targets(self, test_client), call:store.upsert_machine, call:test_client.delete, call:test_client.get("/api/monitoring/prometheus-targets").json, class:TestAlertmanager, method:test_alerts_endpoint_when_not_configured(self, test_client), call:test_client.get, call:response.json, method:test_alerts_endpoint_when_unreachable(self, test_client), call:_am_service, call:patch, call:Exception, call:test_client.get, call:response.json, method:test_alerts_endpoint_returns_summary(self, test_client), call:_am_service, call:MagicMock, call:patch, call:test_client.get, call:response.json, method:test_alerts_endpoint_sends_bearer_token(self, test_client), call:_am_service, call:MagicMock, call:patch, call:test_client.get, method:test_alertmanager_status_when_not_configured(self, test_client), call:test_client.get, call:response.json, method:test_alertmanager_status_when_unreachable(self, test_client), call:_am_service, call:patch, call:Exception, call:test_client.get, call:response.json, method:test_alertmanager_status_returns_cluster_info(self, test_client), call:_am_service, call:MagicMock, call:patch, call:test_client.get, call:response.json, class:TestAlertmanagerWebhook, method:test_webhook_receives_payload_and_logs(self, test_client, caplog), call:caplog.at_level, call:test_client.post, call:response.json, class:TestGrafanaStatus, method:test_grafana_status_when_not_configured(self, test_client), call:test_client.get, call:response.json, method:test_grafana_status_when_unreachable(self, test_client), call:ServiceRecord, call:patch, call:Exception, call:test_client.get, call:response.json, method:test_grafana_status_returns_version(self, test_client), call:ServiceRecord, call:MagicMock, call:patch, call:test_client.get, call:response.json, class:TestPrometheusStatus, method:test_prometheus_status_when_not_configured(self, test_client), call:test_client.get, call:response.json, method:test_prometheus_status_when_unreachable(self, test_client), call:ServiceRecord, call:patch, call:Exception, call:test_client.get, call:response.json, method:test_prometheus_status_returns_version(self, test_client), call:ServiceRecord, call:MagicMock, call:patch, call:test_client.get, call:response.json, func:mock_jellyfin(), call:MagicMock, func:mock_jellyseerr(), call:MagicMock, func:mock_ssh(), call:MagicMock, call:CommandResult, call:json.dumps, func:test_client(mock_jellyfin, mock_jellyseerr, mock_ssh, tmp_path), call:SettingsStore, call:SimpleNamespace, call:patch, call:TestClient, call:app.dependency_overrides.clear, func:_am_service(), call:cfg.update, call:ServiceRecord | dep: json, types, unittest.mock, pytest, fastapi.testclient, media_library_viewer_api.clients.ssh, media_library_viewer_api.dependencies, media_library_viewer_api.main, media_library_viewer_api.routers.media, media_library_viewer_api.services.media_index, media_library_viewer_api.services.settings_store, media_library_viewer_api.widgets.sources, fastapi.testclient.TestClient +- test_backups.py | Tests dashboard backup statistics retrieval and backup report submission endpoints with isolated SQLite database instances. | exp: func:test_dashboard_backups(), call:tempfile.TemporaryDirectory, call:Path, call:SettingsStore, call:store.init_schema, call:TestClient, call:client.get, call:response.json, func:test_post_backup_report(), call:tempfile.TemporaryDirectory, call:Path, call:SettingsStore, call:store.init_schema, call:TestClient, call:get_api_key, call:client.post, call:response.json | dep: tempfile, pathlib, fastapi.testclient, media_library_viewer_api.main, media_library_viewer_api.services.settings_store, media_library_viewer_api.services, media_library_viewer_api.auth +- test_config.py | Unit tests for Pydantic-based Settings configuration loading from environment variables | exp: class:TestSettings, method:test_defaults(self), call:patch.dict, call:Settings, method:test_from_env(self), call:patch.dict, call:Settings, method:test_media_root_property(self), call:patch.dict, call:Settings, method:test_path_prefix_property(self), call:patch.dict, call:Settings, method:test_ssh_key_path_joins_directory_and_name(self), call:patch.dict, call:Settings | dep: os, unittest.mock, media_library_viewer_api.config +- test_domain_media.py | Unit tests for media normalization helpers that transform raw media item data into structured display formats. | exp: class:TestFirstMediaSource, method:test_empty_item(self), call:first_media_source, method:test_no_sources(self), call:first_media_source, method:test_returns_first(self), call:first_media_source, class:TestMediaStreams, method:test_empty(self), call:media_streams, method:test_all_streams(self), call:len, call:media_streams, method:test_filter_by_type(self), call:len, call:media_streams, class:TestStreamValue, method:test_first_key(self), call:stream_value, method:test_second_key(self), call:stream_value, method:test_none_values(self), call:stream_value, method:test_empty_string(self), call:stream_value, method:test_no_match(self), call:stream_value, class:TestIsHdrItem, method:test_sdr_item(self), call:is_hdr_item, method:test_hdr10(self), call:is_hdr_item, method:test_dolby_vision(self), call:is_hdr_item, method:test_bt2020_transfer(self), call:is_hdr_item, method:test_empty_item(self), call:is_hdr_item, class:TestFormatDateAdded, method:test_none(self), call:format_date_added, method:test_empty(self), call:format_date_added, method:test_iso_format(self), call:format_date_added, class:TestTimestampDateAdded, method:test_none(self), call:timestamp_date_added, method:test_empty(self), call:timestamp_date_added, method:test_valid(self), call:timestamp_date_added, call:isinstance, class:TestFormatRateBitsDecimal, method:test_none(self), call:format_rate_bits_decimal, method:test_empty(self), call:format_rate_bits_decimal, method:test_kbps(self), call:format_rate_bits_decimal, method:test_mbps(self), call:format_rate_bits_decimal, class:TestNormalizeMediaItem, method:test_movie(self), call:normalize_media_item, method:test_episode(self), call:normalize_media_item, method:test_minimal_item(self), call:normalize_media_item, class:TestDisplayMediaRow, method:test_basic(self), call:display_media_row, method:test_sdr(self), call:display_media_row | dep: media_library_viewer_api.domain.media, pytest +- test_jellyseerr_client.py | Unit tests for the JellyseerrClient class covering user retrieval, pagination, and URL normalization. | exp: class:JellyseerrClientTests, method:setUp(self) → None, call:JellyseerrClient, call:MagicMock, method:test_jellyfin_users_accepts_wrapped_payload(self) → None, call:MagicMock, call:self.client.jellyfin_users, call:self.assertEqual, call:len, call:self.session.get.assert_called_once, method:test_jellyfin_users_accepts_list_payload(self) → None, call:MagicMock, call:self.client.jellyfin_users, call:self.assertEqual, method:test_users_uses_take_and_skip(self) → None, call:MagicMock, call:self.client.users, call:self.assertEqual, method:test_absolute_url_normalizes_relative_paths(self) → None, call:self.assertEqual, call:self.client.absolute_url | dep: unittest, unittest.mock, media_library_viewer_api.clients.jellyseerr, unittest.mock.MagicMock, media_library_viewer_api.clients.jellyseerr.JellyseerrClient +- test_jobs.py | Unit tests for job template rendering and safety validation in a media library API | exp: class:TestJobTemplate, method:test_render_basic(self), call:JobTemplate, call:template.render, method:test_render_quotes_spaces(self), call:JobTemplate, call:template.render, method:test_render_quotes_special_chars(self), call:JobTemplate, call:template.render, call:result.startswith, class:TestBuiltinTemplates, method:test_all_templates_exist(self), method:test_all_templates_renderable(self), call:JOB_TEMPLATES.items, call:template.render, method:test_no_destructive_in_phase1(self), call:JOB_TEMPLATES.items | dep: media_library_viewer_api.jobs, media_library_viewer_api.jobs (JobTemplate, JOB_TEMPLATES) +- test_mailer.py | Unit tests for SMTP mail helper functions including HTML-to-text conversion, email sending with attachments, TLS/SSL fallback modes, and error handling. | exp: class:_SMTPContext, method:__init__(self, smtp: MagicMock), method:__enter__(self), method:__exit__(self, exc_type, exc, tb), class:MailerTests, method:test_html_to_text_strips_tags(self) → None, call:html_to_text, call:self.assertIn, method:test_send_email_message_uses_smtp_with_attachments(self) → None, call:SimpleNamespace, call:MagicMock, call:_SMTPContext, call:patch, call:send_email_message, call:EmailAttachment, call:smtp_ssl.assert_not_called, call:smtp.starttls.assert_called_once, call:smtp.login.assert_called_once_with, call:smtp.send_message.assert_called_once, call:self.assertEqual, method:test_send_email_message_falls_back_to_fastmail_mode(self) → None, call:SimpleNamespace, call:MagicMock, call:TimeoutError, call:_SMTPContext, call:patch, call:send_email_message, call:self.assertEqual, call:len, call:fallback_smtp.send_message.assert_called_once, method:test_send_email_message_retries_with_smtp_username_when_from_is_rejected(self) → None, call:SimpleNamespace, call:MagicMock, call:smtplib.SMTPDataError, call:_SMTPContext, call:patch, call:send_email_message, call:smtp_ssl.assert_not_called, call:self.assertEqual, method:test_describe_smtp_error_handles_timeout(self) → None, call:describe_smtp_error, call:TimeoutError, call:self.assertIn, call:detail.lower | dep: smtplib, unittest, types, unittest.mock, media_library_viewer_api.services.mailer, types.SimpleNamespace, unittest.mock.MagicMock, unittest.mock.patch +- test_media_index.py | Unit tests for the SQLite media index service, covering status checks, querying with filters/sorting/pagination, item replacement, metadata, and media index building with progress/cancellation support. | exp: class:TestMediaIndexStatus, method:test_nonexistent(self, tmp_path), call:MediaIndex, call:idx.status, method:test_empty_index(self, index), call:index.init_schema, call:index.status, method:test_populated(self, populated_index), call:populated_index.status, class:TestMediaIndexQuery, method:test_query_all(self, populated_index), call:populated_index.query, call:len, method:test_filter_by_library(self, populated_index), call:populated_index.query, call:all, method:test_filter_by_type(self, populated_index), call:populated_index.query, method:test_search(self, populated_index), call:populated_index.query, method:test_hdr_filter(self, populated_index), call:populated_index.query, method:test_sdr_filter(self, populated_index), call:populated_index.query, method:test_sort_by_size_desc(self, populated_index), call:populated_index.query, method:test_sort_by_year_asc(self, populated_index), call:populated_index.query, method:test_pagination(self, populated_index), call:populated_index.query, call:len, method:test_display_row_format(self, populated_index), call:populated_index.query, class:TestMediaIndexReplace, method:test_replace_clears_old(self, index), call:index.replace_items, call:index.status, class:TestMediaIndexMetadata, method:test_set_and_read_metadata(self, index), call:index.set_metadata, call:index.status, class:TestMediaIndexBuildPaths, method:test_build_media_index_patches_remote_media_root(self, tmp_path), call:MediaIndex, call:build_media_index, call:FakeClient, call:index.query, method:test_build_media_index_reports_progress(self, tmp_path), call:self.calls.append, call:kwargs.get, call:MediaIndex, call:build_media_index, call:FakeClient, call:any, call:next, method:test_build_media_index_can_be_cancelled(self, tmp_path), call:self.calls.append, call:kwargs.get, call:events.append, call:MediaIndex, call:FakeClient, call:pytest.raises, call:build_media_index, call:any, func:index(tmp_path), call:MediaIndex, func:populated_index(index), call:index.replace_items | dep: typing, pytest, media_library_viewer_api.services.media_index +- test_observability.py | Tests for Prometheus observability metrics helpers that record backup run counters and timestamps | exp: func:_metric_samples(metric_name), call:tuple, call:s.labels.values, call:REGISTRY.collect, func:test_record_backup_run_increments_counter(), call:record_backup_run, call:_metric_samples, func:test_record_backup_run_sets_last_success_timestamp(), call:time.time, call:record_backup_run, call:_metric_samples | dep: time, prometheus_client, media_library_viewer_api.observability +- test_path_utils.py | Unit tests for path resolution utilities that handle remote media path prefixing, media root mapping, and combined path resolution. | exp: class:TestApplyRemotePathPrefix, method:test_empty_path(self), call:apply_remote_path_prefix, method:test_empty_prefix(self), call:apply_remote_path_prefix, method:test_whitespace_prefix(self), call:apply_remote_path_prefix, method:test_basic_prefix(self), call:apply_remote_path_prefix, method:test_already_prefixed(self), call:apply_remote_path_prefix, method:test_relative_path(self), call:apply_remote_path_prefix, method:test_trailing_slash_prefix(self), call:apply_remote_path_prefix, method:test_path_with_spaces(self), call:apply_remote_path_prefix, class:TestMapPathToMediaRoot, method:test_empty_path(self), call:map_path_to_media_root, method:test_empty_root(self), call:map_path_to_media_root, method:test_already_under_root(self), call:map_path_to_media_root, method:test_anchor_mapping(self), call:map_path_to_media_root, method:test_anchor_mapping_deeper(self), call:map_path_to_media_root, method:test_no_anchor_match(self), call:map_path_to_media_root, method:test_root_is_just_anchor(self), call:map_path_to_media_root, method:test_path_equals_root(self), call:map_path_to_media_root, class:TestResolveRemoteMediaPath, method:test_empty_path(self), call:resolve_remote_media_path, method:test_mapping_takes_priority(self), call:resolve_remote_media_path, method:test_fallback_prefix_when_no_anchor(self), call:resolve_remote_media_path, method:test_no_mapping_no_prefix(self), call:resolve_remote_media_path, method:test_already_resolved(self), call:resolve_remote_media_path | dep: media_library_viewer_api.path_utils +- test_services.py | Tests for a service registry including definitions, encryption/decryption, CRUD operations, cascade deletes, and task run history. | exp: func:_encryption_key(monkeypatch), call:monkeypatch.setenv, call:reset_encryption_key_cache, func:client(tmp_path), call:SettingsStore, call:store.ensure_defaults, call:SimpleNamespace, call:patch, call:TestClient, call:app.dependency_overrides.clear, func:test_registry_contains_seven_service_types(), call:set, func:test_definitions_declare_widget_kinds(), call:get_service_definition, func:test_widget_kind_lookup(), call:get_widget_kind, func:test_service_config_schema_is_json_schema(), call:get_service_definition, func:test_encrypt_decrypt_round_trip(), call:encrypt_value, call:decrypt_value, func:test_encrypt_decrypt_secrets_dict(), call:encrypt_secrets, call:decrypt_secrets, func:test_missing_encryption_key_raises(monkeypatch), call:monkeypatch.delenv, call:reset_encryption_key_cache, call:pytest.raises, call:get_encryption_key, func:test_decrypt_with_wrong_key_raises(monkeypatch), call:encrypt_secrets, call:monkeypatch.setenv, call:Fernet.generate_key().decode, call:reset_encryption_key_cache, call:pytest.raises, call:decrypt_secrets, func:test_invalid_ciphertext_raises(), call:pytest.raises, call:decrypt_value, func:test_list_service_types(client), call:client.get, call:response.json, func:test_service_type_includes_secret_and_widget_metadata(client), call:client.get, call:next, call:response.json, func:_grafana_payload(), call:payload.update, func:test_create_and_list_service(client), call:client.post, call:_grafana_payload, call:response.json, call:client.get, call:len, func:test_list_instances_filters_by_type(client), call:client.post, call:_grafana_payload, call:client.get, call:len, call:response.json, func:test_update_service_preserves_unsent_secrets(client), call:client.post("/api/services/instances", json=_grafana_payload()).json, call:_grafana_payload, call:client.put( f"/api/services/instances/{created['id']}", json={ "service_type": "grafana", "name": "Renamed Grafana", "config": {"base_url": "https://grafana.example.com", "timeout_seconds": 10}, }, ).json, func:test_update_service_can_clear_secret(client), call:client.post("/api/services/instances", json=_grafana_payload()).json, call:_grafana_payload, call:client.put( f"/api/services/instances/{created['id']}", json={ "service_type": "grafana", "name": "Production Grafana", "config": {"base_url": "https://grafana.example.com"}, "secrets": {"api_key": ""}, }, ).json, func:test_unknown_service_type_rejected(client), call:client.post, func:test_invalid_config_rejected(client), call:client.post, func:test_unknown_secret_field_rejected(client), call:client.post, func:test_credential_key_in_config_rejected(client), call:client.post, func:test_update_nonexistent_returns_404(client), call:client.put, call:_grafana_payload, func:test_update_id_mismatch_returns_400(client), call:client.post("/api/services/instances", json=_grafana_payload()).json, call:_grafana_payload, call:client.put, func:test_delete_service(client), call:client.post("/api/services/instances", json=_grafana_payload()).json, call:_grafana_payload, call:client.delete, call:client.get("/api/services/instances").json, func:test_delete_nonexistent_returns_404(client), call:client.delete, func:test_delete_service_cascades_to_widgets(client, tmp_path), call:store.upsert_service, call:store.connect, call:conn.execute("PRAGMA table_info(dashboard_widgets)").fetchall, call:store.delete_service, call:store.get_service, call:conn.execute( "SELECT COUNT(*) FROM dashboard_widgets WHERE service_id = ?", (service["id"],), ).fetchone, call:int, func:test_record_and_list_service_task_runs(client), call:store.upsert_service, call:store.record_service_task_run, call:store.list_service_task_runs, call:len | dep: types, unittest.mock, pytest, cryptography.fernet, fastapi.testclient, media_library_viewer_api.dependencies, media_library_viewer_api.integrations.registry, media_library_viewer_api.main, media_library_viewer_api.services.secrets, media_library_viewer_api.services.settings_store, media_library_viewer_api (dependencies, integrations.registry, main, services.secrets, services.settings_store) +- test_ssh_client.py | Tests that RemoteSSHClient uses existing known hosts file and sets appropriate host key policies without reprobing | exp: func:test_connect_uses_existing_known_hosts_without_reprobing(tmp_path), call:known_hosts_path.write_text, call:MagicMock, call:patch, call:object, call:RemoteSSHClient, call:str, call:client.connect, call:ssh_client.load_system_host_keys.assert_called_once_with, call:ssh_client.load_host_keys.assert_called_once_with, call:ssh_client.set_missing_host_key_policy.assert_called_once, call:ssh_client.connect.assert_called_once | dep: unittest.mock, media_library_viewer_api.clients.ssh, paramiko, pytest +- test_targets.py | Tests the Prometheus Node Exporter target discovery logic for various machine configurations. | exp: class:TestBuildNodeExporterTargets, method:test_disabled_machine_excluded(self, store: SettingsStore), call:store.upsert_machine, call:build_node_exporter_targets, method:test_ssh_enabled_machine_included(self, store: SettingsStore), call:store.upsert_machine, call:build_node_exporter_targets, call:len, method:test_scrape_host_defaults_to_machine_host(self, store: SettingsStore), call:store.upsert_machine, call:build_node_exporter_targets, method:test_local_machine_excluded(self, store: SettingsStore), call:store.upsert_machine, call:build_node_exporter_targets, method:test_missing_host_excluded(self, store: SettingsStore), call:store.upsert_machine, call:build_node_exporter_targets, func:store(tmp_path: Path) → SettingsStore, call:SettingsStore, call:db.init_schema | dep: pathlib, pytest, media_library_viewer_api.services.settings_store, media_library_viewer_api.services.targets +- test_utils.py | Unit tests for media library formatting utility functions including duration, bitrate, file size, stream metadata, and timestamp conversions | exp: class:TestTicksToMinutes, method:test_none(self), call:ticks_to_minutes, method:test_zero(self), call:ticks_to_minutes, method:test_one_hour(self), call:ticks_to_minutes, method:test_90_minutes(self), call:ticks_to_minutes, method:test_rounding(self), call:int, call:ticks_to_minutes, class:TestHumanSize, method:test_none(self), call:human_size, method:test_zero(self), call:human_size, method:test_bytes(self), call:human_size, method:test_kilobytes(self), call:human_size, method:test_megabytes(self), call:human_size, method:test_gigabytes(self), call:human_size, method:test_terabytes(self), call:human_size, class:TestIsKnownVideoFile, method:test_none(self), call:is_known_video_file, method:test_empty(self), call:is_known_video_file, method:test_mkv(self), call:is_known_video_file, method:test_mp4(self), call:is_known_video_file, method:test_txt(self), call:is_known_video_file, method:test_srt(self), call:is_known_video_file, method:test_spaces_in_path(self), call:is_known_video_file, class:TestFormatDuration, method:test_none(self), call:format_duration, method:test_empty(self), call:format_duration, method:test_seconds(self), call:format_duration, method:test_hours(self), call:format_duration, method:test_string_input(self), call:format_duration, class:TestFormatBitrate, method:test_none(self), call:format_bitrate, method:test_empty(self), call:format_bitrate, method:test_low(self), call:format_bitrate, method:test_kbps(self), call:format_bitrate, method:test_mbps(self), call:format_bitrate, method:test_string_input(self), call:format_bitrate, class:TestFfprobeSummary, method:test_empty(self), call:ffprobe_format_summary, method:test_with_format(self), call:ffprobe_format_summary, class:TestSummarizeStreams, method:test_video_streams(self), call:summarize_video_streams, call:len, method:test_audio_streams(self), call:summarize_audio_streams, call:len, method:test_subtitle_streams(self), call:summarize_subtitle_streams, call:len, method:test_summarize_all(self), call:summarize_streams, call:len, class:TestTimestampToLocal, method:test_none(self), call:timestamp_to_local, method:test_valid(self), call:timestamp_to_local | dep: media_library_viewer_api.utils, pytest +- test_widgets.py | Tests the dashboard widget system's API endpoints and underlying data source adapters for built-in and service-bound widgets. | exp: func:_encryption_key(monkeypatch), call:monkeypatch.setenv, func:client(tmp_path), call:SettingsStore, call:store.ensure_defaults, call:SimpleNamespace, call:patch, call:TestClient, call:app.dependency_overrides.clear, func:_make_grafana_service(client), call:config.update, call:client.post( "/api/services/instances", json={"service_type": "grafana", "name": name, "config": config, "enabled": True}, ).json, func:test_list_builtin_kinds(client), call:client.get, call:response.json, func:test_create_and_read_static_widget(client), call:client.post, call:response.json, call:client.get("/api/widgets/instances").json, call:len, func:test_create_backups_widget(client), call:client.post, func:test_unknown_builtin_kind_rejected(client), call:client.post, func:test_credential_key_in_config_rejected(client), call:client.post, func:test_create_service_bound_widget(client), call:_make_grafana_service, call:client.post, call:response.json, func:test_service_bound_widget_unknown_kind_rejected(client), call:_make_grafana_service, call:client.post, func:test_service_bound_widget_service_not_found_rejected(client), call:client.post, func:test_service_bound_widget_invalid_config_rejected(client), call:_make_grafana_service, call:client.post, func:test_update_and_delete_widget(client), call:client.post( "/api/widgets/instances", json={"widget_kind": "static", "title": "Note", "config": {"text": "a"}}, ).json, call:client.put( f"/api/widgets/instances/{created['id']}", json={"widget_kind": "static", "title": "Note2", "config": {"text": "b"}}, ).json, call:client.delete, call:client.get("/api/widgets/instances").json, func:test_update_nonexistent_returns_404(client), call:client.put, func:test_update_id_mismatch_returns_400(client), call:client.post( "/api/widgets/instances", json={"widget_kind": "static", "title": "x", "config": {}}, ).json, call:client.put, func:test_fetch_static_widget_data(client), call:client.post( "/api/widgets/instances", json={"widget_kind": "static", "title": "Note", "config": {"text": "hello"}}, ).json, call:client.get, call:response.json, func:test_fetch_backups_widget_data(client), call:client.post( "/api/widgets/instances", json={"widget_kind": "backups", "title": "Backups", "config": {}}, ).json, call:client.get, call:response.json, func:test_fetch_grafana_link_widget_data(client), call:_make_grafana_service, call:client.post( "/api/widgets/instances", json={ "service_id": service["id"], "widget_kind": "link", "title": "Dashboard", "config": {"dashboard_uid": "overview", "panel_id": 2}, }, ).json, call:client.get, call:response.json, func:test_fetch_widget_service_not_found(client), call:_make_grafana_service, call:client.post( "/api/widgets/instances", json={ "service_id": service["id"], "widget_kind": "link", "title": "x", "config": {"dashboard_uid": "u"}, }, ).json, call:client.delete, call:client.get("/api/widgets/instances").json, func:test_fetch_widget_service_disabled(client), call:_make_grafana_service, call:client.post( "/api/widgets/instances", json={ "service_id": service["id"], "widget_kind": "link", "title": "x", "config": {"dashboard_uid": "u"}, }, ).json, call:client.put, call:client.get, call:response.json, func:test_fetch_widget_not_found(client), call:client.get, func:test_grafana_adapter_builds_url(), call:GrafanaWidgetSource, call:ServiceRecord, call:adapter.fetch, func:test_grafana_adapter_missing_service(), call:GrafanaWidgetSource, call:adapter.fetch, func:test_alertmanager_adapter_summarizes_alerts(), call:AlertmanagerWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, func:test_alertmanager_adapter_applies_severity_filter(), call:AlertmanagerWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, func:test_alertmanager_adapter_sends_bearer_token(), call:AlertmanagerWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, func:test_alertmanager_adapter_missing_service(), call:AlertmanagerWidgetSource, call:adapter.fetch, func:test_static_adapter(), call:StaticWidgetSource, call:adapter.fetch, func:test_backups_adapter(client), call:patch, call:BackupsWidgetSource, call:adapter.fetch, func:test_ssh_task_adapter_missing_service(), call:SshTaskWidgetSource, call:adapter.fetch, func:test_ssh_task_adapter_records_history_on_run(client), call:store.upsert_task, call:store.upsert_service, call:SimpleNamespace, call:SshTaskWidgetSource, call:ServiceRecord, call:patch, call:adapter.fetch, call:store.list_service_task_runs, call:len | dep: types, unittest.mock, pytest, cryptography.fernet, fastapi.testclient, media_library_viewer_api.dependencies, media_library_viewer_api.main, media_library_viewer_api.services.settings_store, media_library_viewer_api.widgets.sources +## arch +Organized by module with mocked dependencies and isolated SQLite instances, following pytest conventions for fast, parallel-safe testing without real remote connections. +## tags +test, service, call:test, media, call:response.json, path, client.get, call: +## symbols +- TestHealth +- TestDashboard +- TestSettingsReset +- TestUsers +- TestFiles +- TestMediaIndexApi +- TestJobs +- TestMonitoring +## workflows +- change tests behavior + read: __init__.py, test_api.py, test_backups.py +- change tests CLI + read: test_jellyseerr_client.py, test_ssh_client.py +- change tests config + read: test_config.py +## dirty +- diff --git a/docs/.pi-map.index.md b/docs/.pi-map.index.md new file mode 100644 index 0000000..e5f3996 --- /dev/null +++ b/docs/.pi-map.index.md @@ -0,0 +1,24 @@ +# docs (index) +dir: docs + +## role +Documentation package containing architectural plans, requirements, and operational guides for the "Manage" media library application and its observability stack. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- docs/superpowers + index: docs/superpowers/.pi-map.index.md + map: docs/superpowers/.pi-map.md +## files +- MIGRATION_PLAN.md +- REQUIREMENTS.md +- monitoring-logging-design.md +- observability-runbooks.md +## links +index: docs/.pi-map.index.md +map: docs/.pi-map.md +## workflows +- +## dirty +- diff --git a/docs/.pi-map.md b/docs/.pi-map.md new file mode 100644 index 0000000..b981db9 --- /dev/null +++ b/docs/.pi-map.md @@ -0,0 +1,22 @@ +# docs +dir: docs + +index: docs/.pi-map.index.md + +## role +Documentation package containing architectural plans, requirements, and operational guides for the "Manage" media library application and its observability stack. +## files +- MIGRATION_PLAN.md | This file documents the architecture, API design, and step-by-step migration plan for transitioning an application from a Streamlit monolith to a FastAPI and React SPA. | dep: FastAPI, React, Vite, TypeScript, pydantic-settings, @tanstack/react-query, ag-grid-react, recharts, tailwindcss +- REQUIREMENTS.md | Living requirements and decision log document for "Manage," a web application for browsing Jellyfin media libraries and inspecting corresponding media files on disk over SSH. | dep: React, TypeScript, shadcn/ui, Tailwind CSS, lucide-react, TanStack Table, Vitest, Jellyfin API, SQLite, Prometheus, Grafana, Alertmanager, OIDC +- monitoring-logging-design.md | Design document detailing a self-hosted observability architecture (metrics, logs, dashboards, alerting) for integration with a platform called Manage. | dep: Prometheus, Grafana, Node Exporter, Grafana Loki, Grafana Alloy, Alertmanager, Authentik, Traefik +- observability-runbooks.md | Provides operational runbooks, configuration, and maintenance procedures for deploying and managing a standalone observability stack. | dep: Prometheus, Grafana, Loki, Alloy, Alertmanager, Node Exporter, Docker Compose, Traefik +## arch +Static markdown documentation organized as living specs, design docs, and runbooks without code structure or dependencies. +## tags +react, design, observability, migration, plan, requirements, prometheus, grafana +## symbols +- +## workflows +- +## dirty +- diff --git a/docs/superpowers/.pi-map.index.md b/docs/superpowers/.pi-map.index.md new file mode 100644 index 0000000..d052175 --- /dev/null +++ b/docs/superpowers/.pi-map.index.md @@ -0,0 +1,23 @@ +# docs/superpowers (index) +dir: docs/superpowers + +## role +Documentation directory for advanced features, capabilities, or customization guides within the project. +## parent +index: docs/.pi-map.index.md +map: docs/.pi-map.md +## children +- docs/superpowers/plans + index: docs/superpowers/plans/.pi-map.index.md + map: docs/superpowers/plans/.pi-map.md +- docs/superpowers/specs + index: docs/superpowers/specs/.pi-map.index.md + map: docs/superpowers/specs/.pi-map.md +## files +## links +index: docs/superpowers/.pi-map.index.md +map: docs/superpowers/.pi-map.md +## workflows +- +## dirty +- diff --git a/docs/superpowers/.pi-map.md b/docs/superpowers/.pi-map.md new file mode 100644 index 0000000..3add6ea --- /dev/null +++ b/docs/superpowers/.pi-map.md @@ -0,0 +1,18 @@ +# docs/superpowers +dir: docs/superpowers + +index: docs/superpowers/.pi-map.index.md + +## role +Documentation directory for advanced features, capabilities, or customization guides within the project. +## files +## arch +Flat-file documentation structure (currently empty of content files), serving as a namespace for specialized or extended documentation topics. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/docs/superpowers/plans/.pi-map.index.md b/docs/superpowers/plans/.pi-map.index.md new file mode 100644 index 0000000..4917ed4 --- /dev/null +++ b/docs/superpowers/plans/.pi-map.index.md @@ -0,0 +1,19 @@ +# docs/superpowers/plans (index) +dir: docs/superpowers/plans + +## role +Documentation directory containing detailed implementation plans for specific project features or standalone systems. +## parent +index: docs/superpowers/.pi-map.index.md +map: docs/superpowers/.pi-map.md +## children +- +## files +- 2026-05-11-backup-monitoring.md +## links +index: docs/superpowers/plans/.pi-map.index.md +map: docs/superpowers/plans/.pi-map.md +## workflows +- +## dirty +- diff --git a/docs/superpowers/plans/.pi-map.md b/docs/superpowers/plans/.pi-map.md new file mode 100644 index 0000000..2541fd7 --- /dev/null +++ b/docs/superpowers/plans/.pi-map.md @@ -0,0 +1,19 @@ +# docs/superpowers/plans +dir: docs/superpowers/plans + +index: docs/superpowers/plans/.pi-map.index.md + +## role +Documentation directory containing detailed implementation plans for specific project features or standalone systems. +## files +- 2026-05-11-backup-monitoring.md | Implementation plan for building a standalone backup monitoring system with FastAPI backend, SQLite storage, and React frontend that receives HTTP reports, tracks jobs/runs/alerts, and provides dashboards with validation and alerting. | dep: FastAPI, SQLite, Pydantic, React, TypeScript, TanStack Query, D3, MUI, Python uuid/time/json modules +## arch +Flat-file markdown documentation following a date-prefixed naming convention (`YYYY-MM-DD-feature.md`) to organize chronological planning artifacts. +## tags +backup, monitoring, fastapi, sqlite, react, 2026, 05, 11 +## symbols +- +## workflows +- +## dirty +- diff --git a/docs/superpowers/specs/.pi-map.index.md b/docs/superpowers/specs/.pi-map.index.md new file mode 100644 index 0000000..c95ab43 --- /dev/null +++ b/docs/superpowers/specs/.pi-map.index.md @@ -0,0 +1,20 @@ +# docs/superpowers/specs (index) +dir: docs/superpowers/specs + +## role +Specification documents defining approved architectural designs for major features of the media library viewer project. +## parent +index: docs/superpowers/.pi-map.index.md +map: docs/superpowers/.pi-map.md +## children +- +## files +- 2026-05-08-obsidian-documentation-design.md +- 2026-05-11-backup-monitoring-design.md +## links +index: docs/superpowers/specs/.pi-map.index.md +map: docs/superpowers/specs/.pi-map.md +## workflows +- +## dirty +- diff --git a/docs/superpowers/specs/.pi-map.md b/docs/superpowers/specs/.pi-map.md new file mode 100644 index 0000000..74cba2b --- /dev/null +++ b/docs/superpowers/specs/.pi-map.md @@ -0,0 +1,20 @@ +# docs/superpowers/specs +dir: docs/superpowers/specs + +index: docs/superpowers/specs/.pi-map.index.md + +## role +Specification documents defining approved architectural designs for major features of the media library viewer project. +## files +- 2026-05-08-obsidian-documentation-design.md | Defines the approved documentation structure for an Obsidian-based knowledge vault for the Manage media library viewer project, targeting developers, contributors, operators, and deployers. | dep: Obsidian, FastAPI, React, TypeScript, Vite, MUI, D3, AG Grid, Docker, Traefik, Authentik, Paramiko, PyJWT, SQLite +- 2026-05-11-backup-monitoring-design.md | Design document for a standalone backup monitoring module with HTTP API ingestion, SQLite storage, automated alerting, and React frontend for a media library viewer application. | dep: FastAPI, React, SQLite, OIDC/JWT, D3, existing MonitoringPoller +## arch +Dated Markdown design documents following a specification pattern, each capturing requirements, architecture decisions, and implementation plans for distinct system components. +## tags +design, obsidian, 2026, 05, documentation, react, sqlite, backup +## symbols +- +## workflows +- +## dirty +- diff --git a/frontend/.pi-map.index.md b/frontend/.pi-map.index.md new file mode 100644 index 0000000..3ea7566 --- /dev/null +++ b/frontend/.pi-map.index.md @@ -0,0 +1,47 @@ +# frontend (index) +dir: frontend + +## role +React-based single-page application frontend for the "Manage" project, providing the user interface with Vite tooling, Tailwind CSS styling, and OIDC authentication. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- frontend/public + index: frontend/public/.pi-map.index.md + map: frontend/public/.pi-map.md +- frontend/src + index: frontend/src/.pi-map.index.md + map: frontend/src/.pi-map.md +- frontend/tests + index: frontend/tests/.pi-map.index.md + map: frontend/tests/.pi-map.md +## files +- .gitignore +- Dockerfile +- README.md +- components.json +- eslint.config.js +- index.html +- nginx.conf +- package-lock.json +- package.json +- postcss.config.cjs +- tailwind.config.cjs +- tsconfig.app.json +- tsconfig.json +- tsconfig.node.json +- vite.config.ts +- vitest.config.ts +## links +index: frontend/.pi-map.index.md +map: frontend/.pi-map.md +## workflows +- change frontend behavior + read: .gitignore, Dockerfile, eslint.config.js +- change frontend config + read: components.json, eslint.config.js, package-lock.json +- explore frontend subdirectories + index: frontend/public/.pi-map.index.md, frontend/src/.pi-map.index.md, frontend/tests/.pi-map.index.md +## dirty +- diff --git a/frontend/.pi-map.md b/frontend/.pi-map.md new file mode 100644 index 0000000..c024731 --- /dev/null +++ b/frontend/.pi-map.md @@ -0,0 +1,39 @@ +# frontend +dir: frontend + +index: frontend/.pi-map.index.md + +## role +React-based single-page application frontend for the "Manage" project, providing the user interface with Vite tooling, Tailwind CSS styling, and OIDC authentication. +## files +- .gitignore | Specifies files and directories for Git to ignore in a Node.js/frontend project +- Dockerfile | Multi-stage Dockerfile for building and serving a Vite-based frontend application with separate production (nginx) and development (node dev server) targets | dep: node:22-alpine, nginx:1.27-alpine, npm, vite +- README.md | Documentation for the Manage Frontend React SPA, covering setup, development, build, pages, environment variables, and configuration workflows. +- components.json | Configuration file for shadcn/ui component library setup with Tailwind CSS and path aliases | dep: shadcn/ui, tailwindcss, lucide-react, radix-ui +- eslint.config.js | Configures ESLint for a TypeScript React project using Vite with recommended rules for JS, TS, React Hooks, and React Refresh. | dep: @eslint/js, globals, eslint-plugin-react-hooks, eslint-plugin-react-refresh, typescript-eslint, eslint/config +- index.html | Standard HTML entry point for a React/Vite single-page application named "Manage" | dep: React (implied by root div and TSX entry), Vite (implied by module script and /src path) +- nginx.conf | Configures Nginx as a reverse proxy serving static files and forwarding API requests to a backend service | dep: nginx +- package-lock.json | Records exact dependency versions and resolved URLs for reproducible npm package installations in a React frontend project. | dep: npm, node, react, react-dom, react-router-dom, @tanstack/react-query, @tanstack/react-table, oidc-client-ts, react-oidc-context, radix-ui, shadcn, tailwindcss, vite, vitest, typescript, eslint, @babel/core, @babel/parser, @babel/generator, @babel/traverse, @babel/types, @babel/helpers, @babel/helper-plugin-utils, @babel/helper-module-imports, @babel/helper-module-transforms, @babel/helper-compilation-targets, @babel/helper-create-class-features-plugin, @babel/helper-replace-supers, @babel/helper-member-expression-to-functions, @babel/helper-optimise-call-expression, @babel/helper-skip-transparent-expression-wrappers, @babel/helper-annotate-as-pure, @babel/helper-globals, @babel/helper-string-parser, @babel/helper-validator-identifier, @babel/helper-validator-option, @babel/compat-data, @babel/code-frame, @babel/template, @babel/plugin-syntax-jsx, @jridgewell/gen-mapping, @jridgewell/remapping, @jridgewell +- package.json | Defines a React-based frontend project configuration with Vite build tooling, Tailwind CSS styling, OIDC authentication, and testing infrastructure. | dep: react, react-dom, react-router-dom, @tanstack/react-query, @tanstack/react-table, radix-ui, react-oidc-context, oidc-client-ts, lucide-react, class-variance-authority, tailwind-merge, clsx, tw-animate-css, @fontsource-variable/geist, shadcn, vite, typescript, tailwindcss, vitest, eslint, @testing-library/react, jsdom +- postcss.config.cjs | Configures PostCSS to use Tailwind CSS and Autoprefixer plugins for CSS processing | dep: postcss, @tailwindcss/postcss, autoprefixer +- tailwind.config.cjs | Configures Tailwind CSS to scan source files for class names and defines theme/plugins settings. | dep: tailwindcss +- tsconfig.app.json | TypeScript configuration file for a React application using Vite with strict linting rules and path aliases | dep: typescript, vite, react +- tsconfig.json | Root TypeScript configuration file for a project using project references to delegate to separate app and node configurations. | dep: typescript +- tsconfig.node.json | TypeScript configuration for Vite's Node.js-based build tooling | dep: typescript, vite, node +- vite.config.ts | Configures Vite build tool with React, Tailwind CSS, environment-based API proxying, and path aliasing for a frontend application. | dep: path, vite, @vitejs/plugin-react, @tailwindcss/vite +- vitest.config.ts | Configures Vitest test runner for a React project with jsdom environment, path aliasing, and scoped test file inclusion. | dep: path, vitest/config, @vitejs/plugin-react, vitest, jsdom +## arch +SPA architecture using React with Vite for bundling/HMR, shadcn/ui component library with Tailwind CSS for styling, TypeScript with strict type checking, Vitest for testing, and Docker multi-stage builds (nginx for production serving, Node for development). +## tags +react, @babel, vite, helper, typescript, project, css, eslint +## symbols +- +## workflows +- change frontend behavior + read: .gitignore, Dockerfile, eslint.config.js +- change frontend config + read: components.json, eslint.config.js, package-lock.json +- explore frontend subdirectories + index: frontend/public/.pi-map.index.md, frontend/src/.pi-map.index.md, frontend/tests/.pi-map.index.md +## dirty +- diff --git a/frontend/public/.pi-map.index.md b/frontend/public/.pi-map.index.md new file mode 100644 index 0000000..6b78e89 --- /dev/null +++ b/frontend/public/.pi-map.index.md @@ -0,0 +1,20 @@ +# frontend/public (index) +dir: frontend/public + +## role +Provides static public assets for the frontend web application, including brand identity graphics and reusable UI icons. +## parent +index: frontend/.pi-map.index.md +map: frontend/.pi-map.md +## children +- +## files +- favicon.svg +- icons.svg +## links +index: frontend/public/.pi-map.index.md +map: frontend/public/.pi-map.md +## workflows +- +## dirty +- diff --git a/frontend/public/.pi-map.md b/frontend/public/.pi-map.md new file mode 100644 index 0000000..c6ddd28 --- /dev/null +++ b/frontend/public/.pi-map.md @@ -0,0 +1,20 @@ +# frontend/public +dir: frontend/public + +index: frontend/public/.pi-map.index.md + +## role +Provides static public assets for the frontend web application, including brand identity graphics and reusable UI icons. +## files +- favicon.svg | Defines a browser favicon as an SVG graphic featuring a stylized lightning bolt shape with purple and blue gradient glow effects. +- icons.svg | Defines a collection of reusable SVG icon symbols (Bluesky, Discord, documentation, GitHub, social, X) for use in web applications via `` references. +## arch +Flat static asset directory following standard web application conventions, serving files directly at the root URL path without build processing. +## tags +favicon, defines, svg, icons, browser, graphic, featuring, stylized +## symbols +- +## workflows +- +## dirty +- diff --git a/frontend/src/.pi-map.index.md b/frontend/src/.pi-map.index.md new file mode 100644 index 0000000..8cfdca2 --- /dev/null +++ b/frontend/src/.pi-map.index.md @@ -0,0 +1,56 @@ +# frontend/src (index) +dir: frontend/src + +## role +Frontend entry point and shared utilities for a React-based admin dashboard with OIDC authentication, user state management, and Tailwind CSS theming. +## parent +index: frontend/.pi-map.index.md +map: frontend/.pi-map.md +## children +- frontend/src/api + index: frontend/src/api/.pi-map.index.md + map: frontend/src/api/.pi-map.md +- frontend/src/components + index: frontend/src/components/.pi-map.index.md + map: frontend/src/components/.pi-map.md +- frontend/src/hooks + index: frontend/src/hooks/.pi-map.index.md + map: frontend/src/hooks/.pi-map.md +- frontend/src/integrations + index: frontend/src/integrations/.pi-map.index.md + map: frontend/src/integrations/.pi-map.md +- frontend/src/lib + index: frontend/src/lib/.pi-map.index.md + map: frontend/src/lib/.pi-map.md +- frontend/src/pages + index: frontend/src/pages/.pi-map.index.md + map: frontend/src/pages/.pi-map.md +- frontend/src/test + index: frontend/src/test/.pi-map.index.md + map: frontend/src/test/.pi-map.md +- frontend/src/types + index: frontend/src/types/.pi-map.index.md + map: frontend/src/types/.pi-map.md +- frontend/src/widgets + index: frontend/src/widgets/.pi-map.index.md + map: frontend/src/widgets/.pi-map.md +## files +- App.tsx +- auth.ts +- index.css +- main.tsx +- userState.d.ts +- userState.js +- users.d.ts +- users.js +- version.ts +## links +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## workflows +- change src behavior + read: App.tsx, auth.ts, index.css +- explore src subdirectories + index: frontend/src/api/.pi-map.index.md, frontend/src/components/.pi-map.index.md, frontend/src/hooks/.pi-map.index.md +## dirty +- diff --git a/frontend/src/.pi-map.md b/frontend/src/.pi-map.md new file mode 100644 index 0000000..1eba476 --- /dev/null +++ b/frontend/src/.pi-map.md @@ -0,0 +1,37 @@ +# frontend/src +dir: frontend/src + +index: frontend/src/.pi-map.index.md + +## role +Frontend entry point and shared utilities for a React-based admin dashboard with OIDC authentication, user state management, and Tailwind CSS theming. +## files +- App.tsx | Main application component that renders a responsive admin dashboard with OIDC authentication, dark mode, collapsible sidebar navigation, and route-based page rendering. | exp: func:App() | dep: react-router-dom, @tanstack/react-query, react, react-oidc-context, ./pages/Dashboard, ./pages/Applications, ./pages/Settings, ./pages/Users, ./pages/FileBrowser, ./pages/Actions, ./components/BackupsPage, ./components/ObservabilityPage, ./pages/ServicePage, ./pages/ServicesPage, ./auth, ./api/client, ./version, ./hooks/usePersistentState, @/components/ui/button, @/components/ui/tooltip, @/components/ui/sheet, lucide-react, ./pages/*, ./components/* +- auth.ts | Manages OIDC authentication configuration and access token retrieval for a browser-based application. | exp: func:isOidcConfigured() → boolean, call:(import.meta.env.VITE_OIDC_ENABLED ?? "true").toLowerCase, call:Boolean, func:getOidcConfig(), call:window.history.replaceState, func:setAccessToken(token: string | null | undefined), func:getAccessToken() → string | null, call:getStoredAccessToken | dep: oidc-client-ts +- index.css | Defines a comprehensive Tailwind CSS v4 theme with custom design tokens, dark mode support, and base styles for a React application. | dep: tailwindcss, Google Fonts (Inter) +- main.tsx | Entry point that renders the React application into the DOM root element with StrictMode enabled. | dep: react, react-dom/client, ./App, ./index.css +- userState.d.ts | Defines TypeScript type declarations for user activity state management, including interfaces for user activity summaries and state items, plus function declarations for merging users with activity data and resolving user selections. | exp: UserActivitySummary, UserStateItem, mergeUsersWithActivity, resolveUserSelection | dep: ./types, types (NowPlayingSession, UserDirectoryItem) +- userState.js | Merges Jellyfin users with their session activity data and provides user lookup by identifier. | exp: func:mergeUsersWithActivity(users, sessions), call:users.map, call:sessions.filter, call:sessionMatchesUser, call:buildActivitySummary, func:resolveUserSelection(users, identifier), call:normalize, call:users.find, call:userKeys(user).some +- users.d.ts | Defines TypeScript interfaces and a function declaration for building a user drawer model from directory data. | exp: UserDetailField, UserContactAction, UserContactState, UserDrawerModel, buildUserDrawerModel | dep: ./types, types +- users.js | Transforms raw user data into a structured UI model for a user details drawer component. | exp: func:buildUserDrawerModel(user), call:displayName, call:splitValues, call:String, call:user.role.charAt(0).toUpperCase, call:user.role.slice, call:permissions.join, call:syncStatus +- version.ts | Exports frontend version and build info constants with a formatter for semantic version labels | exp: FRONTEND_VERSION, FRONTEND_BUILD_INFO, FRONTEND_VERSION_LABEL, func:formatVersionLabel(version: string, buildInfo: string) → string, call:version.trim, call:buildInfo.trim +## arch +React SPA architecture with TypeScript type definitions alongside JavaScript implementations, declarative component composition, and centralized authentication/state helper modules. +## tags +user, activity, version, state, react, pages, users, oidc +## symbols +- App +- isOidcConfigured +- getOidcConfig +- setAccessToken +- getAccessToken +- formatVersionLabel +- call:(import.meta.env.VITE_OIDC_ENABLED ?? "true").toLowerCase +- call:Boolean +## workflows +- change src behavior + read: App.tsx, auth.ts, index.css +- explore src subdirectories + index: frontend/src/api/.pi-map.index.md, frontend/src/components/.pi-map.index.md, frontend/src/hooks/.pi-map.index.md +## dirty +- diff --git a/frontend/src/api/.pi-map.index.md b/frontend/src/api/.pi-map.index.md new file mode 100644 index 0000000..c60fc60 --- /dev/null +++ b/frontend/src/api/.pi-map.index.md @@ -0,0 +1,25 @@ +# frontend/src/api (index) +dir: frontend/src/api + +## role +Frontend API client layer that centralizes all HTTP communication with the backend REST API. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- backups.ts +- client.ts +- services.ts +- widgets.ts +## links +index: frontend/src/api/.pi-map.index.md +map: frontend/src/api/.pi-map.md +## workflows +- change api behavior + read: backups.ts, client.ts, services.ts +- change api CLI + read: client.ts +## dirty +- diff --git a/frontend/src/api/.pi-map.md b/frontend/src/api/.pi-map.md new file mode 100644 index 0000000..e1cc8c1 --- /dev/null +++ b/frontend/src/api/.pi-map.md @@ -0,0 +1,32 @@ +# frontend/src/api +dir: frontend/src/api + +index: frontend/src/api/.pi-map.index.md + +## role +Frontend API client layer that centralizes all HTTP communication with the backend REST API. +## files +- backups.ts | Provides API client functions for fetching and managing backup-related data including jobs, runs, alerts, and dashboard summaries. | exp: func:fetchBackupJobs() → Promise, call:fetch, call:res.json, raise:Error, func:fetchBackupJob(jobId: string) → Promise<{ job: BackupJob; runs: BackupRun[] }>, call:fetch, call:res.json, raise:Error, func:fetchBackupRuns(jobId: string, status: string) → Promise, call:params.append, call:fetch, call:res.json, raise:Error, func:fetchBackupRun(runId: string) → Promise, call:fetch, call:res.json, raise:Error, func:fetchBackupAlerts(jobId: string, acknowledged: boolean, severity: string) → Promise, call:params.append, call:String, call:fetch, call:res.json, raise:Error, func:acknowledgeBackupAlert(alertId: string) → Promise, call:fetch, call:res.json, raise:Error, func:fetchBackupDashboard() → Promise, call:fetch, call:res.json, raise:Error | dep: ../types/backups +- client.ts | Typed API client providing fetch-based helper functions for communicating with a FastAPI backend across dashboard, monitoring, media, files, jobs, and observability endpoints. | exp: fetchCounts, fetchLibraries, fetchActivity, fetchUsers, fetchNowPlaying, fetchMonitoringMachines, fetchAppVersion, fetchDashboardShortcuts, saveDashboardShortcut, deleteDashboardShortcut, fetchMonitoringSettings, fetchSSHKeys, generateSSHKey, saveSSHKey, deleteSSHKey, fetchSavedTasks, fetchSavedTaskRuns, saveTask, deleteTask, runTask, saveMonitoringMachine, testMonitoringMachineSSH, deleteMonitoringMachine, resetLocalDatabase, fetchMediaStatus, buildMediaIndex, stopMediaIndexBuild, forceStopMediaIndexBuild, queryMedia, fetchDirectoryListing, fetchFfprobe, fetchStat, resolvePath, fetchJobTemplates, runJob, fetchUserMessageQueueStatus, sendUserMessage, fetchAlertmanagerAlerts, fetchAlertmanagerStatus, fetchGrafanaStatus, fetchPrometheusStatus, fetchPrometheusTargets | dep: ../auth, ../types +- services.ts | Provides CRUD HTTP client functions for managing service types and service instances via a REST API. | exp: func:fetchServiceTypes() → Promise, call:fetch, call:res.json, raise:Error, func:fetchServiceInstances(serviceType: string) → Promise, call:encodeURIComponent, call:fetch, call:res.json, raise:Error, func:createServiceInstance(input: ServiceInstanceInput) → Promise, call:fetch, call:JSON.stringify, call:res.json, raise:Error, func:updateServiceInstance(input: ServiceInstanceInput) → Promise, call:fetch, call:JSON.stringify, call:res.json, raise:Error, func:deleteServiceInstance(serviceId: string) → Promise<{ status: string }>, call:fetch, call:res.json, raise:Error | dep: ../types +- widgets.ts | Provides CRUD API client functions for managing widget instances and fetching widget-related data from a REST backend. | exp: func:fetchBuiltinWidgetKinds() → Promise< BuiltinWidgetKindInfo[] >, call:fetch, call:res.json, raise:Error, func:fetchWidgetInstances() → Promise, call:fetch, call:res.json, raise:Error, func:createWidgetInstance(input: WidgetInstanceInput) → Promise, call:fetch, call:JSON.stringify, call:res.json, raise:Error, func:updateWidgetInstance(input: WidgetInstanceInput) → Promise, call:fetch, call:JSON.stringify, call:res.json, raise:Error, func:deleteWidgetInstance(widgetId: string) → Promise<{ status: string }>, call:fetch, call:res.json, raise:Error, func:fetchWidgetData(widgetId: string) → Promise, call:fetch, call:res.json, raise:Error | dep: ../types +## arch +Modular typed fetch-based client functions organized by domain (backups, services, widgets, general), exposing per-endpoint helper methods consumed by the UI layer. +## tags +fetch, call:fetch, call:res.json, raise:error, backup, widget, delete, service +## symbols +- fetchBackupJobs +- fetchBackupJob +- fetchBackupRuns +- fetchBackupRun +- fetchBackupAlerts +- acknowledgeBackupAlert +- fetchBackupDashboard +- fetchServiceTypes +## workflows +- change api behavior + read: backups.ts, client.ts, services.ts +- change api CLI + read: client.ts +## dirty +- diff --git a/frontend/src/components/.pi-map.index.md b/frontend/src/components/.pi-map.index.md new file mode 100644 index 0000000..72c45f5 --- /dev/null +++ b/frontend/src/components/.pi-map.index.md @@ -0,0 +1,44 @@ +# frontend/src/components (index) +dir: frontend/src/components + +## role +React UI component library providing reusable display elements, dashboard widgets, and page-level views for backup management, observability, media sessions, and widget configuration. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- frontend/src/components/__tests__ + index: frontend/src/components/__tests__/.pi-map.index.md + map: frontend/src/components/__tests__/.pi-map.md +- frontend/src/components/ui + index: frontend/src/components/ui/.pi-map.index.md + map: frontend/src/components/ui/.pi-map.md +## files +- BackupAlertsTable.tsx +- BackupDashboardWidget.tsx +- BackupJobsTable.tsx +- BackupRunsTable.tsx +- BackupsPage.tsx +- ConfirmDialog.tsx +- DialogFooter.tsx +- HoverEditButton.tsx +- LibraryOverview.tsx +- MetricCard.tsx +- NowPlaying.tsx +- ObservabilityPage.tsx +- SectionCard.tsx +- SelectionRailCard.tsx +- SessionActivityPanel.tsx +- TabbedCard.tsx +- WidgetConfigDialog.tsx +- WidgetInstance.tsx +## links +index: frontend/src/components/.pi-map.index.md +map: frontend/src/components/.pi-map.md +## workflows +- change components behavior + read: BackupAlertsTable.tsx, BackupDashboardWidget.tsx, BackupJobsTable.tsx +- explore components subdirectories + index: frontend/src/components/__tests__/.pi-map.index.md, frontend/src/components/ui/.pi-map.index.md +## dirty +- diff --git a/frontend/src/components/.pi-map.md b/frontend/src/components/.pi-map.md new file mode 100644 index 0000000..91ca769 --- /dev/null +++ b/frontend/src/components/.pi-map.md @@ -0,0 +1,46 @@ +# frontend/src/components +dir: frontend/src/components + +index: frontend/src/components/.pi-map.index.md + +## role +React UI component library providing reusable display elements, dashboard widgets, and page-level views for backup management, observability, media sessions, and widget configuration. +## files +- BackupAlertsTable.tsx | Renders a table of backup alerts with severity badges, timestamps, and acknowledge buttons | exp: func:BackupAlertsTable({ alerts, onAcknowledge }: Props), call:alerts.map, call:severityVariant, call:formatTimestamp, call:onAcknowledge | dep: @/components/ui/badge, @/components/ui/button, @/components/ui/table, ../types/backups +- BackupDashboardWidget.tsx | Displays a dashboard widget summarizing backup job statistics including total jobs, 24-hour success rate, active alerts, and last failure timestamp. | exp: func:BackupDashboardWidget(), call:useBackupDashboard, call:new Date(data.last_failed_at * 1000).toLocaleString | dep: @/components/ui/badge, @/components/ui/card, ../hooks/useBackups +- BackupJobsTable.tsx | Renders a table displaying backup job information with status, schedule, and run history using associated latest run data. | exp: func:BackupJobsTable({ jobs, latestRuns }: Props), call:jobs.map, call:latestRuns.get, call:formatInterval, call:statusVariant, call:formatTimestamp | dep: @/components/ui/badge, @/components/ui/table, ../types/backups +- BackupRunsTable.tsx | Renders a filterable table of backup runs with status, duration, size, and timestamp formatting. | exp: func:BackupRunsTable({ runs }: Props), call:useState, call:runs.filter, call:filteredRuns.map, call:statusVariant, call:formatDuration, call:formatBytes, call:formatTimestamp | dep: react, @/components/ui/badge, @/components/ui/select, @/components/ui/table, ../types/backups +- BackupsPage.tsx | A React page component that displays backup management information across three tabs (Jobs, Runs, and Alerts) with data fetched from custom hooks. | exp: func:BackupsPage(), call:useState, call:useBackupJobs, call:useBackupRuns, call:useBackupAlerts, call:useAcknowledgeAlert, call:latestRuns.get, call:latestRuns.set, call:acknowledgeMutation.mutate | dep: react, @/components/ui/tabs, ../hooks/useBackups, ./BackupAlertsTable, ./BackupJobsTable, ./BackupRunsTable +- ConfirmDialog.tsx | Reusable confirmation dialog component that wraps shadcn/ui Dialog primitives with standardized cancel/confirm footer behavior. | exp: func:ConfirmDialog({ open, title, message, confirmLabel = "Delete", onCancel, onConfirm, busy, }: { open: boolean; title: string; message: string; confirmLabel?: string; onCancel: () => void; onConfirm: () => void; busy?: boolean; }), call:onCancel | dep: @/components/ui/dialog, ./DialogFooter +- DialogFooter.tsx | Renders a dialog footer with cancel/confirm buttons that maps legacy MUI-style props to shadcn Button variants for backward compatibility | exp: func:DialogFooter({ onCancel, cancelLabel = "Cancel", onConfirm, confirmLabel, confirmBusyLabel, confirmDisabled, confirmColor = "primary", confirmVariant = "contained", confirmStartIcon, secondaryAction, }: DialogFooterProps), call:resolveConfirmVariant | dep: react, @/components/ui/button +- HoverEditButton.tsx | A hover-reveal edit button component that migrates from MUI to shadcn/ui while preserving backward-compatible CSS classes for existing hover-reveal behavior. | exp: func:HoverEditButton({ onClick, label = "Edit", }: HoverEditButtonProps), call:e.stopPropagation, call:onClick | dep: lucide-react, @/components/ui/button +- LibraryOverview.tsx | Renders a two-column responsive grid displaying movie and TV library counts using shadcn/ui Card components | exp: func:LibraryOverview({ libraries }: Props), call:libraries.filter, call:movieLibs.map, call:lib.total.toLocaleString, call:lib.movies.toLocaleString, call:tvLibs.map, call:lib.series.toLocaleString | dep: @/components/ui/card, ../types +- MetricCard.tsx | Renders a compact metric display card with label, value, and optional subtext using Tailwind CSS styling. | exp: func:MetricCard({ label, value, subtext }: Props) | dep: @/components/ui/card +- NowPlaying.tsx | Renders a now-playing panel by wrapping SessionActivityPanel with a specific empty message for user activity sessions. | exp: func:NowPlaying({ sessions, onSelectSession }: Props) | dep: ../types, ./SessionActivityPanel +- ObservabilityPage.tsx | This file renders a dashboard page that provides a unified observability view of system health, alerts, metrics, and logs from services like Prometheus, Alertmanager, and Grafana. | exp: func:ObservabilityPage(), call:useAlertmanagerAlerts, call:useAlertmanagerStatus, call:useGrafanaStatus, call:usePrometheusStatus, call:usePrometheusTargets, call:useMonitoringMachines, call:useServiceInstances, call:useState, call:grafanaServices.find, call:useMemo, call:machines.find, call:encodeURIComponent, call:JSON.stringify, call:alertsSummary?.alerts.some, call:alertsSummary.alerts.map, call:machines.map | dep: react, react-router-dom, lucide-react, ../hooks/useObservability, ../hooks/useServices, @/components/ui/card, @/components/ui/badge, @/components/ui/alert, @/components/ui/button, @/components/ui/select, @/components/ui/skeleton, @/components/ui/collapsible, ../types, @/components/ui (card, badge, alert, button, select, skeleton, collapsible) +- SectionCard.tsx | A reusable card component that renders a titled section with optional description and action, built on shadcn/ui Card primitives for comfortable density layout. | exp: func:SectionCard({ title, description, action, children, }: SectionCardProps) | dep: react, @/components/ui/card +- SelectionRailCard.tsx | A reusable card component for a selection rail UI with titled header, scrollable body, and optional footer, preserving legacy API compatibility during a migration from MUI. | exp: func:SelectionRailCard({ title, description, children, footer, minHeight = 420, }: SelectionRailCardProps) | dep: react, @/components/ui/card +- SessionActivityPanel.tsx | Renders a scrollable table panel displaying active media streaming sessions with state badges, metadata, and optional user selection actions. | exp: func:SessionActivityPanel({ sessions, emptyMessage = "No live sessions matched to this user.", selectedUserLabel, onSelectSession, }: Props), call:buildStatusSummary, call:sessions.map, call:formatStateLabel, call:onSelectSession, call:sessionStateVariant, call:event.stopPropagation | dep: @/components/ui/badge, @/components/ui/button, @/components/ui/table, ../types +- TabbedCard.tsx | Renders a card with a line-style tab bar header and content area, acting as a controlled wrapper around shadcn/ui Tabs for backward-compatible API migration from MUI. | exp: func:TabbedCard({ value, onChange, tabs, children, }: TabbedCardProps), call:onChange, call:String | dep: react, @/components/ui/card, @/components/ui/tabs +- WidgetConfigDialog.tsx | A React dialog component for managing dashboard widget instances, including adding, editing, reordering, enabling/disabling, and deleting widgets with service-specific configuration support. | exp: func:WidgetConfigDialog({ open, onClose }: Props), call:useWidgetInstances, call:useServiceInstances, call:useTasks, call:useSaveWidgetInstance, call:useDeleteWidgetInstance, call:useState, call:useMemo, call:[...instances].sort, call:setDraft, call:SERVICE_REGISTRY[ services.find((s) => s.id === serviceId)?.service_type ?? "" ]?.widgets.find, call:services.find, call:saveWidget.mutateAsync, call:reset, call:Promise.all, call:deleteWidget.mutateAsync, call:onClose, call:SERVICE_REGISTRY[ services.find((s) => s.id === draft.serviceId)?.service_type ?? "" ]?.widgets.find, call:String, call:Number, call:sortedInstances.map, call:bindingLabel, call:moveInstance, call:toggleEnabled, call:startEdit, call:removeInstance, call:Object.values(BUILTIN_WIDGETS).map, call:startAddBuiltIn, call:services .filter((s) => s.enabled) .flatMap, call:(SERVICE_REGISTRY[s.service_type]?.widgets ?? []).map, call:startAddService | dep: react, @/components/ui/dialog, @/components/ui/button, @/components/ui/input, @/components/ui/label, @/components/ui/switch, @/components/ui/select, @/components/ui/badge, @/components/ui/alert, lucide-react, ../hooks/useWidgets, ../hooks/useServices, ../hooks/useSettings, ../types, ../integrations/registry +- WidgetInstance.tsx | Renders a widget instance by resolving its component from a registry or displaying an error for unknown widgets. | exp: func:WidgetInstanceCard({ widget }: Props), call:useServiceInstances, call:resolveWidget | dep: @/components/ui/alert, ../hooks/useServices, ../integrations/registry, ../types, ./SectionCard +## arch +Functional component pattern using shadcn/ui and Tailwind CSS, with composable table/card/dialog primitives, custom hooks for data fetching, and a backward-compatibility layer easing migration from MUI. +## tags +call:use, components, ui, card, backup, widget, table, dialog +## symbols +- BackupAlertsTable +- BackupDashboardWidget +- BackupJobsTable +- BackupRunsTable +- BackupsPage +- ConfirmDialog +- DialogFooter +- HoverEditButton +## workflows +- change components behavior + read: BackupAlertsTable.tsx, BackupDashboardWidget.tsx, BackupJobsTable.tsx +- explore components subdirectories + index: frontend/src/components/__tests__/.pi-map.index.md, frontend/src/components/ui/.pi-map.index.md +## dirty +- diff --git a/frontend/src/components/__tests__/.pi-map.index.md b/frontend/src/components/__tests__/.pi-map.index.md new file mode 100644 index 0000000..98bfce9 --- /dev/null +++ b/frontend/src/components/__tests__/.pi-map.index.md @@ -0,0 +1,32 @@ +# frontend/src/components/__tests__ (index) +dir: frontend/src/components/__tests__ + +## role +Test suite providing unit test coverage for reusable UI components and feature widgets in the frontend. +## parent +index: frontend/src/components/.pi-map.index.md +map: frontend/src/components/.pi-map.md +## children +- +## files +- BackupAlertsTable.test.tsx +- BackupDashboardWidget.test.tsx +- BackupRunsTable.test.tsx +- ConfirmDialog.test.tsx +- DialogFooter.test.tsx +- HoverEditButton.test.tsx +- LibraryOverview.test.tsx +- MetricCard.test.tsx +- NowPlaying.test.tsx +- SectionCard.test.tsx +- SelectionRailCard.test.tsx +- SessionActivityPanel.test.tsx +- TabbedCard.test.tsx +## links +index: frontend/src/components/__tests__/.pi-map.index.md +map: frontend/src/components/__tests__/.pi-map.md +## workflows +- update __tests__ tests + read: BackupAlertsTable.test.tsx, BackupDashboardWidget.test.tsx, BackupRunsTable.test.tsx +## dirty +- diff --git a/frontend/src/components/__tests__/.pi-map.md b/frontend/src/components/__tests__/.pi-map.md new file mode 100644 index 0000000..4118491 --- /dev/null +++ b/frontend/src/components/__tests__/.pi-map.md @@ -0,0 +1,32 @@ +# frontend/src/components/__tests__ +dir: frontend/src/components/__tests__ + +index: frontend/src/components/__tests__/.pi-map.index.md + +## role +Test suite providing unit test coverage for reusable UI components and feature widgets in the frontend. +## files +- BackupAlertsTable.test.tsx | Unit tests for the BackupAlertsTable component verifying severity badge mapping, acknowledge button behavior, and conditional rendering | dep: vitest, @testing-library/react, @testing-library/user-event, ../BackupAlertsTable, ../../types/backups, BackupAlertsTable component, BackupAlert type +- BackupDashboardWidget.test.tsx | Unit tests for BackupDashboardWidget component covering loading, success, and alert states | dep: vitest, @testing-library/react, ../BackupDashboardWidget, ../../hooks/useBackups, BackupDashboardWidget, useBackupDashboard hook +- BackupRunsTable.test.tsx | Tests the BackupRunsTable component's status-to-badge variant mapping and formatting of duration/bytes values. | dep: vitest, @testing-library/react, ../BackupRunsTable, ../../types/backups, BackupRunsTable, types/backups +- ConfirmDialog.test.tsx | Tests the ConfirmDialog component's rendering behavior and user interaction handlers. | dep: vitest, @testing-library/react, @testing-library/user-event, ../ConfirmDialog, ConfirmDialog +- DialogFooter.test.tsx | Tests the DialogFooter component's rendering, callback wiring, busy state, destructive styling, and secondary action support. | dep: vitest, @testing-library/react, @testing-library/user-event, ../DialogFooter +- HoverEditButton.test.tsx | Tests the HoverEditButton component's click behavior and label customization. | dep: vitest, @testing-library/react, @testing-library/user-event, ../HoverEditButton, HoverEditButton +- LibraryOverview.test.tsx | Tests the LibraryOverview component renders movie and TV library cards with correct counts | dep: vitest, @testing-library/react, ../LibraryOverview, ../../types, LibraryOverview component, LibraryCount type +- MetricCard.test.tsx | Tests the MetricCard component rendering with and without optional subtext prop | dep: vitest, @testing-library/react, ../MetricCard +- NowPlaying.test.tsx | Tests the NowPlaying component's empty-state rendering when no sessions exist | dep: vitest, @testing-library/react, ../NowPlaying, NowPlaying component +- SectionCard.test.tsx | Unit tests for the SectionCard component verifying it renders title, description, action, and children correctly | dep: vitest, @testing-library/react, ../SectionCard, SectionCard +- SelectionRailCard.test.tsx | Unit tests for the SelectionRailCard component verifying it renders title, body, footer, and applies minHeight prop correctly. | dep: vitest, @testing-library/react, ../SelectionRailCard, SelectionRailCard +- SessionActivityPanel.test.tsx | Tests the SessionActivityPanel component's rendering of session states, empty state, and user interaction callbacks. | dep: vitest, @testing-library/react, @testing-library/user-event, ../SessionActivityPanel, ../../types, SessionActivityPanel, types +- TabbedCard.test.tsx | Tests the TabbedCard component's rendering and tab selection behavior | dep: vitest, @testing-library/react, @testing-library/user-event, ../TabbedCard, @/components/ui/tabs +## arch +Component-level React Testing Library tests organized as co-located per-component test files following a flat naming convention. +## tags +library, @testing, tests, component, vitest, react, user, card.test +## symbols +- +## workflows +- update __tests__ tests + read: BackupAlertsTable.test.tsx, BackupDashboardWidget.test.tsx, BackupRunsTable.test.tsx +## dirty +- diff --git a/frontend/src/components/ui/.pi-map.index.md b/frontend/src/components/ui/.pi-map.index.md new file mode 100644 index 0000000..c360055 --- /dev/null +++ b/frontend/src/components/ui/.pi-map.index.md @@ -0,0 +1,46 @@ +# frontend/src/components/ui (index) +dir: frontend/src/components/ui + +## role +Reusable, design-system UI component library providing styled, accessible primitives (buttons, inputs, dialogs, tables, etc.) for consistent application interfaces. +## parent +index: frontend/src/components/.pi-map.index.md +map: frontend/src/components/.pi-map.md +## children +- frontend/src/components/ui/__tests__ + index: frontend/src/components/ui/__tests__/.pi-map.index.md + map: frontend/src/components/ui/__tests__/.pi-map.md +## files +- alert.tsx +- avatar.tsx +- badge.tsx +- button.tsx +- card.tsx +- checkbox.tsx +- collapsible.tsx +- data-table.tsx +- dialog.tsx +- dropdown-menu.tsx +- input.tsx +- label.tsx +- progress.tsx +- scroll-area.tsx +- select.tsx +- separator.tsx +- sheet.tsx +- skeleton.tsx +- switch.tsx +- table.tsx +- tabs.tsx +- textarea.tsx +- tooltip.tsx +## links +index: frontend/src/components/ui/.pi-map.index.md +map: frontend/src/components/ui/.pi-map.md +## workflows +- change ui behavior + read: alert.tsx, avatar.tsx, badge.tsx +- explore ui subdirectories + index: frontend/src/components/ui/__tests__/.pi-map.index.md +## dirty +- diff --git a/frontend/src/components/ui/.pi-map.md b/frontend/src/components/ui/.pi-map.md new file mode 100644 index 0000000..7a5bf6c --- /dev/null +++ b/frontend/src/components/ui/.pi-map.md @@ -0,0 +1,51 @@ +# frontend/src/components/ui +dir: frontend/src/components/ui + +index: frontend/src/components/ui/.pi-map.index.md + +## role +Reusable, design-system UI component library providing styled, accessible primitives (buttons, inputs, dialogs, tables, etc.) for consistent application interfaces. +## files +- alert.tsx | Implements a composable alert/notification UI component with variant styling and subcomponents for title, description, and action. | dep: react, class-variance-authority, @/lib/utils +- avatar.tsx | Provides a set of composable React avatar components with size variants, fallback support, badges, and grouped avatar layouts using Radix UI primitives. | dep: react, radix-ui, @/lib/utils +- badge.tsx | A reusable React Badge component with multiple style variants built using class-variance-authority | exp: func:Badge({ className, variant = "default", asChild = false, ...props }: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }), call:cn, call:badgeVariants | dep: react, class-variance-authority, radix-ui, @/lib/utils +- button.tsx | A reusable, polymorphic Button component with multiple style variants and sizes using Tailwind CSS and class-variance-authority. | dep: react, class-variance-authority, radix-ui, @/lib/utils, radix-ui (Slot), @/lib/utils (cn) +- card.tsx | A composable React card component system with sub-components for header, title, description, action, content, and footer with Tailwind styling. | dep: react, @/lib/utils, React +- checkbox.tsx | A styled checkbox UI component built on Radix UI primitives with Tailwind CSS styling and accessibility features. | dep: react, radix-ui, @/lib/utils, lucide-react +- collapsible.tsx | Wraps Radix UI's Collapsible primitive components with consistent data-slot attributes for a collapsible UI pattern. | dep: radix-ui, react +- data-table.tsx | Reusable data table component wrapping TanStack Table with shadcn UI primitives, supporting row selection, column visibility toggling, pagination (client and server-side), and row click handling — explicitly excluding sorting and column resizing. | exp: DataTableProps, func:DataTable({ columns, data, getRowId, enableRowSelection = false, rowSelection, onRowSelectionChange, onRowClick, columnVisibility, onColumnVisibilityChange, enableColumnVisibilityToggle = false, enablePagination = false, manualPagination = false, pagination, onPaginationChange, pageSizeOptions = [10, 20, 30, 50], rowCount, emptyMessage = "No results.", }: DataTableProps), call:React.useMemo, call:table.getIsAllPageRowsSelected, call:table.getIsSomePageRowsSelected, call:table.toggleAllPageRowsSelected, call:e.stopPropagation, call:row.getIsSelected, call:row.toggleSelected, call:useReactTable, call:getCoreRowModel, call:getPaginationRowModel, call:Math.max, call:Math.ceil, call:table.getPageCount, call:table .getAllColumns() .filter((column) => column.getCanHide()) .map, call:column.getCanHide, call:column.getIsVisible, call:column.toggleVisibility, call:e.preventDefault, call:table.getHeaderGroups().map, call:headerGroup.headers.map, call:flexRender, call:header.getContext, call:table.getRowModel().rows.map, call:cn, call:onRowClick, call:row.getVisibleCells().map, call:cell.getContext | dep: react, @tanstack/react-table, lucide-react, @/lib/utils, @/components/ui/button, @/components/ui/checkbox, @/components/ui/table, @/components/ui/dropdown-menu, @/components/ui/select +- dialog.tsx | A React dialog/modal component built on Radix UI primitives with styled subcomponents (overlay, content, header, footer, title, description) and close button functionality. | dep: react, radix-ui, @/lib/utils, @/components/ui/button, lucide-react +- dropdown-menu.tsx | A styled React dropdown menu component library built on top of Radix UI primitives with Tailwind CSS styling and animation support. | dep: react, radix-ui, @/lib/utils, lucide-react +- input.tsx | A reusable styled input component that wraps HTML input elements with consistent Tailwind CSS styling and theme-aware states. | dep: react, @/lib/utils, React +- label.tsx | A styled React label component that wraps Radix UI's LabelPrimitive with default Tailwind CSS styling and disabled state handling. | dep: react, radix-ui, @/lib/utils +- progress.tsx | A React component that renders a styled horizontal progress bar using Radix UI primitives with customizable value and appearance. | dep: react, radix-ui, @/lib/utils +- scroll-area.tsx | A React component that wraps Radix UI's ScrollArea primitive to provide a styled, accessible scrollable container with custom scrollbar support. | dep: react, radix-ui, @/lib/utils +- select.tsx | A styled React select dropdown component built on top of Radix UI's Select primitive with Tailwind CSS styling and scroll buttons. | dep: react, radix-ui, @/lib/utils, lucide-react +- separator.tsx | A React component that wraps Radix UI's Separator primitive to render accessible, styled horizontal or vertical dividers with Tailwind CSS. | dep: react, radix-ui, @/lib/utils +- sheet.tsx | Implements a customizable slide-out panel UI component (sheet) with overlay, animations, and positioning options. | dep: react, radix-ui, @/lib/utils, @/components/ui/button, lucide-react +- skeleton.tsx | A reusable Skeleton loading placeholder component that renders a pulsing, rounded div with muted background styling. | dep: @/lib/utils, React +- switch.tsx | A reusable React Switch component that wraps Radix UI's Switch primitive with customizable styling, size variants, and accessibility features. | dep: react, radix-ui, @/lib/utils +- table.tsx | A reusable, styled table component library for React with subcomponents for header, body, footer, rows, cells, and captions. | dep: react, @/lib/utils, React, cn utility from @/lib/utils +- tabs.tsx | A React component library implementing an accessible, styled tabs UI primitive using Radix UI with support for horizontal/vertical orientations and default/line variants. | dep: react, class-variance-authority, radix-ui, @/lib/utils, React +- textarea.tsx | A reusable textarea component that applies consistent styling using Tailwind CSS classes and supports all native textarea props. | dep: react, @/lib/utils, React, cn utility from @/lib/utils +- tooltip.tsx | Provides a styled, composable tooltip component built on top of Radix UI primitives with Tailwind CSS animations and theming. | dep: react, radix-ui, @/lib/utils +## arch +shadcn/ui pattern combining Radix UI accessibility primitives, class-variance-authority (CVA) for variant styling, and Tailwind CSS utility classes, with composable sub-components and TanStack Table for data grids. +## tags +react, radix, lib, utils, ui, component, table, styled +## symbols +- Badge +- DataTable +- call:cn +- call:badgeVariants +- DataTableProps +- call:React.useMemo +- call:table.getIsAllPageRowsSelected +- call:table.getIsSomePageRowsSelected +## workflows +- change ui behavior + read: alert.tsx, avatar.tsx, badge.tsx +- explore ui subdirectories + index: frontend/src/components/ui/__tests__/.pi-map.index.md +## dirty +- diff --git a/frontend/src/components/ui/__tests__/.pi-map.index.md b/frontend/src/components/ui/__tests__/.pi-map.index.md new file mode 100644 index 0000000..106056e --- /dev/null +++ b/frontend/src/components/ui/__tests__/.pi-map.index.md @@ -0,0 +1,21 @@ +# frontend/src/components/ui/__tests__ (index) +dir: frontend/src/components/ui/__tests__ + +## role +Test suite for UI component primitives, validating rendering, variants, and interactive behaviors of foundational elements like Badge and DataTable. +## parent +index: frontend/src/components/ui/.pi-map.index.md +map: frontend/src/components/ui/.pi-map.md +## children +- +## files +- badge.test.tsx +- data-table.test.tsx +## links +index: frontend/src/components/ui/__tests__/.pi-map.index.md +map: frontend/src/components/ui/__tests__/.pi-map.md +## workflows +- update __tests__ tests + read: badge.test.tsx, data-table.test.tsx +## dirty +- diff --git a/frontend/src/components/ui/__tests__/.pi-map.md b/frontend/src/components/ui/__tests__/.pi-map.md new file mode 100644 index 0000000..e4b2ca4 --- /dev/null +++ b/frontend/src/components/ui/__tests__/.pi-map.md @@ -0,0 +1,21 @@ +# frontend/src/components/ui/__tests__ +dir: frontend/src/components/ui/__tests__ + +index: frontend/src/components/ui/__tests__/.pi-map.index.md + +## role +Test suite for UI component primitives, validating rendering, variants, and interactive behaviors of foundational elements like Badge and DataTable. +## files +- badge.test.tsx | Tests that the Badge component renders a success variant with the correct data attribute and CSS class using a Vitest testing harness. | dep: vitest, @testing-library/react, ../badge +- data-table.test.tsx | Tests a DataTable React component wrapper around TanStack Table, covering rendering, row selection, column visibility, row click events, empty states, and pagination. | dep: vitest, react, @testing-library/react, @testing-library/user-event, @tanstack/react-table, ../data-table +## arch +Vitest and React Testing Lab testing patterns with declarative test cases covering visual variants, TanStack Table integration, user interactions, and edge-case rendering. +## tags +react, data, vitest, @testing, library, table, tests, badge +## symbols +- +## workflows +- update __tests__ tests + read: badge.test.tsx, data-table.test.tsx +## dirty +- diff --git a/frontend/src/hooks/.pi-map.index.md b/frontend/src/hooks/.pi-map.index.md new file mode 100644 index 0000000..4a067c2 --- /dev/null +++ b/frontend/src/hooks/.pi-map.index.md @@ -0,0 +1,31 @@ +# frontend/src/hooks (index) +dir: frontend/src/hooks + +## role +Centralized React custom hooks package providing data-fetching, caching, and mutation logic via TanStack Query for all feature domains across the application. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- useBackups.ts +- useDashboard.ts +- useFiles.ts +- useMedia.ts +- useObservability.ts +- usePersistentState.ts +- useSendUserMessage.ts +- useServices.ts +- useSettings.ts +- useUserMessageQueueStatus.ts +- useUsers.ts +- useWidgets.ts +## links +index: frontend/src/hooks/.pi-map.index.md +map: frontend/src/hooks/.pi-map.md +## workflows +- change hooks behavior + read: useBackups.ts, useDashboard.ts, useFiles.ts +## dirty +- diff --git a/frontend/src/hooks/.pi-map.md b/frontend/src/hooks/.pi-map.md new file mode 100644 index 0000000..3f49719 --- /dev/null +++ b/frontend/src/hooks/.pi-map.md @@ -0,0 +1,38 @@ +# frontend/src/hooks +dir: frontend/src/hooks + +index: frontend/src/hooks/.pi-map.index.md + +## role +Centralized React custom hooks package providing data-fetching, caching, and mutation logic via TanStack Query for all feature domains across the application. +## files +- useBackups.ts | Provides React Query custom hooks for fetching and managing backup-related data including jobs, runs, alerts, and dashboard metrics with automatic refetching. | exp: func:useBackupJobs(), call:useQuery, func:useBackupJob(jobId: string), call:useQuery, call:fetchBackupJob, func:useBackupRuns(jobId: string, status: string), call:useQuery, call:fetchBackupRuns, func:useBackupAlerts(jobId: string, acknowledged: boolean, severity: string), call:useQuery, call:fetchBackupAlerts, func:useAcknowledgeAlert(), call:useQueryClient, call:useMutation, call:queryClient.invalidateQueries, func:useBackupDashboard(), call:useQuery | dep: @tanstack/react-query, ../api/backups +- useDashboard.ts | Provides React Query custom hooks for fetching and mutating dashboard data including counts, libraries, activity, and shortcuts. | exp: useNowPlaying, func:useCounts(jellyfinServiceId: string), call:useQuery, call:fetchCounts, func:useLibraries(jellyfinServiceId: string), call:useQuery, call:fetchLibraries, func:useActivity(jellyfinServiceId: string), call:useQuery, call:fetchActivity, func:useDashboardShortcuts(), call:useQuery, func:useSaveDashboardShortcut(), call:useQueryClient, call:useMutation, call:saveDashboardShortcut, call:queryClient.invalidateQueries, func:useDeleteDashboardShortcut(), call:useQueryClient, call:useMutation, call:deleteDashboardShortcut, call:queryClient.invalidateQueries | dep: @tanstack/react-query, ../api/client, ../types +- useFiles.ts | Custom React hooks for file operations and job management using TanStack Query for data fetching, caching, and mutations. | exp: func:useDirectoryListing(path: string, machineId: string), call:useQuery, call:fetchDirectoryListing, func:useFfprobe(path: string, enabled, machineId: string), call:useQuery, call:fetchFfprobe, func:useStat(path: string, enabled, machineId: string), call:useQuery, call:fetchStat, func:useJobTemplates(), call:useQuery, func:useRunJob(machineId: string), call:useMutation, call:runJob | dep: @tanstack/react-query, ../api/client +- useMedia.ts | Custom React hooks for managing media status, querying media, and controlling index build operations using TanStack Query. | exp: func:useMediaStatus(jellyfinServiceId: string), call:useQuery, call:fetchMediaStatus, func:useMediaQuery(params: { libraries?: string; types?: string; search?: string; hdr_filter?: string; sort_key?: string; sort_order?: string; limit?: number; offset?: number; jellyfinServiceId?: string; enabled?: boolean; }), call:useQuery, call:queryMedia, func:useBuildIndex(jellyfinServiceId: string), call:useQueryClient, call:useMutation, call:buildMediaIndex, call:invalidateMedia, func:useStopBuildIndex(jellyfinServiceId: string), call:useQueryClient, call:useMutation, call:stopMediaIndexBuild, call:invalidateMedia, func:useForceStopBuildIndex(jellyfinServiceId: string), call:useQueryClient, call:useMutation, call:forceStopMediaIndexBuild, call:invalidateMedia | dep: @tanstack/react-query, ../api/client +- useObservability.ts | Custom React hooks for fetching and polling observability data (Alertmanager alerts/status, Grafana/Prometheus status, Prometheus targets, monitoring machines) via React Query. | exp: func:useAlertmanagerAlerts(), call:useQuery, func:useAlertmanagerStatus(), call:useQuery, func:useGrafanaStatus(), call:useQuery, func:usePrometheusStatus(), call:useQuery, func:usePrometheusTargets(), call:useQuery, func:useMonitoringMachines(), call:useQuery | dep: @tanstack/react-query, ../api/client +- usePersistentState.ts | React hook that synchronizes state with localStorage to persist UI state across browser sessions | exp: func:usePersistentState(key: string, initialValue: T | (() => T)), call:useCallback, call:window.localStorage.getItem, call:JSON.parse, call:useState, call:useEffect, call:window.localStorage.setItem, call:JSON.stringify | dep: react +- useSendUserMessage.ts | Custom React hook that sends a user message and invalidates the message queue cache on success | exp: func:useSendUserMessage(), call:useQueryClient, call:useMutation, call:queryClient.invalidateQueries | dep: @tanstack/react-query, ../api/client +- useServices.ts | Provides custom React hooks for fetching, creating, updating, and deleting service instances and types using React Query. | exp: func:useServiceTypes(), call:useQuery, func:useServiceInstances(serviceType: string), call:useQuery, call:fetchServiceInstances, func:useSaveServiceInstance(), call:useQueryClient, call:useMutation, call:updateServiceInstance, call:createServiceInstance, call:queryClient.invalidateQueries, func:useDeleteServiceInstance(), call:useQueryClient, call:useMutation, call:deleteServiceInstance, call:queryClient.invalidateQueries | dep: @tanstack/react-query, ../api/services, ../types +- useSettings.ts | Provides React Query custom hooks for managing application settings including monitoring machines, SSH keys, saved tasks, and local database reset operations. | exp: func:useMonitoringSettings(), call:useQuery, func:useSSHKeys(), call:useQuery, func:useGenerateSSHKey(), call:useMutation, call:generateSSHKey, func:useSaveSSHKey(), call:useQueryClient, call:useMutation, call:saveSSHKey, call:queryClient.invalidateQueries, func:useDeleteSSHKey(), call:useQueryClient, call:useMutation, call:deleteSSHKey, call:queryClient.invalidateQueries, func:useTasks(), call:useQuery, func:useTaskRuns(taskId: string), call:useQuery, call:fetchSavedTaskRuns, call:Boolean, func:useSaveTask(), call:useQueryClient, call:useMutation, call:saveTask, call:queryClient.invalidateQueries, func:useDeleteTask(), call:useQueryClient, call:useMutation, call:deleteTask, call:queryClient.invalidateQueries, func:useRunTask(), call:useQueryClient, call:useMutation, call:runTask, call:queryClient.invalidateQueries, func:useSaveMonitoringMachine(), call:useQueryClient, call:useMutation, call:saveMonitoringMachine, call:queryClient.invalidateQueries, func:useTestMonitoringMachineSSH(), call:useMutation, func:useDeleteMonitoringMachine(), call:useQueryClient, call:useMutation, call:deleteMonitoringMachine, call:queryClient.invalidateQueries, func:useResetLocalDatabase(), call:useQueryClient, call:useMutation, call:resetLocalDatabase, call:queryClient.invalidateQueries | dep: @tanstack/react-query, ../api/client, ../types +- useUserMessageQueueStatus.ts | Custom React hook that polls for user message queue status every 5 seconds using TanStack Query | exp: func:useUserMessageQueueStatus(), call:useQuery | dep: @tanstack/react-query, ../api/client +- useUsers.ts | Custom React hook that fetches and caches user directory data using TanStack Query with configurable Jellyfin service ID | exp: func:useUsers(jellyfinServiceId: string), call:useQuery, call:fetchUsers | dep: @tanstack/react-query, ../api/client, ../types +- useWidgets.ts | Custom React hooks for managing widget data fetching, caching, and mutations using TanStack Query. | exp: func:useWidgetInstances(), call:useQuery, func:useWidgetData(widgetId: string, refreshInterval: number), call:useQuery, call:fetchWidgetData, func:useSaveWidgetInstance(), call:useQueryClient, call:useMutation, call:updateWidgetInstance, call:createWidgetInstance, call:queryClient.invalidateQueries, func:useDeleteWidgetInstance(), call:useQueryClient, call:useMutation, call:deleteWidgetInstance, call:queryClient.invalidateQueries, func:useBuiltinWidgetKinds(), call:useQuery | dep: @tanstack/react-query, ../api/widgets, ../types +## arch +React Query (TanStack Query) pattern with domain-specific hook modules, leveraging automatic refetching, polling, and cache invalidation, supplemented by a localStorage-backed persistent state hook. +## tags +call:use, query, client, mutation, call:query, client.invalidate, queries, call:fetch +## symbols +- useBackupJobs +- useBackupJob +- useBackupRuns +- useBackupAlerts +- useAcknowledgeAlert +- useBackupDashboard +- useCounts +- useLibraries +## workflows +- change hooks behavior + read: useBackups.ts, useDashboard.ts, useFiles.ts +## dirty +- diff --git a/frontend/src/integrations/.pi-map.index.md b/frontend/src/integrations/.pi-map.index.md new file mode 100644 index 0000000..e3f2c25 --- /dev/null +++ b/frontend/src/integrations/.pi-map.index.md @@ -0,0 +1,23 @@ +# frontend/src/integrations (index) +dir: frontend/src/integrations + +## role +Frontend integration layer that maps backend service types and widgets to their corresponding React components, metadata, and configuration schemas. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- registry.test.ts +- registry.ts +## links +index: frontend/src/integrations/.pi-map.index.md +map: frontend/src/integrations/.pi-map.md +## workflows +- change integrations behavior + read: registry.ts +- update integrations tests + read: registry.test.ts +## dirty +- diff --git a/frontend/src/integrations/.pi-map.md b/frontend/src/integrations/.pi-map.md new file mode 100644 index 0000000..3bbbe0e --- /dev/null +++ b/frontend/src/integrations/.pi-map.md @@ -0,0 +1,30 @@ +# frontend/src/integrations +dir: frontend/src/integrations + +index: frontend/src/integrations/.pi-map.index.md + +## role +Frontend integration layer that maps backend service types and widgets to their corresponding React components, metadata, and configuration schemas. +## files +- registry.test.ts | Tests the service and widget registry module, verifying correct service registrations, widget bindings, and widget resolution logic. | dep: vitest, ./registry, ../types +- registry.ts | Defines a frontend registry mapping service types and built-in widgets to their React components, metadata, and config schemas, with a resolver to look up widgets by instance. | exp: WidgetComponentProps, ServiceWidgetBinding, ServiceBinding, SERVICE_REGISTRY, BUILTIN_WIDGETS, ResolvedWidget, func:getServiceBinding(serviceType: string) → ServiceBinding | undefined, func:getBuiltinBinding(kind: string) → ServiceWidgetBinding | undefined, func:resolveWidget(widget: WidgetInstance, services: ServiceInstance[]) → ResolvedWidget | undefined, call:services.find, call:getServiceBinding, call:binding?.widgets.find, call:getBuiltinBinding, func:enrichServiceTypes(types: ServiceTypeInfo[]) → ServiceTypeInfo[] | dep: react, ../widgets/AlertmanagerAlertsWidget, ../widgets/BackupsWidget, ../widgets/GrafanaLinkWidget, ../widgets/JellyfinWidget, ../widgets/PrometheusMetricWidget, ../widgets/SshTaskWidget, ../widgets/StaticWidget, ../types, AlertmanagerAlertsWidget, BackupsWidget, GrafanaLinkWidget, JellyfinWidget, PrometheusMetricWidget, SshTaskWidget, StaticWidget, types +## arch +Registry pattern implementing a centralized lookup dictionary with a resolver function to dynamically discover and instantiate the correct widget components based on service instances. +## tags +service, binding, widget, widgets, registry, types, builtin, get +## symbols +- getServiceBinding +- getBuiltinBinding +- resolveWidget +- enrichServiceTypes +- WidgetComponentProps +- ServiceWidgetBinding +- ServiceBinding +- SERVICE_REGISTRY +## workflows +- change integrations behavior + read: registry.ts +- update integrations tests + read: registry.test.ts +## dirty +- diff --git a/frontend/src/lib/.pi-map.index.md b/frontend/src/lib/.pi-map.index.md new file mode 100644 index 0000000..96999c4 --- /dev/null +++ b/frontend/src/lib/.pi-map.index.md @@ -0,0 +1,20 @@ +# frontend/src/lib (index) +dir: frontend/src/lib + +## role +Provides shared utility functions for the frontend application. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- utils.ts +## links +index: frontend/src/lib/.pi-map.index.md +map: frontend/src/lib/.pi-map.md +## workflows +- change lib behavior + read: utils.ts +## dirty +- diff --git a/frontend/src/lib/.pi-map.md b/frontend/src/lib/.pi-map.md new file mode 100644 index 0000000..fcea5ac --- /dev/null +++ b/frontend/src/lib/.pi-map.md @@ -0,0 +1,22 @@ +# frontend/src/lib +dir: frontend/src/lib + +index: frontend/src/lib/.pi-map.index.md + +## role +Provides shared utility functions for the frontend application. +## files +- utils.ts | Utility function that merges Tailwind CSS classes with proper deduplication and conflict resolution | exp: func:cn(...inputs: ClassValue[]), call:twMerge, call:clsx | dep: clsx, tailwind-merge +## arch +Functional utility module pattern leveraging clsx and tailwind-merge for deterministic CSS class composition. +## tags +merge, tailwind, cn, call:tw, call:clsx, utils, utility, merges +## symbols +- cn +- call:twMerge +- call:clsx +## workflows +- change lib behavior + read: utils.ts +## dirty +- diff --git a/frontend/src/pages/.pi-map.index.md b/frontend/src/pages/.pi-map.index.md new file mode 100644 index 0000000..72b28c1 --- /dev/null +++ b/frontend/src/pages/.pi-map.index.md @@ -0,0 +1,34 @@ +# frontend/src/pages (index) +dir: frontend/src/pages + +## role +Top-level page components constituting the primary routed views of the frontend application, each encapsulating a distinct feature domain (dashboard, media, services, actions, files, settings, users). +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- frontend/src/pages/__tests__ + index: frontend/src/pages/__tests__/.pi-map.index.md + map: frontend/src/pages/__tests__/.pi-map.md +## files +- Actions.tsx +- Applications.tsx +- Dashboard.tsx +- FileBrowser.impl.tsx +- FileBrowser.tsx +- Media.tsx +- ServicePage.tsx +- ServicesPage.tsx +- Settings.tsx +- Users.tsx +- UsersPage.impl.tsx +## links +index: frontend/src/pages/.pi-map.index.md +map: frontend/src/pages/.pi-map.md +## workflows +- change pages behavior + read: Actions.tsx, Applications.tsx, Dashboard.tsx +- explore pages subdirectories + index: frontend/src/pages/__tests__/.pi-map.index.md +## dirty +- diff --git a/frontend/src/pages/.pi-map.md b/frontend/src/pages/.pi-map.md new file mode 100644 index 0000000..bd1c94e --- /dev/null +++ b/frontend/src/pages/.pi-map.md @@ -0,0 +1,39 @@ +# frontend/src/pages +dir: frontend/src/pages + +index: frontend/src/pages/.pi-map.index.md + +## role +Top-level page components constituting the primary routed views of the frontend application, each encapsulating a distinct feature domain (dashboard, media, services, actions, files, settings, users). +## files +- Actions.tsx | Provides a React component for managing reusable server tasks (shell/python actions) with CRUD operations, service selection, and execution history display. | exp: func:Actions(), call:useServiceInstances, call:useTasks, call:useSaveTask, call:useDeleteTask, call:useRunTask, call:useState, call:emptyTask, call:useMemo, call:tasks.find, call:useTaskRuns, call:setDraft, call:setDraftBaseline, call:setEditOpen, call:setRunServiceId, call:saveTask.mutateAsync, call:setTab, call:String, call:tasks.map, call:openEdit, call:initialFromTask, call:runTask.mutateAsync, call:sshServices.map, call:selectedRuns.data.items.map, call:new Date(run.created_at * 1000).toLocaleString, call:deleteTask.mutate | dep: react, ../types, ../hooks/useSettings, ../hooks/useServices, ../components/DialogFooter, ../components/HoverEditButton, ../components/SectionCard, ../components/SelectionRailCard, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/card, @/components/ui/dialog, @/components/ui/input, @/components/ui/label, @/components/ui/select, @/components/ui/separator, @/components/ui/tabs, @/components/ui/textarea +- Applications.tsx | Renders a tabbed Applications dashboard with Jellyfin library statistics and media management, plus a placeholder for future Nextcloud support. | exp: func:Applications(), call:useState | dep: react, react-router-dom, @/components/ui/alert, @/components/ui/badge, @/components/ui/tabs, ./Media, ../hooks/useDashboard, ../hooks/useServices, ../components/SectionCard, ../components/TabbedCard +- Dashboard.tsx | Dashboard page component for managing and displaying configurable shortcuts and widget instances. | exp: func:Dashboard(), call:useNavigate, call:useDashboardShortcuts, call:useSaveDashboardShortcut, call:useDeleteDashboardShortcut, call:useState, call:emptyShortcut, call:useWidgetInstances, call:useMemo, call:widgetInstances .filter((w) => w.enabled) .sort, call:setShortcutDraft, call:setShortcutDialogOpen, call:saveShortcut.mutateAsync, call:setWidgetDialogOpen, call:shortcuts.map, call:shortcutHref, call:window.open, call:navigate, call:openEditShortcut, call:setDeleteShortcutId, call:visibleWidgets.map, call:Boolean, call:deleteShortcut.mutate | dep: react, react-router-dom, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/card, @/components/ui/dialog, @/components/ui/input, @/components/ui/label, @/components/ui/select, @/components/ui/switch, ../hooks/useDashboard, ../hooks/useWidgets, ../types, ../components/SectionCard, ../components/ConfirmDialog, ../components/DialogFooter, ../components/WidgetInstance, ../components/WidgetConfigDialog, @/components/ui/* +- FileBrowser.impl.tsx | A React component that implements a file browser with directory listing, file selection, ffprobe media metadata inspection, and job execution capabilities. | exp: func:FileBrowser(), call:useSearchParams, call:useState, call:useMonitoringSettings, call:useMemo, call:(machines ?? []).filter, call:machine.services.includes, call:searchParams.get, call:usePersistentState, call:isVideoFile, call:requestedPath.includes, call:requestedPath.replace, call:selectedPath.replace, call:defaultFileBrowserState, call:useNavigate, call:setBrowserState, call:useDirectoryListing, call:useFfprobe, call:useJobTemplates, call:useRunJob, call:updateBrowserState, call:setSearchParams, call:next.set, call:next.delete, call:navigate, call:currentDir.replace, call:rows.push, call:entry.name.split(".").pop, call:formatSize, call:formatTime, call:updater, call:Object.keys(next).filter, call:rows.find, call:templates?.find, call:fileMachines.map, call:refetch, call:String, call:templates.map, call:runJob.mutate, call:navigateToSettings | dep: react, react-router-dom, @tanstack/react-table, @/components/ui/data-table, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/card, @/components/ui/input, @/components/ui/label, @/components/ui/select, @/components/ui/tabs, ../hooks/useFiles, ../hooks/usePersistentState, ../hooks/useSettings, ../components/SectionCard, ../components/TabbedCard +- FileBrowser.tsx | Re-exports the FileBrowser component from its implementation file | dep: ./FileBrowser.impl +- Media.tsx | A React component for managing and browsing Jellyfin media libraries with server-driven pagination, index building controls, and responsive data table display. | exp: func:Media(), call:useNavigate, call:useSearchParams, call:usePrefersSmallScreen, call:useServiceInstances, call:searchParams.get, call:jellyfinServices.find, call:useCounts, call:useLibraries, call:useMediaStatus, call:useBuildIndex, call:useStopBuildIndex, call:useForceStopBuildIndex, call:usePersistentState, call:defaultMediaTabState, call:setMediaState, call:useState, call:useEffect, call:setSearchParams, call:next.set, call:useMediaDataQuery, call:Math.floor, call:updater, call:useMemo, call:navigate, call:encodeURIComponent, call:Math.max, call:Math.ceil, call:formatDuration, call:jellyfinServices.map, call:status.item_count.toLocaleString, call:counts.movies.toLocaleString, call:counts.series.toLocaleString, call:counts.episodes.toLocaleString, call:(libraries?.length ?? 0).toLocaleString, call:buildIndex.mutate, call:stopBuildIndex.mutate, call:forceStopBuildIndex.mutate, call:Math.round, call:status?.build_items_processed?.toLocaleString, call:status?.build_items_total?.toLocaleString, call:status?.build_library_items_processed?.toLocaleString, call:status?.build_library_items_total?.toLocaleString, call:updateMediaState, call:total.toLocaleString | dep: react, react-router-dom, @tanstack/react-table, @/components/ui/data-table, @/components/ui/alert, @/components/ui/button, @/components/ui/card, @/components/ui/input, @/components/ui/label, @/components/ui/progress, @/components/ui/select, ../hooks/useMedia, ../hooks/usePersistentState, ../types, ../hooks/useServices, ../hooks/useDashboard, @/components/ui (data-table, alert, button, card, input, label, progress, select) +- ServicePage.tsx | Provides a UI for viewing and editing a service instance's configuration, secrets, and widget bindings, with save and delete functionality. | exp: func:ServicePage(), call:useParams, call:useServiceInstances, call:useSaveServiceInstance, call:useDeleteServiceInstance, call:useMemo, call:services.find, call:getServiceBinding, call:useState, call:setName, call:setEnabled, call:setHydrated, call:saveService.mutateAsync, call:buildInput, call:setDeleteOpen, call:binding.widgets.map, call:deleteService.mutate | dep: react, react-router-dom, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/input, @/components/ui/label, @/components/ui/switch, ../hooks/useServices, ../types, ../components/SectionCard, ../components/ConfirmDialog, ../integrations/registry, @/components/ui/* +- ServicesPage.tsx | A React page component for managing external service instances, allowing users to view, create, and delete service configurations with dynamic form fields based on service type schemas. | exp: func:ServicesPage(), call:useNavigate, call:useServiceInstances, call:useServiceTypes, call:useDeleteServiceInstance, call:useState, call:useMemo, call:map.get, call:list.push, call:map.set, call:[...map.entries()].sort, call:map.entries, call:a[0].localeCompare, call:types.find, call:getServiceBinding, call:setCreateOpen, call:grouped.map, call:typeName, call:instances.map, call:Object.entries(s.secrets_set).some, call:navigate, call:setDeleteId, call:Boolean, call:deleteService.mutate | dep: react, react-router-dom, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/input, @/components/ui/label, @/components/ui/switch, @/components/ui/dialog, lucide-react, ../hooks/useServices, ../types, ../components/SectionCard, ../components/ConfirmDialog, ../components/DialogFooter, ../integrations/registry, @/components/ui (alert, badge, button, input, label, switch, dialog) +- Settings.tsx | A React settings page component for managing monitoring machines, SSH keys, and dangerous operations with tabbed navigation, forms, and confirmation dialogs. | exp: func:Settings(), call:useMonitoringSettings, call:useSSHKeys, call:useSaveMonitoringMachine, call:useDeleteMonitoringMachine, call:useTestMonitoringMachineSSH, call:useState, call:emptyMachine, call:useMemo, call:orderedMachines.find, call:setSSHValidationMessage, call:setSSHValidationError, call:setSSHValidationStatus, call:clearSSHValidation, call:setMachineDraft, call:setEditingMachine, call:setMachineDialogOpen, call:saveMachine.mutateAsync, call:testMachineSSH.mutateAsync, call:String, call:message.toLowerCase, call:lowered.includes, call:setTab, call:orderedMachines.map, call:setSelectedMachineId, call:cn, call:openEditMachine, call:setDeleteMachineId, call:closeMachineDialog, call:saveMachineDraft, call:machineDraft.host.trim, call:Boolean, call:deleteMachine.mutate | dep: react, ../types, ../hooks/useSettings, ../components/DialogFooter, ../components/HoverEditButton, ../components/SectionCard, ../components/SelectionRailCard, ../components/TabbedCard, ../components/ConfirmDialog, @/lib/utils, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/card, @/components/ui/checkbox, @/components/ui/dialog, @/components/ui/input, @/components/ui/label, @/components/ui/select, @/components/ui/switch, @/components/ui/tabs, @/components/ui/textarea +- Users.tsx | Re-exports the UsersPage component from its implementation file to provide a cleaner import interface. | dep: ./UsersPage.impl +- UsersPage.impl.tsx | A React page component for managing and messaging Jellyfin users with Jellyseerr enrichment, featuring a searchable directory table, user selection, email composition dialog, and queue status monitoring. | exp: func:UsersPage(), call:useUsers, call:useActivity, call:useUserMessageQueueStatus, call:useSendUserMessage, call:useIsMobile, call:useState, call:useSearchParams, call:useRef, call:useMemo, call:mergeUsersWithActivity, call:search.trim().toLowerCase, call:rows.filter, call:[ row.username, row.display_name, row.email, row.email_source, row.avatar_source, row.name_source, row.access_source, row.user_type_label, row.role, row.permissions_label, row.jellyseerr_username, row.activity_label, row.activity_summary, row.activity.primary_session?.title || "", String(row.jellyseerr_user_id ?? ""), ].some, call:String, call:value.toLowerCase().includes, call:queueStatus.active_request_id.slice, call:selectedIdSet.has, call:selectedRows.filter, call:filteredRows.filter, call:setSelectedUserIds, call:current.includes, call:current.filter, call:filteredRows.forEach, call:next.add, call:next.delete, call:Array.from, call:searchParams.get, call:resolveUserSelection, call:buildUserDrawerModel, call:sendUserMessage.reset, call:subject.trim, call:setSubject, call:htmlBody.trim, call:setHtmlBody, call:setComposeOpen, call:htmlBody.slice, call:requestAnimationFrame, call:textarea.focus, call:textarea.setSelectionRange, call:window.prompt, call:insertMarkup, call:setAttachments, call:formData.append, call:JSON.stringify, call:allSelectedRows.map, call:attachments.forEach, call:sendUserMessage.mutateAsync, call:setSearch, call:cn, call:toggleVisibleSelection, call:filteredRows.map, call:setSearchParams, call:event.stopPropagation, call:toggleUserSelected, call:userLabel(row).charAt(0).toUpperCase, call:activityBadgeVariant, call:Boolean, call:drawerModel.title.charAt(0).toUpperCase, call:drawerModel.identity.map, call:drawerModel.contactActions.map, call:drawerModel.contactActions .map((action) => action.hint) .join, call:drawerModel.permissions.map, call:closeCompose, call:sendUserMessage.data.request_id.slice, call:selectedDeliverableRows.map, call:attachments.map, call:removeAttachment | dep: react, react-router-dom, lucide-react, @/components/ui/dialog, @/components/ui/input, @/components/ui/textarea, @/components/ui/separator, @/components/ui/label, @/components/ui/avatar, @/components/ui/badge, @/components/ui/button, @/components/ui/checkbox, @/components/ui/alert, @/components/ui/progress, @/components/ui/tooltip, @/components/ui/sheet, @/components/ui/table, @/lib/utils, ../components/MetricCard, ../components/SessionActivityPanel, ../hooks/useUsers, ../hooks/useDashboard, ../hooks/useSendUserMessage, ../hooks/useUserMessageQueueStatus, ../types, ../users, ../userState, @/components/ui (dialog, input, textarea, separator, label, avatar, badge, button, checkbox, alert, progress, tooltip, sheet, table) +## arch +React functional component pages organized by feature domain, employing implementation/re-export file pairs for complex views, with each page composing CRUD operations, tabbed navigation, dynamic forms, and data tables against backend service APIs. +## tags +call:use, components, ui, call:set, state, service, react, machine +## symbols +- Actions +- Applications +- Dashboard +- FileBrowser +- Media +- ServicePage +- ServicesPage +- Settings +## workflows +- change pages behavior + read: Actions.tsx, Applications.tsx, Dashboard.tsx +- explore pages subdirectories + index: frontend/src/pages/__tests__/.pi-map.index.md +## dirty +- diff --git a/frontend/src/pages/__tests__/.pi-map.index.md b/frontend/src/pages/__tests__/.pi-map.index.md new file mode 100644 index 0000000..a5c9f2a --- /dev/null +++ b/frontend/src/pages/__tests__/.pi-map.index.md @@ -0,0 +1,26 @@ +# frontend/src/pages/__tests__ (index) +dir: frontend/src/pages/__tests__ + +## role +Test suite providing unit test coverage for top-level page components in the frontend application. +## parent +index: frontend/src/pages/.pi-map.index.md +map: frontend/src/pages/.pi-map.md +## children +- +## files +- Actions.test.tsx +- Applications.test.tsx +- Dashboard.test.tsx +- FileBrowser.test.tsx +- Media.test.tsx +- Settings.test.tsx +- UsersPage.test.tsx +## links +index: frontend/src/pages/__tests__/.pi-map.index.md +map: frontend/src/pages/__tests__/.pi-map.md +## workflows +- update __tests__ tests + read: Actions.test.tsx, Applications.test.tsx, Dashboard.test.tsx +## dirty +- diff --git a/frontend/src/pages/__tests__/.pi-map.md b/frontend/src/pages/__tests__/.pi-map.md new file mode 100644 index 0000000..21f5cad --- /dev/null +++ b/frontend/src/pages/__tests__/.pi-map.md @@ -0,0 +1,26 @@ +# frontend/src/pages/__tests__ +dir: frontend/src/pages/__tests__ + +index: frontend/src/pages/__tests__/.pi-map.index.md + +## role +Test suite providing unit test coverage for top-level page components in the frontend application. +## files +- Actions.test.tsx | Unit tests for the Actions component covering task creation, run button state, and task execution via SSH service selection. | dep: vitest, @testing-library/react, @testing-library/user-event, ../Actions, ../../types, ../../hooks/useSettings, ../../hooks/useServices +- Applications.test.tsx | Tests the Applications component renders library statistics, tabs, and a mocked Media child while isolating slice-4 concerns from still-MUI dependencies. | dep: vitest, @testing-library/react, ../Applications, react-router-dom, ../Media, ../../hooks/useSettings, ../../hooks/useServices, ../../hooks/useDashboard +- Dashboard.test.tsx | Unit tests for the Dashboard component focusing on shortcut CRUD operations (empty state, delete with confirmation, and create) while mocking child widgets and data hooks. | dep: vitest, @testing-library/react, @testing-library/user-event, ../Dashboard, ../../types, react-router-dom, ../../components/WidgetInstance, ../../components/WidgetConfigDialog, ../../hooks/useSettings, ../../hooks/useWidgets, ../../hooks/useDashboard +- FileBrowser.test.tsx | Unit tests for a FileBrowser component verifying column rendering, file selection with ffprobe preview, and directory navigation behavior. | dep: vitest, @testing-library/react, @testing-library/user-event, ../FileBrowser.impl, ../../types, react-router-dom, ../../hooks/useFiles, ../../hooks/useSettings +- Media.test.tsx | Tests the Media component's data table rendering, column toggling, row navigation, selection behavior, server-driven pagination, and build index controls. | dep: vitest, @testing-library/react, @testing-library/user-event, ../Media, ../../types, react-router-dom, custom hooks (useMedia, useSettings, useServices, useDashboard) +- Settings.test.tsx | Tests the Settings component's rendering, machine editing, and machine deletion functionality using mocked hooks | dep: vitest, @testing-library/react, @testing-library/user-event, ../Settings, ../../types, ../../hooks/useSettings +- UsersPage.test.tsx | Unit tests for the UsersPage component covering user directory rendering, row selection, drawer navigation, activity status badges, and compose dialog formatting actions. | dep: vitest, @testing-library/react, @testing-library/user-event, ../UsersPage.impl, ../../components/ui/tooltip, ../../types, react-router-dom, TooltipProvider, useUsers, useDashboard, useUserMessageQueueStatus, useSendUserMessage, SessionActivityPanel +## arch +React Testing Library with Jest, using component mocking, hook stubs, and isolated render testing per page module. +## tags +library, @testing, hooks, react, tests, component, vitest, user +## symbols +- +## workflows +- update __tests__ tests + read: Actions.test.tsx, Applications.test.tsx, Dashboard.test.tsx +## dirty +- diff --git a/frontend/src/test/.pi-map.index.md b/frontend/src/test/.pi-map.index.md new file mode 100644 index 0000000..fe3ea0f --- /dev/null +++ b/frontend/src/test/.pi-map.index.md @@ -0,0 +1,20 @@ +# frontend/src/test (index) +dir: frontend/src/test + +## role +Provides test infrastructure configuration for the frontend's Vitest testing environment. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- setup.ts +## links +index: frontend/src/test/.pi-map.index.md +map: frontend/src/test/.pi-map.md +## workflows +- change test behavior + read: setup.ts +## dirty +- diff --git a/frontend/src/test/.pi-map.md b/frontend/src/test/.pi-map.md new file mode 100644 index 0000000..62b47c4 --- /dev/null +++ b/frontend/src/test/.pi-map.md @@ -0,0 +1,23 @@ +# frontend/src/test +dir: frontend/src/test + +index: frontend/src/test/.pi-map.index.md + +## role +Provides test infrastructure configuration for the frontend's Vitest testing environment. +## files +- setup.ts | Configures Vitest global test environment by registering jest-dom matchers and polyfilling missing jsdom APIs for Radix/shadcn UI component testing. | exp: class:ResizeObserverStub, method:observe(), method:unobserve(), method:disconnect() | dep: @testing-library/jest-dom/vitest, vitest, jsdom +## arch +Setup module pattern that extends Vitest's global matchers with jest-dom and polyfills jsdom gaps for UI component compatibility. +## tags +vitest, jest, dom, jsdom, resize, observer, stub, observe +## symbols +- ResizeObserverStub +- observe +- unobserve +- disconnect +## workflows +- change test behavior + read: setup.ts +## dirty +- diff --git a/frontend/src/types/.pi-map.index.md b/frontend/src/types/.pi-map.index.md new file mode 100644 index 0000000..1647e9b --- /dev/null +++ b/frontend/src/types/.pi-map.index.md @@ -0,0 +1,21 @@ +# frontend/src/types (index) +dir: frontend/src/types + +## role +Central TypeScript type definitions providing shared data contracts and API response interfaces for the frontend application. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- backups.ts +- index.ts +## links +index: frontend/src/types/.pi-map.index.md +map: frontend/src/types/.pi-map.md +## workflows +- change types behavior + read: backups.ts, index.ts +## dirty +- diff --git a/frontend/src/types/.pi-map.md b/frontend/src/types/.pi-map.md new file mode 100644 index 0000000..ccf41e2 --- /dev/null +++ b/frontend/src/types/.pi-map.md @@ -0,0 +1,28 @@ +# frontend/src/types +dir: frontend/src/types + +index: frontend/src/types/.pi-map.index.md + +## role +Central TypeScript type definitions providing shared data contracts and API response interfaces for the frontend application. +## files +- backups.ts | Defines TypeScript interfaces for a backup system including jobs, runs, alerts, and dashboard summaries. | exp: BackupJob, BackupRun, BackupAlert, BackupDashboardSummary +- index.ts | Defines shared TypeScript interfaces for API response types used across a media server management dashboard application. | exp: MediaCounts, LibraryCount, UserDirectoryItem, UserDirectoryResponse, UserMessageResponse, UserMessageQueueStatus, NowPlayingSession, SSHKey, SSHKeyInput, SSHKeyGenerated, SavedTask, SavedTaskInput, SavedTaskRun, MonitoringMachine, MonitoringMachineInput, ResetLocalDatabaseInput, ResetLocalDatabaseResponse, SSHValidationResult, AppVersionInfo, MediaIndexStatus, MediaIndexActionResponse, MediaItem, MediaQueryResponse, FileEntry, DirectoryListing, JobTemplate, JobResult, ResolvedPath, DashboardShortcut, DashboardShortcutInput, AlertmanagerAlert, AlertmanagerAlertSummary, AlertmanagerStatus, GrafanaStatus, PrometheusStatus, PrometheusTarget, WidgetInstance, WidgetInstanceInput, WidgetDataResponse, SecretFieldInfo, ServiceWidgetKindInfo, ServiceTypeInfo, ServiceInstance, ServiceInstanceInput, BuiltinWidgetKindInfo +## arch +Flat module pattern of plain TypeScript interfaces/types with a barrel index.ts export, serving as the single source of truth for data shapes across the UI. +## tags +input, response, media, status, info, widget, backup, dashboard +## symbols +- BackupJob +- BackupRun +- BackupAlert +- BackupDashboardSummary +- MediaCounts +- LibraryCount +- UserDirectoryItem +- UserDirectoryResponse +## workflows +- change types behavior + read: backups.ts, index.ts +## dirty +- diff --git a/frontend/src/widgets/.pi-map.index.md b/frontend/src/widgets/.pi-map.index.md new file mode 100644 index 0000000..0f0cb57 --- /dev/null +++ b/frontend/src/widgets/.pi-map.index.md @@ -0,0 +1,27 @@ +# frontend/src/widgets (index) +dir: frontend/src/widgets + +## role +Provides a collection of self-contained, UI-driven dashboard widgets that fetch and render data from various external services and integrations. +## parent +index: frontend/src/.pi-map.index.md +map: frontend/src/.pi-map.md +## children +- +## files +- AlertmanagerAlertsWidget.tsx +- BackupsWidget.tsx +- GrafanaLinkWidget.tsx +- JellyfinWidget.tsx +- PrometheusMetricWidget.tsx +- SshTaskWidget.tsx +- StaticWidget.tsx +- index.ts +## links +index: frontend/src/widgets/.pi-map.index.md +map: frontend/src/widgets/.pi-map.md +## workflows +- change widgets behavior + read: AlertmanagerAlertsWidget.tsx, BackupsWidget.tsx, GrafanaLinkWidget.tsx +## dirty +- diff --git a/frontend/src/widgets/.pi-map.md b/frontend/src/widgets/.pi-map.md new file mode 100644 index 0000000..fa83df4 --- /dev/null +++ b/frontend/src/widgets/.pi-map.md @@ -0,0 +1,34 @@ +# frontend/src/widgets +dir: frontend/src/widgets + +index: frontend/src/widgets/.pi-map.index.md + +## role +Provides a collection of self-contained, UI-driven dashboard widgets that fetch and render data from various external services and integrations. +## files +- AlertmanagerAlertsWidget.tsx | Renders an Alertmanager alerts dashboard widget displaying alert summaries, severity badges, and individual alert details. | exp: func:AlertmanagerAlertsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Object.entries(summary.by_severity).map, call:severityVariant, call:alerts.slice(0, 5).map | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types +- BackupsWidget.tsx | Displays a dashboard widget showing backup job metrics including total jobs, 24-hour success rate, active alerts, and last failure timestamp. | exp: func:BackupsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:new Date(summary.last_failed_at * 1000).toLocaleString | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types/backups, ../types +- GrafanaLinkWidget.tsx | Renders a widget that displays a link to Grafana with loading, error, and empty states. | exp: func:GrafanaLinkWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/button, @/components/ui/skeleton, lucide-react, ../components/SectionCard, ../hooks/useWidgets, ../types +- JellyfinWidget.tsx | Displays Jellyfin media server activity sessions in a widget with loading, error, and empty states. | exp: func:JellyfinWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Array.isArray | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SessionActivityPanel, ../components/SectionCard, ../hooks/useWidgets, ../types +- PrometheusMetricWidget.tsx | A React component that fetches and displays Prometheus metric data from a widget, handling loading, error, and data states with formatted output for scalar and vector result types. | exp: func:PrometheusMetricWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:formatPrometheusValue | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types +- SshTaskWidget.tsx | Displays SSH task execution results with exit status, stdout, and stderr in a polling widget card | exp: func:SshTaskWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types +- StaticWidget.tsx | Renders a static text widget that displays fetched text content or a fallback message within a section card. | exp: func:StaticWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData, WidgetInstance +- index.ts | Barrel file that re-exports various dashboard widget components from their individual modules. | dep: AlertmanagerAlertsWidget, BackupsWidget, GrafanaLinkWidget, JellyfinWidget, PrometheusMetricWidget, SshTaskWidget, StaticWidget +## arch +Component-based React architecture with each widget encapsulating its own data-fetching logic and standardized loading, error, and empty states, unified through a barrel-export pattern. +## tags +widget, components, data, ui, call:use, types, alert, sectioncard +## symbols +- AlertmanagerAlertsWidget +- BackupsWidget +- GrafanaLinkWidget +- JellyfinWidget +- PrometheusMetricWidget +- SshTaskWidget +- StaticWidget +- call:useWidgetData +## workflows +- change widgets behavior + read: AlertmanagerAlertsWidget.tsx, BackupsWidget.tsx, GrafanaLinkWidget.tsx +## dirty +- diff --git a/frontend/tests/.pi-map.index.md b/frontend/tests/.pi-map.index.md new file mode 100644 index 0000000..0b8d04e --- /dev/null +++ b/frontend/tests/.pi-map.index.md @@ -0,0 +1,20 @@ +# frontend/tests (index) +dir: frontend/tests + +## role +Test suite validating frontend utility functions for user state management and UI model transformation. +## parent +index: frontend/.pi-map.index.md +map: frontend/.pi-map.md +## children +- +## files +- userState.test.mjs +- users.test.mjs +## links +index: frontend/tests/.pi-map.index.md +map: frontend/tests/.pi-map.md +## workflows +- +## dirty +- diff --git a/frontend/tests/.pi-map.md b/frontend/tests/.pi-map.md new file mode 100644 index 0000000..cf32b80 --- /dev/null +++ b/frontend/tests/.pi-map.md @@ -0,0 +1,20 @@ +# frontend/tests +dir: frontend/tests + +index: frontend/tests/.pi-map.index.md + +## role +Test suite validating frontend utility functions for user state management and UI model transformation. +## files +- userState.test.mjs | Tests user state utility functions for merging session activity with user data and resolving user selection by various identifiers | dep: node:assert/strict, node:test, ../src/userState.js +- users.test.mjs | Tests the buildUserDrawerModel function that transforms user data into a UI drawer model with contact and permission details | dep: node:assert/strict, node:test, ../src/users.js +## arch +Node.js Mocha/ES module test pattern testing pure data transformation functions in isolation. +## tags +user, tests, data, node:assert, strict, node:test, src, js +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/.pi-map.index.md b/monitoring/.pi-map.index.md new file mode 100644 index 0000000..fc55320 --- /dev/null +++ b/monitoring/.pi-map.index.md @@ -0,0 +1,32 @@ +# monitoring (index) +dir: monitoring + +## role +Provides observability, metrics collection, and health-checking capabilities for the application. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- monitoring/alertmanager + index: monitoring/alertmanager/.pi-map.index.md + map: monitoring/alertmanager/.pi-map.md +- monitoring/alloy + index: monitoring/alloy/.pi-map.index.md + map: monitoring/alloy/.pi-map.md +- monitoring/grafana + index: monitoring/grafana/.pi-map.index.md + map: monitoring/grafana/.pi-map.md +- monitoring/loki + index: monitoring/loki/.pi-map.index.md + map: monitoring/loki/.pi-map.md +- monitoring/prometheus + index: monitoring/prometheus/.pi-map.index.md + map: monitoring/prometheus/.pi-map.md +## files +## links +index: monitoring/.pi-map.index.md +map: monitoring/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/.pi-map.md b/monitoring/.pi-map.md new file mode 100644 index 0000000..f6f9e55 --- /dev/null +++ b/monitoring/.pi-map.md @@ -0,0 +1,18 @@ +# monitoring +dir: monitoring + +index: monitoring/.pi-map.index.md + +## role +Provides observability, metrics collection, and health-checking capabilities for the application. +## files +## arch +Directory is currently empty with no files to analyze for architectural patterns. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/alertmanager/.pi-map.index.md b/monitoring/alertmanager/.pi-map.index.md new file mode 100644 index 0000000..876bc0b --- /dev/null +++ b/monitoring/alertmanager/.pi-map.index.md @@ -0,0 +1,19 @@ +# monitoring/alertmanager (index) +dir: monitoring/alertmanager + +## role +Central configuration package for Prometheus alert routing, grouping, and notification delivery within the monitoring stack. +## parent +index: monitoring/.pi-map.index.md +map: monitoring/.pi-map.md +## children +- +## files +- alertmanager.yml +## links +index: monitoring/alertmanager/.pi-map.index.md +map: monitoring/alertmanager/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/alertmanager/.pi-map.md b/monitoring/alertmanager/.pi-map.md new file mode 100644 index 0000000..ba5a17b --- /dev/null +++ b/monitoring/alertmanager/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/alertmanager +dir: monitoring/alertmanager + +index: monitoring/alertmanager/.pi-map.index.md + +## role +Central configuration package for Prometheus alert routing, grouping, and notification delivery within the monitoring stack. +## files +- alertmanager.yml | Configures Alertmanager for routing, grouping, and delivering Prometheus alerts via email and webhook with support for critical alert escalation and inhibition rules. | dep: Alertmanager, SMTP server, webhook endpoint, environment variables (SMTP_HOST, SMTP_PORT, SMTP_FROM_ADDRESS, SMTP_USERNAME, SMTP_PASSWORD, ALERT_EMAIL_TO, ALERTMANAGER_WEBHOOK_URL) +## arch +Declarative YAML configuration utilizing a hierarchical routing tree with receiver-based notification patterns (email, webhook) and alert inhibition rules to prevent notification storms. +## tags +alertmanager, configures, routing, grouping, delivering, prometheus, alerts, email +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/alloy/.pi-map.index.md b/monitoring/alloy/.pi-map.index.md new file mode 100644 index 0000000..0419e96 --- /dev/null +++ b/monitoring/alloy/.pi-map.index.md @@ -0,0 +1,19 @@ +# monitoring/alloy (index) +dir: monitoring/alloy + +## role +Centralized log collection configuration that scrapes Docker container logs and forwards them to Loki for storage and querying. +## parent +index: monitoring/.pi-map.index.md +map: monitoring/.pi-map.md +## children +- +## files +- config.alloy +## links +index: monitoring/alloy/.pi-map.index.md +map: monitoring/alloy/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/alloy/.pi-map.md b/monitoring/alloy/.pi-map.md new file mode 100644 index 0000000..d44e8e4 --- /dev/null +++ b/monitoring/alloy/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/alloy +dir: monitoring/alloy + +index: monitoring/alloy/.pi-map.index.md + +## role +Centralized log collection configuration that scrapes Docker container logs and forwards them to Loki for storage and querying. +## files +- config.alloy | Configures Alloy to scrape Docker container logs and forward them to a local Loki instance | dep: docker, loki, discovery.docker, discovery.relabel, loki.source.docker, loki.write +## arch +Declarative agent-based collection pattern using Grafana Alloy's discovery and pipeline stages to discover containers, tail their logs, and stream them to a local Loki destination. +## tags +docker, loki, discovery, config, configures, alloy, scrape, container +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/grafana/.pi-map.index.md b/monitoring/grafana/.pi-map.index.md new file mode 100644 index 0000000..35ba04f --- /dev/null +++ b/monitoring/grafana/.pi-map.index.md @@ -0,0 +1,21 @@ +# monitoring/grafana (index) +dir: monitoring/grafana + +## role +Provides centralized configuration for deploying and securing a Grafana monitoring dashboard instance. +## parent +index: monitoring/.pi-map.index.md +map: monitoring/.pi-map.md +## children +- monitoring/grafana/provisioning + index: monitoring/grafana/provisioning/.pi-map.index.md + map: monitoring/grafana/provisioning/.pi-map.md +## files +- grafana.ini +## links +index: monitoring/grafana/.pi-map.index.md +map: monitoring/grafana/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/grafana/.pi-map.md b/monitoring/grafana/.pi-map.md new file mode 100644 index 0000000..65ac6f2 --- /dev/null +++ b/monitoring/grafana/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/grafana +dir: monitoring/grafana + +index: monitoring/grafana/.pi-map.index.md + +## role +Provides centralized configuration for deploying and securing a Grafana monitoring dashboard instance. +## files +- grafana.ini | Grafana configuration file enabling OAuth authentication via Authentik, iframe embedding support, and role-based access control via group membership | dep: Authentik, Grafana, environment variables (GF_AUTH_GENERIC_OAUTH_*) +## arch +Declarative infrastructure configuration utilizing file-based static settings for OAuth2 (Authentik), role-based access control (RBAC), and UI embedding parameters. +## tags +grafana, authentik, configuration, enabling, oauth, authentication, iframe, embedding +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/.pi-map.index.md b/monitoring/grafana/provisioning/.pi-map.index.md new file mode 100644 index 0000000..a71c2f3 --- /dev/null +++ b/monitoring/grafana/provisioning/.pi-map.index.md @@ -0,0 +1,23 @@ +# monitoring/grafana/provisioning (index) +dir: monitoring/grafana/provisioning + +## role +Provides declarative configuration for automatic Grafana dashboard and datasource provisioning without manual UI intervention. +## parent +index: monitoring/grafana/.pi-map.index.md +map: monitoring/grafana/.pi-map.md +## children +- monitoring/grafana/provisioning/dashboards-json + index: monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md + map: monitoring/grafana/provisioning/dashboards-json/.pi-map.md +- monitoring/grafana/provisioning/datasources + index: monitoring/grafana/provisioning/datasources/.pi-map.index.md + map: monitoring/grafana/provisioning/datasources/.pi-map.md +## files +## links +index: monitoring/grafana/provisioning/.pi-map.index.md +map: monitoring/grafana/provisioning/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/.pi-map.md b/monitoring/grafana/provisioning/.pi-map.md new file mode 100644 index 0000000..03ab1f6 --- /dev/null +++ b/monitoring/grafana/provisioning/.pi-map.md @@ -0,0 +1,18 @@ +# monitoring/grafana/provisioning +dir: monitoring/grafana/provisioning + +index: monitoring/grafana/provisioning/.pi-map.index.md + +## role +Provides declarative configuration for automatic Grafana dashboard and datasource provisioning without manual UI intervention. +## files +## arch +Infrastructure-as-code pattern using Grafana's native provisioning system with YAML configuration files organized into subdirectories for datasources and dashboards. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md b/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md new file mode 100644 index 0000000..7357f44 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md @@ -0,0 +1,21 @@ +# monitoring/grafana/provisioning/dashboards-json (index) +dir: monitoring/grafana/provisioning/dashboards-json + +## role +Provides declarative configuration for automatically provisioning Grafana dashboards from local JSON files. +## parent +index: monitoring/grafana/provisioning/.pi-map.index.md +map: monitoring/grafana/provisioning/.pi-map.md +## children +- monitoring/grafana/provisioning/dashboards-json/dashboards + index: monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md + map: monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md +## files +- dashboards.yml +## links +index: monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md +map: monitoring/grafana/provisioning/dashboards-json/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/dashboards-json/.pi-map.md b/monitoring/grafana/provisioning/dashboards-json/.pi-map.md new file mode 100644 index 0000000..b3cd3c9 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards-json/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/grafana/provisioning/dashboards-json +dir: monitoring/grafana/provisioning/dashboards-json + +index: monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md + +## role +Provides declarative configuration for automatically provisioning Grafana dashboards from local JSON files. +## files +- dashboards.yml | Configures Grafana dashboard provisioning to automatically load and update dashboards from a JSON file directory | dep: Grafana, provisioning system, file system +## arch +Uses Grafana's file-based provisioning system to load dashboard definitions from a configured directory path. +## tags +dashboards, grafana, configures, dashboard, provisioning, automatically, load, update +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md new file mode 100644 index 0000000..f331c35 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md @@ -0,0 +1,20 @@ +# monitoring/grafana/provisioning/dashboards-json/dashboards (index) +dir: monitoring/grafana/provisioning/dashboards-json/dashboards + +## role +This directory stores pre-built Grafana dashboard JSON definitions for monitoring the "manage" service and host-level system metrics via node-exporter. +## parent +index: monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md +map: monitoring/grafana/provisioning/dashboards-json/.pi-map.md +## children +- +## files +- manage-overview.json +- node-exporter-overview.json +## links +index: monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md +map: monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md new file mode 100644 index 0000000..bc20564 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md @@ -0,0 +1,20 @@ +# monitoring/grafana/provisioning/dashboards-json/dashboards +dir: monitoring/grafana/provisioning/dashboards-json/dashboards + +index: monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md + +## role +This directory stores pre-built Grafana dashboard JSON definitions for monitoring the "manage" service and host-level system metrics via node-exporter. +## files +- manage-overview.json | Grafana dashboard configuration for monitoring a "manage" service with metrics and logs visualization | dep: prometheus, loki, grafana +- node-exporter-overview.json | Grafana dashboard JSON for visualizing node-exporter system metrics (CPU, memory, disk, network) | dep: Grafana, Prometheus, node-exporter +## arch +It follows Grafana's infrastructure-as-code provisioning pattern, using static JSON files to declaratively define dashboard layouts, queries, and visualizations that are automatically loaded by Grafana at startup. +## tags +grafana, overview, node, exporter, manage, dashboard, metrics, prometheus +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/datasources/.pi-map.index.md b/monitoring/grafana/provisioning/datasources/.pi-map.index.md new file mode 100644 index 0000000..fe2389b --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/.pi-map.index.md @@ -0,0 +1,19 @@ +# monitoring/grafana/provisioning/datasources (index) +dir: monitoring/grafana/provisioning/datasources + +## role +Provisions and configures Grafana data source connections to Prometheus and Loki monitoring backends. +## parent +index: monitoring/grafana/provisioning/.pi-map.index.md +map: monitoring/grafana/provisioning/.pi-map.md +## children +- +## files +- datasources.yml +## links +index: monitoring/grafana/provisioning/datasources/.pi-map.index.md +map: monitoring/grafana/provisioning/datasources/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/grafana/provisioning/datasources/.pi-map.md b/monitoring/grafana/provisioning/datasources/.pi-map.md new file mode 100644 index 0000000..63565bf --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/grafana/provisioning/datasources +dir: monitoring/grafana/provisioning/datasources + +index: monitoring/grafana/provisioning/datasources/.pi-map.index.md + +## role +Provisions and configures Grafana data source connections to Prometheus and Loki monitoring backends. +## files +- datasources.yml | Configures Grafana data sources for Prometheus and Loki monitoring backends | dep: Grafana, Prometheus, Loki +## arch +Declarative Infrastructure-as-Code (IaC) using Grafana's native YAML provisioning system for automated data source setup. +## tags +grafana, prometheus, loki, datasources, configures, data, sources, monitoring +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/loki/.pi-map.index.md b/monitoring/loki/.pi-map.index.md new file mode 100644 index 0000000..518bda0 --- /dev/null +++ b/monitoring/loki/.pi-map.index.md @@ -0,0 +1,19 @@ +# monitoring/loki (index) +dir: monitoring/loki + +## role +Provides configuration for deploying and managing the Grafana Loki log aggregation server. +## parent +index: monitoring/.pi-map.index.md +map: monitoring/.pi-map.md +## children +- +## files +- loki.yml +## links +index: monitoring/loki/.pi-map.index.md +map: monitoring/loki/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/loki/.pi-map.md b/monitoring/loki/.pi-map.md new file mode 100644 index 0000000..f66d3b4 --- /dev/null +++ b/monitoring/loki/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/loki +dir: monitoring/loki + +index: monitoring/loki/.pi-map.index.md + +## role +Provides configuration for deploying and managing the Grafana Loki log aggregation server. +## files +- loki.yml | Configures Grafana Loki log aggregation server with local filesystem storage and 30-day retention | dep: loki, tsdb, filesystem storage, inmemory ring kvstore +## arch +Single-file declarative configuration defining Loki's storage backend (local filesystem) and data retention policy (30-day table). +## tags +loki, configures, grafana, log, aggregation, server, local, filesystem +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/prometheus/.pi-map.index.md b/monitoring/prometheus/.pi-map.index.md new file mode 100644 index 0000000..41825ea --- /dev/null +++ b/monitoring/prometheus/.pi-map.index.md @@ -0,0 +1,24 @@ +# monitoring/prometheus (index) +dir: monitoring/prometheus + +## role +Provides configuration for standalone Prometheus deployments to scrape and collect metrics from local infrastructure services and remote nodes. +## parent +index: monitoring/.pi-map.index.md +map: monitoring/.pi-map.md +## children +- monitoring/prometheus/file-sd + index: monitoring/prometheus/file-sd/.pi-map.index.md + map: monitoring/prometheus/file-sd/.pi-map.md +- monitoring/prometheus/rules + index: monitoring/prometheus/rules/.pi-map.index.md + map: monitoring/prometheus/rules/.pi-map.md +## files +- prometheus.standalone.yml +## links +index: monitoring/prometheus/.pi-map.index.md +map: monitoring/prometheus/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/prometheus/.pi-map.md b/monitoring/prometheus/.pi-map.md new file mode 100644 index 0000000..07f19b0 --- /dev/null +++ b/monitoring/prometheus/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/prometheus +dir: monitoring/prometheus + +index: monitoring/prometheus/.pi-map.index.md + +## role +Provides configuration for standalone Prometheus deployments to scrape and collect metrics from local infrastructure services and remote nodes. +## files +- prometheus.standalone.yml | Configures Prometheus monitoring server to scrape metrics from itself, node exporters, Loki, Alertmanager, Grafana, and remote nodes via file-based service discovery | dep: alertmanager, node-exporter, loki, grafana, file-sd +## arch +Uses a declarative static YAML configuration pattern leveraging file-based service discovery for dynamic remote target management. +## tags +node, loki, alertmanager, grafana, prometheus.standalone, configures, prometheus, monitoring +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/prometheus/file-sd/.pi-map.index.md b/monitoring/prometheus/file-sd/.pi-map.index.md new file mode 100644 index 0000000..f30808c --- /dev/null +++ b/monitoring/prometheus/file-sd/.pi-map.index.md @@ -0,0 +1,19 @@ +# monitoring/prometheus/file-sd (index) +dir: monitoring/prometheus/file-sd + +## role +Provides static target configuration for Prometheus to discover and scrape node_exporter metrics endpoints. +## parent +index: monitoring/prometheus/.pi-map.index.md +map: monitoring/prometheus/.pi-map.md +## children +- +## files +- node_exporter_targets.json +## links +index: monitoring/prometheus/file-sd/.pi-map.index.md +map: monitoring/prometheus/file-sd/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/prometheus/file-sd/.pi-map.md b/monitoring/prometheus/file-sd/.pi-map.md new file mode 100644 index 0000000..cf2e153 --- /dev/null +++ b/monitoring/prometheus/file-sd/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/prometheus/file-sd +dir: monitoring/prometheus/file-sd + +index: monitoring/prometheus/file-sd/.pi-map.index.md + +## role +Provides static target configuration for Prometheus to discover and scrape node_exporter metrics endpoints. +## files +- node_exporter_targets.json | Empty JSON file intended to define scrape targets for Prometheus node_exporter | dep: Prometheus, node_exporter +## arch +Uses the Prometheus file-based service discovery (file_sd) pattern with JSON-formatted target definitions. +## tags +targets, node, exporter, prometheus, empty, json, intended, define +## symbols +- +## workflows +- +## dirty +- diff --git a/monitoring/prometheus/rules/.pi-map.index.md b/monitoring/prometheus/rules/.pi-map.index.md new file mode 100644 index 0000000..46b5088 --- /dev/null +++ b/monitoring/prometheus/rules/.pi-map.index.md @@ -0,0 +1,19 @@ +# monitoring/prometheus/rules (index) +dir: monitoring/prometheus/rules + +## role +Defines Prometheus alerting rules for monitoring backup job failures and observability stack health. +## parent +index: monitoring/prometheus/.pi-map.index.md +map: monitoring/prometheus/.pi-map.md +## children +- +## files +- backup_alerts.yml +## links +index: monitoring/prometheus/rules/.pi-map.index.md +map: monitoring/prometheus/rules/.pi-map.md +## workflows +- +## dirty +- diff --git a/monitoring/prometheus/rules/.pi-map.md b/monitoring/prometheus/rules/.pi-map.md new file mode 100644 index 0000000..bd459a6 --- /dev/null +++ b/monitoring/prometheus/rules/.pi-map.md @@ -0,0 +1,19 @@ +# monitoring/prometheus/rules +dir: monitoring/prometheus/rules + +index: monitoring/prometheus/rules/.pi-map.index.md + +## role +Defines Prometheus alerting rules for monitoring backup job failures and observability stack health. +## files +- backup_alerts.yml | Defines Prometheus alerting rules for backup job failures and observability stack health monitoring. | dep: Prometheus, Alertmanager, Grafana, manage_backup_runs_total metric, manage_backup_runs_last_success_timestamp metric, up metric +## arch +Configuration-as-code pattern using declarative YAML rule files consumed directly by the Prometheus alerting engine. +## tags +backup, prometheus, alerts, defines, alerting, rules, job, failures +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/.pi-map.index.md b/openspec/.pi-map.index.md new file mode 100644 index 0000000..23651a4 --- /dev/null +++ b/openspec/.pi-map.index.md @@ -0,0 +1,24 @@ +# openspec (index) +dir: openspec + +## role +Defines project-wide coding standards, architecture constraints, and development guidelines for a homelab media dashboard UI migration. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- openspec/changes + index: openspec/changes/.pi-map.index.md + map: openspec/changes/.pi-map.md +- openspec/specs + index: openspec/specs/.pi-map.index.md + map: openspec/specs/.pi-map.md +## files +- config.yaml +## links +index: openspec/.pi-map.index.md +map: openspec/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/.pi-map.md b/openspec/.pi-map.md new file mode 100644 index 0000000..b902d76 --- /dev/null +++ b/openspec/.pi-map.md @@ -0,0 +1,19 @@ +# openspec +dir: openspec + +index: openspec/.pi-map.index.md + +## role +Defines project-wide coding standards, architecture constraints, and development guidelines for a homelab media dashboard UI migration. +## files +- config.yaml | Configuration file specifying coding standards, architecture context, and development rules for a homelab media dashboard migration from MUI to shadcn/ui + Tailwind CSS | dep: FastAPI, React 18, TypeScript, Vite, TanStack Query, Tailwind CSS v4, shadcn/ui, Radix primitives, lucide-react, react-router-dom, react-oidc-context, TanStack Table, @mui/material, @mui/icons-material, @mui/x-data-grid, @emotion/react, @emotion/styled +## arch +Single-source-of-truth declarative configuration using YAML to enforce rules and conventions for an MUI to shadcn/ui + Tailwind CSS migration. +## tags +react, @mui, context, shadcn, material, @emotion, config, configuration +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/.pi-map.index.md b/openspec/changes/.pi-map.index.md new file mode 100644 index 0000000..ab41dd3 --- /dev/null +++ b/openspec/changes/.pi-map.index.md @@ -0,0 +1,23 @@ +# openspec/changes (index) +dir: openspec/changes + +## role +Contains OpenSpec change records documenting proposed, accepted, and rejected modifications to the project's API specification or design. +## parent +index: openspec/.pi-map.index.md +map: openspec/.pi-map.md +## children +- openspec/changes/archive + index: openspec/changes/archive/.pi-map.index.md + map: openspec/changes/archive/.pi-map.md +- openspec/changes/jellyfin-service-registry + index: openspec/changes/jellyfin-service-registry/.pi-map.index.md + map: openspec/changes/jellyfin-service-registry/.pi-map.md +## files +## links +index: openspec/changes/.pi-map.index.md +map: openspec/changes/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/.pi-map.md b/openspec/changes/.pi-map.md new file mode 100644 index 0000000..7c7b11c --- /dev/null +++ b/openspec/changes/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/changes +dir: openspec/changes + +index: openspec/changes/.pi-map.index.md + +## role +Contains OpenSpec change records documenting proposed, accepted, and rejected modifications to the project's API specification or design. +## files +## arch +Artifact-based directory pattern storing individual change proposals as structured files (likely YAML or Markdown) for specification governance and audit trails. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/.pi-map.index.md b/openspec/changes/archive/.pi-map.index.md new file mode 100644 index 0000000..2748e2b --- /dev/null +++ b/openspec/changes/archive/.pi-map.index.md @@ -0,0 +1,35 @@ +# openspec/changes/archive (index) +dir: openspec/changes/archive + +## role +Archive directory for superseded or completed OpenSpec change proposals. +## parent +index: openspec/changes/.pi-map.index.md +map: openspec/changes/.pi-map.md +## children +- openspec/changes/archive/2026-06-17-web-ui-rework + index: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md + map: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md +- openspec/changes/archive/configurable-dashboard-widgets + index: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md + map: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md +- openspec/changes/archive/decommission-monitoring-poller + index: openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md + map: openspec/changes/archive/decommission-monitoring-poller/.pi-map.md +- openspec/changes/archive/observability-service-registry + index: openspec/changes/archive/observability-service-registry/.pi-map.index.md + map: openspec/changes/archive/observability-service-registry/.pi-map.md +- openspec/changes/archive/service-registry + index: openspec/changes/archive/service-registry/.pi-map.index.md + map: openspec/changes/archive/service-registry/.pi-map.md +- openspec/changes/archive/unify-tasks-on-services + index: openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md + map: openspec/changes/archive/unify-tasks-on-services/.pi-map.md +## files +## links +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/.pi-map.md b/openspec/changes/archive/.pi-map.md new file mode 100644 index 0000000..8e79257 --- /dev/null +++ b/openspec/changes/archive/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/changes/archive +dir: openspec/changes/archive + +index: openspec/changes/archive/.pi-map.index.md + +## role +Archive directory for superseded or completed OpenSpec change proposals. +## files +## arch +Flat directory pattern serving as a historical repository of YAML or Markdown change specification files. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md b/openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md new file mode 100644 index 0000000..ac8c2b7 --- /dev/null +++ b/openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md @@ -0,0 +1,29 @@ +# openspec/changes/archive/2026-06-17-web-ui-rework (index) +dir: openspec/changes/archive/2026-06-17-web-ui-rework + +## role +OpenSpec change specification archive documenting the planning, design, implementation, and verification of a React frontend migration from MUI v9 to shadcn/ui + Tailwind v4. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- openspec/changes/archive/2026-06-17-web-ui-rework/specs + index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md + map: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md +## files +- apply-progress.md +- archive-report.md +- design.md +- exploration.md +- proposal.md +- spec.md +- sync-report.md +- tasks.md +- verify-report.md +## links +index: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md +map: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md b/openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md new file mode 100644 index 0000000..88f7160 --- /dev/null +++ b/openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md @@ -0,0 +1,27 @@ +# openspec/changes/archive/2026-06-17-web-ui-rework +dir: openspec/changes/archive/2026-06-17-web-ui-rework + +index: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md + +## role +OpenSpec change specification archive documenting the planning, design, implementation, and verification of a React frontend migration from MUI v9 to shadcn/ui + Tailwind v4. +## files +- apply-progress.md | Cumulative progress log documenting the implementation of a web UI rework across multiple slices, tracking completed tasks, file changes, deviations, and remaining work for a React/TypeScript frontend migration from MUI to shadcn/ui primitives. | dep: shadcn/ui, TanStack Table, Vitest, React, TypeScript, Vite, lucide-react, Tailwind CSS, npm, Node.js, git +- archive-report.md | Documents the archive readiness of a completed `web-ui-rework` OpenSpec change, deferring the final folder move to a parent orchestrator while verifying all 8 lifecycle phases and archive preconditions are satisfied. | dep: git, openspec, sdd-sync, sdd-status, node/npm (build/lint/test toolchain), vitest, eslint, tanstack table, shadcn/ui, tailwind +- design.md | Technical design document for migrating a React web UI from MUI v9 to shadcn/ui + Tailwind v4 + lucide-react, including component mappings, theme tokens, TanStack Table wrapper design, and navigation IA changes. | dep: shadcn/ui, Tailwind v4, lucide-react, @tanstack/react-table, react-router-dom, react-oidc-context, Vitest, @testing-library/react +- exploration.md | A technical planning document mapping the scope, inventory, and migration strategy for replacing MUI v9 with shadcn/ui + Tailwind CSS across a React frontend codebase. | dep: MUI v9, shadcn/ui, Tailwind CSS v4, lucide-react, @tanstack/react-table, @tanstack/react-query, react-router-dom, react-oidc-context +- proposal.md | A proposal document outlining the migration of a React frontend from MUI to shadcn/ui + Tailwind v4, removing orphaned dependencies and standardizing the UI design system. | dep: shadcn/ui, Tailwind v4, lucide-react, @tanstack/react-table, @mui/material, @mui/x-data-grid, @mui/icons-material, @emotion, recharts, d3, react-router-dom, react-oidc-context +- spec.md | Define acceptance requirements for a web UI rework migrating from MUI to shadcn/ui + Tailwind v4 + TanStack Table, delivered in eight build-green slices with zero legacy dependency residue. | dep: shadcn/ui, Tailwind v4, lucide-react, @tanstack/react-table, Vitest, @testing-library/react, react-router-dom, react-oidc-context, Grafana +- sync-report.md | Documents the synchronization of OpenSpec artifacts for the `web-ui-rework` change, reconciling a flat spec into domain-specific delta and canonical specs to unblock the native status engine's sync/archive gates. | dep: gentle-pi.sdd-status, openspec, verify-report.md, git, markdown +- tasks.md | Defines a phased implementation plan for migrating a web UI from MUI v9 to shadcn/ui + Tailwind v4 + lucide-react, with 8 slices of concrete tasks, dependency ordering, and strict exit gates per slice. | dep: shadcn/ui, Tailwind CSS v4, lucide-react, TanStack Table, Vitest, Testing Library, React, Vite, MUI v9 (being removed), @emotion (being removed) +- verify-report.md | Verification report documenting a completed frontend migration from MUI v9 to shadcn/ui + Tailwind v4 + lucide-react, validating all 71 tasks, 18 requirements, and 4 quality gates pass with two non-blocking findings. | dep: npm, node, vitest, @testing-library/react, react, react-router-dom, react-oidc-context, @tanstack/react-table, tailwindcss, lucide-react, radix-ui, jsdom, eslint, typescript, vite, git +## arch +Document-driven specification pattern with eight lifecycle phases (proposal → exploration → design → spec → tasks → apply-progress → verify-report → archive-report), using incremental slice-based delivery with strict exit gates per slice. +## tags +react, shadcn, lucide, ui, report, mui, tailwind, table +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md new file mode 100644 index 0000000..fdd5b15 --- /dev/null +++ b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md @@ -0,0 +1,20 @@ +# openspec/changes/archive/2026-06-17-web-ui-rework/specs (index) +dir: openspec/changes/archive/2026-06-17-web-ui-rework/specs + +## role +Specifications directory for the web UI rework change proposal in the OpenSpec archive. +## parent +index: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.index.md +map: openspec/changes/archive/2026-06-17-web-ui-rework/.pi-map.md +## children +- openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui + index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md + map: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.md +## files +## links +index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md +map: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md new file mode 100644 index 0000000..b33d0b3 --- /dev/null +++ b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/changes/archive/2026-06-17-web-ui-rework/specs +dir: openspec/changes/archive/2026-06-17-web-ui-rework/specs + +index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md + +## role +Specifications directory for the web UI rework change proposal in the OpenSpec archive. +## files +## arch +Empty directory intended to contain structured specification documents following the OpenSpec change-driven workflow pattern. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md new file mode 100644 index 0000000..f478325 --- /dev/null +++ b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui (index) +dir: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui + +## role +Defines durable architectural specifications and post-change contracts for a web UI rework transitioning from MUI/Emotion to shadcn/ui/Tailwind. +## parent +index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.index.md +map: openspec/changes/archive/2026-06-17-web-ui-rework/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md +map: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.md b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.md new file mode 100644 index 0000000..1d61648 --- /dev/null +++ b/openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui +dir: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui + +index: openspec/changes/archive/2026-06-17-web-ui-rework/specs/web-ui/.pi-map.index.md + +## role +Defines durable architectural specifications and post-change contracts for a web UI rework transitioning from MUI/Emotion to shadcn/ui/Tailwind. +## files +- spec.md | Defines durable post-change end-state contracts for a web UI rework migrating from MUI/Emotion to shadcn/ui/Tailwind, removing charting, restructuring navigation, and standardizing on TanStack Table with Vitest testing. | dep: shadcn/ui, Tailwind v4, lucide-react, Radix primitives, @tanstack/react-table, react-router-dom, react-oidc-context, Vitest, @testing-library/react, Grafana +## arch +Specification document establishing declarative technology-stack mandates, component-library standards, and structural conventions governing frontend implementation. +## tags +react, shadcn, table, vitest, spec, defines, durable, post +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md new file mode 100644 index 0000000..98af73d --- /dev/null +++ b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md @@ -0,0 +1,25 @@ +# openspec/changes/archive/configurable-dashboard-widgets (index) +dir: openspec/changes/archive/configurable-dashboard-widgets + +## role +OpenSpec change specification package documenting the design and implementation plan for a configurable dashboard widgets feature. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- openspec/changes/archive/configurable-dashboard-widgets/specs + index: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md + map: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md +## files +- apply-progress.md +- design.md +- exploration.md +- proposal.md +- tasks.md +## links +index: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md +map: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md new file mode 100644 index 0000000..33b26f9 --- /dev/null +++ b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md @@ -0,0 +1,23 @@ +# openspec/changes/archive/configurable-dashboard-widgets +dir: openspec/changes/archive/configurable-dashboard-widgets + +index: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md + +## role +OpenSpec change specification package documenting the design and implementation plan for a configurable dashboard widgets feature. +## files +- apply-progress.md | Documents the implementation progress of a configurable dashboard widgets feature across four development slices, covering backend CRUD, source adapters, frontend components, and dashboard integration. | dep: FastAPI, Pydantic, TanStack Query, React, TypeScript, Vitest, pytest, ruff, docker-compose +- design.md | Design document for a configurable dashboard widget system with closed compile-time registries, adding backend SQLite storage, FastAPI CRUD endpoints, source adapters, and React frontend components for a media library viewer application. | dep: FastAPI, React, SQLite, Pydantic, shadcn/ui, Starlette, Jellyfin client, SSH client, LocalCommandClient/RemoteSSHClient, Grafana, Prometheus +- exploration.md | Explores architectural design for adding configurable dashboard widgets and addon extension points to a media library viewer application. | dep: React, TanStack Query, FastAPI, SQLite, Pydantic, shadcn, lucide, react-grid-layout (considered) +- proposal.md | This file is a software design proposal for adding a configurable widget system to an existing FastAPI/React dashboard application, replacing hard-coded sections with extensible, user-configurable widgets backed by multiple data sources. | dep: FastAPI, React, TanStack Query, SQLite, Pydantic, TypeScript, SQLAlchemy/SettingsStore, Jellyfin client, SSH client, Grafana, Prometheus +- tasks.md | Software design document outlining a phased implementation plan for replacing hard-coded dashboard sections with a configurable, persisted widget system across backend and frontend. | dep: Pydantic, FastAPI, SQLite, JWT/API-key middleware, TanStack Query, React, TypeScript, shadcn/ui, docker compose, ruff, pytest +## arch +Specification-driven development pattern using progressive documentation (exploration → proposal → design → tasks → progress) to drive phased full-stack implementation across backend and frontend. +## tags +react, fastapi, configurable, dashboard, design, pydantic, sqlite, tanstack query +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md new file mode 100644 index 0000000..2341801 --- /dev/null +++ b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md @@ -0,0 +1,20 @@ +# openspec/changes/archive/configurable-dashboard-widgets/specs (index) +dir: openspec/changes/archive/configurable-dashboard-widgets/specs + +## role +Archived specification documents for a configurable dashboard widgets feature change. +## parent +index: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md +map: openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md +## children +- openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets + index: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md + map: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.md +## files +## links +index: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md +map: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md new file mode 100644 index 0000000..217ee53 --- /dev/null +++ b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/changes/archive/configurable-dashboard-widgets/specs +dir: openspec/changes/archive/configurable-dashboard-widgets/specs + +index: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md + +## role +Archived specification documents for a configurable dashboard widgets feature change. +## files +## arch +OpenSpec change specification structure containing design specs for previously implemented widget customization capabilities. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md b/openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md new file mode 100644 index 0000000..f17102d --- /dev/null +++ b/openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets (index) +dir: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets + +## role +Defines the specification document for a Phase 1 configurable dashboard widget system with persisted layouts and REST API access. +## parent +index: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md +map: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md +map: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.md b/openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.md new file mode 100644 index 0000000..fc56276 --- /dev/null +++ b/openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets +dir: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets + +index: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-widgets/.pi-map.index.md + +## role +Defines the specification document for a Phase 1 configurable dashboard widget system with persisted layouts and REST API access. +## files +- spec.md | Define Phase 1 requirements for a persisted, configurable dashboard widget system with closed compile-time registries, SQLite persistence, REST API, and independent widget data fetching. | dep: SQLite, Pydantic, TanStack Query, shadcn/ui, JWT/API-key auth, Jellyfin client, Grafana/Prometheus settings, SSH task runner, BackupDashboardSummary +## arch +Specification document outlining a closed compile-time registry pattern with SQLite persistence, REST API layer, and independent widget data fetching architecture. +## tags +widget, sqlite, api, spec, define, phase, requirements, persisted +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md new file mode 100644 index 0000000..2ba6daa --- /dev/null +++ b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/decommission-monitoring-poller (index) +dir: openspec/changes/archive/decommission-monitoring-poller + +## role +Documents the decommissioning plan for a legacy SSH-based monitoring poller following a migration to Prometheus/Grafana. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- +## files +- plan.md +## links +index: openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md +map: openspec/changes/archive/decommission-monitoring-poller/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md new file mode 100644 index 0000000..30529eb --- /dev/null +++ b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/decommission-monitoring-poller +dir: openspec/changes/archive/decommission-monitoring-poller + +index: openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md + +## role +Documents the decommissioning plan for a legacy SSH-based monitoring poller following a migration to Prometheus/Grafana. +## files +- plan.md | Draft implementation plan for removing a legacy SSH-based monitoring poller and its associated dead code after an observability stack migration to Prometheus/Grafana. | dep: none (this is a planning document, not executable code) +## arch +Single-file Markdown archive artifact following an OpenSpec change-management process, structured as an implementation plan with phased steps and rationale. +## tags +plan, draft, implementation, removing, legacy, ssh, monitoring, poller +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/observability-service-registry/.pi-map.index.md b/openspec/changes/archive/observability-service-registry/.pi-map.index.md new file mode 100644 index 0000000..1079d0d --- /dev/null +++ b/openspec/changes/archive/observability-service-registry/.pi-map.index.md @@ -0,0 +1,21 @@ +# openspec/changes/archive/observability-service-registry (index) +dir: openspec/changes/archive/observability-service-registry + +## role +Proposal and design documentation package detailing the migration of observability services from environment-variable configuration to a dynamic service registry model. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- +## files +- design.md +- proposal.md +- tasks.md +## links +index: openspec/changes/archive/observability-service-registry/.pi-map.index.md +map: openspec/changes/archive/observability-service-registry/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/observability-service-registry/.pi-map.md b/openspec/changes/archive/observability-service-registry/.pi-map.md new file mode 100644 index 0000000..c9ed684 --- /dev/null +++ b/openspec/changes/archive/observability-service-registry/.pi-map.md @@ -0,0 +1,21 @@ +# openspec/changes/archive/observability-service-registry +dir: openspec/changes/archive/observability-service-registry + +index: openspec/changes/archive/observability-service-registry/.pi-map.index.md + +## role +Proposal and design documentation package detailing the migration of observability services from environment-variable configuration to a dynamic service registry model. +## files +- design.md | Design document describing the migration of observability services from environment-variable configuration to a dynamic service registry model. +- proposal.md | This file is a technical proposal document outlining changes to migrate observability services (Alertmanager, Grafana, Prometheus) from environment variables to a first-class service registry. +- tasks.md | Task breakdown document detailing a 5-slice implementation plan for migrating observability services (Alertmanager, Grafana, Prometheus) to a service registry pattern with UI-based configuration. +## arch +OpenSpec change-documentation pattern consisting of three structured Markdown artifacts: a proposal (intent), a design (technical approach), and tasks (implementation breakdown). +## tags +design, document, observability, services, service, registry, proposal, environment +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/observability-service-registry/design.md b/openspec/changes/archive/observability-service-registry/design.md similarity index 100% rename from openspec/changes/observability-service-registry/design.md rename to openspec/changes/archive/observability-service-registry/design.md diff --git a/openspec/changes/observability-service-registry/proposal.md b/openspec/changes/archive/observability-service-registry/proposal.md similarity index 100% rename from openspec/changes/observability-service-registry/proposal.md rename to openspec/changes/archive/observability-service-registry/proposal.md diff --git a/openspec/changes/observability-service-registry/tasks.md b/openspec/changes/archive/observability-service-registry/tasks.md similarity index 100% rename from openspec/changes/observability-service-registry/tasks.md rename to openspec/changes/archive/observability-service-registry/tasks.md diff --git a/openspec/changes/archive/service-registry/.pi-map.index.md b/openspec/changes/archive/service-registry/.pi-map.index.md new file mode 100644 index 0000000..1dd0958 --- /dev/null +++ b/openspec/changes/archive/service-registry/.pi-map.index.md @@ -0,0 +1,22 @@ +# openspec/changes/archive/service-registry (index) +dir: openspec/changes/archive/service-registry + +## role +Archive of an OpenSpec change proposal that refactored a dashboard's widget configuration system from static addon/machine-based configuration into a dynamic, database-persisted runtime service registry. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- +## files +- apply-progress.md +- design.md +- proposal.md +- tasks.md +## links +index: openspec/changes/archive/service-registry/.pi-map.index.md +map: openspec/changes/archive/service-registry/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/service-registry/.pi-map.md b/openspec/changes/archive/service-registry/.pi-map.md new file mode 100644 index 0000000..da8846c --- /dev/null +++ b/openspec/changes/archive/service-registry/.pi-map.md @@ -0,0 +1,22 @@ +# openspec/changes/archive/service-registry +dir: openspec/changes/archive/service-registry + +index: openspec/changes/archive/service-registry/.pi-map.index.md + +## role +Archive of an OpenSpec change proposal that refactored a dashboard's widget configuration system from static addon/machine-based configuration into a dynamic, database-persisted runtime service registry. +## files +- apply-progress.md | Documents the incremental implementation and migration of a runtime service registry across multiple PR slices, replacing legacy addon/machine-based configuration with a dynamic service-based architecture. | dep: React/TypeScript frontend, Python/FastAPI backend, Docker Compose, Ruff, Pytest, npm/Vite, Jellyfin API, Jellyseerr API, SSH transport +- design.md | Design document for refactoring a monolithic addon system into a runtime service registry with pluggable service definitions, encrypted secrets, and derived widget types. | dep: FastAPI, Pydantic, SQLite, Fernet (cryptography), React/TypeScript frontend, SSH task runner, Grafana, Prometheus, Jellyfin, Nextcloud +- proposal.md | Proposes a runtime service registry architecture to replace environment-variable and machine-level configuration with database-persisted, UI-managed service instances for dashboard widgets. | dep: SQLite, Pydantic, Python modules, backend models, frontend routing, settings store, widget registry, adapters +- tasks.md | Defines a phased implementation plan for migrating a dashboard application's widget system from an addon-based model to a service registry model with encrypted secrets, CRUD operations, and runtime service resolution. | dep: cryptography, FastAPI, Pydantic, SQLAlchemy/sqlite, React/TypeScript, pytest, ruff, npm +## arch +OpenSpec change-documentation pattern comprising four structured Markdown artifacts—proposal, design, tasks, and apply-progress—that sequentially define the rationale, technical design, implementation plan, and execution history for the feature. +## tags +service, runtime, registry, addon, react, design, pydantic, sqlite +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md b/openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md new file mode 100644 index 0000000..80950b1 --- /dev/null +++ b/openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md @@ -0,0 +1,21 @@ +# openspec/changes/archive/unify-tasks-on-services (index) +dir: openspec/changes/archive/unify-tasks-on-services + +## role +Archived OpenSpec change proposal that documented the migration of saved task execution from machine-based to SSH service-based references. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- +## files +- design.md +- proposal.md +- tasks.md +## links +index: openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md +map: openspec/changes/archive/unify-tasks-on-services/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/unify-tasks-on-services/.pi-map.md b/openspec/changes/archive/unify-tasks-on-services/.pi-map.md new file mode 100644 index 0000000..e6c2fdc --- /dev/null +++ b/openspec/changes/archive/unify-tasks-on-services/.pi-map.md @@ -0,0 +1,21 @@ +# openspec/changes/archive/unify-tasks-on-services +dir: openspec/changes/archive/unify-tasks-on-services + +index: openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md + +## role +Archived OpenSpec change proposal that documented the migration of saved task execution from machine-based to SSH service-based references. +## files +- design.md | Design document for unifying saved task execution on SSH services by replacing machine-based references with service-based ones, consolidating history into a single table, and creating a shared backend helper. | dep: ssh_tasks service, service_task_runs table, SettingsStore, ServiceRecord, _build_ssh_client, routers/tasks.py, widgets/sources.py, SshTaskWidgetSource, SQLite 3.25+ +- proposal.md | Proposes migrating the Actions page's saved task execution from machine-based to SSH service-based to unify execution paths with the existing SSH task widget. | dep: ssh_tasks service, service registry, service_task_runs, saved_task_runs, _build_ssh_client, _client_for_machine +- tasks.md | A completed task plan for refactoring saved tasks to run on SSH services instead of machines, unifying backend and frontend code around a shared runner and service-based execution model. | dep: backend, frontend, SSH services, settings_store, task_runner, routers, widgets, pytest, npm, ruff +## arch +Specification-driven change management pattern using three documents (proposal, design, tasks) to capture rationale, technical architecture, and implementation tracking for a completed refactoring effort. +## tags +task, execution, ssh, tasks, design, saved, service, backend +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/jellyfin-service-registry/.pi-map.index.md b/openspec/changes/jellyfin-service-registry/.pi-map.index.md new file mode 100644 index 0000000..b20120b --- /dev/null +++ b/openspec/changes/jellyfin-service-registry/.pi-map.index.md @@ -0,0 +1,21 @@ +# openspec/changes/jellyfin-service-registry (index) +dir: openspec/changes/jellyfin-service-registry + +## role +Documentation and planning package for migrating Jellyfin service configuration from machine-level settings to a service-registry model. +## parent +index: openspec/changes/.pi-map.index.md +map: openspec/changes/.pi-map.md +## children +- +## files +- design.md +- proposal.md +- tasks.md +## links +index: openspec/changes/jellyfin-service-registry/.pi-map.index.md +map: openspec/changes/jellyfin-service-registry/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/jellyfin-service-registry/.pi-map.md b/openspec/changes/jellyfin-service-registry/.pi-map.md new file mode 100644 index 0000000..0aef1c5 --- /dev/null +++ b/openspec/changes/jellyfin-service-registry/.pi-map.md @@ -0,0 +1,21 @@ +# openspec/changes/jellyfin-service-registry +dir: openspec/changes/jellyfin-service-registry + +index: openspec/changes/jellyfin-service-registry/.pi-map.index.md + +## role +Documentation and planning package for migrating Jellyfin service configuration from machine-level settings to a service-registry model. +## files +- design.md | Design document for completing the migration of Jellyfin service configuration from machine-level settings to service-registry instances, involving removal of legacy media_root and path_prefix fields across backend, frontend, and docs. | dep: integrations/jellyfin.py, integrations/jellyseerr.py, dependencies.py, settings_store.py, path_utils.py, routers/files.py, types/index.ts, Settings.tsx, docs/REQUIREMENTS.md, CHANGELOG.md +- proposal.md | Proposes completing a migration of Jellyfin configuration from machine-level settings to a service registry, removing dead fields and legacy UI elements. +- tasks.md | Defines a task checklist for completing the migration of Jellyfin from machine-level configuration to a service registry, including backend/frontend cleanup and documentation updates. | dep: backend/src/media_library_viewer_api/services/settings_store.py, backend/src/media_library_viewer_api/path_utils.py, backend/tests, frontend/src/types/index.ts, frontend/src/pages/Settings.tsx, frontend/src/pages/__tests__/Settings.test.tsx, docs/REQUIREMENTS.md, CHANGELOG.md +## arch +OpenSpec change-proposal structure consisting of three coordinated documents (design, proposal, tasks) that define the rationale, technical approach, and implementation checklist for a cross-stack refactoring effort. +## tags +py, settings, backend, frontend, src, jellyfin, service, md +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/specs/.pi-map.index.md b/openspec/specs/.pi-map.index.md new file mode 100644 index 0000000..aea3fb8 --- /dev/null +++ b/openspec/specs/.pi-map.index.md @@ -0,0 +1,20 @@ +# openspec/specs (index) +dir: openspec/specs + +## role +Directory for OpenAPI/Swagger specification files defining API contracts, schemas, and endpoint documentation. +## parent +index: openspec/.pi-map.index.md +map: openspec/.pi-map.md +## children +- openspec/specs/web-ui + index: openspec/specs/web-ui/.pi-map.index.md + map: openspec/specs/web-ui/.pi-map.md +## files +## links +index: openspec/specs/.pi-map.index.md +map: openspec/specs/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/specs/.pi-map.md b/openspec/specs/.pi-map.md new file mode 100644 index 0000000..52b93e6 --- /dev/null +++ b/openspec/specs/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/specs +dir: openspec/specs + +index: openspec/specs/.pi-map.index.md + +## role +Directory for OpenAPI/Swagger specification files defining API contracts, schemas, and endpoint documentation. +## files +## arch +Declarative YAML/JSON specification files following the OpenAPI standard, organized as static schema definitions without executable code. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/specs/web-ui/.pi-map.index.md b/openspec/specs/web-ui/.pi-map.index.md new file mode 100644 index 0000000..8cd7d9d --- /dev/null +++ b/openspec/specs/web-ui/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/specs/web-ui (index) +dir: openspec/specs/web-ui + +## role +Canonical specification document defining the web UI migration plan from MUI/Emotion to shadcn/ui + Tailwind v4 with TanStack Table and observability tooling. +## parent +index: openspec/specs/.pi-map.index.md +map: openspec/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/specs/web-ui/.pi-map.index.md +map: openspec/specs/web-ui/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/specs/web-ui/.pi-map.md b/openspec/specs/web-ui/.pi-map.md new file mode 100644 index 0000000..a53481b --- /dev/null +++ b/openspec/specs/web-ui/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/specs/web-ui +dir: openspec/specs/web-ui + +index: openspec/specs/web-ui/.pi-map.index.md + +## role +Canonical specification document defining the web UI migration plan from MUI/Emotion to shadcn/ui + Tailwind v4 with TanStack Table and observability tooling. +## files +- spec.md | Defines the canonical post-rework specification for a Manage web frontend migration from MUI/Emotion to shadcn/ui + Tailwind v4, with TanStack Table data grids, thin-dashboard observability, and reconciled information architecture. | dep: shadcn/ui, Tailwind v4, lucide-react, TanStack Table, @tanstack/react-table, Radix primitives, react-router-dom, react-oidc-context, Vitest, @testing-library/react, Grafana +## arch +Single-document declarative specification (no code architecture); describes target frontend stack patterns including component library migration, data grid implementation, and information architecture reconciliation. +## tags +react, shadcn, table, spec, defines, canonical, post, rework +## symbols +- +## workflows +- +## dirty +-