diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 36e6b30..d2ccc03 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,6 @@
# These are supported funding model platforms
-custom: ['https://www.buymeacoffee.com/bastien']
+# These are supported funding model platforms
+
+github: [bastienwirtz]
+buy_me_a_coffee: bastien
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index e0063d5..0bb193d 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -9,10 +9,11 @@ Fixes # (issue)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] Refactoring
## Checklist:
- [ ] I've read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md)
-- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
-- [ ] I have made corresponding changes to the documentation (README.md).
+- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
+- [ ] I have made corresponding changes to the documentation (`README.md`).
- [ ] I've checked my modifications for any breaking changes, especially in the `config.yml` file
diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml
index 38a5b8c..4b67502 100644
--- a/.github/workflows/dockerhub.yml
+++ b/.github/workflows/dockerhub.yml
@@ -44,3 +44,5 @@ jobs:
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
platforms: linux/amd64,linux/arm/v7,linux/arm/v6,linux/arm64
+ build-args: |
+ VERSION_TAG=${{ github.ref_name }}
diff --git a/.gitignore b/.gitignore
index ee51c88..6eb9005 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,4 +23,8 @@ yarn-error.log*
# App configuration
config.yml
-.drone.yml
\ No newline at end of file
+.drone.yml
+
+# Specific Agent file
+CLAUDE.md
+GEMINI.md
diff --git a/.jsconfig.json b/.jsconfig.json
new file mode 100644
index 0000000..7c82acb
--- /dev/null
+++ b/.jsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "exclude": ["node_modules", "dist"]
+ }
\ No newline at end of file
diff --git a/.schema/config-schema.json b/.schema/config-schema.json
new file mode 100644
index 0000000..f751668
--- /dev/null
+++ b/.schema/config-schema.json
@@ -0,0 +1,359 @@
+{
+ "$id": "https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema",
+ "description": "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md",
+ "examples": [],
+ "title": "Homer Dashboard configuration",
+ "type": "object",
+ "definitions": {
+ "Colors": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "light": {
+ "$ref": "#/definitions/ColorSet"
+ },
+ "dark": {
+ "$ref": "#/definitions/ColorSet"
+ }
+ },
+ "title": "Colors"
+ },
+ "ColorSet": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "highlight-primary": {
+ "type": "string"
+ },
+ "highlight-secondary": {
+ "type": "string"
+ },
+ "highlight-hover": {
+ "type": "string"
+ },
+ "background": {
+ "type": "string"
+ },
+ "card-background": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "text-header": {
+ "type": "string"
+ },
+ "text-title": {
+ "type": "string"
+ },
+ "text-subtitle": {
+ "type": "string"
+ },
+ "card-shadow": {
+ "type": "string"
+ },
+ "link": {
+ "type": "string"
+ },
+ "link-hover": {
+ "type": "string"
+ },
+ "background-image": {
+ "type": "string"
+ }
+ }
+ },
+ "Defaults": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "layout": {
+ "enum": [
+ "columns",
+ "list"
+ ],
+ "description": "Layout of the dashboard, either 'columns' or 'list'"
+ },
+ "colorTheme": {
+ "enum": [
+ "auto",
+ "light",
+ "dark"
+ ],
+ "description": "One of 'auto', 'light', or 'dark'"
+ }
+ },
+ "title": "Defaults"
+ },
+ "Hotkey": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "search": {
+ "type": "string",
+ "description": "hotkey for search, e.g. Shift"
+ }
+ },
+ "required": [
+ "search"
+ ]
+ },
+ "Link": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name as seen in the navbar"
+ },
+ "icon": {
+ "type": "string",
+ "description": "Fontawesome icon"
+ },
+ "url": {
+ "type": "string",
+ "description": "Url of the link. When #filename is used, it is a link to another homer page, while 'filename' is the name of the config file"
+ },
+ "target": {
+ "type": "string",
+ "description": "html tag target attribute like _blank for a new page"
+ }
+ },
+ "required": [
+ "url"
+ ],
+ "title": "Link"
+ },
+ "Message": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "mapping": {
+ "$ref": "#/definitions/Mapping",
+ "description": "Mapping for the content loaded from the URL"
+ },
+ "refreshInterval": {
+ "type": "integer",
+ "description": "The refresh interval in milliseconds for reloading the message url"
+ },
+ "style": {
+ "type": "string",
+ "description": "See https://bulma.io/documentation/components/message/#colors for styling options"
+ },
+ "title": {
+ "type": "string",
+ "description": "Title of the message box"
+ },
+ "icon": {
+ "type": "string",
+ "description": "Fontawesome icon for the message box"
+ },
+ "content": {
+ "type": "string",
+ "description": "HTML content for the message box"
+ }
+ },
+ "title": "Messagebox"
+ },
+ "Mapping": {
+ "type": "object",
+ "additionalProperties": true,
+ "title": "Mapping"
+ },
+ "Proxy": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "useCredentials": {
+ "type": "boolean",
+ "description": "# send cookies & authorization headers when fetching service specific data. Set to `true` if you use an authentication proxy. Can be overrided on service level. "
+ },
+ "headers": {
+ "$ref": "#/definitions/Headers",
+ "description": "send custom headers when fetching service specific data. Can also be set on a service level."
+ }
+ },
+ "title": "Proxy"
+ },
+ "Headers": {
+ "type": "object",
+ "additionalProperties": true,
+ "title": "Headers"
+ },
+ "Service": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Service name"
+ },
+ "icon": {
+ "type": "string",
+ "description": "Fontawesome icon for the service"
+ },
+ "logo": {
+ "type": "string",
+ "description": "A path to an image can also be provided. Note that icon take precedence if both icon and logo are set."
+ },
+ "class": {
+ "type": "string",
+ "description": "Optional css class to add on the service group. Example 'highlight-purple'"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Item"
+ }
+ }
+ },
+ "required": [
+ "items"
+ ],
+ "title": "Service"
+ },
+ "Item": {
+ "type": "object",
+ "additionalProperties": true,
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "logo": {
+ "type": "string",
+ "description": "Path to a logo. Alternatively a fa icon can be provided"
+ },
+ "icon": {
+ "type": "string",
+ "description": "Fontawesome icon for the item, alternative for logo"
+ },
+ "subtitle": {
+ "type": "string"
+ },
+ "tag": {
+ "type": "string",
+ "description": "Show tag"
+ },
+ "keywords": {
+ "type": "string",
+ "description": "Optional keyword used for searching purpose"
+ },
+ "url": {
+ "type": "string",
+ "description": "Url of this item"
+ },
+ "target": {
+ "type": "string",
+ "description": "html tag target attribute like _blank for a new page"
+ },
+ "tagstyle": {
+ "type": "string",
+ "description": "Styleclass for the tag"
+ },
+ "type": {
+ "type": "string",
+ "description": "Optional, loads a specific component that provides extra features. MUST MATCH a file name (without file extension) available in `src/components/services`"
+ }
+ },
+ "title": "Item"
+ }
+ },
+ "properties": {
+ "externalConfig": {
+ "type": "string",
+ "description": "Use external configuration file. Using this will ignore remaining config in this file externalConfig: https://example.com/server-luci/config.yaml"
+ },
+ "title": {
+ "type": "string",
+ "description": "Title of the dashboard"
+ },
+ "subtitle": {
+ "type": "string",
+ "description": "Subtitle of the dashboard"
+ },
+ "documentTitle": {
+ "type": "string",
+ "description": "Title of the document. When not filled, title (and subtitle will be used)"
+ },
+ "logo": {
+ "type": "string",
+ "description": "Path to logo image"
+ },
+ "icon": {
+ "type": "string",
+ "description": "Dashboard icon"
+ },
+ "header": {
+ "type": "boolean",
+ "description": "Show header, default is true"
+ },
+ "hotkey": {
+ "$ref": "#/definitions/Hotkey",
+ "description": "Define hotkeys, for example for search"
+ },
+ "footer": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "description": "footer Line content. HTML is supported. Set false if you want to hide it."
+ },
+ "columns": {
+ "type": "string",
+ "description": "'auto' or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)",
+ "format": "integer"
+ },
+ "connectivityCheck": {
+ "type": "boolean",
+ "description": "# whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example). You should set it to true when using an authentication proxy, it also reloads the page when a redirection is detected when checking connectivity."
+ },
+ "proxy": {
+ "$ref": "#/definitions/Proxy",
+ "description": "Optional: Proxy / hosting option"
+ },
+ "defaults": {
+ "$ref": "#/definitions/Defaults"
+ },
+ "theme": {
+ "type": "string",
+ "description": "'default' or one of the themes available in 'src/assets/themes'"
+ },
+ "stylesheet": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Will load custom CSS files. Especially useful for custom icon sets. Entries are paths to the stylesheets"
+ },
+ "colors": {
+ "$ref": "#/definitions/Colors"
+ },
+ "message": {
+ "$ref": "#/definitions/Message",
+ "description": "Messagebox"
+ },
+ "links": {
+ "description": "Links in the navigation bar",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Link"
+ }
+ },
+ "services": {
+ "description": "Services",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Service"
+ }
+ }
+ }
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..29d7dfb
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,84 @@
+# AGENTS Instructions
+
+This file provides guidance to AI Agents when working with code in this repository.
+
+## Development Commands
+
+```bash
+pnpm install # Install dependencies (PNPM enforced via packageManager)
+pnpm dev # Start development server on http://localhost:3000
+pnpm mock # Start mock API server for testing service integrations
+pnpm build # Build for production
+pnpm preview # Preview production build
+pnpm lint # Run ESLint with auto-fix
+```
+
+## Architecture Overview
+
+Homer is a static Vue.js 3 PWA dashboard that loads configuration from YAML files. The architecture is service-oriented with dynamic component loading.
+
+### Core Application Structure
+
+- **Entry Point**: `src/main.js` mounts the Vue app
+- **Root Component**: `src/App.vue` handles layout, configuration loading, and routing
+- **Configuration System**: YAML-based with runtime merging of defaults (`src/assets/defaults.yml`) and user config (`/assets/config.yml`)
+- **Service Components**: 53 specialized integrations in `src/components/services/` that extend a Generic component pattern
+
+### Service Integration Pattern
+
+All service components follow this architecture:
+
+- Extend `Generic.vue` using Vue slots (``, ``, ``)
+- Use the `service.js` mixin (`src/mixins/service.js`) for common API functionality
+- Use a custom `fetch` method provided by the service mixin to seamlessly support proxy configuration, custom headers, and credentials.
+
+### Configuration & Routing
+
+- **Multi-page Support**: Hash-based routing without Vue Router
+- **Dynamic Config Loading**: External URLs supported via `config.remote_config`
+- **Theme System**: CSS layers architecture with three built-in themes in `src/assets/themes/`
+- **Asset Management**: Static files served from `/assets/` with runtime configuration merging
+
+### Build System Details
+
+- **Vite 7**: Modern build tool with Vue plugin
+- **PWA**: Auto-updating service worker via `vite-plugin-pwa`
+- **SCSS**: Bulma framework with modular component styling
+- **Docker**: Multi-stage build (Node.js → Alpine + Lighttpd)
+
+### Mock Data Creation Pattern
+
+When creating mock data for service components testing:
+
+**Structure**: `dummy-data/[component-name]/[api-path]/[endpoint]`
+
+**Steps**:
+
+1. **Analyze component**: Read the Vue component file to identify API calls (look for `this.fetch()` calls)
+2. **Check existing mock**: If mock directory exists, read existing files to check for missing fields
+3. **Create/update structure**: `mkdir -p dummy-data/[lowercase-component-name]/` and mirror API endpoint paths
+4. **Create/update JSON files**: Write realistic mock responses matching the expected data structure
+5. **Verify fields**: Ensure all fields used in the component's computed properties and templates are included
+6. **Update existing mocks**: If mock files exist but are missing fields, add the missing fields without removing existing data
+
+**Key Points**:
+
+- Component directory name should be lowercase version of component name (e.g., `AdGuardHome.vue` → `adguardhome/`)
+- Directory structure mirrors API endpoints exactly
+- Files contain JSON responses (no file extension needed)
+- Mock server serves from `dummy-data/` via `pnpm mock` command
+- Each component gets isolated directory to prevent API path conflicts
+- When updating existing mocks, preserve existing data and only add missing fields required by the component
+- Always read existing mock files first to understand current structure before making changes
+
+**Example**: For `AdGuardHome.vue`:
+- API calls: `/control/status`, `/control/stats`
+- Mock files: `dummy-data/adguardhome/control/status`, `dummy-data/adguardhome/control/stats`
+
+### Development Notes
+
+- Use `pnpm mock` to test service integrations with dummy data
+- Configuration changes require restart in development mode
+- New service components should follow the Generic component slot pattern
+- Themes use CSS custom properties for dynamic color switching
+- The app has no backend dependencies and generates static files only
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index de893b6..4754b65 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,10 +6,10 @@ First off, thank you for considering contributing to Homer!
### Project philosophy
-Homer is meant to be a light and very simple dashboard that keeps all your useful utilities at hands. The few features implemented in Homer focus on
-UX and usability. If you are looking for a full featured dashboard, there is tons of great stuff out there like https://heimdall.site/, https://github.com/rmountjoy92/DashMachine or https://organizr.app/.
+Homer is meant to be a light and very simple dashboard that keeps all your useful utilities at hand. The few features implemented in Homer focus on
+UX and usability. If you are looking for a full featured dashboard, there are tons of great stuff out there like https://gethomepage.dev/, https://heimdall.site/, https://github.com/rmountjoy92/DashMachine or https://organizr.app/.
-- Configuration is stored in a simple config file, avoiding the need for a backend/database while making possible to use versioning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html).
+- Configuration is stored in a simple config file, avoiding the need for a backend/database while making it possible to use versioning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html).
- Only modern browsers are supported, feel free to use any JS features without any polyfill as soon as the latest version of the major browsers supports them.
@@ -33,10 +33,19 @@ For all contributions, please respect the following guidelines:
If you want to add a feature, it's often best to talk about it before starting to work on it and submitting a pull request. It's not mandatory at all, but
feel free to open an issue to present your idea.
+### Working with AI Agents
+
+This repository include an [`AGENTS.md`](https://github.com/bastienwirtz/homer/blob/main/AGENTS.md) instruction file for agents. It use an [open format](https://agents.md/), which most agent should natively use for context. However, for specific agent like Claude Code or Gemini, you will have to specifically ask it to read the file or create symlink:
+
+```sh
+ln -s AGENTS.md CLAUDE.md
+ln -s AGENTS.md GEMINI.md
+```
+
### How to submit a contribution
The general process to submit a contribution is as follow:
-1. Take a look to the [development guideline](https://github.com/bastienwirtz/homer/blob/main/docs/development.md).
+1. Take a look at the [development guideline](https://github.com/bastienwirtz/homer/blob/main/docs/development.md).
2. Create your own fork of the code
3. Do the changes in your fork
4. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly.
diff --git a/Dockerfile b/Dockerfile
index e0f6d66..1a55d92 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,10 @@
# build stage
-FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build-stage
+FROM --platform=$BUILDPLATFORM node:22-alpine3.21 AS build-stage
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
-RUN corepack enable && corepack use pnpm@9
+RUN corepack enable && corepack use pnpm@10
WORKDIR /app
@@ -15,7 +15,20 @@ COPY . .
RUN pnpm build
# production stage
-FROM alpine:3.20
+FROM alpine:3.21
+
+ARG VERSION_TAG=latest
+
+LABEL \
+ org.label-schema.schema-version="1.0" \
+ org.label-schema.version="$VERSION_TAG" \
+ org.opencontainers.image.title="Homer Image" \
+ org.opencontainers.image.description="A dead simple static Home-Page for your server to keep your services on hand, from a simple yaml configuration file." \
+ org.opencontainers.image.ref.name="b4bz/homer:${VERSION_TAG}" \
+ org.opencontainers.image.version="$VERSION_TAG" \
+ org.opencontainers.image.licenses="Apache-2.0 license" \
+ org.opencontainers.image.source="https://github.com/bastienwirtz/homer" \
+ org.opencontainers.image.url="https://hub.docker.com/r/b4bz/homer"
ENV GID=1000 \
UID=1000 \
@@ -37,8 +50,8 @@ COPY --from=build-stage --chown=${UID}:${GID} /app/dist/assets /www/default-asse
USER ${UID}:${GID}
-HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
- CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
+HEALTHCHECK --start-period=10s --start-interval=1s --interval=30s --timeout=5s --retries=3 \
+ CMD wget --no-verbose -Y off --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
EXPOSE ${PORT}
diff --git a/README.md b/README.md
index d9e45f0..8d9efb0 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@
•
Docker Hub
•
- Get started
+ Get started
@@ -121,17 +121,6 @@ If you would like to change internal port of Homer from default `8080` to your p
- **`IPV6_DISABLE`** (default: 0)
Set to `1` to disable listening on IPv6.
-#### With docker-compose
-
-A [`docker-compose.yml`](docker-compose.yml) file is available as an example. It must be edited to match your needs. You probably want to adjust the port mapping and volume binding (equivalent to `-p` and `-v` arguments).
-
-Then launch the container:
-
-```sh
-cd /path/to/docker-compose.yml/
-docker-compose up -d
-```
-
### Using the release tarball (prebuilt, ready to use)
Download and extract the latest release (`homer.zip`) from the [release page](https://github.com/bastienwirtz/homer/releases), rename the `assets/config.yml.dist` file to `assets/config.yml`, and put it behind a web server.
diff --git a/docs/configuration.md b/docs/configuration.md
index 6fb47b8..6db5883 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,6 +1,6 @@
# Configuration
-Homer rely on a single [yaml](http://yaml.org/) configuration file, located in the `/assets` directory.
+Homer relies on a single [yaml](http://yaml.org/) configuration file, located in the `/assets` directory.
`.dist` sample configuration files are available to help you get started. Alternatively, the example below can be
copied into the config file.
@@ -183,9 +183,9 @@ Empty values (either in `config.yml` or the endpoint data) will hide the element
## Connectivity checks
As a webapp (PWA) the dashboard can still be displayed when your homer server is offline.
-The connectivity checker periodically send a HEAD request bypassing the PWA cache to the dashbord page to make sure it's still reachable.
+The connectivity checker periodically sends a HEAD request bypassing the PWA cache to the dashbord page to make sure it's still reachable.
-It can be useful when you access your dashboard through a VPN or ssh tunnel for example, to know is your conection is up. It also helps when using an authentication proxy, it will reloads the page if the authentication expires (when a redirection is send in response to the HEAD request).
+It can be useful when you access your dashboard through a VPN or ssh tunnel for example, to know if your conection is up. It also helps when using an authentication proxy, it will reload the page if the authentication expires (when a redirect is send in response to the HEAD request).
## Style Options
@@ -206,9 +206,10 @@ The `/assets/manifest.json` can also be edited to change the app (pwa) name, des
### Community theme
-- [Dracula theme](https://draculatheme.com/homer) by [@Tuetenk0pp](https://github.com/Tuetenk0pp)
-- [Homer Theme v2](https://github.com/walkxcode/homer-theme) by [walkxcode](https://github.com/walkxcode)
- [Catppuccin theme](https://github.com/mrpbennett/catppucin-homer) by [@mrpbenett](https://github.com/mrpbennett)
+- [DietPi theme](https://codeberg.org/Cs137/homer-theme-dietpi) by [@Cs137](https://codeberg.org/Cs137)
+- [Dracula theme](https://draculatheme.com/homer) by [@Tuetenk0pp](https://github.com/Tuetenk0pp)
+- [Homer Theme v2](https://github.com/walkxcode/homer-theme) by [@walkxcode](https://github.com/walkxcode)
## PWA Icons
diff --git a/docs/customservices.md b/docs/customservices.md
index d8af4c5..d1b2e8c 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -1,21 +1,25 @@
# Smart cards
-Some cards can use a specific a component that provides some extra features by adding a `type` key to the service yaml
-configuration and other parameters when needed.
+Smart cards provide specific integrations for external services. They display additional information and extra features beyond basic service card. Smart cards are enabled by adding a `type` key to the service item in your YAML configuration.
+
+Each service integration has different requirements and may need additional configuration parameters (see card list below).
> [!WARNING]
-> Note that `config.yml` is exposed at `/assets/config.yml` via HTTP and any sensitive information, like api keys,
-> included in the configuration file is exposed to anyone who can access the homer instance. Only include an api key
-> if your homer instance is secured behind some form of authentication or access restriction.
+> Your `config.yml` file is exposed at `/assets/config.yml` via HTTP. Any sensitive information (like API keys)
+> in this file is visible to anyone who can access your Homer instance. Only include API keys if your Homer
+> instance is protected by authentication or access controls **or use a proxy like [`CORSair`](https://github.com/bastienwirtz/corsair)
+> to inject your credentials safely**, using environment variable on the server side.
-Available services are in `src/components/`. Here is an overview of all smart cards that are available
-within Homer:
+Available services are located in `src/components/`:
- [Common options](#common-options)
- [AdGuard Home](#adguard-home)
- [CopyToClipboard](#copy-to-clipboard)
+- [Docuseal](#docuseal)
+- [Docker Socket Proxy](#docker-socket-proxy)
- [Emby / Jellyfin](#emby--jellyfin)
- [FreshRSS](#freshrss)
+- [Gatus](#gatus)
- [Gitea / Forgejo](#gitea--forgejo)
- [Glances](#glances)
- [Gotify](#gotify)
@@ -24,17 +28,22 @@ within Homer:
- [Immich](#immich)
- [Jellystat](#jellystat)
- [Lidarr, Prowlarr, Sonarr, Readarr and Radarr](#lidarr-prowlarr-sonarr-readarr-and-radarr)
+- [Linkding](#linkding)
+- [Matrix](#matrix)
- [Mealie](#mealie)
- [Medusa](#medusa)
+- [Miniflux](#miniflux)
- [Nextcloud](#nextcloud)
- [OctoPrint / Moonraker](#octoprintmoonraker)
+- [Olivetin](#olivetin)
- [OpenHAB](#openhab)
- [OpenWeatherMap](#openweathermap)
-- [PaperlessNG](#paperlessng)
+- [Paperless-NGX](#paperlessng)
- [PeaNUT](#peanut)
- [PiAlert](#pialert)
- [PiHole](#pihole)
- [Ping](#ping)
+- [Plex](#plex)
- [Portainer](#portainer)
- [Prometheus](#prometheus)
- [Proxmox](#proxmox)
@@ -45,95 +54,167 @@ within Homer:
- [Speedtest Tracker](#speedtesttracker)
- [Tautulli](#tautulli)
- [Tdarr](#tdarr)
+- [Traefik](#traefik)
+- [Transmission](#transmission)
+- [TrueNas Scale](#truenas-scale)
- [Uptime Kuma](#uptime-kuma)
+- [Vaultwarden](#vaultwarden)
+- [Wallabag](#wallabag)
- [What's Up Docker](#whats-up-docker)
> [!IMPORTANT]
-> Using smart cards will probably requires
-> If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md#my-service-card-doesnt-work-nothing-appears-or-offline-status-is-displayed-pi-hole-sonarr-ping-) page.
+> Smart cards that interact with external services are subject to CORS restrictions, therefore require one of the following:
+>
+> - All services hosted on the **same domain** as Homer (mydomain.tld/pihole, mydomain.tld/proxmox) to avoid cross-domain request entirely.
+> - All services configured to **accept cross-site requests** by sending the necessary CORS headers (either directly in service configuration or via proxy).
+> - **Use a proxy** to add the necessary CORS headers (lot of options, some of them described [here](https://enable-cors.org/server.html). Also check [`CORSair`](https://github.com/bastienwirtz/corsair), a light and simple solution)
+>
+> If you experience any issues, see the [troubleshooting](troubleshooting.md#my-service-card-doesnt-work-nothing-appears-or-offline-status-is-displayed-pi-hole-sonarr-ping-) page.
## Common options
```yaml
- name: "My Service"
- logo: "assets/tools/sample.png"
- url: "http://my-service-link"
- endpoint: "http://my-service-endpoint" # Optional: alternative base URL used to fetch service data is necessary.
+ type: ""
+ logo: "assets/tools/sample.png" # Optional
+ url: https://my-service.url # Optional: Card link and API base url unless 'endpoint' is provided (see below)
+ endpoint: https://my-service-api.url # Optional: alternative base URL used to fetch service data when necessary.
useCredentials: false # Optional: Override global proxy.useCredentials configuration.
headers: # Optional: Override global proxy.headers configuration.
- type: ""
```
+If a subtitle is provided, (using the `subtitle` configuration key), **it will override (hide)** any custom information displayed on the subtitle line by the custom integration.
+
## AdGuard Home
-For AdGuard Home you need to set the type to AdGuard, if you have some issues as 403 responses on requests you need to provide authentication in headers for locations needed as below.
+Displays AdGuard Home protection status and blocked query statistics.
```yaml
-- name: "Adguard"
- logo: "assets/tools/adguardhome.png"
- url: "https://adguard.exemple.com"
- target: "_blank"
+- name: "AdGuard Home"
type: "AdGuardHome"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
```
+> **Note**: If AdGuard Home’s web user is password-protected, you must pass Authorization HTTP header along with all requests. It can be done using a proxy or adding the following to the item configuration:
+>
+> ```yaml
+> headers:
+> Authorization: "Basic "
+> ```
+
## Copy to Clipboard
-This service displays the same information of a generic one, but shows an icon button on the indicator place (right side) you can click to get the content of the `clipboard` field copied to your clipboard.
-
-You can still provide an `url` that would be open when clicked anywhere but on the icon button.
-
-Configuration example:
+Displays a service card with a copy button that copies the specified text to your clipboard when clicked.
```yaml
- name: "Copy me!"
- logo: "assets/tools/sample.png"
- subtitle: "Subtitle text goes here"
- url: "#"
type: "CopyToClipboard"
+ logo: "assets/tools/sample.png"
+ subtitle: "Click the copy icon to copy text"
clipboard: "this text will be copied to your clipboard"
+ url: "https://optional-link.com" # optional: opens when clicking the card (not the copy button)
+```
+
+## Docker Socket Proxy
+
+Displays counts of running, stopped, and error containers from Docker Socket Proxy.
+
+```yaml
+- name: "Docker"
+ type: "DockerSocketProxy"
+ logo: "assets/tools/sample.png"
+ endpoint: "https://my-service-api.url:port"
+```
+
+## Docuseal
+
+Displays the Docuseal version.
+
+```yaml
+- name: Docuseal
+ type: Docuseal
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
```
## Emby / Jellyfin
-You need to set the type to Emby, provide an api key and choose which stats to show if the subtitle is disabled.
+Displays stats from your Emby or Jellyfin server.
+The `libraryType` configuration let you choose which stats to show.
```yaml
- name: "Emby"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151/"
type: "Emby"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
- libraryType: "music" #Choose which stats to show. Can be one of: music, series or movies.
+ libraryType: "music" # Choose which stats to show. Can be one of: music, series or movies.
```
## FreshRSS
-The FreshRSS service displays unread and subscriptions counts from your FreshRSS server.
+Displays unread article count and total subscriptions from your FreshRSS server.
```yaml
- name: "FreshRSS"
type: "FreshRSS"
- username: "<-- Your username -->"
- password: "<-- Your password -->"
+ url: https://my-service.url
updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
+ username: "<---your-username--->"
+ password: "<---your-password--->"
+```
+
+## Gatus
+
+The Gatus service displays information about the configured services from the defined Gatus server.
+Two lines are needed in the config.yml :
+
+```yaml
+ type: "Gatus"
+ url: "http://192.168.0.151/gatus"
+
+```
+
+Optionally, the results can be filtered to only include jobs in the defined groups:
+```yaml
+ groups: [Services, External]
+```
+
+The status can be checked regularly by defining an update Interval in ms:
+```yaml
+ updateInterval: 5000
+```
+
+The average times can be hidden (saves their calculation also) by setting the following:
+```yaml
+ hideaverages: true
```
## Gitea / Forgejo
-This service displays a version string instead of a subtitle. Example configuration:
+Displays a Gitea / Forgejo version.
```yaml
- name: Forgejo
type: Gitea
- logo: assets/tools/sample.png
- url: http://git.example.com
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
```
## Glances
-This is a basic widget for showing cpu and ram usage using a glances server
+Displays system metrics (CPU, memory, swap, load) from a Glances server.
-You'll need a glances server up and running, this is a sample compose.yml
+```yaml
+- name: "System Metrics"
+ type: "Glances"
+ icon: "fa-solid fa-heart-pulse"
+ url: https://my-service.url
+ stats: [cpu, mem] # Options: load, cpu, mem, swap
+```
+
+If you don't already have a glances server up and running, here is a sample Docker compose file to get you started:
```yml
---
@@ -149,176 +230,241 @@ services:
restart: unless-stopped
```
-And this is a sample homer configuration
-
-```yml
-- name: System
- icon: "fa-solid fa-heart-pulse"
- url: http://192.168.1.2:61208
- type: Glances
- stats: [cpu, mem] # Metric to display. Possible values are: load, cpu, mem, swap.
- updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
-```
-
## Gotify
-The Gotify service will show the number of currently oustanding messages
-available as well as the overall health of the system.
-
-Note that `apikey` must be a client token, not an app token.
+Displays the number of outstanding messages and system health status.
```yaml
- name: "Gotify"
type: "Gotify"
- apikey: "" # Client token to retrieve messages
+ url: https://my-service.url
+ apikey: "<---insert-client-token-here--->"
```
+**API Token**: Use a **client token** (not an app token).
+
## Healthchecks
-This service displays information about the configured status checks from the Healthchecks application.
-Two lines are needed in the config.yml :
+Displays status counts (up/down/grace) from your Healthchecks monitoring service.
```yaml
+- name: "Healthchecks"
type: "Healthchecks"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
```
-The url must be the root url of the Healthchecks application.
-The Healthchecks API key can be found in Settings > API Access > API key (read-only). The key is needed to access Healthchecks API.
+**API Key**: Found in Healthchecks web interface under **Settings > API Access > API key (read-only)**.
## Home Assistant
-You need to set the type to HomeAssistant, provide an api key and enable cors on Home Assistant.
+Displays Home Assistant instance status, version, location, and entity count.
```yaml
-- name: "HomeAssistant"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151/"
+- name: "Home Assistant"
type: "HomeAssistant"
- apikey: "<---insert-api-key-here--->"
- items: [] # optional, which items to show (and in which order) in the subtitle. Possible values are "name", "version", "entities"
- separator: " " # optional, how to separate items
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ apikey: "<---insert-long-lived-access-token-here--->"
+ items: [] # optional: "name", "version", "entities"
+ separator: " " # optional
```
-To create an API token on HomeAssistant, follow the [official documentation here](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token).
-To enable cors on HomeAssistant, edit your `configuration.yml` and add the IP of Homer to `https: cors_allowed_origins`
+**API Token**: Create a long-lived access token in Home Assistant:
+1. Go to **Profile > Security > Long-lived access tokens**
+2. Click **Create Token**
+
+**CORS Configuration**: Edit Home Assistant `configuration.yml` and add Homer's IP:
+```yaml
+http:
+ cors_allowed_origins:
+ - "http://homer.local:8080"
+ - "https://your-homer-domain.com"
+```
## Immich
-The Immich service displays stats from your Immich server.
-The Immich server must be running at least version 1.118.0 for the correct api endpoint to work.
+Displays user count, photo/video counts, and storage usage from your Immich server.
```yaml
- name: "Immich"
type: "Immich"
- apikey: "<--- Your api key --->" # administrator user
- updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
-```
-
-## Jellystat
-
-The Jellystat service display the number of concurrent streams on your jellyfin server.
-The Jellystat server must be running behind a reverse proxy to add some cors headers:
-
-- Access-Control-Allow-Origin: ${your_domain}
-- Access-Control-Allow-Headers: Authorization
-
-```yaml
-- name: "Jellystat"
- logo: "assets/tools/jellystat.png"
- url: "http://192.168.1.154:3000"
- type: "Jellystat"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
```
-You can create an API key in the dashboard of you jellystat server: settings/API Keys -> Add Key
+**Requirements**: Immich server version `1.118.0` or later
+**API Key**: Create an API key in Immich web interface under **Administration > API Keys**
+
+## Jellystat
+
+Display the number of concurrent streams on your jellyfin server.
+
+```yaml
+- name: "Jellystat"
+ type: "Jellystat"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ apikey: "<---insert-api-key-here--->"
+```
+
+**API Key**: You can create an API key in the dashboard of you jellystat server: settings/API Keys -> Add Key
+
## Lidarr, Prowlarr, Sonarr, Readarr and Radarr
-This service displays Activity (blue), Missing(purple) Warning (orange) or Error (red) notifications bubbles from the Lidarr, Readarr, Radarr or Sonarr application.
-Two lines are needed in the config.yml :
+Displays Activity (blue), Missing (purple) Warning (orange) or Error (red) notifications bubbles from the Lidarr, Readarr, Radarr or Sonarr application.
+Two lines are needed in the `config.yml`:
```yaml
- type: "Lidarr", "Prowlarr", "Radarr" or "Sonarr"
+- name: "Lidarr"
+ type: "Lidarr" # "Lidarr" "Prowlarr", "Radarr" or "Sonarr"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ checkInterval: 5000 # (Optional) Interval (in ms) for updating the status
apikey: "<---insert-api-key-here--->"
```
The url must be the root url of Lidarr, Prowlarr, Readarr, Radarr or Sonarr application.
-The Lidarr, Prowlarr, Readarr, Radarr or Sonarr API key can be found in Settings > General. It is needed to access the API.
-If you are using an older version of Radarr or Sonarr which don't support the new V3 api endpoints, add the following line to your service config "legacyApi: true", example:
+
+**API Key**: The Lidarr, Prowlarr, Readarr, Radarr or Sonarr API key can be found in `Settings` > `General`. It is needed to access the API.
+
+> [!IMPORTANT]
+> **Radarr API V3 support**: If you are using an older version of Radarr or Sonarr which don't support the new V3 api endpoints, add the following line to your service config `"legacyApi: true"`
+
+## Linkding
+
+This integration makes it possible to query Linkding and list multiple results from Linkding.
+Linkding has to be configured with CORS enabled. Linkding does not support that, but a reverse proxy in front can fix that.
+This integration supports at max 15 results from Linkding, but you can add it multiple times to you dashboard with different queries to retrieve what you need.
```yaml
-- name: "Radarr"
- type: "Radarr"
- url: "http://localhost:7878/"
- apikey: "<---insert-api-key-here--->"
- target: "_blank"
- legacyApi: true
+- name: "Linkding"
+ type: "Linkding"
+ url: https://my-service.url
+ token: "<---insert-api-key-here--->"
+ limit: 10 # Maximum number of items returned by Linkding, minimal 1 and max 15
+ query: "#ToDo #Homer" # query to do on Linkding. Use #tagname to search for tags
+```
+
+## Matrix
+
+Displays a Matrix version, and shows if the server is online.
+
+```yaml
+- name: "Matrix - Server"
+ type: "Matrix"
+ logo: "assets/tools/sample.png"
+ url: "http://matrix.example.com"
```
## Mealie
-First off make sure to remove an existing `subtitle` as it will take precedence if set.
-Setting `type: "Mealie"` will then show the number of recipes Mealie is keeping organized or the planned meal for today if one is planned. You will have to set an API key in the field `apikey` which can be created in your Mealie installation.
+Displays the number of recipes Mealie is keeping organized or the planned meal for today if one is planned.
+
+```yaml
+- name: "Mealie"
+ type: "Mealie"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ apikey: "<---insert-api-key-here--->"
+```
+
+**API Key**: You will have to set an API key in the field `apikey` which can be created in your Mealie installation.
+The API page can be found: Click on hamburger menu -> Click on your profile -> Click on "Manage your API Tokens"
## Medusa
-This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
-Two lines are needed in the config.yml :
+Displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
```yaml
+- name: "Medusa"
type: "Medusa"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
```
The url must be the root url of Medusa application.
-The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
+
+**API Key**: The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
+
+## Miniflux
+
+Displays the number of unread articles from your Miniflux RSS reader.
+
+```yaml
+- name: "Miniflux"
+ type: "Miniflux"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ apikey: "<---insert-api-key-here--->"
+ style: "status" # Either "status" or "counter"
+ checkInterval: 60000 # Optional: Interval (in ms) for updating the unread count
+```
+
+**API Key**: Generate an API key in Miniflux web interface under **Settings > API Keys > Create a new API key**
## Nextcloud
-This service displays a version string instead of a subtitle. The indicator
-shows if Nextcloud is online, offline, or in [maintenance
+Displays Nextcloud version and shows if Nextcloud is online, offline, or in [maintenance
mode](https://docs.nextcloud.com/server/stable/admin_manual/maintenance/upgrade.html#maintenance-mode).
-Example configuration:
```yaml
- name: Nextcloud
type: Nextcloud
logo: assets/tools/sample.png
- url: http://nextcloud.example.com
+ url: https://my-service.url
```
## OctoPrint/Moonraker
The OctoPrint/Moonraker service only needs an `apikey` & `endpoint` and optionally a `display` or `url` option. `url` can be used when you click on the service it will launch the `url`
-
Moonraker's API mimmicks a few of OctoPrint's endpoints which makes these services compatible. See for details.
```yaml
- name: "Octoprint"
- logo: "https://cdn-icons-png.flaticon.com/512/3112/3112529.png"
- apikey: "xxxxxxxxxxxx" # insert your own API key here.
- endpoint: "http://192.168.0.151:8080"
- display: "text" # 'text' or 'bar'. Default to `text`.
type: "OctoPrint"
+ logo: assets/tools/sample.png
+ endpoint: "https://my-service-api.url:port"
+ apikey: "<---insert-api-key-here--->"
+ display: "text" # 'text' or 'bar'. Default to `text`.
+
+```
+
+## Olivetin
+
+Displays a Olivetin version.
+
+```yaml
+- name: Olivetin
+ type: Olivetin
+ logo: assets/tools/sample.png
+ url: https://my-service.url
```
## OpenHAB
-You need to set the type to OpenHAB, provide an api key and enable cors on OpenHAB.
+Displays OpenHAB system status, things count, and items count.
```yaml
- name: "OpenHAB"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151/"
type: "OpenHAB"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
- things: true # true will query the things API and report total and online things count. false will skip the call
- items: true # true will query the items API and report total items count. false will skip the call
+ things: true # query things API for counts
+ items: true # query items API for counts
```
-To create an API token on OpenHAB, follow the [official documentation here](https://www.openhab.org/docs/configuration/apitokens.html).
-To enable cors on OpenHAB, edit your services/runtime.cfg and uncomment or add this line: `org.openhab.cors:enable=true`
+**API Token**: Create an API token following the [official OpenHAB documentation](https://www.openhab.org/docs/configuration/apitokens.html)
+
+**CORS Configuration**: Edit `services/runtime.cfg` and add:
+
+```ini
+org.openhab.cors:enable=true
+```
## OpenWeatherMap
@@ -327,71 +473,83 @@ The following configuration is available for the OpenWeatherMap service:
```yaml
- name: "Weather"
+ type: "OpenWeather"
+ apikey: "<---insert-api-key-here--->" # Request one from https://openweathermap.org/api.
location: "Amsterdam" # your location.
locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
- apikey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
background: "square" # choose which type of background you want behind the image. Can be one of: square, circle, none. Defaults to none.
- type: "OpenWeather"
+
```
**Remarks:**
If for some reason your city can't be found by entering the name in the `location` property, you could also try to configure the OWM city ID in the `locationId` property. To retrieve your specific City ID, go to the [OWM website](https://openweathermap.org), search for your city and retrieve the ID from the URL (for example, the City ID of Amsterdam is 2759794).
-## PaperlessNG
+## Paperless-NGX
-This service displays total number of documents stored. Two lines are required:
+Displays total number of documents stored.
```yaml
+- name: "Paperless"
type: "PaperlessNG"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
```
-API key can be generated in Settings > Administration > Auth Tokens
+**API Key**: API key can be generated in Settings > Administration > Auth Tokens
## PeaNUT
-This service show current status of the UPS device. By default, the subtitle line shows UPS load, unless you provide the `subtitle` property
+Displays current status and UPS load of the UPS device.
```yaml
- name: "PeaNUT"
type: PeaNUT
logo: "assets/tools/sample.png"
- url: "http://192.168.0.151"
+ url: https://my-service.url
# device: "ups" # The ID of the device
```
## PiAlert
-The PiAlert service displays stats from your PiAlert server.
+Displays stats from your PiAlert server.
```yaml
- name: "PiAlert"
type: "PiAlert"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
```
## PiHole
-Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
-
-The following configuration is available for the PiHole service.
+Displays info about your local PiHole instance right on your Homer dashboard.
```yaml
- name: "Pi-hole"
- logo: "assets/tools/sample.png"
- # subtitle: "Network-wide Ad Blocking" # optional, if no subtitle is defined, PiHole statistics will be shown
- url: "http://192.168.0.151/admin"
- apikey: "<---insert-api-key-here--->" # optional, needed if web interface is password protected
type: "PiHole"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ # endpoint: "https://my-service-api.url" # optional, For v6 API, this is the base URL used to fetch Pi-hole data overwriting the url
+ apikey: "<---insert-api-key-here--->" # optional, needed if web interface is password protected
+ apiVersion: 5 # optional, defaults to 5. Use 6 if your PiHole instance uses API v6
+ checkInterval: 3000 # optional, defaults to 300000. interval in ms to check Pi-hole status
```
-**Remarks:**
-If PiHole web interface is password protected, obtain the `apikey` from Settings > API/Web interface > Show API token.
+**API Key**: Required only if Pi-hole web interface is password protected. Go to **Settings > API/Web Interface > Show API token**
+
+**API Versions**:
+
+- **v5** (default): Uses legacy API endpoints
+- **v6**: Uses modern API with session management - set `apiVersion: 6`
## Ping
-This card checks if the target link is available. All you need is to set the `type` to `Ping` and provide a url. By default the HEAD method is used but it can be configured to use GET using the optional `method` property. By default, the subtitle line shows the round trip time (RTT) of the request, unless you provide the `subtitle` property. Optionnaly, use `successCodes` to define which HTTP response status codes should be considered as available status.
+Checks if the target link is available and displays the round trip time (RTT) of the request.
+By default the HEAD method is used but it can be configured to use GET using the optional `method` property.
+Optionally, use `successCodes` to define which HTTP response status codes should be considered as available status.
```yaml
- name: "Awesome app"
@@ -399,65 +557,64 @@ This card checks if the target link is available. All you need is to set the `ty
logo: "assets/tools/sample.png"
url: "https://www.wikipedia.org/"
# method: "head"
- # successCodes: [200, 418] # optional, default to all 2xx HTTP response status codes
- # timeout: 500 # in ms. default 2000
- # subtitle: "Bookmark example" # By default, request round trip time is displayed when subtitle is not set.
+ # successCodes: [200, 418] # Optional, default to all 2xx HTTP response status codes
+ # timeout: 500 # Timeout in ms before ping is aborted. Default 2000
+ # subtitle: "Bookmark example" # By default, request round trip time is displayed when subtitle is not set
+ # updateInterval: 5000 # (Optional) Interval (in ms) for updating ping status
```
+## Plex
+
+Displays active streams, total movies, and total TV series from your Plex server.
+
+```yaml
+- name: "Plex"
+ type: "Plex"
+ logo: "assets/tools/sample.png"
+ url: "https://my-service.url/web"
+ endpoint: "https://my-service.url"
+ token: "<---insert-plex-token-here--->"
+```
+
+**Plex Token**: See [How to find your Plex token](https://www.plexopedia.com/plex-media-server/general/plex-token/)
+
## Portainer
-This service displays info about the total number of containers managed by your Portainer instance.
-In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass
-it to the apikey field.
-By default, every connected environments will be checked. To select specific ones, add an "environments" entry which can be a simple string or an array containing all the selected environments name.
-
-See
+Displays container counts (running/dead/misc), version, and online status from your Portainer instance.
```yaml
- name: "Portainer"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151/"
type: "Portainer"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
apikey: "<---insert-api-key-here--->"
- # environments:
- # - "raspberry"
- # - "local"
+ environments: # optional: specific environments to check
+ - "raspberry"
+ - "local"
```
+**Requirements**: Portainer version 1.11 or later
+
+**API Key**: Generate an access token in Portainer UI. See [Creating an Access Token](https://docs.portainer.io/api/access#creating-an-access-token)
+
## Prometheus
-For Prometheus you need to set the type to Prometheus and provide a url.
-
```yaml
- name: "Prometheus"
- type: Prometheus
+ type: "Prometheus"
logo: "assets/tools/sample.png"
- url: "http://192.168.0.151/"
- # subtitle: "Monitor data server"
+ url: https://my-service.url
```
## Proxmox
-This service displays status information of a Proxmox node (VMs running and disk, memory and cpu used). It uses the proxmox API and [API Tokens](https://pve.proxmox.com/pve-docs/pveum-plain.html) for authorization so you need to generate one to set in the yaml config. You can set it up in Proxmox under Permissions > API Tokens. You also need to know the realm the user of the API Token is assigned to (by default pam).
-
-The API Token (or the user assigned to that token if not separated permissions is checked) are this:
-
-| Path | Permission | Comments |
-|--------------------|------------|-------------------------------------------------------------------|
-| /nodes/ | Sys.Audit | |
-| /vms/ | VM.Audit | You need to have this permission on any VM you want to be counted |
-
-It is highly recommended that you create and API Token with only these permissions on a read-only mode.
-
-If you get errors, they will be shown on browser's dev console. Main issues tend to be CORS related as Proxmox does not include CORS headers and you have to deploy it behind a reverse proxy and make the proxy add this headers.
-
-Configuration example:
+Displays status information of a Proxmox node (VMs running and disk, memory and cpu used).
```yaml
- name: "Proxmox - Node"
- logo: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fgithub.com%2FandOTP%2FandOTP%2Fissues%2F337&psig=AOvVaw2YKVuEUIBeTUikr7kAjm8D&ust=1665323538747000&source=images&cd=vfe&ved=0CAkQjRxqFwoTCPCTruLj0PoCFQAAAAAdAAAAABAN"
type: "Proxmox"
- url: "https://your.proxmox.server"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
node: "your-node-name"
warning_value: 50
danger_value: 80
@@ -469,40 +626,48 @@ Configuration example:
small_font_on_desktop: true # uses small font on desktops (just in case you're showing much info)
```
+**API Key**: You can set it up in Proxmox under Permissions > API Tokens. You also need to know the realm the user of the API Token is assigned to (by default pam).
+
+The API Token (or the user assigned to that token if not separated permissions is checked) are this:
+
+| Path | Permission | Comments |
+|---------------------|------------|-------------------------------------------------------------------|
+| /nodes/\ | Sys.Audit | |
+| /vms/\ | VM.Audit | You need to have this permission on any VM you want to be counted |
+
+It is highly recommended that you create and API Token with only these permissions on a read-only mode.
+
## qBittorrent
-This service displays the global upload and download rates, as well as the number of torrents
+Displays the global upload and download rates, as well as the number of torrents
listed. The service communicates with the qBittorrent API interface which needs
to be accessible from the browser. Please consult
[the instructions](https://github.com/qbittorrent/qBittorrent/pull/12579)
-for setting up qBittorrent and make sure the correct CORS-settings are applied. Examples for various
-servers can be found at [enable-cors.org](https://enable-cors.org/server.html).
+for setting up qBittorrent.
```yaml
- name: "qBittorrent"
- logo: "assets/tools/sample.png"
- url: "http://192.168.1.2:8080" # Your rTorrent web UI, f.e. ruTorrent or Flood.
type: "qBittorrent"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url # Your rTorrent web UI, f.e. ruTorrent or Flood.
rateInterval: 2000 # Interval for updating the download and upload rates.
torrentInterval: 5000 # Interval for updating the torrent count.
- target: "_blank" # optional html a tag target attribute
```
## rTorrent
-This service displays the global upload and download rates, as well as the number of torrents
+Displays the global upload and download rates, as well as the number of torrents
listed in rTorrent. The service communicates with the rTorrent XML-RPC interface which needs
to be accessible from the browser. Please consult
[the instructions](https://github.com/rakshasa/rtorrent-doc/blob/master/RPC-Setup-XMLRPC.md)
-for setting up rTorrent and make sure the correct CORS-settings are applied. Examples for various
-servers can be found at https://enable-cors.org/server.html.
+for setting up rTorrent.
```yaml
- name: "rTorrent"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151" # Your rTorrent web UI, f.e. ruTorrent or Flood.
- xmlrpc: "http://192.168.0.151:8081" # Reverse proxy for rTorrent's XML-RPC.
type: "Rtorrent"
+ logo: "assets/tools/sample.png"
+ url: "https://my-service.url" # Your rTorrent web UI, f.e. ruTorrent or Flood.
+ xmlrpc: "https://my-service.url:port" # Reverse proxy for rTorrent's XML-RPC.
rateInterval: 5000 # Interval for updating the download and upload rates.
torrentInterval: 60000 # Interval for updating the torrent count.
username: "username" # Username for logging into rTorrent (if applicable).
@@ -511,110 +676,169 @@ servers can be found at https://enable-cors.org/server.html.
## SABnzbd
-The SABnzbd service can allow you to show the number of currently active
-downloads on your SABnzbd instance. An API key is required, and can be obtained from
-the "Config" > "General" section of the SABnzbd config in the SABnzbd web UI.
+Displays the number of currently active downloads on your SABnzbd instance.
```yaml
- name: "SABnzbd"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151:8080"
type: "SABnzbd"
- apikey: "MY-SUPER-SECRET-API-KEY"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ apikey: "<---insert-api-key-here--->"
downloadInterval: 5000 # (Optional) Interval (in ms) for updating the download count
```
+**API Key**: An API key is required, and can be obtained from the "Config" > "General" section of the SABnzbd config in the web UI.
+
## Scrutiny
-This service displays info about the total number of disk passed and failed S.M.A.R.T and scrutiny checks
+Displays info about the total number of disk passed and failed S.M.A.R.T and scrutiny checks
```yaml
- name: "Scrutiny"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151:8080"
type: "Scrutiny"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
updateInterval: 5000 # (Optional) Interval (in ms) for updating the status
```
## SpeedtestTracker
-This service will show the download and upload speeds in Mbit/s and the ping in ms.
-To configure the service, you need to define the url of SpeedtestTracker running and an entry with type `SpeedtestTracker`.
-
-Configuration example:
+Displays the download and upload speeds in Mbit/s and the ping in ms.
```yaml
- name: "Speedtest Tracker"
type: "SpeedtestTracker"
- url: "http://192.168.0.1:8080"
- target: "_blank"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
```
## Tautulli
-The Tautulli service can allow you to show the number of currently active
-streams on you Plex instance. An API key is required, and can be obtained from
-the "Web Interface" section of settings on the Tautulli web UI.
+Displays the number of currently active streams on you Plex instance.
```yaml
- name: "Tautulli"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151:8181"
type: "Tautulli"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ checkInterval: 5000 # (Optional) Interval (in ms) for updating the status
apikey: "<---insert-api-key-here--->"
```
+**API Key**: An API key is required, and can be obtained from the "Web Interface" section of settings on the Tautulli web UI.
+
Because the service type and link don't necessarily have to match, you could
even make the service type Tautulli on your Plex card and provide a separate
endpoint pointing to Tautulli!
```yaml
- name: "Plex"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151:32400/web" # Plex
- endpoint: "http://192.168.0.151:8181" # Tautulli
type: "Tautulli"
+ logo: "assets/tools/sample.png"
+ url: https://my-plex.url/web # Plex
+ endpoint: https://my-tautulli.url # Tautulli
apikey: "<---insert-api-key-here--->"
```
## Tdarr
-The Tdarr service can allow you to show the number of currently queued items
-for transcoding on your Tdarr instance as well as the number of errored items.
+Displays the number of currently queued items for transcoding on your Tdarr instance as well as the number of errored items.
```yaml
- name: "Tdarr"
- logo: "assets/tools/sample.png"
- url: "http://192.168.0.151:8265"
type: "Tdarr"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
checkInterval: 5000 # (Optional) Interval (in ms) for updating the queue & error counts
```
+## Traefik
+
+Displays Traefik.
+
+```yaml
+- name: "Traefik"
+ type: "Traefik"
+ logo: "assets/tools/sample.png"
+ url: "http://traefik.example.com"
+ # basic_auth: "admin:password" # (Optional) Send Authorization header.
+```
+
+**Authentication**: If BasicAuth is set, credentials will be encoded in Base64 and sent as an Authorization header (`Basic `). The value must be formatted as "admin:password".
+
+## Transmission
+
+Displays the global upload and download rates, as well as the number of active torrents from your Transmission daemon.
+The service communicates with the Transmission RPC interface which needs to be accessible from the browser.
+
+```yaml
+- name: "Transmission"
+ logo: "assets/tools/sample.png"
+ url: "http://192.168.1.2:9091" # Your Transmission web interface URL
+ type: "Transmission"
+ auth: "username:password" # Optional: HTTP Basic Auth
+ interval: 5000 # Optional: Interval for refreshing data (ms)
+ target: "_blank" # Optional: HTML a tag target attribute
+```
+
+The service automatically handles Transmission's session management and CSRF protection.
+
+## Truenas Scale
+
+Displays TrueNAS version.
+
+```yaml
+- name: "Truenas"
+ type: "TruenasScale"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ api_token: "<---insert-api-key-here--->"
+```
+
## Uptime Kuma
-Using the Uptime Kuma service you can display info about your instance uptime right on your Homer dashboard.
-
-The following configuration is available for the UptimeKuma service. Needs v1.13.1 or later because of the change in APIs due to [multiple status pages support](https://github.com/louislam/uptime-kuma/releases/tag/1.13.1).
+Displays overall status, uptime percentage, and incident information from your Uptime Kuma status page.
```yaml
- name: "Uptime Kuma"
- logo: "assets/tools/sample.png"
- # subtitle: "A fancy self-hosted monitoring tool" # optional, if no subtitle is defined, Uptime Kuma incidents, if any, will be shown
- url: "http://192.168.0.151:3001"
- slug: "myCustomDashboard" # Defaults to "default" if not provided.
type: "UptimeKuma"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ slug: "default" # status page slug, defaults to "default"
+```
+
+**Requirements**: Uptime Kuma version `1.13.1` or later (for [multiple status pages support](https://github.com/louislam/uptime-kuma/releases/tag/1.13.1))
+
+## Vaultwarden
+
+Displays Vaultwarden version and status.
+
+```yaml
+- name: "Vaultwarden - Server"
+ type: "Vaultwarden"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+```
+
+## Wallabag
+
+Displays Wallabag version.
+
+```yaml
+- name: Wallabag
+ type: Wallabag
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
```
## What's up Docker
-What's up Docker allow to display info about the number of container running and the number for which an update is available on your Homer dashboard.
-
-The following configuration is available for the WUD service.
+Display info about the number of container running and the number for which an update is available on your Homer dashboard.
```yaml
- name: "What's Up Docker"
- logo: "assets/tools/sample.png"
- subtitle: "Docker image update notifier"
- url: "http://192.168.1.12:3001"
type: "WUD"
+ logo: "assets/tools/sample.png"
+ url: https://my-service.url
+ subtitle: "Docker image update notifier"
```
diff --git a/docs/tips-and-tricks.md b/docs/tips-and-tricks.md
index 992bc0e..437b586 100644
--- a/docs/tips-and-tricks.md
+++ b/docs/tips-and-tricks.md
@@ -6,7 +6,8 @@ Here is a collection of neat tips and tricks that Homer users have come up with!
Great source to find service icons
--
+-
+-
## Use Homer as a custom "new tab" page
@@ -77,6 +78,21 @@ Then when Homer reads your config, it will substitute your anchors automatically
The end result is that if you want to update the name or style of any particular tag, just update it once, in the tags section!
Great if you have a lot of services or a lot of tags!
+## YAML auto complete with a YAML schema
+
+A lot of editor support auto completion, see
+The homer schema is available here:
+
+For example with IntelliJ you can define:
+
+```yaml
+# $schema: https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json
+```
+With VSCode you can define it like this:
+```yaml
+# yaml-language-server: $schema=https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json
+```
+
## Remotely edit your config with Code Server
#### `by @JamiePhonic`
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index a7d9158..3f49272 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -41,7 +41,7 @@ To resolve this, you can either:
- Host all your target service under the same domain & port.
- Modify the target server configuration so that the response of the server included following header- `Access-Control-Allow-Origin: *` (). It might be an option in the targeted service, otherwise depending on how the service is hosted, the proxy or web server can seamlessly add it.
-- Use a cors proxy server like [`cors-container`](https://github.com/imjacobclark/cors-container), [`cors-anywhere`](https://github.com/Rob--W/cors-anywhere) or many others.
+- **Use a proxy** to add the necessary CORS headers (lot of options, some of them described [here](https://enable-cors.org/server.html). Also check [`CORSair`](https://github.com/bastienwirtz/corsair), a light and simple solution)
## I am using an authentication proxy and homer says I am offline
diff --git a/dummy-data/adguardhome/control/stats b/dummy-data/adguardhome/control/stats
new file mode 100644
index 0000000..c2c8815
--- /dev/null
+++ b/dummy-data/adguardhome/control/stats
@@ -0,0 +1,51 @@
+{
+ "time_units": "hours",
+ "num_dns_queries": 28947,
+ "num_blocked_filtering": 12489,
+ "num_replaced_safebrowsing": 0,
+ "num_replaced_safesearch": 0,
+ "num_replaced_parental": 0,
+ "avg_processing_time": 0.34,
+ "top_queried_domains": [
+ {
+ "name": "example.com",
+ "count": 1289
+ },
+ {
+ "name": "api.github.com",
+ "count": 892
+ }
+ ],
+ "top_clients": [
+ {
+ "name": "192.168.1.100",
+ "count": 8945
+ },
+ {
+ "name": "192.168.1.101",
+ "count": 6234
+ }
+ ],
+ "top_blocked_domains": [
+ {
+ "name": "ads.google.com",
+ "count": 1245
+ },
+ {
+ "name": "tracker.example.com",
+ "count": 987
+ }
+ ],
+ "dns_queries": [
+ 12450, 13200, 14100, 13800, 12900, 11200, 10800, 9600, 8200, 7800,
+ 9200, 10500, 12100, 13600, 14800, 15200, 14900, 13700, 12800, 11900,
+ 11200, 10800, 10200, 9800
+ ],
+ "blocked_filtering": [
+ 5200, 5800, 6100, 5900, 5400, 4800, 4600, 4200, 3600, 3400,
+ 4000, 4500, 5200, 5800, 6300, 6500, 6300, 5800, 5400, 5100,
+ 4800, 4600, 4300, 4200
+ ],
+ "replaced_safebrowsing": [],
+ "replaced_parental": []
+}
\ No newline at end of file
diff --git a/dummy-data/adguardhome/control/status b/dummy-data/adguardhome/control/status
new file mode 100644
index 0000000..3c47165
--- /dev/null
+++ b/dummy-data/adguardhome/control/status
@@ -0,0 +1,16 @@
+{
+ "protection_enabled": true,
+ "version": "v0.107.48",
+ "language": "en",
+ "dns_address": "127.0.0.1:53",
+ "dns_port": 53,
+ "protection_disabled_duration": null,
+ "http_port": 80,
+ "https_port": 443,
+ "querylog_enabled": true,
+ "querylog_size": 5000,
+ "querylog_size_memory": 1000,
+ "querylog_interval": 2160,
+ "dhcp_available": true,
+ "running": true
+}
\ No newline at end of file
diff --git a/dummy-data/dockersocketproxy/containers/json b/dummy-data/dockersocketproxy/containers/json
new file mode 100644
index 0000000..e41c72f
--- /dev/null
+++ b/dummy-data/dockersocketproxy/containers/json
@@ -0,0 +1,215 @@
+[
+ {
+ "Id": "8dfafdbc3a40",
+ "Names": ["/boring_feynman"],
+ "Image": "nginx:latest",
+ "ImageID": "sha256:f6d0b4767a6c",
+ "Command": "/docker-entrypoint.sh nginx -g 'daemon off;'",
+ "Created": 1640995200,
+ "Ports": [
+ {
+ "IP": "0.0.0.0",
+ "PrivatePort": 80,
+ "PublicPort": 8080,
+ "Type": "tcp"
+ }
+ ],
+ "Labels": {
+ "maintainer": "NGINX Docker Maintainers "
+ },
+ "State": "running",
+ "Status": "Up 2 hours",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "f2de39df4171",
+ "EndpointID": "2cdc4edb1ded",
+ "Gateway": "172.17.0.1",
+ "IPAddress": "172.17.0.2",
+ "IPPrefixLen": 16,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "02:42:ac:11:00:02"
+ }
+ }
+ },
+ "Mounts": []
+ },
+ {
+ "Id": "9e87a2b84b8e",
+ "Names": ["/web-app"],
+ "Image": "node:16-alpine",
+ "ImageID": "sha256:c85b8f829d1f",
+ "Command": "npm start",
+ "Created": 1640991600,
+ "Ports": [
+ {
+ "IP": "0.0.0.0",
+ "PrivatePort": 3000,
+ "PublicPort": 3000,
+ "Type": "tcp"
+ }
+ ],
+ "Labels": {},
+ "State": "running",
+ "Status": "Up 3 hours",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "f2de39df4171",
+ "EndpointID": "3edc5fdb2efe",
+ "Gateway": "172.17.0.1",
+ "IPAddress": "172.17.0.3",
+ "IPPrefixLen": 16,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "02:42:ac:11:00:03"
+ }
+ }
+ },
+ "Mounts": [
+ {
+ "Type": "bind",
+ "Source": "/home/user/app",
+ "Destination": "/app",
+ "Mode": "",
+ "RW": true,
+ "Propagation": "rprivate"
+ }
+ ]
+ },
+ {
+ "Id": "7b9a3c6d2e1f",
+ "Names": ["/database"],
+ "Image": "postgres:13",
+ "ImageID": "sha256:b4ed8d5b4f3a",
+ "Command": "docker-entrypoint.sh postgres",
+ "Created": 1640988000,
+ "Ports": [
+ {
+ "IP": "127.0.0.1",
+ "PrivatePort": 5432,
+ "PublicPort": 5432,
+ "Type": "tcp"
+ }
+ ],
+ "Labels": {},
+ "State": "dead",
+ "Status": "Up 4 hours",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "f2de39df4171",
+ "EndpointID": "4fdc6gdb3gfg",
+ "Gateway": "172.17.0.1",
+ "IPAddress": "172.17.0.4",
+ "IPPrefixLen": 16,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": "02:42:ac:11:00:04"
+ }
+ }
+ },
+ "Mounts": [
+ {
+ "Type": "volume",
+ "Name": "postgres_data",
+ "Source": "/var/lib/docker/volumes/postgres_data/_data",
+ "Destination": "/var/lib/postgresql/data",
+ "Driver": "local",
+ "Mode": "rw",
+ "RW": true,
+ "Propagation": ""
+ }
+ ]
+ },
+ {
+ "Id": "5c8d1f4e9a2b",
+ "Names": ["/old-service"],
+ "Image": "ubuntu:20.04",
+ "ImageID": "sha256:f643c72bc252",
+ "Command": "/bin/bash",
+ "Created": 1640984400,
+ "Ports": [],
+ "Labels": {},
+ "State": "exited",
+ "Status": "Exited (0) 2 hours ago",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "",
+ "EndpointID": "",
+ "Gateway": "",
+ "IPAddress": "",
+ "IPPrefixLen": 0,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": ""
+ }
+ }
+ },
+ "Mounts": []
+ },
+ {
+ "Id": "1a2b3c4d5e6f",
+ "Names": ["/backup-job"],
+ "Image": "alpine:latest",
+ "ImageID": "sha256:c059bfaa849c",
+ "Command": "sh -c 'sleep 3600'",
+ "Created": 1640980800,
+ "Ports": [],
+ "Labels": {},
+ "State": "exited",
+ "Status": "Exited (0) 30 minutes ago",
+ "HostConfig": {
+ "NetworkMode": "default"
+ },
+ "NetworkSettings": {
+ "Networks": {
+ "bridge": {
+ "IPAMConfig": null,
+ "Links": null,
+ "Aliases": null,
+ "NetworkID": "",
+ "EndpointID": "",
+ "Gateway": "",
+ "IPAddress": "",
+ "IPPrefixLen": 0,
+ "IPv6Gateway": "",
+ "GlobalIPv6Address": "",
+ "GlobalIPv6PrefixLen": 0,
+ "MacAddress": ""
+ }
+ }
+ },
+ "Mounts": []
+ }
+]
\ No newline at end of file
diff --git a/dummy-data/docuseal/version b/dummy-data/docuseal/version
new file mode 100644
index 0000000..1912e3b
--- /dev/null
+++ b/dummy-data/docuseal/version
@@ -0,0 +1 @@
+1.8.3a
diff --git a/dummy-data/emby/System/info/public b/dummy-data/emby/System/info/public
new file mode 100644
index 0000000..ca727dc
--- /dev/null
+++ b/dummy-data/emby/System/info/public
@@ -0,0 +1,24 @@
+{
+ "LocalAddress": "192.168.1.100:8096",
+ "ServerName": "Homer-Emby-Server",
+ "Version": "4.8.8.0",
+ "ProductName": "Emby Server",
+ "OperatingSystem": "Linux",
+ "Id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
+ "StartupWizardCompleted": true,
+ "SupportsLibraryMonitor": true,
+ "WebSocketPortNumber": 8096,
+ "CompletedInstallations": [],
+ "CanSelfRestart": true,
+ "CanSelfUpdate": true,
+ "CanLaunchWebBrowser": false,
+ "WanAddress": "192.168.1.100:8096",
+ "HasUpdateAvailable": false,
+ "SupportsAutoRunAtStartup": false,
+ "TranscodingTempPath": "/var/lib/emby/transcoding-temp",
+ "CachePath": "/var/lib/emby/cache",
+ "LogPath": "/var/log/emby",
+ "InternalMetadataPath": "/var/lib/emby/metadata",
+ "ItemsByNamePath": "/var/lib/emby/metadata/People",
+ "ProgramDataPath": "/var/lib/emby"
+}
\ No newline at end of file
diff --git a/dummy-data/emby/items/counts b/dummy-data/emby/items/counts
new file mode 100644
index 0000000..4c5243f
--- /dev/null
+++ b/dummy-data/emby/items/counts
@@ -0,0 +1,12 @@
+{
+ "MovieCount": 1247,
+ "SeriesCount": 89,
+ "EpisodeCount": 2156,
+ "ArtistCount": 234,
+ "AlbumCount": 567,
+ "SongCount": 8923,
+ "MusicVideoCount": 42,
+ "BoxSetCount": 23,
+ "BookCount": 156,
+ "ItemCount": 13437
+}
\ No newline at end of file
diff --git a/dummy-data/gatus/api/v1/endpoints/statuses b/dummy-data/gatus/api/v1/endpoints/statuses
new file mode 100644
index 0000000..e8817fa
--- /dev/null
+++ b/dummy-data/gatus/api/v1/endpoints/statuses
@@ -0,0 +1,211 @@
+[
+ {
+ "name": "Gateway",
+ "group": "Services",
+ "key": "services_gateway",
+ "results": [
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": true,
+ "timestamp": "2025-05-26T07:35:41.784208588Z"
+ },
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": true,
+ "timestamp": "2025-05-26T07:40:41.804489793Z"
+ },
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": true,
+ "timestamp": "2025-05-26T07:45:41.837925713Z"
+ },
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": true,
+ "timestamp": "2025-05-26T07:50:41.848391366Z"
+ }
+ ]
+ },
+ {
+ "name": "Website",
+ "group": "External",
+ "key": "external_website",
+ "results": [
+ {
+ "status": 200,
+ "hostname": "www.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": false
+ }
+ ],
+ "success": false,
+ "timestamp": "2025-05-26T07:35:41.784208588Z"
+ },
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": false
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": false,
+ "timestamp": "2025-05-26T07:40:41.804489793Z"
+ },
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": true,
+ "timestamp": "2025-05-26T07:45:41.837925713Z"
+ },
+ {
+ "status": 200,
+ "hostname": "gateway.example.com",
+ "duration": 10000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": true
+ },
+ {
+ "condition": "[RESPONSE_TIME] < 500",
+ "success": true
+ }
+ ],
+ "success": true,
+ "timestamp": "2025-05-26T07:50:41.848391366Z"
+ }
+ ]
+ },
+ {
+ "name": "DNS",
+ "group": "Services",
+ "key": "services_dns",
+ "results": [
+ {
+ "status": 200,
+ "hostname": "ns1.example",
+ "duration": 20000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": false
+ }
+ ],
+ "success": false,
+ "timestamp": "2025-05-26T07:35:41.784208588Z"
+ },
+ {
+ "status": 200,
+ "hostname": "ns1.example.com",
+ "duration": 20000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": false
+ }
+ ],
+ "success": false,
+ "timestamp": "2025-05-26T07:40:41.804489793Z"
+ },
+ {
+ "status": 200,
+ "hostname": "ns1.example.com",
+ "duration": 20000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": false
+ }
+ ],
+ "success": false,
+ "timestamp": "2025-05-26T07:45:41.837925713Z"
+ },
+ {
+ "status": 200,
+ "hostname": "ns1.example.com",
+ "duration": 20000000,
+ "conditionResults": [
+ {
+ "condition": "[STATUS] == 200",
+ "success": false
+ }
+ ],
+ "success": false,
+ "timestamp": "2025-05-26T07:50:41.848391366Z"
+ }
+ ]
+ }
+]
diff --git a/dummy-data/gitea/swagger.v1.json b/dummy-data/gitea/swagger.v1.json
new file mode 100644
index 0000000..0c92ac8
--- /dev/null
+++ b/dummy-data/gitea/swagger.v1.json
@@ -0,0 +1,11 @@
+{
+ "info": {
+ "description": "This documentation describes the Forgejo API.",
+ "title": "Forgejo API",
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/MIT"
+ },
+ "version": "8.0.3+gitea-1.22.0"
+ }
+}
diff --git a/dummy-data/gotify/health b/dummy-data/gotify/health
new file mode 100644
index 0000000..6df9f42
--- /dev/null
+++ b/dummy-data/gotify/health
@@ -0,0 +1,4 @@
+{
+ "health": "green",
+ "database": "green"
+}
\ No newline at end of file
diff --git a/dummy-data/gotify/message b/dummy-data/gotify/message
new file mode 100644
index 0000000..3909775
--- /dev/null
+++ b/dummy-data/gotify/message
@@ -0,0 +1,65 @@
+{
+ "messages": [
+ {
+ "id": 1,
+ "appid": 1,
+ "message": "System backup completed successfully",
+ "title": "Backup Service",
+ "priority": 2,
+ "date": "2024-01-15T10:30:00Z"
+ },
+ {
+ "id": 2,
+ "appid": 2,
+ "message": "Database optimization finished",
+ "title": "Database Manager",
+ "priority": 1,
+ "date": "2024-01-15T09:15:00Z"
+ },
+ {
+ "id": 3,
+ "appid": 1,
+ "message": "Server restart scheduled for maintenance",
+ "title": "System Admin",
+ "priority": 5,
+ "date": "2024-01-15T08:45:00Z"
+ },
+ {
+ "id": 4,
+ "appid": 3,
+ "message": "New user registration: john.doe@example.com",
+ "title": "User Management",
+ "priority": 1,
+ "date": "2024-01-15T07:20:00Z"
+ },
+ {
+ "id": 5,
+ "appid": 2,
+ "message": "Weekly report generated and sent",
+ "title": "Report Generator",
+ "priority": 2,
+ "date": "2024-01-14T18:00:00Z"
+ },
+ {
+ "id": 6,
+ "appid": 4,
+ "message": "Security scan completed - no threats detected",
+ "title": "Security Monitor",
+ "priority": 2,
+ "date": "2024-01-14T16:30:00Z"
+ },
+ {
+ "id": 7,
+ "appid": 1,
+ "message": "Disk usage is at 85% on /var partition",
+ "title": "System Monitor",
+ "priority": 4,
+ "date": "2024-01-14T14:15:00Z"
+ }
+ ],
+ "paging": {
+ "size": 7,
+ "since": 0,
+ "limit": 100
+ }
+}
\ No newline at end of file
diff --git a/dummy-data/healthchecks/api/v1/checks b/dummy-data/healthchecks/api/v1/checks
new file mode 100644
index 0000000..8db7e92
--- /dev/null
+++ b/dummy-data/healthchecks/api/v1/checks
@@ -0,0 +1,95 @@
+{
+ "checks": [
+ {
+ "name": "Database Backup",
+ "tags": "backup database",
+ "desc": "Daily database backup job",
+ "grace": 3600,
+ "n_pings": 127,
+ "status": "up",
+ "last_ping": "2024-01-15T10:30:00+00:00",
+ "next_ping": "2024-01-16T10:30:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
+ },
+ {
+ "name": "Web Server Monitoring",
+ "tags": "web server nginx",
+ "desc": "Monitor web server health",
+ "grace": 300,
+ "n_pings": 2847,
+ "status": "up",
+ "last_ping": "2024-01-15T10:25:00+00:00",
+ "next_ping": "2024-01-15T10:30:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "b2c3d4e5-f6g7-8901-bcde-f23456789012"
+ },
+ {
+ "name": "SSL Certificate Check",
+ "tags": "ssl certificate",
+ "desc": "Weekly SSL certificate expiry check",
+ "grace": 86400,
+ "n_pings": 52,
+ "status": "up",
+ "last_ping": "2024-01-14T12:00:00+00:00",
+ "next_ping": "2024-01-21T12:00:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "c3d4e5f6-g7h8-9012-cdef-345678901234"
+ },
+ {
+ "name": "Log Cleanup Service",
+ "tags": "cleanup logs maintenance",
+ "desc": "Weekly log file cleanup",
+ "grace": 7200,
+ "n_pings": 15,
+ "status": "grace",
+ "last_ping": "2024-01-13T02:00:00+00:00",
+ "next_ping": "2024-01-20T02:00:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "d4e5f6g7-h8i9-0123-defa-456789012345"
+ },
+ {
+ "name": "Email Service",
+ "tags": "email smtp",
+ "desc": "Email service availability check",
+ "grace": 600,
+ "n_pings": 0,
+ "status": "down",
+ "last_ping": "2024-01-12T08:15:00+00:00",
+ "next_ping": "2024-01-15T08:15:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "e5f6g7h8-i9j0-1234-efab-567890123456"
+ },
+ {
+ "name": "API Health Check",
+ "tags": "api health",
+ "desc": "External API endpoint health monitoring",
+ "grace": 180,
+ "n_pings": 1440,
+ "status": "up",
+ "last_ping": "2024-01-15T10:28:00+00:00",
+ "next_ping": "2024-01-15T10:30:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "f6g7h8i9-j0k1-2345-fabc-678901234567"
+ },
+ {
+ "name": "Backup Verification",
+ "tags": "backup verify",
+ "desc": "Verify backup integrity",
+ "grace": 1800,
+ "n_pings": 45,
+ "status": "grace",
+ "last_ping": "2024-01-14T22:30:00+00:00",
+ "next_ping": "2024-01-15T22:30:00+00:00",
+ "manual_resume": false,
+ "methods": "",
+ "unique_key": "g7h8i9j0-k1l2-3456-gbcd-789012345678"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dummy-data/homeassistant/api/config b/dummy-data/homeassistant/api/config
new file mode 100644
index 0000000..209ee32
--- /dev/null
+++ b/dummy-data/homeassistant/api/config
@@ -0,0 +1,45 @@
+{
+ "location_name": "Home",
+ "latitude": 40.7128,
+ "longitude": -74.0060,
+ "elevation": 10,
+ "unit_system": {
+ "length": "km",
+ "mass": "kg",
+ "pressure": "Pa",
+ "temperature": "°C",
+ "volume": "L"
+ },
+ "time_zone": "America/New_York",
+ "components": [
+ "automation",
+ "climate",
+ "device_tracker",
+ "frontend",
+ "history",
+ "light",
+ "logger",
+ "media_player",
+ "recorder",
+ "script",
+ "sensor",
+ "switch",
+ "system_health",
+ "weather"
+ ],
+ "config_dir": "/config",
+ "allowlist_external_dirs": [
+ "/config",
+ "/share"
+ ],
+ "allowlist_external_urls": [],
+ "version": "2024.1.5",
+ "config_source": "storage",
+ "recovery_mode": false,
+ "state": "RUNNING",
+ "external_url": null,
+ "internal_url": null,
+ "currency": "USD",
+ "country": "US",
+ "language": "en"
+}
\ No newline at end of file
diff --git a/dummy-data/homeassistant/api/states b/dummy-data/homeassistant/api/states
new file mode 100644
index 0000000..2cc8989
--- /dev/null
+++ b/dummy-data/homeassistant/api/states
@@ -0,0 +1,141 @@
+[
+ {
+ "entity_id": "sensor.living_room_temperature",
+ "state": "22.5",
+ "attributes": {
+ "unit_of_measurement": "°C",
+ "device_class": "temperature",
+ "friendly_name": "Living Room Temperature"
+ },
+ "last_changed": "2024-01-15T10:30:00+00:00",
+ "last_updated": "2024-01-15T10:30:00+00:00",
+ "context": {
+ "id": "01HMV123456789",
+ "parent_id": null,
+ "user_id": null
+ }
+ },
+ {
+ "entity_id": "light.bedroom_ceiling",
+ "state": "on",
+ "attributes": {
+ "brightness": 180,
+ "color_mode": "brightness",
+ "supported_color_modes": ["brightness"],
+ "friendly_name": "Bedroom Ceiling Light"
+ },
+ "last_changed": "2024-01-15T09:15:00+00:00",
+ "last_updated": "2024-01-15T09:15:00+00:00",
+ "context": {
+ "id": "01HMV234567890",
+ "parent_id": null,
+ "user_id": "user123"
+ }
+ },
+ {
+ "entity_id": "switch.coffee_maker",
+ "state": "off",
+ "attributes": {
+ "friendly_name": "Coffee Maker"
+ },
+ "last_changed": "2024-01-14T22:00:00+00:00",
+ "last_updated": "2024-01-14T22:00:00+00:00",
+ "context": {
+ "id": "01HMV345678901",
+ "parent_id": null,
+ "user_id": null
+ }
+ },
+ {
+ "entity_id": "climate.living_room",
+ "state": "heat",
+ "attributes": {
+ "temperature": 21.0,
+ "current_temperature": 20.5,
+ "hvac_modes": ["off", "heat", "cool", "auto"],
+ "min_temp": 7.0,
+ "max_temp": 35.0,
+ "target_temp_step": 0.5,
+ "friendly_name": "Living Room Thermostat"
+ },
+ "last_changed": "2024-01-15T08:00:00+00:00",
+ "last_updated": "2024-01-15T10:25:00+00:00",
+ "context": {
+ "id": "01HMV456789012",
+ "parent_id": null,
+ "user_id": "user123"
+ }
+ },
+ {
+ "entity_id": "sensor.front_door",
+ "state": "closed",
+ "attributes": {
+ "device_class": "door",
+ "friendly_name": "Front Door"
+ },
+ "last_changed": "2024-01-15T07:30:00+00:00",
+ "last_updated": "2024-01-15T07:30:00+00:00",
+ "context": {
+ "id": "01HMV567890123",
+ "parent_id": null,
+ "user_id": null
+ }
+ },
+ {
+ "entity_id": "media_player.living_room_tv",
+ "state": "playing",
+ "attributes": {
+ "volume_level": 0.4,
+ "is_volume_muted": false,
+ "media_content_type": "tvshow",
+ "media_title": "The Office",
+ "app_name": "Netflix",
+ "friendly_name": "Living Room TV"
+ },
+ "last_changed": "2024-01-15T10:00:00+00:00",
+ "last_updated": "2024-01-15T10:20:00+00:00",
+ "context": {
+ "id": "01HMV678901234",
+ "parent_id": null,
+ "user_id": "user123"
+ }
+ },
+ {
+ "entity_id": "automation.morning_routine",
+ "state": "on",
+ "attributes": {
+ "last_triggered": "2024-01-15T07:00:00+00:00",
+ "mode": "single",
+ "current": 0,
+ "friendly_name": "Morning Routine"
+ },
+ "last_changed": "2024-01-14T07:00:00+00:00",
+ "last_updated": "2024-01-15T07:00:00+00:00",
+ "context": {
+ "id": "01HMV789012345",
+ "parent_id": null,
+ "user_id": null
+ }
+ },
+ {
+ "entity_id": "weather.home",
+ "state": "partly-cloudy",
+ "attributes": {
+ "temperature": 18.0,
+ "humidity": 65,
+ "pressure": 1013.2,
+ "wind_speed": 12.5,
+ "wind_bearing": 225,
+ "visibility": 16.0,
+ "forecast": [],
+ "friendly_name": "Home Weather"
+ },
+ "last_changed": "2024-01-15T10:00:00+00:00",
+ "last_updated": "2024-01-15T10:30:00+00:00",
+ "context": {
+ "id": "01HMV890123456",
+ "parent_id": null,
+ "user_id": null
+ }
+ }
+]
\ No newline at end of file
diff --git a/dummy-data/homeassistant/api_root b/dummy-data/homeassistant/api_root
new file mode 100644
index 0000000..a7ea0ce
--- /dev/null
+++ b/dummy-data/homeassistant/api_root
@@ -0,0 +1,3 @@
+{
+ "message": "API running."
+}
\ No newline at end of file
diff --git a/dummy-data/immich/api/server/statistics b/dummy-data/immich/api/server/statistics
new file mode 100644
index 0000000..1679d5e
--- /dev/null
+++ b/dummy-data/immich/api/server/statistics
@@ -0,0 +1,41 @@
+{
+ "photos": 12847,
+ "videos": 1523,
+ "usage": 248576851456,
+ "usageByUser": [
+ {
+ "userId": "user-1234-5678-9abc-def0",
+ "userName": "john.doe",
+ "photos": 8945,
+ "videos": 892,
+ "usage": 156789012345
+ },
+ {
+ "userId": "user-2345-6789-abcd-ef01",
+ "userName": "jane.smith",
+ "photos": 2134,
+ "videos": 423,
+ "usage": 67891234567
+ },
+ {
+ "userId": "user-3456-789a-bcde-f012",
+ "userName": "family.shared",
+ "photos": 1768,
+ "videos": 208,
+ "usage": 23896604544
+ }
+ ],
+ "usageRaw": 248576851456,
+ "photosGrowth": {
+ "date": "2024-01-15",
+ "value": 42
+ },
+ "videosGrowth": {
+ "date": "2024-01-15",
+ "value": 7
+ },
+ "usageGrowth": {
+ "date": "2024-01-15",
+ "value": 2147483648
+ }
+}
\ No newline at end of file
diff --git a/dummy-data/jellystat/proxy/getSessions b/dummy-data/jellystat/proxy/getSessions
new file mode 100644
index 0000000..a1bbcb0
--- /dev/null
+++ b/dummy-data/jellystat/proxy/getSessions
@@ -0,0 +1,342 @@
+[
+ {
+ "PlayState": {
+ "CanSeek": true,
+ "IsPaused": false,
+ "IsMuted": false,
+ "RepeatMode": "RepeatNone",
+ "ShuffleMode": "Sorted",
+ "VolumeLevel": 85,
+ "AudioStreamIndex": 1,
+ "SubtitleStreamIndex": -1,
+ "MediaSourceId": "12345abcdef",
+ "PlayMethod": "DirectPlay",
+ "PlaySessionId": "session-1-abc123",
+ "PlaylistItemId": "playlist-item-1",
+ "PositionTicks": 18000000000
+ },
+ "AdditionalUsers": [],
+ "Capabilities": {
+ "PlayableMediaTypes": ["Audio", "Video"],
+ "SupportedCommands": ["Play", "Pause", "Stop", "Seek"]
+ },
+ "RemoteEndPoint": "192.168.1.100",
+ "PlayableMediaTypes": ["Audio", "Video"],
+ "Id": "session-1-abc123",
+ "UserId": "user123abc",
+ "UserName": "john_doe",
+ "Client": "Jellyfin Web",
+ "LastActivityDate": "2024-01-15T10:30:00.0000000Z",
+ "LastPlaybackCheckIn": "2024-01-15T10:30:00.0000000Z",
+ "DeviceName": "Chrome on Desktop",
+ "DeviceId": "device-desktop-chrome",
+ "ApplicationVersion": "10.8.13",
+ "IsActive": true,
+ "SupportsMediaControl": true,
+ "SupportsRemoteControl": true,
+ "NowPlayingItem": {
+ "Name": "The Office - S03E01 - Gay Witch Hunt",
+ "OriginalTitle": "Gay Witch Hunt",
+ "Id": "episode123abc",
+ "Etag": "etag123",
+ "SourceType": "Library",
+ "PlaylistItemId": "playlist-item-1",
+ "DateCreated": "2024-01-10T00:00:00.0000000Z",
+ "DateLastMediaAdded": "2024-01-10T00:00:00.0000000Z",
+ "ExtraType": null,
+ "AirsBeforeSeasonNumber": null,
+ "AirsAfterSeasonNumber": null,
+ "AirsBeforeEpisodeNumber": null,
+ "CanDelete": false,
+ "CanDownload": false,
+ "HasSubtitles": true,
+ "Container": "mkv",
+ "SortName": "office s03e01 gay witch hunt",
+ "ForcedSortName": null,
+ "Video3DFormat": null,
+ "PremiereDate": "2006-09-21T00:00:00.0000000Z",
+ "ExternalUrls": [],
+ "MediaSources": [],
+ "CriticRating": null,
+ "ProductionLocations": [],
+ "Path": "/media/tv/The Office/Season 03/S03E01.mkv",
+ "EnableMediaSourceDisplay": true,
+ "OfficialRating": "TV-14",
+ "CustomRating": null,
+ "ChannelId": null,
+ "ChannelName": null,
+ "Overview": "Michael's kiss with Oscar at the Dundies leads to sensitivity training for the office.",
+ "Taglines": [],
+ "Genres": ["Comedy"],
+ "CommunityRating": 8.1,
+ "CumulativeRunTimeTicks": 13050000000,
+ "RunTimeTicks": 13050000000,
+ "PlayAccess": "Full",
+ "AspectRatio": "16:9",
+ "ProductionYear": 2006,
+ "IsPlaceHolder": false,
+ "Number": null,
+ "ChannelNumber": null,
+ "IndexNumber": 1,
+ "IndexNumberEnd": null,
+ "ParentIndexNumber": 3,
+ "RemoteTrailers": [],
+ "ProviderIds": {},
+ "IsHD": true,
+ "IsFolder": false,
+ "ParentId": "season3abc",
+ "Type": "Episode",
+ "People": [],
+ "Studios": [],
+ "GenreItems": [],
+ "ParentLogoItemId": null,
+ "ParentBackdropItemId": "series123",
+ "ParentBackdropImageTags": ["backdrop1"],
+ "LocalTrailerCount": 0,
+ "UserData": {
+ "Rating": null,
+ "PlayedPercentage": 75.5,
+ "UnplayedItemCount": null,
+ "PlaybackPositionTicks": 18000000000,
+ "PlayCount": 1,
+ "IsFavorite": false,
+ "Likes": null,
+ "LastPlayedDate": "2024-01-15T10:30:00.0000000Z",
+ "Played": false,
+ "Key": "episode123abc"
+ },
+ "RecursiveItemCount": 0,
+ "ChildCount": 0,
+ "SeriesName": "The Office",
+ "SeriesId": "series123",
+ "SeasonId": "season3abc",
+ "SpecialFeatureCount": 0,
+ "DisplayPreferencesId": "episode123abc",
+ "Status": null,
+ "AirTime": null,
+ "AirDays": [],
+ "Tags": [],
+ "PrimaryImageAspectRatio": 1.777777777777778,
+ "Artists": [],
+ "ArtistItems": [],
+ "Album": null,
+ "CollectionType": null,
+ "DisplayOrder": null,
+ "AlbumId": null,
+ "AlbumPrimaryImageTag": null,
+ "SeriesPrimaryImageTag": "series-primary",
+ "AlbumArtist": null,
+ "AlbumArtists": [],
+ "SeasonName": "Season 3",
+ "MediaStreams": [],
+ "VideoType": "VideoFile",
+ "PartCount": 1,
+ "MediaSourceCount": 1,
+ "ImageTags": {
+ "Primary": "episode-primary"
+ },
+ "BackdropImageTags": [],
+ "ScreenshotImageTags": [],
+ "ParentLogoImageTag": null,
+ "ParentArtItemId": null,
+ "ParentArtImageTag": null,
+ "SeriesThumbImageTag": null,
+ "ImageBlurHashes": {},
+ "SeriesStudio": "NBC",
+ "ParentThumbItemId": null,
+ "ParentThumbImageTag": null,
+ "ParentPrimaryImageItemId": "series123",
+ "ParentPrimaryImageTag": "series-primary",
+ "Chapters": [],
+ "LocationType": "FileSystem",
+ "IsoType": null,
+ "MediaType": "Video",
+ "EndDate": null,
+ "LockedFields": [],
+ "TrailerCount": 0,
+ "MovieCount": 0,
+ "SeriesCount": 0,
+ "ProgramCount": 0,
+ "EpisodeCount": 0,
+ "SongCount": 0,
+ "AlbumCount": 0,
+ "ArtistCount": 0,
+ "MusicVideoCount": 0,
+ "LockData": false,
+ "Width": 1920,
+ "Height": 1080,
+ "CameraMake": null,
+ "CameraModel": null,
+ "Software": null,
+ "ExposureTime": null,
+ "FocalLength": null,
+ "ImageOrientation": null,
+ "Aperture": null,
+ "ShutterSpeed": null,
+ "Latitude": null,
+ "Longitude": null,
+ "Altitude": null,
+ "IsoSpeedRating": null,
+ "SeriesTimerId": null,
+ "ProgramId": null,
+ "ChannelPrimaryImageTag": null,
+ "StartDate": null,
+ "CompletionPercentage": null,
+ "IsRepeat": null,
+ "EpisodeTitle": "Gay Witch Hunt",
+ "ChannelType": null,
+ "Audio": null,
+ "IsMovie": false,
+ "IsSports": false,
+ "IsNews": false,
+ "IsKids": false,
+ "IsPremiere": false,
+ "TimerId": null,
+ "NormalizationGain": null,
+ "CurrentProgram": null
+ },
+ "FullNowPlayingItem": {},
+ "NowViewingItem": null,
+ "DeviceType": "Desktop",
+ "NowPlayingQueue": [],
+ "NowPlayingQueueFullItems": [],
+ "HasCustomDeviceName": false,
+ "PlaylistItemId": "playlist-item-1",
+ "ServerId": "jellyfin-server-123",
+ "UserPrimaryImageTag": null,
+ "SupportedCommands": []
+ },
+ {
+ "PlayState": {
+ "CanSeek": true,
+ "IsPaused": true,
+ "IsMuted": false,
+ "RepeatMode": "RepeatNone",
+ "ShuffleMode": "Sorted",
+ "VolumeLevel": 65,
+ "AudioStreamIndex": 1,
+ "SubtitleStreamIndex": 2,
+ "MediaSourceId": "67890defghi",
+ "PlayMethod": "DirectPlay",
+ "PlaySessionId": "session-2-def456",
+ "PlaylistItemId": "playlist-item-2",
+ "PositionTicks": 45000000000
+ },
+ "AdditionalUsers": [],
+ "Capabilities": {
+ "PlayableMediaTypes": ["Audio", "Video"],
+ "SupportedCommands": ["Play", "Pause", "Stop", "Seek"]
+ },
+ "RemoteEndPoint": "192.168.1.101",
+ "PlayableMediaTypes": ["Audio", "Video"],
+ "Id": "session-2-def456",
+ "UserId": "user456def",
+ "UserName": "jane_smith",
+ "Client": "Jellyfin Android",
+ "LastActivityDate": "2024-01-15T10:25:00.0000000Z",
+ "LastPlaybackCheckIn": "2024-01-15T10:25:00.0000000Z",
+ "DeviceName": "Samsung Galaxy S21",
+ "DeviceId": "device-android-samsung",
+ "ApplicationVersion": "2.6.2",
+ "IsActive": true,
+ "SupportsMediaControl": true,
+ "SupportsRemoteControl": true,
+ "NowPlayingItem": {
+ "Name": "Inception",
+ "OriginalTitle": "Inception",
+ "Id": "movie456def",
+ "Etag": "etag456",
+ "SourceType": "Library",
+ "PlaylistItemId": "playlist-item-2",
+ "DateCreated": "2024-01-05T00:00:00.0000000Z",
+ "DateLastMediaAdded": "2024-01-05T00:00:00.0000000Z",
+ "Container": "mkv",
+ "SortName": "inception",
+ "PremiereDate": "2010-07-16T00:00:00.0000000Z",
+ "Path": "/media/movies/Inception (2010)/Inception.mkv",
+ "EnableMediaSourceDisplay": true,
+ "OfficialRating": "PG-13",
+ "Overview": "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a C.E.O.",
+ "Taglines": ["Your mind is the scene of the crime"],
+ "Genres": ["Action", "Sci-Fi", "Thriller"],
+ "CommunityRating": 8.8,
+ "CumulativeRunTimeTicks": 88800000000,
+ "RunTimeTicks": 88800000000,
+ "PlayAccess": "Full",
+ "AspectRatio": "2.40:1",
+ "ProductionYear": 2010,
+ "IsPlaceHolder": false,
+ "IsHD": true,
+ "IsFolder": false,
+ "Type": "Movie",
+ "LocalTrailerCount": 0,
+ "UserData": {
+ "PlayedPercentage": 50.6,
+ "PlaybackPositionTicks": 45000000000,
+ "PlayCount": 0,
+ "IsFavorite": true,
+ "LastPlayedDate": "2024-01-15T10:25:00.0000000Z",
+ "Played": false,
+ "Key": "movie456def"
+ },
+ "PrimaryImageAspectRatio": 0.6666666666666666,
+ "VideoType": "VideoFile",
+ "PartCount": 1,
+ "MediaSourceCount": 1,
+ "ImageTags": {
+ "Primary": "movie-primary"
+ },
+ "BackdropImageTags": ["backdrop1", "backdrop2"],
+ "LocationType": "FileSystem",
+ "MediaType": "Video",
+ "Width": 1920,
+ "Height": 800,
+ "IsMovie": true,
+ "IsSports": false,
+ "IsNews": false,
+ "IsKids": false,
+ "IsPremiere": false
+ },
+ "FullNowPlayingItem": {},
+ "NowViewingItem": null,
+ "DeviceType": "Phone",
+ "NowPlayingQueue": [],
+ "NowPlayingQueueFullItems": [],
+ "HasCustomDeviceName": false,
+ "PlaylistItemId": "playlist-item-2",
+ "ServerId": "jellyfin-server-123",
+ "UserPrimaryImageTag": null,
+ "SupportedCommands": []
+ },
+ {
+ "PlayState": null,
+ "AdditionalUsers": [],
+ "Capabilities": {
+ "PlayableMediaTypes": ["Audio", "Video"],
+ "SupportedCommands": ["Play", "Pause", "Stop"]
+ },
+ "RemoteEndPoint": "192.168.1.102",
+ "PlayableMediaTypes": ["Audio", "Video"],
+ "Id": "session-3-ghi789",
+ "UserId": "user789ghi",
+ "UserName": "family_user",
+ "Client": "Jellyfin for Roku",
+ "LastActivityDate": "2024-01-15T10:20:00.0000000Z",
+ "LastPlaybackCheckIn": "2024-01-15T10:20:00.0000000Z",
+ "DeviceName": "Roku Ultra",
+ "DeviceId": "device-roku-ultra",
+ "ApplicationVersion": "1.6.8",
+ "IsActive": true,
+ "SupportsMediaControl": true,
+ "SupportsRemoteControl": true,
+ "FullNowPlayingItem": {},
+ "NowViewingItem": null,
+ "DeviceType": "Tv",
+ "NowPlayingQueue": [],
+ "NowPlayingQueueFullItems": [],
+ "HasCustomDeviceName": false,
+ "ServerId": "jellyfin-server-123",
+ "UserPrimaryImageTag": null,
+ "SupportedCommands": []
+ }
+]
\ No newline at end of file
diff --git a/dummy-data/lidarr/api/v1/health b/dummy-data/lidarr/api/v1/health
new file mode 100644
index 0000000..af26676
--- /dev/null
+++ b/dummy-data/lidarr/api/v1/health
@@ -0,0 +1,35 @@
+[
+ {
+ "source": "IndexerStatusCheck",
+ "type": "warning",
+ "message": "Indexer MusicBrainzDB is unavailable due to recent indexer errors: Service temporarily unavailable",
+ "wikiUrl": "https://wiki.servarr.com/lidarr/health#indexers-are-unavailable-due-to-recent-failures"
+ },
+ {
+ "source": "ImportMechanismCheck",
+ "type": "ok",
+ "message": "No issues with import mechanism checks"
+ },
+ {
+ "source": "DownloadClientStatusCheck",
+ "type": "ok",
+ "message": "All download clients are available"
+ },
+ {
+ "source": "RootFolderCheck",
+ "type": "error",
+ "message": "Missing root folder: /music",
+ "wikiUrl": "https://wiki.servarr.com/lidarr/health#missing-root-folder"
+ },
+ {
+ "source": "UpdateCheck",
+ "type": "ok",
+ "message": "Update available: 1.3.6.3557 -> 2.0.7.3849"
+ },
+ {
+ "source": "MetadataProviderCheck",
+ "type": "warning",
+ "message": "Metadata provider Last.fm API key is invalid or expired",
+ "wikiUrl": "https://wiki.servarr.com/lidarr/health#metadata-provider-issues"
+ }
+]
\ No newline at end of file
diff --git a/dummy-data/lidarr/api/v1/queue/status b/dummy-data/lidarr/api/v1/queue/status
new file mode 100644
index 0000000..cc7e0d1
--- /dev/null
+++ b/dummy-data/lidarr/api/v1/queue/status
@@ -0,0 +1,7 @@
+{
+ "totalCount": 4,
+ "count": 4,
+ "unknownCount": 0,
+ "errors": false,
+ "warnings": false
+}
\ No newline at end of file
diff --git a/dummy-data/lidarr/api/v1/wanted/missing b/dummy-data/lidarr/api/v1/wanted/missing
new file mode 100644
index 0000000..6b81a1b
--- /dev/null
+++ b/dummy-data/lidarr/api/v1/wanted/missing
@@ -0,0 +1,179 @@
+{
+ "page": 1,
+ "pageSize": 20,
+ "sortKey": "releaseDate",
+ "sortDirection": "descending",
+ "totalRecords": 7,
+ "records": [
+ {
+ "artistId": 1,
+ "albumId": 12345,
+ "foreignAlbumId": "mbid-123-456-789",
+ "title": "Dark Side of the Moon",
+ "disambiguation": "",
+ "overview": "The eighth studio album by Pink Floyd, released in 1973.",
+ "artistName": "Pink Floyd",
+ "foreignArtistId": "mbid-artist-123",
+ "monitored": true,
+ "anyReleaseOk": true,
+ "profileId": 1,
+ "duration": 2580000,
+ "albumType": "Album",
+ "secondaryTypes": [],
+ "mediumCount": 1,
+ "releaseDate": "1973-03-01T00:00:00Z",
+ "releases": [
+ {
+ "id": 67890,
+ "albumId": 12345,
+ "foreignReleaseId": "mbid-release-123",
+ "title": "Dark Side of the Moon",
+ "status": "Official",
+ "duration": 2580000,
+ "trackCount": 10,
+ "mediumCount": 1,
+ "disambiguation": "",
+ "country": ["US"],
+ "label": ["Harvest", "Capitol"],
+ "monitored": true
+ }
+ ],
+ "genres": ["Progressive Rock", "Psychedelic Rock"],
+ "media": [
+ {
+ "mediumNumber": 1,
+ "mediumName": "",
+ "mediumFormat": "CD"
+ }
+ ],
+ "artist": {
+ "artistName": "Pink Floyd",
+ "foreignArtistId": "mbid-artist-123",
+ "nameSlug": "pink-floyd",
+ "overview": "English rock band formed in London in 1965.",
+ "disambiguation": "",
+ "links": [],
+ "images": [],
+ "path": "/music/Pink Floyd",
+ "qualityProfileId": 1,
+ "metadataProfileId": 1,
+ "monitored": true,
+ "monitorNewItems": "all",
+ "genres": ["Progressive Rock", "Psychedelic Rock", "Art Rock"],
+ "cleanName": "pinkfloyd",
+ "sortName": "Pink Floyd",
+ "tags": [],
+ "added": "2024-01-01T00:00:00Z",
+ "ratings": {
+ "votes": 54321,
+ "value": 9.2
+ },
+ "statistics": {
+ "albumCount": 15,
+ "trackFileCount": 142,
+ "trackCount": 149,
+ "totalTrackCount": 149,
+ "sizeOnDisk": 7516192768,
+ "percentOfTracks": 95.3
+ },
+ "id": 1
+ },
+ "images": [],
+ "links": [],
+ "statistics": {
+ "trackFileCount": 0,
+ "trackCount": 10,
+ "totalTrackCount": 10,
+ "sizeOnDisk": 0,
+ "percentOfTracks": 0.0
+ },
+ "grabbed": false,
+ "id": 12345
+ },
+ {
+ "artistId": 2,
+ "albumId": 23456,
+ "foreignAlbumId": "mbid-234-567-890",
+ "title": "OK Computer",
+ "disambiguation": "",
+ "overview": "The third studio album by Radiohead, released in 1997.",
+ "artistName": "Radiohead",
+ "foreignArtistId": "mbid-artist-234",
+ "monitored": true,
+ "anyReleaseOk": true,
+ "profileId": 1,
+ "duration": 3230000,
+ "albumType": "Album",
+ "secondaryTypes": [],
+ "mediumCount": 1,
+ "releaseDate": "1997-06-16T00:00:00Z",
+ "releases": [
+ {
+ "id": 78901,
+ "albumId": 23456,
+ "foreignReleaseId": "mbid-release-234",
+ "title": "OK Computer",
+ "status": "Official",
+ "duration": 3230000,
+ "trackCount": 12,
+ "mediumCount": 1,
+ "disambiguation": "",
+ "country": ["GB"],
+ "label": ["Parlophone", "Capitol"],
+ "monitored": true
+ }
+ ],
+ "genres": ["Alternative Rock", "Art Rock"],
+ "media": [
+ {
+ "mediumNumber": 1,
+ "mediumName": "",
+ "mediumFormat": "CD"
+ }
+ ],
+ "artist": {
+ "artistName": "Radiohead",
+ "foreignArtistId": "mbid-artist-234",
+ "nameSlug": "radiohead",
+ "overview": "English rock band formed in Abingdon, Oxfordshire, in 1985.",
+ "disambiguation": "",
+ "links": [],
+ "images": [],
+ "path": "/music/Radiohead",
+ "qualityProfileId": 1,
+ "metadataProfileId": 1,
+ "monitored": true,
+ "monitorNewItems": "all",
+ "genres": ["Alternative Rock", "Art Rock", "Electronic"],
+ "cleanName": "radiohead",
+ "sortName": "Radiohead",
+ "tags": [],
+ "added": "2024-01-01T00:00:00Z",
+ "ratings": {
+ "votes": 45678,
+ "value": 8.9
+ },
+ "statistics": {
+ "albumCount": 9,
+ "trackFileCount": 89,
+ "trackCount": 95,
+ "totalTrackCount": 95,
+ "sizeOnDisk": 4831838208,
+ "percentOfTracks": 93.7
+ },
+ "id": 2
+ },
+ "images": [],
+ "links": [],
+ "statistics": {
+ "trackFileCount": 0,
+ "trackCount": 12,
+ "totalTrackCount": 12,
+ "sizeOnDisk": 0,
+ "percentOfTracks": 0.0
+ },
+ "grabbed": false,
+ "id": 23456
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dummy-data/linkding/api/bookmarks b/dummy-data/linkding/api/bookmarks
new file mode 100644
index 0000000..9701f65
--- /dev/null
+++ b/dummy-data/linkding/api/bookmarks
@@ -0,0 +1,61 @@
+{
+ "count": 12,
+ "next": null,
+ "previous": null,
+ "results": [
+ {
+ "id": 1,
+ "url": "https://github.com/bastienwirtz/homer",
+ "title": "Homer - A very simple static homepage for your server",
+ "description": "A dead simple static HOMepage for your servER to keep your services on hand, from a simple yaml configuration file.",
+ "notes": "",
+ "website_title": "GitHub",
+ "website_description": "GitHub is where over 100 million developers shape the future of software, together.",
+ "web_archive_snapshot_url": "",
+ "favicon_url": "https://github.githubassets.com/favicons/favicon.svg",
+ "preview_image_url": "",
+ "is_archived": false,
+ "unread": false,
+ "shared": false,
+ "tag_names": ["selfhosted", "dashboard", "yaml"],
+ "date_added": "2024-01-15T10:30:00.123456Z",
+ "date_modified": "2024-01-15T10:30:00.123456Z"
+ },
+ {
+ "id": 2,
+ "url": "https://docs.docker.com/",
+ "title": "Docker Documentation",
+ "description": "Official Docker documentation with guides, references, and tutorials for containerization.",
+ "notes": "Essential for container management",
+ "website_title": "Docker Docs",
+ "website_description": "Docker helps developers build, share, run, and verify applications anywhere.",
+ "web_archive_snapshot_url": "",
+ "favicon_url": "https://docs.docker.com/favicons/docs@2x.ico",
+ "preview_image_url": "",
+ "is_archived": false,
+ "unread": false,
+ "shared": false,
+ "tag_names": ["docker", "containers", "documentation"],
+ "date_added": "2024-01-14T15:20:00.123456Z",
+ "date_modified": "2024-01-14T15:20:00.123456Z"
+ },
+ {
+ "id": 3,
+ "url": "https://nginx.org/en/docs/",
+ "title": "nginx documentation",
+ "description": "Official nginx web server documentation covering installation, configuration, and modules.",
+ "notes": "",
+ "website_title": "nginx",
+ "website_description": "nginx [engine x] is an HTTP and reverse proxy server, a mail proxy server, and a generic TCP/UDP proxy server.",
+ "web_archive_snapshot_url": "",
+ "favicon_url": "https://nginx.org/favicon.ico",
+ "preview_image_url": "",
+ "is_archived": false,
+ "unread": false,
+ "shared": false,
+ "tag_names": ["nginx", "webserver", "proxy"],
+ "date_added": "2024-01-13T09:45:00.123456Z",
+ "date_modified": "2024-01-13T09:45:00.123456Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dummy-data/matrix/_matrix/federation/v1/version b/dummy-data/matrix/_matrix/federation/v1/version
new file mode 100644
index 0000000..fbc4e1a
--- /dev/null
+++ b/dummy-data/matrix/_matrix/federation/v1/version
@@ -0,0 +1,6 @@
+{
+ "server": {
+ "name": "Synapse",
+ "version": "1.99.0"
+ }
+}
\ No newline at end of file
diff --git a/dummy-data/mealie/api/admin/about/statistics b/dummy-data/mealie/api/admin/about/statistics
new file mode 100644
index 0000000..80d793f
--- /dev/null
+++ b/dummy-data/mealie/api/admin/about/statistics
@@ -0,0 +1,18 @@
+{
+ "totalRecipes": 247,
+ "totalUsers": 5,
+ "totalGroups": 2,
+ "totalCategories": 18,
+ "totalTags": 42,
+ "totalTools": 15,
+ "totalMealPlans": 156,
+ "totalShoppingLists": 28,
+ "totalComments": 89,
+ "lastUpdated": "2024-01-15T10:30:00Z",
+ "version": "1.0.0",
+ "demoStatus": false,
+ "allowSignup": true,
+ "defaultGroup": "Home",
+ "buildVersion": "v1.0.0-1234567",
+ "apiVersion": "v1"
+}
\ No newline at end of file
diff --git a/dummy-data/mealie/api/groups/mealplans/today b/dummy-data/mealie/api/groups/mealplans/today
new file mode 100644
index 0000000..82e3ffa
--- /dev/null
+++ b/dummy-data/mealie/api/groups/mealplans/today
@@ -0,0 +1,64 @@
+[
+ {
+ "id": "meal-123-abc",
+ "date": "2024-01-15",
+ "entryType": "dinner",
+ "title": "Dinner",
+ "text": "",
+ "recipe": {
+ "id": "recipe-456-def",
+ "name": "Chicken Tikka Masala",
+ "slug": "chicken-tikka-masala",
+ "image": "recipe-456-def.webp",
+ "description": "Creamy tomato-based curry with tender chicken pieces and aromatic spices",
+ "recipeCategory": [
+ {
+ "id": "cat-1",
+ "name": "Indian",
+ "slug": "indian"
+ },
+ {
+ "id": "cat-2",
+ "name": "Main Course",
+ "slug": "main-course"
+ }
+ ],
+ "tags": [
+ {
+ "id": "tag-1",
+ "name": "Curry",
+ "slug": "curry"
+ },
+ {
+ "id": "tag-2",
+ "name": "Chicken",
+ "slug": "chicken"
+ },
+ {
+ "id": "tag-3",
+ "name": "Spicy",
+ "slug": "spicy"
+ }
+ ],
+ "rating": 4.5,
+ "totalTime": "45 minutes",
+ "prepTime": "15 minutes",
+ "cookTime": "30 minutes",
+ "performTime": null,
+ "servings": 4,
+ "dateAdded": "2024-01-10T00:00:00Z",
+ "dateUpdated": "2024-01-14T12:30:00Z",
+ "createdBy": {
+ "id": "user-789",
+ "username": "chef_sarah",
+ "fullName": "Sarah Mitchell"
+ },
+ "updateBy": {
+ "id": "user-789",
+ "username": "chef_sarah",
+ "fullName": "Sarah Mitchell"
+ }
+ },
+ "groupId": "group-default-123"
+ }
+]
\ No newline at end of file
diff --git a/dummy-data/medusa/api/v2/config b/dummy-data/medusa/api/v2/config
new file mode 100644
index 0000000..2a1cd54
--- /dev/null
+++ b/dummy-data/medusa/api/v2/config
@@ -0,0 +1,108 @@
+{
+ "system": {
+ "news": {
+ "unread": 3,
+ "latest": [
+ {
+ "title": "Medusa v1.0.19 Released",
+ "date": "2024-01-14",
+ "content": "Bug fixes and performance improvements",
+ "read": false
+ },
+ {
+ "title": "New indexer support added",
+ "date": "2024-01-12",
+ "content": "Support for additional torrent indexers",
+ "read": false
+ },
+ {
+ "title": "Database maintenance completed",
+ "date": "2024-01-10",
+ "content": "Weekly database optimization finished",
+ "read": false
+ }
+ ]
+ },
+ "version": {
+ "version": "1.0.19",
+ "branch": "master",
+ "commit": "abc123def456",
+ "dbVersion": 44,
+ "pythonVersion": "3.11.7"
+ },
+ "os": {
+ "platform": "Linux",
+ "release": "6.5.0-15-generic",
+ "version": "#15~22.04.1-Ubuntu"
+ },
+ "memory": {
+ "used": 512.5,
+ "total": 8192.0,
+ "percent": 6.3
+ }
+ },
+ "main": {
+ "logs": {
+ "numWarnings": 2,
+ "numErrors": 1,
+ "loggingLevels": [
+ "DEBUG",
+ "INFO",
+ "WARNING",
+ "ERROR"
+ ],
+ "currentLevel": "INFO"
+ },
+ "general": {
+ "webHost": "0.0.0.0",
+ "webPort": 8081,
+ "webRoot": "",
+ "launchBrowser": false,
+ "versionNotify": true,
+ "autoUpdate": false,
+ "logDir": "/app/Logs",
+ "dataDir": "/app/Data",
+ "configVersion": 12
+ },
+ "showDefaults": {
+ "status": "Skipped/Wanted/Snatched/Downloaded",
+ "statusAfter": "Downloaded",
+ "season_folders": true,
+ "anime": false,
+ "scene": false,
+ "archive_firstmatch": false,
+ "quality_default": "Standard Definition",
+ "subtitles": false,
+ "flatten_folders": false,
+ "indexer_default": "tvdb",
+ "indexer_timeout": 20,
+ "skip_removed_files": false
+ }
+ },
+ "search": {
+ "general": {
+ "randomize_providers": false,
+ "download_propers": true,
+ "check_propers_interval": "daily",
+ "propers_search_days": 2,
+ "backlog_days": 7,
+ "cache_trimming": false,
+ "max_cache_age": 30
+ },
+ "nzb": {
+ "nzbs": false,
+ "nzbs_uid": "",
+ "nzbs_hash": ""
+ },
+ "torrent": {
+ "torrent_method": "blackhole",
+ "torrent_path": "",
+ "torrent_seed_time": 0,
+ "torrent_paused": false,
+ "torrent_high_bandwidth": false,
+ "torrent_label": "",
+ "torrent_label_anime": "",
+ "torrent_verify_cert": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/dummy-data/miniflux/v1/entries b/dummy-data/miniflux/v1/entries
new file mode 100644
index 0000000..962714f
--- /dev/null
+++ b/dummy-data/miniflux/v1/entries
@@ -0,0 +1,36 @@
+{
+ "total": 42,
+ "entries": [
+ {
+ "id": 888,
+ "user_id": 1,
+ "feed_id": 42,
+ "title": "Example Unread Entry",
+ "url": "http://example.org/article.html",
+ "comments_url": "",
+ "author": "John Doe",
+ "content": "