replaced onclick handlers with event listeners to resolve possible xss vector from single quotes
This commit is contained in:
parent
7f94597706
commit
95b1a8507b
1 changed files with 52 additions and 7 deletions
|
|
@ -1834,9 +1834,22 @@ function _genreWhitelistRender(genres) {
|
||||||
const searchVal = (document.getElementById('genre-whitelist-search')?.value || '').toLowerCase();
|
const searchVal = (document.getElementById('genre-whitelist-search')?.value || '').toLowerCase();
|
||||||
const filtered = searchVal ? _genreWhitelistCache.filter(g => g.toLowerCase().includes(searchVal)) : _genreWhitelistCache;
|
const filtered = searchVal ? _genreWhitelistCache.filter(g => g.toLowerCase().includes(searchVal)) : _genreWhitelistCache;
|
||||||
container.innerHTML = filtered.map(g =>
|
container.innerHTML = filtered.map(g =>
|
||||||
`<span class="genre-chip">${escapeHtml(g)}<button class="genre-chip-x" onclick="_genreWhitelistRemove('${escapeHtml(g.replace(/'/g, "\\'"))}')">×</button></span>`
|
`<span class="genre-chip">${escapeHtml(g)}<button class="genre-chip-x" data-genre="${escapeHtml(g)}">×</button></span>`
|
||||||
).join('');
|
).join('');
|
||||||
if (countEl) countEl.textContent = `${_genreWhitelistCache.length} genres`;
|
if (countEl) countEl.textContent = `${_genreWhitelistCache.length} genres`;
|
||||||
|
_initGenreChipClickHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _initGenreChipClickHandler() {
|
||||||
|
const container = document.getElementById('genre-whitelist-chips');
|
||||||
|
if (!container) return;
|
||||||
|
container.onclick = (e) => {
|
||||||
|
const btn = e.target.closest('.genre-chip-x');
|
||||||
|
if (btn) {
|
||||||
|
e.preventDefault();
|
||||||
|
_genreWhitelistRemove(btn.dataset.genre);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function _genreWhitelistRemove(genre) {
|
function _genreWhitelistRemove(genre) {
|
||||||
|
|
@ -2787,19 +2800,32 @@ function renderApiKeys(keys) {
|
||||||
container.innerHTML = keys.map(k => `
|
container.innerHTML = keys.map(k => `
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; margin-bottom: 4px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 6px;">
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; margin-bottom: 4px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 6px;">
|
||||||
<div style="flex: 1; min-width: 0;">
|
<div style="flex: 1; min-width: 0;">
|
||||||
<div style="font-size: 13px; color: #e0e0e0; font-weight: 500;">${k.label || 'Unnamed'}</div>
|
<div style="font-size: 13px; color: #e0e0e0; font-weight: 500;">${escapeHtml(k.label || 'Unnamed')}</div>
|
||||||
<div style="font-size: 11px; color: #666; margin-top: 2px;">
|
<div style="font-size: 11px; color: #666; margin-top: 2px;">
|
||||||
<code>${k.key_prefix || 'sk_...'}...</code>
|
<code>${escapeHtml(k.key_prefix || 'sk_...')}...</code>
|
||||||
· Created ${k.created_at ? new Date(k.created_at).toLocaleDateString() : 'unknown'}
|
· Created ${k.created_at ? new Date(k.created_at).toLocaleDateString() : 'unknown'}
|
||||||
${k.last_used_at ? '· Last used ' + new Date(k.last_used_at).toLocaleDateString() : ''}
|
${k.last_used_at ? '· Last used ' + new Date(k.last_used_at).toLocaleDateString() : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="revokeApiKey('${k.id}', '${(k.label || 'this key').replace(/'/g, "\\'")}')"
|
<button class="revoke-api-key-btn" data-key-id="${escapeHtml(k.id)}" data-key-label="${escapeHtml(k.label || 'this key')}"
|
||||||
style="padding: 4px 10px; background: rgba(255,82,82,0.1); border: 1px solid rgba(255,82,82,0.2); color: #ff5252; border-radius: 4px; cursor: pointer; font-size: 11px; white-space: nowrap;">
|
style="padding: 4px 10px; background: rgba(255,82,82,0.1); border: 1px solid rgba(255,82,82,0.2); color: #ff5252; border-radius: 4px; cursor: pointer; font-size: 11px; white-space: nowrap;">
|
||||||
Revoke
|
Revoke
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
_initApiKeyClickHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _initApiKeyClickHandler() {
|
||||||
|
const container = document.getElementById('api-keys-list');
|
||||||
|
if (!container) return;
|
||||||
|
container.onclick = (e) => {
|
||||||
|
const btn = e.target.closest('.revoke-api-key-btn');
|
||||||
|
if (btn) {
|
||||||
|
e.preventDefault();
|
||||||
|
revokeApiKey(btn.dataset.keyId, btn.dataset.keyLabel);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateApiKey() {
|
async function generateApiKey() {
|
||||||
|
|
@ -3291,16 +3317,17 @@ async function loadHiFiInstances() {
|
||||||
listEl.innerHTML = data.instances.map((inst, i) => {
|
listEl.innerHTML = data.instances.map((inst, i) => {
|
||||||
const enabledClass = inst.enabled ? '' : 'hifi-instance-disabled';
|
const enabledClass = inst.enabled ? '' : 'hifi-instance-disabled';
|
||||||
const checkHtml = inst.enabled
|
const checkHtml = inst.enabled
|
||||||
? `<span class="hifi-instance-toggle on" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to disable">✔</span>`
|
? `<span class="hifi-instance-toggle on" data-url="${escapeHtml(inst.url)}" title="Click to disable">✔</span>`
|
||||||
: `<span class="hifi-instance-toggle off" onclick="toggleHiFiInstance('${escapeHtml(inst.url)}')" title="Click to enable">✘</span>`;
|
: `<span class="hifi-instance-toggle off" data-url="${escapeHtml(inst.url)}" title="Click to enable">✘</span>`;
|
||||||
return `<div class="hifi-instance-item${inst.enabled ? '' : ' disabled'}" draggable="true" data-url="${escapeHtml(inst.url)}">
|
return `<div class="hifi-instance-item${inst.enabled ? '' : ' disabled'}" draggable="true" data-url="${escapeHtml(inst.url)}">
|
||||||
<span class="hifi-instance-grip">☰</span>
|
<span class="hifi-instance-grip">☰</span>
|
||||||
<span class="hifi-instance-url">${escapeHtml(inst.url)}</span>
|
<span class="hifi-instance-url">${escapeHtml(inst.url)}</span>
|
||||||
${checkHtml}
|
${checkHtml}
|
||||||
<span class="hifi-instance-remove" onclick="removeHiFiInstance('${escapeHtml(inst.url)}')" title="Remove instance">✖</span>
|
<span class="hifi-instance-remove" data-url="${escapeHtml(inst.url)}" title="Remove instance">✖</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
_initHiFiDragDrop();
|
_initHiFiDragDrop();
|
||||||
|
_initHiFiClickHandlers();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
listEl.innerHTML = `<div style="color:#f44336;font-size:0.85em;">Error loading instances: ${escapeHtml(e.message)}</div>`;
|
listEl.innerHTML = `<div style="color:#f44336;font-size:0.85em;">Error loading instances: ${escapeHtml(e.message)}</div>`;
|
||||||
}
|
}
|
||||||
|
|
@ -3349,6 +3376,24 @@ function _initHiFiDragDrop() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _initHiFiClickHandlers() {
|
||||||
|
const listEl = document.getElementById('hifi-instances-list');
|
||||||
|
if (!listEl) return;
|
||||||
|
listEl.onclick = (e) => {
|
||||||
|
const toggle = e.target.closest('.hifi-instance-toggle');
|
||||||
|
if (toggle) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleHiFiInstance(toggle.dataset.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const remove = e.target.closest('.hifi-instance-remove');
|
||||||
|
if (remove) {
|
||||||
|
e.preventDefault();
|
||||||
|
removeHiFiInstance(remove.dataset.url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function _saveHiFiInstanceOrder(urls) {
|
async function _saveHiFiInstanceOrder(urls) {
|
||||||
try {
|
try {
|
||||||
await fetch('/api/hifi/instances/reorder', {
|
await fetch('/api/hifi/instances/reorder', {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue