diff --git a/.claude/.pi-map.index.md b/.claude/.pi-map.index.md index 7243ac7..c7d63d2 100644 --- a/.claude/.pi-map.index.md +++ b/.claude/.pi-map.index.md @@ -2,7 +2,7 @@ dir: .claude ## role -Configuration directory for the Claude AI assistant, storing project-specific settings, instructions, and behavioral guidelines. +Configuration and settings directory for Claude AI assistant integration within the project workspace. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/.claude/.pi-map.md b/.claude/.pi-map.md index c8f0814..e2c4d23 100644 --- a/.claude/.pi-map.md +++ b/.claude/.pi-map.md @@ -4,10 +4,10 @@ dir: .claude index: .claude/.pi-map.index.md ## role -Configuration directory for the Claude AI assistant, storing project-specific settings, instructions, and behavioral guidelines. +Configuration and settings directory for Claude AI assistant integration within the project workspace. ## 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. +Flat configuration directory following standard AI assistant tool conventions, typically containing permission rules, context files, and project-specific behavioral settings. ## tags - ## symbols diff --git a/.claude/skills/.pi-map.index.md b/.claude/skills/.pi-map.index.md index 17d4237..343f620 100644 --- a/.claude/skills/.pi-map.index.md +++ b/.claude/skills/.pi-map.index.md @@ -2,7 +2,7 @@ dir: .claude/skills ## role -Directory containing custom skill definitions and capability instructions for the Claude AI assistant integration. +Configuration directory storing reusable Claude AI skill definitions and behavioral instructions for the project. ## parent index: .claude/.pi-map.index.md map: .claude/.pi-map.md diff --git a/.claude/skills/.pi-map.md b/.claude/skills/.pi-map.md index b2b2fae..8f06a21 100644 --- a/.claude/skills/.pi-map.md +++ b/.claude/skills/.pi-map.md @@ -4,10 +4,10 @@ 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. +Configuration directory storing reusable Claude AI skill definitions and behavioral instructions for the project. ## files ## arch -Flat configuration file structure defining modular skill behaviors and prompts used to extend Claude's domain-specific abilities. +Flat directory structure with markdown-based skill modules that define specialized assistant capabilities. ## tags - ## symbols diff --git a/.claude/skills/sift-backlog/.pi-map.index.md b/.claude/skills/sift-backlog/.pi-map.index.md index 0cf077f..c767456 100644 --- a/.claude/skills/sift-backlog/.pi-map.index.md +++ b/.claude/skills/sift-backlog/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Provides a structured workflow skill for Claude to triage, organize, and activate backlog tasks into actionable sprint plans using the `sf` CLI tool. ## parent index: .claude/skills/.pi-map.index.md map: .claude/skills/.pi-map.md diff --git a/.claude/skills/sift-backlog/.pi-map.md b/.claude/skills/sift-backlog/.pi-map.md index ea55d18..83db33f 100644 --- a/.claude/skills/sift-backlog/.pi-map.md +++ b/.claude/skills/sift-backlog/.pi-map.md @@ -4,11 +4,11 @@ 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. +Provides a structured workflow skill for Claude to triage, organize, and activate backlog tasks into actionable sprint 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. +Single-file declarative skill definition following a prompt-engineering pattern that encodes step-by-step procedures and decision rules for Claude to execute when invoked. ## tags skill, defines, workflow, triaging, organizing, activating, backlog, tasks ## symbols diff --git a/.opencode/.pi-map.index.md b/.opencode/.pi-map.index.md index af64611..d91271f 100644 --- a/.opencode/.pi-map.index.md +++ b/.opencode/.pi-map.index.md @@ -2,7 +2,7 @@ dir: .opencode ## role -Configuration directory for the opencode tool, managing project-specific settings and preferences. +Configuration and settings package for the opencode tool, defining project-level or user-level preferences and behavior. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/.opencode/.pi-map.md b/.opencode/.pi-map.md index a7ae063..c71d912 100644 --- a/.opencode/.pi-map.md +++ b/.opencode/.pi-map.md @@ -4,10 +4,10 @@ dir: .opencode index: .opencode/.pi-map.index.md ## role -Configuration directory for the opencode tool, managing project-specific settings and preferences. +Configuration and settings package for the opencode tool, defining project-level or user-level preferences and behavior. ## files ## arch -Flat directory structure containing configuration files that define opencode behavior for the associated project. +Flat directory structure with declarative configuration files; no executable code or architectural patterns involved. ## tags - ## symbols diff --git a/.opencode/commands/.pi-map.index.md b/.opencode/commands/.pi-map.index.md index 1a79ea4..f220c71 100644 --- a/.opencode/commands/.pi-map.index.md +++ b/.opencode/commands/.pi-map.index.md @@ -2,7 +2,7 @@ dir: .opencode/commands ## role -Defines slash-command workflows and assistant personas for an OpenSpec-based development process (explore, propose, apply, archive). +Defines structured AI assistant workflow commands for an OpenSpec-based software development lifecycle (explore, propose, apply, archive). ## parent index: .opencode/.pi-map.index.md map: .opencode/.pi-map.md diff --git a/.opencode/commands/.pi-map.md b/.opencode/commands/.pi-map.md index 70c69d6..78be124 100644 --- a/.opencode/commands/.pi-map.md +++ b/.opencode/commands/.pi-map.md @@ -4,14 +4,14 @@ 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). +Defines structured AI assistant workflow commands for an OpenSpec-based software development lifecycle (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. +Markdown-based command-definition pattern where each file encodes a discrete, step-by-step procedural prompt controlling assistant behavior for a specific development phase. ## tags opsx, defines, tasks, md, workflow, openspec, openspec cli, askuserquestion tool ## symbols diff --git a/.opencode/skills/.pi-map.index.md b/.opencode/skills/.pi-map.index.md index f4abd43..8840170 100644 --- a/.opencode/skills/.pi-map.index.md +++ b/.opencode/skills/.pi-map.index.md @@ -2,7 +2,7 @@ dir: .opencode/skills ## role -Directory for defining custom agent skills, capabilities, and behavioral instructions within the opencode configuration framework. +Custom skill/automation definitions for the opencode tooling framework, defining reusable capabilities or behaviors. ## parent index: .opencode/.pi-map.index.md map: .opencode/.pi-map.md diff --git a/.opencode/skills/.pi-map.md b/.opencode/skills/.pi-map.md index 009338d..0fdf20b 100644 --- a/.opencode/skills/.pi-map.md +++ b/.opencode/skills/.pi-map.md @@ -4,10 +4,10 @@ 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. +Custom skill/automation definitions for the opencode tooling framework, defining reusable capabilities or behaviors. ## 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. +Configuration-driven skill registry with declarative definition files (no implementation code present in this directory). ## tags - ## symbols diff --git a/.opencode/skills/openspec-apply-change/.pi-map.index.md b/.opencode/skills/openspec-apply-change/.pi-map.index.md index 0048ab3..9fd5129 100644 --- a/.opencode/skills/openspec-apply-change/.pi-map.index.md +++ b/.opencode/skills/openspec-apply-change/.pi-map.index.md @@ -2,7 +2,7 @@ dir: .opencode/skills/openspec-apply-change ## role -Provides a structured skill definition for implementing OpenSpec changes through a schema-driven workflow with progress tracking. +Provides a structured skill definition for implementing OpenSpec changes through a schema-driven workflow with progress tracking and contextual file reading. ## parent index: .opencode/skills/.pi-map.index.md map: .opencode/skills/.pi-map.md diff --git a/.opencode/skills/openspec-apply-change/.pi-map.md b/.opencode/skills/openspec-apply-change/.pi-map.md index a06be2f..6e51fbc 100644 --- a/.opencode/skills/openspec-apply-change/.pi-map.md +++ b/.opencode/skills/openspec-apply-change/.pi-map.md @@ -4,11 +4,11 @@ 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. +Provides a structured skill definition for implementing OpenSpec changes through a schema-driven workflow with progress tracking and contextual file reading. ## 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. +Declarative skill specification using markdown-based instructions, schema-driven task processing, and progressive context loading patterns. ## tags skill, defines, implementing, tasks, openspec, change, schema, driven ## symbols diff --git a/.opencode/skills/openspec-archive-change/.pi-map.index.md b/.opencode/skills/openspec-archive-change/.pi-map.index.md index 6eb5e3b..2316cc2 100644 --- a/.opencode/skills/openspec-archive-change/.pi-map.index.md +++ b/.opencode/skills/openspec-archive-change/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Defines a specialized skill within the openspec workflow that handles the archival process for completed changes, including validation checks, sync assessment, and user confirmation. ## parent index: .opencode/skills/.pi-map.index.md map: .opencode/skills/.pi-map.md diff --git a/.opencode/skills/openspec-archive-change/.pi-map.md b/.opencode/skills/openspec-archive-change/.pi-map.md index 2899d09..7491405 100644 --- a/.opencode/skills/openspec-archive-change/.pi-map.md +++ b/.opencode/skills/openspec-archive-change/.pi-map.md @@ -4,11 +4,11 @@ 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. +Defines a specialized skill within the openspec workflow that handles the archival process for completed changes, including validation checks, sync assessment, and user confirmation. ## 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. +Single-file declarative skill definition using a markdown-based pattern description format, structured as a procedural workflow with validation gates and conditional user interaction steps. ## tags skill, openspec, sync, defines, archiving, completed, change, experimental ## symbols diff --git a/.opencode/skills/openspec-explore/.pi-map.index.md b/.opencode/skills/openspec-explore/.pi-map.index.md index ad93ef9..41e2cfb 100644 --- a/.opencode/skills/openspec-explore/.pi-map.index.md +++ b/.opencode/skills/openspec-explore/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Provides a conversational "explore mode" skill definition that configures the OpenSpec CLI to act as a collaborative thinking partner for brainstorming, problem investigation, and requirements clarification without code implementation. ## parent index: .opencode/skills/.pi-map.index.md map: .opencode/skills/.pi-map.md diff --git a/.opencode/skills/openspec-explore/.pi-map.md b/.opencode/skills/openspec-explore/.pi-map.md index c7ae04d..e73b728 100644 --- a/.opencode/skills/openspec-explore/.pi-map.md +++ b/.opencode/skills/openspec-explore/.pi-map.md @@ -4,11 +4,11 @@ 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. +Provides a conversational "explore mode" skill definition that configures the OpenSpec CLI to act as a collaborative thinking partner for brainstorming, problem investigation, and requirements clarification without code implementation. ## 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. +Skill-definition pattern using a single Markdown manifest (SKILL.md) that declaratively specifies the assistant's behavioral instructions, interaction style, and operational constraints for the explore workflow. ## tags skill, defines, conversational, explore, mode, openspec, cli, acts ## symbols diff --git a/.opencode/skills/openspec-propose/.pi-map.index.md b/.opencode/skills/openspec-propose/.pi-map.index.md index 0e27001..30731dd 100644 --- a/.opencode/skills/openspec-propose/.pi-map.index.md +++ b/.opencode/skills/openspec-propose/.pi-map.index.md @@ -2,7 +2,7 @@ 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). +Provides an AI assistant skill that automates the openspec proposal workflow, scaffolding directories and generating structured artifacts (proposals, designs, tasks) for new project changes. ## parent index: .opencode/skills/.pi-map.index.md map: .opencode/skills/.pi-map.md diff --git a/.opencode/skills/openspec-propose/.pi-map.md b/.opencode/skills/openspec-propose/.pi-map.md index 855779c..f3f5ee8 100644 --- a/.opencode/skills/openspec-propose/.pi-map.md +++ b/.opencode/skills/openspec-propose/.pi-map.md @@ -4,11 +4,11 @@ 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). +Provides an AI assistant skill that automates the openspec proposal workflow, scaffolding directories and generating structured artifacts (proposals, designs, tasks) for new project changes. ## 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. +Declarative skill-definition pattern using a markdown-based manifest (SKILL.md) that encodes a step-by-step procedural workflow, CLI commands, and file-system conventions for the AI to follow. ## tags skill, defines, assistant, automates, proposing, new, changes, scaffolding ## symbols diff --git a/.pi-map.index.md b/.pi-map.index.md index c9b039f..35be825 100644 --- a/.pi-map.index.md +++ b/.pi-map.index.md @@ -16,7 +16,7 @@ dir: . Trust boundary: index routes, map orients, source decides. ## role -Root project configuration and orchestration package for a media library management application with observability, defining Docker deployment stacks, environment templates, and project documentation. +Root project configuration and documentation directory for a media library/server operations management application with Jellyfin integration, SSH inspection, and observability capabilities. ## parent - ## children @@ -32,6 +32,9 @@ Root project configuration and orchestration package for a media library managem - .pi index: .pi/.pi-map.index.md map: .pi/.pi-map.md +- .pi-tmp + index: .pi-tmp/.pi-map.index.md + map: .pi-tmp/.pi-map.md - .ruff_cache index: .ruff_cache/.pi-map.index.md map: .ruff_cache/.pi-map.md @@ -66,7 +69,6 @@ Root project configuration and orchestration package for a media library managem - docker-compose.dev.yml - docker-compose.observability.yml - docker-compose.yml -- swap-pane - token-usage-output.txt ## links index: ./.pi-map.index.md diff --git a/.pi-map.md b/.pi-map.md index b2f4e3d..2c2d775 100644 --- a/.pi-map.md +++ b/.pi-map.md @@ -18,13 +18,13 @@ index: ./.pi-map.index.md Trust boundary: index routes, map orients, source decides. ## role -Root project configuration and orchestration package for a media library management application with observability, defining Docker deployment stacks, environment templates, and project documentation. +Root project configuration and documentation directory for a media library/server operations management application with Jellyfin integration, SSH inspection, and observability capabilities. ## 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 | Configures Git to ignore Python artifacts, virtual environments, secrets, editor files, frontend builds, and tool-specific metadata from version control. - 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 application across recent versions. +- CHANGELOG.md | Documents notable changes, breaking changes, and migration steps for the Manage application across releases. - CONTRIBUTING.md | Provides contribution guidelines and setup instructions for the Manage project's backend (FastAPI) and frontend (React) codebases. | dep: FastAPI, React, Vite, TypeScript, Ruff, pytest, Docker Compose, Tailwind CSS, TanStack Query - LICENSE | Provides the MIT open-source software license terms for the project - README.md | Project README documenting a media and server operations tool with Jellyfin integration, SSH file inspection, and server monitoring capabilities. | dep: FastAPI, React, TypeScript, Docker Compose, SQLite, Traefik, OIDC/Authentik, Jellyfin, Prometheus, Grafana, Alertmanager @@ -32,10 +32,9 @@ Root project configuration and orchestration package for a media library managem - 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 production Docker Compose stack for a backend-frontend application with OIDC authentication, Traefik routing, TLS, and Prometheus metrics exposure. | dep: Traefik, OIDC provider, Docker, Vite, external observability stack -- swap-pane | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh - 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 -Containerized full-stack architecture using Docker Compose for orchestration, Traefik for production routing/TLS, dual dev/production environments, and an optional standalone observability stack (Prometheus/Grafana/Loki/Alertmanager). +Full-stack containerized architecture using Docker Compose orchestration with a FastAPI/Uvicorn backend and Vite React frontend, Traefik reverse proxy with TLS/OIDC, and an optional observability stack (Prometheus, Grafana, Loki, Alertmanager, Alloy). ## tags docker, grafana, application, fastapi, compose, prometheus, backend, frontend ## symbols diff --git a/.pi-tmp/.pi-map.index.md b/.pi-tmp/.pi-map.index.md new file mode 100644 index 0000000..d626e93 --- /dev/null +++ b/.pi-tmp/.pi-map.index.md @@ -0,0 +1,25 @@ +# .pi-tmp (index) +dir: .pi-tmp + +## role +Documentation and reporting workspace containing acceptance reports and change documentation for iterative feature development and refactoring efforts. +## parent +index: ./.pi-map.index.md +map: ./.pi-map.md +## children +- +## files +- followups-batch1-out.md +- four-fixes-out.md +- grafana-chart-out.md +- refine-23-out.md +- refine-4-out.md +- reusable-widgets-out.md +- widgets-out.md +## links +index: .pi-tmp/.pi-map.index.md +map: .pi-tmp/.pi-map.md +## workflows +- +## dirty +- diff --git a/.pi-tmp/.pi-map.md b/.pi-tmp/.pi-map.md new file mode 100644 index 0000000..674edee --- /dev/null +++ b/.pi-tmp/.pi-map.md @@ -0,0 +1,25 @@ +# .pi-tmp +dir: .pi-tmp + +index: .pi-tmp/.pi-map.index.md + +## role +Documentation and reporting workspace containing acceptance reports and change documentation for iterative feature development and refactoring efforts. +## files +- followups-batch1-out.md | This file documents a batch of fixes for a reusable widget system, including a code change summary, validation test results, and a formal acceptance report. +- four-fixes-out.md | Documentation report detailing four bug fixes across frontend and backend components, including changes made, validation results, and residual risks. +- grafana-chart-out.md | Documentation and acceptance report for replacing a Grafana iframe panel widget with a server-side chart query widget using recharts. | dep: recharts, Grafana API, Tailwind CSS, pytest, ruff, eslint +- refine-23-out.md | Documentation of a refactoring effort that moved service configuration from the ServicePage to Settings, replacing it with instance tabs. +- refine-4-out.md | Documentation of a change implementing configurable per-service widget overview tabs with backend filtering by service_id/scope, replacing stubs with a real OverviewTab component. | dep: React, TypeScript, Python/FastAPI, pytest, ruff, Vite, React Query (useWidgets hook) +- reusable-widgets-out.md | This file is an implementation report documenting the addition of reusable widget references across a full-stack application (backend CRUD/API and frontend UI/hooks). +- widgets-out.md | Documentation/acceptance report describing the implementation of two new widgets (Jellyfin now_playing and Grafana panel embed) across backend and frontend. +## arch +Flat collection of Markdown reports, each following a consistent structure of change summary, validation results, and acceptance/risk assessment across full-stack changes. +## tags +out, widget, report, documentation, widgets, fixes, reusable, backend +## symbols +- +## workflows +- +## dirty +- diff --git a/archive/.pi-map.index.md b/archive/.pi-map.index.md index 8920819..f7519ca 100644 --- a/archive/.pi-map.index.md +++ b/archive/.pi-map.index.md @@ -2,7 +2,7 @@ dir: archive ## role -Archive of an earlier project structure for a Streamlit-based Jellyfin media library browser with SSH remote file inspection capabilities. +Archived/legacy entrypoint and packaging configuration for a Streamlit-based Jellyfin media library browser with SSH remote file inspection capabilities. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/archive/.pi-map.md b/archive/.pi-map.md index 5b2be7b..f85a346 100644 --- a/archive/.pi-map.md +++ b/archive/.pi-map.md @@ -4,13 +4,13 @@ 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. +Archived/legacy entrypoint and packaging configuration 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). +Thin bootstrap layer using path manipulation to delegate to a source module (src/), packaged with standard Python tooling (pyproject.toml) for dependency management and Streamlit deployment. ## tags streamlit, app, python, media, library, package, pyproject, pip ## symbols diff --git a/archive/src/.pi-map.index.md b/archive/src/.pi-map.index.md index 4efd58c..b87cea3 100644 --- a/archive/src/.pi-map.index.md +++ b/archive/src/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Insufficient information — no files provided in the directory listing to determine this package's role. ## parent index: archive/.pi-map.index.md map: archive/.pi-map.md diff --git a/archive/src/.pi-map.md b/archive/src/.pi-map.md index 8b78186..a0119d2 100644 --- a/archive/src/.pi-map.md +++ b/archive/src/.pi-map.md @@ -4,10 +4,10 @@ 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. +Insufficient information — no files provided in the directory listing to determine this package's role. ## files ## arch -Cannot be assessed due to missing file contents; please provide the file listing for analysis. +Unable to assess — empty directory or missing file contents for architectural analysis. ## tags - ## symbols diff --git a/archive/src/media_library_viewer/.pi-map.index.md b/archive/src/media_library_viewer/.pi-map.index.md index abb5fb1..7275619 100644 --- a/archive/src/media_library_viewer/.pi-map.index.md +++ b/archive/src/media_library_viewer/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Streamlit-based UI package for browsing and monitoring a Jellyfin media library with SSH remote file management capabilities. ## parent index: archive/src/.pi-map.index.md map: archive/src/.pi-map.md diff --git a/archive/src/media_library_viewer/.pi-map.md b/archive/src/media_library_viewer/.pi-map.md index 5c9a141..62bf8cc 100644 --- a/archive/src/media_library_viewer/.pi-map.md +++ b/archive/src/media_library_viewer/.pi-map.md @@ -4,7 +4,7 @@ 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. +Streamlit-based UI package for browsing and monitoring a Jellyfin media library with SSH remote file management capabilities. ## 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 @@ -12,7 +12,7 @@ Streamlit-based media library viewer that provides a unified dashboard for brows - 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. +Tab-based modular frontend using immutable dataclass configuration, environment-driven settings, cached data access, template-based remote job execution, and separated utility functions for metadata formatting. ## tags client, path, media, call:, jellyfin, call:get, ssh, cached ## symbols diff --git a/archive/src/media_library_viewer/clients/.pi-map.index.md b/archive/src/media_library_viewer/clients/.pi-map.index.md index 11b38cb..020fcb7 100644 --- a/archive/src/media_library_viewer/clients/.pi-map.index.md +++ b/archive/src/media_library_viewer/clients/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +External service and system integration clients providing media server API access and remote SSH-based system metrics collection for the media library viewer application. ## parent index: archive/src/media_library_viewer/.pi-map.index.md map: archive/src/media_library_viewer/.pi-map.md diff --git a/archive/src/media_library_viewer/clients/.pi-map.md b/archive/src/media_library_viewer/clients/.pi-map.md index 4476ca4..358d214 100644 --- a/archive/src/media_library_viewer/clients/.pi-map.md +++ b/archive/src/media_library_viewer/clients/.pi-map.md @@ -4,14 +4,14 @@ 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. +External service and system integration clients providing media server API access and remote SSH-based system metrics collection for the media library viewer application. ## 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. +Modular client-per-service pattern with plain Python return types for frontend agnosticism, wrapping HTTP APIs (Jellyfin/Emby) and SSH/Paramiko connections with POSIX shell compatibility enforcement. ## tags call:shlex.quote, resource, error, collector, call:ssh.run, raise:runtime, call:self.get, call:result.stdout.strip ## symbols diff --git a/archive/src/media_library_viewer/domain/.pi-map.index.md b/archive/src/media_library_viewer/domain/.pi-map.index.md index 06c017c..b2a84ec 100644 --- a/archive/src/media_library_viewer/domain/.pi-map.index.md +++ b/archive/src/media_library_viewer/domain/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Provides domain-level normalization logic that transforms inconsistent external Jellyfin API responses into stable, application-native data structures. ## parent index: archive/src/media_library_viewer/.pi-map.index.md map: archive/src/media_library_viewer/.pi-map.md diff --git a/archive/src/media_library_viewer/domain/.pi-map.md b/archive/src/media_library_viewer/domain/.pi-map.md index a39cd5c..1b08ac8 100644 --- a/archive/src/media_library_viewer/domain/.pi-map.md +++ b/archive/src/media_library_viewer/domain/.pi-map.md @@ -4,12 +4,12 @@ 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. +Provides domain-level normalization logic that transforms inconsistent external Jellyfin API responses into stable, application-native data structures. ## 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. +Functional transformation layer using dictionary flattening and field mapping to decouple external API data shapes from internal storage (SQLite) and presentation (frontend) concerns. ## tags media, call:str, date, added, item, call:item.get, streams, call:stream ## symbols diff --git a/archive/src/media_library_viewer/services/.pi-map.index.md b/archive/src/media_library_viewer/services/.pi-map.index.md index 792a13e..c48ae70 100644 --- a/archive/src/media_library_viewer/services/.pi-map.index.md +++ b/archive/src/media_library_viewer/services/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Application services layer that coordinates media library clients and domain logic into reusable, UI-agnostic operations like indexing and querying Jellyfin media metadata. ## parent index: archive/src/media_library_viewer/.pi-map.index.md map: archive/src/media_library_viewer/.pi-map.md diff --git a/archive/src/media_library_viewer/services/.pi-map.md b/archive/src/media_library_viewer/services/.pi-map.md index 2d0f493..72c8593 100644 --- a/archive/src/media_library_viewer/services/.pi-map.md +++ b/archive/src/media_library_viewer/services/.pi-map.md @@ -4,12 +4,12 @@ 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. +Application services layer that coordinates media library clients and domain logic into reusable, UI-agnostic operations like indexing and querying Jellyfin media metadata. ## 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. +Service-oriented architecture with SQLite persistence, providing filtering, sorting, and pagination capabilities abstracted away from UI concerns. ## tags media, call:conn.execute, index, call:self.connect, schema, call:int, init, status ## symbols diff --git a/archive/src/media_library_viewer/ui/.pi-map.index.md b/archive/src/media_library_viewer/ui/.pi-map.index.md index 208e34f..dd04f57 100644 --- a/archive/src/media_library_viewer/ui/.pi-map.index.md +++ b/archive/src/media_library_viewer/ui/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Streamlit-based presentation layer for browsing, monitoring, and diagnosing a Jellyfin media server via SSH and a local SQLite index. ## parent index: archive/src/media_library_viewer/.pi-map.index.md map: archive/src/media_library_viewer/.pi-map.md diff --git a/archive/src/media_library_viewer/ui/.pi-map.md b/archive/src/media_library_viewer/ui/.pi-map.md index b5db7c1..3774d56 100644 --- a/archive/src/media_library_viewer/ui/.pi-map.md +++ b/archive/src/media_library_viewer/ui/.pi-map.md @@ -4,7 +4,7 @@ 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. +Streamlit-based presentation layer for browsing, monitoring, and diagnosing a Jellyfin media server via SSH and a local SQLite index. ## 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 @@ -12,7 +12,7 @@ Streamlit UI rendering layer for the media library viewer application, providing - 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. +Modular page-by-page rendering pattern where each module is a self-contained Streamlit view, integrated through shared session state for cross-component synchronization. ## tags call:metric, call:grid, render, call:st.caption, col.button, browser, col.selectbox, media ## symbols diff --git a/archive/tests/.pi-map.index.md b/archive/tests/.pi-map.index.md index 757ee59..4acf36c 100644 --- a/archive/tests/.pi-map.index.md +++ b/archive/tests/.pi-map.index.md @@ -2,7 +2,7 @@ dir: archive/tests ## role -Legacy or archived test directory currently containing only a placeholder file with no active test code. +Empty placeholder directory retained for historical/archived test files that are no longer actively used. ## parent index: archive/.pi-map.index.md map: archive/.pi-map.md diff --git a/archive/tests/.pi-map.md b/archive/tests/.pi-map.md index 7e3e87e..2a891d8 100644 --- a/archive/tests/.pi-map.md +++ b/archive/tests/.pi-map.md @@ -4,11 +4,11 @@ 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. +Empty placeholder directory retained for historical/archived test files that are no longer actively used. ## 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. +No active code; contains only a `.gitkeep` placeholder file (with an unrelated description) to preserve the directory structure in version control. ## tags tmux, swaps, position, two, panes, within, window, windows ## symbols diff --git a/backend/.pi-map.index.md b/backend/.pi-map.index.md index 729a850..83d5e40 100644 --- a/backend/.pi-map.index.md +++ b/backend/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend ## role -FastAPI backend service providing Jellyfin media browsing, SSH file inspection, server monitoring, and JWT-protected API endpoints. +FastAPI backend service providing JWT-protected REST API endpoints for Jellyfin media browsing, SSH file inspection, and server monitoring. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/backend/.pi-map.md b/backend/.pi-map.md index d1d1ec0..0938e45 100644 --- a/backend/.pi-map.md +++ b/backend/.pi-map.md @@ -4,13 +4,13 @@ dir: backend index: backend/.pi-map.index.md ## role -FastAPI backend service providing Jellyfin media browsing, SSH file inspection, server monitoring, and JWT-protected API endpoints. +FastAPI backend service providing JWT-protected 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 for a FastAPI backend that provides Jellyfin media browsing, SSH file inspection, server monitoring, and JWT-protected access. | dep: FastAPI, uvicorn, pydantic-settings, Docker Compose - 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 REST API using FastAPI/uvicorn with JWT authentication, configured via pyproject.toml with linting and testing support. +Layered API architecture using FastAPI with Uvicorn ASGI server, containerized via Docker, configured through pyproject.toml with standardized linting and testing pipelines. ## tags uvicorn, fastapi, python, backend, pyproject, settings, docker, api ## symbols diff --git a/backend/src/.pi-map.index.md b/backend/src/.pi-map.index.md index 64f4109..88cd6f5 100644 --- a/backend/src/.pi-map.index.md +++ b/backend/src/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend/src ## role -Root source directory serving as the main entry point and organizational container for the backend application. +Core backend application source directory containing server-side business logic, API routes, models, and configuration. ## parent index: backend/.pi-map.index.md map: backend/.pi-map.md diff --git a/backend/src/.pi-map.md b/backend/src/.pi-map.md index f15fd38..16609bd 100644 --- a/backend/src/.pi-map.md +++ b/backend/src/.pi-map.md @@ -4,10 +4,10 @@ dir: backend/src index: backend/src/.pi-map.index.md ## role -Root source directory serving as the main entry point and organizational container for the backend application. +Core backend application source directory containing server-side business logic, API routes, models, and configuration. ## files ## arch -Standard layered architecture entry point, typically initializing the application, wiring up configurations, modules, routes, and services (e.g., MVC, modular monolith, or Clean Architecture). +Cannot be fully determined as no files are listed in the directory; likely follows standard Node.js/Python backend patterns (e.g., MVC, layered architecture) depending on framework used. ## tags - ## symbols diff --git a/backend/src/media_library_viewer_api/.pi-map.index.md b/backend/src/media_library_viewer_api/.pi-map.index.md index c867730..90b9ebf 100644 --- a/backend/src/media_library_viewer_api/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend/src/media_library_viewer_api ## role -FastAPI backend service that provides authenticated, observable APIs for viewing and managing media library data across Jellyfin, Jellyseerr, and remote SSH/local systems. +FastAPI backend service providing authenticated API endpoints for viewing and managing media libraries across Jellyfin/Jellyseerr with remote SSH job execution. ## parent index: backend/src/.pi-map.index.md map: backend/src/.pi-map.md diff --git a/backend/src/media_library_viewer_api/.pi-map.md b/backend/src/media_library_viewer_api/.pi-map.md index b4178ae..4ff646f 100644 --- a/backend/src/media_library_viewer_api/.pi-map.md +++ b/backend/src/media_library_viewer_api/.pi-map.md @@ -4,23 +4,23 @@ dir: backend/src/media_library_viewer_api index: backend/src/media_library_viewer_api/.pi-map.index.md ## role -FastAPI backend service that provides authenticated, observable APIs for viewing and managing media library data across Jellyfin, Jellyseerr, and remote SSH/local systems. +FastAPI backend service providing authenticated API endpoints for viewing and managing media libraries across Jellyfin/Jellyseerr with remote SSH job execution. ## 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 functions for resolving and caching service clients (Jellyfin, Jellyseerr, SSH/Local) and settings based on request query parameters. | 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:HTTPException, 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:HTTPException, 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:HTTPException | 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 +- dependencies.py | Provides FastAPI dependency injection functions for resolving and instantiating Jellyfin/Jellyseerr API clients and machine-specific SSH/Local command clients. | 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:HTTPException, 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:HTTPException, 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:HTTPException | dep: logging, functools, typing, fastapi, media_library_viewer_api.clients.jellyfin, 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 +- 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_service_data_harness, 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.service_data, FastAPI, media_library_viewer_api.services.backup_poller - 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 for cached service clients, Pydantic settings configuration, middleware-based OIDC/JWT/API-key authentication, Prometheus observability with structured logging, and template-based remote job execution. +Layered FastAPI architecture using dependency injection for client resolution, middleware-based OIDC/API-key authentication, pydantic-settings configuration, and Prometheus-based observability with structured logging. ## tags -call:, settings, call:get, request, get, client, call:str, id +call:, settings, call:get, request, get, call:str, id, client ## symbols - Settings - JobTemplate 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 index b8d13c9..058b49b 100644 --- a/backend/src/media_library_viewer_api/clients/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/clients/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Provides HTTP and command-line client wrappers for integrating with external media services (Jellyfin, Authentik, Jellyseerr, qBittorrent) and executing local/remote filesystem operations. ## parent index: backend/src/media_library_viewer_api/.pi-map.index.md map: backend/src/media_library_viewer_api/.pi-map.md @@ -10,15 +10,17 @@ map: backend/src/media_library_viewer_api/.pi-map.md - ## files - __init__.py +- authentik.py - jellyfin.py - jellyseerr.py - local.py +- qbittorrent.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 + read: __init__.py, authentik.py, jellyfin.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 index 363b14a..7ba3abf 100644 --- a/backend/src/media_library_viewer_api/clients/.pi-map.md +++ b/backend/src/media_library_viewer_api/clients/.pi-map.md @@ -4,28 +4,30 @@ 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. +Provides HTTP and command-line client wrappers for integrating with external media services (Jellyfin, Authentik, Jellyseerr, qBittorrent) and executing local/remote filesystem operations. ## files - __init__.py | Swaps the position of two tmux panes within a window or between windows | dep: tmux, sh +- authentik.py | Provides a client wrapper around the Authentik REST API for browsing and searching the user directory with pagination. | exp: class:AuthentikClient, method:__init__(self, base_url: str, api_token: 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, search, page, page_size) → dict[str, Any], call:self.get, call:isinstance, call:logger.warning, call:type, call:payload.get, call:int, call:pagination.get, call:logger.info, call:len | dep: logging, typing, requests - 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 +- qbittorrent.py | Provides a minimal read-only client for the qBittorrent Web API to fetch sync/maindata using authenticated requests. | exp: class:QbittorrentClient, method:__init__(self, base_url: str, username: str, password: str, timeout) → None, call:base_url.rstrip, call:self.base_url.endswith, call:requests.Session, raise:ValueError, method:_login(self) → None, call:self._session.post, call:resp.raise_for_status, call:resp.text.strip, call:logger.info, raise:RuntimeError, method:_get(self, path: str, **params: Any) → dict[str, Any], call:self._login, call:self._session.get, call:logger.debug, call:resp.raise_for_status, call:resp.json, method:maindata(self) → dict[str, Any], call:self._get | dep: logging, typing, requests - 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. +Adapter/gateway pattern where each client encapsulates external API or protocol communication behind a uniform interface, isolating transport-level concerns (REST, SSH, local shell) from business logic. ## tags -call:logger.info, call:logger.debug, call:self.get, call:shlex.quote, error, host, call:self.run, init +call:logger.info, error, call:logger.debug, client, call:self.get, init, call:shlex.quote, status ## symbols +- AuthentikClient - JellyfinClient - JellyseerrClient - CommandResult - LocalCommandClient +- QbittorrentClient - RemoteSSHClient - __init__ -- get -- users ## workflows - change clients behavior - read: __init__.py, jellyfin.py, jellyseerr.py + read: __init__.py, authentik.py, jellyfin.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 index 2d6ff23..20f8012 100644 --- a/backend/src/media_library_viewer_api/domain/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/domain/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend/src/media_library_viewer_api/domain ## role -Domain layer providing data normalization and transformation helpers for Jellyfin media data and dashboard summaries. +Provides domain logic for normalizing media API data and building dashboard summaries for the media library viewer application. ## parent index: backend/src/media_library_viewer_api/.pi-map.index.md map: backend/src/media_library_viewer_api/.pi-map.md diff --git a/backend/src/media_library_viewer_api/domain/.pi-map.md b/backend/src/media_library_viewer_api/domain/.pi-map.md index d68737f..93b6f7d 100644 --- a/backend/src/media_library_viewer_api/domain/.pi-map.md +++ b/backend/src/media_library_viewer_api/domain/.pi-map.md @@ -4,13 +4,13 @@ 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. +Provides domain logic for normalizing media API data and building dashboard summaries for the media library viewer application. ## 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. +Functional utility module pattern with pure helper functions that transform external API JSON into normalized domain objects. ## tags media, backup, call:item.get, call:str, date, added, dashboard, call:store.list ## symbols 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 index 23367c7..956b644 100644 --- a/backend/src/media_library_viewer_api/integrations/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/integrations/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend/src/media_library_viewer_api/integrations ## role -Provides a plugin-style integration framework for declaring and registering external service connections (e.g., Grafana, Jellyfin, Prometheus) with config schemas, secrets, and widget definitions for the media library viewer API. +Defines external service integrations (e.g., Jellyfin, Prometheus, Alertmanager) with configuration models, widget schemas, and data-fetching logic 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 @@ -11,12 +11,13 @@ map: backend/src/media_library_viewer_api/.pi-map.md ## files - __init__.py - alertmanager.py +- authentik.py +- backups.py - base.py -- grafana.py - jellyfin.py -- jellyseerr.py - nextcloud.py - prometheus.py +- qbittorrent.py - registry.py - ssh_tasks.py ## links @@ -24,6 +25,6 @@ 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 + read: __init__.py, alertmanager.py, authentik.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 index 978079d..4292c71 100644 --- a/backend/src/media_library_viewer_api/integrations/.pi-map.md +++ b/backend/src/media_library_viewer_api/integrations/.pi-map.md @@ -4,33 +4,34 @@ dir: backend/src/media_library_viewer_api/integrations index: backend/src/media_library_viewer_api/integrations/.pi-map.index.md ## role -Provides a plugin-style integration framework for declaring and registering external service connections (e.g., Grafana, Jellyfin, Prometheus) with config schemas, secrets, and widget definitions for the media library viewer API. +Defines external service integrations (e.g., Jellyfin, Prometheus, Alertmanager) with configuration models, widget schemas, and data-fetching logic for the media library viewer API. ## files - __init__.py | Defines a closed registry module for service integrations. - alertmanager.py | Defines the Alertmanager service integration configuration, widget definitions, and alert summarization logic for a media library viewer API. | 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 | Provides abstract base classes and dataclass definitions for declaring external service integrations with 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:_validate_service_base_url(value: Any) → str, call:isinstance, call:value.strip, call:text.lower, call:lowered.startswith, raise:ValueError, 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 integration configuration, including connection settings, API key secrets, and dashboard link widget support. | exp: class:GrafanaConfig, class:GrafanaLinkWidgetConfig | dep: media_library_viewer_api.integrations.base -- jellyfin.py | Defines the Jellyfin service configuration and activity widget for a media library viewer API integration. | exp: class:JellyfinConfig, class:JellyfinActivityWidgetConfig | dep: media_library_viewer_api.integrations.base -- jellyseerr.py | Defines the Jellyseerr service configuration and its service definition schema for integration as a request management companion to Jellyfin. | exp: class:JellyseerrConfig | dep: media_library_viewer_api.integrations.base +- authentik.py | Defines the service configuration model and definition for integrating Authentik as a user directory and identity provider. | exp: class:AuthentikConfig | dep: media_library_viewer_api.integrations.base +- backups.py | Defines a Backups service type with configuration and summary widget for monitoring backup jobs, run history, and alerting. | exp: class:BackupsConfig, class:BackupsSummaryWidgetConfig | dep: media_library_viewer_api.integrations.base +- base.py | Provides foundational base classes and dataclasses for defining external service integrations, including config validation and widget schema generation. | exp: class:ServiceConfigBase, class:WidgetConfigBase, class:SecretField, class:WidgetKind, class:ServiceDefinition, method:widget_kind(self, kind: str) → WidgetKind | None, func:_validate_service_base_url(value: Any) → str, call:isinstance, call:value.strip, call:text.lower, call:lowered.startswith, raise:ValueError, 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 +- jellyfin.py | Defines the Jellyfin media server service configuration, secret fields, and widget definitions for activity and now-playing sessions. | exp: class:JellyfinConfig, class:JellyfinActivityWidgetConfig, class:JellyfinNowPlayingWidgetConfig | 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 definition and configuration models for integrating Prometheus as a metrics data source with PromQL query widgets. | 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 +- prometheus.py | Defines the Prometheus service configuration and widget types (metric, chart, gauge, mean) for querying and visualizing PromQL data. | exp: class:PrometheusConfig, class:PrometheusMetricWidgetConfig, class:PrometheusChartWidgetConfig, class:PrometheusGaugeWidgetConfig, class:PrometheusMeanWidgetConfig | dep: media_library_viewer_api.integrations.base +- qbittorrent.py | Declares the qBittorrent service definition including config models, secret fields, and three widget kinds (totals, active, speed). | exp: class:QbittorrentConfig, class:QbittorrentWidgetConfig | dep: media_library_viewer_api.integrations.base +- registry.py | Maintains a closed registry of service definitions and provides lookup functions to query available services, their types, and widget kinds. | 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.authentik, media_library_viewer_api.integrations.backups, media_library_viewer_api.integrations.base, media_library_viewer_api.integrations.jellyfin, media_library_viewer_api.integrations.nextcloud, media_library_viewer_api.integrations.prometheus, media_library_viewer_api.integrations.qbittorrent, 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 -Registry pattern with abstract base classes and dataclass-driven configuration models; each integration is a self-contained module registered in a closed registry that supports lookup, enumeration, and declarative widget/kind definitions. +Plugin-style registry pattern with a shared base class hierarchy; each service module independently defines config dataclasses, widget types, and summarization logic, all registered in a central closed registry for discovery and lookup. ## tags -config, service, widget, integrations, base, media_library_viewer_api, definition, kind +config, widget, service, integrations, base, media_library_viewer_api, prometheus, definition ## symbols - AlertmanagerConfig - AlertmanagerAlertsWidgetConfig +- AuthentikConfig +- BackupsConfig +- BackupsSummaryWidgetConfig - ServiceConfigBase - WidgetConfigBase - SecretField -- WidgetKind -- ServiceDefinition -- GrafanaConfig ## workflows - change integrations behavior - read: __init__.py, alertmanager.py, base.py + read: __init__.py, alertmanager.py, authentik.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 index 269f1ed..2d96292 100644 --- a/backend/src/media_library_viewer_api/models/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/models/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Defines Pydantic data models (schemas) for API request/response validation and serialization across backup, dashboard, service, and widget domains. ## parent index: backend/src/media_library_viewer_api/.pi-map.index.md map: backend/src/media_library_viewer_api/.pi-map.md @@ -10,6 +10,7 @@ map: backend/src/media_library_viewer_api/.pi-map.md - ## files - backups.py +- dashboards.py - services.py - widgets.py ## links @@ -17,6 +18,6 @@ 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 + read: backups.py, dashboards.py, services.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 index 52aa1bb..cb9f568 100644 --- a/backend/src/media_library_viewer_api/models/.pi-map.md +++ b/backend/src/media_library_viewer_api/models/.pi-map.md @@ -4,26 +4,27 @@ 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. +Defines Pydantic data models (schemas) for API request/response validation and serialization across backup, dashboard, service, and widget domains. ## 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 +- dashboards.py | Defines Pydantic data models for creating, updating, and representing named dashboard records in an API. | exp: class:NamedDashboardInput, class:NamedDashboard | dep: 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 +- widgets.py | Defines Pydantic models for a dashboard widget system, including input/output schemas and validation to prevent credential leakage in widget configurations. | 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). +Pydantic-based model layer following a schema-first design pattern with built-in validators for domain-specific business rules and data integrity. ## tags -widget, backup, instance, response, info, call:isinstance, call:, service +widget, backup, instance, dashboard, pydantic, response, info, call:isinstance ## symbols - BackupReportRequest - BackupJobResponse - BackupRunResponse - BackupAlertResponse - BackupDashboardSummary +- NamedDashboardInput +- NamedDashboard - ServiceInstanceInput -- ServiceInstance -- SecretFieldInfo ## workflows - change models behavior - read: backups.py, services.py, widgets.py + read: backups.py, dashboards.py, services.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 index 9113f5a..aa25f57 100644 --- a/backend/src/media_library_viewer_api/routers/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/routers/.pi-map.index.md @@ -2,7 +2,7 @@ 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 (files, media, jobs, backups, dashboard, monitoring, services, settings, tasks, users, widgets). +FastAPI router package that defines all HTTP API endpoints for the media library viewer backend, organizing routes by domain (auth, backups, dashboards, files, jobs, media, monitoring, services, settings, tasks, widgets). ## parent index: backend/src/media_library_viewer_api/.pi-map.index.md map: backend/src/media_library_viewer_api/.pi-map.md @@ -10,8 +10,10 @@ map: backend/src/media_library_viewer_api/.pi-map.md - ## files - __init__.py +- authentik_users.py - backups.py - dashboard.py +- dashboards.py - files.py - jobs.py - media.py @@ -19,14 +21,12 @@ map: backend/src/media_library_viewer_api/.pi-map.md - 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 + read: __init__.py, authentik_users.py, backups.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 index 81af666..ac9a756 100644 --- a/backend/src/media_library_viewer_api/routers/.pi-map.md +++ b/backend/src/media_library_viewer_api/routers/.pi-map.md @@ -4,26 +4,27 @@ 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 (files, media, jobs, backups, dashboard, monitoring, services, settings, tasks, users, widgets). +FastAPI router package that defines all HTTP API endpoints for the media library viewer backend, organizing routes by domain (auth, backups, dashboards, files, jobs, media, monitoring, services, settings, tasks, 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 +- authentik_users.py | Provides a FastAPI router that proxies paginated user directory queries and email message enqueueing through an Authentik service client. | exp: class:MessageRequest, func:_build_client(service: ServiceRecord) → AuthentikClient, call:str(service.config.get("base_url") or "").rstrip, call:service.config.get, call:service.secrets.get, call:float, call:AuthentikClient, func:_empty(error: str) → dict[str, Any], func:get_authentik_users(service_id: str, search, page, page_size, store) → dict[str, Any], call:resolve_service_record, call:logger.info, call:_empty, call:_build_client, call:client.users, call:logger.exception, func:get_authentik_message_status(service_id: str, store, mail_queue) → dict[str, Any], call:resolve_service_record, call:mail_queue.status, func:post_authentik_message(service_id: str, body: MessageRequest, store, mail_queue) → dict[str, Any], call:resolve_service_record, call:r.strip, call:get_settings, call:validate_smtp_settings, call:mail_queue.enqueue, call:logger.info, call:len | dep: logging, typing, fastapi, pydantic, media_library_viewer_api.clients.authentik, media_library_viewer_api.config, media_library_viewer_api.dependencies, media_library_viewer_api.services.mail_queue, media_library_viewer_api.services.mailer, media_library_viewer_api.services.service_resolution, media_library_viewer_api.services.settings_store, media_library_viewer_api.widgets.sources +- backups.py | FastAPI router for receiving backup run reports, managing backup jobs/runs, and generating/acknowledging backup alerts. | exp: func:_resolve_backup_service_id(store: SettingsStore, explicit) → str, call:store.list_services, call:svc.get, func:_get_or_create_job(store: SettingsStore, report: BackupReportRequest, service_id) → 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, service_id, store, _auth) → BackupRunResponse, call:_resolve_backup_service_id, 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, service_id, store, _auth) → BackupRunResponse, call:_resolve_backup_service_id, 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 +- dashboards.py | Provides CRUD API endpoints for managing named dashboards via a FastAPI router. | exp: func:list_dashboards(store) → list[NamedDashboard], call:store.list_dashboards, call:NamedDashboard, func:get_dashboard_by_slug(slug: str, store) → NamedDashboard, call:store.get_dashboard_by_slug, call:NamedDashboard, raise:HTTPException, func:create_dashboard(body: NamedDashboardInput, store) → NamedDashboard, call:store.upsert_dashboard, call:body.model_dump, call:NamedDashboard, func:update_dashboard(dashboard_id: str, body: NamedDashboardInput, store) → NamedDashboard, call:store.get_dashboard, call:store.upsert_dashboard, call:body.model_dump, call:NamedDashboard, raise:HTTPException, func:delete_dashboard(dashboard_id: str, store) → dict[str, str], call:store.get_dashboard, call:store.delete_dashboard, raise:HTTPException | dep: fastapi, media_library_viewer_api.dependencies, media_library_viewer_api.models.dashboards, 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 +- media.py | FastAPI router providing endpoints to manage media index lifecycle operations including status checks, building (via subprocess workers), stopping, force-stopping, and querying the media library index. | 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, service_id) → list[str], call:str, func:_start_worker(index: MediaIndex, service_id) → 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(jellyfin_service_id, index) → dict[str, Any], call:_clean_stale_build_state, call:_pid_is_alive, call:logger.warning, call:logger.info, 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, jellyfin_service_id, 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 +- monitoring.py | FastAPI router providing monitoring observability endpoints that proxy and aggregate status, alerts, and scrape targets from Alertmanager and Prometheus. | exp: 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_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.service_resolution, 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 for managing machine definitions, SSH keys, SSH connection validation, 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 +- widgets.py | Provides a FastAPI REST API for managing dashboard widget instances and their references, including CRUD operations, data fetching, and live-link detachments. | exp: class:WidgetReferenceCreate, 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(service_id, scope, 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, func:list_references(dashboard_scope: str, store) → list[dict[str, Any]], call:store.list_widget_references, func:create_reference(body: WidgetReferenceCreate, store) → dict[str, Any], call:store.create_widget_reference, raise:HTTPException, func:delete_reference(reference_id: str, store) → dict[str, str], call:store.delete_widget_reference, func:update_reference(reference_id: str, sort_order: int, store) → dict[str, Any], call:store.update_widget_reference, raise:HTTPException, func:detach_reference(reference_id: str, store) → dict[str, Any], call:store.detach_widget_reference, call:WidgetInstance(**cloned).model_dump, raise:HTTPException | dep: logging, time, typing, fastapi, pydantic, 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 router-per-domain pattern where each file defines an isolated FastAPI APIRouter; routers are registered by the parent application and share common dependencies for SSH orchestration, database access, and service resolution. +Modular FastAPI APIRouter pattern where each domain module exports its own router instance; routers encapsulate endpoint definitions and delegate business logic to underlying service clients, SSH utilities, and subprocess workers. ## tags -call:, service, raise:httpexception, get, backup, media_library_viewer_api, ssh, call:logger.info +call:, raise:httpexception, service, backup, get, media_library_viewer_api, ssh, call:store.get ## symbols +- MessageRequest - RunJobRequest - MonitoringMachineInput - SSHKeyInput @@ -31,9 +32,8 @@ call:, service, raise:httpexception, get, backup, media_library_viewer_api, ssh, - ResetLocalDatabaseInput - TaskInput - RunTaskRequest -- _get_or_create_job ## workflows - change routers behavior - read: __init__.py, backups.py, dashboard.py + read: __init__.py, authentik_users.py, backups.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 index 813d12a..06fab22 100644 --- a/backend/src/media_library_viewer_api/services/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/services/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend/src/media_library_viewer_api/services ## role -Backend service layer providing business logic for media library management, backup monitoring, email notifications, SSH task execution, encryption, and persistent settings storage. +Backend service layer providing business logic for media indexing, backup monitoring, email delivery, secrets management, task execution, and persistent storage operations. ## parent index: backend/src/media_library_viewer_api/.pi-map.index.md map: backend/src/media_library_viewer_api/.pi-map.md @@ -19,7 +19,10 @@ map: backend/src/media_library_viewer_api/.pi-map.md - mailer_impl.py - media_index.py - media_index_impl.py +- qbittorrent_store.py - secrets.py +- service_data.py +- service_resolution.py - settings_store.py - targets.py - task_runner.py diff --git a/backend/src/media_library_viewer_api/services/.pi-map.md b/backend/src/media_library_viewer_api/services/.pi-map.md index 81587c8..e9b3bdf 100644 --- a/backend/src/media_library_viewer_api/services/.pi-map.md +++ b/backend/src/media_library_viewer_api/services/.pi-map.md @@ -4,7 +4,7 @@ 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, backup monitoring, email notifications, SSH task execution, encryption, and persistent settings storage. +Backend service layer providing business logic for media indexing, backup monitoring, email delivery, secrets management, task execution, and persistent storage operations. ## 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 @@ -15,15 +15,18 @@ Backend service layer providing business logic for media library management, bac - 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 +- media_index_impl.py | Provides a SQLite-backed media inventory service that builds, stores, and queries indexed media items from Jellyfin with filtering, sorting, pagination, and multi-instance support. | 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]], service_id) → 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, service_id) → 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, service_id) → 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, media_library_viewer_api.services.service_data, JellyfinClient +- qbittorrent_store.py | Provides a SQLite-backed time-series store for qBittorrent download/upload speed samples with automatic pruning of old entries. | exp: class:QbittorrentSampleStore, method:__init__(self, harness) → None, call:get_service_data_harness, method:append(self, service_id: str, ts: int, dl_speed: int, up_speed: int) → None, call:self._harness.connect, call:conn.execute, call:conn.commit, method:window(self, service_id: str, since_ts) → list[dict[str, Any]], call:self._harness.connect, call:conn.execute( "SELECT ts, dl_speed, up_speed FROM qbittorrent_speed_samples " "WHERE service_id = ? AND ts >= ? ORDER BY ts ASC", (service_id, since_ts), ).fetchall, call:conn.execute( "SELECT ts, dl_speed, up_speed FROM qbittorrent_speed_samples WHERE service_id = ? ORDER BY ts ASC", (service_id,), ).fetchall | dep: logging, typing, media_library_viewer_api.services.service_data - 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 (machines, SSH keys, tasks, services, backups, dashboard widgets) via a SQLite database with schema migrations and CRUD operations. | 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:list, func:get_settings_store() → SettingsStore, call:SettingsStore | dep: json, sqlite3, time, uuid, io, pathlib, typing, paramiko, media_library_viewer_api.models.widgets +- service_data.py | Manages the lifecycle (provisioning, migration, cascade-delete) of per-concern SQLite databases for service-owned persistent data via a singleton harness. | exp: class:StorageConcern, class:ServiceDataHarness, method:__init__(self, base_dir: Path | str) → None, call:Path, method:register(self, concern: StorageConcern) → None, method:db_path(self, concern_key: str) → Path, method:connect(self, concern_key: str) → sqlite3.Connection, call:self.db_path, call:path.parent.mkdir, call:sqlite3.connect, call:conn.execute, method:run_migrations(self) → None, call:self._concerns.values, call:self.db_path, call:path.parent.mkdir, call:sqlite3.connect, call:conn.execute, call:s.strip, call:migration_sql.split, call:str(exc).lower, call:logger.debug, call:conn.commit, call:conn.close, method:cascade_delete(self, service_id: str) → None, call:self._concerns.values, call:self.db_path, call:path.exists, call:sqlite3.connect, call:conn.execute(f"PRAGMA table_info({table})").fetchall, func:get_service_data_harness() → ServiceDataHarness, call:Path, call:os.environ.get, call:ServiceDataHarness, call:_HARNESS.register, call:_HARNESS.run_migrations, func:reset_service_data_harness() → None | dep: logging, os, sqlite3, dataclasses, pathlib, media_library_viewer_api.services.qbittorrent_store, media_library_viewer_api.services.media_index_impl, qbittorrent_store, media_index_impl +- service_resolution.py | Provides a shared helper function to resolve a service instance by ID or fall back to the first enabled instance of a given service type. | 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 | dep: media_library_viewer_api.services.settings_store, media_library_viewer_api.widgets.sources, media_library_viewer_api.services.settings_store.SettingsStore, media_library_viewer_api.widgets.sources.ServiceRecord, media_library_viewer_api.widgets.sources.build_service_record +- settings_store.py | Provides a SQLite-backed persistent settings store managing machine definitions, SSH keys, services, saved tasks, dashboard widgets, backup jobs, and related configuration for a media library viewer application. | 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, call:conn.execute("PRAGMA table_info(backup_jobs)").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, call:self._migrate_jellyseerr_into_jellyfin, method:_migrate_jellyseerr_into_jellyfin(self) → None, call:self.init_schema, call:self.connect, call:conn.execute( "SELECT * FROM services WHERE service_type = 'jellyseerr' ORDER BY name ASC" ).fetchall, call:self.list_services, call:json.loads, call:str(js_config.get("base_url", "")).strip, call:js_config.get, call:str(js_secrets.get("api_key", "")).strip, call:js_secrets.get, call:decrypt_value, call:logger.warning, call:len, call:str(jf["config"].get("jellyseerr_url", "")).strip, call:jf["config"].get, call:dict, call:self.upsert_service, call:target["secrets"].get, call:logger.info, call:conn.commit, 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, call:str( payload.get("service_id") if payload.get("service_id") is not None else (current or {}).get("service_id", "") or "" ).strip, 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, service_id, scope, all_widgets) → list[dict[str, Any]], call:self.init_schema, call:clauses.append, call:params.append, call:' AND '.join, call:self.connect, call:conn.execute( f"SELECT * FROM dashboard_widgets{where} ORDER BY sort_order ASC, created_at ASC", params, ).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:list_widget_references(self, dashboard_scope: str) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute( """ SELECT wr.id AS ref_id, wr.dashboard_scope, wr.widget_id, wr.sort_order, wr.created_at AS ref_created_at FROM widget_references wr WHERE wr.dashboard_scope = ? ORDER BY wr.sort_order ASC, wr.created_at ASC """, (dashboard_scope,), ).fetchall, call:self.get_widget, call:result.append, call:int, method:create_widget_reference(self, dashboard_scope: str, widget_id: str, sort_order) → dict[str, Any], call:self.init_schema, call:self.get_widget, call:uuid.uuid4, call:int, call:time.time, call:self.connect, call:conn.execute, raise:ValueError, method:delete_widget_reference(self, reference_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, method:update_widget_reference(self, reference_id: str, sort_order: int) → dict[str, Any], call:self.init_schema, call:self.connect, call:conn.execute( "SELECT * FROM widget_references WHERE id = ?", (reference_id,), ).fetchone, call:self.get_widget, call:int, raise:ValueError, method:detach_widget_reference(self, reference_id: str, dashboard_scope: str) → dict[str, Any], call:self.init_schema, call:self.connect, call:conn.execute( "SELECT widget_id FROM widget_references WHERE id = ?", (reference_id,), ).fetchone, call:self.get_widget, call:self.upsert_widget, call:source.get, call:self.delete_widget_reference, raise:ValueError, 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, call:get_service_data_harness().cascade_delete, call:logger.exception, 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, method:_unique_slug(self, slug: str, exclude_id) → str, call:self.init_schema, call:self.connect, call:conn.execute( "SELECT id FROM named_dashboards WHERE slug = ? AND id != ?", (slug, exclude_id or ""), ).fetchone, method:_row_to_dashboard(self, row: sqlite3.Row) → dict[str, Any], call:json.loads, method:list_dashboards(self) → list[dict[str, Any]], call:self.init_schema, call:self.connect, call:conn.execute( "SELECT * FROM named_dashboards ORDER BY sort_order ASC, label COLLATE NOCASE" ).fetchall, call:self._row_to_dashboard, method:get_dashboard(self, dashboard_id: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM named_dashboards WHERE id = ?", (dashboard_id,)).fetchone, call:self._row_to_dashboard, method:get_dashboard_by_slug(self, slug: str | None) → dict[str, Any] | None, call:self.init_schema, call:self.connect, call:conn.execute("SELECT * FROM named_dashboards WHERE slug = ?", (slug,)).fetchone, call:self._row_to_dashboard, method:upsert_dashboard(self, payload: dict[str, Any], dashboard_id) → dict[str, Any], call:self.init_schema, call:self.get_dashboard, call:str(payload.get("id") or dashboard_id or uuid.uuid4().hex[:12]).strip, call:payload.get, call:uuid.uuid4, call:str(payload.get("label") or (current or {}).get("label") or "Dashboard").strip, call:(current or {}).get, call:str(payload.get("slug") or "").strip, call:self._slugify, call:self._unique_slug, call:int, call:time.time, call:self.connect, call:conn.execute("SELECT created_at FROM named_dashboards WHERE id = ?", (dash_id,)).fetchone, call:json.dumps, method:delete_dashboard(self, dashboard_id: str) → None, call:self.init_schema, call:self.connect, call:conn.execute, func:_default_local_machine() → dict[str, Any], call:list, func:get_settings_store() → SettingsStore, call:SettingsStore | dep: json, logging, sqlite3, time, uuid, io, pathlib, typing, paramiko, 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 implementation-hiding re-export patterns, background worker threads for async tasks, SQLite-backed persistence, and SSH/SMTP integration abstractions. +Module-per-concern service layer pattern with implementation-behind-facade re-exports, background daemon threads for async work, SQLite-backed persistence per service, and singleton harnesses for database lifecycle management. ## tags -call:str, call:conn.execute, call:self.connect, call:self., schema, call:self.init, call:int, call: +call:conn.execute, call:str, call:self.connect, schema, call:self., call:self.init, call:int, call: ## symbols - BackupAlertPoller - QueuedEmailMessage 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 index 9f61fe3..c95338a 100644 --- a/backend/src/media_library_viewer_api/widgets/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/widgets/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Widget subsystem providing configurable dashboard widget definitions, source adapters, and data transformation helpers 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 @@ -11,12 +11,13 @@ map: backend/src/media_library_viewer_api/.pi-map.md ## files - __init__.py - builtin.py +- prometheus_range.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 + read: __init__.py, builtin.py, prometheus_range.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 index 66574e3..374634a 100644 --- a/backend/src/media_library_viewer_api/widgets/.pi-map.md +++ b/backend/src/media_library_viewer_api/widgets/.pi-map.md @@ -4,26 +4,27 @@ 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. +Widget subsystem providing configurable dashboard widget definitions, source adapters, and data transformation helpers for the media library viewer API. ## 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 +- prometheus_range.py | Provides shared helper functions for Prometheus range queries, including step-size derivation and matrix/series normalization for frontend chart rendering. | exp: func:step_for_window(window_seconds: int, target_points) → int, call:max, call:round, func:normalize_prometheus_matrix(result: list[dict[str, Any]]) → list[dict[str, Any]], call:entry.get, call:sorted, call:metric.items, call:str(k).startswith, call:" ".join, call:_safe_int, call:points.append, call:_safe_float, call:series.append, func:_safe_float(raw: Any) → float | None, call:float, func:_safe_int(ts: Any) → int | None, call:int, call:float | dep: typing +- sources.py | Provides widget source adapters that translate widget instances into dashboard data by fetching and transforming data from various services (Prometheus, Alertmanager, Jellyfin, qBittorrent, SSH tasks) or built-in sources (backups, static). | 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: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:self._fetch_chart, call:self._fetch_gauge, call:self._fetch_mean, call:self._instant_query, call:config.get, call:logger.exception, method:_range_query(self, base_url: str, timeout: int, promql: str, window: int) → dict[str, Any], call:step_for_window, call:int, call:time.time, call:asyncio.wait_for, call:asyncio.to_thread, call:response.raise_for_status, call:response.json, call:logger.exception, call:payload.get("data", {}).get, method:_instant_query(self, base_url: str, timeout: int, promql: str) → dict[str, Any], call:asyncio.wait_for, call:asyncio.to_thread, call:response.raise_for_status, call:response.json, call:logger.exception, call:payload.get, method:_fetch_chart(self, base_url: str, timeout: int, config: dict[str, Any]) → dict[str, Any], call:config.get, call:WINDOW_PRESETS.get, call:self._range_query, call:normalize_prometheus_matrix, method:_fetch_gauge(self, base_url: str, timeout: int, config: dict[str, Any]) → dict[str, Any], call:self._instant_query, call:config.get, call:raw["result"].get, call:len, call:float, method:_fetch_mean(self, base_url: str, timeout: int, config: dict[str, Any]) → dict[str, Any], call:config.get, call:WINDOW_PRESETS.get, call:self._range_query, call:len, call:result[0].get, call:nums.append, call:float, call:sum, 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:s.get("PlayState", {}).get, 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, class:QbittorrentWidgetSource, 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:QbittorrentClient, call:asyncio.wait_for, call:asyncio.to_thread, call:data.get, call:torrents.values, call:t.get, call:by_state.get, call:len, call:server_state.get, call:time.time, call:QbittorrentSampleStore, call:store.append, call:store.window, 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, time, dataclasses, typing, requests, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.clients.qbittorrent, media_library_viewer_api.domain.dashboard, media_library_viewer_api.integrations.alertmanager, media_library_viewer_api.services.qbittorrent_store, media_library_viewer_api.services.settings_store, media_library_viewer_api.services.task_runner, media_library_viewer_api.widgets.prometheus_range, media_library_viewer_api.services.secrets, JellyfinClient, QbittorrentClient, SettingsStore, summarize_alerts, run_saved_task, decrypt_secrets, normalize_prometheus_matrix ## 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. +Adapter pattern with modular source connectors per service, built-in widget registry/schema definitions, and shared utility functions for data normalization and frontend rendering. ## tags -widget, source, fetch, call:logger.exception, builtin, call:config.get, record, call:str +widget, fetch, call:logger.exception, source, call:config.get, call:int, call:self., builtin ## symbols - StaticConfig - ServiceRecord - WidgetSource - BackupsWidgetSource - StaticWidgetSource -- GrafanaWidgetSource - PrometheusWidgetSource - AlertmanagerWidgetSource +- JellyfinWidgetSource ## workflows - change widgets behavior - read: __init__.py, builtin.py, sources.py + read: __init__.py, builtin.py, prometheus_range.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 index 0d0b999..b4c8033 100644 --- a/backend/src/media_library_viewer_api/workers/.pi-map.index.md +++ b/backend/src/media_library_viewer_api/workers/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Provides background worker subprocesses for asynchronously building and updating media indexes from external servers. ## parent index: backend/src/media_library_viewer_api/.pi-map.index.md map: backend/src/media_library_viewer_api/.pi-map.md diff --git a/backend/src/media_library_viewer_api/workers/.pi-map.md b/backend/src/media_library_viewer_api/workers/.pi-map.md index d7a56f1..d4431b6 100644 --- a/backend/src/media_library_viewer_api/workers/.pi-map.md +++ b/backend/src/media_library_viewer_api/workers/.pi-map.md @@ -4,23 +4,23 @@ 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. +Provides background worker subprocesses for asynchronously building and updating media indexes from external servers. ## 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 +- media_index_worker.py | This file acts as a standalone subprocess worker that asynchronously builds and updates a media index from a Jellyfin server, allowing the main API to remain responsive. | 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:_resolve_jellyfin(service_id: str) → tuple[Any, str], call:get_settings_store, call:_service_record, call:str, call:service.get("config", {}).get, call:service.get("secrets", {}).get, call:int, call:JellyfinClient, call:client.users, call:client.libraries, call:next, call:u.get, call:logger.info, call:logger.warning, raise:RuntimeError, func:run_build(final_index_path: str | Path, staging_index_path: str | Path, service_id) → int, call:get_settings, call:configure_logging, call:logger.info, call:describe_settings, call:MediaIndex, call:os.getpid, call:time.perf_counter, call:Path, call:staging_path.unlink, call:_resolve_jellyfin, call:client.libraries, 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.logging_utils, media_library_viewer_api.services.media_index, media_library_viewer_api.clients.jellyfin, media_library_viewer_api.dependencies ## arch -Subprocess worker pattern with cooperative cancellation, designed to be spawned and monitored by a parent process for long-running media indexing tasks. +Standalone subprocess worker pattern that decouples long-running data synchronization tasks from the main API process. ## tags -call:, metadata, set, index, call:get, media, progress, worker +call:, metadata, set, index, jellyfin, settings, media, media_library_viewer_api ## symbols - _set_build_metadata - _cancel_requested - _start_state - _progress_callback +- _resolve_jellyfin - run_build - main - call:state.items -- call:index.set_metadata ## workflows - change workers behavior read: __init__.py, media_index_worker.py diff --git a/backend/tests/.pi-map.index.md b/backend/tests/.pi-map.index.md index 385a38a..96a07b1 100644 --- a/backend/tests/.pi-map.index.md +++ b/backend/tests/.pi-map.index.md @@ -2,7 +2,7 @@ dir: backend/tests ## role -Test suite providing unit and integration tests that validate API endpoints, configuration, external clients, utilities, and service logic across the backend. +Comprehensive test suite providing unit and integration tests that validate the backend's API endpoints, external service clients, data persistence, and utility functions of a media library management dashboard. ## parent index: backend/.pi-map.index.md map: backend/.pi-map.md @@ -11,8 +11,10 @@ map: backend/.pi-map.md ## files - __init__.py - test_api.py +- test_authentik_client.py - test_backups.py - test_config.py +- test_dashboards.py - test_domain_media.py - test_jellyseerr_client.py - test_jobs.py @@ -20,6 +22,9 @@ map: backend/.pi-map.md - test_media_index.py - test_observability.py - test_path_utils.py +- test_prometheus_range.py +- test_qbittorrent_client.py +- test_service_data.py - test_services.py - test_ssh_client.py - test_targets.py @@ -30,9 +35,9 @@ 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 + read: __init__.py, test_api.py, test_authentik_client.py - change tests CLI - read: test_jellyseerr_client.py, test_ssh_client.py + read: test_authentik_client.py, test_jellyseerr_client.py, test_qbittorrent_client.py - change tests config read: test_config.py ## dirty diff --git a/backend/tests/.pi-map.md b/backend/tests/.pi-map.md index a51d3a3..2cd2510 100644 --- a/backend/tests/.pi-map.md +++ b/backend/tests/.pi-map.md @@ -4,42 +4,47 @@ dir: backend/tests index: backend/tests/.pi-map.index.md ## role -Test suite providing unit and integration tests that validate API endpoints, configuration, external clients, utilities, and service logic across the backend. +Comprehensive test suite providing unit and integration tests that validate the backend's API endpoints, external service clients, data persistence, and utility functions of a media library management dashboard. ## 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 mock clients to validate API logic without real network 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, method:test_jellyfin_endpoints_return_503_when_not_configured(self, test_client), call:app.dependency_overrides.pop, call:test_client.get, call:response.json, call:MagicMock, 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_api.py | Integration tests for FastAPI endpoints using TestClient with mocked SSH and Jellyfin clients 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, method:test_jellyfin_endpoints_return_503_when_not_configured(self, test_client), call:app.dependency_overrides.pop, call:test_client.get, call:response.json, call:MagicMock, 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: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: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: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_ssh(), call:MagicMock, call:CommandResult, call:json.dumps, func:test_client(mock_jellyfin, 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, media_library_viewer_api (main, dependencies, routers, services, clients, widgets) +- test_authentik_client.py | Tests the AuthentikClient class and its associated directory/users API endpoint, covering URL normalization, pagination handling, and error scenarios. | exp: class:TestAuthentikClient, method:test_base_url_normalizes_trailing_slash(self) → None, call:AuthentikClient, method:test_base_url_strips_api_v3_suffix(self) → None, call:AuthentikClient, method:test_bearer_header_is_set(self) → None, call:AuthentikClient, method:test_empty_base_url_raises(self) → None, call:pytest.raises, call:AuthentikClient, method:test_empty_api_token_raises(self) → None, call:pytest.raises, call:AuthentikClient, class:TestAuthentikUsersEndpoint, method:test_not_configured_returns_empty_with_error(self, store: SettingsStore) → None, call:TestClient, call:client.get, call:response.json, func:_encryption_key(monkeypatch: pytest.MonkeyPatch) → None, call:monkeypatch.setenv, call:reset_encryption_key_cache, func:store(tmp_path: Path) → SettingsStore, call:SettingsStore, call:s.ensure_defaults, call:app.dependency_overrides.clear | dep: pathlib, unittest.mock, pytest, cryptography.fernet, fastapi.testclient, media_library_viewer_api.clients.authentik, media_library_viewer_api.dependencies, media_library_viewer_api.main, media_library_viewer_api.services.secrets, media_library_viewer_api.services.settings_store - 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_dashboards.py | Tests CRUD operations for named dashboards, including slug uniqueness and explicit slug handling. | exp: func:_client(tmp_path: Path) → TestClient, call:SettingsStore, call:TestClient, func:test_create_and_list_dashboards(tmp_path: Path), call:_client, call:client.post, call:resp.json, call:client.get("/api/dashboards").json, call:len, call:app.dependency_overrides.clear, func:test_update_dashboard(tmp_path: Path), call:_client, call:client.post("/api/dashboards", json={"label": "First"}).json, call:client.put( f"/api/dashboards/{created['id']}", json={"label": "Renamed", "payload": {"widgets": ["w1"]}}, ).json, call:app.dependency_overrides.clear, func:test_delete_dashboard(tmp_path: Path), call:_client, call:client.post("/api/dashboards", json={"label": "Temp"}).json, call:client.delete, call:client.get("/api/dashboards").json, call:app.dependency_overrides.clear, func:test_slug_collision_appends_suffix(tmp_path: Path), call:_client, call:client.post("/api/dashboards", json={"label": "Overview"}).json, call:app.dependency_overrides.clear, func:test_explicit_slug_respected(tmp_path: Path), call:_client, call:client.post( "/api/dashboards", json={"label": "My Dashboard", "slug": "custom-slug"}, ).json, call:app.dependency_overrides.clear, func:test_update_nonexistent_returns_404(tmp_path: Path), call:_client, call:client.put, call:app.dependency_overrides.clear | dep: pathlib, fastapi.testclient, media_library_viewer_api.dependencies, media_library_viewer_api.main, media_library_viewer_api.services.settings_store - 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_media_index.py | Unit tests for a SQLite-based media index service, validating query filtering/sorting/pagination, data replacement, metadata handling, and the media index build process. | 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, method:test_replace_scoped_by_service_id_preserves_other_services(self, index), call:index.replace_items, call:index.status, call:index.query, 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 service registry definitions, secret encryption/decryption, CRUD operations for service instances, and cascade delete behavior via the FastAPI test client. | 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_service_base_url_requires_http_schema(bad_url), call:get_service_definition, call:pytest.raises, call:model.model_validate, func:test_service_base_url_accepts_absolute_urls(service_type), call:get_service_definition, call:model.model_validate, 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, pydantic, 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 (internal modules) +- test_prometheus_range.py | Unit tests for Prometheus range-query helper functions including step calculation and matrix normalization. | exp: class:TestStepForWindow, method:test_floor_of_fifteen_seconds(self) → None, call:step_for_window, method:test_custom_target_points(self) → None, call:step_for_window, class:TestNormalizePrometheusMatrix, method:test_empty_matrix(self) → None, call:normalize_prometheus_matrix, method:test_drops_dunder_labels_and_joins(self) → None, call:normalize_prometheus_matrix, call:len, method:test_falls_back_to_value_when_no_labels(self) → None, call:normalize_prometheus_matrix, method:test_dedup_collisions_with_suffix(self) → None, call:normalize_prometheus_matrix, method:test_non_numeric_sentinels_become_none(self) → None, call:normalize_prometheus_matrix, method:test_malformed_values_are_ignored_not_raised(self) → None, call:normalize_prometheus_matrix | dep: pytest, media_library_viewer_api.widgets.prometheus_range +- test_qbittorrent_client.py | Unit tests for the QbittorrentClient, verifying URL handling, login flow, cookie reuse, 403 re-login behavior, and data retrieval. | exp: class:QbittorrentClientTests, method:setUp(self) → None, call:QbittorrentClient, call:MagicMock, method:_login_response(self, text) → MagicMock, call:MagicMock, method:_get_response(self, json_data: dict, status_code) → MagicMock, call:MagicMock, method:test_base_url_appends_api_v2(self) → None, call:QbittorrentClient, call:self.assertEqual, method:test_base_url_keeps_existing_api_v2(self) → None, call:QbittorrentClient, call:self.assertEqual, method:test_base_url_strips_trailing_slash(self) → None, call:QbittorrentClient, call:self.assertEqual, method:test_empty_base_url_raises(self) → None, call:self.assertRaises, call:QbittorrentClient, method:test_empty_username_raises(self) → None, call:self.assertRaises, call:QbittorrentClient, method:test_login_posts_credentials(self) → None, call:self._login_response, call:self.client._login, call:self.session.post.assert_called_once, call:self.assertIn, call:self.assertEqual, call:self.assertTrue, method:test_login_failure_raises_runtime_error(self) → None, call:self._login_response, call:self.assertRaises, call:self.client._login, method:test_get_auto_logs_in_on_first_call(self) → None, call:self._login_response, call:self._get_response, call:self.client._get, call:self.session.post.assert_called_once, call:self.assertEqual, method:test_cookie_reuse_does_not_re_login(self) → None, call:self._get_response, call:self.client._get, call:self.session.post.assert_not_called, method:test_403_triggers_re_login(self) → None, call:MagicMock, call:self._get_response, call:self._login_response, call:self.client._get, call:self.assertEqual, call:self.session.post.assert_called_once, method:test_maindata_returns_full_payload(self) → None, call:self._get_response, call:self.client.maindata, call:self.assertEqual, call:len | dep: unittest, unittest.mock, requests, media_library_viewer_api.clients.qbittorrent +- test_service_data.py | Tests for ServiceDataHarness database lifecycle management (migrations, cascade delete) and QbittorrentSampleStore operations (append, window, prune). | exp: class:TestServiceDataHarnessMigrations, method:test_run_migrations_creates_tables(self, tmp_path), call:ServiceDataHarness, call:harness.register, call:harness.run_migrations, call:harness.connect, call:conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall, method:test_migrations_are_idempotent(self, tmp_path), call:ServiceDataHarness, call:harness.register, call:harness.run_migrations, method:test_alter_table_idempotency(self, tmp_path), call:StorageConcern, call:ServiceDataHarness, call:harness.register, call:harness.run_migrations, call:harness.connect, call:conn.execute("PRAGMA table_info(items)").fetchall, class:TestServiceDataHarnessCascadeDelete, method:test_cascade_delete_removes_only_matching_service(self, tmp_path), call:ServiceDataHarness, call:harness.register, call:harness.run_migrations, call:harness.connect, call:conn.commit, call:harness.cascade_delete, call:conn.execute("SELECT service_id, value FROM test_items ORDER BY id").fetchall, call:len, method:test_cascade_delete_skips_missing_concern_db(self, tmp_path), call:ServiceDataHarness, call:harness.register, call:harness.cascade_delete, class:TestQbittorrentSampleStore, method:test_append_and_window(self, store), call:store.append, call:store.window, call:len, method:test_window_with_since_ts(self, store), call:store.append, call:store.window, call:len, method:test_prune_enforces_max_samples(self, store), call:range, call:store.append, call:store.window, call:len, method:test_two_services_do_not_cross_contaminate(self, store), call:store.append, call:store.window, call:len | dep: pytest, media_library_viewer_api.services.qbittorrent_store, media_library_viewer_api.services.service_data +- test_services.py | Tests the service registry, including definitions, encryption/decryption, CRUD operations, and cascade deletes. | 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_eight_service_types(), call:set, func:test_jellyseerr_absorbed_into_jellyfin(), call:get_service_definition, func:test_backups_service_definition(), call:get_service_definition, func:test_authentik_service_definition(), call:get_service_definition, 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, call:set, func:_prometheus_payload(), call:payload.update, func:test_create_and_list_service(client), call:client.post, call:_prometheus_payload, call:response.json, call:client.get, call:len, func:test_list_instances_filters_by_type(client), call:client.post, call:_prometheus_payload, call:client.get, call:len, call:response.json, func:test_update_service_preserves_unsent_secrets(client), call:client.post("/api/services/instances", json=_prometheus_payload()).json, call:_prometheus_payload, call:client.put( f"/api/services/instances/{created['id']}", json={ "service_type": "prometheus", "name": "Renamed Prometheus", "config": {"base_url": "https://prometheus.example.com", "timeout_seconds": 10}, }, ).json, func:test_update_service_can_clear_secret(client), call:client.post("/api/services/instances", json=_prometheus_payload()).json, call:_prometheus_payload, call:client.put( f"/api/services/instances/{created['id']}", json={ "service_type": "prometheus", "name": "Production Prometheus", "config": {"base_url": "https://prometheus.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_service_base_url_requires_http_schema(bad_url), call:get_service_definition, call:pytest.raises, call:model.model_validate, func:test_service_base_url_accepts_absolute_urls(service_type), call:get_service_definition, call:model.model_validate, 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:_prometheus_payload, func:test_update_id_mismatch_returns_400(client), call:client.post("/api/services/instances", json=_prometheus_payload()).json, call:_prometheus_payload, call:client.put, func:test_delete_service(client), call:client.post("/api/services/instances", json=_prometheus_payload()).json, call:_prometheus_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_cascade_delete_removes_harness_data_across_concerns(tmp_path, monkeypatch), call:monkeypatch.setenv, call:str, call:reset_service_data_harness, call:get_service_data_harness, call:SettingsStore, call:store.ensure_defaults, call:store.upsert_service, call:QbittorrentSampleStore, call:sample_store.append, call:len, call:sample_store.window, call:store.delete_service, call:MediaIndex, call:harness.db_path, call:index.init_schema, call:index.replace_items, call:index.query, 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, func:test_jellyseerr_migrates_into_single_jellyfin(tmp_path), call:SettingsStore, call:store.ensure_defaults, call:store.upsert_service, call:store.list_services, call:store.get_service, func:test_jellyseerr_dropped_when_no_jellyfin(tmp_path), call:SettingsStore, call:store.ensure_defaults, call:store.upsert_service, call:store.list_services, func:test_jellyseerr_migration_is_idempotent(tmp_path), call:SettingsStore, call:store.ensure_defaults, call:store.upsert_service, call:store.list_services | dep: types, unittest.mock, pytest, cryptography.fernet, fastapi.testclient, pydantic, 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.services.media_index_impl, media_library_viewer_api.services.qbittorrent_store, media_library_viewer_api.services.service_data, media_library_viewer_api (dependencies, integrations.registry, main, services.secrets, services.settings_store, services.media_index_impl, services.qbittorrent_store, services.service_data) - 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 +- test_widgets.py | Tests the CRUD operations and data-fetching logic for a dashboard widget system, covering both standalone built-in widgets 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_prometheus_service(client), call:config.update, call:client.post( "/api/services/instances", json={"service_type": "prometheus", "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_widget_filtering_by_service_id_and_scope(client), call:_make_prometheus_service, call:client.post, call:client.get("/api/widgets/instances").json, call:len, call:client.get(f"/api/widgets/instances?service_id={service['id']}").json, call:client.get("/api/widgets/instances?scope=dashboard").json, call:client.get("/api/widgets/instances?scope=service").json, 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_prometheus_service, call:client.post, call:response.json, func:test_service_bound_widget_unknown_kind_rejected(client), call:_make_prometheus_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_prometheus_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_widget_service_not_found(client), call:_make_prometheus_service, call:client.post( "/api/widgets/instances", json={ "service_id": service["id"], "widget_kind": "metric", "title": "x", "config": {"promql": "up"}, }, ).json, call:client.delete, call:client.get("/api/widgets/instances").json, func:test_fetch_widget_service_disabled(client), call:_make_prometheus_service, call:client.post( "/api/widgets/instances", json={ "service_id": service["id"], "widget_kind": "metric", "title": "x", "config": {"promql": "up"}, }, ).json, call:client.put, call:client.get, call:response.json, func:test_fetch_widget_not_found(client), call:client.get, 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, func:test_jellyfin_definition_has_now_playing_widget(), call:get_service_definition, func:test_prometheus_chart_adapter_runs_range_query(), call:PrometheusWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, call:call.args[0].endswith, call:{"start", "end", "step"}.issubset, func:test_prometheus_chart_adapter_requires_promql(), call:PrometheusWidgetSource, call:ServiceRecord, call:adapter.fetch, func:test_prometheus_chart_adapter_degrades_on_http_error(), call:PrometheusWidgetSource, call:ServiceRecord, call:patch, call:req_mod.ConnectionError, call:adapter.fetch, call:result["error"].lower, func:test_jellyfin_now_playing_filters_active_sessions(), call:JellyfinWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, call:len, func:test_jellyfin_activity_shows_all_sessions(), call:JellyfinWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, call:len, func:widget_ref_client(monkeypatch), call:monkeypatch.setenv, call:Fernet.generate_key().decode, call:reset_encryption_key_cache, call:SettingsStore, call:str, call:Path, call:tempfile.mkdtemp, call:store.ensure_defaults, call:TestClient, call:app.dependency_overrides.pop, func:get_store_override(), func:test_widget_reference_lifecycle(widget_ref_client), call:store.upsert_service, call:store.list_services, call:store.upsert_widget, call:client.post, call:resp.json, call:client.get, call:len, call:client.delete, call:store.get_widget, func:test_widget_reference_detach(widget_ref_client), call:store.upsert_service, call:store.list_services, call:store.upsert_widget, call:client.post, call:resp.json, call:client.get("/api/widgets/references", params={"dashboard_scope": "main"}).json, call:len, call:store.get_widget, func:test_widget_reference_update_sort_order(widget_ref_client), call:store.upsert_widget, call:client.post, call:resp.json, call:client.put, call:store.get_widget, func:test_prometheus_gauge_adapter_returns_scalar(), call:PrometheusWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, call:call.args[0].endswith, func:test_prometheus_gauge_adapter_rejects_multi_series(), call:PrometheusWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, call:result["error"].lower, func:test_prometheus_gauge_adapter_requires_promql(), call:PrometheusWidgetSource, call:ServiceRecord, call:adapter.fetch, func:test_prometheus_mean_adapter_computes_average(), call:PrometheusWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, func:test_prometheus_mean_adapter_rejects_multi_series(), call:PrometheusWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, call:result["error"].lower, func:test_prometheus_mean_adapter_skips_nan_values(), call:PrometheusWidgetSource, call:ServiceRecord, call:SimpleNamespace, call:patch, call:adapter.fetch, func:test_prometheus_mean_adapter_requires_promql(), call:PrometheusWidgetSource, call:ServiceRecord, call:adapter.fetch, func:_fake_qbit_maindata(), func:test_qbittorrent_totals_counts_all_torrents(), call:QbittorrentWidgetSource, call:ServiceRecord, call:patch, call:_fake_qbit_maindata, call:adapter.fetch, func:test_qbittorrent_active_filters_dl_ul_only(), call:QbittorrentWidgetSource, call:ServiceRecord, call:patch, call:_fake_qbit_maindata, call:adapter.fetch, call:len, func:test_qbittorrent_speed_appends_and_returns_series(tmp_path), call:ServiceDataHarness, call:str, call:harness.register, call:harness.run_migrations, call:QbittorrentWidgetSource, call:ServiceRecord, call:patch, call:_fake_qbit_maindata, call:QbittorrentSampleStore, call:adapter.fetch, call:len, func:test_qbittorrent_adapter_missing_service(), call:QbittorrentWidgetSource, call:adapter.fetch, func:test_qbittorrent_adapter_missing_credentials(), call:QbittorrentWidgetSource, call:ServiceRecord, call:adapter.fetch, func:test_qbittorrent_adapter_timeout(), call:QbittorrentWidgetSource, call:ServiceRecord, call:patch, call:_asyncio.sleep, call:(_ for _ in ()).throw, call:TimeoutError, call:adapter.fetch, func:_slow(), call:_asyncio.sleep | 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, media_library_viewer_api.integrations.registry, media_library_viewer_api.services.secrets, tempfile, pathlib, media_library_viewer_api.services.qbittorrent_store, media_library_viewer_api.services.service_data, media_library_viewer_api (main, dependencies, services, widgets) ## arch -Layered testing approach using isolated SQLite instances, mock clients, and FastAPI test client; organized as per-module unit tests plus integration tests for endpoints and services. +Layered testing approach using pytest conventions, with TestClient for FastAPI endpoint integration tests, mocked external clients (SSH, Jellyfin, Authentik, qBittorrent, Jellyseerr) for isolated API-layer testing, and direct unit tests for Pydantic configs, SQLite database operations, Prometheus metrics, and path/formatting utilities. ## tags -test, service, call:test, call:response.json, media, path, client.get, call: +test, service, widget, call:test, media, prometheus, call:response.json, client ## symbols - TestHealth - TestDashboard - TestSettingsReset -- TestUsers - TestFiles - TestMediaIndexApi - TestJobs - TestMonitoring +- TestResolveServiceRecord ## workflows - change tests behavior - read: __init__.py, test_api.py, test_backups.py + read: __init__.py, test_api.py, test_authentik_client.py - change tests CLI - read: test_jellyseerr_client.py, test_ssh_client.py + read: test_authentik_client.py, test_jellyseerr_client.py, test_qbittorrent_client.py - change tests config read: test_config.py ## dirty diff --git a/docs/.pi-map.index.md b/docs/.pi-map.index.md index 9aab6bc..664c1a0 100644 --- a/docs/.pi-map.index.md +++ b/docs/.pi-map.index.md @@ -2,7 +2,7 @@ dir: docs ## role -Documentation directory containing architecture, planning, and operational reference materials for the project. +Documentation package providing architecture decisions, requirements, migration plans, and operational runbooks for the Manage application and its observability stack. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/docs/.pi-map.md b/docs/.pi-map.md index dd687a6..83bb6dd 100644 --- a/docs/.pi-map.md +++ b/docs/.pi-map.md @@ -4,16 +4,16 @@ dir: docs index: docs/.pi-map.index.md ## role -Documentation directory containing architecture, planning, and operational reference materials for the project. +Documentation package providing architecture decisions, requirements, migration plans, and operational runbooks for the Manage 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 | This file is a living requirements and decision log detailing the product goals, architecture, and feature specifications for a web application that manages a remote Jellyfin media library and inspects server files over SSH. +- REQUIREMENTS.md | A living requirements and decision log documenting product goals, architecture choices, and detailed functional specifications for "Manage," a web application for browsing Jellyfin media libraries and inspecting remote media files over SSH. | dep: React, TypeScript, shadcn/ui, Tailwind CSS, TanStack Table, Vitest, Jellyfin API, Prometheus, Grafana, Alertmanager, SQLite - 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 -Flat collection of standalone Markdown documents covering requirements tracking, migration planning, and observability/operational runbooks. +Flat collection of Markdown-based living design documents and runbooks serving as the project's single source of truth for planning, requirements, and operations. ## tags -design, react, observability, architecture, migration, plan, requirements, runbooks +react, design, observability, architecture, migration, plan, requirements, prometheus ## symbols - ## workflows diff --git a/docs/superpowers/.pi-map.index.md b/docs/superpowers/.pi-map.index.md index b9dd802..1194572 100644 --- a/docs/superpowers/.pi-map.index.md +++ b/docs/superpowers/.pi-map.index.md @@ -2,7 +2,7 @@ dir: docs/superpowers ## role -Documentation directory for advanced features, plugins, or capabilities (currently empty). +Documentation directory for the project's "superpowers" feature set, concepts, or capabilities. ## parent index: docs/.pi-map.index.md map: docs/.pi-map.md diff --git a/docs/superpowers/.pi-map.md b/docs/superpowers/.pi-map.md index 00be2b7..a5bc7f0 100644 --- a/docs/superpowers/.pi-map.md +++ b/docs/superpowers/.pi-map.md @@ -4,10 +4,10 @@ dir: docs/superpowers index: docs/superpowers/.pi-map.index.md ## role -Documentation directory for advanced features, plugins, or capabilities (currently empty). +Documentation directory for the project's "superpowers" feature set, concepts, or capabilities. ## files ## arch -Flat file structure intended for Markdown or supplementary documentation resources. +Flat file-based documentation structure with no code files present. ## tags - ## symbols diff --git a/docs/superpowers/plans/.pi-map.index.md b/docs/superpowers/plans/.pi-map.index.md index 4917ed4..1888a87 100644 --- a/docs/superpowers/plans/.pi-map.index.md +++ b/docs/superpowers/plans/.pi-map.index.md @@ -2,7 +2,7 @@ dir: docs/superpowers/plans ## role -Documentation directory containing detailed implementation plans for specific project features or standalone systems. +Documentation directory containing implementation plans for major system features and infrastructure improvements. ## parent index: docs/superpowers/.pi-map.index.md map: docs/superpowers/.pi-map.md diff --git a/docs/superpowers/plans/.pi-map.md b/docs/superpowers/plans/.pi-map.md index 2541fd7..d792df7 100644 --- a/docs/superpowers/plans/.pi-map.md +++ b/docs/superpowers/plans/.pi-map.md @@ -4,11 +4,11 @@ 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. +Documentation directory containing implementation plans for major system features and infrastructure improvements. ## 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. +Date-prefixed Markdown files following a structured planning format with technical requirements, architecture decisions, and implementation steps. ## tags backup, monitoring, fastapi, sqlite, react, 2026, 05, 11 ## symbols diff --git a/docs/superpowers/specs/.pi-map.index.md b/docs/superpowers/specs/.pi-map.index.md index e5f94ce..19936db 100644 --- a/docs/superpowers/specs/.pi-map.index.md +++ b/docs/superpowers/specs/.pi-map.index.md @@ -2,7 +2,7 @@ dir: docs/superpowers/specs ## role -Contains design specification documents for major features and system modules. +Documentation directory containing design specifications for system features ## parent index: docs/superpowers/.pi-map.index.md map: docs/superpowers/.pi-map.md diff --git a/docs/superpowers/specs/.pi-map.md b/docs/superpowers/specs/.pi-map.md index 9bf9c44..31def34 100644 --- a/docs/superpowers/specs/.pi-map.md +++ b/docs/superpowers/specs/.pi-map.md @@ -4,11 +4,11 @@ dir: docs/superpowers/specs index: docs/superpowers/specs/.pi-map.index.md ## role -Contains design specification documents for major features and system modules. +Documentation directory containing design specifications for system features ## files - 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 files following a specification-driven development pattern, each documenting complete system designs including storage, APIs, alerting, and frontend integration. +Single-file design documents using structured Markdown with architectural decisions, data models, and component descriptions ## tags design, backup, monitoring, sqlite, react, 2026, 05, 11 ## symbols diff --git a/frontend/.pi-map.index.md b/frontend/.pi-map.index.md index ad7e054..d3913ce 100644 --- a/frontend/.pi-map.index.md +++ b/frontend/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend ## role -Frontend SPA for the "Manage" application, built with React, Vite, and TypeScript, providing the user interface with OIDC authentication and API integration. +Frontend single-page application built with React, Vite, and TypeScript that serves as the "Manage" UI, styled with Tailwind CSS and shadcn/ui components. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/frontend/.pi-map.md b/frontend/.pi-map.md index 722b3b4..26ded06 100644 --- a/frontend/.pi-map.md +++ b/frontend/.pi-map.md @@ -4,7 +4,7 @@ dir: frontend index: frontend/.pi-map.index.md ## role -Frontend SPA for the "Manage" application, built with React, Vite, and TypeScript, providing the user interface with OIDC authentication and API integration. +Frontend single-page application built with React, Vite, and TypeScript that serves as the "Manage" UI, styled with Tailwind CSS and shadcn/ui components. ## 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 @@ -13,8 +13,8 @@ Frontend SPA for the "Manage" application, built with React, Vite, and TypeScrip - 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 +- package-lock.json | Locks exact dependency versions and integrity hashes for a React/Vite frontend project to ensure reproducible installs. | dep: react, react-dom, @tanstack/react-query, @tanstack/react-table, recharts, radix-ui, shadcn, oidc-client-ts, vite, vitest, tailwindcss, typescript +- package.json | Defines the package configuration, dependencies, and scripts for a React frontend application. | dep: react, react-dom, @tanstack/react-query, @tanstack/react-table, react-router-dom, oidc-client-ts, react-oidc-context, radix-ui, shadcn, tailwindcss, recharts, vite, vitest, typescript - 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 @@ -23,9 +23,9 @@ Frontend SPA for the "Manage" application, built with React, Vite, and TypeScrip - 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 -Component-based React architecture styled with Tailwind CSS/shadcn/ui, bundled via Vite, containerized through multi-stage Docker builds (Nginx for production, Node dev server for development), with Vitest for testing and ESLint for code quality. +Component-based SPA architecture using Vite for bundling, Nginx reverse proxy for production serving with API forwarding, PostCSS/Tailwind for styling, Vitest for testing, and Docker multi-stage builds for deployment. ## tags -react, @babel, vite, helper, typescript, project, css, eslint +react, vite, typescript, frontend, project, configuration, @tanstack, path ## symbols - ## workflows diff --git a/frontend/public/.pi-map.index.md b/frontend/public/.pi-map.index.md index 6b78e89..6893d79 100644 --- a/frontend/public/.pi-map.index.md +++ b/frontend/public/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/public ## role -Provides static public assets for the frontend web application, including brand identity graphics and reusable UI icons. +Provides static public assets for the frontend web application, including browser identity (favicon) and reusable UI iconography. ## parent index: frontend/.pi-map.index.md map: frontend/.pi-map.md diff --git a/frontend/public/.pi-map.md b/frontend/public/.pi-map.md index c6ddd28..6555b8c 100644 --- a/frontend/public/.pi-map.md +++ b/frontend/public/.pi-map.md @@ -4,12 +4,12 @@ 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. +Provides static public assets for the frontend web application, including browser identity (favicon) and reusable UI iconography. ## 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. +Flat static file directory following web application conventions where public assets are served directly at the root URL path without processing. ## tags favicon, defines, svg, icons, browser, graphic, featuring, stylized ## symbols diff --git a/frontend/src/.pi-map.index.md b/frontend/src/.pi-map.index.md index 71472be..53385f4 100644 --- a/frontend/src/.pi-map.index.md +++ b/frontend/src/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src ## role -Frontend React application providing an admin dashboard UI with OIDC authentication, dark mode, and user activity management for a Jellyfin media server. +Root-level frontend source directory providing the application entry point, routing, authentication, global styling, and shared state/type definitions for user management. ## parent index: frontend/.pi-map.index.md map: frontend/.pi-map.md diff --git a/frontend/src/.pi-map.md b/frontend/src/.pi-map.md index 1077e6c..fa3da38 100644 --- a/frontend/src/.pi-map.md +++ b/frontend/src/.pi-map.md @@ -4,21 +4,21 @@ dir: frontend/src index: frontend/src/.pi-map.index.md ## role -Frontend React application providing an admin dashboard UI with OIDC authentication, dark mode, and user activity management for a Jellyfin media server. +Root-level frontend source directory providing the application entry point, routing, authentication, global styling, and shared state/type definitions for user management. ## 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/* +- App.tsx | This file serves as the root component for a React application, setting up routing, authentication, layout (sidebar, top bar), and dark mode state. | exp: func:App() | dep: react-router-dom, @tanstack/react-query, react, lucide-react, react-oidc-context, ./pages/Dashboard, ./pages/NamedDashboardPage, ./pages/Settings, ./pages/ServicePage, ./pages/ServiceTypePage, ./pages/ServicesPage, ./auth, ./api/client, ./version, ./hooks/usePersistentState, ./hooks/useIsMobile, ./hooks/useServices, ./hooks/useDashboards, ./integrations/navEntries, @/components/ui/button, @/components/ui/tooltip, @/components/ui/sheet, @/components/ui/*, ./hooks/*, ./pages/* - 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) +- index.css | Defines global theme variables, dark mode colors, and base styles using Tailwind CSS v4 with semantic design tokens. | dep: Tailwind CSS, 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. +- 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 -Component-based React SPA architecture using Tailwind CSS v4 theming, TypeScript type definitions, and utility modules for authentication and user state transformation. +React SPA with OIDC authentication, TypeScript-first type declarations paired with plain JS implementations, Tailwind CSS v4 theming with semantic design tokens, and declarative data-transform modules following a functional pattern. ## tags -user, version, state, react, pages, oidc, activity, drawer +user, activity, state, version, react, users, drawer, model ## symbols - App - isOidcConfigured diff --git a/frontend/src/api/.pi-map.index.md b/frontend/src/api/.pi-map.index.md index c60fc60..5d2ac12 100644 --- a/frontend/src/api/.pi-map.index.md +++ b/frontend/src/api/.pi-map.index.md @@ -2,23 +2,26 @@ dir: frontend/src/api ## role -Frontend API client layer that centralizes all HTTP communication with the backend REST API. +Frontend API client layer that centralizes all HTTP communication with backend services using typed, token-authenticated request functions. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md ## children - ## files +- authentik.ts - backups.ts - client.ts +- dashboards.ts - services.ts +- shared.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 + read: authentik.ts, backups.ts, client.ts - change api CLI read: client.ts ## dirty diff --git a/frontend/src/api/.pi-map.md b/frontend/src/api/.pi-map.md index e1cc8c1..e3a8579 100644 --- a/frontend/src/api/.pi-map.md +++ b/frontend/src/api/.pi-map.md @@ -4,28 +4,31 @@ 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. +Frontend API client layer that centralizes all HTTP communication with backend services using typed, token-authenticated request functions. ## 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 +- authentik.ts | API client providing functions to fetch users, send messages, and check message status from the Authentik service. | exp: AuthentikUser, AuthentikUsersResponse, AuthentikMessageInput, AuthentikMessageResponse, func:fetchAuthentikUsers(serviceId: string, params: { search?: string; page?: number; page_size?: number }) → Promise, call:get, call:String, func:sendAuthentikMessage(serviceId: string, input: AuthentikMessageInput) → Promise, call:post, func:fetchAuthentikMessageStatus(serviceId: string) → Promise>, call:get | dep: ./shared +- backups.ts | API client functions for fetching and managing backup jobs, runs, alerts, and dashboard summaries. | exp: func:fetchBackupJobs() → Promise, call:get, func:fetchBackupJob(jobId: string) → Promise<{ job: BackupJob; runs: BackupRun[] }>, call:get, func:fetchBackupRuns(jobId: string, status: string) → Promise, call:get, func:fetchBackupRun(runId: string) → Promise, call:get, func:fetchBackupAlerts(jobId: string, acknowledged: boolean, severity: string) → Promise, call:get, call:String, func:acknowledgeBackupAlert(alertId: string) → Promise, call:post, func:fetchBackupDashboard() → Promise, call:get | dep: ./shared, ../types/backups +- client.ts | Typed API client module that provides frontend functions for interacting 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, fetchPrometheusStatus, fetchPrometheusTargets | dep: ../types, ./shared, fetch API +- dashboards.ts | API client providing CRUD operations for named dashboards via REST endpoints. | exp: NamedDashboard, NamedDashboardInput, func:fetchDashboards() → Promise, call:get, func:fetchDashboardBySlug(slug: string) → Promise, call:get, call:encodeURIComponent, func:createDashboard(input: NamedDashboardInput) → Promise, call:post, func:updateDashboard(input: NamedDashboardInput) → Promise, call:put, func:deleteDashboard(id: string) → Promise<{ status: string }>, call:del | dep: ./shared +- services.ts | Provides API service functions for CRUD operations on service instances and fetching service types. | exp: func:fetchServiceTypes() → Promise, call:get, func:fetchServiceInstances(serviceType: string) → Promise, call:get, func:createServiceInstance(input: ServiceInstanceInput) → Promise, call:post, func:updateServiceInstance(input: ServiceInstanceInput) → Promise, call:put, raise:Error, func:deleteServiceInstance(serviceId: string) → Promise<{ status: string }>, call:del | dep: ./shared, ../types +- shared.ts | Provides shared API helper functions (GET, POST, PUT, DELETE, etc.) that automatically attach OIDC auth tokens and handle URL building and error parsing for backend requests. | exp: API_BASE, func:buildUrl(path: string, params: Record) → string, call:isAbsoluteUrl, call:Object.entries, call:url.searchParams.set, call:url.toString, func:readErrorDetail(response: Response) → Promise, call:response.text, call:JSON.parse, call:detail.trim, func:buildHeaders(isJsonBody: boolean) → Headers, call:getAccessToken, call:headers.set, func:get(path: string, params: Record) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:response.json, raise:Error, func:post(path: string, body: unknown) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:JSON.stringify, call:response.json, raise:Error, func:postForm(path: string, body: FormData) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:response.json, raise:Error, func:put(path: string, body: unknown) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:JSON.stringify, call:response.json, raise:Error, func:del(path: string) → Promise, call:fetch, call:buildUrl, call:buildHeaders, call:response.json, raise:Error | dep: ../auth, getAccessToken (from ../auth), fetch API, Headers API, URL API, import.meta.env +- widgets.ts | API client module providing CRUD operations for widget instances, widget references, builtin widget kinds, and widget data retrieval. | exp: WidgetReference, WidgetReferenceInput, func:fetchBuiltinWidgetKinds() → Promise< BuiltinWidgetKindInfo[] >, call:get, func:fetchWidgetInstances(serviceId: string, scope: "dashboard" | "service") → Promise, call:get, func:createWidgetInstance(input: WidgetInstanceInput) → Promise, call:post, func:updateWidgetInstance(input: WidgetInstanceInput) → Promise, call:put, raise:Error, func:deleteWidgetInstance(widgetId: string) → Promise<{ status: string }>, call:del, func:fetchWidgetData(widgetId: string) → Promise, call:get, func:fetchWidgetReferences(dashboardScope: string) → Promise, call:get, func:createWidgetReference(input: WidgetReferenceInput) → Promise, call:post, func:deleteWidgetReference(referenceId: string) → Promise<{ status: string }>, call:del, func:detachWidgetReference(referenceId: string) → Promise, call:post, func:updateWidgetReference(referenceId: string, sortOrder: number) → Promise, call:put | dep: ./shared, ../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. +Modular API client pattern with a shared base client (`shared.ts`) handling authentication and error parsing, while domain-specific modules (authentik, backups, dashboards, services, widgets, client) expose typed CRUD and fetch operations per service area. ## tags -fetch, call:fetch, call:res.json, raise:error, backup, widget, delete, service +fetch, call:get, widget, dashboard, call:build, authentik, delete, backup ## symbols +- fetchAuthentikUsers +- sendAuthentikMessage +- fetchAuthentikMessageStatus - fetchBackupJobs - fetchBackupJob - fetchBackupRuns - fetchBackupRun - fetchBackupAlerts -- acknowledgeBackupAlert -- fetchBackupDashboard -- fetchServiceTypes ## workflows - change api behavior - read: backups.ts, client.ts, services.ts + read: authentik.ts, backups.ts, client.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 index 72c45f5..ed3ef40 100644 --- a/frontend/src/components/.pi-map.index.md +++ b/frontend/src/components/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Reusable UI component library providing presentation-layer building blocks (tables, cards, charts, dialogs, widgets) for the frontend application. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md @@ -18,14 +18,14 @@ map: frontend/src/.pi-map.md - BackupDashboardWidget.tsx - BackupJobsTable.tsx - BackupRunsTable.tsx -- BackupsPage.tsx - ConfirmDialog.tsx - DialogFooter.tsx - HoverEditButton.tsx - LibraryOverview.tsx +- LineSeriesChart.tsx - MetricCard.tsx - NowPlaying.tsx -- ObservabilityPage.tsx +- PinnedServiceLink.tsx - SectionCard.tsx - SelectionRailCard.tsx - SessionActivityPanel.tsx diff --git a/frontend/src/components/.pi-map.md b/frontend/src/components/.pi-map.md index 91ca769..8582260 100644 --- a/frontend/src/components/.pi-map.md +++ b/frontend/src/components/.pi-map.md @@ -4,39 +4,39 @@ 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. +Reusable UI component library providing presentation-layer building blocks (tables, cards, charts, dialogs, widgets) for the frontend application. ## 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 +- BackupAlertsTable.tsx | Renders a responsive table of backup alerts with severity badges and acknowledge actions, switching between desktop table and mobile card layouts. | exp: func:BackupAlertsTable({ alerts, onAcknowledge }: Props), call:useIsMobile, call:onAcknowledge, call:alerts.map, call:severityVariant, call:formatTimestamp | dep: @/components/ui/badge, @/components/ui/button, @/components/ui/table, @/components/ui/mobile-card, ../hooks/useIsMobile, ../types/backups, useIsMobile hook, BackupAlert type - 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 +- BackupJobsTable.tsx | Renders a responsive table of backup jobs with status badges, switching to mobile card layout on small screens. | exp: func:BackupJobsTable({ jobs, latestRuns }: Props), call:useIsMobile, call:jobs.map, call:latestRuns.get, call:formatInterval, call:statusVariant, call:formatTimestamp | dep: @/components/ui/badge, @/components/ui/table, @/components/ui/mobile-card, ../hooks/useIsMobile, ../types/backups +- BackupRunsTable.tsx | Displays a filterable table of backup run records with responsive layout that switches between a desktop table and mobile cards. | exp: func:BackupRunsTable({ runs }: Props), call:useState, call:useIsMobile, 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, @/components/ui/mobile-card, ../hooks/useIsMobile, ../types/backups - 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 +- DialogFooter.tsx | Renders a dialog footer component with cancel, optional secondary action, and confirm buttons, mapping legacy MUI color/variant props to shadcn Button variants. | 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 | Renders a hover-reveal edit button for desktop and always-visible edit button for mobile, preserving legacy CSS class hooks. | exp: func:HoverEditButton({ onClick, label = "Edit", mobile = "always", }: 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 +- LineSeriesChart.tsx | Renders multiple time-series as a shared recharts line chart with merged data and formatted timestamps. | exp: SeriesPoint, ChartSeries, func:LineSeriesChart({ series, height = 300, }: LineSeriesChartProps), call:mergeSeries, call:formatTime, call:Number, call:series.map | dep: recharts - 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) +- PinnedServiceLink.tsx | Renders a navigable card-shaped button for pinned service shortcuts on dashboards and provides a helper to construct service target paths. | exp: PinnedServiceLinkProps, func:PinnedServiceLink({ label, target, icon: Icon = Boxes, className, }: PinnedServiceLinkProps), call:useNavigate, call:navigate, call:cn, func:serviceLinkTarget(serviceType: string, serviceId: string, tab: string) → string | dep: react-router-dom, lucide-react, @/lib/utils - 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 +- SessionActivityPanel.tsx | Renders a scrollable table displaying live media session activity details with status badges and optional session selection callbacks. | 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 +- WidgetConfigDialog.tsx | Provides a user interface dialog and form for creating, configuring, editing, and managing the sort order of dashboard widgets and widget references. | exp: func:WidgetConfigDialog({ open, onClose, serviceId, dashboardScope, editWidgetId, }: Props), call:useWidgetInstances, call:useServiceInstances, call:useTasks, call:useSaveWidgetInstance, call:useDeleteWidgetInstance, call:useWidgetReferences, call:useCreateWidgetReference, call:useDeleteWidgetReference, call:useDetachWidgetReference, call:useUpdateWidgetReference, call:useState, call:Boolean, call:useEffect, call:instances.find, call:references.find, call:startEdit, call:setDraft, call:SERVICE_REGISTRY[ services.find((s) => s.id === serviceId)?.service_type ?? "" ]?.widgets.find, call:services.find, call:setDraftBaseline, call:onClose, call:saveWidget.mutateAsync, call:reset, call:updateRef.mutateAsync, call:deleteWidget.mutateAsync, call:[...instances].sort, call:references.map, call:[...owned, ...refs].sort, call:useMemo, call:instances.map, call:existingSearch.toLowerCase().trim, call:allWidgets .filter((w) => !onDashboard.has(w.id)) .filter, call:onDashboard.has, call:w.title.toLowerCase().includes, call:w.widget_kind.toLowerCase().includes, call:createRef.mutateAsync, call:deleteRef.mutateAsync, call:detachRef.mutateAsync, call:SERVICE_REGISTRY[ services.find((s) => s.id === draft.serviceId)?.service_type ?? "" ]?.widgets.find, call:useIsMobile, call:String, call:Number, call:combinedWidgets.map, call:bindingLabel, call:moveInstance, call:toggleEnabled, call:handleDetach, call:handleRemoveReference, call:removeInstance, call:setShowExisting, call:setExistingSearch, call:availableWidgets.map, call:handleAddReference, call:Object.values(BUILTIN_WIDGETS).map, call:startAddBuiltIn, call:services .filter((s) => s.enabled) // When scoped to a service Overview, only show widgets for THAT // service instance's type (not all services' widgets). .filter((s) => !serviceId || s.id === serviceId) .flatMap, call:(SERVICE_REGISTRY[s.service_type]?.widgets ?? []).map, call:startAddService, call:handleClose, call:JSON.stringify | dep: react, @/components/ui/dialog, @/components/ui/button, @/components/ui/input, @/components/ui/textarea, @/components/ui/label, @/components/ui/switch, @/components/ui/select, @/components/ui/badge, @/components/ui/alert, lucide-react, ../hooks/useWidgets, ../hooks/useServices, ../hooks/useSettings, ../hooks/useIsMobile, @/components/ui/sheet-form, ../types, ../integrations/registry, @/components/ui/* +- WidgetInstance.tsx | Renders a widget instance card that dynamically resolves and displays a widget component, with optional edit and copy actions. | exp: func:WidgetInstanceCard({ widget, onEdit, onCopy }: Props), call:useServiceInstances, call:resolveWidget, call:onCopy, call:onEdit | dep: @/components/ui/alert, @/components/ui/button, lucide-react, ../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. +React functional components with responsive design patterns, built on shadcn/ui and Tailwind CSS with adapter layers for backward-compatible MUI-to-shadcn migration. ## tags -call:use, components, ui, card, backup, widget, table, dialog +call:use, components, ui, card, widget, table, backup, call:on ## symbols - BackupAlertsTable - BackupDashboardWidget - BackupJobsTable - BackupRunsTable -- BackupsPage - ConfirmDialog - DialogFooter - HoverEditButton +- LibraryOverview ## workflows - change components behavior read: BackupAlertsTable.tsx, BackupDashboardWidget.tsx, BackupJobsTable.tsx diff --git a/frontend/src/components/__tests__/.pi-map.index.md b/frontend/src/components/__tests__/.pi-map.index.md index 98bfce9..60997b0 100644 --- a/frontend/src/components/__tests__/.pi-map.index.md +++ b/frontend/src/components/__tests__/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/components/__tests__ ## role -Test suite providing unit test coverage for reusable UI components and feature widgets in the frontend. +Test suite providing unit test coverage for reusable UI components, verifying rendering, user interactions, responsive layouts, and edge cases. ## parent index: frontend/src/components/.pi-map.index.md map: frontend/src/components/.pi-map.md @@ -11,22 +11,26 @@ map: frontend/src/components/.pi-map.md ## files - BackupAlertsTable.test.tsx - BackupDashboardWidget.test.tsx +- BackupJobsTable.test.tsx - BackupRunsTable.test.tsx - ConfirmDialog.test.tsx - DialogFooter.test.tsx - HoverEditButton.test.tsx - LibraryOverview.test.tsx +- LineSeriesChart.test.tsx - MetricCard.test.tsx - NowPlaying.test.tsx +- PinnedServiceLink.test.tsx - SectionCard.test.tsx - SelectionRailCard.test.tsx - SessionActivityPanel.test.tsx - TabbedCard.test.tsx +- WidgetConfigDialog.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 + read: BackupAlertsTable.test.tsx, BackupDashboardWidget.test.tsx, BackupJobsTable.test.tsx ## dirty - diff --git a/frontend/src/components/__tests__/.pi-map.md b/frontend/src/components/__tests__/.pi-map.md index 4118491..2fe2a14 100644 --- a/frontend/src/components/__tests__/.pi-map.md +++ b/frontend/src/components/__tests__/.pi-map.md @@ -4,29 +4,33 @@ 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. +Test suite providing unit test coverage for reusable UI components, verifying rendering, user interactions, responsive layouts, and edge cases. ## 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 +- BackupAlertsTable.test.tsx | Tests the BackupAlertsTable component for severity-to-badge mapping, acknowledge button behavior, and responsive mobile card layout. | dep: vitest, @testing-library/react, @testing-library/user-event, ../BackupAlertsTable, ../../types/backups, BackupAlertsTable, 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 +- BackupJobsTable.test.tsx | Tests the BackupJobsTable component for correct rendering in both desktop table and mobile card layouts. | dep: vitest, @testing-library/react, ../BackupJobsTable, ../../types/backups, BackupJobsTable, types/backups +- BackupRunsTable.test.tsx | Tests the BackupRunsTable component for correct status badge variants, formatted output, and responsive mobile card layout. | 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 +- HoverEditButton.test.tsx | Tests the HoverEditButton component for click handling, custom labels, and responsive visibility behavior across mobile modes. | 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 +- LineSeriesChart.test.tsx | Tests the LineSeriesChart component for crash-free rendering with series data, empty data, and custom height. | dep: vitest, @testing-library/react, ../LineSeriesChart, LineSeriesChart - 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 +- PinnedServiceLink.test.tsx | Tests the PinnedServiceLink component to verify it renders the correct label and navigates to the target route on click. | dep: vitest, @testing-library/react, react-router-dom, @testing-library/user-event, ../PinnedServiceLink, PinnedServiceLink - 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 +- WidgetConfigDialog.test.tsx | Tests the WidgetConfigDialog component for desktop and mobile rendering modes, including draft discard confirmation behavior. | dep: vitest, @testing-library/react, ../WidgetConfigDialog, @testing-library/user-event, WidgetConfigDialog, useWidgets, useServices, useSettings ## arch -Component-level React Testing Library tests organized as co-located per-component test files following a flat naming convention. +React Testing Library with Jest, organized as co-located component test files following a one-test-file-per-component convention. ## tags -library, @testing, tests, component, vitest, react, user, card.test +library, @testing, react, tests, component, vitest, user, rendering ## symbols - ## workflows - update __tests__ tests - read: BackupAlertsTable.test.tsx, BackupDashboardWidget.test.tsx, BackupRunsTable.test.tsx + read: BackupAlertsTable.test.tsx, BackupDashboardWidget.test.tsx, BackupJobsTable.test.tsx ## dirty - diff --git a/frontend/src/components/ui/.pi-map.index.md b/frontend/src/components/ui/.pi-map.index.md index c360055..9bbd7a1 100644 --- a/frontend/src/components/ui/.pi-map.index.md +++ b/frontend/src/components/ui/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Reusable 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 @@ -23,13 +23,16 @@ map: frontend/src/components/.pi-map.md - dropdown-menu.tsx - input.tsx - label.tsx +- mobile-card.tsx - progress.tsx - scroll-area.tsx - select.tsx - separator.tsx +- sheet-form.tsx - sheet.tsx - skeleton.tsx - switch.tsx +- table-pagination.tsx - table.tsx - tabs.tsx - textarea.tsx diff --git a/frontend/src/components/ui/.pi-map.md b/frontend/src/components/ui/.pi-map.md index 7a5bf6c..09846b7 100644 --- a/frontend/src/components/ui/.pi-map.md +++ b/frontend/src/components/ui/.pi-map.md @@ -4,7 +4,7 @@ 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. +Reusable 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 @@ -13,35 +13,38 @@ Reusable, design-system UI component library providing styled, accessible primit - 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 +- data-table.tsx | Reusable, generic data table component wrapping TanStack Table with support for row selection, column visibility, pagination, and row click handling. | 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, call:table.getState | dep: react, @tanstack/react-table, lucide-react, @/lib/utils, @/components/ui/button, @/components/ui/checkbox, @/components/ui/table-pagination, @/components/ui/table, @/components/ui/dropdown-menu +- dialog.tsx | Provides a customizable, accessible dialog/modal component set built on top of Radix UI primitives with Tailwind CSS styling. | 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 +- mobile-card.tsx | Renders a responsive, stacked card list for displaying tabular data on mobile devices, with optional row click and action button support. | exp: MobileCardField, MobileCardRowProps, func:MobileCardRow({ rows, fields, getRowId, onRowClick, actions, className, }: MobileCardRowProps), call:fields.find, call:fields.filter, call:cn, call:rows.map, call:getRowId, call:String, call:primary.render, call:rest.map, call:field.render, call:actions, call:onRowClick, call:e.preventDefault | dep: react, @/lib/utils, cn utility - 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-form.tsx | Provides a mobile-friendly, full-height form sheet component with fixed header/footer, scrolling body, and unsaved-changes discard confirmation. | exp: SheetFormProps, func:SheetForm({ open, onOpenChange, title, onSave, onCancel, isPending = false, saveDisabled = false, saveLabel = "Save", isDirty = false, children, bodyClassName, }: SheetFormProps), call:React.useState, call:React.useCallback, call:setConfirmDiscardOpen, call:onCancel, call:attemptClose, call:onOpenChange, call:e.preventDefault, call:cn | dep: react, lucide-react, @/lib/utils, @/components/ui/button, @/components/ui/sheet, @/components/ConfirmDialog - 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-pagination.tsx | Shared pagination footer component for tables and card layouts, rendering row counts, page-size selector, page indicator, and prev/next navigation buttons. | exp: TablePaginationProps, func:TablePagination({ pageIndex, pageSize, pageSizeOptions, totalRows, pageCount, onPaginationChange, className, }: TablePaginationProps), call:cn, call:String, call:onPaginationChange, call:Number, call:pageSizeOptions.map, call:Math.max | dep: @tanstack/react-table, @/lib/utils, @/components/ui/button, @/components/ui/select - 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 +- tabs.tsx | Provides a styled, reusable Tabs UI component set built on Radix UI primitives with variant support. | dep: react, class-variance-authority, radix-ui, @/lib/utils - 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. +Composable component pattern built on Radix UI primitives with Tailwind CSS styling and class-variance-authority (CVA) for variant management. ## tags -react, radix, lib, utils, ui, component, table, styled +react, radix, lib, utils, ui, component, table, row ## symbols - Badge - DataTable +- MobileCardRow +- SheetForm +- TablePagination - 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 diff --git a/frontend/src/components/ui/__tests__/.pi-map.index.md b/frontend/src/components/ui/__tests__/.pi-map.index.md index 106056e..6915da7 100644 --- a/frontend/src/components/ui/__tests__/.pi-map.index.md +++ b/frontend/src/components/ui/__tests__/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Test suite providing unit and integration tests for reusable UI components including Badge, DataTable, MobileCardRow, and SheetForm. ## parent index: frontend/src/components/ui/.pi-map.index.md map: frontend/src/components/ui/.pi-map.md @@ -11,11 +11,13 @@ map: frontend/src/components/ui/.pi-map.md ## files - badge.test.tsx - data-table.test.tsx +- mobile-card.test.tsx +- sheet-form.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 + read: badge.test.tsx, data-table.test.tsx, mobile-card.test.tsx ## dirty - diff --git a/frontend/src/components/ui/__tests__/.pi-map.md b/frontend/src/components/ui/__tests__/.pi-map.md index e4b2ca4..63cd4da 100644 --- a/frontend/src/components/ui/__tests__/.pi-map.md +++ b/frontend/src/components/ui/__tests__/.pi-map.md @@ -4,18 +4,20 @@ 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. +Test suite providing unit and integration tests for reusable UI components including Badge, DataTable, MobileCardRow, and SheetForm. ## 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 +- mobile-card.test.tsx | Tests the MobileCardRow component for correct rendering of fields, click interactions, actions, and edge cases like empty rows and missing primary fields. | dep: vitest, @testing-library/react, @testing-library/user-event, ../mobile-card +- sheet-form.test.tsx | Tests the SheetForm component, verifying rendering, save/cancel callbacks, pending state, and dirty-state discard confirmation behavior. | dep: vitest, @testing-library/react, @testing-library/user-event, ../sheet-form ## arch -Vitest and React Testing Lab testing patterns with declarative test cases covering visual variants, TanStack Table integration, user interactions, and edge-case rendering. +Vitest-based React Testing Library tests organized in a co-located `__tests__` directory, following a one-test-file-per-component convention with focus on rendering, user interactions, and edge cases. ## tags -react, data, vitest, @testing, library, table, tests, badge +@testing, library, react, vitest, tests, component, data, table ## symbols - ## workflows - update __tests__ tests - read: badge.test.tsx, data-table.test.tsx + read: badge.test.tsx, data-table.test.tsx, mobile-card.test.tsx ## dirty - diff --git a/frontend/src/hooks/.pi-map.index.md b/frontend/src/hooks/.pi-map.index.md index 4a067c2..ab8a82c 100644 --- a/frontend/src/hooks/.pi-map.index.md +++ b/frontend/src/hooks/.pi-map.index.md @@ -2,16 +2,19 @@ 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. +Centralized collection of custom React hooks that encapsulate all server-state data fetching, mutations, caching, and UI state logic for the frontend application. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md ## children - ## files +- useAuthentik.ts - useBackups.ts - useDashboard.ts +- useDashboards.ts - useFiles.ts +- useIsMobile.ts - useMedia.ts - useObservability.ts - usePersistentState.ts @@ -19,13 +22,12 @@ map: frontend/src/.pi-map.md - 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 + read: useAuthentik.ts, useBackups.ts, useDashboard.ts ## dirty - diff --git a/frontend/src/hooks/.pi-map.md b/frontend/src/hooks/.pi-map.md index 3f49719..a4a5539 100644 --- a/frontend/src/hooks/.pi-map.md +++ b/frontend/src/hooks/.pi-map.md @@ -4,35 +4,37 @@ 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. +Centralized collection of custom React hooks that encapsulate all server-state data fetching, mutations, caching, and UI state logic for the frontend application. ## files +- useAuthentik.ts | Custom React hooks for fetching Authentik users, sending messages, and polling message status using TanStack Query. | exp: func:useAuthentikUsers(serviceId: string, params: { search?: string; page?: number; page_size?: number }), call:useQuery, call:fetchAuthentikUsers, func:useSendAuthentikMessage(serviceId: string), call:useQueryClient, call:useMutation, call:sendAuthentikMessage, call:queryClient.invalidateQueries, func:useAuthentikMessageStatus(serviceId: string), call:useQuery, call:fetchAuthentikMessageStatus | dep: @tanstack/react-query, ../api/authentik - 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 +- useDashboards.ts | Custom React hooks for fetching, creating, updating, and deleting dashboards using TanStack React Query. | exp: func:useDashboards(), call:useQuery, func:useDashboardBySlug(slug: string | undefined), call:useQuery, call:fetchDashboardBySlug, func:useSaveDashboard(), call:useQueryClient, call:useMutation, call:updateDashboard, call:createDashboard, call:queryClient.invalidateQueries, func:useDeleteDashboard(), call:useQueryClient, call:useMutation, call:deleteDashboard, call:queryClient.invalidateQueries | dep: @tanstack/react-query, ../api/dashboards - 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 +- useIsMobile.ts | Custom React hook that provides a reactive, SSR-safe boolean indicating whether the viewport is at mobile width (max-width: 768px). | exp: func:useIsMobile() → boolean, call:useState, call:window.matchMedia, call:useEffect, call:setIsMobile, call:mql.addEventListener, call:mql.removeEventListener | dep: react +- useMedia.ts | Custom React hooks for managing media index status, querying, and build lifecycle 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 | Provides custom React hooks for fetching and auto-refreshing observability data (Alertmanager alerts/status, Prometheus status/targets, monitoring machines) using React Query. | exp: func:useAlertmanagerAlerts(), call:useQuery, func:useAlertmanagerStatus(), 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 +- useWidgets.ts | Provides React Query hooks for fetching, creating, updating, and deleting widget instances and references. | exp: func:useWidgetInstances(serviceId: string, scope: "dashboard" | "service"), call:useQuery, call:fetchWidgetInstances, 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, func:useWidgetReferences(dashboardScope: string | undefined), call:useQuery, call:fetchWidgetReferences, func:useCreateWidgetReference(), call:useQueryClient, call:useMutation, call:queryClient.invalidateQueries, func:useDeleteWidgetReference(), call:useQueryClient, call:useMutation, call:queryClient.invalidateQueries, func:useDetachWidgetReference(), call:useQueryClient, call:useMutation, call:queryClient.invalidateQueries, func:useUpdateWidgetReference(), call:useQueryClient, call:useMutation, call:updateWidgetReference, call:queryClient.invalidateQueries | 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. +TanStack React Query (Query/Mutation) pattern with domain-oriented hook organization, automatic refetching/polling, cache invalidation strategies, and supplementary local-state hooks for responsive and persistent UI concerns. ## tags call:use, query, client, mutation, call:query, client.invalidate, queries, call:fetch ## symbols +- useAuthentikUsers +- useSendAuthentikMessage +- useAuthentikMessageStatus - useBackupJobs - useBackupJob - useBackupRuns - useBackupAlerts - useAcknowledgeAlert -- useBackupDashboard -- useCounts -- useLibraries ## workflows - change hooks behavior - read: useBackups.ts, useDashboard.ts, useFiles.ts + read: useAuthentik.ts, useBackups.ts, useDashboard.ts ## dirty - diff --git a/frontend/src/integrations/.pi-map.index.md b/frontend/src/integrations/.pi-map.index.md index e3f2c25..d53ff9f 100644 --- a/frontend/src/integrations/.pi-map.index.md +++ b/frontend/src/integrations/.pi-map.index.md @@ -2,13 +2,16 @@ dir: frontend/src/integrations ## role -Frontend integration layer that maps backend service types and widgets to their corresponding React components, metadata, and configuration schemas. +Central registry and integration layer that maps external services and built-in widgets to their React components, metadata, and navigation entries. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md ## children -- +- frontend/src/integrations/__tests__ + index: frontend/src/integrations/__tests__/.pi-map.index.md + map: frontend/src/integrations/__tests__/.pi-map.md ## files +- navEntries.ts - registry.test.ts - registry.ts ## links @@ -16,8 +19,10 @@ index: frontend/src/integrations/.pi-map.index.md map: frontend/src/integrations/.pi-map.md ## workflows - change integrations behavior - read: registry.ts + read: navEntries.ts, registry.ts - update integrations tests read: registry.test.ts +- explore integrations subdirectories + index: frontend/src/integrations/__tests__/.pi-map.index.md ## dirty - diff --git a/frontend/src/integrations/.pi-map.md b/frontend/src/integrations/.pi-map.md index 3bbbe0e..5904be5 100644 --- a/frontend/src/integrations/.pi-map.md +++ b/frontend/src/integrations/.pi-map.md @@ -4,27 +4,30 @@ 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. +Central registry and integration layer that maps external services and built-in widgets to their React components, metadata, and navigation entries. ## 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 +- navEntries.ts | Defines a static mapping of service types to their navigation entries and provides a filter function to return only entries for currently configured services. | exp: NavEntry, SERVICE_TYPE_NAV_ENTRIES, func:configuredNavEntries(configuredTypes: Set) → NavEntry[], call:SERVICE_TYPE_NAV_ENTRIES.filter, call:configuredTypes.has | dep: lucide-react +- registry.test.ts | Tests the service and widget registry module, validating service registrations, widget bindings, and widget resolution logic. | dep: vitest, ./registry, ../types +- registry.ts | Maps service types and built-in widgets to their respective React components, metadata, and config schemas, and provides lookup functions to resolve widget instances. | 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/PrometheusChartWidget, ../widgets/PrometheusGaugeWidget, ../widgets/PrometheusMeanWidget, ../widgets/JellyfinWidget, ../widgets/JellyfinNowPlayingWidget, ../widgets/PrometheusMetricWidget, ../widgets/QbittorrentActiveTorrentsWidget, ../widgets/QbittorrentSpeedWidget, ../widgets/QbittorrentTotalsWidget, ../widgets/SshTaskWidget, ../widgets/StaticWidget, ../types, various widget components, 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. +Registry pattern with static mappings, lookup/resolution functions, and filter utilities to dynamically expose only configured service integrations. ## tags -service, binding, widget, widgets, registry, types, builtin, get +service, widgets, widget, binding, nav, entries, types, registry ## symbols +- configuredNavEntries - getServiceBinding - getBuiltinBinding - resolveWidget - enrichServiceTypes -- WidgetComponentProps -- ServiceWidgetBinding -- ServiceBinding -- SERVICE_REGISTRY +- NavEntry +- SERVICE_TYPE_NAV_ENTRIES +- call:SERVICE_TYPE_NAV_ENTRIES.filter ## workflows - change integrations behavior - read: registry.ts + read: navEntries.ts, registry.ts - update integrations tests read: registry.test.ts +- explore integrations subdirectories + index: frontend/src/integrations/__tests__/.pi-map.index.md ## dirty - diff --git a/frontend/src/integrations/__tests__/.pi-map.index.md b/frontend/src/integrations/__tests__/.pi-map.index.md new file mode 100644 index 0000000..7803418 --- /dev/null +++ b/frontend/src/integrations/__tests__/.pi-map.index.md @@ -0,0 +1,20 @@ +# frontend/src/integrations/__tests__ (index) +dir: frontend/src/integrations/__tests__ + +## role +Test suite that validates navigation entry generation logic for service type configurations. +## parent +index: frontend/src/integrations/.pi-map.index.md +map: frontend/src/integrations/.pi-map.md +## children +- +## files +- navEntries.test.ts +## links +index: frontend/src/integrations/__tests__/.pi-map.index.md +map: frontend/src/integrations/__tests__/.pi-map.md +## workflows +- update __tests__ tests + read: navEntries.test.ts +## dirty +- diff --git a/frontend/src/integrations/__tests__/.pi-map.md b/frontend/src/integrations/__tests__/.pi-map.md new file mode 100644 index 0000000..83e3ca2 --- /dev/null +++ b/frontend/src/integrations/__tests__/.pi-map.md @@ -0,0 +1,20 @@ +# frontend/src/integrations/__tests__ +dir: frontend/src/integrations/__tests__ + +index: frontend/src/integrations/__tests__/.pi-map.index.md + +## role +Test suite that validates navigation entry generation logic for service type configurations. +## files +- navEntries.test.ts | Tests the `configuredNavEntries` function and `SERVICE_TYPE_NAV_ENTRIES` constant to verify correct navigation entry generation based on configured service types. | dep: vitest, ../navEntries +## arch +Unit testing pattern verifying pure functions and constants produce expected navigation structures based on input configuration. +## tags +nav, service, entries.test, tests, configurednaventries, type, entries, constant +## symbols +- +## workflows +- update __tests__ tests + read: navEntries.test.ts +## dirty +- diff --git a/frontend/src/lib/.pi-map.index.md b/frontend/src/lib/.pi-map.index.md index 96999c4..73b6664 100644 --- a/frontend/src/lib/.pi-map.index.md +++ b/frontend/src/lib/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/lib ## role -Provides shared utility functions for the frontend application. +Provides shared utility functions for the frontend application, specifically Tailwind CSS class merging and conflict resolution. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md diff --git a/frontend/src/lib/.pi-map.md b/frontend/src/lib/.pi-map.md index fcea5ac..8a202f7 100644 --- a/frontend/src/lib/.pi-map.md +++ b/frontend/src/lib/.pi-map.md @@ -4,11 +4,11 @@ dir: frontend/src/lib index: frontend/src/lib/.pi-map.index.md ## role -Provides shared utility functions for the frontend application. +Provides shared utility functions for the frontend application, specifically Tailwind CSS class merging and conflict resolution. ## 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. +Functional utility module pattern using the `clsx` + `tailwind-merge` (cn) convention for deterministic class composition. ## tags merge, tailwind, cn, call:tw, call:clsx, utils, utility, merges ## symbols diff --git a/frontend/src/pages/.pi-map.index.md b/frontend/src/pages/.pi-map.index.md index 3b73823..127a8aa 100644 --- a/frontend/src/pages/.pi-map.index.md +++ b/frontend/src/pages/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/pages ## role -Top-level page components constituting the primary UI screens/routes of the frontend application, covering dashboards, media management, file browsing, services, settings, users, and task automation. +Top-level page components that render the main UI views for dashboards, services, and settings in the application. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md @@ -10,25 +10,23 @@ map: frontend/src/.pi-map.md - frontend/src/pages/__tests__ index: frontend/src/pages/__tests__/.pi-map.index.md map: frontend/src/pages/__tests__/.pi-map.md +- frontend/src/pages/service-tabs + index: frontend/src/pages/service-tabs/.pi-map.index.md + map: frontend/src/pages/service-tabs/.pi-map.md ## files -- Actions.tsx -- Applications.tsx - Dashboard.tsx -- FileBrowser.impl.tsx -- FileBrowser.tsx -- Media.tsx +- NamedDashboardPage.tsx - ServicePage.tsx +- ServiceTypePage.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 + read: Dashboard.tsx, NamedDashboardPage.tsx, ServicePage.tsx - explore pages subdirectories - index: frontend/src/pages/__tests__/.pi-map.index.md + index: frontend/src/pages/__tests__/.pi-map.index.md, frontend/src/pages/service-tabs/.pi-map.index.md ## dirty - diff --git a/frontend/src/pages/.pi-map.md b/frontend/src/pages/.pi-map.md index 0b3d373..24dd913 100644 --- a/frontend/src/pages/.pi-map.md +++ b/frontend/src/pages/.pi-map.md @@ -4,36 +4,31 @@ dir: frontend/src/pages index: frontend/src/pages/.pi-map.index.md ## role -Top-level page components constituting the primary UI screens/routes of the frontend application, covering dashboards, media management, file browsing, services, settings, users, and task automation. +Top-level page components that render the main UI views for dashboards, services, and settings in the application. ## 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 settings form to view, edit (config and secrets), save, and delete a specific service instance, and lists its available widgets. | exp: func:ServicePage(), call:useParams, call:useServiceInstances, call:useServiceTypes, call:useSaveServiceInstance, call:useDeleteServiceInstance, call:useMemo, call:services.find, call:getServiceBinding, call:types.find, call:useState, call:setName, call:setEnabled, call:setDraftConfig, 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 (alert, badge, button, input, label, switch) -- 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 | Provides a React settings UI for managing monitoring machines, SSH keys, and local database operations with form editing and validation. | 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, @/components/ui/* -- 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) +- Dashboard.tsx | This file renders the main dashboard page, displaying widget instances grouped by category and user-created shortcuts with full CRUD dialog management. | exp: func:Dashboard(), call:useNavigate, call:useDashboardShortcuts, call:useSaveDashboardShortcut, call:useDeleteDashboardShortcut, call:useState, call:emptyShortcut, call:useWidgetInstances, call:useWidgetReferences, call:useServiceInstances, call:useIsMobile, call:useMemo, call:widgetReferences .filter((r) => r.widget.enabled) .map, call:[...widgetInstances, ...refs] .filter((w) => w.enabled) .sort, call:widgetReferences.map, call:useDetachWidgetReference, call:groupWidgetsBySection, call:setShortcutDraft, call:setShortcutDialogOpen, call:saveShortcut.mutateAsync, call:navigate, call:setWidgetDialogOpen, call:shortcuts.map, call:shortcutHref, call:window.open, call:openEditShortcut, call:setDeleteShortcutId, call:setEditWidgetId, call:widgetReferences.find, call:detachRef.mutate, call:visibleWidgets.map, call:referencedWidgetIds.has, call:Boolean, call:deleteShortcut.mutate | dep: react, react-router-dom, lucide-react, @/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, ../hooks/useServices, ../hooks/useIsMobile, ../types, ../components/SectionCard, ../components/ConfirmDialog, ../components/DialogFooter, ../components/WidgetInstance, ../components/WidgetConfigDialog, @/components/ui/* +- NamedDashboardPage.tsx | Renders a named dashboard page that displays pinned service links and configurable widgets based on a slug-based lookup. | exp: func:NamedDashboardPage(), call:useParams, call:useDashboardBySlug, call:useWidgetReferences, call:useState, call:useMemo, call:parseItems, call:widgetRefs .filter((r) => r.widget.enabled) .map((r) => r.widget) .sort, call:setConfigOpen, call:visibleWidgets.map, call:setEditWidgetId, call:items.map | dep: react, react-router-dom, lucide-react, @/components/ui/alert, @/components/ui/button, @/components/ui/skeleton, ../hooks/useDashboards, ../hooks/useWidgets, ../components/PinnedServiceLink, ../components/WidgetInstance, ../components/WidgetConfigDialog, useDashboards hook, useWidgets hook, PinnedServiceLink, WidgetInstanceCard, WidgetConfigDialog +- ServicePage.tsx | Displays a detailed service instance page with tabbed content, instance switching, and widget information based on URL parameters. | exp: func:ServicePage(), call:useParams, call:useServiceInstances, call:useNavigate, call:useMemo, call:services.find, call:getServiceBinding, call:serviceContentTabs, call:services.filter, call:binding.widgets.map, call:siblings.map, call:navigate, call:contentTabs.map, call:allTabs.map | dep: react, react-router-dom, @/components/ui/alert, @/components/ui/badge, @/components/ui/tabs, ../hooks/useServices, ../types, ../components/SectionCard, ../integrations/registry, ./service-tabs +- ServiceTypePage.tsx | Resolves the first enabled service instance for a given type and redirects to it, or shows an empty state if none are configured. | exp: func:ServiceTypePage(), call:useParams, call:useServiceInstances, call:useMemo, call:instances.find | dep: react, react-router-dom, @/components/ui/alert, @/components/ui/button, ../hooks/useServices, useServices hook +- ServicesPage.tsx | Provides the UI and logic for configuring external service instances and managing named dashboards with pinned links. | 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, @/components/ui/select, lucide-react, ../hooks/useServices, ../hooks/useDashboards, ../types, ../components/SectionCard, ../components/ConfirmDialog, ../components/DialogFooter, ../integrations/registry, ../components/PinnedServiceLink, ../api/dashboards, @/components/ui/*, useServices, useDashboards, SectionCard, ConfirmDialog, DialogFooter, integrations/registry +- Settings.tsx | Provides the settings page UI for managing monitoring machines, SSH keys, services, and database resets, including CRUD operations and SSH connection validation. | exp: func:Settings(), call:useMonitoringSettings, call:useSSHKeys, call:useSaveMonitoringMachine, call:useDeleteMonitoringMachine, call:useTestMonitoringMachineSSH, call:useState, call:emptyMachine, call:useIsMobile, 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:isMachineDraftDirty, call:Boolean, call:deleteMachine.mutate | dep: react, ../types, ../hooks/useSettings, ../hooks/useIsMobile, @/components/ui/sheet-form, ../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, ../hooks/useServices, @/components/ui/*, @/hooks/useSettings, @/hooks/useServices, @/hooks/useIsMobile ## arch -React functional component pattern with implementation/re-export splitting; each page encapsulates its own state management, CRUD operations, and UI rendering, typically integrating backend APIs, dynamic forms, and tabular/dialog-based interfaces. +React functional components using URL-parameter-driven routing, tabbed interfaces, and dialog-based CRUD management with widget composition. ## tags -call:use, components, ui, call:set, state, service, react, machine +call:use, components, ui, call:set, service, machine, page, dashboard ## symbols -- Actions -- Applications - Dashboard -- FileBrowser -- Media +- NamedDashboardPage - ServicePage +- ServiceTypePage - ServicesPage - Settings +- call:useNavigate +- call:useDashboardShortcuts ## workflows - change pages behavior - read: Actions.tsx, Applications.tsx, Dashboard.tsx + read: Dashboard.tsx, NamedDashboardPage.tsx, ServicePage.tsx - explore pages subdirectories - index: frontend/src/pages/__tests__/.pi-map.index.md + index: frontend/src/pages/__tests__/.pi-map.index.md, frontend/src/pages/service-tabs/.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 index a5c9f2a..770b5c8 100644 --- a/frontend/src/pages/__tests__/.pi-map.index.md +++ b/frontend/src/pages/__tests__/.pi-map.index.md @@ -2,25 +2,22 @@ dir: frontend/src/pages/__tests__ ## role -Test suite providing unit test coverage for top-level page components in the frontend application. +Test suite for top-level page components, ensuring correct rendering, user interactions, and state handling across Dashboard, NamedDashboard, Service, and Settings views. ## 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 +- NamedDashboardPage.test.tsx +- ServicePage.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 + read: Dashboard.test.tsx, NamedDashboardPage.test.tsx, ServicePage.test.tsx ## dirty - diff --git a/frontend/src/pages/__tests__/.pi-map.md b/frontend/src/pages/__tests__/.pi-map.md index 21f5cad..4039b29 100644 --- a/frontend/src/pages/__tests__/.pi-map.md +++ b/frontend/src/pages/__tests__/.pi-map.md @@ -4,23 +4,20 @@ 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. +Test suite for top-level page components, ensuring correct rendering, user interactions, and state handling across Dashboard, NamedDashboard, Service, and Settings views. ## 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 +- Dashboard.test.tsx | Tests the Dashboard component's shortcut CRUD functionality (creation, deletion, empty state rendering) while stubbing out composed widgets and data hooks. | dep: vitest, @testing-library/react, @testing-library/user-event, ../Dashboard, ../../types, react-router-dom, Dashboard, DashboardShortcut +- NamedDashboardPage.test.tsx | Tests the NamedDashboardPage component covering loading, error/404, data rendering, and empty states using mocked hooks. | dep: vitest, @testing-library/react, react-router-dom, @tanstack/react-query, ../NamedDashboardPage, ../../hooks/useDashboards, NamedDashboardPage, useDashboards, useWidgets, WidgetConfigDialog, WidgetInstance +- ServicePage.test.tsx | Tests the ServicePage component's tab rendering logic, verifying conditional display of tabs based on service type and instance count. | dep: vitest, @testing-library/react, @testing-library/user-event, react-router-dom, ../ServicePage, ../../types, ServicePage, ServiceInstance +- Settings.test.tsx | Tests the Settings component's machine list rendering, editing, and deletion flows with mocked hooks. | dep: vitest, @testing-library/react, @testing-library/user-event, ../Settings, ../../types, Settings, MonitoringMachine type, useSettings hooks ## arch -React Testing Library with Jest, using component mocking, hook stubs, and isolated render testing per page module. +React Testing Library with Jest, using mocked hooks and stubbed child components to isolate page-level rendering logic and CRUD workflows. ## tags -library, @testing, hooks, react, tests, component, vitest, user +react, @testing, library, dashboard, tests, component, rendering, hooks ## symbols - ## workflows - update __tests__ tests - read: Actions.test.tsx, Applications.test.tsx, Dashboard.test.tsx + read: Dashboard.test.tsx, NamedDashboardPage.test.tsx, ServicePage.test.tsx ## dirty - diff --git a/frontend/src/pages/service-tabs/.pi-map.index.md b/frontend/src/pages/service-tabs/.pi-map.index.md new file mode 100644 index 0000000..7bf9e08 --- /dev/null +++ b/frontend/src/pages/service-tabs/.pi-map.index.md @@ -0,0 +1,34 @@ +# frontend/src/pages/service-tabs (index) +dir: frontend/src/pages/service-tabs + +## role +Provides service-specific tabbed UI components rendered within service detail pages based on service type. +## parent +index: frontend/src/pages/.pi-map.index.md +map: frontend/src/pages/.pi-map.md +## children +- frontend/src/pages/service-tabs/__tests__ + index: frontend/src/pages/service-tabs/__tests__/.pi-map.index.md + map: frontend/src/pages/service-tabs/__tests__/.pi-map.md +## files +- ActionsTab.tsx +- AlertsTab.tsx +- FilesTab.tsx +- JobsTab.tsx +- MediaTab.tsx +- MessagingTab.tsx +- MetricsTab.tsx +- OverviewTab.tsx +- RequestsTab.tsx +- UsersTab.tsx +- index.ts +## links +index: frontend/src/pages/service-tabs/.pi-map.index.md +map: frontend/src/pages/service-tabs/.pi-map.md +## workflows +- change service-tabs behavior + read: ActionsTab.tsx, AlertsTab.tsx, FilesTab.tsx +- explore service-tabs subdirectories + index: frontend/src/pages/service-tabs/__tests__/.pi-map.index.md +## dirty +- diff --git a/frontend/src/pages/service-tabs/.pi-map.md b/frontend/src/pages/service-tabs/.pi-map.md new file mode 100644 index 0000000..538b529 --- /dev/null +++ b/frontend/src/pages/service-tabs/.pi-map.md @@ -0,0 +1,39 @@ +# frontend/src/pages/service-tabs +dir: frontend/src/pages/service-tabs + +index: frontend/src/pages/service-tabs/.pi-map.index.md + +## role +Provides service-specific tabbed UI components rendered within service detail pages based on service type. +## files +- ActionsTab.tsx | Provides a UI tab for managing, editing, and running saved SSH tasks (shell or Python) within a service page. | exp: func:ActionsTab({ instance }: { instance: ServiceInstance }), 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:saveTask.mutateAsync, call:setTab, call:String, call:openEdit, call:tasks.map, call:initialFromTask, call:runTask.mutateAsync, call:selectedRuns.data.items.map, call:new Date(run.created_at * 1000).toLocaleString, call:deleteTask.mutate | dep: react, ../../types, ../../hooks/useSettings, ../../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 +- AlertsTab.tsx | Renders an Alertmanager alerts summary and expandable alert list for a specific service instance. | exp: func:AlertsTab({ instance }: { instance: ServiceInstance }), call:useAlertmanagerAlerts, call:useAlertmanagerStatus, call:alertsSummary.alerts.map | dep: lucide-react, ../../hooks/useObservability, @/components/ui/card, @/components/ui/badge, @/components/ui/alert, @/components/ui/skeleton, @/components/ui/collapsible, ../../types, useObservability hooks +- FilesTab.tsx | Displays a file browser interface for an SSH task instance, featuring directory navigation, media file previews via ffprobe, and job execution. | exp: func:FilesTab({ instance }: { instance: ServiceInstance }), call:useIsMobile, call:useSearchParams, call:searchParams.get, call:useState, call:usePersistentState, call:isVideoFile, call:path.includes, call:path.replace, call:selectedPath.replace, call:defaultFileBrowserState, call:setBrowserState, call:useDirectoryListing, call:useFfprobe, call:useJobTemplates, call:useRunJob, call:updateBrowserState, 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:refetch, call:String, call:templates.map, call:runJob.mutate | dep: react, react-router-dom, @tanstack/react-table, @/components/ui/data-table, @/components/ui/mobile-card, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/card, @/components/ui/input, @/components/ui/label, @/components/ui/select, ../../hooks/useFiles, ../../hooks/usePersistentState, ../../components/SectionCard, ../../types, ../../hooks/useIsMobile, @/components/ui/* +- JobsTab.tsx | Displays a tabbed UI for backup operations showing Jobs, Runs, and Alerts tables with global (non-instance-scoped) backup data. | exp: func:JobsTab({ instance }: { instance: ServiceInstance }), 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, ../../components/BackupAlertsTable, ../../components/BackupJobsTable, ../../components/BackupRunsTable, ../../types, react/useState +- MediaTab.tsx | Provides the media management interface for a Jellyfin service instance, including index building controls, filtering, and a paginated data table of media items. | exp: func:MediaTab({ instance }: { instance: ServiceInstance }), call:useNavigate, call:useServiceInstances, call:usePrefersSmallScreen, call:useIsMobile, call:useCounts, call:useLibraries, call:useMediaStatus, call:useBuildIndex, call:useStopBuildIndex, call:useForceStopBuildIndex, call:usePersistentState, call:defaultMediaTabState, call:setMediaState, call:useState, call:useMediaDataQuery, call:Math.floor, call:updater, call:useMemo, call:sshServices.find, call:navigate, call:encodeURIComponent, call:Math.max, call:Math.ceil, call:formatDuration, 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/mobile-card, @/components/ui/table-pagination, @/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, ../../hooks/useIsMobile, ../../types, ../../hooks/useDashboard, ../../hooks/useServices, @/components/ui/* (data-table, mobile-card, table-pagination, alert, button, card, input, label, progress, select), useMedia, usePersistentState, useIsMobile, useDashboard, useServices +- MessagingTab.tsx | Provides a UI for composing and sending HTML email messages to Authentik users via a mail queue system. | exp: func:MessagingTab({ instance }: { instance: ServiceInstance }), call:useState, call:useAuthentikUsers, call:useSendAuthentikMessage, call:(data?.items ?? []).filter, call:setSelectedEmails, call:next.has, call:next.delete, call:next.add, call:subject.trim, call:sendMessage.mutate, call:Array.from, call:sendMessage.data.request_id?.slice, call:setSearch, call:users.slice(0, 20).map, call:selectedEmails.has, call:toggleEmail, call:setSubject, call:setHtmlBody | dep: react, @/components/ui/alert, @/components/ui/button, @/components/ui/input, @/components/ui/label, @/components/ui/textarea, ../../hooks/useAuthentik, ../../types +- MetricsTab.tsx | Displays Prometheus status and Node Exporter scrape targets in an instance-scoped metrics tab. | exp: func:MetricsTab({ instance }: { instance: ServiceInstance }), call:usePrometheusStatus, call:usePrometheusTargets | dep: lucide-react, ../../hooks/useObservability, @/components/ui/card, @/components/ui/badge, @/components/ui/alert, @/components/ui/skeleton, ../../types, useObservability hooks, UI components (Card, Badge, Alert, Skeleton), types +- OverviewTab.tsx | Renders a configurable per-service overview tab that displays and manages service-specific widgets in a responsive grid. | exp: func:OverviewTab({ instance }: { instance: ServiceInstance }), call:useWidgetInstances, call:useState, call:useMemo, call:widgets .filter((w) => w.enabled) .sort, call:setConfigOpen, call:visibleWidgets.map, call:setEditWidgetId | dep: react, @/components/ui/alert, @/components/ui/button, lucide-react, ../../hooks/useWidgets, ../../components/WidgetInstance, ../../components/WidgetConfigDialog, ../../types, ui/alert, ui/button, useWidgets hook, WidgetInstance component, WidgetConfigDialog component, types +- RequestsTab.tsx | Displays a Jellyseerr request-management tab within a Jellyfin page, showing either a configuration CTA or a placeholder view depending on whether Jellyseerr config fields are present. | exp: func:RequestsTab({ instance }: { instance: ServiceInstance }), call:String( (instance.config as Record).jellyseerr_url ?? "", ).trim, call:String( (instance.config as Record).jellyseerr_api_key ?? "", ).trim | dep: ../../types, @/components/ui/alert, lucide-react, ServiceInstance type, Alert/AlertDescription UI components, lucide-react ExternalLink icon +- UsersTab.tsx | Displays a searchable, paginated table of Authentik users for a given service instance. | exp: func:UsersTab({ instance }: { instance: ServiceInstance }), call:useState, call:useAuthentikUsers, call:Math.max, call:Math.ceil, call:setPage, call:setCommittedSearch, call:setSearch, call:handleSearch, call:users.map, call:Math.min | dep: react, @/components/ui/alert, @/components/ui/badge, @/components/ui/button, @/components/ui/input, @/components/ui/table, ../../types, ../../hooks/useAuthentik +- index.ts | Maps service types to their corresponding content tab components for rendering a service page. | exp: ServiceTabComponent, ContentTab, OVERVIEW_TAB, func:serviceContentTabs(serviceType: string) → ContentTab[] | dep: react, ../../types, ./OverviewTab, ./AlertsTab, ./MetricsTab, ./MediaTab, ./RequestsTab, ./FilesTab, ./ActionsTab, ./JobsTab, ./UsersTab, ./MessagingTab, OverviewTab, AlertsTab, MetricsTab, MediaTab, RequestsTab, FilesTab, ActionsTab, JobsTab, UsersTab, MessagingTab +## arch +Registry pattern via index.ts mapping service types to React tab components; each tab is a self-contained functional component scoped to a specific service instance. +## tags +call:use, components, ui, tab, state, call:set, locale, string +## symbols +- ActionsTab +- AlertsTab +- FilesTab +- JobsTab +- MediaTab +- MessagingTab +- MetricsTab +- OverviewTab +## workflows +- change service-tabs behavior + read: ActionsTab.tsx, AlertsTab.tsx, FilesTab.tsx +- explore service-tabs subdirectories + index: frontend/src/pages/service-tabs/__tests__/.pi-map.index.md +## dirty +- diff --git a/frontend/src/pages/service-tabs/__tests__/.pi-map.index.md b/frontend/src/pages/service-tabs/__tests__/.pi-map.index.md new file mode 100644 index 0000000..661195a --- /dev/null +++ b/frontend/src/pages/service-tabs/__tests__/.pi-map.index.md @@ -0,0 +1,29 @@ +# frontend/src/pages/service-tabs/__tests__ (index) +dir: frontend/src/pages/service-tabs/__tests__ + +## role +Test suite providing unit test coverage for all service detail tab components in the service management interface. +## parent +index: frontend/src/pages/service-tabs/.pi-map.index.md +map: frontend/src/pages/service-tabs/.pi-map.md +## children +- +## files +- ActionsTab.test.tsx +- AlertsTab.test.tsx +- FilesTab.test.tsx +- JobsTab.test.tsx +- MediaTab.test.tsx +- MessagingTab.test.tsx +- MetricsTab.test.tsx +- OverviewTab.test.tsx +- RequestsTab.test.tsx +- UsersTab.test.tsx +## links +index: frontend/src/pages/service-tabs/__tests__/.pi-map.index.md +map: frontend/src/pages/service-tabs/__tests__/.pi-map.md +## workflows +- update __tests__ tests + read: ActionsTab.test.tsx, AlertsTab.test.tsx, FilesTab.test.tsx +## dirty +- diff --git a/frontend/src/pages/service-tabs/__tests__/.pi-map.md b/frontend/src/pages/service-tabs/__tests__/.pi-map.md new file mode 100644 index 0000000..f75a7d0 --- /dev/null +++ b/frontend/src/pages/service-tabs/__tests__/.pi-map.md @@ -0,0 +1,29 @@ +# frontend/src/pages/service-tabs/__tests__ +dir: frontend/src/pages/service-tabs/__tests__ + +index: frontend/src/pages/service-tabs/__tests__/.pi-map.index.md + +## role +Test suite providing unit test coverage for all service detail tab components in the service management interface. +## files +- ActionsTab.test.tsx | Tests the ActionsTab component to verify it renders the saved-actions rail, task details, and Add action button. | dep: vitest, @testing-library/react, react-router-dom, ../ActionsTab, ../../../types, ActionsTab, useSettings hooks, ServiceInstance type +- AlertsTab.test.tsx | Tests the AlertsTab component to verify it renders alert counts, alert names, and severity badges correctly. | dep: vitest, @testing-library/react, ../AlertsTab, ../../../types, AlertsTab, ServiceInstance type, useObservability hook +- FilesTab.test.tsx | Tests the FilesTab component to verify it renders directory listings, a path bar, and a browser section correctly. | dep: vitest, @testing-library/react, react-router-dom, ../FilesTab, ../../../types, FilesTab, ServiceInstance, useFiles, usePersistentState +- JobsTab.test.tsx | Tests the JobsTab component to verify it renders Jobs, Runs, and Alerts sub-tabs and displays backup job data correctly. | dep: vitest, @testing-library/react, ../JobsTab, ../../../types, JobsTab, useBackups hooks, ServiceInstance type +- MediaTab.test.tsx | Tests the MediaTab component to verify it renders index status, library counts, and filter controls with mocked hooks. | dep: vitest, @testing-library/react, react-router-dom, ../MediaTab, ../../../types, MediaTab, ServiceInstance type +- MessagingTab.test.tsx | Tests the MessagingTab component to verify it renders the compose form and recipient list correctly. | dep: vitest, @testing-library/react, ../MessagingTab, ../../../types, MessagingTab, useAuthentik hook, ServiceInstance type +- MetricsTab.test.tsx | Tests the MetricsTab component to verify it renders Prometheus version and target information correctly. | dep: vitest, @testing-library/react, ../MetricsTab, ../../../types, MetricsTab, useObservability, ServiceInstance types +- OverviewTab.test.tsx | Tests the OverviewTab component to verify widget rendering, empty states, and config dialog interactions. | dep: vitest, @testing-library/react, @testing-library/user-event, ../OverviewTab, ../../../types, useWidgets hook, WidgetInstance component, WidgetConfigDialog component +- RequestsTab.test.tsx | Tests the RequestsTab component to verify it correctly displays empty-state messaging when Jellyseerr is unconfigured and shows the configured URL when both URL and API key are set. | dep: vitest, @testing-library/react, ../RequestsTab, ../../../types, RequestsTab, ServiceInstance +- UsersTab.test.tsx | Tests the UsersTab component to verify it renders a directory table of users, search input, and pagination controls. | dep: vitest, @testing-library/react, ../UsersTab, ../../../types, UsersTab, useAuthentik hook, ServiceInstance type +## arch +React Testing Library with mocked hooks and child components, following a one-test-file-per-tab convention with shallow rendering and assertions on DOM content. +## tags +tab.test, react, library, @testing, tests, component, verify, vitest +## symbols +- +## workflows +- update __tests__ tests + read: ActionsTab.test.tsx, AlertsTab.test.tsx, FilesTab.test.tsx +## dirty +- diff --git a/frontend/src/test/.pi-map.index.md b/frontend/src/test/.pi-map.index.md index fe3ea0f..249c6f5 100644 --- a/frontend/src/test/.pi-map.index.md +++ b/frontend/src/test/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/test ## role -Provides test infrastructure configuration for the frontend's Vitest testing environment. +Provides test infrastructure configuration and environment setup for the frontend's Vitest-based unit testing framework. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md diff --git a/frontend/src/test/.pi-map.md b/frontend/src/test/.pi-map.md index 62b47c4..8975894 100644 --- a/frontend/src/test/.pi-map.md +++ b/frontend/src/test/.pi-map.md @@ -4,11 +4,11 @@ dir: frontend/src/test index: frontend/src/test/.pi-map.index.md ## role -Provides test infrastructure configuration for the frontend's Vitest testing environment. +Provides test infrastructure configuration and environment setup for the frontend's Vitest-based unit testing framework. ## 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. +Bootstrap/configuration module pattern that extends the jsdom environment with custom polyfills and DOM assertion matchers to support UI component testing. ## tags vitest, jest, dom, jsdom, resize, observer, stub, observe ## symbols diff --git a/frontend/src/types/.pi-map.index.md b/frontend/src/types/.pi-map.index.md index 838219a..3292c24 100644 --- a/frontend/src/types/.pi-map.index.md +++ b/frontend/src/types/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/src/types ## role -Central TypeScript type definition module that provides shared interfaces and type contracts for the frontend application's API interactions and domain models. +Central type definition package providing TypeScript interfaces for the application's domain models and API contracts. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md diff --git a/frontend/src/types/.pi-map.md b/frontend/src/types/.pi-map.md index 84980a7..886c175 100644 --- a/frontend/src/types/.pi-map.md +++ b/frontend/src/types/.pi-map.md @@ -4,14 +4,14 @@ dir: frontend/src/types index: frontend/src/types/.pi-map.index.md ## role -Central TypeScript type definition module that provides shared interfaces and type contracts for the frontend application's API interactions and domain models. +Central type definition package providing TypeScript interfaces for the application's domain models and API contracts. ## 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 responses covering media management, monitoring, SSH keys, tasks, widgets, and system administration. | 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 +- index.ts | Defines shared TypeScript interfaces for backend API responses covering media management, user directories, SSH keys, monitoring, widgets, and task execution. | 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, PrometheusStatus, PrometheusTarget, WidgetInstance, WidgetInstanceInput, WidgetDataResponse, SecretFieldInfo, ServiceWidgetKindInfo, ServiceTypeInfo, ServiceInstance, ServiceInstanceInput, BuiltinWidgetKindInfo ## arch -Flat type library pattern with domain-based file organization (backups vs. general types) and a barrel export structure via index.ts, enabling consistent type-safety across UI components and API layers. +Pure type declaration modules with no runtime logic, exporting shared interfaces consumed across the frontend for type-safe data handling. ## tags -input, response, media, status, info, widget, backup, user +input, response, media, info, widget, backup, user, status ## symbols - BackupJob - BackupRun diff --git a/frontend/src/widgets/.pi-map.index.md b/frontend/src/widgets/.pi-map.index.md index 0f0cb57..6957a99 100644 --- a/frontend/src/widgets/.pi-map.index.md +++ b/frontend/src/widgets/.pi-map.index.md @@ -2,18 +2,26 @@ 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. +Dashboard widget components that render visual data for various integrations (Alertmanager, Backups, Jellyfin, Prometheus, qBittorrent, SSH tasks, static content) within the monitoring application. ## parent index: frontend/src/.pi-map.index.md map: frontend/src/.pi-map.md ## children -- +- frontend/src/widgets/__tests__ + index: frontend/src/widgets/__tests__/.pi-map.index.md + map: frontend/src/widgets/__tests__/.pi-map.md ## files - AlertmanagerAlertsWidget.tsx - BackupsWidget.tsx -- GrafanaLinkWidget.tsx +- JellyfinNowPlayingWidget.tsx - JellyfinWidget.tsx +- PrometheusChartWidget.tsx +- PrometheusGaugeWidget.tsx +- PrometheusMeanWidget.tsx - PrometheusMetricWidget.tsx +- QbittorrentActiveTorrentsWidget.tsx +- QbittorrentSpeedWidget.tsx +- QbittorrentTotalsWidget.tsx - SshTaskWidget.tsx - StaticWidget.tsx - index.ts @@ -22,6 +30,8 @@ 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 + read: AlertmanagerAlertsWidget.tsx, BackupsWidget.tsx, JellyfinNowPlayingWidget.tsx +- explore widgets subdirectories + index: frontend/src/widgets/__tests__/.pi-map.index.md ## dirty - diff --git a/frontend/src/widgets/.pi-map.md b/frontend/src/widgets/.pi-map.md index fa83df4..dbca410 100644 --- a/frontend/src/widgets/.pi-map.md +++ b/frontend/src/widgets/.pi-map.md @@ -4,31 +4,39 @@ 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. +Dashboard widget components that render visual data for various integrations (Alertmanager, Backups, Jellyfin, Prometheus, qBittorrent, SSH tasks, static content) within the monitoring application. ## 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 +- JellyfinNowPlayingWidget.tsx | Displays a Jellyfin now-playing widget that fetches and renders active media sessions using a custom data hook and session activity panel. | exp: func:JellyfinNowPlayingWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Array.isArray | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SessionActivityPanel, ../components/SectionCard, ../hooks/useWidgets, ../types, SessionActivityPanel, SectionCard, useWidgetData, 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 +- PrometheusChartWidget.tsx | Renders a Prometheus query widget that fetches time-series data and displays it as a line chart, with loading, error, and empty states. | exp: func:PrometheusChartWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/LineSeriesChart, ../components/SectionCard, ../hooks/useWidgets, ../types, LineSeriesChart, SectionCard, useWidgetData hook, WidgetInstance type +- PrometheusGaugeWidget.tsx | Renders a Prometheus metric as a radial gauge widget with configurable warning/critical threshold bands. | exp: func:PrometheusGaugeWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Math.max, call:Math.round, call:Math.min, call:toPercent, call:trackCells.map, call:formatValue | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, recharts, SectionCard, useWidgetData +- PrometheusMeanWidget.tsx | Displays a single Prometheus metric value (mean) with adaptive formatting and unit support in a dashboard widget. | exp: func:PrometheusMeanWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:formatMean | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData, WidgetInstance - 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 +- QbittorrentActiveTorrentsWidget.tsx | Displays a list of active torrents from a qBittorrent instance with download/upload speeds and status badges in a dashboard widget. | exp: func:QbittorrentActiveTorrentsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:torrents.map, call:formatSpeed | dep: @/components/ui/alert, @/components/ui/badge, @/components/ui/skeleton, ../components/SectionCard, ../hooks/useWidgets, ../types, SectionCard, useWidgetData hook, WidgetInstance type +- QbittorrentSpeedWidget.tsx | Renders a qBittorrent download/upload speed widget with a line series chart, handling loading, error, and empty states. | exp: func:QbittorrentSpeedWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData | dep: @/components/ui/alert, @/components/ui/skeleton, ../components/LineSeriesChart, ../components/SectionCard, ../hooks/useWidgets, ../types, LineSeriesChart, SectionCard, useWidgetData hook +- QbittorrentTotalsWidget.tsx | Displays qBittorrent torrent totals and per-state breakdown using a widget with loading, error, and data states. | exp: func:QbittorrentTotalsWidget({ widget, refreshIntervalMs, description, }: Props), call:useWidgetData, call:Object.keys, call:Object.entries(payload.by_state).map | dep: @/components/ui/alert, @/components/ui/badge, @/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 +- index.ts | Barrel file that re-exports all dashboard widget components from a widgets directory. | dep: AlertmanagerAlertsWidget, BackupsWidget, PrometheusChartWidget, PrometheusGaugeWidget, PrometheusMeanWidget, JellyfinWidget, JellyfinNowPlayingWidget, PrometheusMetricWidget, QbittorrentActiveTorrentsWidget, QbittorrentSpeedWidget, QbittorrentTotalsWidget, 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. +React functional components using a per-widget pattern with consistent loading/error/empty state handling, custom data hooks, and barrel-file aggregation for modular dashboard composition. ## tags -widget, components, data, ui, call:use, types, alert, sectioncard +widget, components, data, ui, call:use, prometheus, sectioncard, types ## symbols - AlertmanagerAlertsWidget - BackupsWidget -- GrafanaLinkWidget +- JellyfinNowPlayingWidget - JellyfinWidget +- PrometheusChartWidget +- PrometheusGaugeWidget +- PrometheusMeanWidget - PrometheusMetricWidget -- SshTaskWidget -- StaticWidget -- call:useWidgetData ## workflows - change widgets behavior - read: AlertmanagerAlertsWidget.tsx, BackupsWidget.tsx, GrafanaLinkWidget.tsx + read: AlertmanagerAlertsWidget.tsx, BackupsWidget.tsx, JellyfinNowPlayingWidget.tsx +- explore widgets subdirectories + index: frontend/src/widgets/__tests__/.pi-map.index.md ## dirty - diff --git a/frontend/src/widgets/__tests__/.pi-map.index.md b/frontend/src/widgets/__tests__/.pi-map.index.md new file mode 100644 index 0000000..97e3346 --- /dev/null +++ b/frontend/src/widgets/__tests__/.pi-map.index.md @@ -0,0 +1,26 @@ +# frontend/src/widgets/__tests__ (index) +dir: frontend/src/widgets/__tests__ + +## role +Test suite providing comprehensive unit tests for dashboard widget components, verifying rendering across loading, data, empty, and error states. +## parent +index: frontend/src/widgets/.pi-map.index.md +map: frontend/src/widgets/.pi-map.md +## children +- +## files +- JellyfinNowPlayingWidget.test.tsx +- PrometheusChartWidget.test.tsx +- PrometheusGaugeWidget.test.tsx +- PrometheusMeanWidget.test.tsx +- QbittorrentActiveTorrentsWidget.test.tsx +- QbittorrentSpeedWidget.test.tsx +- QbittorrentTotalsWidget.test.tsx +## links +index: frontend/src/widgets/__tests__/.pi-map.index.md +map: frontend/src/widgets/__tests__/.pi-map.md +## workflows +- update __tests__ tests + read: JellyfinNowPlayingWidget.test.tsx, PrometheusChartWidget.test.tsx, PrometheusGaugeWidget.test.tsx +## dirty +- diff --git a/frontend/src/widgets/__tests__/.pi-map.md b/frontend/src/widgets/__tests__/.pi-map.md new file mode 100644 index 0000000..3c713b6 --- /dev/null +++ b/frontend/src/widgets/__tests__/.pi-map.md @@ -0,0 +1,26 @@ +# frontend/src/widgets/__tests__ +dir: frontend/src/widgets/__tests__ + +index: frontend/src/widgets/__tests__/.pi-map.index.md + +## role +Test suite providing comprehensive unit tests for dashboard widget components, verifying rendering across loading, data, empty, and error states. +## files +- JellyfinNowPlayingWidget.test.tsx | Tests the JellyfinNowPlayingWidget component by verifying it correctly renders active sessions, empty state, and error conditions. | dep: vitest, @testing-library/react, ../JellyfinNowPlayingWidget, ../../types, JellyfinNowPlayingWidget, useWidgets hook +- PrometheusChartWidget.test.tsx | Tests the PrometheusChartWidget component by verifying its rendering states including loading skeleton, chart with series data, error alerts, and empty states. | dep: vitest, @testing-library/react, ../PrometheusChartWidget, ../../types, ../../hooks/useWidgets, PrometheusChartWidget, useWidgets hook, WidgetInstance type +- PrometheusGaugeWidget.test.tsx | Tests the PrometheusGaugeWidget component, covering loading, data with/without threshold bands, error, and empty states. | dep: vitest, @testing-library/react, ../PrometheusGaugeWidget, ../../types, ../../hooks/useWidgets, PrometheusGaugeWidget, WidgetInstance types, useWidgets hook +- PrometheusMeanWidget.test.tsx | Tests the PrometheusMeanWidget component covering loading, data display, error, and empty states. | dep: vitest, @testing-library/react, ../PrometheusMeanWidget, ../../types, ../../hooks/useWidgets, PrometheusMeanWidget, useWidgets, types +- QbittorrentActiveTorrentsWidget.test.tsx | Tests the QbittorrentActiveTorrentsWidget component for loading, data rendering, empty state, and error handling scenarios. | dep: vitest, @testing-library/react, ../QbittorrentActiveTorrentsWidget, ../../types, ../../hooks/useWidgets, QbittorrentActiveTorrentsWidget, WidgetInstance, useWidgets +- QbittorrentSpeedWidget.test.tsx | Tests the QbittorrentSpeedWidget component for loading, data rendering, empty state, and error handling scenarios. | dep: vitest, @testing-library/react, ../QbittorrentSpeedWidget, ../../types, ../../hooks/useWidgets, QbittorrentSpeedWidget, useWidgets +- QbittorrentTotalsWidget.test.tsx | Tests the QbittorrentTotalsWidget component for loading, data rendering, and error states. | dep: vitest, @testing-library/react, ../QbittorrentTotalsWidget, ../../types, ../../hooks/useWidgets, QbittorrentTotalsWidget, useWidgets, types +## arch +Co-located React component testing using a consistent arrange-act-assert pattern, with each widget receiving isolated test coverage of its visual and data-handling states. +## tags +widget.test, usewidgets, types, tests, component, error, vitest, @testing +## symbols +- +## workflows +- update __tests__ tests + read: JellyfinNowPlayingWidget.test.tsx, PrometheusChartWidget.test.tsx, PrometheusGaugeWidget.test.tsx +## dirty +- diff --git a/frontend/tests/.pi-map.index.md b/frontend/tests/.pi-map.index.md index 0b8d04e..d69f8ff 100644 --- a/frontend/tests/.pi-map.index.md +++ b/frontend/tests/.pi-map.index.md @@ -2,7 +2,7 @@ dir: frontend/tests ## role -Test suite validating frontend utility functions for user state management and UI model transformation. +Test suite validating user state management and UI model transformation functions for the frontend application. ## parent index: frontend/.pi-map.index.md map: frontend/.pi-map.md diff --git a/frontend/tests/.pi-map.md b/frontend/tests/.pi-map.md index cf32b80..a61cff6 100644 --- a/frontend/tests/.pi-map.md +++ b/frontend/tests/.pi-map.md @@ -4,12 +4,12 @@ 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. +Test suite validating user state management and UI model transformation functions for the frontend application. ## 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. +JavaScript test modules (Mocha/ES modules) following a unit testing pattern that verifies pure data transformation and state resolution logic. ## tags user, tests, data, node:assert, strict, node:test, src, js ## symbols diff --git a/monitoring/.pi-map.index.md b/monitoring/.pi-map.index.md index fc55320..6deaa90 100644 --- a/monitoring/.pi-map.index.md +++ b/monitoring/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring ## role -Provides observability, metrics collection, and health-checking capabilities for the application. +No files found in this directory; package is empty or contains no analyzable source files. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/monitoring/.pi-map.md b/monitoring/.pi-map.md index f6f9e55..635d5e0 100644 --- a/monitoring/.pi-map.md +++ b/monitoring/.pi-map.md @@ -4,10 +4,10 @@ dir: monitoring index: monitoring/.pi-map.index.md ## role -Provides observability, metrics collection, and health-checking capabilities for the application. +No files found in this directory; package is empty or contains no analyzable source files. ## files ## arch -Directory is currently empty with no files to analyze for architectural patterns. +N/A - empty directory with no files to analyze. ## tags - ## symbols diff --git a/monitoring/alertmanager/.pi-map.index.md b/monitoring/alertmanager/.pi-map.index.md index 876bc0b..f9c5916 100644 --- a/monitoring/alertmanager/.pi-map.index.md +++ b/monitoring/alertmanager/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/alertmanager ## role -Central configuration package for Prometheus alert routing, grouping, and notification delivery within the monitoring stack. +Manages alert routing, grouping, and notification delivery for Prometheus-generated alerts via email and webhook channels. ## parent index: monitoring/.pi-map.index.md map: monitoring/.pi-map.md diff --git a/monitoring/alertmanager/.pi-map.md b/monitoring/alertmanager/.pi-map.md index ba5a17b..677d6a9 100644 --- a/monitoring/alertmanager/.pi-map.md +++ b/monitoring/alertmanager/.pi-map.md @@ -4,11 +4,11 @@ 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. +Manages alert routing, grouping, and notification delivery for Prometheus-generated alerts via email and webhook channels. ## 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. +Declarative YAML configuration implementing rule-based routing, alert grouping, inhibition policies, and critical escalation patterns. ## tags alertmanager, configures, routing, grouping, delivering, prometheus, alerts, email ## symbols diff --git a/monitoring/alloy/.pi-map.index.md b/monitoring/alloy/.pi-map.index.md index 0419e96..5453f99 100644 --- a/monitoring/alloy/.pi-map.index.md +++ b/monitoring/alloy/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/alloy ## role -Centralized log collection configuration that scrapes Docker container logs and forwards them to Loki for storage and querying. +Centralized configuration for collecting and forwarding container logs to the monitoring system's log aggregation backend. ## parent index: monitoring/.pi-map.index.md map: monitoring/.pi-map.md diff --git a/monitoring/alloy/.pi-map.md b/monitoring/alloy/.pi-map.md index d44e8e4..76af2fb 100644 --- a/monitoring/alloy/.pi-map.md +++ b/monitoring/alloy/.pi-map.md @@ -4,11 +4,11 @@ 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. +Centralized configuration for collecting and forwarding container logs to the monitoring system's log aggregation backend. ## 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. +Declarative agent-based collection pattern using Grafana Alloy's pipeline architecture to scrape Docker logs and ship them to Loki. ## tags docker, loki, discovery, config, configures, alloy, scrape, container ## symbols diff --git a/monitoring/grafana/.pi-map.index.md b/monitoring/grafana/.pi-map.index.md index 35ba04f..47b01e1 100644 --- a/monitoring/grafana/.pi-map.index.md +++ b/monitoring/grafana/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/grafana ## role -Provides centralized configuration for deploying and securing a Grafana monitoring dashboard instance. +Provides centralized Grafana configuration enabling OAuth-based authentication, iframe embedding, and role-based access control for the monitoring dashboard. ## parent index: monitoring/.pi-map.index.md map: monitoring/.pi-map.md diff --git a/monitoring/grafana/.pi-map.md b/monitoring/grafana/.pi-map.md index 65ac6f2..f3169ce 100644 --- a/monitoring/grafana/.pi-map.md +++ b/monitoring/grafana/.pi-map.md @@ -4,11 +4,11 @@ dir: monitoring/grafana index: monitoring/grafana/.pi-map.index.md ## role -Provides centralized configuration for deploying and securing a Grafana monitoring dashboard instance. +Provides centralized Grafana configuration enabling OAuth-based authentication, iframe embedding, and role-based access control for the monitoring dashboard. ## 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. +Infrastructure-as-code pattern using a declarative INI configuration file to externalize service-level settings for authentication and authorization. ## tags grafana, authentik, configuration, enabling, oauth, authentication, iframe, embedding ## symbols diff --git a/monitoring/grafana/provisioning/.pi-map.index.md b/monitoring/grafana/provisioning/.pi-map.index.md index a71c2f3..9432560 100644 --- a/monitoring/grafana/provisioning/.pi-map.index.md +++ b/monitoring/grafana/provisioning/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/grafana/provisioning ## role -Provides declarative configuration for automatic Grafana dashboard and datasource provisioning without manual UI intervention. +Contains Grafana dashboard provisioning configurations for automatically loading dashboards in the monitoring stack. ## parent index: monitoring/grafana/.pi-map.index.md map: monitoring/grafana/.pi-map.md diff --git a/monitoring/grafana/provisioning/.pi-map.md b/monitoring/grafana/provisioning/.pi-map.md index 03ab1f6..34a761a 100644 --- a/monitoring/grafana/provisioning/.pi-map.md +++ b/monitoring/grafana/provisioning/.pi-map.md @@ -4,10 +4,10 @@ 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. +Contains Grafana dashboard provisioning configurations for automatically loading dashboards in the monitoring stack. ## files ## arch -Infrastructure-as-code pattern using Grafana's native provisioning system with YAML configuration files organized into subdirectories for datasources and dashboards. +Follows Grafana's declarative provisioning pattern with structured directories for dashboards and datasources. ## tags - ## symbols diff --git a/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md b/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md index 7357f44..78498eb 100644 --- a/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md +++ b/monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/grafana/provisioning/dashboards-json ## role -Provides declarative configuration for automatically provisioning Grafana dashboards from local JSON files. +Configures automated provisioning of Grafana dashboards from JSON files in the monitoring stack. ## parent index: monitoring/grafana/provisioning/.pi-map.index.md map: monitoring/grafana/provisioning/.pi-map.md diff --git a/monitoring/grafana/provisioning/dashboards-json/.pi-map.md b/monitoring/grafana/provisioning/dashboards-json/.pi-map.md index b3cd3c9..2427b0d 100644 --- a/monitoring/grafana/provisioning/dashboards-json/.pi-map.md +++ b/monitoring/grafana/provisioning/dashboards-json/.pi-map.md @@ -4,11 +4,11 @@ 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. +Configures automated provisioning of Grafana dashboards from JSON files in the monitoring stack. ## 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. +Declarative configuration pattern using Grafana's file-based provisioning system with YAML configuration pointing to a JSON dashboard directory. ## tags dashboards, grafana, configures, dashboard, provisioning, automatically, load, update ## symbols diff --git a/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md index f331c35..72e8725 100644 --- a/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md +++ b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Provides pre-configured Grafana dashboard JSON definitions for visualizing service and system-level infrastructure metrics. ## parent index: monitoring/grafana/provisioning/dashboards-json/.pi-map.index.md map: monitoring/grafana/provisioning/dashboards-json/.pi-map.md diff --git a/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md index bc20564..1568efd 100644 --- a/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md +++ b/monitoring/grafana/provisioning/dashboards-json/dashboards/.pi-map.md @@ -4,12 +4,12 @@ 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. +Provides pre-configured Grafana dashboard JSON definitions for visualizing service and system-level infrastructure metrics. ## 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. +Declarative infrastructure-as-code pattern using static JSON dashboard configurations consumed by Grafana's provisioning system. ## tags grafana, overview, node, exporter, manage, dashboard, metrics, prometheus ## symbols diff --git a/monitoring/grafana/provisioning/datasources/.pi-map.index.md b/monitoring/grafana/provisioning/datasources/.pi-map.index.md index fe2389b..d6ee3de 100644 --- a/monitoring/grafana/provisioning/datasources/.pi-map.index.md +++ b/monitoring/grafana/provisioning/datasources/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/grafana/provisioning/datasources ## role -Provisions and configures Grafana data source connections to Prometheus and Loki monitoring backends. +Provisions 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 diff --git a/monitoring/grafana/provisioning/datasources/.pi-map.md b/monitoring/grafana/provisioning/datasources/.pi-map.md index 63565bf..76c8839 100644 --- a/monitoring/grafana/provisioning/datasources/.pi-map.md +++ b/monitoring/grafana/provisioning/datasources/.pi-map.md @@ -4,11 +4,11 @@ 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. +Provisions 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. +Declarative Infrastructure-as-Code pattern using Grafana's native YAML provisioning system for automated data source configuration. ## tags grafana, prometheus, loki, datasources, configures, data, sources, monitoring ## symbols diff --git a/monitoring/loki/.pi-map.index.md b/monitoring/loki/.pi-map.index.md index 518bda0..223b3a7 100644 --- a/monitoring/loki/.pi-map.index.md +++ b/monitoring/loki/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/loki ## role -Provides configuration for deploying and managing the Grafana Loki log aggregation server. +Provides configuration for Grafana Loki, the centralized log aggregation server in the project's monitoring stack. ## parent index: monitoring/.pi-map.index.md map: monitoring/.pi-map.md diff --git a/monitoring/loki/.pi-map.md b/monitoring/loki/.pi-map.md index f66d3b4..69ac5f8 100644 --- a/monitoring/loki/.pi-map.md +++ b/monitoring/loki/.pi-map.md @@ -4,11 +4,11 @@ dir: monitoring/loki index: monitoring/loki/.pi-map.index.md ## role -Provides configuration for deploying and managing the Grafana Loki log aggregation server. +Provides configuration for Grafana Loki, the centralized log aggregation server in the project's monitoring stack. ## 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). +Uses a declarative YAML configuration pattern utilizing local filesystem storage with a 30-day log retention policy. ## tags loki, configures, grafana, log, aggregation, server, local, filesystem ## symbols diff --git a/monitoring/prometheus/.pi-map.index.md b/monitoring/prometheus/.pi-map.index.md index 41825ea..23613d4 100644 --- a/monitoring/prometheus/.pi-map.index.md +++ b/monitoring/prometheus/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/prometheus ## role -Provides configuration for standalone Prometheus deployments to scrape and collect metrics from local infrastructure services and remote nodes. +Provides Prometheus server configuration for scraping metrics from infrastructure components and remote nodes using standalone deployment. ## parent index: monitoring/.pi-map.index.md map: monitoring/.pi-map.md diff --git a/monitoring/prometheus/.pi-map.md b/monitoring/prometheus/.pi-map.md index 07f19b0..c28c78f 100644 --- a/monitoring/prometheus/.pi-map.md +++ b/monitoring/prometheus/.pi-map.md @@ -4,11 +4,11 @@ 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. +Provides Prometheus server configuration for scraping metrics from infrastructure components and remote nodes using standalone deployment. ## 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. +Declarative monitoring configuration using static targets and file-based service discovery patterns for federated metrics collection. ## tags node, loki, alertmanager, grafana, prometheus.standalone, configures, prometheus, monitoring ## symbols diff --git a/monitoring/prometheus/file-sd/.pi-map.index.md b/monitoring/prometheus/file-sd/.pi-map.index.md index f30808c..9275228 100644 --- a/monitoring/prometheus/file-sd/.pi-map.index.md +++ b/monitoring/prometheus/file-sd/.pi-map.index.md @@ -2,7 +2,7 @@ dir: monitoring/prometheus/file-sd ## role -Provides static target configuration for Prometheus to discover and scrape node_exporter metrics endpoints. +Provides file-based service discovery configuration for Prometheus node_exporter scrape targets. ## parent index: monitoring/prometheus/.pi-map.index.md map: monitoring/prometheus/.pi-map.md diff --git a/monitoring/prometheus/file-sd/.pi-map.md b/monitoring/prometheus/file-sd/.pi-map.md index cf2e153..3f3a0d2 100644 --- a/monitoring/prometheus/file-sd/.pi-map.md +++ b/monitoring/prometheus/file-sd/.pi-map.md @@ -4,11 +4,11 @@ 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. +Provides file-based service discovery configuration for Prometheus node_exporter scrape targets. ## 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. +Static file service discovery pattern using Prometheus file_sd_config mechanism with JSON-formatted target definitions. ## tags targets, node, exporter, prometheus, empty, json, intended, define ## symbols diff --git a/monitoring/prometheus/rules/.pi-map.md b/monitoring/prometheus/rules/.pi-map.md index bd459a6..ea061e4 100644 --- a/monitoring/prometheus/rules/.pi-map.md +++ b/monitoring/prometheus/rules/.pi-map.md @@ -8,7 +8,7 @@ Defines Prometheus alerting rules for monitoring backup job failures and observa ## 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. +Declarative Infrastructure-as-Code using YAML-based Prometheus alerting rule definitions. ## tags backup, prometheus, alerts, defines, alerting, rules, job, failures ## symbols diff --git a/openspec/.pi-map.index.md b/openspec/.pi-map.index.md index a4c65c5..88cc612 100644 --- a/openspec/.pi-map.index.md +++ b/openspec/.pi-map.index.md @@ -2,7 +2,7 @@ dir: openspec ## role -Central configuration and documentation package defining coding standards, architecture context, and development rules for a homelab media dashboard project. +Central configuration and governance file that defines project context, tech stack, and rules for an AI-assisted, spec-driven homelab media and observability dashboard. ## parent index: ./.pi-map.index.md map: ./.pi-map.md diff --git a/openspec/.pi-map.md b/openspec/.pi-map.md index ed4aa5b..49230fc 100644 --- a/openspec/.pi-map.md +++ b/openspec/.pi-map.md @@ -4,13 +4,13 @@ dir: openspec index: openspec/.pi-map.index.md ## role -Central configuration and documentation package defining coding standards, architecture context, and development rules for a homelab media dashboard project. +Central configuration and governance file that defines project context, tech stack, and rules for an AI-assisted, spec-driven homelab media and observability dashboard. ## 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 +- config.yaml | Configuration file defining project context, tech stack, migration goals, and development rules for an AI-assisted spec-driven homelab media and observability dashboard. ## arch -Single-file declarative configuration pattern using YAML to establish project conventions, migration guidelines (MUI to shadcn/ui + Tailwind CSS), and governance constraints. +Single declarative YAML configuration file using a convention-over-configuration pattern to codify project metadata, constraints, and migration goals for AI-driven development workflows. ## tags -react, @mui, context, shadcn, material, @emotion, config, configuration +config, configuration, defining, project, context, tech, stack, migration ## symbols - ## workflows diff --git a/openspec/changes/.pi-map.index.md b/openspec/changes/.pi-map.index.md index a22ac9f..3133ea8 100644 --- a/openspec/changes/.pi-map.index.md +++ b/openspec/changes/.pi-map.index.md @@ -2,7 +2,7 @@ dir: openspec/changes ## role -Directory for tracking proposed and applied changes to the project's OpenAPI specification. +No files present in this directory; appears to be an empty or placeholder directory likely intended for OpenSpec change tracking artifacts. ## parent index: openspec/.pi-map.index.md map: openspec/.pi-map.md @@ -10,6 +10,12 @@ map: openspec/.pi-map.md - openspec/changes/archive index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md +- openspec/changes/mobile-responsive-parity + index: openspec/changes/mobile-responsive-parity/.pi-map.index.md + map: openspec/changes/mobile-responsive-parity/.pi-map.md +- openspec/changes/services-as-hub-ia + index: openspec/changes/services-as-hub-ia/.pi-map.index.md + map: openspec/changes/services-as-hub-ia/.pi-map.md ## files ## links index: openspec/changes/.pi-map.index.md diff --git a/openspec/changes/.pi-map.md b/openspec/changes/.pi-map.md index ef45ca8..908536e 100644 --- a/openspec/changes/.pi-map.md +++ b/openspec/changes/.pi-map.md @@ -4,10 +4,10 @@ dir: openspec/changes index: openspec/changes/.pi-map.index.md ## role -Directory for tracking proposed and applied changes to the project's OpenAPI specification. +No files present in this directory; appears to be an empty or placeholder directory likely intended for OpenSpec change tracking artifacts. ## files ## arch -Follows the "changes folder" pattern where each change proposal is stored as a structured markdown file describing modifications, rationale, and migration steps. +Undefined due to absence of files; expected convention would follow a structured directory of change proposal documents (e.g., Markdown or YAML) describing project modifications. ## tags - ## symbols diff --git a/openspec/changes/archive/.pi-map.index.md b/openspec/changes/archive/.pi-map.index.md index 4811c0e..ded4d0f 100644 --- a/openspec/changes/archive/.pi-map.index.md +++ b/openspec/changes/archive/.pi-map.index.md @@ -2,7 +2,7 @@ dir: openspec/changes/archive ## role -Archive directory for historical change records and specifications that have been completed or superseded. +Archive directory for completed and historical change specifications from the OpenSpec change management process. ## parent index: openspec/changes/.pi-map.index.md map: openspec/changes/.pi-map.md @@ -10,6 +10,12 @@ map: openspec/changes/.pi-map.md - 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/2026-07-08-prometheus-direct-charting + index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md + map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.md +- openspec/changes/archive/2026-07-09-service-storage-harness + index: openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md + map: openspec/changes/archive/2026-07-09-service-storage-harness/.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 diff --git a/openspec/changes/archive/.pi-map.md b/openspec/changes/archive/.pi-map.md index 3116119..a5b911b 100644 --- a/openspec/changes/archive/.pi-map.md +++ b/openspec/changes/archive/.pi-map.md @@ -4,10 +4,10 @@ dir: openspec/changes/archive index: openspec/changes/archive/.pi-map.index.md ## role -Archive directory for historical change records and specifications that have been completed or superseded. +Archive directory for completed and historical change specifications from the OpenSpec change management process. ## files ## arch -Simple file-based archive pattern storing obsolete or completed change documents without active processing logic. +Simple file-based archive pattern that stores completed change documents, organized by lifecycle status to separate active proposals from historical records. ## tags - ## symbols 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 index ac8c2b7..3a159c4 100644 --- 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 @@ -2,7 +2,7 @@ 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. +OpenSpec change specification archive documenting a completed migration of a React web UI from MUI v9 to shadcn/ui + Tailwind v4 + lucide-react across eight incremental slices. ## parent index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md 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 index 88f7160..2dfcdae 100644 --- 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 @@ -4,7 +4,7 @@ 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. +OpenSpec change specification archive documenting a completed migration of a React web UI from MUI v9 to shadcn/ui + Tailwind v4 + lucide-react across eight incremental slices. ## 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 @@ -16,7 +16,7 @@ OpenSpec change specification archive documenting the planning, design, implemen - 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. +Specification-driven lifecycle documentation following a structured eight-phase change management process (proposal → exploration → design → spec → tasks → apply → verify → archive), with progress tracking, sync reconciliation, and verification gate reports. ## tags react, shadcn, lucide, ui, report, mui, tailwind, table ## symbols 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 index fdd5b15..c3f43cc 100644 --- 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 @@ -2,7 +2,7 @@ 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. +Specification directory containing archived design documents for the 2026-06-17 web UI rework change. ## 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 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 index b33d0b3..0f91b52 100644 --- 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 @@ -4,10 +4,10 @@ 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. +Specification directory containing archived design documents for the 2026-06-17 web UI rework change. ## files ## arch -Empty directory intended to contain structured specification documents following the OpenSpec change-driven workflow pattern. +OpenSpec change-based specification pattern, archiving approved proposal specs in a dated directory structure. ## tags - ## symbols 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 index f478325..84d51bd 100644 --- 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 @@ -2,7 +2,7 @@ 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. +Defines durable specification contracts for a web UI rework migrating from MUI/Emotion to shadcn/ui/Tailwind with standardized component and testing patterns. ## 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 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 index 1d61648..c358ab0 100644 --- 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 @@ -4,11 +4,11 @@ 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. +Defines durable specification contracts for a web UI rework migrating from MUI/Emotion to shadcn/ui/Tailwind with standardized component and testing patterns. ## 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. +Specification-driven contract document establishing end-state requirements across UI framework migration, navigation restructuring, table standardization, and testing conventions. ## tags react, shadcn, table, vitest, spec, defines, durable, post ## symbols diff --git a/openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md new file mode 100644 index 0000000..f9a7f3b --- /dev/null +++ b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md @@ -0,0 +1,28 @@ +# openspec/changes/archive/2026-07-08-prometheus-direct-charting (index) +dir: openspec/changes/archive/2026-07-08-prometheus-direct-charting + +## role +Archived OpenSpec change documentation package for replacing Grafana integration with direct Prometheus-based charting and metric widgets. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs + index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md + map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.md +## files +- apply-progress.md +- archive-report.md +- design.md +- proposal.md +- spec.md +- sync-report.md +- tasks.md +- verify-report.md +## links +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md +map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.md b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.md new file mode 100644 index 0000000..b8aac7d --- /dev/null +++ b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.md @@ -0,0 +1,26 @@ +# openspec/changes/archive/2026-07-08-prometheus-direct-charting +dir: openspec/changes/archive/2026-07-08-prometheus-direct-charting + +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md + +## role +Archived OpenSpec change documentation package for replacing Grafana integration with direct Prometheus-based charting and metric widgets. +## files +- apply-progress.md | This file documents the completion status and implementation details of a project change that replaced Grafana integration with direct Prometheus-based charting and metric widgets. | dep: prometheus, recharts, pytest, ruff, npm, vitest +- archive-report.md | This file is an archive report documenting the completion and archival of the "prometheus-direct-charting" OpenSpec change, verifying all lifecycle phases were completed and recording the folder's move to the archive directory. | dep: git, OpenSpec +- design.md | Design document detailing the architectural changes required to replace Grafana with direct Prometheus-based charting, including backend API extensions, frontend widget rewrites, and full removal of Grafana dependencies. | dep: Prometheus API, recharts, Pydantic, GrafanaWidgetSource, PrometheusWidgetSource +- proposal.md | This file is a Software Design Document (SDD) proposal outlining the architectural plan to remove Grafana as a middleman by implementing direct Prometheus charting, alongside introducing new gauge and mean metric widgets. | dep: prometheus, recharts, openspec/config.yaml, integrations/grafana.py +- spec.md | Specification document defining acceptance requirements for replacing Grafana with direct Prometheus-based charting, gauge, and mean widgets. +- sync-report.md | This file is a documentation report detailing the synchronization of OpenSpec artifacts for a specific software change, reconciling flat and domain-specific specifications into a canonical store. | dep: openspec, prometheus-charting, web-ui +- tasks.md | Task tracking document for implementing a multi-slice migration to replace Grafana with direct Prometheus charting +- verify-report.md | Verification report documenting an adversarial, read-only audit of a code change that replaces Grafana with direct Prometheus charting. +## arch +Lifecycle-based specification archive following a structured documentation pattern (proposal → design → spec → tasks → verification → archival) with cross-referencing reports across each phase. +## tags +prometheus, report, grafana, charting, direct, openspec, change, archive +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md new file mode 100644 index 0000000..502d62d --- /dev/null +++ b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md @@ -0,0 +1,20 @@ +# openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs (index) +dir: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs + +## role +Contains specification documents defining requirements and acceptance criteria for a Prometheus direct charting feature change. +## parent +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.index.md +map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/.pi-map.md +## children +- openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting + index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.index.md + map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.md +## files +## links +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md +map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.md b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.md new file mode 100644 index 0000000..8747643 --- /dev/null +++ b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs +dir: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs + +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md + +## role +Contains specification documents defining requirements and acceptance criteria for a Prometheus direct charting feature change. +## files +## arch +Structured specification files following OpenSpec conventions, organized as archived change proposals with feature capability descriptions. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.index.md b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.index.md new file mode 100644 index 0000000..3196317 --- /dev/null +++ b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting (index) +dir: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting + +## role +Defines durable specification requirements for replacing Grafana with direct Prometheus-backed metric visualization widgets (charts, gauges, and means). +## parent +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.index.md +map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.index.md +map: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.md b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.md new file mode 100644 index 0000000..d4ef38e --- /dev/null +++ b/openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting +dir: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting + +index: openspec/changes/archive/2026-07-08-prometheus-direct-charting/specs/prometheus-charting/.pi-map.index.md + +## role +Defines durable specification requirements for replacing Grafana with direct Prometheus-backed metric visualization widgets (charts, gauges, and means). +## files +- spec.md | Specifies the durable end-state requirements for replacing Grafana with direct Prometheus-backed metric visualization (chart, gauge, and mean widgets). +## arch +Follows an OpenSpec specification pattern describing end-state functional requirements without prescribing implementation details. +## tags +spec, specifies, durable, end, state, requirements, replacing, grafana +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md b/openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md new file mode 100644 index 0000000..e33ad61 --- /dev/null +++ b/openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md @@ -0,0 +1,28 @@ +# openspec/changes/archive/2026-07-09-service-storage-harness (index) +dir: openspec/changes/archive/2026-07-09-service-storage-harness + +## role +Specification and change-management archive for the "service-storage-harness" feature, documenting the full OpenSpec SDD lifecycle from proposal through verification. +## parent +index: openspec/changes/archive/.pi-map.index.md +map: openspec/changes/archive/.pi-map.md +## children +- openspec/changes/archive/2026-07-09-service-storage-harness/specs + index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md + map: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.md +## files +- apply-progress.md +- archive-report.md +- design.md +- proposal.md +- spec.md +- sync-report.md +- tasks.md +- verify-report.md +## links +index: openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md +map: openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.md b/openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.md new file mode 100644 index 0000000..e7b9453 --- /dev/null +++ b/openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.md @@ -0,0 +1,26 @@ +# openspec/changes/archive/2026-07-09-service-storage-harness +dir: openspec/changes/archive/2026-07-09-service-storage-harness + +index: openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md + +## role +Specification and change-management archive for the "service-storage-harness" feature, documenting the full OpenSpec SDD lifecycle from proposal through verification. +## files +- apply-progress.md | Documents the completion progress and detailed deliverables of a 35-task "service-storage-harness" software feature implementation across four code slices. +- archive-report.md | This file is an archive report documenting the completion and archival of the `service-storage-harness` change within an OpenSpec SDD workflow. | dep: OpenSpec, git, pytest, ruff, npm, vitest +- design.md | This document outlines the software design for a service storage harness and qBittorrent integration, detailing database migrations, per-service data isolation, and widget data retrieval. | dep: sqlite3, requests, asyncio +- proposal.md | Proposal document describing a service storage harness abstraction with qBittorrent widget integration and MediaIndex migration for a homelab management dashboard. | dep: SQLite, Pydantic, recharts, @tanstack/react-table, Fernet, qBittorrent Web API +- spec.md | Defines acceptance requirements and specifications for implementing a service storage harness with qBittorrent widgets, MediaIndex migration, and cascade-delete wiring. +- sync-report.md | Documentation report detailing the synchronization of the `service-storage-harness` OpenSpec change artifacts into a canonical specification format. | dep: openspec +- tasks.md | Task breakdown document outlining a phased implementation plan for a service storage harness, qBittorrent integration, and MediaIndex migration across backend and frontend. +- verify-report.md | Fresh-context adversarial verification report documenting the read-only test and spec-coverage results for the `service-storage-harness` change. +## arch +Document-driven specification archive following a structured SDD workflow (proposal → design → spec → tasks → implementation → verification → archival) with multiple artifacts capturing each phase. +## tags +service, report, storage, harness, openspec, qbittorrent, progress, archive +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md new file mode 100644 index 0000000..d86d0a5 --- /dev/null +++ b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md @@ -0,0 +1,20 @@ +# openspec/changes/archive/2026-07-09-service-storage-harness/specs (index) +dir: openspec/changes/archive/2026-07-09-service-storage-harness/specs + +## role +Directory containing archived OpenSpec change specifications for the service storage harness feature implemented on 2026-07-09. +## parent +index: openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.index.md +map: openspec/changes/archive/2026-07-09-service-storage-harness/.pi-map.md +## children +- openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage + index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.index.md + map: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.md +## files +## links +index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md +map: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.md b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.md new file mode 100644 index 0000000..7ca21ef --- /dev/null +++ b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.md @@ -0,0 +1,18 @@ +# openspec/changes/archive/2026-07-09-service-storage-harness/specs +dir: openspec/changes/archive/2026-07-09-service-storage-harness/specs + +index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md + +## role +Directory containing archived OpenSpec change specifications for the service storage harness feature implemented on 2026-07-09. +## files +## arch +Flat-file specification archive following OpenSpec change-tracking conventions, preserving historical design docs and spec deltas for auditability. +## tags +- +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.index.md b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.index.md new file mode 100644 index 0000000..bfbf05e --- /dev/null +++ b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage (index) +dir: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage + +## role +Specification document defining requirements for a service storage harness including qBittorrent integration, MediaIndex migration, and cascade-delete functionality. +## parent +index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.index.md +map: openspec/changes/archive/2026-07-09-service-storage-harness/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.index.md +map: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.md b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.md new file mode 100644 index 0000000..125f623 --- /dev/null +++ b/openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage +dir: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage + +index: openspec/changes/archive/2026-07-09-service-storage-harness/specs/service-storage/.pi-map.index.md + +## role +Specification document defining requirements for a service storage harness including qBittorrent integration, MediaIndex migration, and cascade-delete functionality. +## files +- spec.md | Specification document defining 28 requirements (SS-101 through SS-128) for a service storage harness, qBittorrent integration, MediaIndex migration, and cascade-delete wiring. +## arch +Single Markdown specification file organized by numbered requirements (SS-101 through SS-128), following an OpenSpec change-proposal pattern with structured requirement definitions. +## tags +spec, specification, document, defining, requirements, 101, 128, service +## 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 index 98af73d..e408fc4 100644 --- a/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md +++ b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.index.md @@ -2,7 +2,7 @@ dir: openspec/changes/archive/configurable-dashboard-widgets ## role -OpenSpec change specification package documenting the design and implementation plan for a configurable dashboard widgets feature. +Archived OpenSpec change specification for adding a configurable, user-customizable widget system to a media library dashboard application. ## parent index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md diff --git a/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md index 33b26f9..bf32740 100644 --- a/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md +++ b/openspec/changes/archive/configurable-dashboard-widgets/.pi-map.md @@ -4,7 +4,7 @@ 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. +Archived OpenSpec change specification for adding a configurable, user-customizable widget system to a media library dashboard application. ## 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 @@ -12,7 +12,7 @@ OpenSpec change specification package documenting the design and implementation - 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. +Design proposal package following OpenSpec workflow (exploration → proposal → design → tasks → progress), documenting a phased full-stack implementation using compile-time widget registries, SQLite persistence, FastAPI CRUD endpoints, source adapters, and React frontend components. ## tags react, fastapi, configurable, dashboard, design, pydantic, sqlite, tanstack query ## symbols 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 index 2341801..c48c620 100644 --- a/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md +++ b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md @@ -2,7 +2,7 @@ dir: openspec/changes/archive/configurable-dashboard-widgets/specs ## role -Archived specification documents for a configurable dashboard widgets feature change. +Archive directory containing historical specification documents for the 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 diff --git a/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md index 217ee53..46b1e83 100644 --- a/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md +++ b/openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md @@ -4,10 +4,10 @@ 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. +Archive directory containing historical specification documents for the configurable dashboard widgets feature change. ## files ## arch -OpenSpec change specification structure containing design specs for previously implemented widget customization capabilities. +OpenSpec convention-based specification format storing change proposals as structured markdown files with deltas, requirements, and scenario definitions. ## tags - ## symbols 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 index f17102d..142c6bc 100644 --- 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 @@ -2,7 +2,7 @@ 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. +Specification document defining Phase 1 requirements for a configurable dashboard widget system with persistence, REST API, and compile-time registries. ## parent index: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.index.md map: openspec/changes/archive/configurable-dashboard-widgets/specs/.pi-map.md 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 index fc56276..0896082 100644 --- 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 @@ -4,11 +4,11 @@ dir: openspec/changes/archive/configurable-dashboard-widgets/specs/dashboard-wid 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. +Specification document defining Phase 1 requirements for a configurable dashboard widget system with persistence, REST API, and compile-time registries. ## 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. +Specification-driven design pattern defining bounded context requirements for a widget system using closed registries, SQLite persistence layer, REST API endpoints, and independent data-fetching widgets. ## tags widget, sqlite, api, spec, define, phase, requirements, persisted ## symbols diff --git a/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md index 2ba6daa..f0b2b3a 100644 --- a/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md +++ b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Documentation archive of a completed change plan to decommission a legacy SSH-based monitoring poller after migrating to Prometheus/Grafana. ## parent index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md diff --git a/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md index 30529eb..8f8463a 100644 --- a/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md +++ b/openspec/changes/archive/decommission-monitoring-poller/.pi-map.md @@ -4,11 +4,11 @@ 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. +Documentation archive of a completed change plan to decommission a legacy SSH-based monitoring poller after migrating 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. +Single-file markdown documentation following an "OpenSpec" change-management pattern, serving as a historical record of an infrastructure decommissioning plan. ## tags plan, draft, implementation, removing, legacy, ssh, monitoring, poller ## symbols diff --git a/openspec/changes/archive/jellyfin-service-registry/.pi-map.index.md b/openspec/changes/archive/jellyfin-service-registry/.pi-map.index.md index 819c456..69fed35 100644 --- a/openspec/changes/archive/jellyfin-service-registry/.pi-map.index.md +++ b/openspec/changes/archive/jellyfin-service-registry/.pi-map.index.md @@ -2,7 +2,7 @@ dir: openspec/changes/archive/jellyfin-service-registry ## role -Archived change specification for migrating Jellyfin configuration from machine-level settings to a service-registry model. +OpenSpec change proposal directory documenting the migration of Jellyfin service configuration from machine-level settings to a service-registry model. ## parent index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md diff --git a/openspec/changes/archive/jellyfin-service-registry/.pi-map.md b/openspec/changes/archive/jellyfin-service-registry/.pi-map.md index 281a7ee..ef2458f 100644 --- a/openspec/changes/archive/jellyfin-service-registry/.pi-map.md +++ b/openspec/changes/archive/jellyfin-service-registry/.pi-map.md @@ -4,13 +4,13 @@ dir: openspec/changes/archive/jellyfin-service-registry index: openspec/changes/archive/jellyfin-service-registry/.pi-map.index.md ## role -Archived change specification for migrating Jellyfin configuration from machine-level settings to a service-registry model. +OpenSpec change proposal directory documenting the migration of 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-document pattern consisting of three coordinated documents: proposal (rationale), design (technical approach), and tasks (implementation checklist). +Spec-driven planning package containing three structured Markdown artifacts (proposal, design, tasks) that define scope, technical approach, and implementation checklist for a single coordinated change. ## tags py, settings, backend, frontend, src, jellyfin, service, md ## symbols diff --git a/openspec/changes/archive/observability-service-registry/.pi-map.index.md b/openspec/changes/archive/observability-service-registry/.pi-map.index.md index 1079d0d..053169c 100644 --- a/openspec/changes/archive/observability-service-registry/.pi-map.index.md +++ b/openspec/changes/archive/observability-service-registry/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +OpenSpec change proposal package documenting the migration of observability services (Alertmanager, Grafana, Prometheus) 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 diff --git a/openspec/changes/archive/observability-service-registry/.pi-map.md b/openspec/changes/archive/observability-service-registry/.pi-map.md index c9ed684..63c6174 100644 --- a/openspec/changes/archive/observability-service-registry/.pi-map.md +++ b/openspec/changes/archive/observability-service-registry/.pi-map.md @@ -4,13 +4,13 @@ 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. +OpenSpec change proposal package documenting the migration of observability services (Alertmanager, Grafana, Prometheus) 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). +OpenSpec structured proposal pattern consisting of three documents: design.md (technical design), proposal.md (change justification and scope), and tasks.md (incremental implementation plan broken into 5 slices). ## tags design, document, observability, services, service, registry, proposal, environment ## symbols diff --git a/openspec/changes/archive/service-registry/.pi-map.index.md b/openspec/changes/archive/service-registry/.pi-map.index.md index 1dd0958..8288201 100644 --- a/openspec/changes/archive/service-registry/.pi-map.index.md +++ b/openspec/changes/archive/service-registry/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Archive of an OpenSpec change proposal that refactored a monolithic addon configuration system into a runtime, database-persisted service registry with UI-managed service instances. ## parent index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md diff --git a/openspec/changes/archive/service-registry/.pi-map.md b/openspec/changes/archive/service-registry/.pi-map.md index da8846c..82edc9a 100644 --- a/openspec/changes/archive/service-registry/.pi-map.md +++ b/openspec/changes/archive/service-registry/.pi-map.md @@ -4,14 +4,14 @@ 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. +Archive of an OpenSpec change proposal that refactored a monolithic addon configuration system into a runtime, database-persisted service registry with UI-managed service instances. ## 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. +OpenSpec change-management convention comprising four structured Markdown documents (proposal, design, tasks, apply-progress) that collectively define the rationale, technical design, phased implementation plan, and migration tracking for an architectural refactor. ## tags service, runtime, registry, addon, react, design, pydantic, sqlite ## symbols 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 index 80950b1..5d80e88 100644 --- a/openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md +++ b/openspec/changes/archive/unify-tasks-on-services/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +OpenSpec change specification for unifying saved task execution on SSH services by replacing machine-based references with service-based ones. ## parent index: openspec/changes/archive/.pi-map.index.md map: openspec/changes/archive/.pi-map.md diff --git a/openspec/changes/archive/unify-tasks-on-services/.pi-map.md b/openspec/changes/archive/unify-tasks-on-services/.pi-map.md index e6c2fdc..eb946e1 100644 --- a/openspec/changes/archive/unify-tasks-on-services/.pi-map.md +++ b/openspec/changes/archive/unify-tasks-on-services/.pi-map.md @@ -4,13 +4,13 @@ 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. +OpenSpec change specification for unifying saved task execution on SSH services by replacing machine-based references with service-based ones. ## 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. +Documentation-driven change proposal following a design-proposal-tasks pattern with markdown artifacts defining the rationale, technical design, and completed task breakdown for the refactoring effort. ## tags task, execution, ssh, tasks, design, saved, service, backend ## symbols diff --git a/openspec/changes/mobile-responsive-parity/.pi-map.index.md b/openspec/changes/mobile-responsive-parity/.pi-map.index.md new file mode 100644 index 0000000..dd2fd44 --- /dev/null +++ b/openspec/changes/mobile-responsive-parity/.pi-map.index.md @@ -0,0 +1,23 @@ +# openspec/changes/mobile-responsive-parity (index) +dir: openspec/changes/mobile-responsive-parity + +## role +Planning and specification package documenting the design, requirements, and implementation plan for achieving mobile-responsive UI parity across a React frontend application. +## parent +index: openspec/changes/.pi-map.index.md +map: openspec/changes/.pi-map.md +## children +- +## files +- design.md +- proposal.md +- spec.md +- tasks.md +- verify-report.md +## links +index: openspec/changes/mobile-responsive-parity/.pi-map.index.md +map: openspec/changes/mobile-responsive-parity/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/mobile-responsive-parity/.pi-map.md b/openspec/changes/mobile-responsive-parity/.pi-map.md new file mode 100644 index 0000000..94d4998 --- /dev/null +++ b/openspec/changes/mobile-responsive-parity/.pi-map.md @@ -0,0 +1,23 @@ +# openspec/changes/mobile-responsive-parity +dir: openspec/changes/mobile-responsive-parity + +index: openspec/changes/mobile-responsive-parity/.pi-map.index.md + +## role +Planning and specification package documenting the design, requirements, and implementation plan for achieving mobile-responsive UI parity across a React frontend application. +## files +- design.md | Design document detailing a mobile-responsive UI parity implementation plan for a React application. | dep: React, Vite, TanStack Query, TanStack Table, Tailwind CSS, shadcn/ui, Radix, lucide-react, react-router-dom, react-oidc-context +- proposal.md | A technical proposal document outlining the plan for achieving mobile responsive parity in the Manage frontend application. +- spec.md | Specification document defining mobile responsive parity requirements across all application routes, covering breakpoints, data tables, forms, touch targets, and dashboard layout. +- tasks.md | Project task breakdown plan for implementing mobile responsive UI parity across a frontend application in 10 incremental slices. | dep: frontend (React/TypeScript), Vitest, shadcn/ui, Tailwind CSS +- verify-report.md | Documents the verification phase results for a mobile-responsive UI parity change, detailing acceptance criteria, test outcomes, and residual risks. +## arch +OpenSpec change-based documentation pattern with sequential artifacts tracking the full lifecycle: design, proposal, specification, task breakdown, and verification report. +## tags +mobile, responsive, parity, react, application, design, document, plan +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/changes/services-as-hub-ia/.pi-map.index.md b/openspec/changes/services-as-hub-ia/.pi-map.index.md new file mode 100644 index 0000000..0ba1e83 --- /dev/null +++ b/openspec/changes/services-as-hub-ia/.pi-map.index.md @@ -0,0 +1,23 @@ +# openspec/changes/services-as-hub-ia (index) +dir: openspec/changes/services-as-hub-ia + +## role +Architectural change specification package documenting the reorganization of a media library application's information architecture to center around configured services rather than abstract concepts. +## parent +index: openspec/changes/.pi-map.index.md +map: openspec/changes/.pi-map.md +## children +- +## files +- design.md +- proposal.md +- spec.md +- tasks.md +- verify-report.md +## links +index: openspec/changes/services-as-hub-ia/.pi-map.index.md +map: openspec/changes/services-as-hub-ia/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/changes/services-as-hub-ia/.pi-map.md b/openspec/changes/services-as-hub-ia/.pi-map.md new file mode 100644 index 0000000..d0517cf --- /dev/null +++ b/openspec/changes/services-as-hub-ia/.pi-map.md @@ -0,0 +1,23 @@ +# openspec/changes/services-as-hub-ia +dir: openspec/changes/services-as-hub-ia + +index: openspec/changes/services-as-hub-ia/.pi-map.index.md + +## role +Architectural change specification package documenting the reorganization of a media library application's information architecture to center around configured services rather than abstract concepts. +## files +- design.md | Design document describing a refactoring of application architecture to organize around services as the central information architecture, including backend service registry changes and frontend IA refactoring. | dep: React 18, Vite, TanStack Query/Table, Tailwind v4, shadcn/ui, react-router-dom, FastAPI, SQLite +- proposal.md | This file is a technical proposal document outlining an architectural reorganization of an application to center its navigation and information architecture around configured services rather than abstract concepts. | dep: Authentik, Jellyfin, Alertmanager, Grafana, Prometheus, TanStack Query, SMTP +- spec.md | Specification document detailing a frontend information architecture reorganization around services as the central hub, including new service types, routing changes, and navigation requirements. +- tasks.md | Defines a 12-slice task breakdown for restructuring a media library viewer app's information architecture to be service-centric. | dep: backend (ruff, pytest), frontend (npm, vitest), jellyseerr, jellyfin, authentik, alertmanager, grafana, prometheus +- verify-report.md | Documents the verification phase results for a UI restructuring effort that reorganizes an application's information architecture around services. +## arch +OpenSpec change-management pattern with structured proposal-to-verification lifecycle documents (design, proposal, spec, tasks, verification) defining a service-centric IA refactoring across backend registry and frontend navigation. +## tags +architecture, information, around, services, design, document, application, service +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/specs/.pi-map.index.md b/openspec/specs/.pi-map.index.md index aea3fb8..46deb10 100644 --- a/openspec/specs/.pi-map.index.md +++ b/openspec/specs/.pi-map.index.md @@ -2,11 +2,17 @@ dir: openspec/specs ## role -Directory for OpenAPI/Swagger specification files defining API contracts, schemas, and endpoint documentation. +Specification directory defining project standards, schemas, or API contracts. ## parent index: openspec/.pi-map.index.md map: openspec/.pi-map.md ## children +- openspec/specs/prometheus-charting + index: openspec/specs/prometheus-charting/.pi-map.index.md + map: openspec/specs/prometheus-charting/.pi-map.md +- openspec/specs/service-storage + index: openspec/specs/service-storage/.pi-map.index.md + map: openspec/specs/service-storage/.pi-map.md - openspec/specs/web-ui index: openspec/specs/web-ui/.pi-map.index.md map: openspec/specs/web-ui/.pi-map.md diff --git a/openspec/specs/.pi-map.md b/openspec/specs/.pi-map.md index 52b93e6..9755be5 100644 --- a/openspec/specs/.pi-map.md +++ b/openspec/specs/.pi-map.md @@ -4,10 +4,10 @@ dir: openspec/specs index: openspec/specs/.pi-map.index.md ## role -Directory for OpenAPI/Swagger specification files defining API contracts, schemas, and endpoint documentation. +Specification directory defining project standards, schemas, or API contracts. ## files ## arch -Declarative YAML/JSON specification files following the OpenAPI standard, organized as static schema definitions without executable code. +Empty directory placeholder within an OpenSpec spec framework, intended to house declarative specification files. ## tags - ## symbols diff --git a/openspec/specs/prometheus-charting/.pi-map.index.md b/openspec/specs/prometheus-charting/.pi-map.index.md new file mode 100644 index 0000000..491ef5d --- /dev/null +++ b/openspec/specs/prometheus-charting/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/specs/prometheus-charting (index) +dir: openspec/specs/prometheus-charting + +## role +Specification package defining requirements for replacing Grafana with native Prometheus-backed metric visualization widgets in a Manage application. +## parent +index: openspec/specs/.pi-map.index.md +map: openspec/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/specs/prometheus-charting/.pi-map.index.md +map: openspec/specs/prometheus-charting/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/specs/prometheus-charting/.pi-map.md b/openspec/specs/prometheus-charting/.pi-map.md new file mode 100644 index 0000000..6b56e47 --- /dev/null +++ b/openspec/specs/prometheus-charting/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/specs/prometheus-charting +dir: openspec/specs/prometheus-charting + +index: openspec/specs/prometheus-charting/.pi-map.index.md + +## role +Specification package defining requirements for replacing Grafana with native Prometheus-backed metric visualization widgets in a Manage application. +## files +- spec.md | Specification defining the requirements for replacing Grafana with direct Prometheus-backed metric visualization widgets (chart, gauge, mean) in a Manage application. +## arch +Specification-driven document architecture capturing functional requirements, use cases, and integration design for direct Prometheus API-based charting components (chart, gauge, mean). +## tags +spec, specification, defining, requirements, replacing, grafana, direct, prometheus +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/specs/service-storage/.pi-map.index.md b/openspec/specs/service-storage/.pi-map.index.md new file mode 100644 index 0000000..986db20 --- /dev/null +++ b/openspec/specs/service-storage/.pi-map.index.md @@ -0,0 +1,19 @@ +# openspec/specs/service-storage (index) +dir: openspec/specs/service-storage + +## role +Specification document defining acceptance requirements and feature contracts for a per-service data storage and lifecycle layer. +## parent +index: openspec/specs/.pi-map.index.md +map: openspec/specs/.pi-map.md +## children +- +## files +- spec.md +## links +index: openspec/specs/service-storage/.pi-map.index.md +map: openspec/specs/service-storage/.pi-map.md +## workflows +- +## dirty +- diff --git a/openspec/specs/service-storage/.pi-map.md b/openspec/specs/service-storage/.pi-map.md new file mode 100644 index 0000000..beef5fc --- /dev/null +++ b/openspec/specs/service-storage/.pi-map.md @@ -0,0 +1,19 @@ +# openspec/specs/service-storage +dir: openspec/specs/service-storage + +index: openspec/specs/service-storage/.pi-map.index.md + +## role +Specification document defining acceptance requirements and feature contracts for a per-service data storage and lifecycle layer. +## files +- spec.md | Specification document defining acceptance requirements for a per-service data lifecycle/storage layer, qBittorrent integration, MediaIndex migration, and cascade-delete behavior. | dep: ServiceDataHarness, QbittorrentSampleStore, QbittorrentClient, QbittorrentWidgetSource, LineSeriesChart, MediaIndex, recharts, Fernet +## arch +Single-file markdown specification using numbered acceptance-criteria sections, integration contracts (qBittorrent, MediaIndex), and behavioral definitions (cascade-delete) without executable code. +## tags +mediaindex, spec, specification, document, defining, acceptance, requirements, per +## symbols +- +## workflows +- +## dirty +- diff --git a/openspec/specs/web-ui/.pi-map.index.md b/openspec/specs/web-ui/.pi-map.index.md index 8cd7d9d..1811c84 100644 --- a/openspec/specs/web-ui/.pi-map.index.md +++ b/openspec/specs/web-ui/.pi-map.index.md @@ -2,7 +2,7 @@ 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. +Defines the canonical specification for migrating the Manage web frontend 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 diff --git a/openspec/specs/web-ui/.pi-map.md b/openspec/specs/web-ui/.pi-map.md index a53481b..bfc7720 100644 --- a/openspec/specs/web-ui/.pi-map.md +++ b/openspec/specs/web-ui/.pi-map.md @@ -4,11 +4,11 @@ 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. +Defines the canonical specification for migrating the Manage web frontend 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. +Single-document technical specification outlining target technology stack, component patterns, and information architecture guidelines for a UI layer migration. ## tags react, shadcn, table, spec, defines, canonical, post, rework ## symbols