diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32ac5ba..7117a0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,35 @@
+# v0.4.3 — Auto-logo's legacy-apps + fix lege Docker Images
+
+## Nieuw / fixes in v0.4.3
+
+### 🖼️ Automatische logo's voor legacy-apps
+Legacy-apps (eigen `app.json`/`stack.json`-stacks) krijgen nu automatisch een
+logo via de dashboard-icons CDN, afgeleid uit de app-naam (bv. "Nextcloud" →
+`nextcloud.png`). Een expliciete logo-URL in de metadata wint; bestaat het
+geraden icoon niet, dan valt de UI terug op het emoji-icoon. Geldt voor zowel
+de App Store als geïnstalleerde stacks.
+
+### 🐛 Fix: Docker Images-pagina was leeg
+De afbeeldingenlijst gebruikte `:key="img.id"`, maar meerdere tags kunnen
+dezelfde image-ID delen → dubbele Alpine-keys waardoor de tabel niet rendert
+(en de "geen images"-melding ook niet, want er waren wél images). De `x-for`
+gebruikt nu de index als key.
+
+---
+
+# v0.4.2 — App-logo's bij stacks
+
+## Nieuw in v0.4.2
+
+Geïnstalleerde stacks tonen nu het app-logo (of een emoji-icoon als fallback),
+zowel op het dashboard als op de Stacks-pagina. Bij installatie wordt het
+logo/icoon van de bron-app opgeslagen in `.serverup.json` in de stack-map.
+Bestaande installaties krijgen hun logo via een naam-match met de App Store,
+dus ze hoeven niet opnieuw geïnstalleerd te worden. De store toonde al logo's
+voor boilerplate-apps (via de selfhst/dashboard-icons CDN).
+
+---
+
# v0.4.1 — Fix: styling werd niet toegepast
## Fix bovenop v0.4.0
diff --git a/server-up/app.py b/server-up/app.py
index 60af414..60c625b 100644
--- a/server-up/app.py
+++ b/server-up/app.py
@@ -18,7 +18,7 @@ from core.modules import Module, CORE, discover
app = Flask(__name__, static_folder="static", template_folder="templates")
-VERSION = os.environ.get("SU_VERSION", "0.4.1")
+VERSION = os.environ.get("SU_VERSION", "0.4.3")
MODULES: list[Module] = []
USER_MOD = APP / "modules"
@@ -173,6 +173,37 @@ def api_stacks():
# Bescherm tegen verkeerde LIBRARY_DIR
if lib == Path("/app").resolve():
return jsonify([])
+ # Logo/icon-lijst uit de store opbouwen (best effort) voor naam-fallback
+ icon_map = {}
+ try:
+ c = cfg.load()
+ for repo in c.get("APP_REPOS", []):
+ for st in git.scan_stacks(repo.get("id", ""), lib, subdir=repo.get("subdir", "")):
+ key = st.get("dir") or st.get("name")
+ if key and (st.get("logo_url") or st.get("icon")):
+ icon_map.setdefault(key, {"logo_url": st.get("logo_url", ""),
+ "icon": st.get("icon", "")})
+ except Exception:
+ pass
+
+ def _stack_logo(d):
+ # 1) Voorkeur: opgeslagen metadata bij installatie
+ mf = d / ".serverup.json"
+ if mf.exists():
+ try:
+ m = json.loads(mf.read_text("utf-8"))
+ if m.get("logo_url") or m.get("icon"):
+ return m.get("logo_url", ""), m.get("icon", "")
+ except Exception:
+ pass
+ # 2) Fallback: match op naam met de store (ook voor oudere installaties)
+ if d.name in icon_map:
+ h = icon_map[d.name]; return h["logo_url"], h["icon"]
+ for key, h in icon_map.items():
+ if d.name.startswith(key + "-"):
+ return h["logo_url"], h["icon"]
+ return "", ""
+
out = []
for d in sorted(lib.iterdir()):
if not d.is_dir() or d.name.startswith("."):
@@ -180,10 +211,12 @@ def api_stacks():
if not docker.has_compose(d):
continue
ct = docker.compose_ps(d, name=d.name)
+ logo_url, icon = _stack_logo(d)
out.append({
"name": d.name, "path": str(d), "has_compose": True,
"running": any(c["running"] for c in ct),
"containers": ct,
+ "logo_url": logo_url, "icon": icon,
})
return jsonify(out)
@@ -637,6 +670,22 @@ def api_store_install():
except Exception:
pass
+ # Bewaar logo/icon-metadata zodat de stack-lijst het kan tonen
+ try:
+ meta = boilerplates.metadata(src) if is_bp else git._meta(src)
+ logo = meta.get("logo_url", "")
+ if not logo and not is_bp:
+ logo = git.guess_logo_url(meta.get("name") or stack)
+ (dest / ".serverup.json").write_text(json.dumps({
+ "source": stack,
+ "repo_id": rid,
+ "name": meta.get("name", stack),
+ "logo_url": logo,
+ "icon": meta.get("icon", ""),
+ }, ensure_ascii=False, indent=2), encoding="utf-8")
+ except Exception:
+ pass
+
# Start
jobs.log(qq, "section", "Starten")
rc = docker.compose_up(dest, log_fn=lambda m: jobs.log(qq, "dim", m), name=inst)
diff --git a/server-up/core/git.py b/server-up/core/git.py
index 3a9984b..7886ace 100644
--- a/server-up/core/git.py
+++ b/server-up/core/git.py
@@ -1,9 +1,18 @@
"""Git operations — clone, pull, scan for stacks and modules."""
-import json, os, subprocess, shutil
+import json, os, re, subprocess, shutil
from pathlib import Path
from core.docker import COMPOSE_NAMES
from core import boilerplates as bp
+
+def guess_logo_url(name: str) -> str:
+ """Leid een dashboard-icons logo-URL af uit een app-naam.
+ Bestaat het icoon niet, dan faalt de en valt de UI terug op de emoji."""
+ slug = re.sub(r"[^a-z0-9]+", "-", (name or "").lower()).strip("-")
+ if not slug:
+ return ""
+ return f"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/{slug}.png"
+
CACHE = Path(os.environ.get("SU_GIT_CACHE", "/data/git"))
# Fix git "dubious ownership" — container draait als root, cache kan door andere uid zijn
@@ -129,6 +138,14 @@ def _scan_compose_dirs(base: Path, lib: Path) -> list[dict]:
else:
meta = _meta(d)
meta.setdefault("format", "compose")
+ # Auto-logo voor legacy-apps: gebruik expliciete logo_url, anders raden
+ # op basis van de naam (dashboard-icons). Emoji blijft fallback in de UI.
+ if not meta.get("logo_url"):
+ ic = meta.get("icon", "")
+ if isinstance(ic, str) and (ic.startswith("http://") or ic.startswith("https://")):
+ meta["logo_url"] = ic
+ else:
+ meta["logo_url"] = guess_logo_url(meta.get("name") or d.name)
installed = []
if lib.exists():
diff --git a/server-up/templates/index.html b/server-up/templates/index.html
index df6b9e2..ec356b7 100644
--- a/server-up/templates/index.html
+++ b/server-up/templates/index.html
@@ -164,6 +164,8 @@ tailwind.config = {