table optimization (filtering, resizing and settings saved in user settings
This commit is contained in:
@@ -136,12 +136,12 @@
|
||||
{% 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 %}
|
||||
{% if not selected %}<th draggable="true" data-field="category" data-column-width="180" data-label="{{ t('common.category', 'Kategorie')|e }}" style="width:180px;min-width:180px;max-width:180px">{{ t('common.category', 'Kategorie') }}</th>{% endif %}
|
||||
{% for f in fields %}{% set list_width = f.definition.list_width if f.definition and f.definition.list_width else (220 if f.field_name == 'name' else 180) %}<th draggable="true" data-field="{{ f.field_name }}" data-column-width="{{ list_width }}" data-label="{{ f.label|e }}" style="width:{{ list_width }}px;min-width:{{ list_width }}px;max-width:{{ list_width }}px">{{ f.label }}</th>{% endfor %}
|
||||
{% for f in fields %}{% set list_width = f.definition.list_width if f.definition and f.definition.list_width else (220 if f.field_name == 'name' else 180) %}<th draggable="true" data-field="{{ f.field_name }}" data-column-width="{{ list_width }}" data-label="{{ f.label|e }}" data-filter-type="{{ f.definition.data_type if f.definition else '' }}" style="width:{{ list_width }}px;min-width:{{ list_width }}px;max-width:{{ list_width }}px">{{ f.label }}</th>{% endfor %}
|
||||
{% if job_filter %}
|
||||
<th data-field="job_filter_status" data-label="{{ t('assets.job_filter.status', 'Jobstatus')|e }}" style="width:180px;min-width:180px;max-width:180px">{{ t('assets.job_filter.status', 'Jobstatus') }}</th>
|
||||
<th data-field="job_filter_last_execution" data-label="{{ t('assets.job_filter.last_execution', 'Letzte Ausführung')|e }}" style="width:175px;min-width:175px;max-width:175px">{{ t('assets.job_filter.last_execution', 'Letzte Ausführung') }}</th>
|
||||
<th data-field="job_filter_last_success" data-label="{{ t('assets.job_filter.last_success', 'Letzter Erfolg')|e }}" style="width:175px;min-width:175px;max-width:175px">{{ t('assets.job_filter.last_success', 'Letzter Erfolg') }}</th>
|
||||
<th data-field="job_filter_revision" data-label="{{ t('assets.job_filter.revision', 'Revision')|e }}" style="width:100px;min-width:100px;max-width:100px">{{ t('assets.job_filter.revision', 'Revision') }}</th>
|
||||
<th data-field="job_filter_last_execution" data-filter-type="datetime" data-label="{{ t('assets.job_filter.last_execution', 'Letzte Ausführung')|e }}" style="width:175px;min-width:175px;max-width:175px">{{ t('assets.job_filter.last_execution', 'Letzte Ausführung') }}</th>
|
||||
<th data-field="job_filter_last_success" data-filter-type="datetime" data-label="{{ t('assets.job_filter.last_success', 'Letzter Erfolg')|e }}" style="width:175px;min-width:175px;max-width:175px">{{ t('assets.job_filter.last_success', 'Letzter Erfolg') }}</th>
|
||||
<th data-field="job_filter_revision" data-filter-type="integer" data-label="{{ t('assets.job_filter.revision', 'Revision')|e }}" style="width:100px;min-width:100px;max-width:100px">{{ t('assets.job_filter.revision', 'Revision') }}</th>
|
||||
{% endif %}
|
||||
<th data-field="mesh_presence" data-label="{{ t('presence.title')|e }}" style="width:125px;min-width:125px;max-width:125px">{{ t('presence.title') }}</th>
|
||||
<th data-no-sort="1" data-no-filter="1" class="actions-column">{{ t('common.actions', 'Aktionen') }}</th>
|
||||
@@ -371,6 +371,23 @@
|
||||
});
|
||||
headerRow.insertBefore(headerFragment, headerActions);
|
||||
|
||||
// Keep the generated filter row in exactly the same field order. It is
|
||||
// created by table-tools.js after the initial page setup, so this matters
|
||||
// especially when the user reorders a column interactively afterwards.
|
||||
const filterRow = table.tHead.querySelector('tr[data-table-filter-row="1"]');
|
||||
if (filterRow) {
|
||||
const filterActions = filterRow.lastElementChild;
|
||||
const filterFragment = document.createDocumentFragment();
|
||||
valid.forEach(field => {
|
||||
const cell = filterRow.querySelector(`th[data-field="${CSS.escape(field)}"]`);
|
||||
if (cell) filterFragment.appendChild(cell);
|
||||
});
|
||||
if (filterActions) filterRow.insertBefore(filterFragment, filterActions);
|
||||
[...filterRow.cells].forEach((cell, index) => {
|
||||
cell.dataset.columnIndex = String(index);
|
||||
});
|
||||
}
|
||||
|
||||
[...table.tBodies[0].rows].forEach(row => {
|
||||
const actions = row.lastElementChild;
|
||||
const fragment = document.createDocumentFragment();
|
||||
@@ -397,6 +414,10 @@
|
||||
order.splice(order.indexOf(dragged), 1);
|
||||
order.splice(order.indexOf(target), 0, dragged);
|
||||
applyOrder(order);
|
||||
// Reapply widths by field after the DOM order changed. Resizer handlers
|
||||
// use the header's current cellIndex, so the visible separator and the
|
||||
// resized data cells stay aligned after drag & drop.
|
||||
table.assetApplyStoredColumnWidths?.();
|
||||
window.AssetBrowserState?.set(key, JSON.stringify(order));
|
||||
window.dispatchEvent(new Event('asset-table-columns-changed'));
|
||||
});
|
||||
|
||||
@@ -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-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-multi-value-filter-hint="{{ t('lists.multi_value_filter_hint', 'Mehrere Suchwerte können zeilenweise eingefügt werden. Innerhalb einer Spalte gilt ODER.')|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-multi-value-filter-hint="{{ t('lists.multi_value_filter_hint', 'Mehrere Suchwerte können zeilenweise eingefügt werden. Innerhalb einer Spalte gilt ODER.')|e }}" data-advanced-filter-title="{{ t('lists.advanced_filter.title')|e }}" data-advanced-filter-operator="{{ t('lists.advanced_filter.operator')|e }}" data-advanced-filter-value="{{ t('lists.advanced_filter.value')|e }}" data-advanced-filter-value-from="{{ t('lists.advanced_filter.value_from')|e }}" data-advanced-filter-value-to="{{ t('lists.advanced_filter.value_to')|e }}" data-advanced-filter-equal="{{ t('lists.advanced_filter.equal')|e }}" data-advanced-filter-gt="{{ t('lists.advanced_filter.gt')|e }}" data-advanced-filter-gte="{{ t('lists.advanced_filter.gte')|e }}" data-advanced-filter-lt="{{ t('lists.advanced_filter.lt')|e }}" data-advanced-filter-lte="{{ t('lists.advanced_filter.lte')|e }}" data-advanced-filter-range="{{ t('lists.advanced_filter.range')|e }}" data-advanced-filter-apply="{{ t('lists.advanced_filter.apply')|e }}" data-advanced-filter-clear="{{ t('lists.advanced_filter.clear')|e }}" data-advanced-filter-empty="{{ t('lists.advanced_filter.empty')|e }}" data-advanced-filter-not-empty="{{ t('lists.advanced_filter.not_empty')|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>
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<button type="submit" class="button button-save" data-save-shortcut>💾 {{ t('common.save') }}</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/profile/browser-state" class="auth-card"><h2>{{ t('profile.browser_storage') }}</h2><p class="table-hint">{{ t('profile.browser_storage_help') }}</p><label class="checkbox-label"><input type="checkbox" name="persist_browser_state" {% if user.persist_browser_state %}checked{% endif %}>{{ t('profile.browser_storage_enable') }}</label><button type="submit" class="button button-save" data-save-shortcut>💾 {{ t('profile.save_browser_storage') }}</button></form>
|
||||
<section class="auth-card"><h2>{{ t('profile.browser_storage') }}</h2><p class="table-hint">{{ t('profile.browser_storage_help') }}</p></section>
|
||||
|
||||
<form method="post" action="/profile/toast-settings" class="auth-card">
|
||||
<h2>{{ t('profile.toast_settings') }}</h2>
|
||||
|
||||
@@ -87,13 +87,13 @@
|
||||
>
|
||||
<thead><tr>
|
||||
<th class="selection-column" data-no-sort="1" data-no-filter="1"><input type="checkbox" id="select-all-restartable-jobs" aria-label="{{ t('jobs.select_all_restartable')|e }}"></th>
|
||||
<th data-server-filter-name="job_id" data-server-filter-value="{{ job_filters.job_id|e }}">{{ t('software.job_id') }}</th>
|
||||
<th data-server-filter-name="created_at" data-server-filter-value="{{ job_filters.created_at|e }}">{{ t('software.time') }}</th>
|
||||
<th data-server-filter-name="job_id" data-filter-type="integer" data-server-filter-value="{{ job_filters.job_id|e }}">{{ t('software.job_id') }}</th>
|
||||
<th data-server-filter-name="created_at" data-filter-type="datetime" data-server-filter-value="{{ job_filters.created_at|e }}">{{ t('software.time') }}</th>
|
||||
<th data-server-filter-name="asset" data-server-filter-value="{{ job_filters.asset|e }}">{{ t('common.assets') }}</th>
|
||||
<th data-server-filter-name="job_type" data-server-filter-value="{{ job_filters.job_type|e }}">{{ t('jobs.type') }}</th>
|
||||
<th data-server-filter-name="package" data-server-filter-value="{{ job_filters.package|e }}">{{ t('software.package') }}</th>
|
||||
<th data-server-filter-name="action" data-server-filter-value="{{ job_filters.action|e }}">{{ t('jobs.action') }}</th>
|
||||
<th data-server-filter-name="priority" data-server-filter-value="{{ job_filters.priority|e }}">{{ t('jobs.priority') }}</th>
|
||||
<th data-server-filter-name="priority" data-filter-type="integer" data-server-filter-value="{{ job_filters.priority|e }}">{{ t('jobs.priority') }}</th>
|
||||
<th data-server-filter-name="platform" data-server-filter-value="{{ job_filters.platform|e }}">{{ t('software.platform') }}</th>
|
||||
<th data-server-filter-name="status" data-server-filter-value="{{ job_filters.status|e }}">{{ t('software.status') }}</th>
|
||||
<th>{{ t('software.details') }}</th>
|
||||
|
||||
Reference in New Issue
Block a user