Suchfeld im Designbereich, Sicherung als Arbeitspaket O
Neunzig Eigenschaften in acht zugeklappten Gruppen: "wo ist die Rahmenstaerke" ist eine berechtigte Frage. Jetzt filtert ein Suchfeld die Eigenschaften, oeffnet die passenden Gruppen und sagt, wie viele Treffer es gibt - und wenn keine, wo eigenes CSS zu finden ist. Getestet: "rahmen" laesst die Gruppen Rahmen und Bild stehen, 14 Treffer. Neu im Ausbauplan, Paket O: Sicherung und Wiederherstellung als Funktion statt als Disziplin. Heute entstehen Sicherungen nur als Nebenprodukt des Updaters und weil ich vor jeder Migration von Hand eine Kopie lege. Geplant sind Datenbank per VACUUM INTO statt copy (eine Kopie waehrend eines Schreibvorgangs kann unbrauchbar sein), Medienordner, ein Manifest mit Fassung, Migrationsstand und Pruefsummen - und ohne .env, weil darin APP_SECRET steht und eine Sicherung den Server verlaesst. Dazu Rotation, damit die Platte nicht vollaeuft und die naechste Sicherung nicht genau dann scheitert, wenn man sie braucht. Die Abnahme ist die Wiederherstellung, nicht die Sicherung: eine Seite loeschen, zurueckspielen, pruefen dass sie wieder da ist. Eine Sicherungsfunktion, deren Wiederherstellung nie gelaufen ist, ist eine Vermutung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
806df4b5df
commit
464f52b147
3 files changed
+61
-1
No files matched your search
+31
-1
@@ -151,7 +151,7 @@ Abnahme: Templatewechsel erhält die Inhalte. Das Referenzplugin lässt sich akt
|
||||
|
||||
*Braucht: nichts; teils vorziehbar.*
|
||||
|
||||
~~Benutzerverwaltung und Passwortänderung in der Oberfläche.~~ — **erledigt am 12.09.2026** (ADR 030). Offen bleibt der Rest des Pakets: Inhaltscache mit gezielter Erneuerung beim Veröffentlichen. Bildformate und Ressourcenpakete prüfen. Sicherung und Wiederherstellung erproben. Updateverfahren mit Datenbankmigration und Rückfallstrategie dokumentieren — **geplant am 12.09.2026 in `docs/UPDATE.md`**: neue Version über einen Git-Tag ziehen, mit Vorprüfung, Sicherung, Wartungszustand, Migration, Rauchtest und Rückweg. Umsetzung offen; zwei Entscheidungen hängen am Hosting (Composer auf dem Server oder `vendor/` im Tag, und ob es Shell-Zugriff gibt).
|
||||
~~Benutzerverwaltung und Passwortänderung in der Oberfläche.~~ — **erledigt am 12.09.2026** (ADR 030). Offen bleibt der Rest des Pakets: Inhaltscache mit gezielter Erneuerung beim Veröffentlichen. Bildformate und Ressourcenpakete prüfen. Sicherung und Wiederherstellung erproben — **als eigenes Arbeitspaket O geführt**, weil Umut daraus eine Funktion haben will und nicht eine Übung. Updateverfahren mit Datenbankmigration und Rückfallstrategie dokumentieren — **geplant am 12.09.2026 in `docs/UPDATE.md`**: neue Version über einen Git-Tag ziehen, mit Vorprüfung, Sicherung, Wartungszustand, Migration, Rauchtest und Rückweg. Umsetzung offen; zwei Entscheidungen hängen am Hosting (Composer auf dem Server oder `vendor/` im Tag, und ob es Shell-Zugriff gibt).
|
||||
|
||||
Abnahme: Veröffentlichen macht die Änderung sofort sichtbar und erneuert nur die betroffenen Einträge. Eine Wiederherstellung aus dem Backup ist nachgewiesen.
|
||||
|
||||
@@ -208,6 +208,36 @@ Umut: „Einen Installer brauche ich noch wie wordpress schritt für schritt all
|
||||
|
||||
Abnahme: eine leere Installation ist im Browser in sechs Schritten betriebsbereit, ohne Konsole. Ein zweiter Aufruf von `/install` liefert 404. Die Systemprüfung schlägt bei fehlenden Schreibrechten fehl und sagt, welcher Pfad fehlt. Getestet gegen ein leeres Verzeichnis und eine leere Datenbank, nicht gegen die Entwicklungsinstallation.
|
||||
|
||||
### O – Sicherung und Wiederherstellung als Funktion (Nutzerauftrag vom 12.09.2026)
|
||||
|
||||
*Braucht: nichts. Für MySQL zusätzlich die Hostingentscheidung aus Arbeitspaket J.*
|
||||
|
||||
Umut: „Todo aufnehmen. Standard backup funktion.“ Heute gibt es Sicherungen nur als Nebenprodukt: der Updater kopiert die Datenbank vor einem Update, und ich habe vor jeder Migration von Hand eine Kopie in `var/backups/` gelegt. Das ist keine Sicherungsfunktion, das ist Disziplin – und Disziplin fällt aus, sobald jemand anderes arbeitet.
|
||||
|
||||
**Was in eine Sicherung gehört**, und was nicht:
|
||||
|
||||
- **Die Datenbank.** Bei SQLite die Datei, und zwar konsistent: eine Kopie während eines Schreibvorgangs kann unbrauchbar sein, deshalb `VACUUM INTO` statt `copy()`. Bei MySQL `mysqldump`.
|
||||
- **Der Medienordner.** Ohne ihn ist die Sicherung wertlos: die Dokumente verweisen auf Bild-IDs, die Dateien liegen im Medienordner. Das ist auch der Teil, der groß wird.
|
||||
- **Ein Manifest**: Zeitpunkt, Fassung aus `VERSION`, höchste angewandte Migration, Anzahl Seiten, Fassungen und Medien, und eine Prüfsumme je Teil. Ohne das weiß man beim Zurückspielen nicht, ob die Sicherung zum Code passt.
|
||||
- **Nicht `.env`.** Darin steht `APP_SECRET`; eine Sicherung, die man herunterlädt und weitergibt, darf das nicht enthalten. Die Datei wird beim serverseitigen Aufbewahren mitkopiert (der Updater tut das schon), aber nicht in ein Archiv gelegt, das den Server verlässt. Beim Zurückspielen wird sie deshalb auch nicht überschrieben.
|
||||
|
||||
**Ablauf einer Sicherung:** Wartungszustand ist *nicht* nötig – `VACUUM INTO` liefert einen konsistenten Stand, und Medien werden nur hinzugefügt, nie verändert. Geschrieben wird nach `var/backups/sicherung-<zeitstempel>/` mit Manifest; auf Wunsch als ein Archiv. `var/` liegt außerhalb des Dokumentenstamms (`public/`), also ist keine Sicherung über das Netz erreichbar – das muss die Systemprüfung nachweisen, nicht annehmen.
|
||||
|
||||
**Aufbewahrung mit Rotation:** die letzten sieben täglichen, vier wöchentlichen, drei monatlichen. Ohne Rotation läuft die Platte voll und dann scheitert die nächste Sicherung genau dann, wenn man sie braucht.
|
||||
|
||||
**Herunterladen:** in der Verwaltung, als Archiv, gestreamt. Mit zwei ehrlichen Hinweisen: ohne `.env`, und bei einem großen Medienordner bricht ein Browserdownload womöglich ab – dann ist die Konsole oder ein Dateizugriff der Weg. Kein Archiv im Speicher aufbauen.
|
||||
|
||||
**Wiederherstellung** ist der Teil, der zählt, und der Teil, den niemand übt:
|
||||
|
||||
1. `cms:restore <verzeichnis-oder-archiv>`, und in der Verwaltung nur die Anzeige, welche Sicherungen es gibt. Ein Knopf, der die Datenbank überschreibt, gehört nicht neben einen Knopf, der eine Seite speichert.
|
||||
2. Manifest gegen die laufende Installation prüfen: eine Sicherung aus einer **neueren** Fassung wird abgewiesen – deren Migrationen kennt der alte Code nicht. Eine ältere ist erlaubt, dann läuft danach die Migration.
|
||||
3. Wartungszustand an, **Sicherung des aktuellen Stands** anlegen (auch eine Wiederherstellung kann die falsche sein), Datenbank ersetzen, Medien ergänzen statt löschen, Migration, Cache, Rauchtest, Wartungszustand aus.
|
||||
4. Protokoll nach `var/backups.log`.
|
||||
|
||||
**Was gebaut wird:** `src/Ops/Backup.php`, `cms:backup` (mit `--rotate`, `--archive`), `cms:restore` (mit `--force`), eine Übersicht im Bildschirm „System“ mit Größe, Zeitpunkt, Fassung und Prüfsummenstatus je Sicherung, und ein Eintrag im Cronjob-Abschnitt des README.
|
||||
|
||||
**Abnahme – und hier liegt der offene Punkt aus Arbeitspaket I:** eine Wiederherstellung ist *nachgewiesen*. Nicht „die Funktion existiert“, sondern: eine Sicherung wird angelegt, die Datenbank wird nachweislich verändert (eine Seite gelöscht, ein Bild entfernt), die Sicherung wird zurückgespielt, und danach ist beides wieder da – geprüft im Test gegen eine wegwerfbare Installation und einmal von Hand auf der Entwicklungsinstallation. Eine Sicherungsfunktion, deren Wiederherstellung nie gelaufen ist, ist eine Vermutung.
|
||||
|
||||
### K – Migration von kant-it.de
|
||||
|
||||
*Braucht: B, C, D, E, F, H, J und Backendzugang.*
|
||||
|
||||
@@ -265,6 +265,35 @@ export function createDesignPanel({ schema, onChange, onSavePreset, getBlock, ge
|
||||
panel.append(scope);
|
||||
panel.append(element('p', 'field-help', 'Leere Felder erben vom größeren Gerät und zuletzt vom Theme. Werte mit eigenem Wert sind hervorgehoben und lassen sich einzeln zurücksetzen.'));
|
||||
|
||||
// A filter over the properties. With more than ninety of them across eight collapsed groups,
|
||||
// "where is the border thickness" is a fair question - so it can be typed instead of hunted.
|
||||
const search = element('input', 'design-search');
|
||||
search.type = 'search';
|
||||
search.id = 'design-search';
|
||||
search.placeholder = 'Eigenschaft suchen, z. B. Rahmen …';
|
||||
const searchLabel = element('label', 'search-label', 'Eigenschaft suchen');
|
||||
searchLabel.htmlFor = search.id;
|
||||
const found = element('p', 'field-help');
|
||||
search.addEventListener('input', () => {
|
||||
const term = search.value.trim().toLowerCase();
|
||||
let matches = 0;
|
||||
for (const groupElement of panel.querySelectorAll('.design-group')) {
|
||||
let visible = 0;
|
||||
for (const entry of groupElement.querySelectorAll('.design-row, .side-wrap, .design-group-label')) {
|
||||
// A per-side control is a heading plus a block of fields; they travel together.
|
||||
const text = (entry.classList.contains('side-wrap') ? entry.previousElementSibling?.textContent : entry.textContent) ?? '';
|
||||
const hit = '' === term || text.toLowerCase().includes(term);
|
||||
entry.hidden = !hit;
|
||||
if (hit) visible++;
|
||||
}
|
||||
groupElement.hidden = '' !== term && 0 === visible;
|
||||
if (!groupElement.hidden && '' !== term) groupElement.open = true;
|
||||
matches += visible;
|
||||
}
|
||||
found.textContent = '' === term ? '' : (0 === matches ? 'Nichts gefunden. Unter „Erweitert“ gibt es außerdem eigenes CSS für dieses Element.' : `${matches} Treffer.`);
|
||||
});
|
||||
panel.append(searchLabel, search, found);
|
||||
|
||||
for (const [group, groupLabel] of Object.entries(GROUPS)) {
|
||||
const properties = Object.entries(schema.properties).filter(([, p]) => p.group === group);
|
||||
if (properties.length === 0) continue;
|
||||
|
||||
@@ -208,3 +208,4 @@ dialog p{font-size:14px;line-height:1.7;color:var(--muted)}
|
||||
.row-library button{display:flex;align-items:center;gap:10px;min-height:38px;padding:8px 12px;border:1px solid var(--line);border-radius:7px;background:#f7f9f2;color:var(--ink);font-size:11px}
|
||||
.row-library button:hover:not(:disabled){border-color:var(--accent)}
|
||||
.row-library span{letter-spacing:-2px;font-size:13px}
|
||||
#design-search{font-size:12px;min-height:36px;padding:9px}
|
||||
Reference in new issue
Block a user