fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

View File

@@ -1,5 +1,3 @@
import { Module } from '../core/module.js';
/**
* Admin Data Table Module
*
@@ -9,9 +7,9 @@ import { Module } from '../core/module.js';
* - Search/filtering
* - Auto-refresh
*/
export class AdminDataTable extends Module {
export class AdminDataTable {
constructor(element) {
super(element);
this.element = element;
this.resource = element.dataset.resource;
this.apiEndpoint = element.dataset.apiEndpoint;
@@ -26,11 +24,6 @@ export class AdminDataTable extends Module {
}
async init() {
if (!this.apiEndpoint) {
console.warn('AdminDataTable: No API endpoint configured');
return;
}
this.tbody = this.element.querySelector('tbody');
this.thead = this.element.querySelector('thead');
@@ -39,27 +32,38 @@ export class AdminDataTable extends Module {
return;
}
// Always setup event listeners for sorting, even if data is already loaded
this.setupEventListeners();
// Only load data if table is initially empty
if (this.tbody.children.length === 0) {
// Only load data via AJAX if API endpoint is configured and table is empty
if (this.apiEndpoint && this.tbody.children.length === 0) {
await this.loadData();
}
}
setupEventListeners() {
// Sortable columns
if (this.sortable && this.thead) {
this.thead.querySelectorAll('th').forEach((th, index) => {
const columnKey = th.dataset.column || this.getColumnKeyFromIndex(index);
// Sortable columns - only for columns with data-column attribute
// Check both table-level sortable flag and individual column sortable attribute
if (this.thead) {
this.thead.querySelectorAll('th[data-column]').forEach((th) => {
const columnKey = th.dataset.column;
if (columnKey) {
th.style.cursor = 'pointer';
th.classList.add('sortable');
// Only make sortable if table-level sortable is enabled OR column has data-column
if (this.sortable || th.hasAttribute('data-column')) {
th.style.cursor = 'pointer';
th.classList.add('sortable');
th.addEventListener('click', () => {
this.sortByColumn(columnKey, th);
});
th.addEventListener('click', () => {
// If API endpoint is available, use AJAX sorting
if (this.apiEndpoint) {
this.sortByColumn(columnKey, th);
} else {
// Otherwise, do client-side sorting
this.sortByColumnClientSide(columnKey, th);
}
});
}
}
});
}
@@ -114,8 +118,49 @@ export class AdminDataTable extends Module {
th.dataset.sortDir = newDir;
th.classList.add(`sort-${newDir}`);
// Reload data
await this.loadData();
// Reload data if API endpoint is available
if (this.apiEndpoint) {
await this.loadData();
}
}
sortByColumnClientSide(columnKey, th) {
// Client-side sorting for tables without API endpoint
const currentDir = th.dataset.sortDir;
const newDir = currentDir === 'asc' ? 'desc' : 'asc';
// Update UI
this.thead.querySelectorAll('th').forEach(header => {
header.removeAttribute('data-sort-dir');
header.classList.remove('sort-asc', 'sort-desc');
});
th.dataset.sortDir = newDir;
th.classList.add(`sort-${newDir}`);
// Get all rows
const rows = Array.from(this.tbody.querySelectorAll('tr'));
const columnIndex = Array.from(this.thead.querySelectorAll('th')).indexOf(th);
// Sort rows
rows.sort((a, b) => {
const aValue = a.cells[columnIndex]?.textContent?.trim() || '';
const bValue = b.cells[columnIndex]?.textContent?.trim() || '';
// Try numeric comparison first
const aNum = parseFloat(aValue);
const bNum = parseFloat(bValue);
if (!isNaN(aNum) && !isNaN(bNum)) {
return newDir === 'asc' ? aNum - bNum : bNum - aNum;
}
// String comparison
const comparison = aValue.localeCompare(bValue);
return newDir === 'asc' ? comparison : -comparison;
});
// Re-append sorted rows
rows.forEach(row => this.tbody.appendChild(row));
}
async loadData() {
@@ -138,7 +183,17 @@ export class AdminDataTable extends Module {
try {
this.setLoadingState(true);
const response = await fetch(url);
const response = await fetch(url, {
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
@@ -152,7 +207,20 @@ export class AdminDataTable extends Module {
}
} catch (error) {
console.error('AdminDataTable: Failed to load data', error);
this.showError('Failed to load data');
console.error('AdminDataTable: URL was', url);
// Fallback to client-side sorting if API fails
if (this.currentSort && this.tbody.children.length > 0) {
console.warn('AdminDataTable: API failed, falling back to client-side sorting');
const th = this.thead.querySelector(`th[data-column="${this.currentSort.column}"]`);
if (th) {
this.sortByColumnClientSide(this.currentSort.column, th);
} else {
this.showError('Failed to load data. Please refresh the page.');
}
} else {
this.showError('Failed to load data');
}
} finally {
this.setLoadingState(false);
}
@@ -288,8 +356,19 @@ export class AdminDataTable extends Module {
}
// Auto-initialize all admin data tables
// Initialize tables with API endpoints (AJAX loading)
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-resource][data-api-endpoint]').forEach(table => {
new AdminDataTable(table).init();
});
// Also initialize tables with sortable attribute but without API endpoint (client-side sorting)
document.querySelectorAll('table.admin-table[data-sortable="true"]').forEach(table => {
if (!table.dataset.apiEndpoint) {
// Create a minimal AdminDataTable instance for client-side sorting
const dataTable = new AdminDataTable(table);
dataTable.sortable = true;
dataTable.init();
}
});
});