{"id":51202241,"url":"https://github.com/mayerwin/frigate-layout-sync","last_synced_at":"2026-06-28T01:03:33.086Z","repository":{"id":366104039,"uuid":"1275008275","full_name":"mayerwin/frigate-layout-sync","owner":"mayerwin","description":"Save, sync and responsively restore Frigate camera-group dashboard layouts across devices. A tiny standalone companion service that transparently proxies Frigate and stores layouts server-side.","archived":false,"fork":false,"pushed_at":"2026-06-20T08:01:55.000Z","size":167,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T10:04:17.789Z","etag":null,"topics":["camera","frigate","home-automation","layout","nvr","reverse-proxy"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mayerwin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-20T06:19:00.000Z","updated_at":"2026-06-20T08:01:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mayerwin/frigate-layout-sync","commit_stats":null,"previous_names":["mayerwin/frigate-layout-sync"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mayerwin/frigate-layout-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayerwin%2Ffrigate-layout-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayerwin%2Ffrigate-layout-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayerwin%2Ffrigate-layout-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayerwin%2Ffrigate-layout-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mayerwin","download_url":"https://codeload.github.com/mayerwin/frigate-layout-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mayerwin%2Ffrigate-layout-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34873664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["camera","frigate","home-automation","layout","nvr","reverse-proxy"],"created_at":"2026-06-28T01:03:32.373Z","updated_at":"2026-06-28T01:03:33.080Z","avatar_url":"https://github.com/mayerwin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# frigate-layout-sync\n\nSave, restore and **sync [Frigate](https://frigate.video) camera-group dashboard layouts across all your devices**, with **responsive, per-width breakpoints** so a phone, a tablet and a desktop can each load the arrangement that fits its screen.\n\nFrigate stores each camera group's draggable tile layout (and per-camera stream settings like compatibility mode) **per-browser, in IndexedDB**. Every new device or browser starts from scratch, and there is no way to keep a different layout for a phone versus a wall display. This tool fixes both: one click saves the current layout to a small server, another click loads the right one back, on any device. It also gives Frigate's built-in **All Cameras** view a custom, draggable layout, which Frigate itself does not support.\n\n\u003e Community answer to Frigate issue [#23462](https://github.com/blakeblackshear/frigate/issues/23462) (*\"Add import/export for camera group layouts\"*), which is on Frigate's roadmap but planned as per-device file export only. This tool adds true **server-side, cross-device, responsive** sync today.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/button.png\" alt=\"The injected Layouts button, sitting natively above Frigate's fullscreen control\" width=\"150\"\u003e\n  \u0026nbsp;\u0026nbsp;\n  \u003cimg src=\"docs/panel.png\" alt=\"Layout Sync panel\" width=\"250\"\u003e\n  \u0026nbsp;\u0026nbsp;\n  \u003cimg src=\"docs/save-dialog.png\" alt=\"Save dialog\" width=\"300\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003eThe \u003cb\u003eLayouts\u003c/b\u003e button drops in with Frigate's own controls (above the fullscreen button) · the panel · the save dialog\u003c/sub\u003e\u003c/p\u003e\n\n---\n\n## How it works — no reverse proxy\n\nFrigate has no frontend plugin API, so this runs as a tiny companion container that makes **Frigate's own nginx** inject one button. **Frigate stays the front door on its normal port** — it serves all your assets, the live-view websockets and video exactly as before. The companion only does two small things:\n\n```\n   your browser ───► Frigate (nginx, port 8971, unchanged) ──► cameras / recordings\n   (any device)          │  injects \u003cscript\u003e via sub_filter\n                         /__layoutsync/*  ───► frigate-layout-sync companion ──► layouts.yaml\n```\n\n1. The companion adds two lines to Frigate's nginx config: a `sub_filter` that injects one `\u003cscript\u003e` into the dashboard, and a `location /__layoutsync/` that routes just that path to the companion. It applies them by editing the config **inside the running Frigate container** (over the Docker socket), validating with `nginx -t`, then reloading.\n2. It **re-applies automatically whenever Frigate (re)starts** — so a Frigate upgrade, which recreates the container from a fresh image and wipes the edit, *self-heals* with no manual step.\n3. The companion serves the injected client (`/__layoutsync/inject.js`) and a tiny layout API (`/__layoutsync/api/*`), storing profiles as YAML.\n\nBecause the button runs **inside** the real Frigate page (same origin, same port) it reads and writes Frigate's own IndexedDB layout entries directly, and your existing dragged layouts always carry over. Because the layouts live on the **server**, every device that opens Frigate shares them — including phones — with nothing to install per-device. If the companion stops, you simply lose the extra button; if Frigate ever can't reach it, that one `location` just 502s while the rest of Frigate is unaffected.\n\n---\n\n## Install\n\n### Docker Compose (recommended)\n\nAdd the companion next to your existing Frigate service and put both on a shared\nnetwork so Frigate's nginx can reach it. **You do not move Frigate's port or touch\nits config** — the companion does the nginx wiring itself.\n\n```yaml\nservices:\n  frigate:\n    image: ghcr.io/blakeblackshear/frigate:stable\n    container_name: frigate            # must match FRIGATE_CONTAINER below\n    ports: [\"8971:8971\"]               # Frigate keeps its normal port\n    networks: [layoutsync]             # \u003c-- add this line\n    # ...your usual frigate config...\n\n  frigate-layout-sync:\n    build: .                           # or image: ghcr.io/mayerwin/frigate-layout-sync:latest\n    container_name: frigate-layout-sync\n    restart: unless-stopped\n    environment:\n      FRIGATE_CONTAINER: frigate\n      LAYOUTSYNC_UPSTREAM: frigate-layout-sync:3000\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock          # to wire + reload Frigate's nginx\n      - ./frigate/config/layout-sync:/app/data             # layouts.yaml (inside /config = backed up)\n    networks: [layoutsync]\n    depends_on: [frigate]\n\nnetworks:\n  layoutsync:\n```\n\n```bash\ndocker compose up -d --build\n```\n\nOpen Frigate at its **usual** `https://\u003cfrigate-host\u003e:8971`. A small **Layouts** icon\nappears bottom-right. (First run waits for Frigate to be up, then injects + reloads nginx.)\n\n\u003e **Why the Docker socket?** It's how the companion keeps the one-line nginx\n\u003e injection in place and reloads nginx after a Frigate upgrade, with zero manual\n\u003e steps. It only ever execs `nginx` in your Frigate container. If you'd rather not\n\u003e mount it, use the manual mode below.\n\n### Manual mode (no Docker socket)\n\nSet `NGINX_AUTOCONFIG=false` and wire Frigate's nginx yourself. Frigate\n[supports bind-mounting a custom `nginx.conf`](https://docs.frigate.video/configuration/advanced/).\nAdd, **inside `location / { … }`**:\n\n```nginx\nsub_filter '\u003c/body\u003e' '\u003cscript src=\"/__layoutsync/inject.js\" defer\u003e\u003c/script\u003e\u003c/body\u003e';\n```\n\nand, **inside `server { … }`** (a sibling of `location /`):\n\n```nginx\nlocation /__layoutsync/ { proxy_pass http://frigate-layout-sync:3000; }\n```\n\nTo generate a ready-to-mount file from your exact Frigate version:\n\n```bash\ndocker run --rm ghcr.io/blakeblackshear/frigate:0.17.1 \\\n  cat /usr/local/nginx/conf/nginx.conf \u003e nginx.conf\n# then add the two snippets above and bind-mount it:\n#   - ./nginx.conf:/usr/local/nginx/conf/nginx.conf\n```\n\n### Running more than one injecting companion\n\nThe companion edits Frigate's single `nginx.conf` cooperatively, so multiple\nextensions can inject at once without clobbering each other. The convention is\ndocumented in **[PROTOCOL.md](PROTOCOL.md)** (`src/nginx.js` is the reference\nimplementation) — follow it if you're building another Frigate companion.\n\n### Configuration\n\n| Env var | Default | Meaning |\n|---|---|---|\n| `FRIGATE_CONTAINER` | `frigate` | Name/id of your Frigate container (the companion execs `nginx` inside it). |\n| `LAYOUTSYNC_UPSTREAM` | `frigate-layout-sync:3000` | How Frigate's nginx reaches the companion (must resolve on the shared network). |\n| `PORT` | `3000` | Port the companion listens on (internal; Frigate's nginx proxies to it). |\n| `DATA_DIR` | `./data` | Where `layouts.yaml` is written. Point it inside Frigate's `/config` to get backups. |\n| `NGINX_RESOLVER` | `127.0.0.11` | Docker's embedded DNS, used by the injected `location` to resolve the companion lazily. |\n| `NGINX_AUTOCONFIG` | `true` | Set `false` to manage Frigate's nginx yourself (manual mode above). |\n| `FRIGATE_NGINX_CONF` | `/usr/local/nginx/conf/nginx.conf` | Path to Frigate's nginx.conf inside the container. |\n\n---\n\n## Usage\n\n1. Open **`https://\u003cfrigate-host\u003e:8971`** as normal and log in.\n2. Arrange a camera **group** the way you want using Frigate's drag/resize editor, then click **Layouts → Save current layout**. Resize the window to the maximum width this layout should apply to (the dialog shows the live width), or tick **No limit** for the default, give it a label, and Save.\n3. On a phone, tablet or another browser, it loads **automatically** (see below). You can also click **Layouts → Load layout for this screen** to force the best-fit layout for the current width.\n4. **Layouts → Manage saved layouts** lists every saved breakpoint and lets you delete any.\n\nBuild responsive breakpoints by repeating step 2 at a few widths (for example one at `≤ 600 px` for phones, one at `≤ 1200 px` for tablets, and a no-limit default for desktop). You can configure them all from a single computer just by resizing the window.\n\n### Auto-load (on by default)\n\nBy default the saved layout for the current screen width is **applied automatically when you open Frigate** — on every device, with nothing to click. A checkbox at the top of the **Layouts** panel, **\"Auto-load saved layout\"**, lets you turn this off per browser (then it only changes when you click *Load layout for this screen*).\n\n- **Installing never loses your existing layout.** If nothing is saved yet, the tool *bootstraps* from whatever you currently have arranged in this browser — it saves that as your default and starts syncing it. Opening the extension only ever **adds** the sync capability; it never changes or discards your current arrangement.\n- Auto-load is **idempotent**: it only re-applies (a quick reload) when the saved layout actually differs from what the browser already shows, so there's no flicker on every open and no reload loop. The **All Cameras** custom layout applies instantly with no reload.\n- With auto-load on, **\"Load layout for this screen\"** is rarely needed — it's there to force a specific width's layout, or to pull the latest if you've turned auto-load off on that device.\n\n### Custom layout for the built-in \"All Cameras\" view\n\nFrigate makes only **named camera groups** draggable; its built-in **All Cameras** view is a fixed auto-grid you cannot rearrange. This tool adds that missing capability. Open the All Cameras view, then **Layouts → Edit All-Cameras layout**, and:\n\n- **drag a camera** to reorder it,\n- **click a camera** to cycle its width (how many columns it spans),\n- set the **column count** from the toolbar.\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"docs/editbar.png\" alt=\"All-Cameras layout toolbar\" width=\"560\"\u003e\u003c/p\u003e\n\nClick **Save…** to store it like any other layout (responsive, per width). It is re-applied automatically on every device and survives refreshes. Frigate is not modified; the arrangement is layered on top of Frigate's own grid as CSS, keyed on each tile's camera name. (Drag uses pointer events, so it works with a mouse or touch.)\n\n---\n\n## What gets saved\n\nThe presentation entries Frigate keeps in IndexedDB (`keyval-store`/`keyval`):\n\n- `` `\u003cgroup\u003e-draggable-layout` ``: the `react-grid-layout` tile arrangement per camera group (optionally suffixed `:\u003cusername\u003e` when logged in).\n- `streaming-settings`: per-camera stream choice, **compatibility mode**, audio, volume.\n- `live-layout`, `autoLiveView`, `displayCameraNames`: dashboard preferences.\n\nPlus the **All Cameras** layout (column count, camera order and per-camera width). Frigate has no place to store that, so this tool keeps it in the layout profile and re-applies it as CSS over Frigate's grid.\n\nCamera group **membership and icons** are not touched; those live in Frigate's own `config.yaml`.\n\n---\n\n## Notes \u0026 limitations\n\n- The button only changes the dashboard's *presentation*; it never alters Frigate's config, recordings, or detection.\n- Frigate's draggable grid is a **desktop** feature; phones use a separate list/grid view. The responsive breakpoints are most useful across desktop and tablet widths, but the saved preferences (stream and compatibility settings) apply everywhere.\n- Layout keys are saved verbatim, including the `:\u003cusername\u003e` suffix. If you log into Frigate with the **same** account everywhere (the normal case) they match across devices.\n- The companion edits only Frigate's *served HTML* and adds one `location`; it never touches recordings, detection or config. The change is fully reversible: `npm run nginx:remove` (or just stop the container and restart Frigate) restores a vanilla nginx.\n\n## Testing\n\nAn end-to-end test drives a real browser against your Frigate (with the companion's\ninjection applied) and exercises the whole flow (login, injection, live-view\nsurvival, save, load, responsive pick, delete). It uses throwaway IndexedDB keys, so\nit never touches your real dragged layouts (it does reset this tool's own saved profiles).\n\n```bash\nnpm install                          # includes the playwright devDependency\nnpx playwright install chromium\n# FRIGATE_PROXY = your Frigate URL (the companion has injected the button there)\nFRIGATE_PASSWORD=yourpass FRIGATE_PROXY=https://\u003cfrigate-host\u003e:8971 npm test\n```\n\n## License\n\nMIT © Erwin Mayer. Not affiliated with the Frigate project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayerwin%2Ffrigate-layout-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmayerwin%2Ffrigate-layout-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmayerwin%2Ffrigate-layout-sync/lists"}