Initialer Import des AssetManagers

This commit is contained in:
2026-08-02 21:28:50 +00:00
commit ed72ce8821
256 changed files with 26361 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
{% extends 'base.html' %}{% block content %}
<div class="toolbar"><h1>{{t('mesh.title')}}</h1><button id="sync-start">{{t('mesh.start')}}</button></div>
<div class="cards"><div class="card"><strong>{{linked}}</strong><span>{{t('mesh.linked_assets')}}</span></div><div class="card"><strong>{{conflicts}}</strong><span>{{t('mesh.conflicts')}}</span></div><div class="card"><strong>{{missing}}</strong><span>{{t('mesh.missing')}}</span></div></div>
<section class="panel"><div class="log-header"><h2>{{t('mesh.live_log')}}</h2><span id="sync-state" class="sync-status">{{t('mesh.ready')}}</span></div><div id="sync-log" class="sync-log"><div class="log-line log-info">{{t('mesh.ready_message')}}</div></div></section>
<section class="panel">
<h2>1. {{ t('mesh.connection') }}</h2>
<p class="muted">{{ t('mesh.password_env_help') }}</p>
<form method="post" action="/sync/meshcentral/settings" class="compact-form">
<div class="form-grid">
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="enabled" {% if config.meshcentral.enabled %}checked{% endif %}> {{ t('common.active') }}</label>
<label>WebSocket-URL<input name="url" value="{{config.meshcentral.url or ''}}" placeholder="wss://meshcentral.example.org:443"></label>
<label>{{ t('mesh.username') }}<input name="username" value="{{config.meshcentral.username or ''}}"></label>
<label>{{ t('mesh.password_env') }}<input name="password_env" value="{{config.meshcentral.password_env or 'MESHCENTRAL_PASSWORD'}}"></label>
<label>Tenant<input name="tenant" value="{{config.meshcentral.tenant or ''}}"></label>
<label>meshctrl.js-Pfad<input name="meshctrl_path" value="{{config.meshcentral.meshctrl_path}}"></label>
<label>{{ t('mesh.timeout_seconds') }}<input type="number" min="10" max="3600" name="timeout_seconds" value="{{config.meshcentral.timeout_seconds or 600}}"></label>
</div>
<div class="checkbox-row">
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="verify_tls" {% if config.meshcentral.verify_tls %}checked{% endif %}> {{ t('mesh.verify_tls') }}</label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="include_details" {% if config.meshcentral.get('include_details',true) %}checked{% endif %}> {{ t('mesh.include_details') }}</label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="create_missing_assets" {% if config.meshcentral.create_missing_assets %}checked{% endif %}> {{ t('mesh.create_missing_assets') }}</label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="mark_missing_devices" {% if config.meshcentral.mark_missing_devices %}checked{% endif %}> {{ t('mesh.mark_missing_devices') }}</label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="store_source_json" {% if config.meshcentral.get('store_source_json',true) %}checked{% endif %}> {{ t('mesh.store_source_json') }}</label>
</div>
<h3>{{ t('mesh.asset_matching') }}</h3><div class="checkbox-row"><label class="checkbox-label"><input class="checkbox" type="checkbox" name="match_order" value="node_id" {% if 'node_id' in config.meshcentral.match_order %}checked{% endif %}> Node-ID</label><label class="checkbox-label"><input class="checkbox" type="checkbox" name="match_order" value="serial_number" {% if 'serial_number' in config.meshcentral.match_order %}checked{% endif %}> Seriennummer</label></div>
<button class="button button-save">💾 {{ t('mesh.save_connection') }}</button>
</form>
</section>
<section class="panel">
<h2>2. {{ t('mesh.field_mappings') }}</h2>
<p class="muted">{{ t('mesh.mappings_help') }} {{ t('mesh.mappings_examples') }}: <code>node.osdesc</code>, <code>sys.hardware.windows.cpu[].Name</code>, <code>node.name|name</code>.</p>
<form method="post" action="/sync/meshcentral/mappings/save">
<div class="table-scroll management-table-scroll"><table class="data-table mesh-mapping-table"><thead><tr><th>{{ t('common.active') }}</th><th>{{ t('mesh.priority') }}</th><th>{{ t('mesh.target_field') }}</th><th>Source Path</th><th>{{ t('mesh.transformation') }}</th><th>{{ t('mesh.multi_values') }}</th><th>{{ t('mesh.separator') }}</th><th>{{ t('mesh.update_rule') }}</th><th>{{ t('common.description') }}</th><th></th></tr></thead><tbody>
{% for m in mappings %}
<tr>
<td><input type="checkbox" name="mapping_{{m.id}}_enabled" {% if m.enabled %}checked{% endif %}></td>
<td><input class="mapping-priority" type="number" name="mapping_{{m.id}}_priority" value="{{m.priority}}"></td>
<td data-filter-value="{% for d in definitions %}{% if d.field_name==m.target_field_name %}{{ d.label|e }} {{ d.field_name|e }} {{ 'System' if d.is_system else 'Custom' }}{% endif %}{% endfor %}"><select name="mapping_{{m.id}}_target_field_name">{% for d in definitions %}<option value="{{d.field_name}}" {% if d.field_name==m.target_field_name %}selected{% endif %}>{{d.label}} ({{d.field_name}}) {{'System' if d.is_system else 'Custom'}}</option>{% endfor %}</select>{% if m.is_system %}<small class="muted">{{ t('mesh.system_mapping') }}</small>{% else %}<small class="muted">{{ t('mesh.custom_mapping') }}</small>{% endif %}</td>
<td><input class="mapping-path" name="mapping_{{m.id}}_source_path" value="{{m.source_path}}" required></td>
<td data-filter-value="{{ m.transform|e }}"><select name="mapping_{{m.id}}_transform">{% for v in transform_options %}<option value="{{v}}" {% if m.transform==v %}selected{% endif %}>{{v}}</option>{% endfor %}</select></td>
<td><select name="mapping_{{m.id}}_multi_value_mode">{% for v in ['first','join','json','count'] %}<option value="{{v}}" {% if m.multi_value_mode==v %}selected{% endif %}>{{v}}</option>{% endfor %}</select></td>
<td><input class="mapping-separator" name="mapping_{{m.id}}_separator" value="{{m.separator|replace('\n','\\n')|replace('\t','\\t')}}"></td>
<td><select name="mapping_{{m.id}}_update_rule"><option value="fill_empty" {% if m.update_rule=='fill_empty' %}selected{% endif %}>{{ t('mesh.rule_fill_empty') }}</option><option value="meshcentral_wins" {% if m.update_rule=='meshcentral_wins' %}selected{% endif %}>{{ t('mesh.rule_mesh_wins') }}</option><option value="local_wins" {% if m.update_rule=='local_wins' %}selected{% endif %}>{{ t('mesh.rule_local_wins') }}</option></select></td>
<td><input name="mapping_{{m.id}}_description" value="{{m.description or ''}}"></td>
<td>{% if not m.is_system %}<button class="button danger" type="submit" formaction="/sync/meshcentral/mappings/{{m.id}}/delete" formmethod="post" onclick="return confirm({{ t('mesh.delete_mapping_confirm')|tojson }})">{{ t('common.delete') }}</button>{% endif %}</td>
</tr>
{% endfor %}
</tbody></table></div>
<button class="button button-save">💾 {{ t('mesh.save_mappings') }}</button>
</form>
</section>
<section class="panel">
<h2>3. {{ t('mesh.new_mapping') }}</h2>
<p class="muted">{{ t('mesh.new_mapping_help') }}</p>
<form method="post" action="/sync/meshcentral/mappings/new" class="compact-form">
<div class="form-grid">
<label>Zielfeld<select name="target_field_name" required><option value="">{{ t('common.select') }}</option>{% for d in definitions %}<option value="{{d.field_name}}">{{d.label}} ({{d.field_name}}) {{'System' if d.is_system else 'Custom'}}</option>{% endfor %}</select></label>
<label>Source Path<input name="source_path" required placeholder="node.example.path"></label>
<label>Transformation<select name="transform">{% for v in transform_options %}<option value="{{v}}">{{v}}</option>{% endfor %}</select></label>
<label>Mehrfachwerte<select name="multi_value_mode"><option value="first">first</option><option value="join">join</option><option value="json">json</option><option value="count">count</option></select></label>
<label>Trennzeichen<input name="separator" value="\n"></label>
<label>Update-Regel<select name="update_rule"><option value="fill_empty">{{ t('mesh.rule_fill_empty') }}</option><option value="meshcentral_wins">{{ t('mesh.rule_mesh_wins') }}</option><option value="local_wins">{{ t('mesh.rule_local_wins') }}</option></select></label>
<label>Beschreibung<input name="description"></label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="enabled" checked> {{ t('common.active') }}</label>
</div><button class="button button-save"> {{ t('mesh.create_mapping') }}</button>
</form>
</section>
<section class="panel"><h2>{{t('mesh.recent_runs')}}</h2><table><thead><tr><th>{{t('mesh.start_time')}}</th><th>{{t('assets.status')}}</th><th>{{t('mesh.found')}}</th><th>{{t('mesh.created')}}</th><th>{{t('mesh.changed')}}</th><th>{{t('mesh.conflicts')}}</th><th>{{t('mesh.errors')}}</th><th>{{t('mesh.message')}}</th></tr></thead><tbody>{% for run in runs %}<tr><td><time class="local-time" datetime="{{ utc_iso(run.started_at) }}" data-utc="{{ utc_iso(run.started_at) }}">{{ run.started_at }}</time></td><td>{{run.status}}</td><td>{{run.devices_found}}</td><td>{{run.created_count}}</td><td>{{run.updated_count}}</td><td>{{run.conflict_count}}</td><td>{{run.error_count}}</td><td>{{run.message or ''}}</td></tr>{% else %}<tr><td colspan="8">{{t('mesh.no_runs')}}</td></tr>{% endfor %}</tbody></table></section>
<script>(()=>{const button=document.getElementById('sync-start'),logBox=document.getElementById('sync-log'),state=document.getElementById('sync-state');let offset=0,timer=null;function addLog(e){const l=document.createElement('div');l.className=`log-line log-${e.level||'info'}`;const localTime=window.AssetManagerTime?.formatUtc(e.time,e.time||'--')||(e.time||'--');l.textContent=`[${localTime}] ${e.message}`;logBox.appendChild(l);logBox.scrollTop=logBox.scrollHeight}async function jsonResponse(response,fallback){const text=await response.text();let data;try{data=JSON.parse(text)}catch(_){throw new Error(`${fallback} (HTTP ${response.status}): ${text.slice(0,300)||'{{ t('mesh.empty_response') }}'}`)}if(!response.ok)throw new Error(data.error||data.detail||fallback);return data}async function poll(id){try{const r=await fetch(`/sync/meshcentral/jobs/${id}?offset=${offset}`,{cache:'no-store'}),d=await jsonResponse(r,'{{ t('mesh.log_load_error') }}');d.logs.forEach(addLog);offset=d.next_offset;state.textContent=d.status;if(d.finished){clearInterval(timer);button.disabled=false}}catch(e){clearInterval(timer);button.disabled=false;addLog({level:'error',message:e.message})}}button.onclick=async()=>{button.disabled=true;logBox.innerHTML='';offset=0;try{const r=await fetch('/sync/meshcentral/start',{method:'POST'}),d=await jsonResponse(r,'{{ t('mesh.start_error') }}');await poll(d.job_id);timer=setInterval(()=>poll(d.job_id),800)}catch(e){addLog({level:'error',message:e.message});button.disabled=false}}})();</script>
{% endblock %}