diff --git a/homelab/docker-compose.yaml b/homelab/docker-compose.yaml index d6be65a..d00907a 100644 --- a/homelab/docker-compose.yaml +++ b/homelab/docker-compose.yaml @@ -17,13 +17,15 @@ services: - type: bind source: /srv/sys/caddy target: /data/caddy - - type: bind - source: /srv/storage/www - target: /www - read_only: true configs: - source: Caddyfile target: /etc/caddy/Caddyfile + - source: index.html + target: /www/index.html + - source: index.mjs + target: /www/index.mjs + - source: index.css + target: /www/index.css extra_hosts: - host.docker.internal:host-gateway @@ -223,7 +225,17 @@ configs: name: dave.yaml-v3 Caddyfile: file: Caddyfile - name: Caddyfile-v63 + name: Caddyfile-v65 + index.html: + file: www/index.html + name: index.html-v32 + index.mjs: + file: www/index.mjs + name: index.mjs-v1 + index.css: + file: www/index.css + name: index.css-v1 + secrets: diff --git a/homelab/www/index.css b/homelab/www/index.css new file mode 100644 index 0000000..93ca0b4 --- /dev/null +++ b/homelab/www/index.css @@ -0,0 +1,78 @@ +body { + display: flex; + flex-flow: column; + height: 100vh; + margin: 0; + font-family: Helvetica, sans-serif; + background-color: #222; + color: white; +} + +.hidden { + display: none; +} + +nav { + overflow-x: auto; + font-weight: bold; + font-size: 12pt; + display: flex; +} +nav img { + max-height: 2em; +} +nav a { + padding: 0.5em 1.5em; + color: #aaa; + text-decoration: none; + white-space: nowrap; +} +nav a[target] { + display: none; +} +nav a:hover { + background: #555; +} +nav a.active { + background: #8b8; + color: black; +} + +#app { + flex-grow: 1; +} +iframe { + display: block; + border: none; + width: 100%; + height: 100%; +} + +.icons { + margin: auto 2em; +} +.icons { + display: flex; + flex-wrap: wrap; + justify-content: center; + max-width: 40em; + margin: auto; + gap: 20px; +} +.icons a { + background-color: #444; + color: #fff; + width: 120px; + height: 120px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; +} +.icons a img { + max-width: 66%; + max-height: 66%; + width: 100%; + height: 100%; + image-rendering: pixelated; +} diff --git a/homelab/www/index.html b/homelab/www/index.html new file mode 100644 index 0000000..5df8d27 --- /dev/null +++ b/homelab/www/index.html @@ -0,0 +1,35 @@ + + + + Deer Grove + + + + + + + + +
+ +
+ + diff --git a/homelab/www/index.mjs b/homelab/www/index.mjs new file mode 100644 index 0000000..774d219 --- /dev/null +++ b/homelab/www/index.mjs @@ -0,0 +1,94 @@ +let frames = {} +function activate(event, element) { + if (element.target) { + return + } + event.preventDefault() + + let parent = element.parentElement + for (let e of parent.getElementsByClassName("active")) { + e.classList.remove("active") + } + element.classList.add("active") + let href = element.href + let app = document.querySelector("#app") + + let frame = frames[href] + if (frame) { + frame.dispatchEvent(new Event("load")) + } else { + frame = app.appendChild(document.createElement("iframe")) + frame.addEventListener("load", e => frameLoaded(frame)) + frame.src = href + frames[href] = frame + } + + for (let fhref in frames) { + let f = frames[fhref] + if (fhref == href) { + f.classList.remove("hidden") + } else { + f.classList.add("hidden") + } + } +} + + +function frameLoaded(frame) { + let doc = frame.contentDocument + if (doc.title.length > 0) { + document.title = doc.title + } + let icon = document.querySelector("link[rel~='icon']") + let dicon = doc.querySelector("link[rel~='icon']") + if (dicon) { + icon.href = dicon.href + } else { + icon.href = defaultIcon + } +} + +let defaultIcon = null +function init() { + let doc = document.querySelector("iframe").contentDocument + + defaultIcon = document.querySelector("link[rel~='icon']").href + + for (let l of document.head.querySelectorAll("style")) { + doc.head.appendChild(l.cloneNode(true)) + } + for (let l of document.head.querySelectorAll("link[rel='stylesheet']")) { + doc.head.appendChild(l.cloneNode()) + } + for (let f of document.querySelectorAll("#app iframe")) { + frames[f.src] = f + } + + + let icons = doc.body.appendChild(doc.createElement("section")) + icons.classList.add("icons") + + for (let link of document.querySelectorAll("nav a")) { + let dlink = icons.appendChild(link.cloneNode(true)) + dlink.textContent = "" + + if (link.dataset.icon) { + let icon = dlink.appendChild(doc.createElement("img")) + icon.src = link.dataset.icon + icon.style.objectFit = "cover" + } else { + let text = dlink.appendChild(doc.createElement("div")) + text.textContent = link.textContent + } + + // Make both of them update the selected tab + link.addEventListener("click", event => activate(event, link)) + dlink.addEventListener("click", event => activate(event, link)) + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +}