feat(apps): MagicMirror as a Void app (#/mirror, mirror.hynesy.com)
Embed MagicMirror² (CT 111) via the shared embedView factory, exposed at mirror.hynesy.com through Traefik + CF Access. Traefik mirror-frame middleware swaps MM's X-Frame-Options for a CSP frame-ancestors allowing the Void origins. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,11 @@
|
|||||||
All notable changes to Void 2.0 are documented here.
|
All notable changes to Void 2.0 are documented here.
|
||||||
Format: [Keep a Changelog](https://keepachangelog.com).
|
Format: [Keep a Changelog](https://keepachangelog.com).
|
||||||
|
|
||||||
|
## 2.3.0 — MagicMirror² as a Void app
|
||||||
|
- **New "MagicMirror" Apps view** (`#/mirror`, `public/views/mirror.js`) — embeds the smart-mirror dashboard (CT 111) via the shared `embedView` factory, like Timelapse / AI Usage.
|
||||||
|
- **Exposure:** MagicMirror (LAN-only `192.168.1.224:8080`) is now published at **mirror.hynesy.com** through Traefik + the `*.hynesy.com` tunnel, private behind **CF Access** (Farm policy / Google IdP). A Traefik `mirror-frame` middleware replaces MM's `X-Frame-Options: SAMEORIGIN` with a CSP `frame-ancestors` allowing the Void origins so the iframe renders.
|
||||||
|
- Unrelated to the Void code: CT 111 itself was updated **MagicMirror 2.25.0 → 2.36.0** on **Node 22**.
|
||||||
|
|
||||||
## 2.2.0 — Links: self-hosted URL shortener (Kutt) as a Void app
|
## 2.2.0 — Links: self-hosted URL shortener (Kutt) as a Void app
|
||||||
- **New "Links" Apps view** (`#/links`, `public/views/links.js`) — a Void-native card (Kutt **version / update tracker** + one-field **quick-add shortener**) on top of the blackflame-themed **Kutt** UI embedded via iframe (`link.hynesy.com`). Hybrid model: native convenience + the full Kutt UI in one tab.
|
- **New "Links" Apps view** (`#/links`, `public/views/links.js`) — a Void-native card (Kutt **version / update tracker** + one-field **quick-add shortener**) on top of the blackflame-themed **Kutt** UI embedded via iframe (`link.hynesy.com`). Hybrid model: native convenience + the full Kutt UI in one tab.
|
||||||
- **`/api/kutt` proxy** (`lib/api/routes/kutt.js`, `lib/links/kutt.js`) — owner-gated server-side proxy that holds the Kutt API key (`GET /version` vs latest GitHub release, cached 6h; `POST /` create; `GET /recent`). The key never reaches the browser. *(Mounted at `/api/kutt`, not `/api/links` — the latter is the Void's existing internal cross-entity linking router.)*
|
- **`/api/kutt` proxy** (`lib/api/routes/kutt.js`, `lib/links/kutt.js`) — owner-gated server-side proxy that holds the Kutt API key (`GET /version` vs latest GitHub release, cached 6h; `POST /` create; `GET /recent`). The key never reaches the browser. *(Mounted at `/api/kutt`, not `/api/links` — the latter is the Void's existing internal cross-entity linking router.)*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "void-server",
|
"name": "void-server",
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const VIEWS = {
|
|||||||
'ai-usage': () => import('./views/aiusage.js'),
|
'ai-usage': () => import('./views/aiusage.js'),
|
||||||
obd2: () => import('./views/obd2.js'),
|
obd2: () => import('./views/obd2.js'),
|
||||||
links: () => import('./views/links.js'),
|
links: () => import('./views/links.js'),
|
||||||
|
mirror: () => import('./views/mirror.js'),
|
||||||
settings: () => import('./views/settings.js'),
|
settings: () => import('./views/settings.js'),
|
||||||
jobs: () => import('./views/jobs.js')
|
jobs: () => import('./views/jobs.js')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ export function renderSidebar(root) {
|
|||||||
navItem('Timelapse', '/timelapse'),
|
navItem('Timelapse', '/timelapse'),
|
||||||
navItem('AI Usage', '/ai-usage'),
|
navItem('AI Usage', '/ai-usage'),
|
||||||
navItem('OBD2', '/obd2'),
|
navItem('OBD2', '/obd2'),
|
||||||
navItem('Links', '/links')
|
navItem('Links', '/links'),
|
||||||
|
navItem('MagicMirror', '/mirror')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const ROUTES = [
|
|||||||
{ name: 'ai-usage', re: /^\/ai-usage$/, keys: [] },
|
{ name: 'ai-usage', re: /^\/ai-usage$/, keys: [] },
|
||||||
{ name: 'obd2', re: /^\/obd2$/, keys: [] },
|
{ name: 'obd2', re: /^\/obd2$/, keys: [] },
|
||||||
{ name: 'links', re: /^\/links$/, keys: [] },
|
{ name: 'links', re: /^\/links$/, keys: [] },
|
||||||
|
{ name: 'mirror', re: /^\/mirror$/, keys: [] },
|
||||||
{ name: 'settings', re: /^\/settings$/, keys: [] },
|
{ name: 'settings', re: /^\/settings$/, keys: [] },
|
||||||
{ name: 'jobs', re: /^\/jobs$/, keys: [] },
|
{ name: 'jobs', re: /^\/jobs$/, keys: [] },
|
||||||
{ name: 'home', re: /^\/?$/, keys: [] }
|
{ name: 'home', re: /^\/?$/, keys: [] }
|
||||||
|
|||||||
6
public/views/mirror.js
Normal file
6
public/views/mirror.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// public/views/mirror.js — #/mirror (MagicMirror² on CT 111)
|
||||||
|
import { embedView } from './embed.js';
|
||||||
|
export const render = embedView({
|
||||||
|
title: 'MagicMirror', sub: 'smart mirror dashboard',
|
||||||
|
src: 'https://mirror.hynesy.com/'
|
||||||
|
});
|
||||||
@@ -14,7 +14,7 @@ import { mcpAuth } from './lib/api/middleware/mcp_auth.js';
|
|||||||
import { handleMcp } from './lib/mcp/http.js';
|
import { handleMcp } from './lib/mcp/http.js';
|
||||||
import httpProxy from 'http-proxy';
|
import httpProxy from 'http-proxy';
|
||||||
|
|
||||||
const VERSION = '2.2.0';
|
const VERSION = '2.3.0';
|
||||||
|
|
||||||
// Proxy /terminal (+ its WebSocket) to ttyd on CT 300, so the embedded terminal
|
// Proxy /terminal (+ its WebSocket) to ttyd on CT 300, so the embedded terminal
|
||||||
// works whether the Void is reached via Traefik (void2-app.hynesy.com) OR the
|
// works whether the Void is reached via Traefik (void2-app.hynesy.com) OR the
|
||||||
|
|||||||
Reference in New Issue
Block a user