Web Dashboard Optimizer
Create smooth, stable web monitoring dashboards with smart refresh, scroll position retention, modal interaction, auto-restart, and responsive design.
MIT-0 · Free to use, modify, and redistribute. No attribution required.
⭐ 0 · 93 · 0 current installs · 0 all-time installs
MIT-0
Security Scan
OpenClaw
Suspicious
high confidencePurpose & Capability
Name/description claim frontend/dashboard optimizations, and the SKILL.md/README contain relevant HTML/CSS/JS guidance. However the README and metadata reference executable files (server.js, run-monitor.sh, dashboard.html) and require 'node' in _meta.json, while the package as published contains only README.md, SKILL.md, and _meta.json (no server.js, no dashboard.html, no run-monitor.sh). The registry-level 'Requirements' reported earlier also showed no required binaries, creating a clear mismatch between what the skill claims it needs/contains and what is actually provided.
Instruction Scope
SKILL.md instructs creating files under /workspace/tools, running node server.js, using an auto-restart shell loop, and checking logs in /tmp/openclaw-monitor and killing processes on a port. Those actions are within the dashboard task but include system-level operations (writing files, running node, using lsof/kill) that will execute on the user's environment if followed. The instructions do not attempt to read unrelated credentials, nor do they post data to external endpoints in the included docs. The main concern is that the instructions assume additional scripts/files that are missing from the package.
Install Mechanism
This is an instruction-only skill with no install spec (lowest install risk). README suggests installing via 'clawhub install' and copying files from skills/web-dashboard-optimizer, but the published bundle lacks the referenced files. There is no evidence of any download URLs or archive extraction in the package itself.
Credentials
The runtime/docs do not request secrets or credentials. However _meta.json includes a 'requires' listing with 'node' as a required binary, while the registry metadata provided earlier reported no required binaries. This mismatch is disproportionate/unclear: the skill appears to expect node/npm to run server.js but the publish metadata did not consistently declare that requirement.
Persistence & Privilege
The skill is not marked always:true and does not request privileged or persistent platform integration. It does instruct the user to create a run-monitor.sh infinite restart loop if they choose to run provided scripts — that creates a persistent process on the host only if the user runs it. This is a user-executed behavior rather than an automatic platform privilege escalation.
What to consider before installing
The skill's purpose (dashboard UX and smart refresh) is plausible, but there are important inconsistencies you should resolve before installing or running anything: 1) The package as published only contains docs (README.md, SKILL.md, _meta.json); README and SKILL.md refer to server.js, run-monitor.sh, and dashboard.html which are missing — ask the author for the missing files or a repository link. 2) _meta.json claims 'node' is required but the registry metadata showed no required binaries — ensure you have node and verify exactly what will be executed. 3) Do not run shell scripts or start services (e.g., an auto-restart loop) on production machines until you inspect their contents; run them in an isolated environment or container first. 4) Inspect any server.js or run-monitor.sh you receive for network calls, remote endpoints, or code that reads system files before executing. 5) Prefer to obtain this skill from a verified homepage or repository (the package's source is listed as unknown) or ask the publisher to correct the manifest so files and requirements match. If the author cannot clarify or provide the missing artifacts, treat the package as untrusted.Like a lobster shell, security has layers — review code before you run it.
Current versionv1.0.0
Download ziplatest
License
MIT-0
Free to use, modify, and redistribute. No attribution required.
SKILL.md
Web 界面交互优化技能
为 AI Agent 创建流畅、稳定的 Web 监控面板界面。
功能特性
- 📊 数据监控面板模板
- 🔄 智能刷新(数据变化才更新)
- 📜 滚动位置保持
- 🎨 弹窗交互优化
- 🚀 服务自动重启
- 📱 响应式设计
适用场景
- 任务执行监控
- 数据可视化展示
- 实时状态面板
- 后台管理界面
快速开始
1. 创建基础 HTML 文件
cd /workspace/tools
cat > dashboard.html << 'EOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>监控面板</title>
<style>
/* 基础样式 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container { max-width: 1400px; margin: 0 auto; }
/* 卡片样式 */
.card {
background: white;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
/* 统计卡片 */
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: white;
border-radius: 12px;
padding: 20px;
text-align: center;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
transition: box-shadow 0.3s;
}
.stat-card:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.15); }
.stat-value { font-size: 36px; font-weight: bold; color: #667eea; }
.stat-label { color: #666; margin-top: 5px; }
/* 任务列表 */
.task-card {
border: 2px solid #e5e7eb;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
background: #fafafa;
transition: border-color 0.3s, box-shadow 0.3s;
}
.task-card:hover {
border-color: #667eea;
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.3);
}
.task-card.updated {
animation: highlightUpdate 1s;
}
@keyframes highlightUpdate {
0% { background: #fef3c7; }
100% { background: #fafafa; }
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
overflow-y: auto;
}
.modal.show {
display: flex;
align-items: flex-start;
justify-content: center;
padding: 40px 20px;
}
.modal-content {
background: white;
border-radius: 20px;
max-width: 1000px;
width: 100%;
position: relative;
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
.modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 25px 30px;
border-radius: 20px 20px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-close {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 40px; height: 40px;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
transition: all 0.3s;
}
.modal-close:hover {
background: rgba(255,255,255,0.3);
transform: rotate(90deg);
}
.modal-body {
padding: 30px;
overflow-y: auto;
max-height: 70vh;
}
/* 防止弹窗打开时页面滚动 */
body.modal-open {
position: fixed;
width: 100%;
overflow: hidden;
}
/* 按钮样式 */
.btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: box-shadow 0.3s;
}
.btn:hover { box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
</style>
</head>
<body>
<div class="container">
<header class="card">
<h1>📊 监控面板</h1>
<div class="auto-refresh">
<span>自动刷新 (60 秒)</span>
<button class="btn" id="refreshBtn" onclick="loadData()">🔄 刷新</button>
</div>
</header>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="stat1">-</div>
<div class="stat-label">统计 1</div>
</div>
<div class="stat-card">
<div class="stat-value" id="stat2">-</div>
<div class="stat-label">统计 2</div>
</div>
</div>
<div class="card">
<h2>📋 列表</h2>
<div id="list"></div>
</div>
</div>
<!-- 弹窗 -->
<div id="modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">标题</h2>
<button class="modal-close" onclick="closeModal()">×</button>
</div>
<div class="modal-body" id="modalBody">内容</div>
</div>
</div>
<script>
let autoRefresh = true;
let refreshInterval;
let lastDataHash = '';
let isModalOpen = false;
// 智能刷新(仅数据变化时更新)
function generateDataHash(data) {
return JSON.stringify(data);
}
async function loadData() {
const btn = document.getElementById('refreshBtn');
btn.disabled = true;
btn.textContent = '刷新中...';
// 保存滚动位置
const scrollY = window.scrollY;
try {
const response = await fetch('/api/data');
const data = await response.json();
const newDataHash = generateDataHash(data);
const hasChanges = newDataHash !== lastDataHash;
if (hasChanges || !lastDataHash) {
// 渲染数据
render(data);
lastDataHash = newDataHash;
// 标记更新
document.querySelectorAll('.card').forEach(card => {
card.classList.add('updated');
setTimeout(() => card.classList.remove('updated'), 1000);
});
// 恢复滚动位置
window.scrollTo(0, scrollY);
}
} catch (error) {
console.error('加载失败:', error);
} finally {
btn.disabled = false;
btn.textContent = '🔄 刷新';
}
}
function render(data) {
// 自定义渲染逻辑
document.getElementById('list').innerHTML = '<p>数据已加载</p>';
}
// 弹窗管理
function openModal(title, content) {
if (isModalOpen) return;
isModalOpen = true;
// 保存滚动位置
const scrollY = window.scrollY;
document.body.style.top = -scrollY + 'px';
document.body.classList.add('modal-open');
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalBody').innerHTML = content;
document.getElementById('modal').classList.add('show');
}
function closeModal() {
document.getElementById('modal').classList.remove('show');
// 恢复滚动位置
const scrollY = parseInt(document.body.style.top || '0');
document.body.classList.remove('modal-open');
document.body.style.top = '';
window.scrollTo(0, Math.abs(scrollY));
isModalOpen = false;
}
// ESC 键关闭弹窗
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && isModalOpen) {
closeModal();
}
});
// 点击弹窗外部关闭
document.getElementById('modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// 自动刷新
function startAutoRefresh() {
refreshInterval = setInterval(loadData, 60000);
}
function stopAutoRefresh() {
clearInterval(refreshInterval);
}
// 初始化
loadData();
startAutoRefresh();
</script>
</body>
</html>
EOF
2. 创建 Node.js 服务器
cat > server.js << 'EOF'
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 18790;
// 读取 HTML 文件
const htmlPath = path.join(__dirname, 'dashboard.html');
const html = fs.readFileSync(htmlPath, 'utf-8');
// 模拟数据
function getData() {
return {
timestamp: Date.now(),
items: [
{ id: 1, name: '任务 1', status: 'success' },
{ id: 2, name: '任务 2', status: 'pending' },
]
};
}
const server = http.createServer((req, res) => {
// CORS
res.setHeader('Access-Control-Allow-Origin', '*');
if (req.url === '/' || req.url === '/index.html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
return;
}
if (req.url === '/api/data') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(getData()));
return;
}
res.writeHead(404);
res.end('Not Found');
});
server.listen(PORT, () => {
console.log(`📊 监控面板已启动`);
console.log(` 访问地址:http://localhost:${PORT}`);
console.log(` API: http://localhost:${PORT}/api/data`);
});
EOF
3. 创建带自动重启的启动脚本
cat > run-monitor.sh << 'EOF'
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="/tmp/openclaw-monitor"
PID_FILE="$LOG_DIR/monitor.pid"
LOG_FILE="$LOG_DIR/monitor.log"
mkdir -p "$LOG_DIR"
# 检查是否已在运行
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null 2>&1; then
echo "✅ 监控面板已在运行 (PID: $PID)"
exit 0
fi
fi
# 启动服务(带自动重启)
echo "🚀 启动监控面板..."
cd "$SCRIPT_DIR"
(
while true; do
node server.js >> "$LOG_FILE" 2>&1
echo "⚠️ 服务意外停止,5 秒后重启..." >> "$LOG_FILE"
sleep 5
done
) > /dev/null 2>&1 &
echo $! > "$PID_FILE"
sleep 3
if ps -p $(cat "$PID_FILE") > /dev/null 2>&1; then
echo "✅ 监控面板已启动"
echo " 访问地址:http://localhost:18790"
echo " 日志文件:$LOG_FILE"
echo " 进程 PID: $(cat $PID_FILE)"
else
echo "❌ 启动失败"
rm -f "$PID_FILE"
fi
EOF
chmod +x run-monitor.sh
4. 运行
./run-monitor.sh
关键优化点
1. 智能刷新
// 只在数据变化时重新渲染
const hasChanges = newDataHash !== lastDataHash;
if (hasChanges) {
render(data);
// 恢复滚动位置
window.scrollTo(0, scrollY);
}
2. 滚动位置保持
// 刷新前保存
const scrollY = window.scrollY;
// 渲染后恢复
window.scrollTo(0, scrollY);
3. 弹窗交互
// 打开时保存位置
const scrollY = window.scrollY;
document.body.style.top = -scrollY + 'px';
document.body.classList.add('modal-open');
// 关闭时恢复
const scrollY = parseInt(document.body.style.top || '0');
document.body.classList.remove('modal-open');
window.scrollTo(0, Math.abs(scrollY));
4. 服务自动重启
while true; do
node server.js
sleep 5 # 5 秒后自动重启
done
自定义
修改数据源
编辑 server.js 中的 getData() 函数:
function getData() {
// 从数据库、API 或文件读取数据
const data = fs.readFileSync('/path/to/data.json');
return JSON.parse(data);
}
修改样式
编辑 dashboard.html 中的 <style> 部分。
修改刷新间隔
// 改为 30 秒
refreshInterval = setInterval(loadData, 30000);
最佳实践
- 数据哈希对比:避免不必要的重新渲染
- 滚动位置保存:提升用户体验
- 弹窗位置管理:防止页面跳动
- 自动重启:确保服务持续在线
- 日志记录:便于问题排查
故障排查
服务无法启动
# 查看日志
tail -f /tmp/openclaw-monitor/monitor.log
# 检查端口占用
lsof -ti:18790 | xargs kill -9
# 重新启动
./run-monitor.sh
页面空白
- 检查浏览器控制台错误
- 确认服务器正在运行
- 检查 HTML 文件路径
自动刷新导致跳动
确保 loadData() 函数中保存和恢复了滚动位置。
扩展功能
添加搜索过滤
function filterTasks() {
const searchText = document.getElementById('searchInput').value.toLowerCase();
filteredTasks = allTasks.filter(task =>
task.name.toLowerCase().includes(searchText)
);
renderTasks(filteredTasks);
}
添加状态筛选
<select onchange="filterByStatus(this.value)">
<option value="all">全部状态</option>
<option value="success">✅ 成功</option>
<option value="error">❌ 失败</option>
</select>
添加数据分组
const taskGroups = {
'stock': { name: '📈 股票监控', keywords: ['股票', '持仓'] },
'news': { name: '📰 新闻资讯', keywords: ['新闻', '简报'] }
};
function getTaskGroup(taskName) {
for (const [group, config] of Object.entries(taskGroups)) {
if (config.keywords.some(k => taskName.includes(k))) {
return group;
}
}
return 'other';
}
总结
这个技能提供了:
- ✅ 完整的监控面板模板
- ✅ 滚动位置保持
- ✅ 智能刷新机制
- ✅ 弹窗交互优化
- ✅ 服务自动重启
- ✅ 响应式设计
可以直接用于创建各种监控面板,无需重复处理交互细节。
Files
3 totalSelect a file
Select a file to preview.
Comments
Loading comments…
