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
376 lines
23 KiB
HTML
376 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN" class="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>Nexus — Vuetify 风格预览</title>
|
||
<script src="/app/vendor/tailwindcss-browser.js"></script>
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||
|
||
/* ═══ Vuetify Material 配色 ═══ */
|
||
:root {
|
||
--v-primary: #1867C0;
|
||
--v-secondary: #5CBBF6;
|
||
--v-accent: #E7810D;
|
||
--v-error: #972800;
|
||
--v-info: #3462E3;
|
||
--v-success: #356859;
|
||
--v-warning: #E7810D;
|
||
|
||
--v-surface: #FFFFFF;
|
||
--v-surface-variant: #F5F5F5;
|
||
--v-background: #FAFAFA;
|
||
--v-on-surface: #212121;
|
||
--v-on-surface-variant: #616161;
|
||
--v-on-surface-muted: #9E9E9E;
|
||
--v-border: rgba(0,0,0,.12);
|
||
--v-divider: rgba(0,0,0,.08);
|
||
|
||
--v-radius: 12px;
|
||
--v-radius-sm: 8px;
|
||
}
|
||
:root.dark {
|
||
--v-surface: #1E1E1E;
|
||
--v-surface-variant: #2D2D2D;
|
||
--v-background: #121212;
|
||
--v-on-surface: #FFFFFF;
|
||
--v-on-surface-variant: #BDBDBD;
|
||
--v-on-surface-muted: #757575;
|
||
--v-border: rgba(255,255,255,.12);
|
||
--v-divider: rgba(255,255,255,.08);
|
||
}
|
||
body {
|
||
background: var(--v-background); color: var(--v-on-surface);
|
||
font-family: Roboto,-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
|
||
-webkit-font-smoothing: antialiased; font-size: 14px;
|
||
}
|
||
.scrollbar::-webkit-scrollbar{width:6px}
|
||
.scrollbar::-webkit-scrollbar-thumb{background:var(--v-on-surface-muted);border-radius:3px}
|
||
|
||
/* ═══ Material Elevation ═══ */
|
||
.elevation-1{box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.06)}
|
||
.elevation-2{box-shadow:0 2px 4px rgba(0,0,0,.12),0 1px 4px rgba(0,0,0,.08)}
|
||
.elevation-4{box-shadow:0 4px 8px rgba(0,0,0,.14),0 2px 8px rgba(0,0,0,.1)}
|
||
.dark .elevation-1{box-shadow:0 1px 3px rgba(0,0,0,.4)}
|
||
.dark .elevation-2{box-shadow:0 2px 6px rgba(0,0,0,.5)}
|
||
.dark .elevation-4{box-shadow:0 4px 12px rgba(0,0,0,.6)}
|
||
|
||
/* ═══ Sidebar ═══ */
|
||
.nav-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--v-on-surface-muted);padding:8px 16px 4px}
|
||
.nav-item{
|
||
display:flex;align-items:center;gap:12px;padding:10px 16px;margin:0 4px;border-radius:var(--v-radius-sm);
|
||
font-size:14px;font-weight:500;color:var(--v-on-surface-variant);
|
||
transition:all .2s;cursor:pointer;text-decoration:none;position:relative;
|
||
}
|
||
.nav-item:hover{background:rgba(24,103,192,.08)}
|
||
.nav-item.active{color:var(--v-primary);background:rgba(24,103,192,.12)}
|
||
.nav-item.active::before{content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:60%;border-radius:0 3px 3px 0;background:var(--v-primary)}
|
||
|
||
/* ═══ Buttons ═══ */
|
||
.v-btn{
|
||
display:inline-flex;align-items:center;justify-content:center;gap:8px;
|
||
height:40px;padding:0 20px;border-radius:var(--v-radius-sm);font-size:14px;font-weight:500;
|
||
letter-spacing:.02em;transition:all .2s;cursor:pointer;border:none;
|
||
text-transform:none;position:relative;overflow:hidden;
|
||
}
|
||
.v-btn::after{content:'';position:absolute;inset:0;background:currentColor;opacity:0;transition:opacity .2s}
|
||
.v-btn:hover::after{opacity:.08}
|
||
.v-btn:active::after{opacity:.12}
|
||
.v-btn-primary{background:var(--v-primary);color:#fff}.v-btn-primary:hover{box-shadow:0 2px 8px rgba(24,103,192,.4)}
|
||
.v-btn-tonal{background:rgba(24,103,192,.08);color:var(--v-primary)}.v-btn-tonal:hover{background:rgba(24,103,192,.12)}
|
||
.v-btn-outlined{border:1px solid var(--v-border);background:transparent;color:var(--v-on-surface)}
|
||
.v-btn-outlined:hover{border-color:var(--v-on-surface-muted)}
|
||
.v-btn-text{background:transparent;color:var(--v-on-surface-variant)}.v-btn-text:hover{background:var(--v-surface-variant)}
|
||
.v-btn-sm{height:32px;padding:0 12px;font-size:12px;border-radius:6px}
|
||
|
||
/* ═══ Inputs ═══ */
|
||
.v-input{
|
||
height:40px;padding:0 14px;border-radius:var(--v-radius-sm);font-size:14px;
|
||
border:1px solid var(--v-border);background:var(--v-surface);color:var(--v-on-surface);
|
||
outline:none;transition:all .2s;width:100%;
|
||
}
|
||
.v-input:hover{border-color:var(--v-on-surface-muted)}
|
||
.v-input:focus{border-color:var(--v-primary);border-width:2px;padding:0 13px}
|
||
.v-input::placeholder{color:var(--v-on-surface-muted)}
|
||
select.v-input{-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24'%3E%3Cpath fill='%239E9E9E' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;padding-right:36px}
|
||
|
||
/* ═══ Cards ═══ */
|
||
.v-card{
|
||
background:var(--v-surface);border-radius:var(--v-radius);border:1px solid var(--v-divider);
|
||
overflow:hidden;box-shadow:0 1px 2px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.06);
|
||
}
|
||
.v-card-header{padding:16px 20px;border-bottom:1px solid var(--v-divider);font-size:14px;font-weight:500;color:var(--v-on-surface)}
|
||
.v-card-body{padding:20px}
|
||
.v-card-actions{padding:12px 20px;border-top:1px solid var(--v-divider);display:flex;justify-content:flex-end;gap:8px}
|
||
|
||
/* ═══ Table ═══ */
|
||
.v-table{width:100%;border-collapse:collapse}
|
||
.v-table thead{background:var(--v-surface-variant)}
|
||
.v-table th{
|
||
padding:12px 20px;text-align:left;font-size:12px;font-weight:700;text-transform:uppercase;
|
||
letter-spacing:.06em;color:var(--v-on-surface-variant);border-bottom:1px solid var(--v-divider);height:48px;
|
||
}
|
||
.v-table td{
|
||
padding:14px 20px;border-bottom:1px solid var(--v-divider);color:var(--v-on-surface);height:52px;
|
||
}
|
||
.v-table tr:last-child td{border-bottom:none}
|
||
.v-table tbody tr:hover{background:rgba(24,103,192,.04)}
|
||
|
||
/* ═══ Badges ═══ */
|
||
.v-chip{
|
||
display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:16px;
|
||
font-size:12px;font-weight:500;white-space:nowrap;
|
||
}
|
||
.v-chip-success{background:rgba(53,104,89,.12);color:var(--v-success)}
|
||
.v-chip-error{background:rgba(151,40,0,.12);color:var(--v-error)}
|
||
.v-chip-muted{background:var(--v-surface-variant);color:var(--v-on-surface-muted)}
|
||
.v-chip-primary{background:rgba(24,103,192,.08);color:var(--v-primary)}
|
||
.v-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||
.v-dot-success{background:var(--v-success)}
|
||
.v-dot-error{background:var(--v-error)}
|
||
.v-dot-muted{background:var(--v-on-surface-muted)}
|
||
.v-dot-error-pulse{animation:v-pulse 2s infinite}
|
||
@keyframes v-pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
||
|
||
.v-link{font-size:13px;color:var(--v-primary);text-decoration:none;cursor:pointer;background:none;border:none;font-weight:500;padding:4px 8px;border-radius:4px;transition:all .15s}
|
||
.v-link:hover{background:rgba(24,103,192,.08)}
|
||
.v-link-muted{color:var(--v-on-surface-variant)}.v-link-muted:hover{color:var(--v-on-surface)}
|
||
.v-link-error{color:var(--v-error)}.v-link-error:hover{background:rgba(151,40,0,.08)}
|
||
|
||
/* ═══ Tabs ═══ */
|
||
.v-tabs{display:flex;border-bottom:1px solid var(--v-divider)}
|
||
.v-tab{
|
||
padding:12px 20px;font-size:13px;font-weight:500;color:var(--v-on-surface-variant);
|
||
border-bottom:2px solid transparent;transition:all .2s;cursor:pointer;background:none;border-top:none;border-left:none;border-right:none;letter-spacing:.02em;
|
||
}
|
||
.v-tab:hover{color:var(--v-on-surface);background:var(--v-surface-variant)}
|
||
.v-tab.active{color:var(--v-primary);border-bottom-color:var(--v-primary);font-weight:600}
|
||
|
||
/* ═══ Pagination ═══ */
|
||
.v-pagination{display:flex;align-items:center;gap:4px}
|
||
.v-page-btn{min-width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%;border:none;background:transparent;color:var(--v-on-surface-variant);font-size:13px;font-weight:500;cursor:pointer;transition:all .15s}
|
||
.v-page-btn:hover{background:var(--v-surface-variant)}
|
||
.v-page-btn.active{background:var(--v-primary);color:#fff;box-shadow:0 2px 6px rgba(24,103,192,.3)}
|
||
.v-page-btn:disabled{opacity:.3;cursor:default}
|
||
|
||
/* ═══ Dialog ═══ */
|
||
.v-overlay{position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.5);opacity:0;visibility:hidden;transition:all .2s}
|
||
.v-overlay.open{opacity:1;visibility:visible}
|
||
.v-dialog{background:var(--v-surface);border-radius:var(--v-radius);width:100%;max-width:460px;box-shadow:0 11px 15px rgba(0,0,0,.2),0 24px 38px rgba(0,0,0,.14);overflow:hidden}
|
||
.v-dialog-title{padding:20px 24px 0;font-size:16px;font-weight:500;color:var(--v-on-surface)}
|
||
.v-dialog-text{padding:16px 24px;font-size:14px;color:var(--v-on-surface-variant);line-height:1.6}
|
||
.v-dialog-actions{padding:16px 24px 20px;display:flex;justify-content:flex-end;gap:8px}
|
||
|
||
/* ═══ Toast ═══ */
|
||
.v-snackbar{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none}
|
||
.v-snackbar-item{
|
||
pointer-events:auto;display:flex;align-items:center;gap:12px;padding:10px 20px;border-radius:var(--v-radius-sm);
|
||
font-size:13px;background:var(--v-surface);color:var(--v-on-surface);
|
||
box-shadow:0 4px 12px rgba(0,0,0,.25);max-width:400px;
|
||
}
|
||
.v-snackbar-success{border-left:3px solid var(--v-success)}
|
||
.v-snackbar-error{border-left:3px solid var(--v-error)}
|
||
.v-snackbar-warning{border-left:3px solid var(--v-warning)}
|
||
|
||
/* ═══ Stat Cards ═══ */
|
||
.v-stat{padding:20px 24px}
|
||
.v-stat-label{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--v-on-surface-muted);margin-bottom:8px}
|
||
.v-stat-value{font-size:34px;font-weight:700;line-height:1;letter-spacing:-.02em}
|
||
.v-stat-sub{font-size:12px;color:var(--v-on-surface-muted);margin-top:4px}
|
||
|
||
.slide-up{animation:v-slide-up .25s ease}
|
||
@keyframes v-slide-up{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
||
|
||
.v-chip-group{display:flex;gap:6px;flex-wrap:wrap}
|
||
</style>
|
||
</head>
|
||
<body class="flex min-h-screen">
|
||
<div class="v-snackbar" id="toastContainer"></div>
|
||
|
||
<!-- ═══ NAVIGATION DRAWER ═══ -->
|
||
<aside class="w-60 shrink-0 flex flex-col border-r scrollbar" style="background:var(--v-surface);border-color:var(--v-divider)">
|
||
<div class="px-4 py-4 border-b" style="border-color:var(--v-divider)">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-9 h-9 rounded-lg flex items-center justify-center" style="background:var(--v-primary)">
|
||
<svg class="w-5 h-5 text-white" 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>
|
||
</div>
|
||
<span class="font-bold text-base tracking-tight" style="color:var(--v-on-surface)">Nexus</span>
|
||
</div>
|
||
</div>
|
||
<nav class="flex-1 py-3 overflow-y-auto">
|
||
<div class="nav-label">主菜单</div>
|
||
<a class="nav-item active"><span class="v-chip v-chip-primary" style="padding:2px 6px;font-size:11px">H</span>仪表盘</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">S</span>服务器</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">F</span>文件管理</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">P</span>推送</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">C</span>脚本库</a>
|
||
<div style="border-top:1px solid var(--v-divider);margin:8px 16px"></div>
|
||
<div class="nav-label">系统</div>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">L</span>命令日志</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">A</span>告警中心</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">U</span>审计日志</a>
|
||
<a class="nav-item"><span class="v-chip v-chip-muted" style="padding:2px 6px;font-size:11px">G</span>设置</a>
|
||
</nav>
|
||
<div style="border-top:1px solid var(--v-divider);padding:12px 16px">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold text-white" style="background:var(--v-primary)">A</div>
|
||
<div><div class="text-xs font-medium" style="color:var(--v-on-surface)">admin</div><div class="text-[10px]" style="color:var(--v-on-surface-muted)">管理员</div></div>
|
||
</div>
|
||
<button onclick="document.documentElement.classList.toggle('dark')" class="v-btn v-btn-text" style="width:36px;height:36px;padding:0;border-radius:50%" title="切换主题">
|
||
<svg style="width:18px;height:18px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"/></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ═══ MAIN ═══ -->
|
||
<div class="flex-1 flex flex-col min-w-0">
|
||
<header class="h-14 shrink-0 flex items-center justify-between px-6 border-b" style="background:var(--v-surface);border-color:var(--v-divider)">
|
||
<div class="flex items-center gap-4">
|
||
<h1 class="text-base font-medium tracking-tight" style="color:var(--v-on-surface)">仪表盘</h1>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<select class="v-input" style="width:140px"><option>全部分类</option><option>商城</option></select>
|
||
<button class="v-btn v-btn-tonal v-btn-sm" onclick="showToast('success','数据已刷新')">刷新</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="flex-1 overflow-y-auto p-6 space-y-6 scrollbar">
|
||
|
||
<!-- ═══ STATS ═══ -->
|
||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||
<div class="v-card v-stat"><div class="v-stat-label">服务器总数</div><div class="v-stat-value" style="color:var(--v-primary)">8</div><div class="v-stat-sub">+2 本月新增</div></div>
|
||
<div class="v-card v-stat"><div class="v-stat-label">在线</div><div class="v-stat-value" style="color:var(--v-success)">1</div><div class="v-stat-sub">12.5% 在线率</div></div>
|
||
<div class="v-card v-stat"><div class="v-stat-label">离线</div><div class="v-stat-value" style="color:var(--v-error)">7</div><div class="v-stat-sub">需要关注</div></div>
|
||
<div class="v-card v-stat"><div class="v-stat-label">告警中</div><div class="v-stat-value" style="color:var(--v-warning)">0</div><div class="v-stat-sub">一切正常</div></div>
|
||
</div>
|
||
|
||
<!-- ═══ TABLE ═══ -->
|
||
<div class="v-card">
|
||
<div class="v-card-header">服务器列表</div>
|
||
<div style="overflow-x:auto">
|
||
<table class="v-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="v-chip v-chip-success"><span class="v-dot v-dot-success"></span>在线</span></td>
|
||
<td><span style="font-weight:500">prod-web-01</span></td>
|
||
<td style="color:var(--v-on-surface-variant)">192.168.1.10:22</td>
|
||
<td><span class="v-chip v-chip-primary">商城</span></td>
|
||
<td style="color:var(--v-on-surface-variant)">v1.2.0</td>
|
||
<td style="color:var(--v-on-surface-muted);font-size:13px">3分钟前</td>
|
||
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">文件</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="v-chip v-chip-error"><span class="v-dot v-dot-error v-dot-error-pulse"></span>离线</span></td>
|
||
<td><span style="font-weight:500">dev-db-01</span></td>
|
||
<td style="color:var(--v-on-surface-variant)">10.0.0.5:22</td>
|
||
<td style="color:var(--v-on-surface-muted)">—</td>
|
||
<td style="color:var(--v-on-surface-variant)">v1.1.8</td>
|
||
<td style="color:var(--v-on-surface-muted);font-size:13px">2小时前</td>
|
||
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">文件</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="v-chip v-chip-muted"><span class="v-dot v-dot-muted"></span>未安装</span></td>
|
||
<td><span style="font-weight:500">new-server-03</span></td>
|
||
<td style="color:var(--v-on-surface-variant)">172.16.0.8:22</td>
|
||
<td style="color:var(--v-on-surface-muted)">—</td>
|
||
<td style="color:var(--v-on-surface-muted)">—</td>
|
||
<td style="color:var(--v-on-surface-muted);font-size:13px">—</td>
|
||
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="v-chip v-chip-success"><span class="v-dot v-dot-success"></span>在线</span></td>
|
||
<td><span style="font-weight:500">cache-redis-01</span></td>
|
||
<td style="color:var(--v-on-surface-variant)">10.0.0.10:6379</td>
|
||
<td><span class="v-chip v-chip-primary">数据库</span></td>
|
||
<td style="color:var(--v-on-surface-variant)">v1.2.0</td>
|
||
<td style="color:var(--v-on-surface-muted);font-size:13px">1分钟前</td>
|
||
<td style="text-align:right"><button class="v-link">终端</button><button class="v-link v-link-muted">文件</button><button class="v-link v-link-muted">编辑</button><button class="v-link v-link-error">删除</button></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="v-card-actions">
|
||
<span style="color:var(--v-on-surface-muted);font-size:12px;margin-right:auto">每页 50 条 · 共 8 台</span>
|
||
<div class="v-pagination">
|
||
<button class="v-page-btn" disabled>‹</button>
|
||
<button class="v-page-btn active">1</button>
|
||
<button class="v-page-btn">2</button>
|
||
<button class="v-page-btn">›</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ FORMS + COMPONENTS ═══ -->
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div class="v-card">
|
||
<div class="v-card-header">添加服务器</div>
|
||
<div class="v-card-body space-y-4">
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">名称 *</label><input class="v-input" placeholder="web-server-01"></div>
|
||
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">地址 *</label><input class="v-input" placeholder="192.168.1.10"></div>
|
||
</div>
|
||
<div class="grid grid-cols-3 gap-4">
|
||
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">端口</label><input class="v-input" value="22"></div>
|
||
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">用户</label><input class="v-input" value="root"></div>
|
||
<div><label class="block text-[11px] font-semibold mb-1.5 uppercase tracking-wider" style="color:var(--v-on-surface-muted)">认证</label><select class="v-input"><option>密码</option><option>密钥</option></select></div>
|
||
</div>
|
||
</div>
|
||
<div class="v-card-actions">
|
||
<button class="v-btn v-btn-primary v-btn-sm" onclick="showToast('success','服务器已添加')">保存</button>
|
||
<button class="v-btn v-btn-text v-btn-sm">取消</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v-card">
|
||
<div class="v-card-header">组件展示</div>
|
||
<div class="v-card-body space-y-5">
|
||
<div class="v-chip-group"><button class="v-btn v-btn-primary v-btn-sm">主要</button><button class="v-btn v-btn-tonal v-btn-sm">色调</button><button class="v-btn v-btn-outlined v-btn-sm">描边</button><button class="v-btn v-btn-text v-btn-sm">文本</button></div>
|
||
<div style="height:1px;background:var(--v-divider)"></div>
|
||
<div class="v-tabs"><button class="v-tab active">系统信息</button><button class="v-tab">同步日志</button><button class="v-tab">SSH 会话</button></div>
|
||
<div style="height:1px;background:var(--v-divider)"></div>
|
||
<div class="v-chip-group">
|
||
<span class="v-chip v-chip-success"><span class="v-dot v-dot-success"></span>在线</span>
|
||
<span class="v-chip v-chip-error"><span class="v-dot v-dot-error v-dot-error-pulse"></span>离线</span>
|
||
<span class="v-chip v-chip-muted"><span class="v-dot v-dot-muted"></span>未安装</span>
|
||
</div>
|
||
<div style="height:1px;background:var(--v-divider)"></div>
|
||
<label class="flex items-center gap-3 cursor-pointer" style="color:var(--v-on-surface)">
|
||
<input type="checkbox" checked style="width:18px;height:18px;accent-color:var(--v-primary)"> 全选服务器
|
||
</label>
|
||
<div style="height:1px;background:var(--v-divider)"></div>
|
||
<button class="v-btn v-btn-outlined v-btn-sm" onclick="document.getElementById('demoDialog').classList.add('open')">打开删除确认对话框</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- ═══ DIALOG ═══ -->
|
||
<div class="v-overlay" id="demoDialog" onclick="if(event.target===this)this.classList.remove('open')">
|
||
<div class="v-dialog slide-up">
|
||
<div class="v-dialog-title">确认删除</div>
|
||
<div class="v-dialog-text">您确定要删除服务器 <strong style="color:var(--v-on-surface)">prod-web-01</strong> 吗?此操作无法撤销。</div>
|
||
<div class="v-dialog-actions">
|
||
<button class="v-btn v-btn-text v-btn-sm" onclick="document.getElementById('demoDialog').classList.remove('open')">取消</button>
|
||
<button class="v-btn v-btn-primary v-btn-sm" style="background:var(--v-error)" onclick="document.getElementById('demoDialog').classList.remove('open');showToast('success','服务器已删除')">删除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function showToast(type,msg){
|
||
const icons={success:'<svg style="width:18px;height:18px" viewBox="0 0 24 24"><path fill="#356859" d="M21 7L9 19l-5.5-5.5 1.41-1.41L9 16.17 19.59 5.59z"/></svg>',error:'<svg style="width:18px;height:18px" viewBox="0 0 24 24"><path fill="#972800" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/></svg>',warning:'<svg style="width:18px;height:18px" viewBox="0 0 24 24"><path fill="#E7810D" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>'};
|
||
const el=document.createElement('div');
|
||
el.className='v-snackbar-item v-snackbar-'+type+' slide-up';
|
||
el.innerHTML=(icons[type]||icons.success)+'<span style="flex:1">'+msg+'</span><button onclick="this.parentElement.remove()" class="v-btn v-btn-text" style="width:28px;height:28px;padding:0;border-radius:50%">×</button>';
|
||
document.getElementById('toastContainer').appendChild(el);
|
||
setTimeout(()=>{el.style.opacity='0';el.style.transition='opacity .2s';setTimeout(()=>el.remove(),200)},3500);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|