Einstieg Schritt 4: Erste Schritte auf dem Dashboard, Navigation in Reihenfolge des Wegs, Standort/Zweck statt OE

Kunden landen nach der Anmeldung auf dem Dashboard; der Assistent
verschwindet mit dem ersten Gerät online, danach ein Hinweis auf
Standorte/Zwecke, solange es nur "Neue Geräte" gibt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-25 12:47:35 +01:00
1 parent ecb5c9b63e
commit e44b1b64b8
17 files changed
+120 -91

No files matched your search

+9 -2
View File
@@ -448,10 +448,17 @@ def fetch_admin_konten() -> list[dict]:
router = APIRouter()
def startseite(user) -> str:
"""Kunden landen auf dem Dashboard mit „Erste Schritte“ (Einstieg
vereinfachen, Schritt 4), Tuxflotte-Admins wie bisher in der Flotte."""
return "/geraete" if user.is_superuser else "/dashboard"
@router.get("/login")
async def login_form(request: Request, user: Optional[User] = Depends(_current_user_optional)):
if user is not None:
return RedirectResponse("/geraete", status_code=303)
return RedirectResponse(startseite(user), status_code=303)
hinweis = None
if request.query_params.get("zurueckgesetzt"):
@@ -493,7 +500,7 @@ async def login_submit(
# die Sitzung muss zum gespeicherten Hash passen.
user = await user_manager.get(user.id)
response = RedirectResponse("/geraete", status_code=303)
response = RedirectResponse(startseite(user), status_code=303)
await sitzung_setzen(response, user)
return response
+14
View File
@@ -81,12 +81,26 @@ def dashboard_ansicht(request: Request, user=Depends(get_current_user)):
naechste_schritte = (dashboard or {}).get("naechste_schritte", {})
naechste_schritte["hat_installationsmedium"] = _hat_installationsmedium(organization_id)
# Einstieg vereinfachen, Schritt 4: "Erste Schritte", bis das erste Gerät
# online ist. Dazu der Workspace, den neue Geräte bekommen, und ob es ein
# WLAN der Organisation gibt.
erste_schritte = None
if not naechste_schritte.get("hat_aktives_geraet"):
wlan = anode_request("GET", f"/api/v1/organizations/{organization_id}/org-wlan")
oes = anode_request("GET", f"/api/v1/organizations/{organization_id}/organisationseinheiten")
standard = next((o for o in oes.get("organisationseinheiten", []) if o.get("ist_standard")), None)
erste_schritte = {
"org_wlan": wlan.get("org_wlan") if wlan.get("success") else None,
"workspace": (standard or {}).get("wirksamer_workspace"),
}
return templates.TemplateResponse(
request,
"dashboard.html",
{
"user": user,
"naechste_schritte": naechste_schritte,
"erste_schritte": erste_schritte,
"ampel": (dashboard or {}).get("ampel"),
"aktivitaeten": aktivitaeten_zusammenfassen(aktivitaeten),
"verteilungen": (dashboard or {}).get("verteilungen"),
+9 -9
View File
@@ -70,7 +70,7 @@ def oe_workspace(
organization_id = str(user.organization_id)
if find_oe_in_organization(oe_id, organization_id) is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
if workspace_id:
workspace = next((w for w in zuweisbare_workspaces(organization_id) if w["id"] == workspace_id), None)
if workspace is None:
@@ -111,7 +111,7 @@ def oe_umbenennen_form(request: Request, oe_id: str, user=Depends(get_current_us
oe = find_oe_in_organization(oe_id, user.organization_id)
if oe is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
return templates.TemplateResponse(request, "struktur_umbenennen.html", {"user": user, "oe": oe})
@@ -121,7 +121,7 @@ def oe_umbenennen(oe_id: str, name: str = Form(...), user=Depends(get_current_us
oe = find_oe_in_organization(oe_id, user.organization_id)
if oe is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
anode_request(
"PATCH",
@@ -137,7 +137,7 @@ def oe_loeschen_form(request: Request, oe_id: str, user=Depends(get_current_user
oe = find_oe_in_organization(oe_id, user.organization_id)
if oe is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
return templates.TemplateResponse(request, "struktur_loeschen.html", {"user": user, "oe": oe, "fehler": None})
@@ -147,7 +147,7 @@ def oe_loeschen(request: Request, oe_id: str, user=Depends(get_current_user)):
oe = find_oe_in_organization(oe_id, user.organization_id)
if oe is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
result = anode_request("DELETE", f"/api/v1/organisationseinheiten/{oe_id}")
@@ -164,7 +164,7 @@ def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_u
oe = find_oe_in_organization(oe_id, user.organization_id)
if oe is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
result = anode_request("GET", f"/api/v1/organisationseinheiten/{oe_id}/merkmale")
merkmale = result.get("merkmale", []) if result.get("success") else []
@@ -184,7 +184,7 @@ def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_u
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/select")
async def oe_merkmal_select(request: Request, oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
if find_oe_in_organization(oe_id, user.organization_id) is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
form = await request.form()
optionen = parse_optionen_form(form, merkmal_key)
@@ -200,7 +200,7 @@ async def oe_merkmal_select(request: Request, oe_id: str, merkmal_key: str, user
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/deselect")
def oe_merkmal_deselect(oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
if find_oe_in_organization(oe_id, user.organization_id) is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/deselect")
@@ -210,7 +210,7 @@ def oe_merkmal_deselect(oe_id: str, merkmal_key: str, user=Depends(get_current_u
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/unset")
def oe_merkmal_unset(oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
if find_oe_in_organization(oe_id, user.organization_id) is None:
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
raise HTTPException(status_code=404, detail="Standort/Zweck wurde nicht gefunden.")
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/unset")
+3 -3
View File
@@ -37,11 +37,11 @@
alle Geräte der Organisation
</label>
{% if ressourcen_oes or ressourcen_gruppen %}
<small>Sonst nur auf die gewählten Organisationseinheiten (samt untergeordneten) und Gruppen:</small>
<small>Sonst nur auf die gewählten Standorte/Zwecke (samt darunter) und Gruppen:</small>
{% for oe in ressourcen_oes %}
<label>
<input type="checkbox" name="oe_ids" value="{{ oe.id }}" {% if ressource and oe.id in ressource.oes | map(attribute='id') | list %}checked{% endif %}>
OE: {{ oe.name }}
Standort/Zweck: {{ oe.name }}
</label>
{% endfor %}
{% for gruppe in ressourcen_gruppen %}
@@ -86,7 +86,7 @@
{% if r.ganze_organisation %}ganze Organisation
{% elif not r.oes and not r.gruppen %}<em>nirgends (nicht verteilt)</em>
{% else %}
{% for oe in r.oes %}OE {{ oe.name }}<br>{% endfor %}
{% for oe in r.oes %}{{ oe.name }}<br>{% endfor %}
{% for g in r.gruppen %}Gruppe {{ g.name }}<br>{% endfor %}
{% endif %}
</small></td>
+2 -2
View File
@@ -66,7 +66,7 @@
{% else %}
<ul>
{% if nutzung.profile %}<li>{{ nutzung.profile | length }} Installationsprofil(e): {% for p in nutzung.profile %}{{ p.label }} <small>({{ p.organisation }})</small>{% if not loop.last %}, {% endif %}{% endfor %}</li>{% endif %}
{% if nutzung.oes %}<li>{{ nutzung.oes | length }} OE(s): {% for o in nutzung.oes %}{{ o.name }} <small>({{ o.organisation }}, {{ "automatisch" if o.automatisch else "auf v" ~ o.version ~ " gepinnt" }})</small>{% if not loop.last %}, {% endif %}{% endfor %}</li>{% endif %}
{% if nutzung.oes %}<li>{{ nutzung.oes | length }} Standort(e)/Zweck(e): {% for o in nutzung.oes %}{{ o.name }} <small>({{ o.organisation }}, {{ "automatisch" if o.automatisch else "auf v" ~ o.version ~ " gepinnt" }})</small>{% if not loop.last %}, {% endif %}{% endfor %}</li>{% endif %}
<li>{{ nutzung.geraete_automatisch }} Gerät(e) übernehmen neue Versionen automatisch
{%- for g in nutzung.geraete_gepinnt %}, {{ g.anzahl }} auf v{{ g.version }} gepinnt{% endfor %}</li>
</ul>
@@ -74,7 +74,7 @@
</div>
{% else %}
<p><small>Diese Vorlage pflegt tuxflotte. Du kannst sie unten unter „Kopieren“ als eigenen Workspace übernehmen und dann anpassen,
oder sie unter <a href="/struktur">Organisation › Struktur</a> direkt einer OE zuweisen; dann bekommen deine Geräte die Änderungen von tuxflotte automatisch.</small></p>
oder sie unter <a href="/struktur">Organisation › Standorte/Zwecke</a> direkt zuweisen; dann bekommen deine Geräte die Änderungen von tuxflotte automatisch.</small></p>
{% endif %}
{% if not ist_entwurf %}
+8 -8
View File
@@ -8,18 +8,18 @@
Im fest ausgerichteten Menü klappte Firefox die Auswahlliste wieder zu,
abgeschickt wurde dann die alte OE. #}
<section id="oe">
<h3>OE</h3>
<h3>Standort/Zweck</h3>
{% if request.query_params.get("oe_fehler") %}<p><mark>{{ request.query_params.get("oe_fehler") }}</mark></p>{% endif %}
{% if request.query_params.get("oe_hinweis") %}<p><ins>{{ request.query_params.get("oe_hinweis") }}</ins></p>{% endif %}
<p><small>Das Gerät bekommt Workspace und Merkmale seiner OE. Hat die neue OE einen Workspace mit anderer Distro, lehnt das Verschieben ab.</small></p>
<p><small>Das Gerät bekommt Workspace und Einstellungen seines Standorts/Zwecks. Hat der neue einen Workspace mit einer anderen Distribution, ginge das nur mit einer Neuinstallation; dann lehnt das Verschieben ab.</small></p>
<form method="post" action="{{ zurueck_url }}/{{ device.id }}/oe">
<div role="group">
<select name="oe_id" aria-label="OE auswählen">
<select name="oe_id" aria-label="Standort/Zweck auswählen">
{% for oe in organisationseinheiten %}
<option value="{{ oe.id }}" {% if oe.id == device.oe_id %}selected{% endif %}>{{ oe.name }}</option>
{% endfor %}
</select>
<button type="submit" class="outline">In OE verschieben</button>
<button type="submit" class="outline">Verschieben</button>
</div>
</form>
</section>
@@ -161,7 +161,7 @@
</button>
</form>
{% else %}
<p><small>Für dieses Gerät ist „Personalisierung möglich“ nicht gesetzt (Workspace, OE oder Gruppe).</small></p>
<p><small>Für dieses Gerät ist „Personalisierung möglich“ nicht gesetzt (Workspace, Standort/Zweck oder Gruppe).</small></p>
{% endif %}
{% if personalisierungen %}
@@ -195,7 +195,7 @@
Einzelgerät-Ausnahmen laufen über eine Gerätegruppe mit genau diesem
einen Gerät als Mitglied, nicht mehr über eine direkte Auswahl hier
- so laufen alle Ausnahmen über denselben, nachvollziehbaren
Mechanismus wie OE- und Gruppen-Einstellungen.
Mechanismus wie Einstellungen an Standort/Zweck und Gruppen.
</small></p>
<form method="post" action="{{ zurueck_url }}/{{ device.id }}/eigene-gruppe">
<button type="submit" class="outline">Eigene Gerätegruppe für dieses Gerät anlegen</button>
@@ -219,9 +219,9 @@
{% if eintrag.quelle and eintrag.quelle != "workspace" %}
<br><small>
{% if eintrag.quelle_url %}
via <a href="{{ eintrag.quelle_url }}">{{ {"oe": "OE", "gruppe": "Gruppe"}[eintrag.quelle] }}</a>
via <a href="{{ eintrag.quelle_url }}">{{ {"oe": "Standort/Zweck", "gruppe": "Gruppe"}[eintrag.quelle] }}</a>
{% else %}
via {{ {"oe": "OE", "gruppe": "Gruppe"}[eintrag.quelle] }}
via {{ {"oe": "Standort/Zweck", "gruppe": "Gruppe"}[eintrag.quelle] }}
{% endif %}
</small>
{% endif %}
+6 -6
View File
@@ -69,7 +69,6 @@
bleibt Picos zentrierendes Verhalten unveraendert richtig. */
.primary-nav ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; align-items: stretch; gap: 0.25rem; }
.primary-nav a { display: block; padding: 0.5rem 0.75rem; border-radius: var(--pico-border-radius); text-decoration: none; text-align: left; }
.primary-nav a.nav-gesperrt { opacity: 0.45; }
.primary-nav a.active { background: var(--pico-primary-background); color: var(--pico-primary-inverse); }
.app-main { flex: 1 1 auto; min-width: 0; }
@@ -268,7 +267,7 @@
else "" %}
<header class="container">
<nav>
<ul><li><strong><a href="/geraete"><img src="/static/tuxflotte-logo.svg" alt="Tuxflotte" class="brand-logo">Flotten-Management</a></strong></li></ul>
<ul><li><strong><a href="{{ '/geraete' if user and user.is_superuser else '/dashboard' }}"><img src="/static/tuxflotte-logo.svg" alt="Tuxflotte" class="brand-logo">Flotten-Management</a></strong></li></ul>
<ul>
<li><button id="theme-toggle" type="button" class="secondary outline" aria-label="Design umschalten">🌓</button></li>
{% if user %}
@@ -289,15 +288,16 @@
<li><a href="/admin/geraete" {% if aktiver_bereich == "admin_geraete" %}class="active"{% endif %}>🖥️ Flotte</a></li>
<li><a href="/admin/accounts" {% if aktiver_bereich == "admin_accounts" %}class="active"{% endif %}>🔑 Admin-Accounts</a></li>
{% else %}
{# Reihenfolge des Wegs einer neuen Organisation (Einstieg vereinfachen, Schritt 4). #}
<li><a href="/dashboard" {% if aktiver_bereich == "dashboard" %}class="active"{% endif %}>📊 Dashboard</a></li>
<li><a href="/installationsmedium" {% if aktiver_bereich == "installationsmedium" %}class="active"{% endif %}>💿 Installationsmedium</a></li>
<li><a href="/geraete" {% if aktiver_bereich == "flotte" %}class="active"{% endif %}>🖥️ Flotte</a></li>
<li><a href="/organisation" {% if aktiver_bereich == "organisation" %}class="active"{% endif %}>🏢 Organisation</a></li>
{% if workspace_creator_frei(user.organization_id) %}
<li><a href="/workspace" {% if aktiver_bereich == "workspace" %}class="active"{% endif %}>🧩 Workspace</a></li>
{% else %}
<li><a href="/workspace" class="nav-gesperrt{% if aktiver_bereich == "workspace" %} active{% endif %}" title="Dein Workspace wird derzeit von tuxflotte eingerichtet.">🧩 Workspace</a></li>
<li><a href="/workspace" {% if aktiver_bereich == "workspace" %}class="active"{% endif %} title="Deine Geräte bekommen den Tuxflotte-Standard.">🧩 Workspace</a></li>
{% endif %}
<li><a href="/installationsmedium" {% if aktiver_bereich == "installationsmedium" %}class="active"{% endif %}>💿 Installationsmedium</a></li>
<li><a href="/geraete" {% if aktiver_bereich == "flotte" %}class="active"{% endif %}>🖥️ Flotte</a></li>
{% endif %}
</ul>
</nav>
@@ -310,7 +310,7 @@
{% if user and user.organization_id and org_ressourcen_freigeschaltet(user.organization_id) %}
<li><a href="/organisation/ressourcen" {% if aktive_unterseite == "ressourcen" %}class="active"{% endif %}>Ressourcen</a></li>
{% endif %}
<li><a href="/struktur" {% if aktive_unterseite == "struktur" %}class="active"{% endif %}>Struktur</a></li>
<li><a href="/struktur" {% if aktive_unterseite == "struktur" %}class="active"{% endif %}>Standorte/Zwecke</a></li>
<li><a href="/gruppen" {% if aktive_unterseite == "gruppen" %}class="active"{% endif %}>Gruppen</a></li>
</ul>
</nav>
+29 -19
View File
@@ -4,31 +4,41 @@
<h2>Dashboard</h2>
{% import "_diagramme.html" as diagramme %}
<section>
<h3>Nächste Schritte</h3>
<ul class="dashboard-checkliste">
<li class="{{ 'erledigt' if naechste_schritte.hat_workspace }}">
{{ "✅" if naechste_schritte.hat_workspace else "⬜" }}
{% if workspace_creator_frei(user.organization_id) %}
<a href="/workspace">Workspace komponiert</a>
{% else %}
Workspace eingerichtet <small>(übernimmt derzeit tuxflotte für dich)</small>
{% endif %}
</li>
{% if erste_schritte %}
{# Einstieg vereinfachen, Schritt 4: bis das erste Gerät online ist. #}
<article id="erste-schritte">
<h3>Erste Schritte</h3>
<p>In drei Schritten zum ersten fertig eingerichteten Gerät.{% if erste_schritte.workspace %} Es bekommt den Tuxflotte-Standard <strong>„{{ erste_schritte.workspace.name }}“</strong>, einstellen musst du dafür nichts.{% endif %}</p>
<ol class="dashboard-checkliste">
<li class="{{ 'erledigt' if naechste_schritte.hat_installationsmedium }}">
{{ "✅" if naechste_schritte.hat_installationsmedium else "⬜" }}
<a href="/installationsmedium">Installationsmedium erstellt</a>
<a href="/installationsmedium/erstellen">Installationsmedium erstellen</a>
<br><small>{% if erste_schritte.org_wlan %}Die Geräte installieren über das WLAN der Organisation „{{ erste_schritte.org_wlan.ssid }}“.{% else %}Tipp vorher: Unter <a href="/organisation#wlan">Organisation</a> das WLAN der Organisation eintragen, dann bekommen es alle Geräte dauerhaft.{% endif %}</small>
</li>
<li class="{{ 'erledigt' if naechste_schritte.hat_geraet }}">
{{ "✅" if naechste_schritte.hat_geraet else "⬜" }}
<a href="/installationsmedium">Herunterladen, auf einen USB-Stick schreiben und ein Gerät davon starten</a>
<br><small>Die Installation läuft von selbst. Alle Daten auf dem Gerät werden dabei gelöscht.</small>
</li>
<li class="{{ 'erledigt' if naechste_schritte.hat_aktives_geraet }}">
{{ "✅" if naechste_schritte.hat_aktives_geraet else "⬜" }}
<a href="/geraete">Erstes Gerät aktiv</a>
<a href="/geraete">Das Gerät meldet sich in der Flotte</a>
<br><small>Nach dem ersten Start richtet es sich in einigen Minuten fertig ein.</small>
</li>
<li class="{{ 'erledigt' if naechste_schritte.hostname_konvention_aktiv }}">
{{ "✅" if naechste_schritte.hostname_konvention_aktiv else "⬜" }}
Hostnamenkonvention eingerichtet <small>(optional, richtet tuxflotte auf Wunsch ein)</small>
</li>
</ul>
</section>
</ol>
</article>
{% elif naechste_schritte.nur_standard_standort %}
<article id="anpassen">
<h3>Geräte anpassen</h3>
<p><small>
Alle Geräte bekommen bisher dasselbe. Sollen etwa der Informatikraum,
Dienstgeräte oder Schülerleihgeräte etwas anderes bekommen, lege unter
<a href="/struktur">Organisation › Standorte/Zwecke</a> dafür je einen
an und verschiebe die Geräte dorthin.{% if workspace_creator_frei(user.organization_id) %}
Mit einem eigenen <a href="/workspace">Workspace</a> bestimmst du, welche Programme sie bekommen.{% endif %}
</small></p>
</article>
{% endif %}
<section>
<h3>Flotten-Gesundheit</h3>
+3 -3
View File
@@ -6,7 +6,7 @@
{% if not devices %}
<p>Keine Geräte gefunden.</p>
{% else %}
<input type="search" placeholder="Suche nach Hostname, Kennung, OE, Gruppe …" data-table-search="#geraete-tabelle" style="max-width: 24rem">
<input type="search" placeholder="Suche nach Hostname, Kennung, Standort, Gruppe …" data-table-search="#geraete-tabelle" style="max-width: 24rem">
<figure>
<table id="geraete-tabelle" class="kompakt">
@@ -14,7 +14,7 @@
<tr>
<th data-sort data-filter>Hostname</th>
<th data-sort data-filter>Workspace</th>
<th data-sort data-filter>OE</th>
<th data-sort data-filter>Standort/Zweck</th>
<th data-sort data-filter>Gruppen</th>
<th data-sort data-filter>Personalisiert</th>
<th data-sort data-filter>Letzter Check-in</th>
@@ -68,7 +68,7 @@
{% endif %}
</li>
<li>
<a href="/geraete/{{ device.id }}#oe">In andere OE verschieben …</a>
<a href="/geraete/{{ device.id }}#oe">Verschieben …</a>
</li>
<li>
{% if gruppen %}
+1 -1
View File
@@ -6,7 +6,7 @@
<section>
<h3>Merkmale</h3>
<p><small>Gruppen ergänzen die Grundausstattung (Workspace) der Mitgliedsgeräte. "Nicht festgelegt" überlässt die Entscheidung der OE-Kette bzw. dem Workspace-Standard des Geräts. Sagen OE-Kette und Gruppen des Geräts Widersprüchliches, gewinnt "aktiv" – eine Gruppe kann ein Merkmal also aktivieren, aber nicht gegen die OE abschalten.</small></p>
<p><small>Gruppen ergänzen die Grundausstattung (Workspace) der Mitgliedsgeräte. „Nicht festgelegt“ überlässt die Entscheidung dem Standort/Zweck bzw. dem Workspace des Geräts. Widersprechen sich Standort/Zweck und Gruppen, gewinnt „aktiv“ – eine Gruppe kann ein Merkmal also einschalten, aber nicht gegen den Standort/Zweck abschalten.</small></p>
{% if not kategorien %}
<p>Keine katalogfähigen Merkmale vorhanden.</p>
{% else %}
+3 -3
View File
@@ -2,13 +2,13 @@
{% block title %}Gruppen — Tuxflotte{% endblock %}
{% block content %}
<h2>Gruppen</h2>
<p>Querschnitts-Gruppen (z.B. Lehrer/Schulleitung) — ein Gerät kann in mehreren gleichzeitig sein. Anders als Organisationseinheiten sind Gruppen nicht verschachtelt.</p>
<p>Querschnitts-Gruppen (z.B. Lehrer/Schulleitung) — ein Gerät kann in mehreren gleichzeitig sein. Anders als bei Standorten/Zwecken sind Gruppen nicht verschachtelt.</p>
<p><small>
Wofür: Gruppen eignen sich für Zuordnungen, die quer zu deiner
Raum-/Organisationsstruktur liegen — z.B. "Schulleitung" bekommt ein
zusätzliches Merkmal, egal in welchem Raum das Gerät gerade steht.
Anders als bei OEs kann ein Gerät in mehreren Gruppen gleichzeitig
sein; bei widersprüchlichen Einstellungen – auch gegenüber der OE –
Anders als bei Standorten/Zwecken kann ein Gerät in mehreren Gruppen gleichzeitig
sein; bei widersprüchlichen Einstellungen – auch gegenüber dem Standort/Zweck –
gewinnt "aktiv". Eine
Gruppe kann außerdem einen eigenen Workspace tragen — praktisch für
eine einzelne, gezielte Ausnahme (z.B. ein einzelnes Gerät testweise
+15 -18
View File
@@ -1,23 +1,20 @@
{% extends "base.html" %}
{% block title %}Struktur — Tuxflotte{% endblock %}
{% block title %}Standorte/Zwecke — Tuxflotte{% endblock %}
{% block content %}
<h2>Struktur</h2>
<h2>Standorte/Zwecke</h2>
{% if not meldung_oe %}
{% if fehler %}<p><mark>{{ fehler }}</mark></p>{% endif %}
{% if hinweis %}<p><ins>{{ hinweis }}</ins></p>{% endif %}
{% endif %}
<p>Organisationseinheiten (OEs) zum Verorten deiner Geräte — z.B. Gebäude oder Klassen. Ein Gerät gehört immer zu genau einer OE, neu aktivierte Geräte landen automatisch in "Neue Geräte".</p>
<p>Wo stehen deine Geräte, oder wofür sind sie da? Zum Beispiel „Informatikraum“, „Dienstgeräte“ oder „Schülerleihgeräte“. Jedes Gerät gehört zu genau einem Standort/Zweck; neu installierte landen in „Neue Geräte“, und du kannst sie verschieben, wenn du sie austeilst.</p>
<p><small>
Wofür: Du kannst jeder OE eigene Merkmal-Einstellungen geben, die
dann für alle Geräte darin gelten — spezifischere OEs überschreiben
dabei die allgemeineren. Beispiel: "Gebäude A" → "Klasse 5a" — wenn
du z.B. am Zeitserver von "Gebäude A" etwas änderst, gilt das für
alle Räume darin, außer "Klasse 5a" hat selbst eine abweichende
Einstellung dazu. Gruppen zählen gleichberechtigt mit: Sagt die OE
oder eine Gruppe des Geräts "aktiv", ist das Merkmal aktiv — eine OE
kann ein Merkmal, das eine Gruppe aktiviert, also nicht abschalten.
Für Zuordnungen quer zur Ortsstruktur (z.B. "Lehrer" statt
"Raum X") eignen sich Gruppen.
Brauchst du am Anfang nicht: Solange alle Geräte gleich sein sollen,
reicht „Neue Geräte“. Eigene Standorte/Zwecke lohnen sich, wenn
Geräte etwas anderes bekommen sollen, etwa einen eigenen Workspace
oder einzelne Einstellungen. Was du einem Standort/Zweck gibst, gilt
auch für die darunter, außer dort ist etwas anderes festgelegt.
Gruppen zählen gleichberechtigt mit: Schaltet der Standort/Zweck oder
eine Gruppe des Geräts eine Einstellung ein, ist sie an.
</small></p>
{% macro knoten(oe, oes) %}
@@ -27,7 +24,7 @@
{% elif oe.wirksamer_workspace %}<small> · Workspace: {{ oe.wirksamer_workspace.name }} (geerbt von {{ oe.wirksamer_workspace.geerbt_von }})</small>{% endif %}
{% if oe.anzahl_geraete is defined %}<small> · {{ oe.anzahl_geraete }} Gerät(e){% if oe.distros %}: {{ oe.distros | join(", ") }}{% endif %}</small>{% endif %}
<small>
· <a href="/struktur/{{ oe.id }}/merkmale">Merkmale bearbeiten</a>
· <a href="/struktur/{{ oe.id }}/merkmale">Einstellungen</a>
{% if not oe.ist_standard %}
· <a href="/struktur/{{ oe.id }}/umbenennen">Umbenennen</a>
· <a href="/struktur/{{ oe.id }}/loeschen">Löschen</a>
@@ -39,7 +36,7 @@
<details id="oe-{{ oe.id }}-workspace"{% if hier and fehler %} open{% endif %}>
<summary role="button" class="secondary outline">Workspace zuweisen</summary>
{% if hier and fehler %}<p><mark>{{ fehler }}</mark></p>{% endif %}
<p><small>Gilt für alle Geräte dieser OE und ihrer Unter-OEs, außer eine Unter-OE hat einen eigenen.
<p><small>Gilt für alle Geräte hier und darunter, außer darunter ist ein eigener zugewiesen.
Die Geräte müssen zur Distro des Workspace passen, sonst lehnt das Speichern mit Begründung ab.</small></p>
<form method="post" action="/struktur/{{ oe.id }}/workspace">
<div class="grid">
@@ -65,7 +62,7 @@
</details>
{% endif %}
<details>
<summary role="button" class="secondary outline">Unter-OE anlegen</summary>
<summary role="button" class="secondary outline">Darunter anlegen</summary>
<form method="post" action="/struktur">
<input type="hidden" name="parent_id" value="{{ oe.id }}">
<input type="text" name="name" placeholder="Name" required>
@@ -85,7 +82,7 @@
{% set wurzeln = organisationseinheiten | selectattr("parent_id", "equalto", none) | list %}
{% if not wurzeln %}
<p>Keine OEs gefunden.</p>
<p>Noch keine Standorte/Zwecke.</p>
{% else %}
<ul>
{% for wurzel in wurzeln %}
@@ -95,7 +92,7 @@
{% endif %}
<details>
<summary role="button" class="secondary outline">Neue OE auf oberster Ebene anlegen</summary>
<summary role="button" class="secondary outline">Neuen Standort/Zweck anlegen</summary>
<form method="post" action="/struktur">
<input type="text" name="name" placeholder="Name" required>
<button type="submit">Anlegen</button>
+5 -5
View File
@@ -1,17 +1,17 @@
{% extends "base.html" %}
{% block title %}OE löschen — Tuxflotte{% endblock %}
{% block title %}Standort/Zweck löschen — Tuxflotte{% endblock %}
{% block content %}
<p><a href="/struktur">← Struktur</a></p>
<h2>OE löschen</h2>
<p><a href="/struktur">← Standorte/Zwecke</a></p>
<h2>Standort/Zweck löschen</h2>
{% if fehler %}
<p><mark>{{ fehler }}</mark></p>
{% endif %}
<p>Soll die OE "{{ oe.name }}" wirklich gelöscht werden?</p>
<p>Soll „{{ oe.name }}“ wirklich gelöscht werden?</p>
<form method="post" action="/struktur/{{ oe.id }}/loeschen">
<button type="submit">Ja, OE löschen</button>
<button type="submit">Ja, löschen</button>
<a href="/struktur" role="button" class="secondary outline">Abbrechen</a>
</form>
{% endblock %}
+2 -2
View File
@@ -1,9 +1,9 @@
{% extends "base.html" %}
{% block title %}Merkmale {{ oe.name }} — Tuxflotte{% endblock %}
{% block content %}
<p><a href="/struktur">← Struktur</a></p>
<p><a href="/struktur">← Standorte/Zwecke</a></p>
<h2>Merkmale: {{ oe.name }}</h2>
<p>Legt fest, was Geräte in dieser OE (und ihren Unter-OEs, sofern dort nichts Spezifischeres gilt) standardmäßig bekommen. "Nicht festgelegt" überlässt die Entscheidung der nächsten Schicht (Gruppen, dann Workspace-Standard).</p>
<p>Legt fest, was Geräte hier (und darunter, sofern dort nichts anderes gilt) standardmäßig bekommen. "Nicht festgelegt" überlässt die Entscheidung der nächsten Schicht (Gruppen, dann Workspace-Standard).</p>
{% if not kategorien %}
<p>Keine katalogfähigen Merkmale vorhanden.</p>
+3 -3
View File
@@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block title %}OE umbenennen — Tuxflotte{% endblock %}
{% block title %}Standort/Zweck umbenennen — Tuxflotte{% endblock %}
{% block content %}
<p><a href="/struktur">← Struktur</a></p>
<h2>OE umbenennen</h2>
<p><a href="/struktur">← Standorte/Zwecke</a></p>
<h2>Standort/Zweck umbenennen</h2>
<form method="post" action="/struktur/{{ oe.id }}/umbenennen">
<label for="name">Name</label>
+5 -4
View File
@@ -3,11 +3,12 @@
{% block content %}
<h2>Workspace</h2>
<article>
<p><strong>Dein Workspace wird derzeit von tuxflotte eingerichtet.</strong></p>
<p><strong>Deine Geräte bekommen den Tuxflotte-Standard.</strong></p>
<p>
Die Zusammenstellung von Programmen und Einstellungen und ihre
Zuweisung an deine Geräte übernehmen wir im Moment für dich. Wende
dich mit Änderungswünschen bitte direkt an uns.
Welche Programme und Einstellungen darauf sind, haben wir für dich
zusammengestellt; du musst dafür nichts einrichten. Hast du
Änderungswünsche, wende dich an uns. Eigene Workspaces zusammenstellen
kannst du, sobald wir das für deine Organisation freischalten.
</p>
</article>
+3 -3
View File
@@ -7,8 +7,8 @@
{% if hinweis %}<p><ins>{{ hinweis }}</ins></p>{% endif %}
<p><small>
Ein Workspace legt fest, was auf deinen Geräten ist: Programme, Webapps und Einstellungen. Er läuft auf genau einer Distro.
Auf die Geräte kommt er, indem du ihn unter <a href="/struktur">Organisation › Struktur</a> einer OE zuweist; er gilt dann für
alle Geräte darin und in den Unter-OEs. Änderungen machst du in einem Entwurf und veröffentlichst sie als neue Version, erst dann
Auf die Geräte kommt er, indem du ihn unter <a href="/struktur">Organisation › Standorte/Zwecke</a> zuweist; er gilt dann für
alle Geräte dort und darunter. Änderungen machst du in einem Entwurf und veröffentlichst sie als neue Version, erst dann
erreichen sie die Geräte.
</small></p>
<p><a href="/workspace/neu" role="button">+ Workspace erstellen</a></p>
@@ -38,7 +38,7 @@
<section>
<h3>Vorlagen von tuxflotte</h3>
<p><small>Nur lesbar. Du kannst eine Vorlage direkt einer OE zuweisen (dann kommen Verbesserungen von tuxflotte automatisch)
<p><small>Nur lesbar. Du kannst eine Vorlage direkt einem Standort/Zweck zuweisen (dann kommen Verbesserungen von tuxflotte automatisch)
oder sie kopieren und die Kopie selbst anpassen.</small></p>
{% if not vorlagen %}
<p><small>Aktuell keine Vorlagen verfügbar.</small></p>