Abmelden nur noch in den Einstellungen, App-Menue statt Zahnrad

Marcs Entscheidung: In der HoneyBar fremder Apps steht kein Abmelden mehr
— es war dort ohnehin nur ein Link auf den Schreibtisch. Als einzige
Schaltflaeche, die eine Sitzung beendet, steht es jetzt an genau einer
Stelle: unten in den Einstellungen, sichtbar nur bei eigener Anmeldung.
Damit faellt auch der ?abmelden=1-Pfad wieder weg und /api/apps braucht
das oidc-Feld nicht mehr.

Das Zahnrad rechts in der Menueleiste ist durch den Pfeil mit der
App-Liste ersetzt — derselbe wie in der HoneyBar. Beide Leisten tragen
damit dieselben vier Dinge: Logo, Apps, Wetter/Uhr, Aufklappmenue.
This commit is contained in:
marc committed 2026-08-29 21:51:00 +02:00
1 parent 0a5cd94ba0
commit 6d75ce78c2
7 files changed
+136 -65

No files matched your search

+10 -2
View File
@@ -41,8 +41,16 @@ Eine Annahme von damals hat sich nicht gehalten: „ohne sie zu verlassen"
schreibt, und die HoneyBar darf in einer fremden App nicht schreiben — die schreibt, und die HoneyBar darf in einer fremden App nicht schreiben — die
Sitzung kommt über die Site-Grenze nicht mit, und selbst unter einer Domain Sitzung kommt über die Site-Grenze nicht mit, und selbst unter einer Domain
liegt das CSRF-Prüfwort in einem Cookie, das nur HoneyOS' eigene Seiten lesen. liegt das CSRF-Prüfwort in einem Cookie, das nur HoneyOS' eigene Seiten lesen.
Also reicht die HoneyBar die Frage als `?einstellungen=1` / `?abmelden=1` an den Also reicht die HoneyBar die Frage als `?einstellungen=1` an den Schreibtisch
Schreibtisch weiter. Ein Klick, ein Seitenwechsel — dort, wo man angemeldet ist. weiter. Ein Klick, ein Seitenwechsel — dort, wo man angemeldet ist.
**Abmelden ist ganz aus den Leisten verschwunden** und steht unten in den
Einstellungen (Marcs Entscheidung, 29.08.2026). Aus fremden Apps war es ohnehin
nur ein Link auf den Schreibtisch gewesen; als einzige Schaltfläche, die eine
Sitzung beendet, gehört es an eine Stelle statt an drei. Im selben Zug ist das
Zahnrad aus der Menüleiste des Schreibtischs gefallen und durch den Pfeil mit
der App-Liste ersetzt worden — damit tragen beide Leisten wirklich dieselben
vier Dinge: Logo, Apps, Wetter/Uhr, Aufklappmenü.
**Was ohne Seitenwechsel ginge, wenn es je stört:** die reinen Aussehensschalter **Was ohne Seitenwechsel ginge, wenn es je stört:** die reinen Aussehensschalter
(Röhre, Leuchten, Scanlines) direkt im Menü. Sie kämen ohne Prüfwort aus, wenn (Röhre, Leuchten, Scanlines) direkt im Menü. Sie kämen ohne Prüfwort aus, wenn
+17 -14
View File
@@ -395,25 +395,28 @@ Das `■ HONEYOS` links ist ein Menü — auf dem Schreibtisch wie in jeder App:
| Schreibtisch | Über HoneyOS | | Schreibtisch | Über HoneyOS |
| Einstellungen … | Einstellungen | | Einstellungen … | Einstellungen |
| HoneyBar einklappen | Terminal-/Desktop-Modus | | HoneyBar einklappen | Terminal-/Desktop-Modus |
| Abmelden | Abmelden |
Rechts daneben bleibt der Pfeil mit den Apps. Eines auf heißt anderes zu. Rechts daneben klappt ein Pfeil die App-Liste auf — in beiden Leisten gleich.
Das Zahnrad, das dort früher stand, ist weg: Einstellungen stehen jetzt links
im Systemmenü, wo sie in jeder App auch stehen.
**In den Apps sind es Links auf den Schreibtisch, keine Handlungen** — und das **In den Apps sind die Punkte Links auf den Schreibtisch, keine Handlungen** —
ist eine Grenze, keine Bequemlichkeit. Einstellungen und Abmelden schreiben, und und das ist eine Grenze, keine Bequemlichkeit. Einstellungen schreiben, und
dafür braucht der Server die HoneyOS-Sitzung: Aus einer fremden Site schickt der dafür braucht der Server die HoneyOS-Sitzung: Aus einer fremden Site schickt der
Browser sie nicht mit, und selbst unter einer Domain liegt das CSRF-Prüfwort in Browser sie nicht mit, und selbst unter einer Domain liegt das CSRF-Prüfwort in
einem Cookie, das nur HoneyOS' eigene Seiten lesen dürfen. Ein Abmelden, das ein einem Cookie, das nur HoneyOS' eigene Seiten lesen dürfen. Also stellt die
einfacher Aufruf erledigt, könnte außerdem jede beliebige Seite auslösen. HoneyBar die Frage in der Adresse (`?einstellungen=1`), und der Schreibtisch
beantwortet sie: Er macht dasselbe Feld auf, das auch `[S]` öffnet, und nimmt
den Anhang danach wieder aus der Adresse.
Also stellt die HoneyBar die Frage in der Adresse (`?einstellungen=1`, **Abmelden steht in den Einstellungen, ganz unten — und sonst nirgends.** Nicht
`?abmelden=1`), und der Schreibtisch beantwortet sie: Er macht dasselbe in der HoneyBar fremder Apps, nicht im Systemmenü, nicht im Startmenü. Es ist
Einstellungsfeld auf, das auch `[S]` öffnet, und meldet mit einem echten POST die einzige Schaltfläche, die eine Sitzung beendet statt etwas einzustellen, und
ab. Danach nimmt er den Anhang aus der Adresse wieder heraus. sie gehört dorthin, wo man sich angemeldet hat. Wer es aus einer App heraus
braucht, geht über „Einstellungen …" — ein Klick mehr, dafür kann kein
`Abmelden` steht nur da, wenn HoneyOS überhaupt anmeldet — im offenen Betrieb eingebettetes Bild einer fremden Seite eine Abmeldung auslösen. Der Punkt
oder hinter einem Proxy fehlt der Punkt. Deshalb gibt `/api/apps` neben Liste erscheint nur, wenn HoneyOS selbst anmeldet; hinter einem Proxy oder im offenen
und Aussehen auch `oidc` heraus. Betrieb fehlt er.
## Wer angemeldet ist ## Wer angemeldet ist
-3
View File
@@ -316,9 +316,6 @@ func (s *Server) handleApps(w http.ResponseWriter, r *http.Request, sub string)
writeJSON(w, http.StatusOK, map[string]any{ writeJSON(w, http.StatusOK, map[string]any{
"apps": s.store.Apps(), "apps": s.store.Apps(),
"settings": s.store.SettingsFor(sub), "settings": s.store.SettingsFor(sub),
// Fuer das Systemmenue der HoneyBar: ohne Anbieter gibt es nichts
// abzumelden, dann steht der Punkt dort auch nicht.
"oidc": s.auth.Enabled(),
}) })
} }
+19
View File
@@ -777,6 +777,25 @@ body {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
} }
.mb-logo[aria-expanded="true"] { background: var(--p-faint); } .mb-logo[aria-expanded="true"] { background: var(--p-faint); }
/* Das App-Menü hängt am Pfeil rechts — sonst dasselbe Feld. */
.appmenu {
top: calc(32px + env(safe-area-inset-top) + 2px);
bottom: auto;
left: auto;
right: 6px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
}
/* Abmelden steht unten in den Einstellungen, abgesetzt vom Rest: Es ist die
einzige Schaltfläche dort, die nicht etwas einstellt, sondern etwas beendet. */
.field-out {
border-top: 1px solid var(--p-faint);
padding-top: 12px;
margin-top: 4px;
margin-bottom: 0;
}
.btn-out { width: 100%; color: var(--p-mid); }
.btn-out:hover { color: var(--bg); background: var(--p); }
.startmenu .sm-head { .startmenu .sm-head {
padding: 4px 8px; padding: 4px 8px;
font-size: 10px; font-size: 10px;
+14 -25
View File
@@ -56,8 +56,6 @@
let apps = DEFAULT_APPS; let apps = DEFAULT_APPS;
let settings = null; let settings = null;
// Ob HoneyOS ueberhaupt anmeldet — ohne Anbieter gaebe es nichts abzumelden.
let oidc = false;
// Was die Seite oben schon hatte, bevor die HoneyBar sie heruntergeschoben hat. // Was die Seite oben schon hatte, bevor die HoneyBar sie heruntergeschoben hat.
let urspruenglichesPolster = null; let urspruenglichesPolster = null;
@@ -222,8 +220,6 @@
<a id="mSettings" role="menuitem">Einstellungen &hellip;</a> <a id="mSettings" role="menuitem">Einstellungen &hellip;</a>
<hr /> <hr />
<button id="hide" role="menuitem">HoneyBar einklappen</button> <button id="hide" role="menuitem">HoneyBar einklappen</button>
<hr id="mLogoutSep" hidden />
<a id="mLogout" role="menuitem" hidden>Abmelden</a>
</div> </div>
<div class="menu" id="menu" role="menu"> <div class="menu" id="menu" role="menu">
@@ -400,31 +396,25 @@
} }
/** /**
* Die drei Wege ins System. Alle drei sind Links auf den Schreibtisch, keine * Die zwei Wege ins System. Beides sind Links auf den Schreibtisch, keine
* Aktionen hier — und das ist keine Bequemlichkeit, sondern die Grenze: * Aktionen hier — und das ist keine Bequemlichkeit, sondern die Grenze:
* *
* Einstellungen und Abmelden **schreiben**. Dafuer braucht der Server die * Einstellungen **schreiben**. Dafuer braucht der Server die HoneyOS-Sitzung,
* HoneyOS-Sitzung, und die schickt der Browser aus einer fremden App nicht * und die schickt der Browser aus einer fremden App nicht mit; liegt alles
* mit; liegt alles unter einer Domain, kommt sie zwar mit, aber das * unter einer Domain, kommt sie zwar mit, aber das CSRF-Prüfwort steht in
* CSRF-Prüfwort steht in einem Cookie der HoneyOS-Adresse, das dieses Skript * einem Cookie der HoneyOS-Adresse, das dieses Skript von hier aus nicht
* von hier aus nicht lesen darf. Und ein Abmelden, das ein simpler Aufruf * lesen darf.
* erledigt, koennte jede fremde Seite ausloesen.
* *
* Also: die HoneyBar zeigt den Weg, gehandelt wird dort, wo man angemeldet * Abmelden steht hier bewusst gar nicht: Es gehoert dorthin, wo man sich
* ist. Der Schreibtisch sieht die Frage in der Adresse und macht sie auf. * angemeldet hat. Wer es aus einer App heraus braucht, geht ueber
* „Einstellungen …" — dort unten steht der Knopf.
*/ */
function systemZiele() { function systemZiele() {
const ziel = (q) => { const heim = new URL(homeUrl, location.href);
const u = new URL(homeUrl, location.href); root.getElementById("mHome").href = heim.href;
u.search = q; const einst = new URL(homeUrl, location.href);
return u.href; einst.search = "einstellungen=1";
}; root.getElementById("mSettings").href = einst.href;
root.getElementById("mHome").href = new URL(homeUrl, location.href).href;
root.getElementById("mSettings").href = ziel("einstellungen=1");
const ab = root.getElementById("mLogout");
ab.href = ziel("abmelden=1");
ab.hidden = !oidc;
root.getElementById("mLogoutSep").hidden = !oidc;
} }
/** /**
@@ -520,7 +510,6 @@
apps = [{ id: "honeyos", name: "DESKTOP", url: homeUrl }].concat(data.apps); apps = [{ id: "honeyos", name: "DESKTOP", url: homeUrl }].concat(data.apps);
} }
settings = data.settings || null; settings = data.settings || null;
oidc = !!data.oidc;
} }
} catch { } catch {
/* nicht angemeldet, kein CORS, kein Netz → eingebaute Liste; /* nicht angemeldet, kein CORS, kein Netz → eingebaute Liste;
+4 -1
View File
@@ -39,7 +39,10 @@
<div class="mb-right"> <div class="mb-right">
<span class="mb-wx" id="mbWx" hidden></span> <span class="mb-wx" id="mbWx" hidden></span>
<span class="mb-clock" id="clock">--:--</span> <span class="mb-clock" id="clock">--:--</span>
<button class="mb-btn" id="btnSettings" title="Einstellungen (S)">&#9881;</button> <!-- Derselbe Pfeil wie in der HoneyBar: die Apps zum Aufklappen,
fuer schmale Fenster, in denen der Streifen oben nicht alles
zeigt. Einstellungen stehen im Systemmenü links. -->
<button class="mb-btn" id="btnAppMenu" title="Apps" aria-haspopup="true" aria-expanded="false">&#9662;</button>
</div> </div>
</header> </header>
+72 -20
View File
@@ -590,7 +590,15 @@ function openSettings() {
<p class="login-note" style="margin-top:4px"> <p class="login-note" style="margin-top:4px">
Diese Einstellungen liegen auf dem Server, nicht im Browser — auf jedem Diese Einstellungen liegen auf dem Server, nicht im Browser — auf jedem
Gerät, an dem du angemeldet bist, sieht HoneyOS gleich aus. Gerät, an dem du angemeldet bist, sieht HoneyOS gleich aus.
</p>`, </p>` +
// Abmelden steht hier und nur hier: Es ist das Einzige, was man aus einer
// fremden App heraus nicht anklicken können soll (siehe honeybar.js), und
// von HoneyOS aus findet man es dort, wo alles andere auch steht.
(state.oidc
? `<div class="field field-out">
<button class="btn btn-out" id="btnLogout">⏻ Abmelden</button>
</div>`
: ""),
(root) => { (root) => {
root.querySelectorAll(".seg").forEach((segEl) => { root.querySelectorAll(".seg").forEach((segEl) => {
segEl.addEventListener("click", (e) => { segEl.addEventListener("click", (e) => {
@@ -662,6 +670,7 @@ function openSettings() {
}); });
root.querySelector("#btnApps").addEventListener("click", openApps); root.querySelector("#btnApps").addEventListener("click", openApps);
root.querySelector("#btnLogout")?.addEventListener("click", logout);
}, },
420 420
); );
@@ -791,6 +800,58 @@ function openAbout() {
/* ── Hilfsmittel ───────────────────────────────────────────────────────── */ /* ── Hilfsmittel ───────────────────────────────────────────────────────── */
/* ── App-Menü im Pfeil rechts ──────────────────────────────────────────────
* Der Zwilling des Pfeils in honeybar.js. Die Apps stehen zwar schon als
* Streifen in der Leiste, aber der schiebt bei vielen Apps seitwärts — das
* Menü zeigt sie alle untereinander. Das Zahnrad ist dafür weggefallen:
* Einstellungen stehen im Systemmenü links, wie in jeder App.
*/
function buildAppMenu() {
const menu = document.createElement("div");
menu.className = "startmenu appmenu";
menu.id = "appmenu";
$("desktop").appendChild(menu);
const btn = $("btnAppMenu");
btn.addEventListener("click", (e) => {
e.stopPropagation();
if (menu.dataset.open === "1") return closeAppMenu();
closeSystemMenu();
closeStartMenu();
menu.innerHTML =
`<div class="sm-head">wechseln zu</div>` +
(state.cfg.apps.length
? state.cfg.apps.map((a) => `<button data-app="${escapeAttr(a.id)}">${escapeHtml(a.name)}</button>`).join("")
: `<button data-settings="1">keine app eingetragen …</button>`);
menu.querySelectorAll("[data-app]").forEach((b) =>
b.addEventListener("click", () => {
const app = state.cfg.apps.find((a) => a.id === b.dataset.app);
closeAppMenu();
if (app) launch(app);
})
);
menu.querySelector("[data-settings]")?.addEventListener("click", () => {
closeAppMenu();
openApps();
});
menu.dataset.open = "1";
btn.setAttribute("aria-expanded", "true");
});
document.addEventListener("click", closeAppMenu);
}
function closeAppMenu() {
const menu = $("appmenu");
if (!menu) return;
menu.dataset.open = "0";
$("btnAppMenu").setAttribute("aria-expanded", "false");
}
/* ── Systemmenü im Logo ──────────────────────────────────────────────────── /* ── Systemmenü im Logo ────────────────────────────────────────────────────
* Der Zwilling des Menüs in honeybar.js: gleiche Punkte, gleiche Reihenfolge. * Der Zwilling des Menüs in honeybar.js: gleiche Punkte, gleiche Reihenfolge.
* Hier sind es Handlungen, dort Links hierher — denn nur hier ist man * Hier sind es Handlungen, dort Links hierher — denn nur hier ist man
@@ -809,6 +870,7 @@ function buildSystemMenu() {
e.stopPropagation(); e.stopPropagation();
if (menu.dataset.open === "1") return closeSystemMenu(); if (menu.dataset.open === "1") return closeSystemMenu();
closeStartMenu(); closeStartMenu();
closeAppMenu();
const terminal = state.settings.mode === "terminal"; const terminal = state.settings.mode === "terminal";
menu.innerHTML = menu.innerHTML =
@@ -816,8 +878,7 @@ function buildSystemMenu() {
<button data-do="about">Über HoneyOS</button> <button data-do="about">Über HoneyOS</button>
<button data-do="settings">Einstellungen</button> <button data-do="settings">Einstellungen</button>
<hr /> <hr />
<button data-do="mode">${terminal ? "▣ Desktop-Modus" : "↩ Terminal-Modus"}</button>` + <button data-do="mode">${terminal ? "▣ Desktop-Modus" : "↩ Terminal-Modus"}</button>`;
(state.oidc ? `<hr /><button data-do="logout">⏻ Abmelden</button>` : "");
menu.querySelectorAll("[data-do]").forEach((b) => menu.querySelectorAll("[data-do]").forEach((b) =>
b.addEventListener("click", () => { b.addEventListener("click", () => {
@@ -826,7 +887,6 @@ function buildSystemMenu() {
about: openAbout, about: openAbout,
settings: openSettings, settings: openSettings,
mode: () => setMode(terminal ? "desktop" : "terminal"), mode: () => setMode(terminal ? "desktop" : "terminal"),
logout,
})[b.dataset.do](); })[b.dataset.do]();
}) })
); );
@@ -858,10 +918,11 @@ function buildStartMenu() {
e.stopPropagation(); e.stopPropagation();
const open = menu.dataset.open === "1"; const open = menu.dataset.open === "1";
if (open) return closeStartMenu(); if (open) return closeStartMenu();
// Beide Menüs offen ginge, sähe aber aus wie ein Fehler: Der Klick auf den // Zwei offene Menüs sähen aus wie ein Fehler: Der Klick auf den Startknopf
// Startknopf hält sein Ereignis auf, also erreicht es den Schließer des // hält sein Ereignis auf, also erreicht es die Schließer der anderen nicht
// Systemmenüs nicht von selbst. // von selbst.
closeSystemMenu(); closeSystemMenu();
closeAppMenu();
menu.innerHTML = `<div class="sm-head">starten</div>` + menu.innerHTML = `<div class="sm-head">starten</div>` +
state.cfg.apps.map((a) => `<button data-app="${a.id}">${escapeHtml(a.name)}</button>`).join("") + state.cfg.apps.map((a) => `<button data-app="${a.id}">${escapeHtml(a.name)}</button>`).join("") +
@@ -869,10 +930,7 @@ function buildStartMenu() {
<button data-win="system">System</button> <button data-win="system">System</button>
<button data-win="settings">Einstellungen</button> <button data-win="settings">Einstellungen</button>
<button data-win="about">Über HoneyOS</button> <button data-win="about">Über HoneyOS</button>
<hr /><button data-mode="terminal">↩ Terminal-Modus</button>` + <hr /><button data-mode="terminal">↩ Terminal-Modus</button>`;
// Ohne Anbieter gibt es nichts abzumelden — im Systemmenü steht der
// Punkt aus demselben Grund nur bei Anmeldung.
(state.oidc ? `<button data-logout="1">⏻ Abmelden</button>` : "");
menu.querySelectorAll("[data-app]").forEach((b) => menu.querySelectorAll("[data-app]").forEach((b) =>
b.addEventListener("click", () => { b.addEventListener("click", () => {
@@ -893,7 +951,6 @@ function buildStartMenu() {
closeStartMenu(); closeStartMenu();
setMode("terminal"); setMode("terminal");
}); });
menu.querySelector("[data-logout]")?.addEventListener("click", logout);
menu.dataset.open = "1"; menu.dataset.open = "1";
btn.setAttribute("aria-expanded", "true"); btn.setAttribute("aria-expanded", "true");
@@ -981,13 +1038,6 @@ async function main() {
loadLocalSettings(); loadLocalSettings();
applySettings(); applySettings();
// ?abmelden=1 kommt aus dem Systemmenü der HoneyBar. Es hier zu erledigen
// statt am Server heißt: Die Abmeldung bleibt ein POST von dieser Seite aus,
// und keine fremde Seite kann sie durch ein eingebettetes Bild auslösen.
if (new URLSearchParams(location.search).get("abmelden") === "1") {
return logout();
}
let pub = { oidc: false, buttonLabel: "Anmelden" }; let pub = { oidc: false, buttonLabel: "Anmelden" };
try { try {
pub = await api("/api/public"); pub = await api("/api/public");
@@ -1015,11 +1065,11 @@ async function main() {
renderShell(); renderShell();
buildStartMenu(); buildStartMenu();
buildSystemMenu(); buildSystemMenu();
buildAppMenu();
probeApps(); probeApps();
setInterval(tickClock, 15000); setInterval(tickClock, 15000);
setInterval(probeApps, 120000); setInterval(probeApps, 120000);
$("btnSettings").addEventListener("click", openSettings);
$("btnClose").addEventListener("click", closeModal); $("btnClose").addEventListener("click", closeModal);
$("modal").addEventListener("click", (e) => { $("modal").addEventListener("click", (e) => {
if (e.target === $("modal")) closeModal(); if (e.target === $("modal")) closeModal();
@@ -1044,6 +1094,8 @@ async function main() {
if (menu && menu.dataset.open === "1") return closeStartMenu(); if (menu && menu.dataset.open === "1") return closeStartMenu();
const sys = $("sysmenu"); const sys = $("sysmenu");
if (sys && sys.dataset.open === "1") return closeSystemMenu(); if (sys && sys.dataset.open === "1") return closeSystemMenu();
const apps = $("appmenu");
if (apps && apps.dataset.open === "1") return closeAppMenu();
return state.settings.mode === "desktop" ? closeTopWindow() : closeModal(); return state.settings.mode === "desktop" ? closeTopWindow() : closeModal();
} }
if (typing) return; if (typing) return;