Die restlichen zwei Änderungswünsche. **Protokoll.** „Wer was wo macht" stand seit dem ersten Tag in AuditLog - harte Regel 3 sorgt dafür. Was fehlte, war die Ansicht darauf. /admin/protokoll ist genau das und keine zweite Aufzeichnung: filterbar nach Person, Aktion, Art und Zeitraum, rückwärts blätternd. Nur für die Leitung. Eine Zeile nennt Aktenzeichen und Namen, und schon die Existenz einer Zeile verrät, dass es einen Vorgang gibt; ein gefiltertes Protokoll für alle wäre ein Weg, die Sichtbarkeitsregeln von hinten aufzurollen. Aus demselben Grund werden Kennungen ohne Sichtbarkeitsfilter zu Aktenzeichen aufgelöst - ein Protokoll aus UUIDs liest niemand, und eines, das niemand liest, ist keine Kontrolle. **Speichern.** Jedes Feld trug seinen eigenen Knopf: ein Arbeitsvertrag mit zwölf Feldern hiess zwölfmal speichern. Der Knopf steht jetzt am Blatt. Geschrieben wird trotzdem je Feld und nicht als Transaktion, und das folgt aus dem Datenmodell: Abschnitte sind einzeln versioniert, weil mehrere Leute gleichzeitig an einer Akte arbeiten. Als Transaktion blockierte ein einziges zwischenzeitlich geändertes Feld das ganze Blatt - elf gute Änderungen verloren, weil jemand das zwölfte angefasst hat. So geht durch, was durchgeht, und die Rückmeldung sagt, was offen blieb und wo. Lichtbilder bleiben ausgenommen: ein Upload mit Prüfkette ist keine Feldänderung. lib/audit.ts liegt jetzt als lib/audit/write.ts neben lib/audit/query.ts.
204 lines
7.8 KiB
TypeScript
204 lines
7.8 KiB
TypeScript
export const auditText = {
|
|
heading: 'Protokoll',
|
|
intro:
|
|
'Wer was wo getan hat. Jede schreibende Aktion steht hier, dazu jeder Lesezugriff ab Stufe 3. Einträge lassen sich nicht ändern und nicht entfernen.',
|
|
when: 'Zeitpunkt',
|
|
who: 'Wer',
|
|
what: 'Was',
|
|
where: 'Wo',
|
|
clearance: 'Stufe',
|
|
filterActor: 'Person',
|
|
filterAction: 'Aktion',
|
|
filterTarget: 'Art',
|
|
filterFrom: 'Ab',
|
|
filterTo: 'Bis',
|
|
apply: 'Filtern',
|
|
reset: 'Alles zeigen',
|
|
all: 'Alle',
|
|
empty: 'Für diesen Ausschnitt steht nichts im Protokoll.',
|
|
older: 'Ältere anzeigen',
|
|
gone: 'nicht mehr auffindbar',
|
|
ofUser: 'Nur von dieser Person',
|
|
ofTarget: 'Nur zu diesem Vorgang',
|
|
actions: {
|
|
'case.read': 'Fallakte gelesen',
|
|
'case.create': 'Fallakte angelegt',
|
|
'case.update': 'Fallakte geändert',
|
|
'case.reclassify': 'Fallakte umklassifiziert',
|
|
'case.status': 'Status geändert',
|
|
'entry.create': 'Vermerk angelegt',
|
|
'entry.edit': 'Vermerk geändert',
|
|
'entry.retract': 'Vermerk zurückgezogen',
|
|
'entry.restore': 'Vermerk wiederhergestellt',
|
|
'case.participant.add': 'Beteiligten eingetragen',
|
|
'case.participant.remove': 'Beteiligten ausgetragen',
|
|
'case.link': 'Akten verknüpft',
|
|
'case.unlink': 'Verknüpfung gelöst',
|
|
'personnel.read': 'Personalakte gelesen',
|
|
'personnel.create': 'Personalakte angelegt',
|
|
'personnel.update': 'Personalakte geändert',
|
|
'personnel.reclassify': 'Personalakte umklassifiziert',
|
|
'personnel.assign': 'Person zugeordnet',
|
|
'personnel.unassign': 'Zuordnung aufgehoben',
|
|
'document.read': 'Dokument gelesen',
|
|
'document.create': 'Dokument angelegt',
|
|
'document.update': 'Dokument geändert',
|
|
'document.finalize': 'Dokument abgeschlossen',
|
|
'document.retract': 'Dokument zurückgezogen',
|
|
'document.reclassify': 'Dokument umklassifiziert',
|
|
'attachment.upload': 'Datei hochgeladen',
|
|
'attachment.read': 'Datei abgerufen',
|
|
'attachment.release': 'Datei freigegeben',
|
|
'chat.adopt': 'Nachricht als Vermerk übernommen',
|
|
'roles.update': 'Rollenzuordnung geändert',
|
|
'export.pdf': 'PDF erzeugt',
|
|
'export.approve': 'Export freigegeben',
|
|
'export.reject': 'Export abgelehnt',
|
|
'export.download': 'Export heruntergeladen',
|
|
'export.database': 'Datenbankabzug erzeugt',
|
|
} as Record<string, string>,
|
|
targets: {
|
|
Case: 'Fallakte',
|
|
CaseEntry: 'Vermerk',
|
|
CaseSection: 'Abschnitt',
|
|
PersonnelFile: 'Personalakte',
|
|
Document: 'Dokument',
|
|
DocumentSection: 'Dokumentabschnitt',
|
|
Attachment: 'Datei',
|
|
ChatMessage: 'Nachricht',
|
|
ExportJob: 'Export',
|
|
RoleMapping: 'Rollenzuordnung',
|
|
ChangePolicy: 'Änderungsrichtlinie',
|
|
Database: 'Datenbank',
|
|
} as Record<string, string>,
|
|
} as const
|
|
|
|
export const adminText = {
|
|
heading: 'Admin',
|
|
intro: 'Rollen, Vorlagen, Regeln und das Protokoll der Abteilung.',
|
|
sections: {
|
|
roles: 'Rollen',
|
|
templates: 'Vorlagen',
|
|
policy: 'Änderungsrichtlinie',
|
|
log: 'Protokoll',
|
|
system: 'System',
|
|
} as Record<string, string>,
|
|
descriptions: {
|
|
roles: 'Welche Discord-Rolle welche Freigabestufe und welche Rechte trägt.',
|
|
templates: 'Aufbau der Fallakten und Dokumente, Bausteine und Fassungen.',
|
|
policy: 'Ob und wie lange Verlaufseinträge nachträglich geändert werden dürfen.',
|
|
log: 'Wer was wo getan hat. Vollständig und nicht änderbar.',
|
|
system: 'Zustand der Dienste, Ladewerte, Nummernkreise.',
|
|
} as Record<string, string>,
|
|
empty: 'Für die Verwaltung fehlen dir die Rechte. Die Abteilungsleitung vergibt sie.',
|
|
} as const
|
|
|
|
/**
|
|
* Der Adminbereich: Rollen, Vorlagen, Einstellungen.
|
|
*
|
|
* Nach Bereichen getrennt, weil eine **Client-Komponente die ganze Datei ins
|
|
* Bündel zieht**, in der ihr Text steht. Lägen alle Texte in einer, trüge jede
|
|
* Ansicht die der ganzen Anwendung mit sich herum — das Budget aus
|
|
* docs/09-performance-und-ux.md wäre nach wenigen Phasen aufgebraucht.
|
|
*
|
|
* Serverkomponenten nehmen weiter `de` aus `lib/i18n/de.ts`: was dorthin
|
|
* gezogen wird, verlässt den Server nicht.
|
|
*/
|
|
|
|
export const roleAdminText = {
|
|
heading: 'Rollenverwaltung',
|
|
intro:
|
|
'Jede Rolle des Discord-Servers steht hier. Zugangsrollen bestimmen die Freigabestufe, Rangrollen die Rechte, und eine Zugehörigkeitsrolle entscheidet, wer sich überhaupt anmelden darf.',
|
|
empty:
|
|
'Noch keine Rollen eingelesen. Der Worker spiegelt sie, sobald er am Discord-Gateway hängt.',
|
|
role: 'Rolle',
|
|
category: 'Sorte',
|
|
clearance: 'Freigabestufe',
|
|
grantsMembership: 'Darf sich anmelden',
|
|
permissions: 'Rechte',
|
|
missingFromGuild: 'Auf dem Server nicht mehr vorhanden',
|
|
save: 'Zuordnung speichern',
|
|
invalidInput: 'Eingabe nicht gültig',
|
|
roleGone: 'Rolle nicht gefunden, sie wurde zwischenzeitlich gelöscht',
|
|
saving: 'Wird gespeichert',
|
|
saved: 'Zuordnung gespeichert',
|
|
confirm: 'Rollen-Mapping ändern? Das wirkt sofort für alle angemeldeten Personen.',
|
|
categories: {
|
|
MEMBERSHIP: 'Zugehörigkeit',
|
|
RANK: 'Rang',
|
|
CLEARANCE: 'Zugang',
|
|
UNSORTED: 'Nicht eingeordnet',
|
|
} as Record<string, string>,
|
|
optIn:
|
|
'Dieses Recht ist im Auslieferungszustand keiner Rolle zugeordnet. Vergib es bewusst.',
|
|
preview: {
|
|
heading: 'Vorschau',
|
|
intro: 'Was jemand mit dieser Stufe sieht, wenn er keine weiteren Rollen hat.',
|
|
level: 'Stufe',
|
|
sees: 'Sieht Akten bis einschliesslich',
|
|
hidden: 'Höhere Akten sind unsichtbar',
|
|
lockedOnly: 'Höhere Akten sind sichtbar, aber gesperrt',
|
|
nobody: 'Keine Rolle führt zu dieser Stufe',
|
|
viaRoles: 'Erreichbar über',
|
|
},
|
|
} as const
|
|
|
|
export const templateAdminText = {
|
|
heading: 'Vorlagen',
|
|
intro:
|
|
'Eine Vorlage bestimmt, aus welchen Bausteinen eine Fallart besteht. Speichern legt eine neue Version an; bestehende Akten behalten die Version, mit der sie angelegt wurden.',
|
|
empty: 'Noch keine Vorlage.',
|
|
version: (v: number) => `Version ${v}`,
|
|
fieldsLabel: 'Bausteine als JSON',
|
|
fieldsHelp:
|
|
'Liste von Bausteinen. Eingabefelder: shorttext, longtext, select, multiselect, date, person, number, checklist, table. Feste Bausteine: heading, paragraph, signature, pagebreak.',
|
|
numberPrefix: 'Präfix des Aktenzeichens',
|
|
numberPadding: 'Stellen der laufenden Nummer',
|
|
save: 'Als neue Version speichern',
|
|
saved: (v: number) => `Gespeichert als Version ${v}`,
|
|
preview: 'Vorschau',
|
|
previewIntro: 'So sieht eine neue Akte mit dieser Vorlage aus.',
|
|
invalid: 'Die Bausteine sind kein gültiges JSON.',
|
|
fieldsInvalid: 'Die Bausteine sind nicht gültig.',
|
|
inputField: 'Eingabe',
|
|
staticBlock: 'Fester Baustein',
|
|
} as const
|
|
|
|
export const settingsText = {
|
|
heading: 'Änderungsrichtlinie',
|
|
intro:
|
|
'Regelt, ob und wie lange Verlaufseinträge nachträglich geändert werden dürfen. Gilt für die ganze Abteilung.',
|
|
policy: 'Richtlinie',
|
|
graceMinutes: 'Karenzzeit in Minuten',
|
|
markGrace: 'Auch Änderungen in der Karenzzeit sichtbar markieren',
|
|
save: 'Richtlinie speichern',
|
|
saved: 'Richtlinie gespeichert',
|
|
confirm: 'Änderungsrichtlinie ändern? Das gilt sofort für alle Akten.',
|
|
policies: {
|
|
APPEND_ONLY: 'Keine Änderung möglich, nur Korrektureinträge',
|
|
GRACE_WINDOW: 'Eigene Einträge in der Karenzzeit frei änderbar, danach nur mit Recht',
|
|
OPEN: 'Jederzeit änderbar, sofern das Recht vorliegt',
|
|
} as Record<string, string>,
|
|
} as const
|
|
|
|
export const vitalsText = {
|
|
heading: 'Ladewerte',
|
|
intro:
|
|
'Gemessen in den Browsern der Nutzer, letzte sieben Tage. Nur Zahlen und das Routenmuster — wer wann welche Akte geöffnet hat, steht im Zugriffsprotokoll.',
|
|
empty: 'Noch nichts gemessen.',
|
|
metric: 'Wert',
|
|
median: 'Median',
|
|
p75: '75. Perzentil',
|
|
count: 'Messungen',
|
|
target: 'Ziel',
|
|
worstRoutes: 'Langsamste Routen',
|
|
route: 'Route',
|
|
targets: {
|
|
LCP: 'unter 1,5 s',
|
|
INP: 'unter 200 ms',
|
|
CLS: 'unter 0,05',
|
|
TTFB: 'unter 500 ms',
|
|
FCP: 'unter 1,0 s',
|
|
} as Record<string, string>,
|
|
} as const
|