diff --git a/.env.example b/.env.example index 299ee14..e378df8 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,6 @@ LOCAL_ADMIN_USERNAME=emergency-admin LOCAL_ADMIN_PASSWORD=CHANGE_ME_MIN_12_CHARS BACKUP_INTERVAL_HOURS=8 BACKUP_RETENTION_DAYS=3 + +# Optional host port for the callback-only Docker Compose profile. +CALLBACK_PORT=8090 diff --git a/README.md b/README.md index 1080668..57db8a0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AssetManager 0.5.5.59 +# AssetManager 0.5.5.64 AssetManager is a self-hosted web application for managing IT equipment and other organizational assets. It provides asset inventory, software inventory, remote job execution, reporting, privacy/retention documentation, and optional integration with MeshCentral. diff --git a/VERSION b/VERSION index 8b1ae97..4d1b92d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.5.63 +0.5.5.64 diff --git a/app/i18n.py b/app/i18n.py index 6b2d311..9929fe5 100644 --- a/app/i18n.py +++ b/app/i18n.py @@ -14,6 +14,46 @@ BASE_TRANSLATIONS = { "common.import": ("Import", "Importieren"), "common.back": ("Back", "Zurück"), "common.yes": ("Yes", "Ja"), "common.no": ("No", "Nein"), "common.active": ("Active", "Aktiv"), "common.actions": ("Actions", "Aktionen"), "common.name": ("Name", "Name"), "common.description": ("Description", "Beschreibung"), "common.language": ("Language", "Sprache"), "common.all": ("All", "Alle"), + +"fields.title": ("Fields", "Felder"), +"fields.add": ("Add field", "Feld hinzufügen"), +"fields.edit": ("Edit field", "Feld bearbeiten"), +"fields.catalog_help": ("Central labels and data types apply immediately to all categories, forms, tables and exports.", "Zentrale Bezeichnungen und Datentypen gelten sofort für alle Kategorien, Formulare, Tabellen und Exporte."), +"fields.field": ("Field", "Feld"), +"fields.label": ("Label", "Bezeichnung"), +"fields.data_type": ("Data type", "Datentyp"), +"fields.read_only": ("Read only", "Nur lesen"), +"fields.system": ("System", "System"), +"fields.stored_values": ("Stored values", "Gespeicherte Werte"), +"fields.delete_confirm": ("Delete this field and all values?", "Dieses Feld und alle gespeicherten Werte löschen?"), +"fields.confirm_values": ("Confirm values", "Werte bestätigen"), +"fields.technical_name": ("Technical field name", "Technischer Feldname"), +"fields.technical_name_help": ("Letters, numbers and underscores only.", "Nur Buchstaben, Zahlen und Unterstriche."), +"fields.automatic": ("automatic", "automatisch"), +"fields.default_field": ("Default field", "Standardfeld"), +"fields.searchable": ("Searchable", "Durchsuchbar"), +"fields.filterable": ("Filterable", "Filterbar"), +"fields.chart_enabled": ("Available for charts", "Für Diagramme verfügbar"), +"fields.excel_import": ("Excel import", "Excel-Import"), +"fields.excel_export": ("Excel export", "Excel-Export"), +"fields.bulk_edit": ("Bulk edit", "Sammelbearbeitung"), +"fields.data_type_short_text": ("Short text", "Kurztext"), +"fields.data_type_long_text": ("Long text", "Langtext"), +"fields.data_type_integer": ("Integer", "Ganzzahl"), +"fields.data_type_decimal": ("Decimal number", "Dezimalzahl"), +"fields.data_type_boolean": ("Boolean", "Ja/Nein"), +"fields.data_type_date": ("Date", "Datum"), +"fields.data_type_datetime": ("Date and time", "Datum und Uhrzeit"), +"fields.data_type_url": ("URL", "URL"), +"fields.data_type_email": ("E-mail address", "E-Mail-Adresse"), +"fields.data_type_ip": ("IP address", "IP-Adresse"), +"fields.data_type_json": ("JSON", "JSON"), +"fields.data_type_asset_reference": ("Asset reference", "Asset-Verweis"), +"fields.data_type_user_reference": ("User reference", "Benutzer-Verweis"), +"fields.data_type_status": ("Status", "Status"), +"software.settings.dedicated_listener": ("Dedicated callback listener", "Separater Callback-Zugang"), +"software.settings.dedicated_listener_optional": ("Optional – callback API only", "Optional – nur Callback-API"), +"software.settings.dedicated_listener_help": ("For Internet/DMZ scenarios, the callback API can run as a separate Docker Compose service on host port {port}. This listener exposes only the callback endpoint and health check, never the AssetManager web interface. Configure the public FQDN above as the callback base URL.", "Für Internet-/DMZ-Szenarien kann die Callback-API als eigener Docker-Compose-Dienst auf Host-Port {port} laufen. Dieser Zugang stellt ausschließlich den Callback-Endpunkt und den Healthcheck bereit, niemals die AssetManager-Weboberfläche. Trage oben den öffentlichen FQDN als Callback-Basisadresse ein."), "profile.title": ("User profile", "Benutzerprofil"), "profile.username": ("Username", "Benutzername"), "profile.display_name": ("Display name", "Anzeigename"), "profile.email": ("Email", "E-Mail"), "profile.source": ("Source", "Quelle"), "profile.last_login": ("Last sign-in", "Letzte Anmeldung"), "profile.change_password": ("Change password", "Passwort ändern"), diff --git a/app/main.py b/app/main.py index 231e23a..fe78523 100644 --- a/app/main.py +++ b/app/main.py @@ -212,6 +212,15 @@ if not ldap_logger.handlers: ldap_logger.addHandler(ldap_console_handler) app = FastAPI(title=os.getenv("APP_TITLE", "AssetManager")) +# Dedicated callback-only ASGI application. It deliberately exposes only the +# software callback endpoint and its health endpoint so deployments may publish +# callbacks through a separate port/FQDN without exposing the AssetManager UI. +callback_app = FastAPI( + title=f"{os.getenv('APP_TITLE', 'AssetManager')} Callback", + docs_url=None, + redoc_url=None, + openapi_url=None, +) _session_cfg = load_config().get("authentication", {}) _session_env = _session_cfg.get("session_secret_env", "SESSION_SECRET") _session_secret = os.getenv(_session_env) or os.getenv("SESSION_SECRET") or "assetmanager-change-this-session-secret" @@ -4870,6 +4879,7 @@ def category_delete(category_id: int, request: Request, db: Session = Depends(ge @app.get("/api/software-callback/health") +@callback_app.get("/api/software-callback/health") def software_callback_health(): return { "ok": True, @@ -6397,6 +6407,7 @@ def _process_software_job_callback( @app.post("/api/software-jobs/{job_id}/callback") +@callback_app.post("/api/software-jobs/{job_id}/callback") async def software_job_callback(job_id: int, request: Request, token: str = ""): client_host = request.client.host if request.client else "-" content_type = request.headers.get("content-type", "-") @@ -7775,6 +7786,7 @@ def settings_software_page(request: Request): "callback_health_url": f"{callback_base}/api/software-callback/health" if callback_base else "", "debug_log": _software_debug_tail(), "debug_log_path": str(SOFTWARE_CALLBACK_DEBUG_LOG), + "callback_dedicated_port": str(os.getenv("CALLBACK_PORT", "8090") or "8090"), }, ) diff --git a/app/templates/field_definition_form.html b/app/templates/field_definition_form.html index 5971f9b..b22ace7 100644 --- a/app/templates/field_definition_form.html +++ b/app/templates/field_definition_form.html @@ -1,11 +1,11 @@ -{% extends 'base.html' %}{% block content %}
Central labels and data types apply immediately to all categories, forms, tables and exports.
-| Field | Label | Description | Data type | Read only | System | Stored values | {{ t('common.actions') }} |
|---|---|---|---|---|---|---|---|
{{d.field_name}} | {{d.label}} | {{d.description or ''}} | {{data_types.get(d.data_type,d.data_type)}} | {{t('common.yes') if d.readonly else t('common.no')}} | {{t('common.yes') if d.is_system else t('common.no')}} | {{value_counts.get(d.id,0)}} | {{t('common.edit')}}{% if not d.is_system %}{% endif %} |
| {{ t('fields.field') }} | {{ t('fields.label') }} | {{ t('common.description') }} | {{ t('fields.data_type') }} | {{ t('fields.read_only') }} | {{ t('fields.system') }} | {{ t('fields.stored_values') }} | {{ t('common.actions') }} |
|---|---|---|---|---|---|---|---|
{{d.field_name}} | {{d.label}} | {{d.description or ''}} | {{ t('fields.data_type_' ~ d.data_type, data_types.get(d.data_type,d.data_type)) }} | {{t('common.yes') if d.readonly else t('common.no')}} | {{t('common.yes') if d.is_system else t('common.no')}} | {{value_counts.get(d.id,0)}} | {{t('common.edit')}}{% if not d.is_system %}{% endif %} |
{{ callback_health_url or t('software.settings.automatic_url') }}
+
+ {{ t('software.settings.dedicated_listener_help', port=callback_dedicated_port) }}
+CALLBACK_PORT={{ callback_dedicated_port }}
+ docker compose --profile callback up -d callback
+