Privacy Policy DSGVO with sheets for categories, export-, check- and first erase-functions

This commit is contained in:
2026-08-06 20:27:26 +00:00
parent fc82a28290
commit daac478714
12 changed files with 511 additions and 34 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# AssetManager 0.5.5.40 # AssetManager 0.5.5.43
AssetManager is a self-hosted web application for managing IT equipment and other organizational assets. The project is released under the **Apache License 2.0** and may be used, modified, and redistributed for private and commercial purposes. AssetManager is a self-hosted web application for managing IT equipment and other organizational assets. The project is released under the **Apache License 2.0** and may be used, modified, and redistributed for private and commercial purposes.
+1 -1
View File
@@ -1 +1 @@
0.5.5.40 0.5.5.43
+2
View File
@@ -140,6 +140,8 @@ BASE_TRANSLATIONS = {
"lists.filters_active": ("{count} filters active", "{count} Filter aktiv"), "lists.filters_active": ("{count} filters active", "{count} Filter aktiv"),
"lists.filter_status": ("Filtered: {visible} of {total} records", "Gefiltert: {visible} von {total} Datensätzen"), "lists.filter_status": ("Filtered: {visible} of {total} records", "Gefiltert: {visible} von {total} Datensätzen"),
"lists.clear_filters": ("Clear filters", "Filter zurücksetzen"), "lists.clear_filters": ("Clear filters", "Filter zurücksetzen"),
"lists.row_number": ("Row number", "Zeilennummer"),
"lists.row_number_resize": ("Drag to change the row-number column width", "Ziehen, um die Breite der Nummernspalte zu ändern"),
"appearance.colors_title": ("System colors", "Systemfarben"), "appearance.colors_title": ("System colors", "Systemfarben"),
"appearance.colors_help": ("Configure the central colors used throughout the application.", "Konfigurieren Sie die zentral im gesamten System verwendeten Farben."), "appearance.colors_help": ("Configure the central colors used throughout the application.", "Konfigurieren Sie die zentral im gesamten System verwendeten Farben."),
"appearance.primary_color": ("Primary color", "Primärfarbe"), "appearance.primary_color": ("Primary color", "Primärfarbe"),
+107
View File
@@ -2851,3 +2851,110 @@ table.server-filter-loading tbody{opacity:.55;transition:opacity .12s ease}
.privacy-settings-form[data-privacy-readonly="true"] input:disabled, .privacy-settings-form[data-privacy-readonly="true"] input:disabled,
.privacy-settings-form[data-privacy-readonly="true"] textarea:disabled, .privacy-settings-form[data-privacy-readonly="true"] textarea:disabled,
.privacy-settings-form[data-privacy-readonly="true"] select:disabled { opacity: 1; color: inherit; -webkit-text-fill-color: currentColor; cursor: default; } .privacy-settings-form[data-privacy-readonly="true"] select:disabled { opacity: 1; color: inherit; -webkit-text-fill-color: currentColor; cursor: default; }
/* v0.5.5.41: readable chart status/category filters in dark mode. */
html[data-theme="dark"] .checkbox-option{
background:#f8fafc !important;
color:#111827 !important;
border-color:#cbd5e1 !important;
}
html[data-theme="dark"] .checkbox-option label,
html[data-theme="dark"] .checkbox-option span,
html[data-theme="dark"] .checkbox-option strong{
color:#111827 !important;
}
/* v0.5.5.41: generated visual row number column for data tables. */
.data-table .table-row-number-header,
.data-table .table-row-number-cell{
width:1%;
min-width:3.25rem;
white-space:nowrap;
text-align:right;
font-variant-numeric:tabular-nums;
}
.data-table .table-row-number-header{
cursor:default;
}
.data-table .table-row-number-cell{
color:var(--am-muted,#64748b);
user-select:none;
}
/* Visual row-number column: readable by default and user-resizable per table. */
.data-table .table-row-number-header,
.data-table .table-row-number-filter,
.data-table .table-row-number-cell {
width: var(--table-row-number-width, 64px);
min-width: var(--table-row-number-width, 64px);
max-width: var(--table-row-number-width, 64px);
text-align: right;
white-space: nowrap;
}
.data-table .table-row-number-header {
position: relative;
padding-right: 14px;
overflow: visible;
}
.data-table .table-row-number-cell {
font-variant-numeric: tabular-nums;
overflow: hidden;
text-overflow: clip;
}
.data-table .table-row-number-resizer {
position: absolute;
top: 0;
right: -4px;
bottom: 0;
width: 9px;
cursor: col-resize;
z-index: 3;
touch-action: none;
}
.data-table .table-row-number-resizer::after {
content: "";
position: absolute;
top: 20%;
bottom: 20%;
left: 4px;
border-left: 1px solid transparent;
}
.data-table .table-row-number-resizer:hover::after,
.data-table .table-row-number-resizer:focus-visible::after {
border-left-color: currentColor;
}
html.table-column-resizing,
html.table-column-resizing * {
cursor: col-resize !important;
user-select: none !important;
}
/* v0.5.5.43: user-resizable widths for every standard table column. */
.data-table thead tr:first-child > th:not(.table-row-number-header) {
position: relative;
overflow: visible;
}
.data-table .table-column-resizer {
position: absolute;
top: 0;
right: -4px;
bottom: 0;
width: 9px;
z-index: 4;
cursor: col-resize;
touch-action: none;
}
.data-table .table-column-resizer::after {
content: "";
position: absolute;
top: 18%;
bottom: 18%;
left: 4px;
border-left: 1px solid transparent;
}
.data-table .table-column-resizer:hover::after,
.data-table .table-column-resizer:focus-visible::after {
border-left-color: currentColor;
}
+6
View File
@@ -224,6 +224,12 @@
} }
}); });
window.addEventListener('asset-table-column-width-changed', event => {
if (!event.detail?.table || event.detail.table === table) {
applyHidden();
}
});
applyHidden(); applyHidden();
window.dispatchEvent(new CustomEvent('asset-table-columns-ready', { window.dispatchEvent(new CustomEvent('asset-table-columns-ready', {
detail: { table } detail: { table }
+42 -1
View File
@@ -35,15 +35,35 @@
return control.name || control.dataset.field || cell?.dataset.field || `filter-${index}`; return control.name || control.dataset.field || cell?.dataset.field || `filter-${index}`;
} }
function restoreSort(table, index, force = false) {
if (!force && table.dataset.browserSortRestored === '1') return;
if (typeof table.assetApplySort !== 'function') return;
const key = `${tableKey(table, index)}:sort`;
let state = null;
try { state = JSON.parse(api.get(key) || 'null'); } catch (_) {}
if (state?.field && (state.direction === 'asc' || state.direction === 'desc')) {
table.assetApplySort(state.field, state.direction, {silent: true});
} else {
table.assetUpdateRowNumbers?.();
}
table.dataset.browserSortRestored = '1';
}
function restoreTable(table, index) { function restoreTable(table, index) {
if (table.dataset.browserStateReady === '1') return; if (table.dataset.browserStateReady === '1') return;
if (table.dataset.serverFilters === '1') { if (table.dataset.serverFilters === '1') {
table.dataset.browserStateReady = '1'; table.dataset.browserStateReady = '1';
restoreSort(table, index);
return; return;
} }
const restoreStarted = performance.now(); const restoreStarted = performance.now();
const controls = filterControls(table); const controls = filterControls(table);
if (!controls.length) return; if (!controls.length) {
table.dataset.browserStateReady = '1';
restoreSort(table, index);
return;
}
table.dataset.browserStateReady = '1'; table.dataset.browserStateReady = '1';
const key = `${tableKey(table, index)}:filters`; const key = `${tableKey(table, index)}:filters`;
@@ -71,6 +91,7 @@
if (typeof table.assetApplyFilters === 'function') { if (typeof table.assetApplyFilters === 'function') {
table.assetApplyFilters(); table.assetApplyFilters();
} }
restoreSort(table, index);
table.dataset.browserStateRestored = '1'; table.dataset.browserStateRestored = '1';
window.dispatchEvent(new CustomEvent('asset-table-state-ready', { detail: { table } })); window.dispatchEvent(new CustomEvent('asset-table-state-ready', { detail: { table } }));
const duration = performance.now() - restoreStarted; const duration = performance.now() - restoreStarted;
@@ -109,5 +130,25 @@
api.remove(key); api.remove(key);
}); });
window.addEventListener('asset-table-sort-changed', event => {
const table = event.detail?.table;
if (!(table instanceof HTMLTableElement)) return;
const tables = [...document.querySelectorAll('table')];
const index = tables.indexOf(table);
const key = `${tableKey(table, index >= 0 ? index : 0)}:sort`;
api.set(key, JSON.stringify({
field: event.detail?.field || '',
direction: event.detail?.direction || 'asc'
}));
});
window.addEventListener('asset-table-content-reloaded', event => {
const table = event.detail?.table;
if (!(table instanceof HTMLTableElement)) return;
const tables = [...document.querySelectorAll('table')];
const index = tables.indexOf(table);
restoreSort(table, index >= 0 ? index : 0, true);
});
perf?.end('browser-state:gesamtes Modul'); perf?.end('browser-state:gesamtes Modul');
})(); })();
+334 -28
View File
@@ -163,6 +163,104 @@
const headerRow = thead.rows[0]; const headerRow = thead.rows[0];
// Add a purely visual row-number column before every real table column.
// It is recalculated after filtering, sorting and partial server reloads and
// therefore never represents a database identifier.
if (!headerRow.querySelector('th[data-row-number-column="1"]')) {
const numberHeader = document.createElement('th');
numberHeader.className = 'table-row-number-header';
numberHeader.dataset.rowNumberColumn = '1';
numberHeader.dataset.noSort = '1';
numberHeader.dataset.noFilter = '1';
numberHeader.textContent = '#';
numberHeader.title = document.body.dataset.rowNumberLabel || 'Row number';
headerRow.insertBefore(numberHeader, headerRow.firstChild);
}
const numberHeader = headerRow.querySelector('th[data-row-number-column="1"]');
const rowNumberStoragePart = table.dataset.storageKey || table.id || window.location.pathname;
const rowNumberWidthKey = `assetmanager:table:${rowNumberStoragePart}:row-number-width`;
const defaultRowNumberWidth = 64;
const minimumRowNumberWidth = 48;
const maximumRowNumberWidth = 180;
function readStoredRowNumberWidth() {
try {
const stored = Number.parseInt(window.localStorage.getItem(rowNumberWidthKey) || '', 10);
if (Number.isFinite(stored)) {
return Math.min(maximumRowNumberWidth, Math.max(minimumRowNumberWidth, stored));
}
} catch (_) {}
return defaultRowNumberWidth;
}
function applyRowNumberWidth(width) {
const normalized = Math.min(maximumRowNumberWidth, Math.max(minimumRowNumberWidth, Math.round(width)));
table.style.setProperty('--table-row-number-width', `${normalized}px`);
table.dataset.rowNumberWidth = String(normalized);
return normalized;
}
function installRowNumberResizer() {
if (!numberHeader || numberHeader.querySelector('.table-row-number-resizer')) return;
const handle = document.createElement('span');
handle.className = 'table-row-number-resizer';
handle.setAttribute('role', 'separator');
handle.setAttribute('aria-orientation', 'vertical');
handle.tabIndex = 0;
handle.title = document.body.dataset.rowNumberResizeLabel || 'Drag to change column width';
numberHeader.appendChild(handle);
const saveWidth = width => {
const normalized = applyRowNumberWidth(width);
try { window.localStorage.setItem(rowNumberWidthKey, String(normalized)); } catch (_) {}
};
handle.addEventListener('pointerdown', event => {
event.preventDefault();
event.stopPropagation();
const startX = event.clientX;
const startWidth = Number.parseInt(table.dataset.rowNumberWidth || '', 10) || defaultRowNumberWidth;
handle.setPointerCapture?.(event.pointerId);
document.documentElement.classList.add('table-column-resizing');
const move = moveEvent => applyRowNumberWidth(startWidth + moveEvent.clientX - startX);
const finish = finishEvent => {
saveWidth(Number.parseInt(table.dataset.rowNumberWidth || '', 10) || startWidth);
handle.releasePointerCapture?.(finishEvent.pointerId);
document.documentElement.classList.remove('table-column-resizing');
handle.removeEventListener('pointermove', move);
handle.removeEventListener('pointerup', finish);
handle.removeEventListener('pointercancel', finish);
};
handle.addEventListener('pointermove', move);
handle.addEventListener('pointerup', finish);
handle.addEventListener('pointercancel', finish);
});
handle.addEventListener('keydown', event => {
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
event.preventDefault();
const current = Number.parseInt(table.dataset.rowNumberWidth || '', 10) || defaultRowNumberWidth;
saveWidth(current + (event.key === 'ArrowRight' ? 8 : -8));
});
}
applyRowNumberWidth(readStoredRowNumberWidth());
installRowNumberResizer();
function ensureRowNumberCells() {
[...tbody.rows].forEach(row => {
if (row.querySelector('td[data-row-number-column="1"]')) return;
const cell = document.createElement('td');
cell.className = 'table-row-number-cell';
cell.dataset.rowNumberColumn = '1';
cell.setAttribute('aria-hidden', 'true');
row.insertBefore(cell, row.firstChild);
});
}
ensureRowNumberCells();
const isAssetTable = table.id === 'asset-table'; const isAssetTable = table.id === 'asset-table';
const usesServerFilters = table.dataset.serverFilters === '1'; const usesServerFilters = table.dataset.serverFilters === '1';
let serverFilterTimer = null; let serverFilterTimer = null;
@@ -173,15 +271,19 @@
// real data-field keys on its movable columns; selection and action // real data-field keys on its movable columns; selection and action
// columns must remain without a field key. // columns must remain without a field key.
if (!isAssetTable) { if (!isAssetTable) {
[...headerRow.cells].forEach((header, index) => { let syntheticIndex = 0;
[...headerRow.cells].forEach(header => {
if (header.dataset.rowNumberColumn === '1') return;
if (!header.dataset.field) { if (!header.dataset.field) {
header.dataset.field = `__col_${index}`; header.dataset.field = `__col_${syntheticIndex}`;
header.dataset.syntheticField = '1'; header.dataset.syntheticField = '1';
} }
syntheticIndex += 1;
}); });
[...tbody.rows].forEach(row => { [...tbody.rows].forEach(row => {
[...row.cells].forEach((cell, index) => { [...row.cells].forEach((cell, index) => {
if (cell.dataset.rowNumberColumn === '1') return;
const header = headerRow.cells[index]; const header = headerRow.cells[index];
if (header && !cell.dataset.field) { if (header && !cell.dataset.field) {
cell.dataset.field = header.dataset.field; cell.dataset.field = header.dataset.field;
@@ -197,6 +299,160 @@
const filterDefinitions = []; const filterDefinitions = [];
// User-resizable widths for all real table columns. Widths are stored per
// table and column key. The visual row-number column keeps its dedicated
// implementation above because it is generated dynamically.
const columnWidthsStorageKey = `assetmanager:table:${rowNumberStoragePart}:column-widths`;
const minimumColumnWidth = 56;
const maximumColumnWidth = 900;
function readStoredColumnWidths() {
try {
const parsed = JSON.parse(window.localStorage.getItem(columnWidthsStorageKey) || '{}');
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch (_) {
return {};
}
}
let storedColumnWidths = readStoredColumnWidths();
function columnStorageKey(header, columnIndex) {
return header?.dataset.field || header?.dataset.serverFilterName || `__column_${columnIndex}`;
}
function normalizedColumnWidth(width) {
return Math.min(maximumColumnWidth, Math.max(minimumColumnWidth, Math.round(width)));
}
function applyColumnWidth(columnIndex, width, options = {}) {
const header = headerRow.cells[columnIndex];
if (!header || header.dataset.rowNumberColumn === '1') return null;
const normalized = normalizedColumnWidth(width);
const cells = [header, filterRow.cells[columnIndex], ...[...tbody.rows].map(row => row.cells[columnIndex])];
cells.filter(Boolean).forEach(cell => {
cell.style.width = `${normalized}px`;
cell.style.minWidth = `${normalized}px`;
cell.style.maxWidth = `${normalized}px`;
});
header.dataset.userColumnWidth = String(normalized);
// The asset list calculates its scroll width from data-column-width.
// Keep that existing mechanism in sync instead of introducing a second
// competing table-width calculation.
if (isAssetTable && header.dataset.field) {
header.dataset.columnWidth = String(normalized);
window.dispatchEvent(new CustomEvent('asset-table-column-width-changed', {
detail: { table, field: header.dataset.field, width: normalized }
}));
}
if (options.save) {
storedColumnWidths[columnStorageKey(header, columnIndex)] = normalized;
try {
window.localStorage.setItem(columnWidthsStorageKey, JSON.stringify(storedColumnWidths));
} catch (_) {}
}
return normalized;
}
function applyStoredColumnWidths() {
[...headerRow.cells].forEach((header, columnIndex) => {
if (header.dataset.rowNumberColumn === '1') return;
const stored = Number.parseInt(storedColumnWidths[columnStorageKey(header, columnIndex)], 10);
if (Number.isFinite(stored)) applyColumnWidth(columnIndex, stored);
});
}
function resetColumnWidth(columnIndex) {
const header = headerRow.cells[columnIndex];
if (!header || header.dataset.rowNumberColumn === '1') return;
delete storedColumnWidths[columnStorageKey(header, columnIndex)];
try {
window.localStorage.setItem(columnWidthsStorageKey, JSON.stringify(storedColumnWidths));
} catch (_) {}
[header, filterRow.cells[columnIndex], ...[...tbody.rows].map(row => row.cells[columnIndex])]
.filter(Boolean)
.forEach(cell => {
cell.style.removeProperty('width');
cell.style.removeProperty('min-width');
cell.style.removeProperty('max-width');
});
delete header.dataset.userColumnWidth;
if (isAssetTable && header.dataset.field) {
const configured = Number.parseInt(header.dataset.defaultColumnWidth || '', 10);
const fallback = Number.isFinite(configured) ? configured : 180;
header.dataset.columnWidth = String(fallback);
window.dispatchEvent(new CustomEvent('asset-table-column-width-changed', {
detail: { table, field: header.dataset.field, width: fallback }
}));
}
}
function installColumnResizers() {
[...headerRow.cells].forEach((header, columnIndex) => {
if (header.dataset.rowNumberColumn === '1' || header.colSpan > 1) return;
if (header.querySelector(':scope > .table-column-resizer')) return;
if (isAssetTable && !header.dataset.defaultColumnWidth) {
header.dataset.defaultColumnWidth = header.dataset.columnWidth || '';
}
const handle = document.createElement('span');
handle.className = 'table-column-resizer';
handle.setAttribute('role', 'separator');
handle.setAttribute('aria-orientation', 'vertical');
handle.tabIndex = 0;
handle.title = document.body.dataset.rowNumberResizeLabel || 'Drag to change column width';
header.appendChild(handle);
handle.addEventListener('click', event => {
event.preventDefault();
event.stopPropagation();
});
handle.addEventListener('dblclick', event => {
event.preventDefault();
event.stopPropagation();
resetColumnWidth(columnIndex);
});
handle.addEventListener('pointerdown', event => {
event.preventDefault();
event.stopPropagation();
const startX = event.clientX;
const startWidth = Math.round(header.getBoundingClientRect().width);
handle.setPointerCapture?.(event.pointerId);
document.documentElement.classList.add('table-column-resizing');
const move = moveEvent => applyColumnWidth(columnIndex, startWidth + moveEvent.clientX - startX);
const finish = finishEvent => {
const current = Number.parseInt(header.dataset.userColumnWidth || '', 10) || startWidth;
applyColumnWidth(columnIndex, current, { save: true });
handle.releasePointerCapture?.(finishEvent.pointerId);
document.documentElement.classList.remove('table-column-resizing');
handle.removeEventListener('pointermove', move);
handle.removeEventListener('pointerup', finish);
handle.removeEventListener('pointercancel', finish);
};
handle.addEventListener('pointermove', move);
handle.addEventListener('pointerup', finish);
handle.addEventListener('pointercancel', finish);
});
handle.addEventListener('keydown', event => {
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
event.preventDefault();
event.stopPropagation();
const current = Number.parseInt(header.dataset.userColumnWidth || '', 10)
|| Math.round(header.getBoundingClientRect().width);
applyColumnWidth(columnIndex, current + (event.key === 'ArrowRight' ? 12 : -12), { save: true });
});
});
}
function filterStatusTarget() { function filterStatusTarget() {
const wrapper = table.closest( const wrapper = table.closest(
'#asset-table-scroll, .management-table-scroll, .table-scroll, .table-wrap, .sticky-table' '#asset-table-scroll, .management-table-scroll, .table-scroll, .table-wrap, .sticky-table'
@@ -291,16 +547,22 @@
} }
function rebuildMissingCellKeys() { function rebuildMissingCellKeys() {
if (isAssetTable) return; ensureRowNumberCells();
if (isAssetTable) {
applyStoredColumnWidths();
return;
}
[...tbody.rows].forEach(row => { [...tbody.rows].forEach(row => {
[...row.cells].forEach((cell, index) => { [...row.cells].forEach((cell, index) => {
if (cell.dataset.rowNumberColumn === '1') return;
const header = headerRow.cells[index]; const header = headerRow.cells[index];
if (header && !cell.dataset.field) { if (header && !cell.dataset.field) {
cell.dataset.field = header.dataset.field; cell.dataset.field = header.dataset.field;
} }
}); });
}); });
applyStoredColumnWidths();
} }
function numericFilterMatches(rawValue, rawTerm) { function numericFilterMatches(rawValue, rawTerm) {
@@ -405,6 +667,20 @@
else serverFilterTimer = window.setTimeout(navigate, 700); else serverFilterTimer = window.setTimeout(navigate, 700);
} }
function updateRowNumbers() {
let visibleIndex = 0;
[...tbody.rows].forEach(row => {
const cell = row.querySelector('td[data-row-number-column="1"]');
if (!cell) return;
if (row.hidden) {
cell.textContent = '';
return;
}
visibleIndex += 1;
cell.textContent = String(visibleIndex);
});
}
function applyFilters() { function applyFilters() {
rebuildMissingCellKeys(); rebuildMissingCellKeys();
@@ -419,6 +695,7 @@
const serverTotal = Number.parseInt(table.dataset.serverFilterTotal || '', 10); const serverTotal = Number.parseInt(table.dataset.serverFilterTotal || '', 10);
const total = Number.isFinite(serverTotal) ? serverTotal : tbody.rows.length; const total = Number.isFinite(serverTotal) ? serverTotal : tbody.rows.length;
updateFilterPresentation(active, tbody.rows.length, total); updateFilterPresentation(active, tbody.rows.length, total);
updateRowNumbers();
window.dispatchEvent(new CustomEvent('asset-table-filter-changed', { window.dispatchEvent(new CustomEvent('asset-table-filter-changed', {
detail: { detail: {
table, table,
@@ -447,6 +724,7 @@
}); });
updateFilterPresentation(active, visibleRows, tbody.rows.length); updateFilterPresentation(active, visibleRows, tbody.rows.length);
updateRowNumbers();
window.dispatchEvent(new CustomEvent('asset-table-filter-changed', { window.dispatchEvent(new CustomEvent('asset-table-filter-changed', {
detail: { detail: {
@@ -458,6 +736,43 @@
})); }));
} }
function applySort(field, direction, options = {}) {
const header = [...headerRow.cells].find(item => item.dataset.field === field);
if (!header || header.dataset.noSort) return false;
const columnIndex = header.cellIndex;
[...headerRow.cells].forEach(item => {
delete item.dataset.sortDirection;
item.classList.remove('sort-asc', 'sort-desc');
});
const normalizedDirection = direction === 'desc' ? 'desc' : 'asc';
header.dataset.sortDirection = normalizedDirection;
header.classList.add(normalizedDirection === 'asc' ? 'sort-asc' : 'sort-desc');
const rows = [...tbody.rows];
rows.sort((left, right) => {
const definition = { field, columnIndex };
const result = compareValues(
textValue(cellForDefinition(left, definition)),
textValue(cellForDefinition(right, definition))
);
return normalizedDirection === 'asc' ? result : -result;
});
const fragment = document.createDocumentFragment();
rows.forEach(row => fragment.appendChild(row));
tbody.appendChild(fragment);
applyFilters();
if (!options.silent) {
window.dispatchEvent(new CustomEvent('asset-table-sort-changed', {
detail: { table, field, direction: normalizedDirection }
}));
}
return true;
}
[...headerRow.cells].forEach((header, columnIndex) => { [...headerRow.cells].forEach((header, columnIndex) => {
const field = header.dataset.field || ''; const field = header.dataset.field || '';
const serverName = header.dataset.serverFilterName || ''; const serverName = header.dataset.serverFilterName || '';
@@ -468,37 +783,18 @@
header.addEventListener('click', event => { header.addEventListener('click', event => {
if (event.target.closest('input,button,a,select,textarea,label')) return; if (event.target.closest('input,button,a,select,textarea,label')) return;
const direction = header.dataset.sortDirection === 'asc' ? 'desc' : 'asc'; const direction = header.dataset.sortDirection === 'asc' ? 'desc' : 'asc';
applySort(field, direction);
[...headerRow.cells].forEach(item => {
delete item.dataset.sortDirection;
item.classList.remove('sort-asc', 'sort-desc');
});
header.dataset.sortDirection = direction;
header.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');
const rows = [...tbody.rows];
rows.sort((left, right) => {
const definition = { field, columnIndex };
const result = compareValues(
textValue(cellForDefinition(left, definition)),
textValue(cellForDefinition(right, definition))
);
return direction === 'asc' ? result : -result;
});
const fragment = document.createDocumentFragment();
rows.forEach(row => fragment.appendChild(row));
tbody.appendChild(fragment);
applyFilters();
}); });
} }
const filterCell = document.createElement('th'); const filterCell = document.createElement('th');
if (field) filterCell.dataset.field = field; if (field) filterCell.dataset.field = field;
filterCell.dataset.columnIndex = String(columnIndex); filterCell.dataset.columnIndex = String(columnIndex);
if (header.dataset.rowNumberColumn === '1') {
filterCell.dataset.rowNumberColumn = '1';
filterCell.className = 'table-row-number-filter';
}
if (!header.dataset.noFilter && (!usesServerFilters || serverName)) { if (!header.dataset.noFilter && (!usesServerFilters || serverName)) {
const input = document.createElement('input'); const input = document.createElement('input');
@@ -541,6 +837,8 @@
}); });
thead.appendChild(filterRow); thead.appendChild(filterRow);
installColumnResizers();
applyStoredColumnWidths();
// Editable cells are always read live, so changed values immediately // Editable cells are always read live, so changed values immediately
// participate in filtering without a stale cache. // participate in filtering without a stale cache.
@@ -553,6 +851,8 @@
}); });
table.assetApplyFilters = applyFilters; table.assetApplyFilters = applyFilters;
table.assetApplySort = (field, direction, options = {}) => applySort(field, direction, options);
table.assetUpdateRowNumbers = updateRowNumbers;
table.assetRebuildFilterCache = rebuildMissingCellKeys; table.assetRebuildFilterCache = rebuildMissingCellKeys;
table.dataset.tableToolsReady = '1'; table.dataset.tableToolsReady = '1';
ensureFilterStatus(); ensureFilterStatus();
@@ -586,7 +886,13 @@
const table = event.detail?.table; const table = event.detail?.table;
if (table?.assetRebuildFilterCache) { if (table?.assetRebuildFilterCache) {
table.assetRebuildFilterCache(); table.assetRebuildFilterCache();
table.assetApplyFilters?.(); const sortedHeader = table.tHead?.rows?.[0]?.querySelector('[data-sort-direction]');
if (sortedHeader?.dataset.field && table.assetApplySort) {
table.assetApplySort(sortedHeader.dataset.field, sortedHeader.dataset.sortDirection, {silent: true});
} else {
table.assetApplyFilters?.();
}
table.assetUpdateRowNumbers?.();
} }
}); });
})(); })();
+1 -1
View File
@@ -14,7 +14,7 @@
--am-warning:{{ colors.get('warning_color', '#d97706') }}; --am-warning:{{ colors.get('warning_color', '#d97706') }};
--am-danger:{{ colors.get('danger_color', '#b42318') }}; --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-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"> <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> <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>
+1 -2
View File
@@ -1,2 +1 @@
APP_VERSION = "0.5.5.40" APP_VERSION = "0.5.5.43"
__version__ = APP_VERSION
+1
View File
@@ -162,3 +162,4 @@ See [UPDATE-NOTE.md](UPDATE-NOTE.md).
- [0.5.5.39](UPDATE-0.5.5.39.md) Privacy read-only tabs and permission-aware dashboard counts. - [0.5.5.39](UPDATE-0.5.5.39.md) Privacy read-only tabs and permission-aware dashboard counts.
- [0.5.5.40](UPDATE-0.5.5.40.md) Privacy exports available to all authenticated users. - [0.5.5.40](UPDATE-0.5.5.40.md) Privacy exports available to all authenticated users.
- [0.5.5.41](UPDATE-0.5.5.41.md) Dark-mode filter readability, persisted table sorting, and generated row numbers.
+8
View File
@@ -0,0 +1,8 @@
# Version 0.5.5.41
## Changes
- Improved readability of chart status and category filter options in dark mode.
- Table sorting is now stored per table in browser state and restored when the user returns.
- Every standard data table now receives a generated first column with visual row numbers.
- Row numbers are recalculated after filtering, sorting, and partial server-side table refreshes and are not database identifiers.
+7
View File
@@ -0,0 +1,7 @@
# Version 0.5.5.43
- Added drag handles to resize every standard table column.
- Stored column widths independently for each table and column.
- Kept the asset list's existing horizontal-scroll width calculation in sync.
- Added keyboard resizing with the left and right arrow keys.
- Added double-click on a separator to reset a column to its default width.