Merge pull request #12 from TauNeutrino/fix-xss-render-history-904859585010159921
🔒 Fix XSS Vulnerability in renderHistory
This commit is contained in:
175
dist/kantine.bundle.js
vendored
175
dist/kantine.bundle.js
vendored
@@ -273,87 +273,154 @@ function renderHistory(orders) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
content.innerHTML = '';
|
||||||
const sortedYears = Object.keys(groups).sort((a, b) => b - a);
|
const sortedYears = Object.keys(groups).sort((a, b) => b - a);
|
||||||
let html = '';
|
|
||||||
|
|
||||||
sortedYears.forEach(yKey => {
|
sortedYears.forEach(yKey => {
|
||||||
const yearGroup = groups[yKey];
|
const yearGroup = groups[yKey];
|
||||||
html += `<div class="history-year-group">
|
const yearGroupDiv = document.createElement('div');
|
||||||
<h2 class="history-year-header">${yearGroup.year}</h2>`;
|
yearGroupDiv.className = 'history-year-group';
|
||||||
|
|
||||||
|
const yearHeader = document.createElement('h2');
|
||||||
|
yearHeader.className = 'history-year-header';
|
||||||
|
yearHeader.textContent = yearGroup.year;
|
||||||
|
yearGroupDiv.appendChild(yearHeader);
|
||||||
|
|
||||||
const sortedMonths = Object.keys(yearGroup.months).sort((a, b) => b.localeCompare(a));
|
const sortedMonths = Object.keys(yearGroup.months).sort((a, b) => b.localeCompare(a));
|
||||||
|
|
||||||
sortedMonths.forEach(mKey => {
|
sortedMonths.forEach(mKey => {
|
||||||
const monthGroup = yearGroup.months[mKey];
|
const monthGroup = yearGroup.months[mKey];
|
||||||
|
|
||||||
html += `<div class="history-month-group">
|
const monthGroupDiv = document.createElement('div');
|
||||||
<div class="history-month-header" tabindex="0" role="button" aria-expanded="false" title="Klicken, um die Bestellungen für diesen Monat ein-/auszublenden">
|
monthGroupDiv.className = 'history-month-group';
|
||||||
<div style="display:flex; flex-direction:column; gap:4px;">
|
|
||||||
<span>${monthGroup.name}</span>
|
const monthHeader = document.createElement('div');
|
||||||
<div class="history-month-summary">
|
monthHeader.className = 'history-month-header';
|
||||||
<span>${monthGroup.count} Bestellungen • <strong>€${monthGroup.total.toFixed(2)}</strong></span>
|
monthHeader.setAttribute('tabindex', '0');
|
||||||
</div>
|
monthHeader.setAttribute('role', 'button');
|
||||||
</div>
|
monthHeader.setAttribute('aria-expanded', 'false');
|
||||||
<span class="material-icons-round">expand_more</span>
|
monthHeader.setAttribute('title', 'Klicken, um die Bestellungen für diesen Monat ein-/auszublenden');
|
||||||
</div>
|
|
||||||
<div class="history-month-content">`;
|
const monthHeaderContent = document.createElement('div');
|
||||||
|
monthHeaderContent.style.display = 'flex';
|
||||||
|
monthHeaderContent.style.flexDirection = 'column';
|
||||||
|
monthHeaderContent.style.gap = '4px';
|
||||||
|
|
||||||
|
const monthNameSpan = document.createElement('span');
|
||||||
|
monthNameSpan.textContent = monthGroup.name;
|
||||||
|
monthHeaderContent.appendChild(monthNameSpan);
|
||||||
|
|
||||||
|
const monthSummary = document.createElement('div');
|
||||||
|
monthSummary.className = 'history-month-summary';
|
||||||
|
|
||||||
|
const monthSummarySpan = document.createElement('span');
|
||||||
|
monthSummarySpan.innerHTML = `${monthGroup.count} Bestellungen • <strong>€${monthGroup.total.toFixed(2)}</strong>`;
|
||||||
|
monthSummary.appendChild(monthSummarySpan);
|
||||||
|
|
||||||
|
monthHeaderContent.appendChild(monthSummary);
|
||||||
|
monthHeader.appendChild(monthHeaderContent);
|
||||||
|
|
||||||
|
const expandIcon = document.createElement('span');
|
||||||
|
expandIcon.className = 'material-icons-round';
|
||||||
|
expandIcon.textContent = 'expand_more';
|
||||||
|
monthHeader.appendChild(expandIcon);
|
||||||
|
|
||||||
|
monthHeader.addEventListener('click', () => {
|
||||||
|
const parentGroup = monthHeader.parentElement;
|
||||||
|
const isOpen = parentGroup.classList.contains('open');
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
parentGroup.classList.remove('open');
|
||||||
|
monthHeader.setAttribute('aria-expanded', 'false');
|
||||||
|
} else {
|
||||||
|
parentGroup.classList.add('open');
|
||||||
|
monthHeader.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
monthGroupDiv.appendChild(monthHeader);
|
||||||
|
|
||||||
|
const monthContentDiv = document.createElement('div');
|
||||||
|
monthContentDiv.className = 'history-month-content';
|
||||||
|
|
||||||
const sortedKWs = Object.keys(monthGroup.weeks).sort((a, b) => parseInt(b) - parseInt(a));
|
const sortedKWs = Object.keys(monthGroup.weeks).sort((a, b) => parseInt(b) - parseInt(a));
|
||||||
|
|
||||||
sortedKWs.forEach(kw => {
|
sortedKWs.forEach(kw => {
|
||||||
const week = monthGroup.weeks[kw];
|
const week = monthGroup.weeks[kw];
|
||||||
html += `<div class="history-week-group">
|
const weekGroupDiv = document.createElement('div');
|
||||||
<div class="history-week-header">
|
weekGroupDiv.className = 'history-week-group';
|
||||||
<strong>${week.label}</strong>
|
|
||||||
<span>${week.count} Bestellungen • <strong>€${week.total.toFixed(2)}</strong></span>
|
const weekHeader = document.createElement('div');
|
||||||
</div>`;
|
weekHeader.className = 'history-week-header';
|
||||||
|
|
||||||
|
const weekLabel = document.createElement('strong');
|
||||||
|
weekLabel.textContent = week.label;
|
||||||
|
weekHeader.appendChild(weekLabel);
|
||||||
|
|
||||||
|
const weekSummary = document.createElement('span');
|
||||||
|
weekSummary.innerHTML = `${week.count} Bestellungen • <strong>€${week.total.toFixed(2)}</strong>`;
|
||||||
|
weekHeader.appendChild(weekSummary);
|
||||||
|
|
||||||
|
weekGroupDiv.appendChild(weekHeader);
|
||||||
|
|
||||||
week.items.forEach(item => {
|
week.items.forEach(item => {
|
||||||
const dateObj = new Date(item.date);
|
const dateObj = new Date(item.date);
|
||||||
const dayStr = dateObj.toLocaleDateString('de-AT', { weekday: 'short', day: '2-digit', month: '2-digit' });
|
const dayStr = dateObj.toLocaleDateString('de-AT', { weekday: 'short', day: '2-digit', month: '2-digit' });
|
||||||
|
|
||||||
let statusBadge = '';
|
const historyItem = document.createElement('div');
|
||||||
|
historyItem.className = 'history-item';
|
||||||
if (item.state === 9) {
|
if (item.state === 9) {
|
||||||
statusBadge = '<span class="history-item-status">Storniert</span>';
|
historyItem.classList.add('history-item-cancelled');
|
||||||
} else if (item.state === 8) {
|
|
||||||
statusBadge = '<span class="history-item-status">Abgeschlossen</span>';
|
|
||||||
} else {
|
|
||||||
statusBadge = '<span class="history-item-status">Übertragen</span>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `
|
const dateDiv = document.createElement('div');
|
||||||
<div class="history-item ${item.state === 9 ? 'history-item-cancelled' : ''}">
|
dateDiv.style.fontSize = '0.85rem';
|
||||||
<div style="font-size: 0.85rem; color: var(--text-secondary);">${dayStr}</div>
|
dateDiv.style.color = 'var(--text-secondary)';
|
||||||
<div class="history-item-details">
|
dateDiv.textContent = dayStr;
|
||||||
<span class="history-item-name">${(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__/* .escapeHtml */ .ZD)(item.name)}</span>
|
historyItem.appendChild(dateDiv);
|
||||||
<div>${statusBadge}</div>
|
|
||||||
</div>
|
const detailsDiv = document.createElement('div');
|
||||||
<div class="history-item-price ${item.state === 9 ? 'history-item-price-cancelled' : ''}">€${item.price.toFixed(2)}</div>
|
detailsDiv.className = 'history-item-details';
|
||||||
</div>`;
|
|
||||||
|
const nameSpan = document.createElement('span');
|
||||||
|
nameSpan.className = 'history-item-name';
|
||||||
|
nameSpan.textContent = item.name;
|
||||||
|
detailsDiv.appendChild(nameSpan);
|
||||||
|
|
||||||
|
const statusDiv = document.createElement('div');
|
||||||
|
const statusSpan = document.createElement('span');
|
||||||
|
statusSpan.className = 'history-item-status';
|
||||||
|
if (item.state === 9) {
|
||||||
|
statusSpan.textContent = 'Storniert';
|
||||||
|
} else if (item.state === 8) {
|
||||||
|
statusSpan.textContent = 'Abgeschlossen';
|
||||||
|
} else {
|
||||||
|
statusSpan.textContent = 'Übertragen';
|
||||||
|
}
|
||||||
|
statusDiv.appendChild(statusSpan);
|
||||||
|
detailsDiv.appendChild(statusDiv);
|
||||||
|
|
||||||
|
historyItem.appendChild(detailsDiv);
|
||||||
|
|
||||||
|
const priceDiv = document.createElement('div');
|
||||||
|
priceDiv.className = 'history-item-price';
|
||||||
|
if (item.state === 9) {
|
||||||
|
priceDiv.classList.add('history-item-price-cancelled');
|
||||||
|
}
|
||||||
|
priceDiv.textContent = `€${item.price.toFixed(2)}`;
|
||||||
|
historyItem.appendChild(priceDiv);
|
||||||
|
|
||||||
|
weekGroupDiv.appendChild(historyItem);
|
||||||
});
|
});
|
||||||
html += `</div>`;
|
|
||||||
|
monthContentDiv.appendChild(weekGroupDiv);
|
||||||
});
|
});
|
||||||
html += `</div></div>`;
|
|
||||||
|
monthGroupDiv.appendChild(monthContentDiv);
|
||||||
|
yearGroupDiv.appendChild(monthGroupDiv);
|
||||||
});
|
});
|
||||||
html += `</div>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
content.innerHTML = html;
|
content.appendChild(yearGroupDiv);
|
||||||
|
|
||||||
const monthHeaders = content.querySelectorAll('.history-month-header');
|
|
||||||
monthHeaders.forEach(header => {
|
|
||||||
header.addEventListener('click', () => {
|
|
||||||
const parentGroup = header.parentElement;
|
|
||||||
const isOpen = parentGroup.classList.contains('open');
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
parentGroup.classList.remove('open');
|
|
||||||
header.setAttribute('aria-expanded', 'false');
|
|
||||||
} else {
|
|
||||||
parentGroup.classList.add('open');
|
|
||||||
header.setAttribute('aria-expanded', 'true');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "kantine-wrapper",
|
"name": "app",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
175
src/actions.js
175
src/actions.js
@@ -241,87 +241,154 @@ export function renderHistory(orders) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
content.innerHTML = '';
|
||||||
const sortedYears = Object.keys(groups).sort((a, b) => b - a);
|
const sortedYears = Object.keys(groups).sort((a, b) => b - a);
|
||||||
let html = '';
|
|
||||||
|
|
||||||
sortedYears.forEach(yKey => {
|
sortedYears.forEach(yKey => {
|
||||||
const yearGroup = groups[yKey];
|
const yearGroup = groups[yKey];
|
||||||
html += `<div class="history-year-group">
|
const yearGroupDiv = document.createElement('div');
|
||||||
<h2 class="history-year-header">${yearGroup.year}</h2>`;
|
yearGroupDiv.className = 'history-year-group';
|
||||||
|
|
||||||
|
const yearHeader = document.createElement('h2');
|
||||||
|
yearHeader.className = 'history-year-header';
|
||||||
|
yearHeader.textContent = yearGroup.year;
|
||||||
|
yearGroupDiv.appendChild(yearHeader);
|
||||||
|
|
||||||
const sortedMonths = Object.keys(yearGroup.months).sort((a, b) => b.localeCompare(a));
|
const sortedMonths = Object.keys(yearGroup.months).sort((a, b) => b.localeCompare(a));
|
||||||
|
|
||||||
sortedMonths.forEach(mKey => {
|
sortedMonths.forEach(mKey => {
|
||||||
const monthGroup = yearGroup.months[mKey];
|
const monthGroup = yearGroup.months[mKey];
|
||||||
|
|
||||||
html += `<div class="history-month-group">
|
const monthGroupDiv = document.createElement('div');
|
||||||
<div class="history-month-header" tabindex="0" role="button" aria-expanded="false" title="Klicken, um die Bestellungen für diesen Monat ein-/auszublenden">
|
monthGroupDiv.className = 'history-month-group';
|
||||||
<div style="display:flex; flex-direction:column; gap:4px;">
|
|
||||||
<span>${monthGroup.name}</span>
|
const monthHeader = document.createElement('div');
|
||||||
<div class="history-month-summary">
|
monthHeader.className = 'history-month-header';
|
||||||
<span>${monthGroup.count} Bestellungen • <strong>€${monthGroup.total.toFixed(2)}</strong></span>
|
monthHeader.setAttribute('tabindex', '0');
|
||||||
</div>
|
monthHeader.setAttribute('role', 'button');
|
||||||
</div>
|
monthHeader.setAttribute('aria-expanded', 'false');
|
||||||
<span class="material-icons-round">expand_more</span>
|
monthHeader.setAttribute('title', 'Klicken, um die Bestellungen für diesen Monat ein-/auszublenden');
|
||||||
</div>
|
|
||||||
<div class="history-month-content">`;
|
const monthHeaderContent = document.createElement('div');
|
||||||
|
monthHeaderContent.style.display = 'flex';
|
||||||
|
monthHeaderContent.style.flexDirection = 'column';
|
||||||
|
monthHeaderContent.style.gap = '4px';
|
||||||
|
|
||||||
|
const monthNameSpan = document.createElement('span');
|
||||||
|
monthNameSpan.textContent = monthGroup.name;
|
||||||
|
monthHeaderContent.appendChild(monthNameSpan);
|
||||||
|
|
||||||
|
const monthSummary = document.createElement('div');
|
||||||
|
monthSummary.className = 'history-month-summary';
|
||||||
|
|
||||||
|
const monthSummarySpan = document.createElement('span');
|
||||||
|
monthSummarySpan.innerHTML = `${monthGroup.count} Bestellungen • <strong>€${monthGroup.total.toFixed(2)}</strong>`;
|
||||||
|
monthSummary.appendChild(monthSummarySpan);
|
||||||
|
|
||||||
|
monthHeaderContent.appendChild(monthSummary);
|
||||||
|
monthHeader.appendChild(monthHeaderContent);
|
||||||
|
|
||||||
|
const expandIcon = document.createElement('span');
|
||||||
|
expandIcon.className = 'material-icons-round';
|
||||||
|
expandIcon.textContent = 'expand_more';
|
||||||
|
monthHeader.appendChild(expandIcon);
|
||||||
|
|
||||||
|
monthHeader.addEventListener('click', () => {
|
||||||
|
const parentGroup = monthHeader.parentElement;
|
||||||
|
const isOpen = parentGroup.classList.contains('open');
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
parentGroup.classList.remove('open');
|
||||||
|
monthHeader.setAttribute('aria-expanded', 'false');
|
||||||
|
} else {
|
||||||
|
parentGroup.classList.add('open');
|
||||||
|
monthHeader.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
monthGroupDiv.appendChild(monthHeader);
|
||||||
|
|
||||||
|
const monthContentDiv = document.createElement('div');
|
||||||
|
monthContentDiv.className = 'history-month-content';
|
||||||
|
|
||||||
const sortedKWs = Object.keys(monthGroup.weeks).sort((a, b) => parseInt(b) - parseInt(a));
|
const sortedKWs = Object.keys(monthGroup.weeks).sort((a, b) => parseInt(b) - parseInt(a));
|
||||||
|
|
||||||
sortedKWs.forEach(kw => {
|
sortedKWs.forEach(kw => {
|
||||||
const week = monthGroup.weeks[kw];
|
const week = monthGroup.weeks[kw];
|
||||||
html += `<div class="history-week-group">
|
const weekGroupDiv = document.createElement('div');
|
||||||
<div class="history-week-header">
|
weekGroupDiv.className = 'history-week-group';
|
||||||
<strong>${week.label}</strong>
|
|
||||||
<span>${week.count} Bestellungen • <strong>€${week.total.toFixed(2)}</strong></span>
|
const weekHeader = document.createElement('div');
|
||||||
</div>`;
|
weekHeader.className = 'history-week-header';
|
||||||
|
|
||||||
|
const weekLabel = document.createElement('strong');
|
||||||
|
weekLabel.textContent = week.label;
|
||||||
|
weekHeader.appendChild(weekLabel);
|
||||||
|
|
||||||
|
const weekSummary = document.createElement('span');
|
||||||
|
weekSummary.innerHTML = `${week.count} Bestellungen • <strong>€${week.total.toFixed(2)}</strong>`;
|
||||||
|
weekHeader.appendChild(weekSummary);
|
||||||
|
|
||||||
|
weekGroupDiv.appendChild(weekHeader);
|
||||||
|
|
||||||
week.items.forEach(item => {
|
week.items.forEach(item => {
|
||||||
const dateObj = new Date(item.date);
|
const dateObj = new Date(item.date);
|
||||||
const dayStr = dateObj.toLocaleDateString('de-AT', { weekday: 'short', day: '2-digit', month: '2-digit' });
|
const dayStr = dateObj.toLocaleDateString('de-AT', { weekday: 'short', day: '2-digit', month: '2-digit' });
|
||||||
|
|
||||||
let statusBadge = '';
|
const historyItem = document.createElement('div');
|
||||||
|
historyItem.className = 'history-item';
|
||||||
if (item.state === 9) {
|
if (item.state === 9) {
|
||||||
statusBadge = '<span class="history-item-status">Storniert</span>';
|
historyItem.classList.add('history-item-cancelled');
|
||||||
} else if (item.state === 8) {
|
|
||||||
statusBadge = '<span class="history-item-status">Abgeschlossen</span>';
|
|
||||||
} else {
|
|
||||||
statusBadge = '<span class="history-item-status">Übertragen</span>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `
|
const dateDiv = document.createElement('div');
|
||||||
<div class="history-item ${item.state === 9 ? 'history-item-cancelled' : ''}">
|
dateDiv.style.fontSize = '0.85rem';
|
||||||
<div style="font-size: 0.85rem; color: var(--text-secondary);">${dayStr}</div>
|
dateDiv.style.color = 'var(--text-secondary)';
|
||||||
<div class="history-item-details">
|
dateDiv.textContent = dayStr;
|
||||||
<span class="history-item-name">${escapeHtml(item.name)}</span>
|
historyItem.appendChild(dateDiv);
|
||||||
<div>${statusBadge}</div>
|
|
||||||
</div>
|
const detailsDiv = document.createElement('div');
|
||||||
<div class="history-item-price ${item.state === 9 ? 'history-item-price-cancelled' : ''}">€${item.price.toFixed(2)}</div>
|
detailsDiv.className = 'history-item-details';
|
||||||
</div>`;
|
|
||||||
|
const nameSpan = document.createElement('span');
|
||||||
|
nameSpan.className = 'history-item-name';
|
||||||
|
nameSpan.textContent = item.name;
|
||||||
|
detailsDiv.appendChild(nameSpan);
|
||||||
|
|
||||||
|
const statusDiv = document.createElement('div');
|
||||||
|
const statusSpan = document.createElement('span');
|
||||||
|
statusSpan.className = 'history-item-status';
|
||||||
|
if (item.state === 9) {
|
||||||
|
statusSpan.textContent = 'Storniert';
|
||||||
|
} else if (item.state === 8) {
|
||||||
|
statusSpan.textContent = 'Abgeschlossen';
|
||||||
|
} else {
|
||||||
|
statusSpan.textContent = 'Übertragen';
|
||||||
|
}
|
||||||
|
statusDiv.appendChild(statusSpan);
|
||||||
|
detailsDiv.appendChild(statusDiv);
|
||||||
|
|
||||||
|
historyItem.appendChild(detailsDiv);
|
||||||
|
|
||||||
|
const priceDiv = document.createElement('div');
|
||||||
|
priceDiv.className = 'history-item-price';
|
||||||
|
if (item.state === 9) {
|
||||||
|
priceDiv.classList.add('history-item-price-cancelled');
|
||||||
|
}
|
||||||
|
priceDiv.textContent = `€${item.price.toFixed(2)}`;
|
||||||
|
historyItem.appendChild(priceDiv);
|
||||||
|
|
||||||
|
weekGroupDiv.appendChild(historyItem);
|
||||||
});
|
});
|
||||||
html += `</div>`;
|
|
||||||
|
monthContentDiv.appendChild(weekGroupDiv);
|
||||||
});
|
});
|
||||||
html += `</div></div>`;
|
|
||||||
|
monthGroupDiv.appendChild(monthContentDiv);
|
||||||
|
yearGroupDiv.appendChild(monthGroupDiv);
|
||||||
});
|
});
|
||||||
html += `</div>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
content.innerHTML = html;
|
content.appendChild(yearGroupDiv);
|
||||||
|
|
||||||
const monthHeaders = content.querySelectorAll('.history-month-header');
|
|
||||||
monthHeaders.forEach(header => {
|
|
||||||
header.addEventListener('click', () => {
|
|
||||||
const parentGroup = header.parentElement;
|
|
||||||
const isOpen = parentGroup.classList.contains('open');
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
parentGroup.classList.remove('open');
|
|
||||||
header.setAttribute('aria-expanded', 'false');
|
|
||||||
} else {
|
|
||||||
parentGroup.classList.add('open');
|
|
||||||
header.setAttribute('aria-expanded', 'true');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user