seperate port and container for external callback
This commit is contained in:
+40
@@ -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"),
|
||||
|
||||
+12
@@ -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"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{% extends 'base.html' %}{% block content %}<h1>{% if definition %}Edit field{% else %}Add field{% endif %}</h1>
|
||||
{% extends 'base.html' %}{% block content %}<h1>{% if definition %}{{ t('fields.edit') }}{% else %}{{ t('fields.add') }}{% endif %}</h1>
|
||||
<form method="post" class="settings-form">
|
||||
{% if not definition %}<label>Technical field name<input name="field_name" pattern="[a-zA-Z0-9_]+" required><small>Letters, numbers and underscores only.</small></label>{% else %}<label>Technical field name<input value="{{definition.field_name}}" readonly></label>{% endif %}
|
||||
<label>Label<input name="label" value="{{definition.label if definition else ''}}" required></label><label>Description<textarea name="description">{{definition.description or '' if definition else ''}}</textarea></label>
|
||||
<label>Data type<select name="data_type">{% for key,label in data_types.items() %}<option value="{{key}}" {% if definition and definition.data_type==key %}selected{% endif %}>{{label}}</option>{% endfor %}</select></label><label>{{ t('fields.list_width', 'Breite in Listen (Pixel)') }}
|
||||
<input type="number" name="list_width" min="40" max="800" step="10" value="{{ definition.list_width if definition and definition.list_width else '' }}" placeholder="automatisch">
|
||||
<small>{{ t('fields.list_width_hint', 'Leer = automatische Breite. Sinnvoll sind etwa 60 für kurze Werte und 180–300 für längere Texte.') }}</small>
|
||||
{% if not definition %}<label>{{ t('fields.technical_name') }}<input name="field_name" pattern="[a-zA-Z0-9_]+" required><small>{{ t('fields.technical_name_help') }}</small></label>{% else %}<label>{{ t('fields.technical_name') }}<input value="{{definition.field_name}}" readonly></label>{% endif %}
|
||||
<label>{{ t('fields.label') }}<input name="label" value="{{definition.label if definition else ''}}" required></label><label>{{ t('common.description') }}<textarea name="description">{{definition.description or '' if definition else ''}}</textarea></label>
|
||||
<label>{{ t('fields.data_type') }}<select name="data_type">{% for key,label in data_types.items() %}<option value="{{key}}" {% if definition and definition.data_type==key %}selected{% endif %}>{{ t('fields.data_type_' ~ key, label) }}</option>{% endfor %}</select></label><label>{{ t('fields.list_width') }}
|
||||
<input type="number" name="list_width" min="40" max="800" step="10" value="{{ definition.list_width if definition and definition.list_width else '' }}" placeholder="{{ t('fields.automatic') }}">
|
||||
<small>{{ t('fields.list_width_hint') }}</small>
|
||||
</label>
|
||||
<div class="checkbox-row"><label class="checkbox-label"><input class="checkbox" type="checkbox" name="readonly" {% if definition and definition.readonly %}checked{% endif %}{% if definition and definition.field_name in ['mesh_node_id','mesh_mtype'] %} disabled{% endif %}> Read only</label>{% if definition and not definition.is_system %}<label class="checkbox-label"><input class="checkbox" type="checkbox" name="is_active" {% if definition.is_active %}checked{% endif %}> Active</label>{% endif %}<label class="checkbox-label"><input class="checkbox" type="checkbox" name="is_default" {% if definition and definition.is_default %}checked{% endif %}> Standardfeld</label></div>
|
||||
<div class="checkbox-row">{% for key,label in [('searchable','Searchable'),('filterable','Filterable'),('chart_enabled','Available for charts'),('excel_import','Excel import'),('excel_export','Excel export'),('bulk_edit','Bulk edit')] %}<label class="checkbox-label"><input class="checkbox" type="checkbox" name="{{key}}" {% if not definition or definition|attr(key) %}checked{% endif %}> {{label}}</label>{% endfor %}</div>
|
||||
<div class="checkbox-row"><label class="checkbox-label"><input class="checkbox" type="checkbox" name="readonly" {% if definition and definition.readonly %}checked{% endif %}{% if definition and definition.field_name in ['mesh_node_id','mesh_mtype','mesh_icon'] %} disabled{% endif %}> {{ t('fields.read_only') }}</label>{% if definition and not definition.is_system %}<label class="checkbox-label"><input class="checkbox" type="checkbox" name="is_active" {% if definition.is_active %}checked{% endif %}> {{ t('common.active') }}</label>{% endif %}<label class="checkbox-label"><input class="checkbox" type="checkbox" name="is_default" {% if definition and definition.is_default %}checked{% endif %}> {{ t('fields.default_field') }}</label></div>
|
||||
<div class="checkbox-row">{% for key,label_key in [('searchable','fields.searchable'),('filterable','fields.filterable'),('chart_enabled','fields.chart_enabled'),('excel_import','fields.excel_import'),('excel_export','fields.excel_export'),('bulk_edit','fields.bulk_edit')] %}<label class="checkbox-label"><input class="checkbox" type="checkbox" name="{{key}}" {% if not definition or definition|attr(key) %}checked{% endif %}> {{ t(label_key) }}</label>{% endfor %}</div>
|
||||
<button class="button button-save">💾 {{t('common.save')}}</button></form>{% endblock %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends 'base.html' %}{% block content %}
|
||||
<div class="toolbar"><h1>Fields</h1><div class="toolbar-actions"><a class="button" href="/settings/fields/new">➕ Add field</a><a class="button button-secondary" href="/settings">{{ t('common.back') }}</a></div></div>
|
||||
<p class="muted">Central labels and data types apply immediately to all categories, forms, tables and exports.</p>
|
||||
<div class="sticky-table management-table-scroll"><table class="data-table"><thead><tr><th>Field</th><th>Label</th><th>Description</th><th>Data type</th><th>Read only</th><th>System</th><th>Stored values</th><th>{{ t('common.actions') }}</th></tr></thead><tbody>
|
||||
{% for d in definitions %}<tr><td><code>{{d.field_name}}</code></td><td>{{d.label}}</td><td>{{d.description or ''}}</td><td>{{data_types.get(d.data_type,d.data_type)}}</td><td>{{t('common.yes') if d.readonly else t('common.no')}}</td><td>{{t('common.yes') if d.is_system else t('common.no')}}</td><td>{{value_counts.get(d.id,0)}}</td><td><a class="button button-secondary" href="/settings/fields/{{d.id}}/edit">{{t('common.edit')}}</a>{% if not d.is_system %}<form class="inline-form" method="post" action="/settings/fields/{{d.id}}/delete" onsubmit="return confirm('Delete this field and all values?');"><label class="checkbox-label"><input class="checkbox" type="checkbox" name="confirm_values">Confirm values</label><button class="button button-danger">{{t('common.delete')}}</button></form>{% endif %}</td></tr>{% endfor %}
|
||||
<div class="toolbar"><h1>{{ t('fields.title') }}</h1><div class="toolbar-actions"><a class="button" href="/settings/fields/new">➕ {{ t('fields.add') }}</a><a class="button button-secondary" href="/settings">{{ t('common.back') }}</a></div></div>
|
||||
<p class="muted">{{ t('fields.catalog_help') }}</p>
|
||||
<div class="sticky-table management-table-scroll"><table class="data-table"><thead><tr><th>{{ t('fields.field') }}</th><th>{{ t('fields.label') }}</th><th>{{ t('common.description') }}</th><th>{{ t('fields.data_type') }}</th><th>{{ t('fields.read_only') }}</th><th>{{ t('fields.system') }}</th><th>{{ t('fields.stored_values') }}</th><th>{{ t('common.actions') }}</th></tr></thead><tbody>
|
||||
{% for d in definitions %}<tr><td><code>{{d.field_name}}</code></td><td>{{d.label}}</td><td>{{d.description or ''}}</td><td>{{ t('fields.data_type_' ~ d.data_type, data_types.get(d.data_type,d.data_type)) }}</td><td>{{t('common.yes') if d.readonly else t('common.no')}}</td><td>{{t('common.yes') if d.is_system else t('common.no')}}</td><td>{{value_counts.get(d.id,0)}}</td><td><a class="button button-secondary" href="/settings/fields/{{d.id}}/edit">{{t('common.edit')}}</a>{% if not d.is_system %}<form class="inline-form" method="post" action="/settings/fields/{{d.id}}/delete" onsubmit="return confirm({{ t('fields.delete_confirm')|tojson }});"><label class="checkbox-label"><input class="checkbox" type="checkbox" name="confirm_values">{{ t('fields.confirm_values') }}</label><button class="button button-danger">{{t('common.delete')}}</button></form>{% endif %}</td></tr>{% endfor %}
|
||||
</tbody></table></div>{% endblock %}
|
||||
|
||||
@@ -129,6 +129,16 @@
|
||||
<span>{{ t('software.settings.health_url') }}</span>
|
||||
<code id="callback-health-preview">{{ callback_health_url or t('software.settings.automatic_url') }}</code>
|
||||
</div>
|
||||
|
||||
<div class="software-callback-preview software-settings-wide">
|
||||
<span>{{ t('software.settings.dedicated_listener') }}</span>
|
||||
<div>
|
||||
<strong>{{ t('software.settings.dedicated_listener_optional') }}</strong>
|
||||
<p class="muted">{{ t('software.settings.dedicated_listener_help', port=callback_dedicated_port) }}</p>
|
||||
<code>CALLBACK_PORT={{ callback_dedicated_port }}</code>
|
||||
<code>docker compose --profile callback up -d callback</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = "0.5.5.63"
|
||||
APP_VERSION = "0.5.5.64"
|
||||
__version__ = APP_VERSION
|
||||
|
||||
Reference in New Issue
Block a user