Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c62c7783d6 | ||
|
|
ecc1283de7 | ||
|
|
48205e6e46 | ||
|
|
c31f9b69cb |
@@ -61,6 +61,14 @@ conflicting profile is preserved unless the reviewed fragment explicitly sets
|
|||||||
`on_conflict` to `update`. Credential bindings are added idempotently and are
|
`on_conflict` to `update`. Credential bindings are added idempotently and are
|
||||||
never removed merely because a package omits a credential reference.
|
never removed merely because a package omits a credential reference.
|
||||||
|
|
||||||
|
Mail also registers a module-owned infrastructure dependency provider. Its
|
||||||
|
authorized Ops inventory lists every persisted SMTP endpoint and legacy SMTP
|
||||||
|
profile by stable non-secret reference, state and scope, together with numeric
|
||||||
|
credential-binding evidence. Before the host deployer changes or removes
|
||||||
|
`mail.smtp`, it requires a fresh, complete inventory from the same installation
|
||||||
|
and displays these dependencies in the plan. The inventory never contains
|
||||||
|
transport credentials or decrypted envelope data.
|
||||||
|
|
||||||
Preflight does not prove SMTP reachability. After apply, use the normal Mail
|
Preflight does not prove SMTP reachability. After apply, use the normal Mail
|
||||||
profile test and Ops health surfaces. If the receipt says SMTP is unavailable,
|
profile test and Ops health surfaces. If the receipt says SMTP is unavailable,
|
||||||
is invalid, or is not mounted, import is blocked with an operator-facing
|
is invalid, or is not mounted, import is blocked with an operator-facing
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.22",
|
"version": "0.1.26",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.22",
|
"version": "0.1.26",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.22",
|
"version": "0.1.26",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "webui/src/index.ts",
|
"main": "webui/src/index.ts",
|
||||||
|
|||||||
+2
-2
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-mail"
|
name = "govoplan-mail"
|
||||||
version = "0.1.22"
|
version = "0.1.26"
|
||||||
description = "GovOPlaN mail module with backend and WebUI integration."
|
description = "GovOPlaN mail module with backend and WebUI integration."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.18",
|
"govoplan-core>=0.1.42",
|
||||||
"pydantic>=2,<3",
|
"pydantic>=2,<3",
|
||||||
"redis>=5,<6",
|
"redis>=5,<6",
|
||||||
"SQLAlchemy>=2,<3",
|
"SQLAlchemy>=2,<3",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from dataclasses import dataclass, replace
|
|||||||
import re
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import or_, select
|
from sqlalchemy import func, or_, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from govoplan_core.core.configuration_packages import (
|
from govoplan_core.core.configuration_packages import (
|
||||||
@@ -24,6 +24,8 @@ from govoplan_core.core.configuration_packages import (
|
|||||||
from govoplan_core.core.infrastructure_capabilities import (
|
from govoplan_core.core.infrastructure_capabilities import (
|
||||||
InfrastructureCapability,
|
InfrastructureCapability,
|
||||||
InfrastructureCapabilityReceipt,
|
InfrastructureCapabilityReceipt,
|
||||||
|
InfrastructureDependency,
|
||||||
|
InfrastructureDependencyProvider,
|
||||||
)
|
)
|
||||||
from govoplan_core.security.credential_envelopes import (
|
from govoplan_core.security.credential_envelopes import (
|
||||||
CredentialAccessContext,
|
CredentialAccessContext,
|
||||||
@@ -51,6 +53,9 @@ from govoplan_mail.backend.server_hierarchy import (
|
|||||||
|
|
||||||
|
|
||||||
MAIL_CONFIGURATION_CAPABILITY = "mail.configuration"
|
MAIL_CONFIGURATION_CAPABILITY = "mail.configuration"
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY = (
|
||||||
|
"infrastructure.dependency_inventory.mail"
|
||||||
|
)
|
||||||
SMTP_PROFILE_FRAGMENT = "smtp_profile"
|
SMTP_PROFILE_FRAGMENT = "smtp_profile"
|
||||||
_SYSTEM_CONFIGURATION_SCOPES = frozenset(
|
_SYSTEM_CONFIGURATION_SCOPES = frozenset(
|
||||||
{"system:settings:write", "system:governance:write"}
|
{"system:settings:write", "system:governance:write"}
|
||||||
@@ -132,8 +137,12 @@ class _ProfileTarget:
|
|||||||
on_conflict: str
|
on_conflict: str
|
||||||
|
|
||||||
|
|
||||||
class SqlMailConfigurationProvider(ConfigurationProvider):
|
class SqlMailConfigurationProvider(
|
||||||
|
ConfigurationProvider,
|
||||||
|
InfrastructureDependencyProvider,
|
||||||
|
):
|
||||||
module_id = "mail"
|
module_id = "mail"
|
||||||
|
capability_ids = ("mail.smtp",)
|
||||||
|
|
||||||
def describe(self) -> ConfigurationProviderDescription:
|
def describe(self) -> ConfigurationProviderDescription:
|
||||||
return ConfigurationProviderDescription(
|
return ConfigurationProviderDescription(
|
||||||
@@ -204,6 +213,84 @@ class SqlMailConfigurationProvider(ConfigurationProvider):
|
|||||||
item for item in import_result.diagnostics if item.severity == "blocker"
|
item for item in import_result.diagnostics if item.severity == "blocker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def infrastructure_dependencies(self) -> tuple[InfrastructureDependency, ...]:
|
||||||
|
with get_database().session() as session:
|
||||||
|
return _smtp_infrastructure_dependencies(session)
|
||||||
|
|
||||||
|
|
||||||
|
def _smtp_infrastructure_dependencies(
|
||||||
|
session: Session,
|
||||||
|
) -> tuple[InfrastructureDependency, ...]:
|
||||||
|
endpoints = tuple(
|
||||||
|
session.execute(
|
||||||
|
select(MailServerEndpoint)
|
||||||
|
.where(MailServerEndpoint.protocol == "smtp")
|
||||||
|
.order_by(MailServerEndpoint.id)
|
||||||
|
).scalars()
|
||||||
|
)
|
||||||
|
binding_counts = {
|
||||||
|
str(server_id): int(count)
|
||||||
|
for server_id, count in session.execute(
|
||||||
|
select(
|
||||||
|
MailServerCredentialBinding.server_id,
|
||||||
|
func.count(MailServerCredentialBinding.id),
|
||||||
|
).group_by(MailServerCredentialBinding.server_id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
dependencies: list[InfrastructureDependency] = []
|
||||||
|
endpoint_profile_ids: set[str] = set()
|
||||||
|
for endpoint in endpoints:
|
||||||
|
endpoint_profile_ids.add(endpoint.profile_id)
|
||||||
|
dependencies.append(
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="mail.smtp",
|
||||||
|
module_id="mail",
|
||||||
|
dependency_type="smtp_endpoint",
|
||||||
|
dependency_ref=mail_server_ref(endpoint.id) or f"mail:{endpoint.id}",
|
||||||
|
state="active" if endpoint.is_active else "inactive",
|
||||||
|
scope=str(endpoint.scope_type or "tenant"),
|
||||||
|
summary=(
|
||||||
|
"A persisted Mail SMTP endpoint is bound to the deployment relay."
|
||||||
|
),
|
||||||
|
metrics={
|
||||||
|
"credential_binding_count": binding_counts.get(endpoint.id, 0),
|
||||||
|
"default_endpoint": int(bool(endpoint.is_default)),
|
||||||
|
},
|
||||||
|
required_action=(
|
||||||
|
"Rebind, migrate, or explicitly retire this SMTP endpoint and its credential-envelope references before changing the relay capability."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
legacy_profiles = tuple(
|
||||||
|
session.execute(
|
||||||
|
select(MailServerProfile)
|
||||||
|
.where(MailServerProfile.smtp_config.is_not(None))
|
||||||
|
.order_by(MailServerProfile.id)
|
||||||
|
).scalars()
|
||||||
|
)
|
||||||
|
for profile in legacy_profiles:
|
||||||
|
if profile.id in endpoint_profile_ids or not dict(profile.smtp_config or {}):
|
||||||
|
continue
|
||||||
|
dependencies.append(
|
||||||
|
InfrastructureDependency(
|
||||||
|
capability_id="mail.smtp",
|
||||||
|
module_id="mail",
|
||||||
|
dependency_type="legacy_smtp_profile",
|
||||||
|
dependency_ref=f"mail-profile:{profile.id}",
|
||||||
|
state="active" if profile.is_active else "inactive",
|
||||||
|
scope=str(profile.scope_type or "tenant"),
|
||||||
|
summary=(
|
||||||
|
"A persisted legacy Mail profile still contains SMTP transport configuration."
|
||||||
|
),
|
||||||
|
metrics={"credential_binding_count": 0},
|
||||||
|
required_action=(
|
||||||
|
"Migrate or explicitly retire this legacy profile before changing the relay capability."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return tuple(dependencies)
|
||||||
|
|
||||||
|
|
||||||
def _preflight_smtp_profile(
|
def _preflight_smtp_profile(
|
||||||
session: Session,
|
session: Session,
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
"""German translations for public structured documentation metadata."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
GERMAN_STRUCTURED_TRANSLATIONS: dict[str, dict[str, Any]] = {'mail.bounce-processing': {'verification': 'Senden Sie eine Nachricht mit einer eindeutigen '
|
||||||
|
'Message-ID, nehmen Sie zweimal einen DSN auf und '
|
||||||
|
'überprüfen Sie eine korrelierte Beobachtung, während '
|
||||||
|
'die SMTP-Akzeptanz intakt bleibt.'},
|
||||||
|
'mail.privacy.data-subject-requests': {'limitations': ['Verschlüsselte ausgehende Nutzlasten '
|
||||||
|
'können vom Empfänger nicht ohne eine '
|
||||||
|
'unabhängig bestätigte '
|
||||||
|
'Mail-Befehlsreferenz durchsucht werden.',
|
||||||
|
'Die externe Mailbox-Löschung befindet '
|
||||||
|
'sich außerhalb des DSAR-Anbieters und '
|
||||||
|
'muss mit dem konfigurierten Anbieter '
|
||||||
|
'koordiniert werden.'],
|
||||||
|
'steps': ['Führen Sie die Mail-Provider-Suche und '
|
||||||
|
'Überprüfung von Mailbox-Index, POP3-Import, '
|
||||||
|
'Profil, Lieferung, Abgleich und '
|
||||||
|
'Bounce-Dispositionen aus.',
|
||||||
|
'Bewahren Sie den unveränderlichen '
|
||||||
|
'Transportnachweis mit seinem Grund auf.',
|
||||||
|
'Koordinieren Sie die genehmigte '
|
||||||
|
'Mailbox-Inhaltelöschung mit der autoritativen '
|
||||||
|
'externen Mailbox und aktualisieren Sie dann den '
|
||||||
|
'abgeleiteten Index.',
|
||||||
|
'Verwenden Sie '
|
||||||
|
'E-Mail-Profillebenszykluskontrollen für '
|
||||||
|
'genehmigte Änderungen des persönlichen Profils; '
|
||||||
|
'Bearbeiten Sie nicht direkt verschlüsselte '
|
||||||
|
'Nutzlast- oder Beweiszeilen.']},
|
||||||
|
'mail.profile-standard-folder-mappings': {'steps': ['Öffnen Sie einen editierbaren IMAP-Server in '
|
||||||
|
'einem wiederverwendbaren Mail-Profil.',
|
||||||
|
'Wählen Sie Ordner erkennen, um die vom '
|
||||||
|
'Anbieter sichtbaren Ordnernamen zu laden, '
|
||||||
|
'oder geben Sie genaue Namen manuell ein.',
|
||||||
|
'Wenden Sie die erkannten Zuordnungen an, '
|
||||||
|
'überprüfen Sie jede Rolle und lassen Sie '
|
||||||
|
'unsichere Rollen für automatisches Verhalten '
|
||||||
|
'leer.',
|
||||||
|
'Speichern Sie das Profil und laden Sie es '
|
||||||
|
'neu, um zu überprüfen, ob die Zuordnungen '
|
||||||
|
'beibehalten wurden.'],
|
||||||
|
'verification': 'Bestätigen Sie, dass ältere '
|
||||||
|
'Sent-only-Profile das gleiche '
|
||||||
|
'effektive Sent-Mapping zeigen, und '
|
||||||
|
'bestätigen Sie, dass ein '
|
||||||
|
'kampagnenlokaler Sent-Override '
|
||||||
|
'unverändert bleibt.'},
|
||||||
|
'mail.reference.campaign-delivery-contract': {'verification': 'Nachweisen Sie, dass veraltete '
|
||||||
|
'Revisionen vor der Entschlüsselung '
|
||||||
|
'fehlschlagen, der Batch-Preflight '
|
||||||
|
'vor DATA fehlschlägt, zwei '
|
||||||
|
'Nachrichten eine gesunde '
|
||||||
|
'Verbindung wiederverwenden, eine '
|
||||||
|
'veraltete Verbindung vor der '
|
||||||
|
'nächsten Nachricht wieder '
|
||||||
|
'verbunden wird, die Trennung nach '
|
||||||
|
'der DATA nie wiedergegeben wird, '
|
||||||
|
'systemische Ausfälle verbleibende '
|
||||||
|
'Jobs anhalten, Anbieterdetails '
|
||||||
|
'werden gelöscht und das Interface '
|
||||||
|
'/ Versionsgate wird übergeben.'},
|
||||||
|
'mail.reference.credentials-egress-retirement': {'verification': 'Testen Sie DNS-Rebinding und '
|
||||||
|
'verweigerte Adressen, beweisen '
|
||||||
|
'Sie, dass der verbundene Peer '
|
||||||
|
'angeheftet ist, injizieren Sie '
|
||||||
|
'Credential-Scrub- und '
|
||||||
|
'Auditfehler, wiederholen Sie '
|
||||||
|
'die Löschung für Idempotenz und '
|
||||||
|
'führen Sie den '
|
||||||
|
'Ruhestands-Preflight gegen '
|
||||||
|
'einen Snapshot aus.'},
|
||||||
|
'mail.workflow.choose-and-test-profile': {'outcome': 'Die verbrauchende Aufgabe verweist auf ein '
|
||||||
|
'verfügbares Mail-eigenes Profil und enthält '
|
||||||
|
'keine kopierte Transportkonfiguration oder '
|
||||||
|
'Anmeldeinformationen.',
|
||||||
|
'prerequisites': ['Mail ist installiert und Sie können '
|
||||||
|
'Profile lesen, verwenden und testen, '
|
||||||
|
'die im aktuellen Kontext sichtbar '
|
||||||
|
'sind.',
|
||||||
|
'Ein Profiladministrator hat '
|
||||||
|
'Anmeldeinformationen und effektive '
|
||||||
|
'Richtlinien konfiguriert.'],
|
||||||
|
'steps': ['Öffnen Sie Mail-Profile und wählen Sie ein '
|
||||||
|
'sichtbares aktives Profil.',
|
||||||
|
'Überprüfen Sie den Sicherheitsumfang, die '
|
||||||
|
'Verfügbarkeit von SMTP/IMAP/JMAP und die '
|
||||||
|
'Absenderidentität, ohne Anmeldewerte zu '
|
||||||
|
'erwarten.',
|
||||||
|
'Führen Sie den entsprechenden SMTP-, IMAP- '
|
||||||
|
'oder '
|
||||||
|
'JMAP-Konnektivitäts-/Authentifizierungstest '
|
||||||
|
'zuerst gegen ein Nicht-Produktionsziel aus.',
|
||||||
|
'Kehren Sie zur verbrauchenden Aufgabe zurück '
|
||||||
|
'und wählen Sie dasselbe Profil über den '
|
||||||
|
'Picker aus.'],
|
||||||
|
'verification': 'Laden Sie beide Oberflächen neu, '
|
||||||
|
'bestätigen Sie, dass nur die stabile '
|
||||||
|
'Referenz vom Verbraucher beibehalten '
|
||||||
|
'wird, und führen Sie die eigene '
|
||||||
|
'kontextbezogene Richtlinienvalidierung '
|
||||||
|
'des Verbrauchers durch.'},
|
||||||
|
'mail.workflow.legacy-pop3-import': {'fields': [{'label': 'Verkehrssicherheit',
|
||||||
|
'user_description': 'Verwenden Sie TLS oder '
|
||||||
|
'STARTTLS; unverschlüsselter '
|
||||||
|
'Transport unterliegt '
|
||||||
|
'weiterhin der '
|
||||||
|
'Deployment-Egress-Richtlinie '
|
||||||
|
'und ist nicht der sichere '
|
||||||
|
'Standard.'},
|
||||||
|
{'label': 'Grenzwerte für Nachrichten und Chargen',
|
||||||
|
'user_description': 'Bound sowohl jede '
|
||||||
|
'heruntergeladene Nachricht '
|
||||||
|
'als auch die gesamte '
|
||||||
|
'importierte Charge, bevor '
|
||||||
|
'der Anbieterinhalt in den '
|
||||||
|
'lokalen verschlüsselten '
|
||||||
|
'Speicher gelangt.'},
|
||||||
|
{'label': 'Ausdrücklich Legacy Import ermöglichen',
|
||||||
|
'user_description': 'Hält die Quelle inaktiv, '
|
||||||
|
'bis die Endpunktmetadaten '
|
||||||
|
'und ihre verschlüsselten '
|
||||||
|
'Anmeldeinformationen beide '
|
||||||
|
'gespeichert wurden.'},
|
||||||
|
{'label': 'Anträge auf Löschung nach Einfuhr',
|
||||||
|
'user_description': 'Erlaubt, aber wählt niemals '
|
||||||
|
'die separat autorisierte '
|
||||||
|
'destruktive Anforderung '
|
||||||
|
'aus; jede Charge benötigt '
|
||||||
|
'noch eine explizite '
|
||||||
|
'Bestätigung.'}],
|
||||||
|
'limitations': ['Ein POP3-Server muss stabile '
|
||||||
|
'UIDL-Identifikatoren bereitstellen; '
|
||||||
|
'ansonsten ist eine sichere doppelte '
|
||||||
|
'Verhinderung nicht verfügbar und der Import '
|
||||||
|
'wird abgelehnt.',
|
||||||
|
'POP3 hat keine Ordner- oder Flag-Semantik '
|
||||||
|
'und ist nicht das empfohlene Protokoll für '
|
||||||
|
'den laufenden Mailbox-Zugriff.',
|
||||||
|
'Die Quelllöschung kann nicht zurückgesetzt '
|
||||||
|
'werden und hat möglicherweise ein '
|
||||||
|
'unbekanntes Ergebnis, wenn die Verbindung '
|
||||||
|
'fehlschlägt, während QUIT Löschungen '
|
||||||
|
'festlegt.'],
|
||||||
|
'operational_consequences': ['Durch das Ändern von Host-, '
|
||||||
|
'Port-, Sicherheits-, '
|
||||||
|
'Kontoidentitäts- oder '
|
||||||
|
'Importlimits wird die '
|
||||||
|
'Transportrevision geändert und '
|
||||||
|
'veraltete Previews ungültig '
|
||||||
|
'gemacht.',
|
||||||
|
'Durch das Ermöglichen des '
|
||||||
|
'Löschens von Quellen entsteht '
|
||||||
|
'eine irreversible Grenze für '
|
||||||
|
'externe Effekte; unbekannte '
|
||||||
|
'QUIT-Ergebnisse erfordern eine '
|
||||||
|
'Versöhnung statt eines blinden '
|
||||||
|
'Wiederholens.',
|
||||||
|
'Die Deaktivierung von '
|
||||||
|
'Legacy-Importen führt zu '
|
||||||
|
'regulierten lokalen Importen '
|
||||||
|
'und deren Nachweisen, die zur '
|
||||||
|
'Überprüfung zur Verfügung '
|
||||||
|
'stehen, während der Zugang '
|
||||||
|
'neuer Anbieter verhindert '
|
||||||
|
'wird.'],
|
||||||
|
'steps': ['Bitten Sie einen Mail-Administrator, eine '
|
||||||
|
'dedizierte POP3-Altquelle zu konfigurieren und '
|
||||||
|
'explizit zu aktivieren.',
|
||||||
|
'Testen Sie die Quelle und aktualisieren Sie eine '
|
||||||
|
'begrenzte Vorschau; Es werden keine '
|
||||||
|
'Nachrichtenflags oder Löschstatus geändert.',
|
||||||
|
'Wählen Sie Nachrichten aus und importieren Sie '
|
||||||
|
'sie in verschlüsselte Pending-Review-Datensätze.',
|
||||||
|
'Verwenden Sie delete-after-import nur, wenn '
|
||||||
|
'Richtlinien und eine separate destruktive '
|
||||||
|
'Berechtigung dies zulassen, und versöhnen Sie '
|
||||||
|
'dann fehlgeschlagene oder unbekannte Ergebnisse.'],
|
||||||
|
'verification': 'Nachweisen Sie deaktivierte Richtlinien, '
|
||||||
|
'TLS- und Authentifizierungsdiagnostik, '
|
||||||
|
'zerstörungsfreie Vorschau / Import, '
|
||||||
|
'UIDL-Duplikate-Verhinderung, verschlüsselte '
|
||||||
|
'Rohdatenspeicherung, separate '
|
||||||
|
'Löschberechtigung, '
|
||||||
|
'Import-Vor-Löschen-Auditbestellung und '
|
||||||
|
'fehlgeschlagene oder ergebnisunbekannte '
|
||||||
|
'Löschnachweise.'},
|
||||||
|
'mail.workflow.read-mailbox': {'outcome': 'Die erforderliche Nachricht wurde inspiziert, ohne den '
|
||||||
|
'Status der Provider-Mailbox zu ändern.',
|
||||||
|
'prerequisites': ['Ein aktives sichtbares Profil hat IMAP oder '
|
||||||
|
'JMAP konfiguriert.',
|
||||||
|
'Sie können beide dieses profil verwenden und '
|
||||||
|
'seine mailbox lesen.'],
|
||||||
|
'steps': ['Öffnen Sie Mail und wählen Sie ein autorisiertes IMAP- '
|
||||||
|
'oder JMAP-fähiges Profil.',
|
||||||
|
'Wählen Sie einen Ordner aus, überprüfen Sie das '
|
||||||
|
'Live/Cache-Synchronisationslabel und stellen Sie den '
|
||||||
|
'begrenzten Nachrichtenindex auf die Seite; JMAP-Suchen '
|
||||||
|
'werden beim Anbieter ausgeführt.',
|
||||||
|
'Verwenden Sie den vom Anbieter abgeleiteten '
|
||||||
|
'Read/Unread-Indikator und öffnen Sie dann nur die für '
|
||||||
|
'die Aufgabe benötigte Nachricht.',
|
||||||
|
'Wechseln Sie bei Bedarf zwischen sicheren Klartext- und '
|
||||||
|
'isolierten HTML-Ansichten und überprüfen Sie die '
|
||||||
|
'Anhang- oder Nichtverfügbarkeitsangaben, bevor Sie die '
|
||||||
|
'Vorschau schließen.'],
|
||||||
|
'verification': 'Aktualisieren Sie das Provider-Postfach '
|
||||||
|
'unabhängig und bestätigen Sie, dass keine Lese-, '
|
||||||
|
'Verschiebe-, Lösch-, Antwort- oder Flag-Mutation '
|
||||||
|
'durch GovOPlaN verursacht wurde.'}}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from govoplan_core.core.modules import with_documentation_structured_translations
|
||||||
|
from govoplan_mail.backend.german_structured_documentation import GERMAN_STRUCTURED_TRANSLATIONS
|
||||||
|
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -29,7 +32,9 @@ from govoplan_core.core.modules import (
|
|||||||
ModuleManifest,
|
ModuleManifest,
|
||||||
NavItem,
|
NavItem,
|
||||||
PermissionDefinition,
|
PermissionDefinition,
|
||||||
|
ProductAvailabilityExplanation,
|
||||||
ProductAreaContribution,
|
ProductAreaContribution,
|
||||||
|
ProductSurfaceContribution,
|
||||||
QuickAccessTool,
|
QuickAccessTool,
|
||||||
RoleTemplate,
|
RoleTemplate,
|
||||||
)
|
)
|
||||||
@@ -43,7 +48,10 @@ from govoplan_core.core.provider_governance import (
|
|||||||
from govoplan_core.core.search import SearchSourceProviderRegistration
|
from govoplan_core.core.search import SearchSourceProviderRegistration
|
||||||
from govoplan_core.core.views import ViewSurface
|
from govoplan_core.core.views import ViewSurface
|
||||||
from govoplan_core.db.base import Base
|
from govoplan_core.db.base import Base
|
||||||
from govoplan_mail.backend.configuration_provider import MAIL_CONFIGURATION_CAPABILITY
|
from govoplan_mail.backend.configuration_provider import (
|
||||||
|
MAIL_CONFIGURATION_CAPABILITY,
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
|
)
|
||||||
from govoplan_mail.backend.documentation import (
|
from govoplan_mail.backend.documentation import (
|
||||||
documentation_configuration_states,
|
documentation_configuration_states,
|
||||||
documentation_topics,
|
documentation_topics,
|
||||||
@@ -442,7 +450,7 @@ POP3_PROVIDER = ExternalProviderDeclaration(
|
|||||||
manifest = ModuleManifest(
|
manifest = ModuleManifest(
|
||||||
id="mail",
|
id="mail",
|
||||||
name="Mail",
|
name="Mail",
|
||||||
version="0.1.22",
|
version="0.1.26",
|
||||||
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||||
optional_dependencies=("campaigns", "addresses", "calendar", "postbox", "search"),
|
optional_dependencies=("campaigns", "addresses", "calendar", "postbox", "search"),
|
||||||
provides_interfaces=(
|
provides_interfaces=(
|
||||||
@@ -559,6 +567,39 @@ manifest = ModuleManifest(
|
|||||||
order=40,
|
order=40,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
product_surfaces=(
|
||||||
|
ProductSurfaceContribution(
|
||||||
|
id="communication.messages",
|
||||||
|
module_id="mail",
|
||||||
|
label="i18n:govoplan-core.product_surface.messages",
|
||||||
|
description="i18n:govoplan-core.product_surface.messages_description",
|
||||||
|
icon="mail",
|
||||||
|
entry_path="/messages",
|
||||||
|
route_path="/mail",
|
||||||
|
surface_ids=("mail.nav.mail", "mail.route.mail"),
|
||||||
|
presentations=("task", "reader"),
|
||||||
|
search_source_ids=("mail.mailbox_messages",),
|
||||||
|
help_context_ids=("mail.quick_access.messages",),
|
||||||
|
documentation_topic_ids=("mail.quick-access-and-product-area",),
|
||||||
|
required_any=("mail:mailbox:read",),
|
||||||
|
aliases=("/inbox",),
|
||||||
|
order=10,
|
||||||
|
unavailable=ProductAvailabilityExplanation(
|
||||||
|
reason="authorization",
|
||||||
|
title="i18n:govoplan-core.product_surface.messages_unavailable",
|
||||||
|
description="i18n:govoplan-core.product_surface.messages_unavailable_description",
|
||||||
|
resolution="i18n:govoplan-core.product_surface.messages_unavailable_resolution",
|
||||||
|
responsible_role="i18n:govoplan-core.access_administrator",
|
||||||
|
),
|
||||||
|
degraded=ProductAvailabilityExplanation(
|
||||||
|
reason="provider_degraded",
|
||||||
|
title="i18n:govoplan-core.product_surface.messages_degraded",
|
||||||
|
description="i18n:govoplan-core.product_surface.messages_degraded_description",
|
||||||
|
resolution="i18n:govoplan-core.product_surface.messages_degraded_resolution",
|
||||||
|
responsible_role="i18n:govoplan-core.integration_operator",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
quick_access_tools=(
|
quick_access_tools=(
|
||||||
QuickAccessTool(
|
QuickAccessTool(
|
||||||
id="mail.messages",
|
id="mail.messages",
|
||||||
@@ -604,6 +645,7 @@ manifest = ModuleManifest(
|
|||||||
),
|
),
|
||||||
capability_factories={
|
capability_factories={
|
||||||
MAIL_CONFIGURATION_CAPABILITY: _configuration_provider,
|
MAIL_CONFIGURATION_CAPABILITY: _configuration_provider,
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY: _configuration_provider,
|
||||||
"mail.campaign_delivery": lambda context: __import__("govoplan_mail.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
|
"mail.campaign_delivery": lambda context: __import__("govoplan_mail.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
|
||||||
CAPABILITY_MAIL_DELIVERY_OUTBOX: lambda context: __import__(
|
CAPABILITY_MAIL_DELIVERY_OUTBOX: lambda context: __import__(
|
||||||
"govoplan_mail.backend.capabilities",
|
"govoplan_mail.backend.capabilities",
|
||||||
@@ -840,7 +882,9 @@ manifest = ModuleManifest(
|
|||||||
"The Mail configuration provider reads the validated mail.smtp deployment capability and derives authoritative endpoint metadata. "
|
"The Mail configuration provider reads the validated mail.smtp deployment capability and derives authoritative endpoint metadata. "
|
||||||
"Missing non-secret transport fields are collected by preflight; authentication is represented only by an existing credential-envelope id. "
|
"Missing non-secret transport fields are collected by preflight; authentication is represented only by an existing credential-envelope id. "
|
||||||
"Tenant scope is the default, system scope requires system authority, conflicting profiles are preserved unless update is explicitly reviewed, "
|
"Tenant scope is the default, system scope requires system authority, conflicting profiles are preserved unless update is explicitly reviewed, "
|
||||||
"and an unchanged second apply is a no-op. SMTP reachability remains a separate Mail profile test."
|
"and an unchanged second apply is a no-op. Before the host deployer changes or removes mail.smtp, Mail inventories every persisted SMTP endpoint, "
|
||||||
|
"legacy SMTP profile, and credential-binding count through the non-secret Ops dependency report. Missing, stale, or incomplete inventory blocks apply; "
|
||||||
|
"SMTP reachability remains a separate Mail profile test."
|
||||||
),
|
),
|
||||||
layer="configured",
|
layer="configured",
|
||||||
documentation_types=("admin",),
|
documentation_types=("admin",),
|
||||||
@@ -872,7 +916,9 @@ manifest = ModuleManifest(
|
|||||||
"Der Mail-Konfigurationsprovider liest die validierte Bereitstellungsfähigkeit mail.smtp und übernimmt deren maßgebliche Endpunktdaten. "
|
"Der Mail-Konfigurationsprovider liest die validierte Bereitstellungsfähigkeit mail.smtp und übernimmt deren maßgebliche Endpunktdaten. "
|
||||||
"Fehlende nicht geheime Transportangaben werden im Preflight abgefragt; Authentifizierung wird ausschließlich durch die ID eines vorhandenen Zugangsdaten-Umschlags referenziert. "
|
"Fehlende nicht geheime Transportangaben werden im Preflight abgefragt; Authentifizierung wird ausschließlich durch die ID eines vorhandenen Zugangsdaten-Umschlags referenziert. "
|
||||||
"Mandantenbezug ist der Standard, Systembezug erfordert Systemberechtigung, abweichende vorhandene Profile bleiben ohne ausdrücklich geprüfte Aktualisierung unverändert, "
|
"Mandantenbezug ist der Standard, Systembezug erfordert Systemberechtigung, abweichende vorhandene Profile bleiben ohne ausdrücklich geprüfte Aktualisierung unverändert, "
|
||||||
"und eine unveränderte zweite Anwendung bleibt wirkungslos. Die SMTP-Erreichbarkeit wird weiterhin separat im Mail-Profil getestet."
|
"und eine unveränderte zweite Anwendung bleibt wirkungslos. Bevor der Host-Deployer mail.smtp ändert oder entfernt, inventarisiert Mail alle gespeicherten SMTP-Endpunkte, "
|
||||||
|
"älteren SMTP-Profile und die Anzahl ihrer Zugangsdatenbindungen im nicht geheimen Ops-Abhängigkeitsbericht. Ein fehlendes, veraltetes oder unvollständiges Inventar blockiert die Anwendung; "
|
||||||
|
"die SMTP-Erreichbarkeit wird weiterhin separat im Mail-Profil getestet."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -890,6 +936,9 @@ manifest = ModuleManifest(
|
|||||||
body=(
|
body=(
|
||||||
"Mail contributes its full mailbox to Communication. When Quick Access is enabled, its compact provider surface "
|
"Mail contributes its full mailbox to Communication. When Quick Access is enabled, its compact provider surface "
|
||||||
"appears inside the shared Messages drawer alongside independent Postbox and future chat contributions. "
|
"appears inside the shared Messages drawer alongside independent Postbox and future chat contributions. "
|
||||||
|
"Mail also contributes to the stable Messages product entry: `/messages` and its migration alias `/inbox` open "
|
||||||
|
"Mail only when its mailbox route is currently authorized and visible in the effective View; otherwise another "
|
||||||
|
"available Messages owner is selected or the shell explains how access can be restored. "
|
||||||
"Recent entries open the exact authorized profile, folder, and message; Drafts resolves the configured or "
|
"Recent entries open the exact authorized profile, folder, and message; Drafts resolves the configured or "
|
||||||
"provider-detected Drafts folder, and the full Mail page keeps the versioned Quick Access return context. "
|
"provider-detected Drafts folder, and the full Mail page keeps the versioned Quick Access return context. "
|
||||||
"When an active Case requests a selection, choosing a recent message returns only its tenant-bound profile, "
|
"When an active Case requests a selection, choosing a recent message returns only its tenant-bound profile, "
|
||||||
@@ -911,6 +960,9 @@ manifest = ModuleManifest(
|
|||||||
"body": (
|
"body": (
|
||||||
"Mail ordnet das vollständige Postfach Kommunikation zu. Ist der Schnellzugriff aktiviert, erscheint die kompakte "
|
"Mail ordnet das vollständige Postfach Kommunikation zu. Ist der Schnellzugriff aktiviert, erscheint die kompakte "
|
||||||
"Mail-Oberfläche gemeinsam mit unabhängigen Beiträgen aus Postbox und künftig Chat unter Nachrichten. "
|
"Mail-Oberfläche gemeinsam mit unabhängigen Beiträgen aus Postbox und künftig Chat unter Nachrichten. "
|
||||||
|
"Mail trägt außerdem zum stabilen Produkteinstieg Nachrichten bei: `/messages` und der Migrationsalias `/inbox` "
|
||||||
|
"öffnen Mail nur, wenn die Postfachroute aktuell berechtigt und in der wirksamen View sichtbar ist. Andernfalls "
|
||||||
|
"wählt die Shell einen anderen verfügbaren Nachrichten-Eigentümer oder erklärt die Wiederherstellung des Zugriffs. "
|
||||||
"Aktuelle Einträge öffnen das genaue berechtigte Profil, den Ordner und die Nachricht; Entwürfe verwendet "
|
"Aktuelle Einträge öffnen das genaue berechtigte Profil, den Ordner und die Nachricht; Entwürfe verwendet "
|
||||||
"den konfigurierten oder vom Anbieter erkannten Entwurfsordner. Die vollständige Mail-Seite erhält den "
|
"den konfigurierten oder vom Anbieter erkannten Entwurfsordner. Die vollständige Mail-Seite erhält den "
|
||||||
"versionierten Rücksprungkontext. Verfassen öffnet bewusst die konfigurierte Mail-Anwendung des Benutzers, "
|
"versionierten Rücksprungkontext. Verfassen öffnet bewusst die konfigurierte Mail-Anwendung des Benutzers, "
|
||||||
@@ -1484,5 +1536,10 @@ manifest = ModuleManifest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
manifest = with_documentation_structured_translations(
|
||||||
|
manifest, locale="de", translations=GERMAN_STRUCTURED_TRANSLATIONS
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_manifest() -> ModuleManifest:
|
def get_manifest() -> ModuleManifest:
|
||||||
return manifest
|
return manifest
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from govoplan_core.db.session import configure_database, reset_database
|
|||||||
from govoplan_core.security.credential_envelopes import CredentialEnvelope
|
from govoplan_core.security.credential_envelopes import CredentialEnvelope
|
||||||
from govoplan_mail.backend.configuration_provider import (
|
from govoplan_mail.backend.configuration_provider import (
|
||||||
MAIL_CONFIGURATION_CAPABILITY,
|
MAIL_CONFIGURATION_CAPABILITY,
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
SqlMailConfigurationProvider,
|
SqlMailConfigurationProvider,
|
||||||
)
|
)
|
||||||
from govoplan_mail.backend.db.models import (
|
from govoplan_mail.backend.db.models import (
|
||||||
@@ -168,9 +169,30 @@ class MailConfigurationProviderTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_provider_is_registered_and_describes_receipt_bound_fragment(self) -> None:
|
def test_provider_is_registered_and_describes_receipt_bound_fragment(self) -> None:
|
||||||
self.assertIn(MAIL_CONFIGURATION_CAPABILITY, manifest.capability_factories)
|
self.assertIn(MAIL_CONFIGURATION_CAPABILITY, manifest.capability_factories)
|
||||||
|
self.assertIn(
|
||||||
|
MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY,
|
||||||
|
manifest.capability_factories,
|
||||||
|
)
|
||||||
description = self.provider.describe()
|
description = self.provider.describe()
|
||||||
self.assertEqual(("smtp_profile",), description.fragment_types)
|
self.assertEqual(("smtp_profile",), description.fragment_types)
|
||||||
|
|
||||||
|
def test_inventory_reports_actual_smtp_endpoint_and_credential_binding(self) -> None:
|
||||||
|
fragment = ConfigurationPackageFragment(
|
||||||
|
module_id="mail",
|
||||||
|
fragment_type="smtp_profile",
|
||||||
|
fragment_id="inventory-smtp",
|
||||||
|
payload={"credential_envelope_id": "credential-1"},
|
||||||
|
)
|
||||||
|
self.provider.apply(fragment, {}, self.context)
|
||||||
|
|
||||||
|
dependencies = self.provider.infrastructure_dependencies()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(dependencies))
|
||||||
|
self.assertEqual("mail.smtp", dependencies[0].capability_id)
|
||||||
|
self.assertEqual("smtp_endpoint", dependencies[0].dependency_type)
|
||||||
|
self.assertEqual(1, dependencies[0].metrics["credential_binding_count"])
|
||||||
|
self.assertNotIn("test-mail", str(dependencies[0].to_dict()))
|
||||||
|
|
||||||
def test_apply_is_idempotent_and_binds_existing_credential_envelope(self) -> None:
|
def test_apply_is_idempotent_and_binds_existing_credential_envelope(self) -> None:
|
||||||
fragment = ConfigurationPackageFragment(
|
fragment = ConfigurationPackageFragment(
|
||||||
module_id="mail",
|
module_id="mail",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.22",
|
"version": "0.1.26",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.22",
|
"version": "0.1.26",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.22",
|
"version": "0.1.26",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1495,7 +1495,7 @@ function ProfileForm({
|
|||||||
{editTarget.kind === "credentials" &&
|
{editTarget.kind === "credentials" &&
|
||||||
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
<FormGrid columns={2} gap="small" collapseAt="workspace" className="">
|
||||||
{creatingCredential &&
|
{creatingCredential &&
|
||||||
<FormField label="Reusable credential" documentation={MAIL_PROFILE_WORKFLOW_DOCUMENTATION}>
|
<FormField label="Reusable credential" documentation={MAIL_PROFILE_WORKFLOW_DOCUMENTATION} helpContextId="mail.profiles" helpModuleId="mail">
|
||||||
<select value={reuseCredentialId} disabled={credentialDisabled} onChange={(event) => setReuseCredentialId(event.target.value)}>
|
<select value={reuseCredentialId} disabled={credentialDisabled} onChange={(event) => setReuseCredentialId(event.target.value)}>
|
||||||
<option value="">Create a new credential</option>
|
<option value="">Create a new credential</option>
|
||||||
{availableCredentials.map((credential) =>
|
{availableCredentials.map((credential) =>
|
||||||
@@ -1509,13 +1509,13 @@ function ProfileForm({
|
|||||||
{creatingCredential && <div />}
|
{creatingCredential && <div />}
|
||||||
{!reuseCredentialId &&
|
{!reuseCredentialId &&
|
||||||
<>
|
<>
|
||||||
<FormField label="Credential name" documentation={MAIL_PROFILE_DOCUMENTATION}>
|
<FormField label="Credential name" documentation={MAIL_PROFILE_DOCUMENTATION} helpContextId="mail.profiles" helpModuleId="mail">
|
||||||
<input value={draft.credentialName} disabled={credentialDisabled} onChange={(event) => setDraft({ ...draft, credentialName: event.target.value })} />
|
<input value={draft.credentialName} disabled={credentialDisabled} onChange={(event) => setDraft({ ...draft, credentialName: event.target.value })} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="Description">
|
<FormField label="Description">
|
||||||
<input value={draft.credentialDescription} disabled={credentialDisabled} onChange={(event) => setDraft({ ...draft, credentialDescription: event.target.value })} />
|
<input value={draft.credentialDescription} disabled={credentialDisabled} onChange={(event) => setDraft({ ...draft, credentialDescription: event.target.value })} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="Available to modules" help="Choose installed modules. Keep Mail selected to use this credential here.">
|
<FormField label="Available to modules" help="Choose installed modules. Keep Mail selected to use this credential here." helpContextId="mail.profiles" helpModuleId="mail">
|
||||||
<ReferenceMultiSelect
|
<ReferenceMultiSelect
|
||||||
values={commaValues(draft.credentialAllowedModules)}
|
values={commaValues(draft.credentialAllowedModules)}
|
||||||
onChange={(values) => setDraft({ ...draft, credentialAllowedModules: values.join(", ") })}
|
onChange={(values) => setDraft({ ...draft, credentialAllowedModules: values.join(", ") })}
|
||||||
@@ -1527,7 +1527,9 @@ function ProfileForm({
|
|||||||
</FormField>
|
</FormField>
|
||||||
<FormField
|
<FormField
|
||||||
label="Limited to servers"
|
label="Limited to servers"
|
||||||
help="Choose an announced server or enter a module-qualified reference. Leave blank for every permitted server.">
|
help="Choose an announced server or enter a module-qualified reference. Leave blank for every permitted server."
|
||||||
|
helpContextId="mail.profiles"
|
||||||
|
helpModuleId="mail">
|
||||||
<ReferenceMultiSelect
|
<ReferenceMultiSelect
|
||||||
values={commaValues(draft.credentialAllowedServerRefs)}
|
values={commaValues(draft.credentialAllowedServerRefs)}
|
||||||
onChange={(values) => setDraft({ ...draft, credentialAllowedServerRefs: values.join(", ") })}
|
onChange={(values) => setDraft({ ...draft, credentialAllowedServerRefs: values.join(", ") })}
|
||||||
@@ -1540,6 +1542,8 @@ function ProfileForm({
|
|||||||
</FormField>
|
</FormField>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={draft.credentialIsDefault}
|
checked={draft.credentialIsDefault}
|
||||||
|
helpContextId="mail.profiles"
|
||||||
|
helpModuleId="mail"
|
||||||
disabled={credentialDisabled}
|
disabled={credentialDisabled}
|
||||||
onChange={(checked) => setDraft({ ...draft, credentialIsDefault: checked })}
|
onChange={(checked) => setDraft({ ...draft, credentialIsDefault: checked })}
|
||||||
label={`Default credential for this ${editTarget.protocol.toUpperCase()} server`} />
|
label={`Default credential for this ${editTarget.protocol.toUpperCase()} server`} />
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
import { createElement, lazy } from "react";
|
import { createElement, lazy } from "react";
|
||||||
import type { MailDevMailboxUiCapability, MailProfilesUiCapability, PlatformWebModule, QuickAccessToolsUiCapability } from "@govoplan/core-webui";
|
import { messagesProductSurfaceTranslations, type MailDevMailboxUiCapability, type MailProfilesUiCapability, type PlatformWebModule, type QuickAccessToolsUiCapability } from "@govoplan/core-webui";
|
||||||
import { MailProfilePolicyEditor, MailProfileScopeManager } from "./features/mail/MailProfileManagement";
|
import { MailProfilePolicyEditor, MailProfileScopeManager } from "./features/mail/MailProfileManagement";
|
||||||
import { validateMailPolicy } from "./features/mail/mailPolicyValidation";
|
import { validateMailPolicy } from "./features/mail/mailPolicyValidation";
|
||||||
import { mailCredentialReferenceSelectors } from "./features/mail/mailReferenceProviders";
|
import { mailCredentialReferenceSelectors } from "./features/mail/mailReferenceProviders";
|
||||||
@@ -14,8 +14,8 @@ const mailboxRead = ["mail:mailbox:read"];
|
|||||||
const bounceRead = ["mail:bounce:read", "mail:bounce:manage"];
|
const bounceRead = ["mail:bounce:read", "mail:bounce:manage"];
|
||||||
const legacyImportAccess = ["mail:pop3:import", "mail:pop3:manage"];
|
const legacyImportAccess = ["mail:pop3:import", "mail:pop3:manage"];
|
||||||
const translations = {
|
const translations = {
|
||||||
en: generatedTranslations.en,
|
en: { ...generatedTranslations.en, ...messagesProductSurfaceTranslations.en },
|
||||||
de: generatedTranslations.de
|
de: { ...generatedTranslations.de, ...messagesProductSurfaceTranslations.de }
|
||||||
};
|
};
|
||||||
const mailQuickAccessTools: QuickAccessToolsUiCapability = {
|
const mailQuickAccessTools: QuickAccessToolsUiCapability = {
|
||||||
tools: [
|
tools: [
|
||||||
|
|||||||
Reference in New Issue
Block a user