ui: start template safety cleanup
This commit is contained in:
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
## Current
|
## Current
|
||||||
|
|
||||||
### `feat/frontend-xss-hardening`
|
### `feat/frontend-template-safety-cleanup`
|
||||||
|
|
||||||
Status: in_progress
|
Status: in_progress
|
||||||
|
|
||||||
DoD:
|
DoD:
|
||||||
- API-provided values are not rendered into `innerHTML` on user-facing admin pages
|
- local UI helpers do not use `innerHTML` for static SVG or icon insertion
|
||||||
- error strings from API responses are rendered through text nodes
|
- wizard shell and workspace shell use DOM-based icon and fragment construction
|
||||||
- streaming admin pages use DOM construction instead of HTML string concatenation for dynamic cards
|
- remaining HTML-bearing i18n/template paths are explicitly isolated and tracked
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -18,6 +18,16 @@ function tfKey(key, vars) {
|
|||||||
return window.tf ? tf(key, vars) : tKey(key);
|
return window.tf ? tf(key, vars) : tKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildIconSvg(href, width, height) {
|
||||||
|
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
|
svg.setAttribute('width', String(width));
|
||||||
|
svg.setAttribute('height', String(height));
|
||||||
|
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||||
|
use.setAttribute('href', href);
|
||||||
|
svg.appendChild(use);
|
||||||
|
return svg;
|
||||||
|
}
|
||||||
|
|
||||||
function mapKeyRecord(record) {
|
function mapKeyRecord(record) {
|
||||||
var apiKey = record.api_key || {};
|
var apiKey = record.api_key || {};
|
||||||
return {
|
return {
|
||||||
@@ -310,7 +320,13 @@ document.getElementById('copy-key-btn').addEventListener('click', function() {
|
|||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(value).catch(function() {});
|
navigator.clipboard.writeText(value).catch(function() {});
|
||||||
}
|
}
|
||||||
this.innerHTML = '<svg width="13" height="13"><use href="' + (window.APP_BASE || '') + 'icons/general/check.svg#icon"/></svg>';
|
this.replaceChildren(
|
||||||
|
buildIconSvg(
|
||||||
|
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
|
||||||
|
13,
|
||||||
|
13
|
||||||
|
)
|
||||||
|
);
|
||||||
if (window.CrankUi) {
|
if (window.CrankUi) {
|
||||||
window.CrankUi.info(
|
window.CrankUi.info(
|
||||||
tKey('apikeys.toast.copy_message'),
|
tKey('apikeys.toast.copy_message'),
|
||||||
|
|||||||
+22
-5
@@ -12,6 +12,16 @@ function tfKey(key, vars) {
|
|||||||
return typeof tf === 'function' ? tf(key, vars) : key;
|
return typeof tf === 'function' ? tf(key, vars) : key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildIconSvg(href, width, height) {
|
||||||
|
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
|
svg.setAttribute('width', String(width));
|
||||||
|
svg.setAttribute('height', String(height));
|
||||||
|
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||||
|
use.setAttribute('href', href);
|
||||||
|
svg.appendChild(use);
|
||||||
|
return svg;
|
||||||
|
}
|
||||||
|
|
||||||
function workspaceColor(index) {
|
function workspaceColor(index) {
|
||||||
return ['#0d9488', '#7c3aed', '#0891b2', '#f59e0b', '#2563eb'][index % 5];
|
return ['#0d9488', '#7c3aed', '#0891b2', '#f59e0b', '#2563eb'][index % 5];
|
||||||
}
|
}
|
||||||
@@ -139,10 +149,11 @@ function renderWorkspaceList() {
|
|||||||
item.appendChild(info);
|
item.appendChild(info);
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
var check = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
var check = buildIconSvg(
|
||||||
check.setAttribute('width', '12');
|
(window.APP_BASE || '') + 'icons/general/check.svg#icon',
|
||||||
check.setAttribute('height', '12');
|
12,
|
||||||
check.innerHTML = '<use href="' + (window.APP_BASE || '') + 'icons/general/check.svg#icon"></use>';
|
12
|
||||||
|
);
|
||||||
item.appendChild(check);
|
item.appendChild(check);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +171,13 @@ function renderWorkspaceList() {
|
|||||||
var dd = document.getElementById('ws-dropdown');
|
var dd = document.getElementById('ws-dropdown');
|
||||||
if (dd) dd.style.display = 'none';
|
if (dd) dd.style.display = 'none';
|
||||||
});
|
});
|
||||||
manageLink.innerHTML = '<svg width="14" height="14"><use href="' + (window.APP_BASE || '') + 'icons/general/settings.svg#icon"/></svg>';
|
manageLink.appendChild(
|
||||||
|
buildIconSvg(
|
||||||
|
(window.APP_BASE || '') + 'icons/general/settings.svg#icon',
|
||||||
|
14,
|
||||||
|
14
|
||||||
|
)
|
||||||
|
);
|
||||||
manageLink.appendChild(document.createTextNode(tKey('settings.ws.title')));
|
manageLink.appendChild(document.createTextNode(tKey('settings.ws.title')));
|
||||||
list.appendChild(manageLink);
|
list.appendChild(manageLink);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user