`;
}).join('')
@@ -403,35 +418,48 @@ async function refresh() {
loadInternetSlots();
}
-// Populates each installed internet-viable app's "Expose to Internet" /
-// "Public …" control. Separate from the main refresh() render because the
-// current publish state needs its own per-app fetch — the /api/apps list
-// only carries whether an app is *capable* of being exposed, not whether
-// it currently is.
+// Populates each installed internet-viable app's public-URL status line
+// and its "Expose to Internet" / "Stop exposing" menu item. Separate from
+// the main refresh() render because the current publish state needs its
+// own per-app fetch — the /api/apps list only carries whether an app is
+// *capable* of being exposed, not whether it currently is.
async function loadInternetSlots() {
- for (const slot of document.querySelectorAll('.internet-slot')) {
- const name = slot.dataset.name;
+ for (const item of document.querySelectorAll('.internet-item')) {
+ const name = item.dataset.name;
+ const status = document.querySelector(`.internet-status[data-name="${CSS.escape(name)}"]`);
try {
const r = await fetch(`/api/apps/${encodeURIComponent(name)}/internet`);
if (!r.ok) continue;
- renderInternetSlot(slot, name, await r.json());
+ renderInternetSlot(item, status, name, await r.json());
} catch (e) {
- // Leave the slot empty — next refresh() retries.
+ // Leave the slots empty — next refresh() retries.
}
}
}
-function renderInternetSlot(slot, name, data) {
+function renderInternetSlot(item, status, name, data) {
if (data.enabled) {
- slot.innerHTML =
- `Public ↗` +
- ``;
+ const host = data.public_url.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');
+ if (status) {
+ status.innerHTML =
+ `` +
+ `${esc(host)} ↗`;
+ }
+ item.innerHTML = ``;
} else {
- slot.innerHTML = ``;
+ if (status) status.innerHTML = '';
+ item.innerHTML = ``;
}
- slot.querySelector('[data-internet]').addEventListener('click', () => handleInternetToggle(name, !!data.enabled));
+ item.querySelector('[data-internet]').addEventListener('click', () => handleInternetToggle(name, !!data.enabled));
}
+// menus don't close on an outside click by themselves.
+document.addEventListener('click', (e) => {
+ for (const menu of document.querySelectorAll('details.menu[open]')) {
+ if (!menu.contains(e.target)) menu.removeAttribute('open');
+ }
+});
+
async function handleInternetToggle(name, currentlyEnabled) {
const logEl = document.getElementById('log');
if (currentlyEnabled) {