a2ae74d582
- Backend: auth refresh reuse, schedule/retry/sync/agent/files fixes - Frontend: push WS, files ETag browse, vite build assets - Docs: audit/changelog, production deploy gate, bug scan registry B7-77 - Deploy: deploy-production.sh, prune assets, gate logs
360 lines
25 KiB
HTML
360 lines
25 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>Nexus Dashboard — Nuxt UI 风格</title>
|
||
<script src="/app/vendor/tailwindcss-browser.js"></script>
|
||
<style type="text/tailwindcss">
|
||
@theme {
|
||
--color-brand-50: #eff6ff; --color-brand-100: #dbeafe; --color-brand-200: #bfdbfe;
|
||
--color-brand-500: #3b82f6; --color-brand-600: #2563eb; --color-brand-700: #1d4ed8;
|
||
}
|
||
@custom-variant dark (&:where(.dark, .dark *));
|
||
</style>
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
:root {
|
||
--ui-bg: #ffffff;
|
||
--ui-bg-elevated: oklch(96.7% .001 286.375);
|
||
--ui-bg-muted: oklch(98.5% 0 0);
|
||
--ui-bg-accented: oklch(92% .004 286.32);
|
||
--ui-border: oklch(92% .004 286.32);
|
||
--ui-border-muted: oklch(92% .004 286.32);
|
||
--ui-text: oklch(37% .013 285.805);
|
||
--ui-text-muted: oklch(55.2% .016 285.938);
|
||
--ui-text-dimmed: oklch(70.5% .015 286.067);
|
||
}
|
||
:root.dark {
|
||
--ui-bg: oklch(21% .006 285.885);
|
||
--ui-bg-elevated: oklch(27.4% .006 286.033);
|
||
--ui-bg-muted: oklch(27.4% .006 286.033);
|
||
--ui-bg-accented: oklch(37% .013 285.805);
|
||
--ui-border: oklch(27.4% .006 286.033);
|
||
--ui-border-muted: oklch(27.4% .006 286.033);
|
||
--ui-text: oklch(92% .004 286.32);
|
||
--ui-text-muted: oklch(70.5% .015 286.067);
|
||
--ui-text-dimmed: oklch(55.2% .016 285.938);
|
||
}
|
||
body {
|
||
background: var(--ui-bg); color: var(--ui-text);
|
||
font-family: 'Inter', -apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
|
||
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
||
font-size: 14px; line-height: 1.5;
|
||
}
|
||
input,select,textarea,button{font-family:inherit}
|
||
|
||
.scrollbar::-webkit-scrollbar{width:4px}
|
||
.scrollbar::-webkit-scrollbar-track{background:transparent}
|
||
.scrollbar::-webkit-scrollbar-thumb{background:var(--ui-border);border-radius:2px}
|
||
|
||
/* ── Sidebar ── */
|
||
.nav-item {
|
||
display:flex;align-items:center;gap:11px;padding:9px 12px;border-radius:7px;
|
||
font-size:14px;font-weight:500;color:var(--ui-text-muted);
|
||
transition:all .15s;cursor:pointer;text-decoration:none;
|
||
}
|
||
.nav-item:hover{background:var(--ui-bg-elevated);color:var(--ui-text)}
|
||
.nav-item.active{background:var(--ui-bg-elevated);color:var(--ui-text);font-weight:600}
|
||
.nav-item svg{width:18px;height:18px}
|
||
|
||
/* ── Buttons (Nuxt UI style) ── */
|
||
.btn {
|
||
display:inline-flex;align-items:center;justify-content:center;gap:6px;height:36px;
|
||
padding:0 14px;border-radius:6px;font-size:13px;font-weight:500;
|
||
transition:all .15s;cursor:pointer;border:1px solid transparent;line-height:1;
|
||
}
|
||
.btn:focus-visible{outline:2px solid #3b82f6;outline-offset:2px}
|
||
.btn-primary{background:#3b82f6;color:#fff;border-color:#3b82f6}.btn-primary:hover{background:#2563eb;border-color:#2563eb}
|
||
.btn-outline{background:transparent;color:var(--ui-text);border-color:var(--ui-border)}.btn-outline:hover{background:var(--ui-bg-elevated)}
|
||
.btn-ghost{background:transparent;color:var(--ui-text-muted);border-color:transparent}.btn-ghost:hover{background:var(--ui-bg-elevated);color:var(--ui-text)}
|
||
.btn-danger{background:#ef4444;color:#fff;border-color:#ef4444}.btn-danger:hover{background:#dc2626}
|
||
.btn-sm{height:30px;padding:0 10px;font-size:12px;border-radius:5px}
|
||
|
||
/* ── Inputs (Nuxt UI style) ── */
|
||
.input-wrap{position:relative}
|
||
.input {
|
||
height:34px;padding:0 11px;border-radius:6px;font-size:13px;border:1px solid var(--ui-border);
|
||
background:var(--ui-bg);color:var(--ui-text);outline:none;transition:all .15s;width:100%;
|
||
}
|
||
.input:hover{border-color:var(--ui-text-dimmed)}
|
||
.input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.12)}
|
||
.input::placeholder{color:var(--ui-text-dimmed)}
|
||
select.input{
|
||
-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%2370.5%25' stroke-width='2'%3E%3Cpath d='M2 5l4 4 4-4'/%3E%3C/svg%3E");
|
||
background-repeat:no-repeat;background-position:right 10px center;padding-right:32px;
|
||
}
|
||
|
||
/* ── Cards (Nuxt UI style) ── */
|
||
.card {
|
||
background:var(--ui-bg-elevated);border:1px solid var(--ui-border);border-radius:6px;
|
||
padding:20px 24px;
|
||
}
|
||
.stat-card {
|
||
background:var(--ui-bg-elevated);border:1px solid var(--ui-border);border-radius:6px;
|
||
padding:24px;
|
||
}
|
||
|
||
/* ── Table (Nuxt UI style) ── */
|
||
.table-wrap {
|
||
border:1px solid var(--ui-border);border-radius:8px;overflow:hidden;
|
||
background:var(--ui-bg-elevated);
|
||
}
|
||
.table-wrap table{width:100%;border-collapse:collapse;font-size:13px}
|
||
.table-wrap thead{background:var(--ui-bg-muted)}
|
||
.table-wrap th {
|
||
padding:9px 14px;text-align:left;font-size:11px;font-weight:600;
|
||
text-transform:uppercase;letter-spacing:.04em;color:var(--ui-text-dimmed);
|
||
border-bottom:1px solid var(--ui-border);height:38px;vertical-align:middle;
|
||
}
|
||
.table-wrap td {
|
||
padding:11px 14px;border-bottom:1px solid var(--ui-border-muted);
|
||
color:var(--ui-text);vertical-align:middle;height:44px;
|
||
}
|
||
.table-wrap tr:last-child td{border-bottom:none}
|
||
.table-wrap tbody tr:hover{background:var(--ui-bg-accented)}
|
||
|
||
/* ── Status Badges ── */
|
||
.dot-green{width:6px;height:6px;border-radius:50%;background:#10b981;flex-shrink:0}
|
||
.dot-red{width:6px;height:6px;border-radius:50%;background:#ef4444;flex-shrink:0;animation:pulse 2s infinite}
|
||
.dot-gray{width:6px;height:6px;border-radius:50%;background:var(--ui-text-dimmed);flex-shrink:0}
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||
|
||
.status-badge{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:500}
|
||
.status-text-green{color:#059669}.status-text-red{color:#dc2626}.status-text-gray{color:var(--ui-text-dimmed)}
|
||
|
||
.cell-label{display:inline-block;padding:1px 6px;border-radius:4px;font-size:11px;font-weight:500}
|
||
.cell-label-blue{background:rgba(59,130,246,.1);color:#3b82f6}.cell-label-gray{background:var(--ui-bg-muted);color:var(--ui-text-muted)}
|
||
|
||
.link{font-size:12px;color:var(--ui-text-muted);text-decoration:none;background:none;border:none;cursor:pointer;transition:.1s;padding:0;display:inline}
|
||
.link:hover{color:var(--ui-text)}
|
||
.link-primary{color:#3b82f6}.link-primary:hover{color:#1d4ed8}
|
||
.link-success{color:#059669}.link-success:hover{color:#047857}
|
||
.link-danger{color:#dc2626}.link-danger:hover{color:#b91c1c}
|
||
|
||
/* ── Pagination (Nuxt UI style) ── */
|
||
.pagination{display:flex;align-items:center;gap:2px;margin-top:14px}
|
||
.page-btn{min-width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:5px;border:1px solid transparent;background:transparent;color:var(--ui-text-muted);font-size:12px;cursor:pointer;transition:all .15s}
|
||
.page-btn:hover{background:var(--ui-bg-elevated);border-color:var(--ui-border)}
|
||
.page-btn.active{background:#3b82f6;color:#fff;border-color:#3b82f6;font-weight:600}
|
||
.page-btn:disabled{opacity:.3;cursor:default}
|
||
|
||
/* ── Tabs (Nuxt UI style - underline) ── */
|
||
.tab-bar{display:flex;gap:0}
|
||
.tab-btn{padding:8px 16px;font-size:13px;font-weight:500;color:var(--ui-text-muted);border-bottom:2px solid transparent;transition:all .15s;cursor:pointer;background:none;border-top:none;border-left:none;border-right:none}
|
||
.tab-btn:hover{color:var(--ui-text);border-bottom-color:var(--ui-border)}
|
||
.tab-btn.active{color:var(--ui-text);border-bottom-color:var(--ui-text);font-weight:600}
|
||
|
||
/* ── Modal (Nuxt UI style) ── */
|
||
.modal-overlay{position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;padding:16px;background:rgba(0,0,0,.4);backdrop-filter:blur(4px);opacity:0;visibility:hidden;transition:all .2s}
|
||
.modal-overlay.open{opacity:1;visibility:visible}
|
||
.modal{background:var(--ui-bg);border:1px solid var(--ui-border);border-radius:8px;padding:20px;width:100%;max-width:420px;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,.15)}
|
||
|
||
/* ── Toast (Nuxt UI style) ── */
|
||
.toast{position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;max-width:380px;pointer-events:none}
|
||
.toast-item{pointer-events:auto;display:flex;align-items:flex-start;gap:10px;padding:12px 16px;border-radius:8px;font-size:13px;border:1px solid var(--ui-border);background:var(--ui-bg);color:var(--ui-text);box-shadow:0 4px 16px rgba(0,0,0,.1)}
|
||
.toast-success{border-left:3px solid #10b981}
|
||
.toast-error{border-left:3px solid #ef4444}
|
||
.toast-warning{border-left:3px solid #f59e0b}
|
||
|
||
/* ── Animations ── */
|
||
@keyframes slideIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}
|
||
.slide-in{animation:slideIn .2s ease}
|
||
|
||
/* ── Separator ── */
|
||
.sep{height:1px;background:var(--ui-border);margin:12px 0;border:none}
|
||
|
||
/* ── Checkbox ── */
|
||
.form-check{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:var(--ui-text);user-select:none}
|
||
.form-check input[type=checkbox]{width:15px;height:15px;border-radius:3px;accent-color:#3b82f6;cursor:pointer}
|
||
</style>
|
||
</head>
|
||
<body class="flex min-h-screen scrollbar">
|
||
<div class="toast" id="toastContainer"></div>
|
||
|
||
<!-- ════════ SIDEBAR ════════ -->
|
||
<aside class="w-60 shrink-0 flex flex-col border-r scrollbar" style="background:var(--ui-bg);border-color:var(--ui-border);overflow-y:auto">
|
||
<div class="px-4 py-3.5 border-b" style="border-color:var(--ui-border)">
|
||
<div class="flex items-center gap-2.5">
|
||
<svg class="w-6 h-6" style="color:#3b82f6" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"/></svg>
|
||
<span class="font-bold text-sm" style="color:var(--ui-text)">Nexus</span>
|
||
</div>
|
||
</div>
|
||
<nav class="flex-1 px-2.5 py-3 space-y-0.5">
|
||
<a class="nav-item active"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg>仪表盘</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7"/></svg>服务器</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"/></svg>文件管理</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/></svg>推送</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"/></svg>脚本库</a>
|
||
<div style="border-top:1px solid var(--ui-border);margin:6px 0"></div>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"/></svg>命令日志</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0"/></svg>告警中心</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192M5.801 8.248c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75A2.25 2.25 0 0010.25 7.5M8.25 8.25H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z"/></svg>审计日志</a>
|
||
<a class="nav-item"><svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>设置</a>
|
||
</nav>
|
||
<div style="border-top:1px solid var(--ui-border);padding:10px 12px">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-2">
|
||
<div class="w-6 h-6 rounded-md flex items-center justify-center text-[10px] font-bold" style="background:rgba(59,130,246,.1);color:#3b82f6">A</div>
|
||
<span class="text-xs" style="color:var(--ui-text-muted)">admin</span>
|
||
</div>
|
||
<button onclick="document.documentElement.classList.toggle('dark')" class="p-1 rounded hover:bg-[var(--ui-bg-elevated)] text-sm transition" style="color:var(--ui-text-muted)" title="切换主题">
|
||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M4.93 19.07l1.41-1.41m11.32-11.32l1.41-1.41"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ════════ MAIN ════════ -->
|
||
<div class="flex-1 flex flex-col min-w-0">
|
||
<header class="h-13 shrink-0 flex items-center justify-between px-6 border-b" style="background:var(--ui-bg);border-color:var(--ui-border)">
|
||
<h1 class="text-sm font-semibold" style="color:var(--ui-text)">仪表盘</h1>
|
||
<button class="btn btn-outline btn-sm" onclick="showToast('info','数据已刷新')">刷新</button>
|
||
</header>
|
||
|
||
<main class="flex-1 overflow-y-auto p-6 space-y-6 scrollbar">
|
||
|
||
<!-- Stats Cards -->
|
||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||
<div class="stat-card"><div class="text-[11px] font-semibold uppercase tracking-wide mb-3" style="color:var(--ui-text-dimmed)">服务器总数</div><div class="text-[32px] font-bold tracking-tight leading-none" style="color:var(--ui-text)">8</div></div>
|
||
<div class="stat-card"><div class="text-[11px] font-semibold uppercase tracking-wide mb-3" style="color:var(--ui-text-dimmed)">在线</div><div class="text-[32px] font-bold tracking-tight leading-none" style="color:#059669">1</div></div>
|
||
<div class="stat-card"><div class="text-[11px] font-semibold uppercase tracking-wide mb-3" style="color:var(--ui-text-dimmed)">离线</div><div class="text-[32px] font-bold tracking-tight leading-none" style="color:#dc2626">7</div></div>
|
||
<div class="stat-card"><div class="text-[11px] font-semibold uppercase tracking-wide mb-3" style="color:var(--ui-text-dimmed)">告警中</div><div class="text-[32px] font-bold tracking-tight leading-none" style="color:#d97706">0</div></div>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
<div>
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h2 class="text-[13px] font-semibold" style="color:var(--ui-text)">服务器列表</h2>
|
||
<div class="flex items-center gap-2">
|
||
<select class="input" style="width:110px"><option>全部分类</option><option>商城</option><option>数据库</option></select>
|
||
<input class="input" style="width:180px" placeholder="搜索...">
|
||
<button class="btn btn-primary btn-sm">+ 添加</button>
|
||
</div>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr><th>状态</th><th>名称</th><th>地址</th><th>分类</th><th>Agent</th><th>最后心跳</th><th style="text-align:right">操作</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span class="status-badge"><span class="dot-green"></span><span class="status-text-green">在线</span></span></td>
|
||
<td><span class="font-semibold">prod-web-01</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">192.168.1.10<span style="color:var(--ui-border)">:22</span></td>
|
||
<td><span class="cell-label cell-label-blue">商城</span></td>
|
||
<td style="color:var(--ui-text-muted)">v1.2.0</td>
|
||
<td style="color:var(--ui-text-dimmed);font-size:12px">3分钟前</td>
|
||
<td style="text-align:right"><a href="#" class="link link-primary">终端</a> <a href="#" class="link">文件</a> <a href="#" class="link link-success">检查</a> <a href="#" class="link">编辑</a> <button class="link link-danger">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="status-badge"><span class="dot-red"></span><span class="status-text-red">离线</span></span></td>
|
||
<td><span class="font-semibold">dev-db-01</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">10.0.0.5<span style="color:var(--ui-border)">:22</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">—</td>
|
||
<td style="color:var(--ui-text-muted)">v1.1.8</td>
|
||
<td style="color:var(--ui-text-dimmed);font-size:12px">2小时前</td>
|
||
<td style="text-align:right"><a href="#" class="link link-primary">终端</a> <a href="#" class="link">文件</a> <a href="#" class="link">编辑</a> <button class="link link-danger">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="status-badge"><span class="dot-gray"></span><span class="status-text-gray">未安装</span></span></td>
|
||
<td><span class="font-semibold">new-server-03</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">172.16.0.8<span style="color:var(--ui-border)">:22</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">—</td>
|
||
<td style="color:var(--ui-text-dimmed)">—</td>
|
||
<td style="color:var(--ui-text-dimmed);font-size:12px">—</td>
|
||
<td style="text-align:right"><a href="#" class="link link-primary">终端</a> <a href="#" class="link">编辑</a> <button class="link link-danger">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="status-badge"><span class="dot-green"></span><span class="status-text-green">在线</span></span></td>
|
||
<td><span class="font-semibold">cache-redis-01</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">10.0.0.10<span style="color:var(--ui-border)">:6379</span></td>
|
||
<td><span class="cell-label cell-label-blue">数据库</span></td>
|
||
<td style="color:var(--ui-text-muted)">v1.2.0</td>
|
||
<td style="color:var(--ui-text-dimmed);font-size:12px">1分钟前</td>
|
||
<td style="text-align:right"><a href="#" class="link link-primary">终端</a> <a href="#" class="link">文件</a> <a href="#" class="link link-success">检查</a> <a href="#" class="link">编辑</a> <button class="link link-danger">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="status-badge"><span class="dot-red"></span><span class="status-text-red">离线</span></td>
|
||
<td><span class="font-semibold">old-web-02</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">192.168.1.20<span style="color:var(--ui-border)">:22</span></td>
|
||
<td><span class="cell-label cell-label-blue">商城</span></td>
|
||
<td style="color:var(--ui-text-dimmed)">—</td>
|
||
<td style="color:var(--ui-text-dimmed);font-size:12px">3天前</td>
|
||
<td style="text-align:right"><a href="#" class="link link-primary">终端</a> <a href="#" class="link">编辑</a> <button class="link link-danger">删除</button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="pagination">
|
||
<span class="text-xs" style="color:var(--ui-text-dimmed);margin-right:auto">共 8 台服务器</span>
|
||
<button class="page-btn" disabled>‹</button>
|
||
<button class="page-btn active">1</button>
|
||
<button class="page-btn">2</button>
|
||
<button class="page-btn">›</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Form + Component Showcase -->
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div class="card space-y-3" style="padding:20px">
|
||
<h3 class="text-[13px] font-semibold mb-1" style="color:var(--ui-text)">添加服务器</h3>
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<div><label class="block text-[11px] font-semibold mb-1.5" style="color:var(--ui-text-muted)">名称</label><input class="input" placeholder="web-server-01"></div>
|
||
<div><label class="block text-[11px] font-semibold mb-1.5" style="color:var(--ui-text-muted)">地址</label><input class="input" placeholder="192.168.1.10"></div>
|
||
</div>
|
||
<div class="grid grid-cols-3 gap-3">
|
||
<div><label class="block text-[11px] font-semibold mb-1.5" style="color:var(--ui-text-muted)">端口</label><input class="input" value="22"></div>
|
||
<div><label class="block text-[11px] font-semibold mb-1.5" style="color:var(--ui-text-muted)">用户</label><input class="input" value="root"></div>
|
||
<div><label class="block text-[11px] font-semibold mb-1.5" style="color:var(--ui-text-muted)">认证</label><select class="input"><option>密码</option><option>密钥</option></select></div>
|
||
</div>
|
||
<div class="flex gap-2 pt-1"><button class="btn btn-primary btn-sm" onclick="showToast('success','服务器已添加')">保存</button><button class="btn btn-outline btn-sm">取消</button></div>
|
||
</div>
|
||
<div class="card space-y-3" style="padding:20px">
|
||
<h3 class="text-[13px] font-semibold mb-1" style="color:var(--ui-text)">组件</h3>
|
||
<div class="flex flex-wrap gap-2"><button class="btn btn-primary btn-sm">主要</button><button class="btn btn-outline btn-sm">次要</button><button class="btn btn-ghost btn-sm">幽灵</button><button class="btn btn-danger btn-sm">危险</button></div>
|
||
<hr class="sep">
|
||
<div class="tab-bar"><button class="tab-btn active">系统信息</button><button class="tab-btn">同步日志</button><button class="tab-btn">SSH 会话</button></div>
|
||
<hr class="sep">
|
||
<div class="flex gap-4">
|
||
<label class="form-check"><input type="checkbox" checked> 服务器 A</label>
|
||
<label class="form-check"><input type="checkbox"> 服务器 B</label>
|
||
</div>
|
||
<hr class="sep">
|
||
<div class="flex gap-2"><span class="status-badge"><span class="dot-green"></span><span class="status-text-green">在线</span></span><span class="status-badge"><span class="dot-red"></span><span class="status-text-red">离线</span></span><span class="status-badge"><span class="dot-gray"></span><span class="status-text-gray">未安装</span></span></div>
|
||
<hr class="sep">
|
||
<button class="btn btn-outline btn-sm" onclick="document.getElementById('demoModal').classList.add('open')">打开删除确认 Modal</button>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Modal -->
|
||
<div class="modal-overlay" id="demoModal" onclick="if(event.target===this)this.classList.remove('open')">
|
||
<div class="modal slide-in">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-sm font-semibold" style="color:var(--ui-text)">确认删除</h3>
|
||
<button onclick="document.getElementById('demoModal').classList.remove('open')" style="background:none;border:none;cursor:pointer;color:var(--ui-text-dimmed);font-size:16px">×</button>
|
||
</div>
|
||
<p class="text-xs mb-6 leading-relaxed" style="color:var(--ui-text-muted)">确定要删除服务器 <strong style="color:var(--ui-text)">prod-web-01</strong> 吗?此操作无法撤销。</p>
|
||
<div class="flex justify-end gap-2">
|
||
<button class="btn btn-outline btn-sm" onclick="document.getElementById('demoModal').classList.remove('open')">取消</button>
|
||
<button class="btn btn-danger btn-sm" onclick="document.getElementById('demoModal').classList.remove('open');showToast('success','服务器已删除')">删除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function showToast(type, msg) {
|
||
const icons = {success:'<svg style="width:15px;height:15px;shrink:0" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 12l2 2 4-4"/></svg>',error:'<svg style="width:15px;height:15px;shrink:0" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6m0-6l6 6"/></svg>',info:'<svg style="width:15px;height:15px;shrink:0" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4m0-4h.01"/></svg>',warning:'<svg style="width:15px;height:15px;shrink:0" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><path d="M12 9v4m0 4h.01"/></svg>'}
|
||
const el = document.createElement('div');
|
||
el.className = 'toast-item toast-'+type+' slide-in';
|
||
el.innerHTML = (icons[type]||icons.info)+'<span style="flex:1">'+msg+'</span><button onclick="this.parentElement.remove()" style="background:none;border:none;cursor:pointer;color:var(--ui-text-dimmed);font-size:16px;line-height:1">×</button>';
|
||
document.getElementById('toastContainer').appendChild(el);
|
||
setTimeout(()=>{el.style.opacity='0';el.style.transition='opacity .2s';setTimeout(()=>el.remove(),200)}, 3500);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|