Files
Assetmanager/app/templates/meshcentral_sync.html
T

76 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}