Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d77b19f61f | ||
|
|
dbeaa45644 |
79
CLAUDE.md
79
CLAUDE.md
@@ -39,8 +39,8 @@ Regelbasiert, nicht ML: die Ableitungsregeln (Datenklasse, KI-VO-
|
||||
Einstufung, Anforderungsprofil) liegen als versionierte YAML-Dateien
|
||||
vor, nicht im Code — ein fachlich Verantwortlicher soll sie anpassen
|
||||
können, ohne Go-Code anzufassen. Ein LLM ist v1 höchstens optional zur
|
||||
Zweckerkennung aus dem Freitext vorgesehen (Textgenerierung/Übersetzung/
|
||||
Code/Bild/Transkription/Recherche erkennen), nie für die Einstufung
|
||||
Zweckerkennung aus dem Freitext vorgesehen (die zwölf Zweck-Kategorien
|
||||
erkennen, siehe `gueltigeZwecke` weiter unten), nie für die Einstufung
|
||||
selbst — und auch dort nur als Vorschlag, den der Mensch bestätigt.
|
||||
|
||||
---
|
||||
@@ -253,10 +253,13 @@ zentralen Katalog zu verändern.
|
||||
**Pflicht:** `letzte_pruefung` und `quelle` sind NOT NULL — jede
|
||||
Zusicherung im Katalog (AVV verfügbar, Verarbeitungsort, Training-Opt-
|
||||
out, Zertifizierungen) muss belegbar sein, sonst haftet Deklarix für
|
||||
eine Aussage, die nicht nachweisbar ist. Einträge älter als 180 Tage
|
||||
werden im zentralen (`GET /betreiber/werkzeuge`) und im mandanten-
|
||||
seitigen Katalog (`GET /verwaltung/werkzeuge`) als "nicht aktuell
|
||||
geprüft" markiert (`werkzeugPruefungVeraltetNach`, 180 Tage).
|
||||
eine Aussage, die nicht nachweisbar ist. Beide Katalogansichten zeigen
|
||||
eine dreistufige Ampel zum Prüfalter (`pruefStatus()` in
|
||||
`betreiber_werkzeug_handlers.go`, CSS-Klassen `status-niedrig`/
|
||||
`-mittel`/`-hoch`, dieselben Farbtoken wie die KI-VO-Risikoampel):
|
||||
**aktuell** (grün, < 150 Tage), **bald fällig** (gelb, 150–180 Tage —
|
||||
dasselbe 30-Tage-Vorwarnfenster wie bei der Wiedervorlage), **abgelaufen**
|
||||
(rot, > 180 Tage).
|
||||
|
||||
`aufbewahrung_tage` ist NULLABLE (Migration 0015) — `NULL` bedeutet
|
||||
"vom Anbieter nicht beziffert" und ist bewusst von `0` ("belegte
|
||||
@@ -266,13 +269,23 @@ aussahen — `formatAufbewahrung()` zeigt entsprechend "unbekannt" statt
|
||||
"0 Tage".
|
||||
|
||||
`geeignete_zwecke` ist seit derselben Migration ein kontrolliertes
|
||||
Vokabular statt Freitext: das Formular zeigt Checkboxen für genau die
|
||||
sechs Zweck-Kategorien aus dem Fragebogen (Textgenerierung,
|
||||
Übersetzung, Code, Bild, Transkription, Recherche — `gueltigeZwecke` in
|
||||
`betreiber_werkzeug_handlers.go`), serverseitig validiert
|
||||
(`istGueltigerZweck`). Grund: ein künftiger automatischer Zweckabgleich
|
||||
(Bewertungslogik Schritt 3, weiterhin nicht gebaut) würde an
|
||||
inkonsistentem Freitext ("Text" statt "Textgenerierung") scheitern.
|
||||
Vokabular statt Freitext: das Formular zeigt Checkboxen statt eines
|
||||
Freitextfelds (`gueltigeZwecke` in `betreiber_werkzeug_handlers.go`,
|
||||
serverseitig validiert via `istGueltigerZweck`). Grund: ein künftiger
|
||||
automatischer Zweckabgleich (Bewertungslogik Schritt 3, weiterhin nicht
|
||||
gebaut) würde an inkonsistentem Freitext ("Text" statt
|
||||
"Textgenerierung") scheitern. Am 2026-08-31 von sechs auf zwölf
|
||||
Kategorien erweitert, nachdem der Nutzer zurecht einwandte, dass sechs
|
||||
Kategorien die tatsächliche Bandbreite an Einsatzgebieten nicht
|
||||
abdecken: Textgenerierung, Übersetzung, Code, Bild, Transkription,
|
||||
Recherche, **Datenanalyse, Video, Audio/Sprache,
|
||||
Kundenservice/Chatbot, Präsentation/Design, Automatisierung/Agenten**.
|
||||
Es gibt keinen einheitlichen Branchenstandard für KI-Funktions-
|
||||
kategorien (regulatorische Taxonomien wie die KI-VO selbst oder OECD/
|
||||
NIST klassifizieren nach Risiko, nicht nach Funktion; Marktplatz-
|
||||
Kategorien wie bei G2 sind Marketing, kein Standard) — diese zwölf
|
||||
sind Deklarix' eigene, bewusst begrenzte Liste für den künftigen
|
||||
Zweckabgleich, keine allgemeine KI-Taxonomie.
|
||||
|
||||
Neues Feld `subprozessoren` (TEXT[], Migration 0015) macht
|
||||
Unterauftragsverarbeiter (z. B. Anthropic bei Microsoft 365 Copilot,
|
||||
@@ -316,8 +329,8 @@ bleibt, mit welchem Katalogstand ein Vorschlag erzeugt wurde.
|
||||
nicht erfüllen — Grund je Werkzeug festhalten, auch aussortierte
|
||||
Werkzeuge werden im Ergebnis mit Begründung gezeigt.
|
||||
3. **Zweckabgleich.** Aus `beschreibung`/`ergebnis` den Zweck ableiten
|
||||
(Textgenerierung, Übersetzung, Code, Bild, Transkription, Recherche)
|
||||
und gegen `geeignete_zwecke` filtern.
|
||||
(die zwölf Kategorien aus `gueltigeZwecke`, siehe Werkzeugkatalog
|
||||
weiter unten) und gegen `geeignete_zwecke` filtern.
|
||||
4. **Rangfolge.** Verbleibende Werkzeuge sortieren: EU-Verarbeitung,
|
||||
Training standardmäßig aus, kurze Aufbewahrung, Aktualität der
|
||||
Prüfung.
|
||||
@@ -464,7 +477,41 @@ kein separates Desktop-Template.
|
||||
**Tabellen** (`table`/`th`/`td` in `app.css`) sind seit dieser Änderung
|
||||
gestylt (vorher komplett ungestylt, betraf v. a. `register_liste.html`);
|
||||
breite Tabellen stehen in einem `.table-scroll`-Wrapper (horizontales
|
||||
Scrollen auf dem Handy statt gequetschter Spalten).
|
||||
Scrollen auf dem Handy statt gequetschter Spalten). Seit 2026-08-31 hat
|
||||
`.table-scroll` zusätzlich einen rechten Fade-Rand (`mask-image`) —
|
||||
ohne den sah eine am Fensterrand abgeschnittene breite Tabelle wie ein
|
||||
Rendering-Fehler aus statt wie "hier geht's mit Scrollen weiter" (per
|
||||
Screenshot am Werkzeugkatalog beobachtet: die letzten zwei Spalten
|
||||
waren nur als einzelne Buchstaben sichtbar, ganz ohne Hinweis auf mehr
|
||||
Inhalt).
|
||||
|
||||
**Formulare** (`.form-card`/`.form-section`/`.form-grid`/`.chip-group`
|
||||
in `app.css`, seit 2026-08-31, erstmals angewendet auf
|
||||
`betreiber_werkzeug_form.html`): vorher war jedes Formular eine flache
|
||||
Spalte aus `<label>`+`<input>` ohne Gruppierung — bei einem Formular
|
||||
mit über einem Dutzend Feldern (Werkzeug anlegen) wirkte das "klobig,
|
||||
einfach nur untereinander" (O-Ton Nutzer). Jetzt: `.form-card` (weiße
|
||||
Karte mit Rahmen/Schatten, Ant-Design-Tokens aus `design/enterprise.css`
|
||||
übernommen) enthält mehrere `<fieldset class="form-section">`
|
||||
(thematische Abschnitte mit Titel, per `border-top` getrennt), darin
|
||||
`.form-grid` (2 Spalten ab 640px für kurze Felder wie Name/Anbieter
|
||||
nebeneinander, `.form-full` erzwingt volle Breite für lange Felder).
|
||||
Checkbox-/Radio-Gruppen mit vielen Optionen (Verarbeitungsort, die
|
||||
zwölf Zwecke) sind `.chip-group` — abgerundete Pillen statt einer
|
||||
langen Untereinander-Liste, ausgewählte Chips werden über
|
||||
`:has(input:checked)` blau hervorgehoben (kein JavaScript). Ein
|
||||
Löschen-Button steht nicht mehr einfach unter dem Formular, sondern in
|
||||
einer abgesetzten `.form-gefahrenzone`-Fußzeile der Karte. Dieselben
|
||||
Klassen sind bewusst generisch (nicht an ein Formular gebunden) — noch
|
||||
nicht auf andere Formulare (Login/Registrierung, Fragebogen, übrige
|
||||
Admin-Formulare) angewendet, das wäre der nächste Schritt, falls
|
||||
gewünscht. Beim Umbau auffällig gewordener, vorbestehender Bug: das
|
||||
Lösch-Formular in `betreiber_werkzeug_form.html` war fest auf
|
||||
`/betreiber/werkzeuge/{id}/loeschen` verdrahtet, obwohl dieselbe
|
||||
Vorlage auch vom Mandanten-Formular (`/verwaltung/werkzeuge/...`)
|
||||
genutzt wird — ein Mandant hätte beim Löschen eines eigenen Werkzeugs
|
||||
403 bekommen. Jetzt `{{.ActionBase}}/{{.ID}}/loeschen`, wie die
|
||||
Formular-`action` selbst.
|
||||
|
||||
**Verifikationsmethode für CSS-Änderungen:** Live-Cookie-Auth per
|
||||
Chromium-Headless/CDP ist im Sandbox-Environment nicht möglich (kein
|
||||
|
||||
@@ -26,12 +26,17 @@ func istGueltigerVerarbeitungsort(v string) bool {
|
||||
}
|
||||
|
||||
// gueltigeZwecke ist das kontrollierte Vokabular für "geeignete Zwecke"
|
||||
// — exakt die sechs Zweck-Kategorien aus dem Fragebogen/der geplanten
|
||||
// Zweckabgleich-Stufe (siehe CLAUDE.md, Bewertungslogik Schritt 3).
|
||||
// Vorher war das Feld Freitext; ein Tippfehler ("Text" statt
|
||||
// "Textgenerierung") hätte den künftigen automatischen Zweckabgleich
|
||||
// unbemerkt kaputt gemacht.
|
||||
var gueltigeZwecke = []string{"Textgenerierung", "Übersetzung", "Code", "Bild", "Transkription", "Recherche"}
|
||||
// (siehe CLAUDE.md, Bewertungslogik Schritt 3). Vorher war das Feld
|
||||
// Freitext; ein Tippfehler ("Text" statt "Textgenerierung") hätte den
|
||||
// künftigen automatischen Zweckabgleich unbemerkt kaputt gemacht. Am
|
||||
// 2026-08-31 von sechs auf zwölf Kategorien erweitert (bewusste
|
||||
// v1-Scope-Entscheidung überarbeitet, nicht stillschweigend geändert)
|
||||
// — die ursprünglichen sechs deckten nur klassische Wissensarbeit ab,
|
||||
// nicht Datenanalyse/Video/Audio/Kundenservice/Design/Automatisierung.
|
||||
var gueltigeZwecke = []string{
|
||||
"Textgenerierung", "Übersetzung", "Code", "Bild", "Transkription", "Recherche",
|
||||
"Datenanalyse", "Video", "Audio/Sprache", "Kundenservice/Chatbot", "Präsentation/Design", "Automatisierung/Agenten",
|
||||
}
|
||||
|
||||
func istGueltigerZweck(v string) bool {
|
||||
for _, z := range gueltigeZwecke {
|
||||
@@ -67,7 +72,8 @@ type werkzeugListItem struct {
|
||||
Zertifizierungen string
|
||||
GeeigneteZwecke string
|
||||
LetztePruefung string
|
||||
NichtAktuell bool
|
||||
PruefStatusLabel string
|
||||
PruefStatusCSS string
|
||||
}
|
||||
|
||||
// formatAufbewahrung stellt sicher, dass "unbekannt" (Anbieter hat
|
||||
@@ -90,10 +96,32 @@ type werkzeugListeData struct {
|
||||
}
|
||||
|
||||
// werkzeugPruefungVeraltetNach ist die Schwelle, ab der ein
|
||||
// Katalogeintrag im Ergebnis als "nicht aktuell geprüft" markiert wird
|
||||
// (siehe CLAUDE.md, Werkzeugkatalog) — jede Zusicherung im Katalog muss
|
||||
// belegbar UND aktuell sein.
|
||||
const werkzeugPruefungVeraltetNach = 180 * 24 * time.Hour
|
||||
// Katalogeintrag im Ergebnis als "abgelaufen" markiert wird (siehe
|
||||
// CLAUDE.md, Werkzeugkatalog) — jede Zusicherung im Katalog muss
|
||||
// belegbar UND aktuell sein. werkzeugPruefungBaldFaelligNach liegt
|
||||
// 30 Tage davor (dasselbe Vorwarnfenster wie die Wiedervorlage,
|
||||
// wiedervorlageSchwelle) — "bald fällig" soll rechtzeitig auffallen,
|
||||
// nicht erst wenn die Frist schon gerissen ist.
|
||||
const (
|
||||
werkzeugPruefungBaldFaelligNach = 150 * 24 * time.Hour
|
||||
werkzeugPruefungVeraltetNach = 180 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// pruefStatus liefert eine Ampel-Kennzeichnung für das Alter einer
|
||||
// letzten Werkzeug-Prüfung: aktuell (grün) / bald fällig (gelb) /
|
||||
// abgelaufen (rot) — die CSS-Klassen sind dieselben wie für die
|
||||
// Risiko-Ampel anderswo im Produkt (status-niedrig/-mittel/-hoch).
|
||||
func pruefStatus(letztePruefung time.Time) (label, css string) {
|
||||
alter := time.Since(letztePruefung)
|
||||
switch {
|
||||
case alter > werkzeugPruefungVeraltetNach:
|
||||
return "abgelaufen", "status-hoch"
|
||||
case alter > werkzeugPruefungBaldFaelligNach:
|
||||
return "bald fällig", "status-mittel"
|
||||
default:
|
||||
return "aktuell", "status-niedrig"
|
||||
}
|
||||
}
|
||||
|
||||
// handleBetreiberWerkzeugListe zeigt den zentralen Katalog.
|
||||
func (s *Server) handleBetreiberWerkzeugListe(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -104,13 +132,14 @@ func (s *Server) handleBetreiberWerkzeugListe(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
data := werkzeugListeData{Title: "Werkzeugkatalog", Nav: navFor(r)}
|
||||
for _, wz := range werkzeuge {
|
||||
label, css := pruefStatus(wz.LetztePruefung)
|
||||
data.Werkzeuge = append(data.Werkzeuge, werkzeugListItem{
|
||||
ID: wz.ID, Name: wz.Name, Anbieter: wz.Anbieter, Verarbeitungsort: wz.Verarbeitungsort,
|
||||
AVVVerfuegbar: wz.AVVVerfuegbar, TrainingStandard: wz.TrainingStandard,
|
||||
Aufbewahrung: formatAufbewahrung(wz.AufbewahrungTage),
|
||||
Zertifizierungen: strings.Join(wz.Zertifizierungen, ", "), GeeigneteZwecke: strings.Join(wz.GeeigneteZwecke, ", "),
|
||||
LetztePruefung: wz.LetztePruefung.Format("02.01.2006"),
|
||||
NichtAktuell: time.Since(wz.LetztePruefung) > werkzeugPruefungVeraltetNach,
|
||||
PruefStatusLabel: label, PruefStatusCSS: css,
|
||||
})
|
||||
}
|
||||
if err := s.templates.ExecuteTemplate(w, "betreiber-werkzeuge", data); err != nil {
|
||||
|
||||
@@ -11,7 +11,6 @@ package web
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mandantWerkzeugItem struct {
|
||||
@@ -24,7 +23,8 @@ type mandantWerkzeugItem struct {
|
||||
Aufbewahrung string
|
||||
GeeigneteZwecke string
|
||||
LetztePruefung string
|
||||
NichtAktuell bool
|
||||
PruefStatusLabel string
|
||||
PruefStatusCSS string
|
||||
Gesperrt bool
|
||||
}
|
||||
|
||||
@@ -71,13 +71,14 @@ func (s *Server) handleMandantWerkzeugListe(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
data := mandantWerkzeugListeData{Title: "Werkzeugkatalog", Nav: navFor(r)}
|
||||
for _, wz := range zentral {
|
||||
label, css := pruefStatus(wz.LetztePruefung)
|
||||
data.Werkzeuge = append(data.Werkzeuge, mandantWerkzeugItem{
|
||||
ID: wz.ID, Name: wz.Name, Anbieter: wz.Anbieter, Verarbeitungsort: wz.Verarbeitungsort,
|
||||
AVVVerfuegbar: wz.AVVVerfuegbar, TrainingStandard: wz.TrainingStandard,
|
||||
Aufbewahrung: formatAufbewahrung(wz.AufbewahrungTage),
|
||||
GeeigneteZwecke: strings.Join(wz.GeeigneteZwecke, ", "),
|
||||
LetztePruefung: wz.LetztePruefung.Format("02.01.2006"),
|
||||
NichtAktuell: time.Since(wz.LetztePruefung) > werkzeugPruefungVeraltetNach,
|
||||
PruefStatusLabel: label, PruefStatusCSS: css,
|
||||
Gesperrt: gesperrt[wz.ID],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -392,6 +392,16 @@ nav button {
|
||||
color: var(--color-mittel);
|
||||
}
|
||||
|
||||
.status-niedrig {
|
||||
background: var(--color-niedrig-bg);
|
||||
color: var(--color-niedrig);
|
||||
}
|
||||
|
||||
.status-hoch {
|
||||
background: var(--color-hoch-bg);
|
||||
color: var(--color-hoch);
|
||||
}
|
||||
|
||||
.beitraege-liste {
|
||||
list-style: none;
|
||||
margin: 0 0 16px;
|
||||
@@ -545,6 +555,126 @@ button.entfernen {
|
||||
margin: 4px 12px 4px 0;
|
||||
}
|
||||
|
||||
/* ── Formular-Karten (Enterprise-Look, siehe design/enterprise.css
|
||||
.ant-card/.ant-form-item) ─────────────────────────────────────────
|
||||
Ersetzt "ein Formular = eine flache, ununterbrochene Spalte" durch
|
||||
klar abgegrenzte Abschnitte mit Titel und ein Grid für kurze Felder
|
||||
nebeneinander statt jedes Feld einzeln untereinander. */
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-card form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-section + .form-section {
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section > legend {
|
||||
padding: 0 0 12px;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-heading);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.form-grid .form-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Checkbox-/Radio-Gruppen als Chips statt einer langen
|
||||
Untereinander-Liste — v. a. bei den zwölf Zweck-Kategorien war das
|
||||
vorher eine kaum überblickbare Spalte. */
|
||||
.chip-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.chip-group label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 0;
|
||||
padding: 7px 14px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chip-group input[type="checkbox"],
|
||||
.chip-group input[type="radio"] {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chip-group label:has(input:checked) {
|
||||
border-color: var(--branding-primary);
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
color: var(--branding-primary);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.form-actions form,
|
||||
.form-actions button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-gefahrenzone {
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.form-gefahrenzone form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fragebogen fieldset input[type="radio"] {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
@@ -595,11 +725,17 @@ tbody tr:hover {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
/* Ohne dieses Signal sieht eine breite, am Fensterrand abgeschnittene
|
||||
Tabelle wie ein Rendering-Fehler aus statt wie "hier geht's mit
|
||||
Scrollen weiter" — der Fade ist bewusst immer sichtbar (kein JS, das
|
||||
den Scroll-Zustand prüfen könnte), auch am Ende der Tabelle. */
|
||||
.table-scroll {
|
||||
overflow-x: auto;
|
||||
box-shadow: var(--shadow);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 16px;
|
||||
-webkit-mask-image: linear-gradient(to right, black calc(100% - 24px), transparent 100%);
|
||||
mask-image: linear-gradient(to right, black calc(100% - 24px), transparent 100%);
|
||||
}
|
||||
|
||||
/* Ab hier mehr Platz (Tablet) — der Container bekommt spürbaren
|
||||
|
||||
@@ -12,61 +12,107 @@
|
||||
Pflicht.
|
||||
</p>
|
||||
{{if .Error}}<p class="fehler">{{.Error}}</p>{{end}}
|
||||
<div class="form-card">
|
||||
<form method="post" action="{{if .ID}}{{.ActionBase}}/{{.ID}}{{else}}{{.ActionBase}}{{end}}">
|
||||
<fieldset class="form-section">
|
||||
<legend>Grunddaten</legend>
|
||||
<div class="form-grid">
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" name="name" value="{{.Name}}" required>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label for="anbieter">Anbieter</label>
|
||||
<input type="text" id="anbieter" name="anbieter" value="{{.Anbieter}}" required>
|
||||
|
||||
<fieldset>
|
||||
<legend>Verarbeitungsort</legend>
|
||||
</div>
|
||||
<div class="form-full">
|
||||
<label>Verarbeitungsort</label>
|
||||
{{$gewaehlt := .Verarbeitungsort}}
|
||||
<div class="chip-group">
|
||||
{{range .Verarbeitungsorte}}
|
||||
<label><input type="radio" name="verarbeitungsort" value="{{.}}" {{if eq . $gewaehlt}}checked{{end}} required> {{.}}</label>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-section">
|
||||
<legend>Rechtliches & Datenschutz</legend>
|
||||
<div class="form-grid">
|
||||
<div class="form-full">
|
||||
<label>Zusicherungen</label>
|
||||
<div class="chip-group">
|
||||
<label><input type="checkbox" name="avv_verfuegbar" {{if .AVVVerfuegbar}}checked{{end}}> AVV verfügbar</label>
|
||||
<label for="avv_url">AVV-URL</label>
|
||||
<input type="text" id="avv_url" name="avv_url" value="{{.AVVURL}}">
|
||||
|
||||
<label><input type="checkbox" name="training_opt_out" {{if .TrainingOptOut}}checked{{end}}> Training-Opt-out möglich</label>
|
||||
<label><input type="checkbox" name="training_standard" {{if .TrainingStandard}}checked{{end}}> Training standardmäßig aus</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-full">
|
||||
<label for="avv_url">AVV-URL</label>
|
||||
<input type="text" id="avv_url" name="avv_url" value="{{.AVVURL}}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="aufbewahrung_tage">Aufbewahrung (Tage, leer lassen wenn vom Anbieter nicht beziffert)</label>
|
||||
<input type="number" id="aufbewahrung_tage" name="aufbewahrung_tage" min="0" value="{{.AufbewahrungTage}}">
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label for="zertifizierungen">Zertifizierungen (kommagetrennt)</label>
|
||||
<input type="text" id="zertifizierungen" name="zertifizierungen" value="{{.Zertifizierungen}}">
|
||||
|
||||
</div>
|
||||
<div class="form-full">
|
||||
<label for="subprozessoren">Subprozessoren (kommagetrennt, z. B. Anthropic, AWS)</label>
|
||||
<input type="text" id="subprozessoren" name="subprozessoren" value="{{.Subprozessoren}}">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Geeignete Zwecke</legend>
|
||||
<fieldset class="form-section">
|
||||
<legend>Einsatzbereich</legend>
|
||||
<div class="form-grid">
|
||||
<div class="form-full">
|
||||
<label>Geeignete Zwecke</label>
|
||||
<div class="chip-group">
|
||||
{{range .ZweckeOptionen}}
|
||||
<label><input type="checkbox" name="geeignete_zwecke" value="{{.}}" {{if index $.ZweckeAusgewaehlt .}}checked{{end}}> {{.}}</label>
|
||||
{{end}}
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-full">
|
||||
<label for="einschraenkungen">Einschränkungen (kommagetrennt)</label>
|
||||
<input type="text" id="einschraenkungen" name="einschraenkungen" value="{{.Einschraenkungen}}">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-section">
|
||||
<legend>Beleg</legend>
|
||||
<div class="form-grid">
|
||||
<div>
|
||||
<label for="letzte_pruefung">Letzte Prüfung</label>
|
||||
<input type="date" id="letzte_pruefung" name="letzte_pruefung" value="{{.LetztePruefung}}" required>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label for="quelle">Quelle (Beleg)</label>
|
||||
<input type="text" id="quelle" name="quelle" value="{{.Quelle}}" required>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
{{if .ID}}
|
||||
<form method="post" action="/betreiber/werkzeuge/{{.ID}}/loeschen" onsubmit="return confirm('Werkzeug wirklich löschen?')">
|
||||
<div class="form-gefahrenzone">
|
||||
<span class="hinweis">Ein gelöschtes Werkzeug kann nicht wiederhergestellt werden.</span>
|
||||
<form method="post" action="{{.ActionBase}}/{{.ID}}/loeschen" onsubmit="return confirm('Werkzeug wirklich löschen?')">
|
||||
<button type="submit" class="entfernen">Werkzeug löschen</button>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<td>{{.Aufbewahrung}}</td>
|
||||
<td>{{.Zertifizierungen}}</td>
|
||||
<td>{{.GeeigneteZwecke}}</td>
|
||||
<td>{{.LetztePruefung}}{{if .NichtAktuell}} <span class="status">nicht aktuell geprüft</span>{{end}}</td>
|
||||
<td>{{.LetztePruefung}} <span class="status {{.PruefStatusCSS}}">{{.PruefStatusLabel}}</span></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<td>{{if .TrainingStandard}}ja{{else}}nein{{end}}</td>
|
||||
<td>{{.Aufbewahrung}}</td>
|
||||
<td>{{.GeeigneteZwecke}}</td>
|
||||
<td>{{.LetztePruefung}}{{if .NichtAktuell}} <span class="status">nicht aktuell geprüft</span>{{end}}</td>
|
||||
<td>{{.LetztePruefung}} <span class="status {{.PruefStatusCSS}}">{{.PruefStatusLabel}}</span></td>
|
||||
<td>
|
||||
{{if .Gesperrt}}
|
||||
<span class="status">gesperrt</span>
|
||||
|
||||
Reference in New Issue
Block a user