feat(dnfs): 前端代码节点支持完整HTML/CSS/JS并注入当前页面
将内联 JS 节点扩展为内联前端代码节点:内容存完整 HTML 文档, 运行后解析注入当前页面——style 注入 head、script 在全局作用域执行 可操作整页加特效、body 渲染到节点下方容器,console 输出捕获到下方。
This commit is contained in:
1 parent
ec3df34b43
commit
30360cb106
6 files changed
+62
-20
No files matched your search
+1
-1
@@ -81,7 +81,7 @@ static :9200 (Flask + CORS)
|
||||
- 流媒体:视频(MP4/WebM/OGG/AVI/MOV/MKV)与音频(MP3/WAV/OGG/FLAC/AAC/M4A)在线播放,支持进度拖动
|
||||
- 深色模式:`css/theme.css` 统一主题,自动适配系统偏好
|
||||
- 表单组件:注册页图形/邮箱验证码统一 `.code-box` 行内布局(文字标签 + 输入框 + 控件同排,窄屏自动换行),验证码图片样式复用 `theme.css` 的 `.captcha-img` 类
|
||||
- 特殊节点(dnfs 管理员专属):信封 JSON `{"_sp":...}` 内容差异化渲染 —— 链接(favicon 卡片)/ JS 代码(可审计/可运行,在当前页面执行可加特效)/ 图片(直显)/ 文件(下载);发布权限由 dnfs 后端按 `is_admin` 强制
|
||||
- 特殊节点(dnfs 管理员专属):信封 JSON `{"_sp":...}` 内容差异化渲染 —— 链接(favicon 卡片)/ 前端代码(完整 HTML/CSS/JS,可审计/可运行,注入当前页面可加特效)/ 图片(直显)/ 文件(下载);发布权限由 dnfs 后端按 `is_admin` 强制
|
||||
- aibox Web 客户端:`aibox.html` 移动端适配聊天页,经 gateway `[WsProxy]` 反向代理(如 `/ws/aibox → ws://127.0.0.1:8765`)与 aibox 智能体通信,握手由 gateway 强制 `is_admin` 验签;协议与 BotOps/client.py 兼容(chunk 流式 / reasoning 思考区 / task 任务面板 / 指数退避重连)
|
||||
- 路径遍历防护:`safe_path_join` 限制访问范围在 `directory.root` 内
|
||||
- Nuitka 编译:可打包为独立二进制 `fsys.bin`(`sys.frozen` 检测 + BASE_DIR 锚定)
|
||||
|
||||
@@ -527,10 +527,17 @@ input[type="button"]:disabled {
|
||||
}
|
||||
.sp-image { display: block; max-width: 100%; border-radius: var(--radius-sm); }
|
||||
.sp-run {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.sp-log {
|
||||
margin-top: 0.5rem;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
padding: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<select id="sp-type">
|
||||
<option value="text">普通文本</option>
|
||||
<option value="link">链接(自动显示站点图标)</option>
|
||||
<option value="js">JS 代码(可审计/可运行)</option>
|
||||
<option value="js">前端代码(可审计/可运行)</option>
|
||||
<option value="image">图片(自动显示)</option>
|
||||
<option value="file">文件(可下载)</option>
|
||||
</select>
|
||||
|
||||
+49
-14
@@ -492,10 +492,12 @@ function renderSpLink(container, sp, appendNote) {
|
||||
appendNote();
|
||||
}
|
||||
|
||||
/* JS 代码节点:审计(展开源码,textContent 防注入)+ 运行(注入 <script> 在当前页面上下文执行,可直接操作页面加特效)。
|
||||
* 不使用 new Function/eval(CSP 未开 unsafe-eval);改为注入内联 <script>(CSP 允许 'unsafe-inline')执行。
|
||||
* 代码运行在全局作用域,可访问 document/window 对当前页面施加特效;console.log/error/warn 输出捕获到下方输出区。
|
||||
* 仅管理员可发布(后端强制)。 */
|
||||
/* 前端代码节点:审计(展开源码,textContent 防注入)+ 运行(注入当前页面执行,支持完整 HTML/CSS/JS)。
|
||||
* 不使用 new Function/eval(CSP 未开 unsafe-eval);解析完整 HTML 文档后注入当前页面:
|
||||
* - <style> 注入到页面 head
|
||||
* - <script> 在当前页面全局作用域执行(可操作整页 DOM 加特效)
|
||||
* - <body> 内容渲染到节点下方容器
|
||||
* console.log/error/warn 输出捕获到下方输出区。仅管理员可发布(后端强制)。 */
|
||||
function renderSpJs(container, sp, appendNote) {
|
||||
var bar = document.createElement('div');
|
||||
bar.className = 'sp-actions';
|
||||
@@ -543,15 +545,48 @@ function renderSpJs(container, sp, appendNote) {
|
||||
window.__spLines.push('Error: ' + ev.message);
|
||||
});
|
||||
}
|
||||
/* 注入内联 <script> 在当前页面全局作用域执行,可操作页面加特效 */
|
||||
var s = document.createElement('script');
|
||||
s.textContent = sp.code;
|
||||
document.head.appendChild(s);
|
||||
out.textContent = window.__spLines.join('\n') || '执行完成';
|
||||
/* 异步输出(setTimeout 等)轮询刷新 */
|
||||
var timer = setInterval(function() {
|
||||
/* 解析完整 HTML 文档 */
|
||||
var doc;
|
||||
try {
|
||||
doc = new DOMParser().parseFromString(sp.code, 'text/html');
|
||||
} catch (err) {
|
||||
window.__spLines.push('Error: 解析 HTML 失败: ' + (err.message || err));
|
||||
out.textContent = window.__spLines.join('\n');
|
||||
}, 400);
|
||||
return;
|
||||
}
|
||||
/* 注入 <style> 到页面 head */
|
||||
Array.prototype.forEach.call(doc.querySelectorAll('style'), function(st) {
|
||||
var s = document.createElement('style');
|
||||
s.textContent = st.textContent;
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
/* 注入 <script> 在当前页面全局作用域执行(可操作整页加特效) */
|
||||
Array.prototype.forEach.call(doc.querySelectorAll('script'), function(sc) {
|
||||
var s = document.createElement('script');
|
||||
if (sc.getAttribute('src')) {
|
||||
s.src = sc.getAttribute('src');
|
||||
} else {
|
||||
s.textContent = sc.textContent;
|
||||
}
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
/* 渲染 <body> 内容到节点下方容器 */
|
||||
if (doc.body) {
|
||||
Array.prototype.forEach.call(doc.body.childNodes, function(node) {
|
||||
out.appendChild(node);
|
||||
});
|
||||
}
|
||||
/* console 输出区 */
|
||||
var logBox = document.createElement('div');
|
||||
logBox.className = 'sp-log';
|
||||
out.appendChild(logBox);
|
||||
var render = function() {
|
||||
var lines = window.__spLines.slice();
|
||||
logBox.textContent = lines.join('\n') || '执行完成';
|
||||
};
|
||||
render();
|
||||
/* 异步输出(setTimeout 等)轮询刷新 */
|
||||
var timer = setInterval(render, 400);
|
||||
});
|
||||
bar.appendChild(audit_btn);
|
||||
bar.appendChild(run_btn);
|
||||
@@ -680,7 +715,7 @@ sp_type.addEventListener('change', function() {
|
||||
sp_edit_original = null;
|
||||
}
|
||||
if (t === 'link') input_note_content.placeholder = '请输入链接 URL(https://…)';
|
||||
else if (t === 'js') input_note_content.placeholder = '请输入 JS 代码';
|
||||
else if (t === 'js') input_note_content.placeholder = '请输入完整前端代码(可含 HTML/CSS/JS)';
|
||||
else input_note_content.placeholder = '';
|
||||
});
|
||||
|
||||
@@ -705,7 +740,7 @@ async function buildSubmitContent() {
|
||||
}
|
||||
if (t === 'js') {
|
||||
var code = input_note_content.value;
|
||||
if (!code.trim()) { alert('请输入 JS 代码'); return null; }
|
||||
if (!code.trim()) { alert('请输入前端代码'); return null; }
|
||||
return JSON.stringify({ _sp: 'js', code: code });
|
||||
}
|
||||
/* image / file:优先使用新选择的文件;编辑模式下未选新文件则保留原信封 */
|
||||
|
||||
Reference in new issue
Block a user