mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 20:30:00 +08:00
feat: ClawPanel v0.1.0 项目骨架
- Tauri v2 + Vanilla JS + Vite 技术栈 - 9 个页面: 仪表盘/服务管理/日志/模型配置/Agent配置/Gateway/MCP工具/记忆文件/部署 - Rust 后端: 配置读写/服务管理(launchd)/日志读取/记忆文件管理 - 暗色主题 + 玻璃拟态 UI - Mock 数据支持纯浏览器开发调试
This commit is contained in:
77
src/components/sidebar.js
Normal file
77
src/components/sidebar.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 侧边导航栏
|
||||
*/
|
||||
import { navigate, getCurrentRoute } from '../router.js'
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{
|
||||
section: '概览',
|
||||
items: [
|
||||
{ route: '/dashboard', label: '仪表盘', icon: 'dashboard' },
|
||||
{ route: '/services', label: '服务管理', icon: 'services' },
|
||||
{ route: '/logs', label: '日志查看', icon: 'logs' },
|
||||
]
|
||||
},
|
||||
{
|
||||
section: '配置',
|
||||
items: [
|
||||
{ route: '/models', label: '模型配置', icon: 'models' },
|
||||
{ route: '/agents', label: 'Agent 配置', icon: 'agents' },
|
||||
{ route: '/gateway', label: 'Gateway', icon: 'gateway' },
|
||||
{ route: '/mcp', label: 'MCP 工具', icon: 'mcp' },
|
||||
]
|
||||
},
|
||||
{
|
||||
section: '数据',
|
||||
items: [
|
||||
{ route: '/memory', label: '记忆文件', icon: 'memory' },
|
||||
{ route: '/deploy', label: 'ClawApp 部署', icon: 'deploy' },
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const ICONS = {
|
||||
dashboard: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>',
|
||||
services: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>',
|
||||
logs: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>',
|
||||
models: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/><path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12"/></svg>',
|
||||
agents: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>',
|
||||
gateway: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>',
|
||||
mcp: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg>',
|
||||
memory: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>',
|
||||
deploy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
||||
}
|
||||
|
||||
export function renderSidebar(el) {
|
||||
const current = getCurrentRoute()
|
||||
|
||||
let html = `
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-logo">CP</div>
|
||||
<span class="sidebar-title">ClawPanel</span>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
`
|
||||
|
||||
for (const section of NAV_ITEMS) {
|
||||
html += `<div class="nav-section">
|
||||
<div class="nav-section-title">${section.section}</div>`
|
||||
|
||||
for (const item of section.items) {
|
||||
const active = current === item.route ? ' active' : ''
|
||||
html += `<div class="nav-item${active}" data-route="${item.route}">
|
||||
${ICONS[item.icon] || ''}
|
||||
<span>${item.label}</span>
|
||||
</div>`
|
||||
}
|
||||
html += '</div>'
|
||||
}
|
||||
|
||||
html += '</nav>'
|
||||
el.innerHTML = html
|
||||
|
||||
// 绑定点击事件
|
||||
el.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.onclick = () => navigate(item.dataset.route)
|
||||
})
|
||||
}
|
||||
28
src/components/toast.js
Normal file
28
src/components/toast.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Toast 通知组件
|
||||
*/
|
||||
let _container = null
|
||||
|
||||
function ensureContainer() {
|
||||
if (!_container) {
|
||||
_container = document.createElement('div')
|
||||
_container.className = 'toast-container'
|
||||
document.body.appendChild(_container)
|
||||
}
|
||||
return _container
|
||||
}
|
||||
|
||||
export function toast(message, type = 'info', duration = 3000) {
|
||||
const container = ensureContainer()
|
||||
const el = document.createElement('div')
|
||||
el.className = `toast ${type}`
|
||||
el.textContent = message
|
||||
container.appendChild(el)
|
||||
|
||||
setTimeout(() => {
|
||||
el.style.opacity = '0'
|
||||
el.style.transform = 'translateX(20px)'
|
||||
el.style.transition = 'all 250ms ease'
|
||||
setTimeout(() => el.remove(), 250)
|
||||
}, duration)
|
||||
}
|
||||
Reference in New Issue
Block a user