Commit Graph

6 Commits (86477ad2658e2d51cdda690eccb7b41d6d723492)

Author SHA1 Message Date
nexxo 51a97019a8 Sperren sehen und aufheben: Portalseite fuer den Inhaber, Abschnitte in der Konsole
Aufgabe 6 des Fruehwarnsystems. Der Inhaber sieht im Portal die Sperren SEINER
Instanzen und hebt sie dort auf; der Betreiber sieht in der Konsole alle, an der
Kunden- und an der Host-Detailseite, und auf der Uebersicht steht ein Hinweis,
solange irgendwo eine Sperre aktiv ist.

Aufgehoben wird ueber ein Bestaetigungs-Modal (R23), und das Modal mutiert
nichts: es wirft ein Ereignis, das die Seite auffaengt und an ihre eigene
Methode weiterreicht. Die Berechtigungspruefung bleibt damit an der einen
Stelle, an der sie schon stand — noetig, weil ein Modal ohne die Middleware der
Seite erreichbar ist (R20). Dazu die Berechtigung `instances.manage`, nach dem
Muster der bestehenden `instances.restart`-Migration; Abrechnung und Read-only
bleiben unberuehrt.

ACHTUNG, was hier sonst noch drinsteckt und NICHT zu dieser Aufgabe gehoert:
rund 150 Zeilen zum Versandtakt — das Merkmal `RidesALane`, vierzehn Mailables
und `MailLaneRoutingTest`. Die stammen aus einer PARALLEL laufenden Sitzung an
einem anderen Feature.

Wie das hineingeriet: der Implementierer dieser Aufgabe brach vor dem Commit ab
und liess seine fertige Arbeit ungespeichert im Baum. Ich habe sie dateigenau
mit `git add <dateien>` vorgemerkt, um nichts Fremdes mitzunehmen — und dabei
uebersehen, dass `git add` nur HINZUFUEGT: die andere Sitzung hatte ihre Arbeit
bereits vorgemerkt, und `git commit` schreibt den ganzen Index, nicht nur das
zuletzt Hinzugefuegte. Richtig waere `git commit -- <dateien>` gewesen.

Nichts ist verloren, und die volle Suite ist auf diesem Stand gruen (2571).
Aber diese Botschaft soll nicht behaupten, sie beschreibe alles, was hier steht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:53:24 +02:00
nexxo 5dcfee9957 Wegwahl je Mailart: welche Mail aus welchem Postfach geht
MailCatalogue haelt die eine Liste aller sechzehn Mailarten (Schluessel,
Beschriftung, Vorgabe-Zweck), aus MailPreviews herausgezogen, damit es
nur noch eine Stelle gibt, die beim naechsten Mailtyp vergessen werden
kann. MailRoute sitzt darueber: ein Eintrag ist eine Ausnahme fuer GENAU
diese eine Mailart, keine zweite Zuordnungsebene — ohne Eintrag oder bei
abgeschaltetem Zielpostfach faellt sie unveraendert auf den Zweck
zurueck, den MailboxResolver schon kennt.

SendsFromMailbox bekommt dafuer einen optionalen $mailKey; alle
bestehenden Aufrufer (inklusive ContactRequestMail, das mailboxAddresses
selbst zusammensetzt) bleiben bei null und damit beim alten Verhalten.
Jede Mailklasse und die CloudReady-Benachrichtigung nennen jetzt ihren
Katalog-Schluessel. Die Konsole bekommt eine vierte Karte unter der
Zweck-Zuordnung: eine Zeile je Mailart, ein <select> mit den aktiven
Postfaechern und "wie der Zweck (...)" als Vorgabe.

Der wichtigste Test schickt eine Mail ohne jeden Eintrag und prueft,
dass sie exakt beim bisherigen Postfach landet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 12:43:31 +02:00
nexxo b1ab0f7f31 Put every mail in one design, and confirm an order when the money arrives
tests / pest (push) Failing after 7m48s Details
tests / assets (push) Successful in 25s Details
tests / release (push) Has been skipped Details
Four templates, one layout. Two of them — the maintenance announcement and its
cancellation — still wore Laravel's markdown component, which brings its own
logo, its own button and its own footer: the product was sending mail that
looked like two different companies, one of which was the framework.

New: an order confirmation, sent when the payment lands rather than when
provisioning finishes. Those are minutes apart at best and can be much longer,
and somebody who has just been charged and heard nothing assumes the worst
about both the charge and the product. It deliberately promises no time for the
cloud itself — provisioning takes what it takes, CloudReady announces the end,
and a promised minute that slips is worse than no promise. Queued after the
transaction, never inside it, and never allowed to fail the webhook: Stripe
retries anything that is not a 2xx, and a retry would re-enter provisioning
over a mail server having a bad minute.

The finished-cloud mail now names what was actually built — address, plan,
storage, location — because somebody who ordered a fortnight ago is checking it
against what they bought, and that is a comparison rather than a sentence. Still
no credential in it.

Two tests earn their place. One holds every template on the shared layout, so a
new one written in a hurry cannot copy-paste its way back to the framework's.
The other proves the finished-cloud mail carries no password — asserted against
the data the mail is given and the HTML it renders, not against the template's
source, because the first version of that test tripped over the word in a
comment and would have passed a template that leaked the value through a
variable. It tested the prose, not the mail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 00:14:04 +02:00
nexxo 64a33c675e Put a real sender on every mail, and a reply address where one helps 2026-07-27 23:40:34 +02:00
nexxo 493b81aadf fix(admin): confirm maintenance delivery via MessageSent (retryable until sent); guard resend/cancel on derived state; fix flaky non-unique host wg_ip
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:14:01 +02:00
nexxo 52fa7a34a2 feat(admin): email affected customers when maintenance is cancelled (ledger-guarded)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:07:21 +02:00