Files

317 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""Homarr dashboard — alle actieve Foodlinkk links + tiles op board."""
from __future__ import annotations
import json
import random
import sqlite3
import string
DB = "/opt/homarr_db/db.sqlite"
BOARD_ID = "jnyjc9756utndw62gsg78ssb"
SECTION_ID = "lvq9qc0p59sllgek92n77qi8"
LAYOUT_ID = "lft1byu8yp01glpe006l7cuq"
ICON = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons"
# (name, description, href, ping_url, icon_url)
APPS: list[tuple[str, str, str, str | None, str]] = [
# —— Kern (Foodlinkk) ——
(
"Foodlinkk Cockpit",
"Command Center — hoofd-UI",
"http://10.4.7.18:8600/",
"http://10.4.7.18:8600/",
f"{ICON}/svg/home-assistant.svg",
),
(
"Wereldexport",
"Export Intel · kaart · distributeurs",
"http://10.4.7.18:8600/export-intel",
"http://10.4.7.18:8600/export-intel",
f"{ICON}/svg/world.svg",
),
(
"Voice Live",
"Herman voice · hands-free",
"http://10.4.7.18:8600/voice",
"http://10.4.7.18:8600/voice",
f"{ICON}/svg/google-assistant.svg",
),
(
"Herman Chat",
"Browser chat + agy builds",
"http://10.4.7.18:8600/herman",
"http://10.4.7.18:8600/herman",
f"{ICON}/svg/open-webui.svg",
),
(
"Agents",
"Mesh · terminals · webbuilder",
"http://10.4.7.18:8600/agents",
"http://10.4.7.18:8600/agents",
f"{ICON}/svg/portainer.svg",
),
(
"Revenue Cockpit",
"Excel-live dashboard",
"http://10.4.7.18:8600/revenue-cockpit",
"http://10.4.7.18:8600/revenue-cockpit",
f"{ICON}/svg/grafana.svg",
),
(
"Gitea",
"Git · backups · repo",
"http://10.4.7.18:3001/aissa/foodlinkk-command-center",
"http://10.4.7.18:3001/",
f"{ICON}/svg/gitea.svg",
),
# —— Infra ——
(
"Proxmox",
"Hypervisor R340",
"https://10.4.7.14:8006/",
"https://10.4.7.14:8006/",
f"{ICON}/svg/proxmox.svg",
),
(
"Synology NAS",
"FL-nas DSM",
"http://10.4.7.11:5000/",
"http://10.4.7.11:5000/",
f"{ICON}/png/synology-dsm.png",
),
(
"Homarr",
"Dit dashboard",
"http://10.4.7.13:3000/",
"http://10.4.7.13:3000/",
f"{ICON}/svg/homarr.svg",
),
(
"Dockhand",
"Docker beheer VM106",
"http://10.4.7.18:3000/",
"http://10.4.7.18:3000/",
f"{ICON}/svg/dockge.svg",
),
# —— AI VM105 / VM107 ——
(
"Herman Orchestrator",
"VM105 · agent routing",
"http://10.4.7.19:8090/",
"http://10.4.7.19:8090/health",
f"{ICON}/svg/ollama-light.svg",
),
(
"Open WebUI",
"Ollama chat UI",
"http://10.4.7.19:8080/",
"http://10.4.7.19:8080/",
f"{ICON}/svg/open-webui.svg",
),
(
"Doc Ingest",
"NAS documenten · RAG",
"http://10.4.7.19:8750/",
"http://10.4.7.19:8750/health",
f"{ICON}/svg/paperless-ngx.svg",
),
(
"Browser Use",
"AI browser agent",
"http://10.4.7.19:7788/",
"http://10.4.7.19:7788/",
f"{ICON}/svg/chromium.svg",
),
(
"n8n",
"Workflow automation",
"http://10.4.7.19:5678/",
"http://10.4.7.19:5678/",
f"{ICON}/svg/n8n.svg",
),
(
"Web Builder (agy)",
"Antigravity · VM107 API",
"http://10.4.7.27:8798/health",
"http://10.4.7.27:8798/health",
f"{ICON}/svg/nginx.svg",
),
(
"Website Preview",
"agy build preview",
"http://10.4.7.27:8080/",
"http://10.4.7.27:8080/",
f"{ICON}/svg/nginx.svg",
),
# —— Tools VM106 ——
(
"ComfyUI",
"Image generation",
"http://10.4.7.18:8188/",
"http://10.4.7.18:8188/",
f"{ICON}/svg/comfyui.svg",
),
(
"Browser VNC",
"Live browser VM106",
"http://10.4.7.18:6080/",
"http://10.4.7.18:6080/",
f"{ICON}/svg/chromium.svg",
),
(
"MinIO",
"Object storage",
"http://10.4.7.18:9001/",
"http://10.4.7.18:9001/",
f"{ICON}/svg/minio-light.svg",
),
(
"Grafana",
"Monitoring dashboards",
"http://10.4.7.18:3002/",
"http://10.4.7.18:3002/",
f"{ICON}/svg/grafana.svg",
),
(
"Prometheus",
"Metrics",
"http://10.4.7.18:9090/",
"http://10.4.7.18:9090/",
f"{ICON}/svg/prometheus.svg",
),
# —— Overig ——
(
"BentoPDF",
"PDF tools",
"http://10.4.7.17:8080/",
"http://10.4.7.17:8080/",
f"{ICON}/svg/stirling-pdf.svg",
),
(
"Excalidraw",
"Whiteboard",
"http://10.4.7.16:3000/",
"http://10.4.7.16:3000/",
f"{ICON}/svg/excalidraw.svg",
),
]
# Volgorde op het board (namen)
BOARD_ORDER = [
"Foodlinkk Cockpit",
"Wereldexport",
"Voice Live",
"Herman Chat",
"Agents",
"Revenue Cockpit",
"Gitea",
"Proxmox",
"Synology NAS",
"Homarr",
"Dockhand",
"Herman Orchestrator",
"Open WebUI",
"Web Builder (agy)",
"Website Preview",
"Doc Ingest",
"Browser Use",
"n8n",
"ComfyUI",
"Browser VNC",
"MinIO",
"Grafana",
"Prometheus",
"BentoPDF",
"Excalidraw",
]
MOBILE_CSS = """
@media (max-width: 900px) {
html { font-size: 18px !important; }
[data-testid="app-tile"] { min-height: 72px !important; }
button, .mantine-Button-root { min-height: 44px !important; }
}
"""
def gen_id() -> str:
return "".join(random.choices(string.ascii_lowercase + string.digits, k=24))
def upsert_app(cur: sqlite3.Cursor, name: str, desc: str, href: str, ping: str | None, icon: str) -> str:
row = cur.execute("SELECT id FROM app WHERE name=?", (name,)).fetchone()
if row:
aid = row[0]
cur.execute(
"UPDATE app SET description=?, href=?, ping_url=?, icon_url=? WHERE id=?",
(desc, href, ping, icon, aid),
)
else:
aid = gen_id()
cur.execute(
"INSERT INTO app (id, name, description, icon_url, href, ping_url) VALUES (?,?,?,?,?,?)",
(aid, name, desc, icon, href, ping),
)
return aid
def main() -> None:
conn = sqlite3.connect(DB)
cur = conn.cursor()
cur.execute("UPDATE board SET custom_css=?, item_radius=? WHERE id=?", (MOBILE_CSS.strip(), "md", BOARD_ID))
cur.execute("UPDATE layout SET column_count=? WHERE id=?", (4, LAYOUT_ID))
name_to_id: dict[str, str] = {}
for name, desc, href, ping, icon in APPS:
name_to_id[name] = upsert_app(cur, name, desc, href, ping, icon)
# Verwijder oude board-tiles (behoud app-records)
old_items = [r[0] for r in cur.execute("SELECT id FROM item WHERE board_id=?", (BOARD_ID,)).fetchall()]
for iid in old_items:
cur.execute("DELETE FROM item_layout WHERE item_id=?", (iid,))
cur.execute("DELETE FROM item WHERE id=?", (iid,))
cols = 4
for slot, name in enumerate(BOARD_ORDER):
aid = name_to_id.get(name)
if not aid:
continue
item_id = gen_id()
opts = json.dumps(
{
"json": {
"appId": aid,
"openInNewTab": True,
"showTitle": True,
"pingEnabled": True,
}
}
)
cur.execute(
"INSERT INTO item (id, board_id, kind, options, advanced_options) VALUES (?,?,?,?,?)",
(item_id, BOARD_ID, "app", opts, "{}"),
)
x = slot % cols
y = slot // cols
cur.execute(
"""INSERT INTO item_layout
(item_id, section_id, layout_id, x_offset, y_offset, width, height)
VALUES (?,?,?,?,?,?,?)""",
(item_id, SECTION_ID, LAYOUT_ID, x, y, 1, 1),
)
conn.commit()
print(f"Board tiles: {len(BOARD_ORDER)}")
for name in BOARD_ORDER:
href = cur.execute("SELECT href FROM app WHERE name=?", (name,)).fetchone()
if href:
print(f" ✓ {name}: {href[0]}")
conn.close()
if __name__ == "__main__":
main()