fix table with handling, icons implemented
This commit is contained in:
@@ -73,7 +73,22 @@
|
||||
</div>
|
||||
|
||||
<div class="detail">
|
||||
<div>{% if asset.image_path %}<img class="asset-image" src="{{ asset.image_path }}">{% elif asset.category.image_path %}<img class="asset-image" src="{{ asset.category.image_path }}">{% endif %}</div>
|
||||
<div>
|
||||
{% set visual_source = asset.visual_source or 'auto' %}
|
||||
{% if visual_source == 'asset_image' %}
|
||||
{% if asset.image_path %}<img class="asset-image" src="{{ asset.image_path }}">{% endif %}
|
||||
{% elif visual_source == 'category_image' %}
|
||||
{% if asset.category.image_path %}<img class="asset-image" src="{{ asset.category.image_path }}">{% endif %}
|
||||
{% elif visual_source == 'mesh_icon' %}
|
||||
{% if asset.mesh_icon and asset.mesh_icon >= 1 and asset.mesh_icon <= 8 %}<img class="asset-image meshcentral-device-icon" src="/static/img/meshcentral-device-icons/icons256-{{ asset.mesh_icon }}-1.png" alt="">{% endif %}
|
||||
{% elif asset.image_path %}
|
||||
<img class="asset-image" src="{{ asset.image_path }}">
|
||||
{% elif asset.category.image_path %}
|
||||
<img class="asset-image" src="{{ asset.category.image_path }}">
|
||||
{% elif asset.mesh_icon and asset.mesh_icon >= 1 and asset.mesh_icon <= 8 %}
|
||||
<img class="asset-image meshcentral-device-icon" src="/static/img/meshcentral-device-icons/icons256-{{ asset.mesh_icon }}-1.png" alt="">
|
||||
{% endif %}
|
||||
</div>
|
||||
<dl>{% for f in active_fields %}{% if f.field_name not in ['room_number', 'parent_asset_id'] %}{% set d=f.definition %}{% set v=custom_values.get(f.field_name) if d and not d.is_system else asset|attr(f.field_name) %}{% if v %}<dt>{{ t(d.translation_key, d.label) if d else f.label }}</dt><dd>{{ v }}</dd>{% endif %}{% endif %}{% endfor %}{% if asset.room_number %}<dt>{{ t('assets.room') }}</dt><dd>{{ asset.room_number }}</dd>{% endif %}
|
||||
{% if parent_asset %}<dt>{{ t('assets.parent') }}</dt><dd><a href="/assets/{{ parent_asset.id }}">{{ parent_asset.name }}</a></dd>{% endif %}
|
||||
{% if child_assets %}<dt>{{ t('assets.child_assets') }}</dt><dd>{% for child in child_assets %}<a href="/assets/{{ child.id }}">{{ child.name }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</dd>{% endif %}
|
||||
|
||||
@@ -23,18 +23,61 @@
|
||||
{% elif d and d.data_type in ['integer','decimal'] %}<input type="number" {% if d.data_type=='decimal' %}step="any"{% endif %} name="{{f.field_name}}" value="{{value or ''}}" {% if f.required %}required{% endif %}>
|
||||
{% elif d and d.data_type == 'json' %}<textarea name="{{f.field_name}}">{{value|tojson if value else ''}}</textarea>
|
||||
{% else %}<input name="{{f.field_name}}" value="{{value or ''}}" {% if f.required %}required{% endif %}>{% endif %}</label>{% endfor %}{% endif %}
|
||||
<label>{{t('assets.image', 'Image')}}<input type="file" name="image" accept="image/*"></label><button class="button button-save">💾 {{t('common.save')}}</button></form>
|
||||
<div class="form-field asset-image-editor" data-image-editor>
|
||||
<label for="asset-image-input">{{t('assets.image', 'Image')}}</label>
|
||||
<div class="asset-edit-image-preview" id="asset-current-image-preview" data-image-preview {% if not (asset and not duplicate_mode and asset.image_path) %}hidden style="display:none"{% endif %}>
|
||||
<img src="{{ asset.image_path if asset and not duplicate_mode and asset.image_path else '' }}" alt="" class="asset-edit-preview-image">
|
||||
<div class="asset-edit-image-preview-actions">
|
||||
<span class="muted">{{ t('assets.current_image', 'Aktuelles Assetbild') }}</span>
|
||||
<button type="button" class="button button-danger button-small" id="remove-asset-image-button" data-image-remove aria-pressed="false" {% if not (asset and not duplicate_mode and asset.image_path) %}hidden disabled{% endif %}>{{ t('assets.remove_image', 'Assetbild entfernen') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="remove_image" id="remove-asset-image" value="0" data-image-remove-input>
|
||||
<div class="image-remove-pending" data-image-remove-pending hidden>{{ t('assets.image_remove_pending', 'Assetbild wird beim Speichern entfernt.') }}</div>
|
||||
<div class="standard-image-library">
|
||||
<strong>{{ t('images.library', 'Standardbild-Bibliothek') }}</strong>
|
||||
<div class="muted image-library-help">{{ t('images.library_help', 'Wähle ein bereits vorhandenes Standardbild oder füge ein Bild einmalig zur Bibliothek hinzu.') }}</div>
|
||||
{% if standard_images %}
|
||||
<div class="standard-image-grid">
|
||||
{% for item in standard_images %}
|
||||
<label class="standard-image-choice" title="{{ item.name }}">
|
||||
<input type="radio" name="library_image" value="{{ item.path }}" data-image-library-choice data-image-src="{{ item.path }}" {% if asset and not duplicate_mode and asset.image_path == item.path %}checked{% endif %}>
|
||||
<img src="{{ item.path }}" alt="">
|
||||
<span>{{ item.name }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted image-library-empty">{{ t('images.library_empty', 'Noch keine Standardbilder vorhanden.') }}</div>
|
||||
{% endif %}
|
||||
<label class="image-upload-label">{{ t('images.library_upload', 'Neues Standardbild hinzufügen und verwenden') }}
|
||||
<input type="file" name="library_upload" accept="image/*" data-image-library-upload>
|
||||
</label>
|
||||
<small class="muted">{{ t('images.library_upload_help', 'Das Bild wird einmal in der gemeinsamen Bibliothek gespeichert und kann danach bei weiteren Assets und Kategorien wiederverwendet werden.') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<label>{{ t('assets.visual_source', 'Anzeigequelle') }}
|
||||
<select name="visual_source">
|
||||
{% set current_visual_source = asset.visual_source if asset and asset.visual_source else 'auto' %}
|
||||
<option value="auto" {% if current_visual_source == 'auto' %}selected{% endif %}>{{ t('assets.visual_source.auto', 'Automatisch: Assetbild → Kategoriebild → MeshCentral-Icon') }}</option>
|
||||
<option value="asset_image" {% if current_visual_source == 'asset_image' %}selected{% endif %}>{{ t('assets.visual_source.asset_image', 'Nur Assetbild') }}</option>
|
||||
<option value="category_image" {% if current_visual_source == 'category_image' %}selected{% endif %}>{{ t('assets.visual_source.category_image', 'Kategoriebild') }}</option>
|
||||
<option value="mesh_icon" {% if current_visual_source == 'mesh_icon' %}selected{% endif %}>{{ t('assets.visual_source.mesh_icon', 'MeshCentral-Icon') }}</option>
|
||||
</select>
|
||||
<small class="muted">{{ t('assets.visual_source.help', 'Legt fest, welches Bild oder Icon für dieses Asset in Listen und Details bevorzugt angezeigt wird.') }}</small>
|
||||
</label><button class="button button-save">💾 {{t('common.save')}}</button></form>
|
||||
{% if asset and not duplicate_mode %}
|
||||
<script>
|
||||
(() => {
|
||||
const input = document.getElementById('mesh-node-id-input');
|
||||
const clearButton = document.getElementById('mesh-node-id-clear');
|
||||
if (!input || !clearButton) return;
|
||||
clearButton.addEventListener('click', () => {
|
||||
input.value = '';
|
||||
input.focus();
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
});
|
||||
if (input && clearButton) {
|
||||
clearButton.addEventListener('click', () => {
|
||||
input.value = '';
|
||||
input.focus();
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
<div class="table-scroll sticky-table" id="asset-table-scroll">
|
||||
<table class="data-table asset-table-fixed" id="asset-table" data-storage-key="asset-columns-{{ selected.id if selected else 'all' }}" data-fixed-column-width="{% if is_admin %}224{% else %}180{% endif %}">
|
||||
<thead><tr>
|
||||
<th class="asset-visual-column" data-no-sort="1" data-no-filter="1" data-field="asset_visual" data-label="{{ t('assets.device_visual', 'Gerät')|e }}" title="{{ t('assets.device_visual', 'Gerät')|e }}">{{ t('assets.device_visual', 'Gerät') }}</th>
|
||||
{% if is_admin %}<th class="selection-column" data-no-sort="1" data-no-filter="1"><input type="checkbox" id="select-all-assets" aria-label="{{ t('assets.select_all') }}"></th>{% endif %}
|
||||
{% set has_name = fields|selectattr('field_name', 'equalto', 'name')|list|length > 0 %}
|
||||
{% if not has_name %}<th draggable="true" data-field="name" data-column-width="220" data-label="{{ t('field.name', 'Bezeichnung')|e }}" style="width:220px;min-width:220px;max-width:220px">{{ t('field.name', 'Bezeichnung') }}</th>{% endif %}
|
||||
@@ -120,6 +121,26 @@
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for a in assets %}<tr data-platform="{{ asset_platforms.get(a.id, 'unknown') }}">
|
||||
<td class="asset-visual-column" data-field="asset_visual">
|
||||
<a href="/assets/{{ a.id }}" class="asset-visual-link" aria-label="{{ a.name|e }}">
|
||||
{% set visual_source = a.visual_source or 'auto' %}
|
||||
{% if visual_source == 'asset_image' %}
|
||||
{% if a.image_path %}<img src="{{ a.image_path }}" alt="" class="asset-list-device-image">{% else %}<span class="asset-list-device-placeholder" aria-hidden="true">▣</span>{% endif %}
|
||||
{% elif visual_source == 'category_image' %}
|
||||
{% if a.category and a.category.image_path %}<img src="{{ a.category.image_path }}" alt="" class="asset-list-device-image category-device-image" title="{{ a.category.name|e }}">{% else %}<span class="asset-list-device-placeholder" aria-hidden="true">▣</span>{% endif %}
|
||||
{% elif visual_source == 'mesh_icon' %}
|
||||
{% if a.mesh_icon and a.mesh_icon >= 1 and a.mesh_icon <= 8 %}<img src="/static/img/meshcentral-device-icons/icons256-{{ a.mesh_icon }}-1.png" alt="" class="asset-list-device-image meshcentral-device-icon" title="{{ t('assets.mesh_icon_title', 'MeshCentral-Geräte-Icon {icon}').format(icon=a.mesh_icon)|e }}">{% else %}<span class="asset-list-device-placeholder" aria-hidden="true">▣</span>{% endif %}
|
||||
{% elif a.image_path %}
|
||||
<img src="{{ a.image_path }}" alt="" class="asset-list-device-image">
|
||||
{% elif a.category and a.category.image_path %}
|
||||
<img src="{{ a.category.image_path }}" alt="" class="asset-list-device-image category-device-image" title="{{ a.category.name|e }}">
|
||||
{% elif a.mesh_icon and a.mesh_icon >= 1 and a.mesh_icon <= 8 %}
|
||||
<img src="/static/img/meshcentral-device-icons/icons256-{{ a.mesh_icon }}-1.png" alt="" class="asset-list-device-image meshcentral-device-icon" title="{{ t('assets.mesh_icon_title', 'MeshCentral-Geräte-Icon {icon}').format(icon=a.mesh_icon)|e }}">
|
||||
{% else %}
|
||||
<span class="asset-list-device-placeholder" aria-hidden="true">▣</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
{% if is_admin %}<td class="selection-column"><input type="checkbox" class="asset-select" value="{{ a.id }}" aria-label="{{ t('assets.select_named', 'Asset {name} markieren').format(name=a.name)|e }}"></td>{% endif %}
|
||||
{% if not has_name %}<td data-field="name"><a href="/assets/{{ a.id }}">{{ a.name }}</a></td>{% endif %}
|
||||
{% if not selected %}<td data-field="category">{{ a.category.name }}</td>{% endif %}
|
||||
|
||||
@@ -105,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?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>
|
||||
</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 src="/static/js/image-editor.js?v={{ app_version }}"></script><script>
|
||||
(() => {
|
||||
document.addEventListener('keydown', event => {
|
||||
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== 's') return;
|
||||
|
||||
@@ -122,19 +122,20 @@
|
||||
</dialog>
|
||||
|
||||
<div class="table-scroll management-table-scroll">
|
||||
<table class="data-table categories-table">
|
||||
<table class="data-table categories-table" data-storage-key="categories">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="selection-column" data-no-sort="1" data-no-filter="1">
|
||||
<th class="selection-column" data-no-sort="1" data-no-filter="1" data-field="selection">
|
||||
<input type="checkbox" id="select-all-categories" aria-label="{{ t('categories.select_all', 'Alle Kategorien markieren') }}">
|
||||
</th>
|
||||
<th>{{ t('common.name') }}</th>
|
||||
<th>{{ t('common.assets') }}</th>
|
||||
<th>{{ t('categories.fields_total', 'Felder gesamt') }}</th>
|
||||
<th>{{ t('categories.active_fields') }}</th>
|
||||
<th>{{ t('categories.list_fields', 'In Liste') }}</th>
|
||||
<th>{{ t('categories.attributes', 'Aktive Attribute') }}</th>
|
||||
<th data-no-filter="1" data-no-sort="1">{{ t('common.actions') }}</th>
|
||||
<th data-no-sort="1" data-no-filter="1" data-field="category_image">{{ t('categories.image', 'Bild') }}</th>
|
||||
<th data-field="name">{{ t('common.name') }}</th>
|
||||
<th data-field="assets_count">{{ t('common.assets') }}</th>
|
||||
<th data-field="fields_total">{{ t('categories.fields_total', 'Felder gesamt') }}</th>
|
||||
<th data-field="active_fields">{{ t('categories.active_fields') }}</th>
|
||||
<th data-field="list_fields">{{ t('categories.list_fields', 'In Liste') }}</th>
|
||||
<th data-field="attributes">{{ t('categories.attributes', 'Aktive Attribute') }}</th>
|
||||
<th data-no-filter="1" data-no-sort="1" data-field="actions">{{ t('common.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -143,23 +144,26 @@
|
||||
{% set active_fields = c.visible_fields|selectattr('active')|list %}
|
||||
{% set list_fields = active_fields|selectattr('show_in_list')|list %}
|
||||
<tr>
|
||||
<td class="selection-column">
|
||||
<td class="selection-column" data-field="selection">
|
||||
<input class="category-select" type="checkbox" form="category-bulk-form" name="category_ids" value="{{ c.id }}"
|
||||
aria-label="{{ c.name|e }} {{ t('common.select', 'markieren') }}">
|
||||
</td>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>{{ count }}</td>
|
||||
<td>{{ c.visible_fields|length }}</td>
|
||||
<td>{{ active_fields|length }}</td>
|
||||
<td>{{ list_fields|length }}</td>
|
||||
<td class="category-attributes">
|
||||
<td class="category-image-cell" data-field="category_image">
|
||||
{% if c.image_path %}<img src="{{ c.image_path }}" alt="" class="category-table-image">{% else %}<span class="asset-list-device-placeholder" aria-hidden="true">▣</span>{% endif %}
|
||||
</td>
|
||||
<td data-field="name">{{ c.name }}</td>
|
||||
<td data-field="assets_count">{{ count }}</td>
|
||||
<td data-field="fields_total">{{ c.visible_fields|length }}</td>
|
||||
<td data-field="active_fields">{{ active_fields|length }}</td>
|
||||
<td data-field="list_fields">{{ list_fields|length }}</td>
|
||||
<td class="category-attributes" data-field="attributes">
|
||||
{% for field in active_fields %}
|
||||
<span class="attribute-chip{% if field.show_in_list %} attribute-chip-list{% endif %}" title="{{ t('categories.shown_in_list', 'In Liste sichtbar') if field.show_in_list else t('categories.active_only', 'Aktiv, aber nicht in Liste sichtbar') }}">{{ field_label(field) }}</span>
|
||||
{% else %}
|
||||
<span class="muted">{{ t('categories.no_active_fields', 'Keine aktiven Felder') }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="category-actions">
|
||||
<td class="category-actions" data-field="actions">
|
||||
<div class="action-buttons">
|
||||
<a class="button button-secondary" href="/categories/{{ c.id }}/edit" title="{{ t('common.edit') }}">{{ t('common.edit') }}</a>
|
||||
<button type="submit" class="button button-secondary" form="duplicate-category-{{ c.id }}" title="{{ t('common.duplicate') }}">{{ t('common.duplicate') }}</button>
|
||||
|
||||
@@ -3,7 +3,39 @@
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<label>{{ t('common.name') }}<input name="name" value="{{category.name if category else ''}}" required></label>
|
||||
<label>{{ t('common.description') }}<textarea name="description">{{category.description if category and category.description else ''}}</textarea></label>
|
||||
<label>{{ t('categories.image') }}<input type="file" name="image" accept="image/*"></label>
|
||||
<div class="form-field category-image-editor" data-image-editor>
|
||||
<label for="category-image-input">{{ t('categories.image') }}</label>
|
||||
<div class="asset-edit-image-preview" id="category-current-image-preview" data-image-preview {% if not (category and category.image_path) %}hidden style="display:none"{% endif %}>
|
||||
<img src="{{ category.image_path if category and category.image_path else '' }}" class="asset-edit-preview-image" alt="">
|
||||
<div class="asset-edit-image-preview-actions">
|
||||
<span class="muted">{{ t('categories.current_image', 'Aktuelles Kategoriebild') }}</span>
|
||||
<button type="button" class="button button-danger button-small" id="remove-category-image-button" data-image-remove aria-pressed="false" {% if not (category and category.image_path) %}hidden disabled{% endif %}>{{ t('categories.remove_image', 'Kategoriebild entfernen') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="remove_image" id="remove-category-image" value="0" data-image-remove-input>
|
||||
<div class="image-remove-pending" data-image-remove-pending hidden>{{ t('categories.image_remove_pending', 'Kategoriebild wird beim Speichern entfernt.') }}</div>
|
||||
<div class="standard-image-library">
|
||||
<strong>{{ t('images.library', 'Standardbild-Bibliothek') }}</strong>
|
||||
<div class="muted image-library-help">{{ t('images.library_help', 'Wähle ein bereits vorhandenes Standardbild oder füge ein Bild einmalig zur Bibliothek hinzu.') }}</div>
|
||||
{% if standard_images %}
|
||||
<div class="standard-image-grid">
|
||||
{% for item in standard_images %}
|
||||
<label class="standard-image-choice" title="{{ item.name }}">
|
||||
<input type="radio" name="library_image" value="{{ item.path }}" data-image-library-choice data-image-src="{{ item.path }}" {% if category and category.image_path == item.path %}checked{% endif %}>
|
||||
<img src="{{ item.path }}" alt="">
|
||||
<span>{{ item.name }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="muted image-library-empty">{{ t('images.library_empty', 'Noch keine Standardbilder vorhanden.') }}</div>
|
||||
{% endif %}
|
||||
<label class="image-upload-label">{{ t('images.library_upload', 'Neues Standardbild hinzufügen und verwenden') }}
|
||||
<input type="file" name="library_upload" accept="image/*" data-image-library-upload>
|
||||
</label>
|
||||
<small class="muted">{{ t('images.library_upload_help', 'Das Bild wird einmal in der gemeinsamen Bibliothek gespeichert und kann danach bei weiteren Assets und Kategorien wiederverwendet werden.') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<h2>{{ t('categories.fields_title') }}</h2>
|
||||
<p class="muted">Labels, descriptions, data types and read-only settings are managed centrally under Settings → Fields.</p>
|
||||
<table class="data-table"><thead><tr><th>{{ t('common.active') }}</th><th>{{ t('categories.database_field') }}</th><th>{{ t('categories.display_name') }}</th><th>{{ t('categories.required') }}</th><th>{{ t('categories.in_list') }}</th><th>{{ t('categories.readonly') }}</th></tr></thead><tbody>
|
||||
@@ -13,4 +45,5 @@
|
||||
<td><input class="checkbox" type="checkbox" name="required_{{definition.id}}" {% if (cfg and cfg.required) or definition.field_name == 'name' %}checked{% endif %}{% if definition.readonly or definition.field_name == 'name' %} disabled{% endif %}></td>
|
||||
<td><input class="checkbox" type="checkbox" name="list_{{definition.id}}" {% if (cfg and cfg.show_in_list) or definition.field_name == 'name' %}checked{% endif %}></td>
|
||||
<td>{{ t('common.yes') if definition.readonly else t('common.no') }}</td></tr>{% endfor %}</tbody></table>
|
||||
<button>💾 {{ t('common.save') }}</button></form>{% endblock %}
|
||||
<button>💾 {{ t('common.save') }}</button></form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<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>
|
||||
<p class="muted">{{ t('settings.mesh_icon_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>
|
||||
@@ -73,7 +73,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 }})
|
||||
<label class="mesh-icon-category-label"><span class="mesh-icon-category-title"><img src="/static/img/meshcentral-device-icons/icons256-{{ type_id }}-1.png" alt="" class="mesh-icon-preview">{{ t(type_label) }} ({{ t('settings.meshcentral_type') }} {{ type_id }})</span>
|
||||
<select name="mesh_category_{{ type_id }}">
|
||||
<option value="">{{ t('settings.use_fallback') }}</option>
|
||||
{% for category in categories %}
|
||||
|
||||
Reference in New Issue
Block a user