diff --git a/apps/tesm-license/pyproject.toml b/apps/tesm-license/pyproject.toml index 7956513..cde5e86 100644 --- a/apps/tesm-license/pyproject.toml +++ b/apps/tesm-license/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-license" -version = "2.6.1" +version = "2.7.0" description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/pyproject.toml b/packages/tesm-core/pyproject.toml index 2b5ed2c..dbe60fe 100644 --- a/packages/tesm-core/pyproject.toml +++ b/packages/tesm-core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-core" -version = "2.6.1" +version = "2.7.0" description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/src/tesm_core/app_factory.py b/packages/tesm-core/src/tesm_core/app_factory.py index dd7939c..57d893e 100644 --- a/packages/tesm-core/src/tesm_core/app_factory.py +++ b/packages/tesm-core/src/tesm_core/app_factory.py @@ -148,7 +148,7 @@ def create_base_app( #: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide #: Anwendungen. auth_tagline: str = "", - app_version: str = "2.6.1", + app_version: str = "2.7.0", built_in_trusted_keys: tuple[str, ...] = (), enable_license: bool = True, template_folder: str | None = None, diff --git a/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css b/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css index 7d49fa0..c3672a4 100644 --- a/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css +++ b/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css @@ -1458,3 +1458,50 @@ pointer-events: none; z-index: 3; } + +/* -- Ergebnis der Signaturpruefung ---------------------------------------- */ +/* Der Schleier liegt ueber der Seite und nimmt keine Klicks an: ein + unterschriebenes Dokument ist flach, aber bei einem Irrtum soll die + Kennzeichnung nicht auch noch die Bedienung blockieren. */ + +.viewer__pdf--unveraendert { + box-shadow: inset 0 0 0 3px var(--success); +} + +.viewer__pdf--unveraendert::after, +.viewer__pdf--veraendert::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + z-index: 4; +} + +.viewer__pdf--unveraendert::after { + background: rgba(47, 158, 68, 0.1); +} + +.viewer__pdf--veraendert { + box-shadow: inset 0 0 0 3px var(--danger); +} + +.viewer__pdf--veraendert::after { + background: rgba(201, 42, 42, 0.08); +} + +.viewer__pruefung { + margin: 0; + padding: 6px 12px; + font-size: var(--fs-xs); + border-top: 1px solid var(--border); +} + +.viewer__pruefung--gut { + color: var(--success); + background: rgba(47, 158, 68, 0.12); +} + +.viewer__pruefung--schlecht { + color: var(--danger); + background: rgba(201, 42, 42, 0.12); +} diff --git a/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js b/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js index 8df28b2..8cd0b16 100644 --- a/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js +++ b/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js @@ -601,9 +601,18 @@ async function pdfBetrachter(behaelter, seiten, url, basis, { formulare = true } const anteilY = (event.clientY - rechteck.top) / rechteck.height; const seite = await dokument.getPage(nummer); const blick = seite.getViewport({ scale: 1 }); - const breite = Math.min(170, blick.width * 0.35); - const hoehe = - (wartendeUnterschrift.hoehe / wartendeUnterschrift.breite) * breite; + // Ein menschliches Mass: rund fuenf Zentimeter breit, hoechstens knapp + // zwei hoch. Ohne die Hoehengrenze wird aus einem hohen Gekritzel ein + // Stempel, der die halbe Seite einnimmt -- das Seitenverhaeltnis kommt ja + // aus dem Zuschnitt, nicht aus der Vorlage. + const MAX_BREITE = 150; + const MAX_HOEHE = 50; + let breite = Math.min(MAX_BREITE, blick.width * 0.3); + let hoehe = (wartendeUnterschrift.hoehe / wartendeUnterschrift.breite) * breite; + if (hoehe > MAX_HOEHE) { + breite *= MAX_HOEHE / hoehe; + hoehe = MAX_HOEHE; + } const eintrag = { ...wartendeUnterschrift, seite: nummer, @@ -700,6 +709,7 @@ export function initFilePreview(wurzel = document) { const nameFeld = stueck("name"); const ersetzenFeld = stueck("ersetzen"); const ersetzenKasten = stueck("ersetzenfeld"); + const pruefzeile = stueck("pruefung"); const jetzt = stueck("jetzt"); const abbrechen = stueck("abbrechen"); const modusFeld = stueck("modus"); @@ -761,6 +771,7 @@ export function initFilePreview(wurzel = document) { }; const aufraeumen = () => { + pruefungZuruecksetzen(); bild.removeAttribute("src"); bilder.zuruecksetzen(); rahmen.src = "about:blank"; @@ -778,6 +789,52 @@ export function initFilePreview(wurzel = document) { aktuell = null; }; + /* Unterschrieben und unveraendert? Die Antwort kommt aus dem + Aenderungsprotokoll: dort steht die Pruefsumme der abgelegten Fassung. + Stimmt sie, bekommt die Seite einen gruenen Schleier -- man soll es sehen, + bevor man liest, nicht danach. */ + + const pruefungZuruecksetzen = () => { + pdfBehaelter.classList.remove("viewer__pdf--unveraendert", "viewer__pdf--veraendert"); + if (pruefzeile) { + pruefzeile.hidden = true; + pruefzeile.textContent = ""; + pruefzeile.className = "viewer__pruefung"; + } + }; + + const pruefen = async (adresse) => { + pruefungZuruecksetzen(); + if (!adresse) return; + try { + const stand = await window.tesm.api(adresse); + if (stand.status === "unveraendert") { + pdfBehaelter.classList.add("viewer__pdf--unveraendert"); + if (pruefzeile) { + pruefzeile.hidden = false; + pruefzeile.className = "viewer__pruefung viewer__pruefung--gut"; + const namen = (stand.namen || []).join(", "); + pruefzeile.textContent = + `Unveraendert seit der Unterschrift${namen ? ` von ${namen}` : ""}` + + ` -- Pruefsumme stimmt mit dem Aenderungsprotokoll ueberein.`; + } + } else if (stand.status === "veraendert") { + pdfBehaelter.classList.add("viewer__pdf--veraendert"); + if (pruefzeile) { + pruefzeile.hidden = false; + pruefzeile.className = "viewer__pruefung viewer__pruefung--schlecht"; + pruefzeile.textContent = + "Diese Datei wurde nach der Unterschrift veraendert -- die Pruefsumme " + + "weicht vom Aenderungsprotokoll ab."; + } + } + } catch (fehler) { + // Keine Aussage ist besser als eine falsche: ohne Antwort bleibt die + // Seite schlicht ungekennzeichnet. + console.error("Signaturpruefung", fehler); + } + }; + const oeffnen = async (ausloeser) => { aufraeumen(); const art = ausloeser.dataset.fillKind || "text"; @@ -793,6 +850,7 @@ export function initFilePreview(wurzel = document) { return; } if (hinweis) hinweis.textContent = "PDF wird geladen ..."; + pruefen(ausloeser.dataset.fillPruefung || ""); try { pdf = await pdfBetrachter(pdfScroll, pdfSeiten, quelle, basis, { formulare: rumpf.dataset.viewerFormulare === "1", diff --git a/packages/tesm-licensing-server/pyproject.toml b/packages/tesm-licensing-server/pyproject.toml index 621d26a..fe0f74e 100644 --- a/packages/tesm-licensing-server/pyproject.toml +++ b/packages/tesm-licensing-server/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-licensing-server" -version = "2.6.1" +version = "2.7.0" description = "Serverhaelfte des TESM-Lizenzprotokolls: ausstellen, erneuern, antworten" requires-python = ">=3.11" -dependencies = ["tesm-licensing==2.6.1", "cryptography>=41"] +dependencies = ["tesm-licensing==2.7.0", "cryptography>=41"] [tool.setuptools.packages.find] where = ["src"] diff --git a/packages/tesm-licensing/pyproject.toml b/packages/tesm-licensing/pyproject.toml index fa24659..066d6ac 100644 --- a/packages/tesm-licensing/pyproject.toml +++ b/packages/tesm-licensing/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-licensing" -version = "2.6.1" +version = "2.7.0" description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = ["cryptography>=42"]