fmeshsync fix with synology,vm's and lxc

This commit is contained in:
root
2026-08-08 19:44:27 +02:00
parent 2786f12ab6
commit 5e32be7d0c
41 changed files with 1335 additions and 1674 deletions
+19 -1
View File
@@ -36,6 +36,24 @@
</div>
</div>
<div class="filters"><a href="/assets">{{ t('common.all', 'Alle') }}</a>{% for c in categories %}<a href="/assets?category_id={{ c.id }}">{{ c.name }}</a>{% endfor %}</div>
<div id="asset-context-filter-status" class="table-filter-status asset-context-filter-status{% if selected %} is-active{% endif %}" {% if not selected %}hidden{% endif %} role="status" aria-live="polite"
data-active-label="{{ t('assets.active_selection', 'Aktive Auswahl')|e }}"
data-duplicate-template="{{ t('duplicates.active_filter', 'Duplikate: {field}')|e }}">
<span class="table-filter-status-icon" aria-hidden="true"></span>
<strong class="table-filter-status-label">{{ t('assets.active_selection', 'Aktive Auswahl') }}</strong>
<span class="table-filter-status-details asset-context-filter-details">
{% if selected %}
<span class="asset-filter-chip" id="asset-category-filter-chip">
{{ t('assets.category_filter', 'Kategorie: {category}').format(category=selected.name) }}
<button type="button" class="asset-filter-chip-remove" id="asset-category-filter-remove" title="{{ t('assets.clear_category_filter', 'Kategoriefilter entfernen')|e }}" aria-label="{{ t('assets.clear_category_filter', 'Kategoriefilter entfernen')|e }}">×</button>
</span>
{% endif %}
<span class="asset-filter-chip" id="asset-duplicate-filter-chip" hidden>
<span id="asset-duplicate-filter-label"></span>
<button type="button" class="asset-filter-chip-remove" id="asset-duplicate-filter-remove" title="{{ t('duplicates.remove_filter', 'Dublettenfilter entfernen')|e }}" aria-label="{{ t('duplicates.remove_filter', 'Dublettenfilter entfernen')|e }}">×</button>
</span>
</span>
</div>
{% if job_filter %}
<div class="notice job-filter-summary">
<strong>{{ t('assets.job_filter.active', 'Aktiver Jobfilter') }}:</strong>
@@ -193,7 +211,7 @@
</dialog>
{% endif %}
<dialog id="duplicate-search-dialog" class="duplicate-search-dialog">
<dialog id="duplicate-search-dialog" class="duplicate-search-dialog" data-result-summary-template="{{ t('duplicates.result_summary', '{records} Datensätze in {groups} Dublettengruppen gefunden.')|e }}">
<form method="dialog">
<div class="dialog-heading">
<h2>{{ t('duplicates.search_title') }}</h2>
+8 -2
View File
@@ -14,7 +14,7 @@
--am-warning:{{ colors.get('warning_color', '#d97706') }};
--am-danger:{{ colors.get('danger_color', '#b42318') }};
}
</style><script>(()=>{try{if(localStorage.getItem('assetmanager.sidebar.collapsed')==='true')document.documentElement.classList.add('sidebar-collapsed-preset')}catch(_){}})();</script><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ title or general.get('title', 'AssetManager') }}</title>{% set favicon_path = general.get('favicon') or general.get('logo') %}{% if favicon_path %}<link rel="icon" href="{{ favicon_path }}">{% endif %}<link rel="stylesheet" href="/static/css/app.css?v={{ application_version() }}"><link rel="stylesheet" href="/custom.css"></head><body{% if request and request.url.path == '/assets' %} class="asset-list-scroll-page"{% endif %} data-record-count-template="{{ t('lists.record_count', 'Angezeigt: {visible} von {total} Datensätzen')|e }}" data-record-count-title="{{ t('lists.record_count_hint', 'Die Anzeige berücksichtigt die aktuell gesetzten Tabellenfilter.')|e }}" data-filter-active-label="{{ t('lists.filter_active', 'Filter aktiv')|e }}" data-filters-active-template="{{ t('lists.filters_active', '{count} Filter aktiv')|e }}" data-filter-status-template="{{ t('lists.filter_status', 'Gefiltert: {visible} von {total} Datensätzen')|e }}" data-clear-filters-label="{{ t('lists.clear_filters', 'Filter zurücksetzen')|e }}" data-persist-browser-state="{{ 'true' if session_user and session_user.persist_browser_state else 'false' }}" data-toast-position="{{ session_user.toast_position if session_user else 'top-right' }}" data-toast-duration="{{ session_user.toast_duration_seconds if session_user else 6 }}">
</style><script>(()=>{try{if(localStorage.getItem('assetmanager.sidebar.collapsed')==='true')document.documentElement.classList.add('sidebar-collapsed-preset')}catch(_){}})();</script><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ title or general.get('title', 'AssetManager') }}</title>{% set favicon_path = general.get('favicon') or general.get('logo') %}{% if favicon_path %}<link rel="icon" href="{{ favicon_path }}">{% endif %}<link rel="stylesheet" href="/static/css/app.css?v={{ application_version() }}"><link rel="stylesheet" href="/custom.css"></head><body{% if request and request.url.path == '/assets' %} class="asset-list-scroll-page"{% endif %} data-record-count-template="{{ t('lists.record_count', 'Angezeigt: {visible} von {total} Datensätzen')|e }}" data-record-count-title="{{ t('lists.record_count_hint', 'Die Anzeige berücksichtigt die aktuell gesetzten Tabellenfilter.')|e }}" data-filter-active-label="{{ t('lists.filter_active', 'Filter aktiv')|e }}" data-filters-active-template="{{ t('lists.filters_active', '{count} Filter aktiv')|e }}" data-filter-status-template="{{ t('lists.filter_status', 'Gefiltert: {visible} von {total} Datensätzen')|e }}" data-clear-filters-label="{{ t('lists.clear_filters', 'Filter zurücksetzen')|e }}" data-row-number-label="{{ t('lists.row_number', 'Zeilennummer')|e }}" data-row-number-resize-label="{{ t('lists.row_number_resize', 'Ziehen, um die Breite der Nummernspalte zu ändern')|e }}" data-column-resize-label="{{ t('lists.column_resize', 'Ziehen, um die Spaltenbreite zu ändern')|e }}" data-persist-browser-state="{{ 'true' if session_user and session_user.persist_browser_state else 'false' }}" data-toast-position="{{ session_user.toast_position if session_user else 'top-right' }}" data-toast-duration="{{ session_user.toast_duration_seconds if session_user else 6 }}">
<header class="main-header">
<a class="brand" href="/">{% if general.get('logo') %}<img src="{{ general.get('logo') }}" alt="Logo">{% endif %}<span>{{ general.get('title', 'AssetManager') }}</span>{% if general.get('company_name') %}<span class="brand-company"> {{ general.get('company_name') }}</span>{% endif %}</a>
@@ -33,6 +33,12 @@
<details class="language-menu"><summary>🌐 {{ current_language|upper }} ▾</summary><div class="user-menu-panel">{% for language in available_languages() %}<form method="post" action="/profile/language"><input type="hidden" name="language_code" value="{{ language.code }}"><input type="hidden" name="next_url" value="{{ request.url.path }}{% if request.url.query %}?{{ request.url.query }}{% endif %}"><button type="submit" class="menu-button{% if language.code == current_language %} active{% endif %}">{{ language.native_name }}</button></form>{% endfor %}</div></details>
<details class="user-menu"><summary>👤 {{ session_user.display_name if session_user else t('app.guest') }} ▾</summary><div class="user-menu-panel">{% if session_user %}<a href="/profile">{{ t('app.profile') }}</a><a href="/logout">{{ t('app.logout') }}</a>{% else %}{% if auth.get('mode', 'none') == 'none' %}<span class="muted">Authentication disabled</span>{% endif %}<a href="/login">{{ t('app.login') }}</a>{% endif %}</div></details></div>
</header>
{% if auth.get('mode', 'none') == 'none' %}
<div class="authentication-disabled-banner" role="status">
<strong>⚠ {{ t('auth.disabled_banner_title') }}</strong>
<span>{{ t('auth.disabled_banner_text') }}</span>
</div>
{% endif %}
<dialog id="about-dialog" class="about-dialog">
<form method="dialog">
<div class="dialog-heading">
@@ -99,7 +105,7 @@
{% endif %}
</aside>
<main class="app-content">{% block content %}{% endblock %}</main>
</div><div id="toast-container" class="toast-container" aria-live="polite"></div><script src="/static/js/local-time.js"></script><script src="/static/js/browser-state.js?v={{ app_version }}"></script><script src="/static/js/table-tools.js?v={{ app_version }}"></script><script src="/static/js/list-tools.js"></script><script src="/static/js/asset-columns.js"></script><script src="/static/js/asset-duplicates.js"></script><script src="/static/js/toasts.js"></script><script>
</div><div id="toast-container" class="toast-container" aria-live="polite"></div><script src="/static/js/local-time.js"></script><script src="/static/js/browser-state.js?v={{ app_version }}"></script><script src="/static/js/table-tools.js?v={{ app_version }}"></script><script src="/static/js/list-tools.js?v={{ app_version }}"></script><script src="/static/js/asset-columns.js?v={{ app_version }}"></script><script src="/static/js/asset-duplicates.js?v={{ app_version }}"></script><script src="/static/js/toasts.js"></script><script>
(() => {
document.addEventListener('keydown', event => {
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== 's') return;
+54 -3
View File
@@ -28,16 +28,67 @@
<p class="muted">{{ t('mesh.asset_matching_help') }}</p>
<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 %}> {{ t('mesh.match_node_id') }}</label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="match_order" value="mac_address" {% if 'mac_address' in config.meshcentral.match_order %}checked{% endif %}> {{ t('mesh.match_mac') }}</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 %}> {{ t('mesh.match_serial') }}</label>
<label class="checkbox-label"><input class="checkbox" type="checkbox" name="serial_match_manufacturer" {% if config.meshcentral.get('serial_match_manufacturer', false) %}checked{% endif %}> {{ t('mesh.match_serial_manufacturer') }}</label>
</div>
<p class="muted">{{ t('mesh.match_serial_manufacturer_help') }}</p>
<p class="muted">{{ t('mesh.match_mac_help') }} {{ t('mesh.match_serial_manufacturer_help') }}</p>
<div class="form-grid mesh-identity-settings">
<label>{{ t('mesh.invalid_serial_values') }}
<textarea name="invalid_serial_values" rows="6" placeholder="Unknown&#10;N/A&#10;To be filled by O.E.M.">{{ (config.meshcentral.get('invalid_serial_values', []) or []) | join('\n') }}</textarea>
<small class="muted">{{ t('mesh.invalid_serial_values_help') }}</small>
</label>
<label>{{ t('mesh.manufacturer_invalid_serial_rules') }}
<textarea name="manufacturer_invalid_serial_rules" rows="6" placeholder="Synology | 123456789&#10;Synology | Unknown">{% for rule in config.meshcentral.get('manufacturer_invalid_serial_rules', []) or [] %}{% for value in rule.get('values', []) or [] %}{{ rule.get('manufacturer', '') }} | {{ value }}{{ '\n' }}{% endfor %}{% endfor %}</textarea>
<small class="muted">{{ t('mesh.manufacturer_invalid_serial_rules_help') }}</small>
</label>
</div>
<div class="notice notice-info">{{ t('mesh.node_id_identity_help') }}</div>
<button class="button button-save">💾 {{ t('mesh.save_connection') }}</button>
</form>
</section>
<section class="panel">
<h2>2. {{ t('mesh.field_mappings') }}</h2>
<h2>2. {{ t('settings.mesh_mapping') }}</h2>
{% set mesh = config.meshcentral %}
{% set category_mapping = mesh.get('category_mapping', {}) %}
<p class="muted">{{ t('settings.mesh_mapping_help') }}</p>
<form method="post" action="/sync/meshcentral/categories/save" class="compact-form">
<label>{{ t('settings.fallback_category') }}
<select name="mesh_fallback_category_id" required>
{% for category in categories %}
<option value="{{ category.id }}" {% if category.id == (mesh.get('fallback_category_id', 1)|int) %}selected{% endif %}>{{ category.name }} (ID {{ category.id }})</option>
{% endfor %}
</select>
</label>
<div class="form-grid">
{% set mesh_types = [
('1', 'settings.mesh_type_desktop'),
('2', 'settings.mesh_type_notebook'),
('3', 'settings.mesh_type_mobile'),
('4', 'settings.mesh_type_server'),
('5', 'settings.mesh_type_5'),
('6', 'settings.mesh_type_6'),
('7', 'settings.mesh_type_7'),
('8', 'settings.mesh_type_linux_other')
] %}
{% for type_id, type_label in mesh_types %}
<label>{{ t(type_label) }} ({{ t('settings.meshcentral_type') }} {{ type_id }})
<select name="mesh_category_{{ type_id }}">
<option value="">{{ t('settings.use_fallback') }}</option>
{% for category in categories %}
<option value="{{ category.id }}" {% if category_mapping.get(type_id) is not none and category.id == (category_mapping.get(type_id)|int) %}selected{% endif %}>{{ category.name }} (ID {{ category.id }})</option>
{% endfor %}
</select>
</label>
{% endfor %}
</div>
<button class="button button-save">💾 {{ t('settings.save_mesh_mapping') }}</button>
</form>
</section>
<section class="panel">
<h2>3. {{ 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>
@@ -61,7 +112,7 @@
</section>
<section class="panel">
<h2>3. {{ t('mesh.new_mapping') }}</h2>
<h2>4. {{ 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">
-35
View File
@@ -147,41 +147,6 @@
.inventory-tab { color: #000; }">{{ settings.general.get('custom_css', '') }}</textarea>
</label>
</section>
<section class="settings-section" data-settings-section="meshcentral">
<h2>{{ t('settings.mesh_mapping') }}</h2>
{% set mesh = settings.meshcentral %}
{% set mapping = mesh.get('category_mapping', {}) %}
<p class="muted">{{ t('settings.mesh_mapping_help') }}</p>
<label>{{ t('settings.fallback_category') }}
<select name="mesh_fallback_category_id" required>
{% for category in categories %}
<option value="{{ category.id }}" {% if category.id == (mesh.get('fallback_category_id', 1)|int) %}selected{% endif %}>{{ category.name }} (ID {{ category.id }})</option>
{% endfor %}
</select>
</label>
<div class="form-grid">
{% set mesh_types = [
('1', 'settings.mesh_type_desktop'),
('2', 'settings.mesh_type_notebook'),
('3', 'settings.mesh_type_mobile'),
('4', 'settings.mesh_type_server'),
('5', 'settings.mesh_type_5'),
('6', 'settings.mesh_type_6'),
('7', 'settings.mesh_type_7'),
('8', 'settings.mesh_type_linux_other')
] %}
{% for type_id, type_label in mesh_types %}
<label>{{ t(type_label) }} ({{ t('settings.meshcentral_type') }} {{ type_id }})
<select name="mesh_category_{{ type_id }}">
<option value="">{{ t('settings.use_fallback') }}</option>
{% for category in categories %}
<option value="{{ category.id }}" {% if mapping.get(type_id) is not none and category.id == (mapping.get(type_id)|int) %}selected{% endif %}>{{ category.name }} (ID {{ category.id }})</option>
{% endfor %}
</select>
</label>
{% endfor %}
</div>
</section>
<section class="settings-section" data-settings-section="authentication">
<h2>{{ t('settings.authentication') }}</h2>
<p class="muted">{{ t('settings.auth_help') }}</p>