Files
Docker-Proxy/hubcmdui/web/admin.html

4968 lines
163 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docker 镜像代理加速 - 管理面板</title>
<link rel="icon" href="https://cdn.jsdelivr.net/gh/dqzboy/Blog-Image/BlogCourse/docker-proxy.png" type="image/png">
<!-- 引入前端样式表 -->
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 引入Bootstrap CSS和JS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- 引入 jQuery (Editor.md 需要) -->
<script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<!-- 引入 Markdown 编辑器 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- 引入 Editor.md -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/editor.md@1.5.0/css/editormd.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/mode/overlay.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/mode/markdown/markdown.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/mode/gfm/gfm.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/editor.md@1.5.0/editormd.min.js"></script>
<!-- 自定义样式 -->
<link rel="stylesheet" href="css/admin.css">
<style>
/* 管理面板特定样式 */
.admin-container {
display: flex;
min-height: 100vh;
background-color: var(--background-color);
}
.sidebar {
width: 280px;
background: linear-gradient(180deg, var(--container-bg) 0%, #f8fafc 100%);
box-shadow: var(--shadow-lg);
padding: 1.5rem 0;
z-index: 10;
transition: all 0.3s ease;
border-right: 1px solid var(--border-light, #e5e7eb);
}
.sidebar-header {
text-align: center;
margin-bottom: 1rem;
padding: 0 1rem;
}
.sidebar-header h2 {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 1.25rem;
font-weight: 700;
color: var(--primary-color);
margin: 0;
}
.sidebar-header h2 i {
font-size: 1.1rem;
}
/* 文档管理新建文档徽章 */
.new-badge {
display: inline-block;
padding: 2px 8px;
background: linear-gradient(135deg, #10b981, #34d399);
color: white;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
box-shadow: 0 2px 5px rgba(16, 185, 129, 0.3);
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
/* 用户信息部分样式 */
.user-profile {
padding: 1.25rem 1.5rem;
margin: 0 1rem 1.5rem;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background: linear-gradient(135deg, rgba(61, 124, 244, 0.08) 0%, rgba(99, 102, 241, 0.05) 100%);
border-radius: var(--radius-lg);
border: 1px solid rgba(61, 124, 244, 0.1);
}
.user-avatar {
width: 75px;
height: 75px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color), #6366f1);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: white;
margin-bottom: 1rem;
box-shadow: 0 4px 15px rgba(61, 124, 244, 0.3);
border: 3px solid white;
}
.user-info {
text-align: center;
margin-bottom: 0.5rem;
}
.user-name {
font-weight: 700;
font-size: 1.15rem;
color: var(--text-primary);
margin-bottom: 0.25rem;
}
.user-role {
color: var(--text-secondary);
font-size: 0.85rem;
margin-bottom: 0.5rem;
background: rgba(61, 124, 244, 0.1);
padding: 0.25rem 0.75rem;
border-radius: 20px;
display: inline-block;
}
.user-actions {
display: flex;
gap: 0.75rem;
margin-top: 0.75rem;
}
.user-action-btn {
padding: 0.5rem 1rem;
font-size: 0.85rem;
border-radius: var(--radius-md);
background-color: rgba(61, 124, 244, 0.1);
color: var(--primary-color);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
}
.user-action-btn:hover {
background-color: var(--primary-color);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(61, 124, 244, 0.25);
}
.user-action-btn.logout {
background-color: rgba(239, 68, 68, 0.1);
color: var(--danger-color);
}
.user-action-btn.logout:hover {
background-color: var(--danger-color);
color: white;
box-shadow: 0 4px 10px rgba(239, 68, 68, 0.25);
}
/* 仪表盘卡片样式 */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.dashboard-card {
background-color: var(--container-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 1.75rem;
transition: all var(--transition-normal);
border: 1px solid var(--border-light);
position: relative;
overflow: hidden;
}
.dashboard-card:hover {
transform: translateY(-6px);
box-shadow: 0 12px 25px rgba(0, 0, 0, 0.1);
border-color: var(--primary-light);
}
.dashboard-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 5px;
height: 100%;
background: linear-gradient(180deg, var(--primary-color), #6366f1);
border-radius: 3px 0 0 3px;
}
.dashboard-card::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, transparent 60%, rgba(61, 124, 244, 0.03) 100%);
pointer-events: none;
}
.card-icon {
width: 55px;
height: 55px;
border-radius: 14px;
background: linear-gradient(135deg, rgba(61, 124, 244, 0.15), rgba(99, 102, 241, 0.1));
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.25rem;
color: var(--primary-color);
font-size: 1.5rem;
position: relative;
z-index: 1;
}
.card-title {
font-size: 1.15rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-primary);
position: relative;
z-index: 1;
}
.card-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.card-description {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1rem;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid var(--border-light);
padding-top: 1rem;
margin-top: 0.5rem;
}
.trend {
display: flex;
align-items: center;
gap: 0.3rem;
font-size: 0.9rem;
}
.trend.up {
color: var(--success-color);
}
.trend.down {
color: var(--danger-color);
}
.card-action {
color: var(--primary-color);
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
}
.welcome-banner {
background: linear-gradient(135deg, var(--primary-color) 0%, #6366f1 50%, var(--primary-dark) 100%);
border-radius: var(--radius-lg);
padding: 2.5rem;
margin-bottom: 2rem;
color: white;
position: relative;
overflow: hidden;
box-shadow: 0 10px 40px rgba(61, 124, 244, 0.3);
}
.welcome-content {
position: relative;
z-index: 1;
}
.welcome-title {
font-size: 2rem;
margin-bottom: 0.75rem;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.welcome-title span {
background: linear-gradient(90deg, #fff, #e0e7ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.welcome-subtitle {
font-size: 1.1rem;
margin-bottom: 1.5rem;
opacity: 0.95;
font-weight: 400;
}
.welcome-action {
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 0.85rem 1.75rem;
border-radius: var(--radius-md);
font-weight: 500;
cursor: pointer;
transition: all var(--transition-fast);
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.95rem;
}
.welcome-action:hover {
background-color: rgba(255, 255, 255, 0.35);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.welcome-action i {
transition: transform 0.3s ease;
}
.welcome-action:hover i {
transform: rotate(180deg);
}
.welcome-banner::after {
content: '';
position: absolute;
top: -60%;
right: -15%;
width: 350px;
height: 350px;
background: rgba(255, 255, 255, 0.08);
border-radius: 50%;
animation: pulse-slow 4s ease-in-out infinite;
}
.welcome-banner::before {
content: '';
position: absolute;
bottom: -40%;
left: -8%;
width: 250px;
height: 250px;
background: rgba(255, 255, 255, 0.06);
border-radius: 50%;
animation: pulse-slow 4s ease-in-out infinite 1s;
}
@keyframes pulse-slow {
0%, 100% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.05); opacity: 1; }
}
/* Docker 状态指示器 */
.docker-status {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 0.5rem 1rem;
border-radius: var(--radius-md);
font-size: 0.9rem;
font-weight: 500;
transition: all var(--transition-fast);
}
.docker-status.docker-running {
background: rgba(16, 185, 129, 0.2);
border-color: rgba(16, 185, 129, 0.4);
}
.docker-status.docker-stopped {
background: rgba(239, 68, 68, 0.2);
border-color: rgba(239, 68, 68, 0.4);
}
/* 用户中心样式 */
.user-center-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.user-center-title {
font-size: 1.8rem;
font-weight: 600;
color: var(--text-primary);
}
.user-center-subtitle {
color: var(--text-secondary);
margin-top: 0.5rem;
}
.user-center-card {
background-color: var(--container-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 2rem;
margin-bottom: 2rem;
transition: all 0.3s ease;
}
.user-center-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-lg);
}
.user-center-section {
margin-bottom: 1rem;
}
.user-center-section-title {
font-size: 1.3rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text-primary);
position: relative;
padding-bottom: 0.75rem;
}
.user-center-section-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60px;
height: 2px;
background: var(--primary-color);
border-radius: 2px;
}
.user-stats {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 1rem;
}
.stat-card {
background-color: var(--container-bg);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
padding: 1.5rem;
border: 1px solid var(--border-light);
text-align: center;
transition: all 0.3s ease;
}
.stat-card:hover {
border-color: var(--primary-color);
transform: translateY(-3px);
box-shadow: var(--shadow-md);
}
.stat-icon {
font-size: 1.8rem;
color: var(--primary-color);
margin-bottom: 0.75rem;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 0.5rem;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9rem;
}
/* 新增用户个人资料卡片样式 */
.user-profile-card {
display: flex;
align-items: center;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
color: white;
border-radius: var(--radius-lg);
padding: 2rem;
margin-bottom: 2rem;
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
}
.user-profile-card::before {
content: '';
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 200px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
z-index: 0;
}
.user-profile-card::after {
content: '';
position: absolute;
bottom: -60px;
left: 30%;
width: 150px;
height: 150px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
z-index: 0;
}
.user-profile-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
font-size: 2.5rem;
margin-right: 1.5rem;
z-index: 1;
}
.user-profile-info {
flex: 1;
z-index: 1;
}
.user-profile-name {
font-size: 1.8rem;
font-weight: 600;
margin: 0 0 0.3rem 0;
}
.user-profile-role {
font-size: 1rem;
opacity: 0.8;
margin: 0 0 1rem 0;
}
.user-profile-badges {
display: flex;
gap: 0.75rem;
}
.user-badge {
padding: 0.3rem 0.75rem;
border-radius: 20px;
font-size: 0.85rem;
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.user-badge.admin {
background-color: rgba(255, 193, 7, 0.3);
}
.user-badge.active {
background-color: rgba(40, 167, 69, 0.3);
}
.user-profile-actions {
z-index: 1;
}
.btn-outline {
background-color: transparent;
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 0.6rem 1rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-outline:hover {
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.btn-danger-outline {
background-color: transparent;
border: 2px solid var(--danger-color);
color: var(--danger-color);
padding: 0.6rem 1.2rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
}
.btn-danger-outline:hover {
background-color: var(--danger-color);
color: white;
}
/* 用户统计行样式 */
.user-stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
margin-bottom: 2rem;
}
@media (max-width: 1200px) {
.user-stats-row {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.user-stats-row {
grid-template-columns: 1fr;
}
}
.stat-card-enhanced {
background: var(--container-bg);
border-radius: var(--radius-lg);
padding: 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-light);
transition: all 0.3s ease;
}
.stat-card-enhanced:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
border-color: var(--primary-color);
}
.stat-icon-wrapper {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.stat-icon-wrapper.blue {
background: linear-gradient(135deg, #3d7cf4 0%, #5e95f7 100%);
color: white;
}
.stat-icon-wrapper.green {
background: linear-gradient(135deg, #28a745 0%, #48c764 100%);
color: white;
}
.stat-icon-wrapper.purple {
background: linear-gradient(135deg, #6f42c1 0%, #8c68cd 100%);
color: white;
}
.stat-icon-wrapper.orange {
background: linear-gradient(135deg, #fd7e14 0%, #fea347 100%);
color: white;
}
.stat-content {
flex: 1;
}
.stat-card-enhanced .stat-value {
font-size: 1.4rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.25rem;
}
.stat-card-enhanced .stat-label {
font-size: 0.85rem;
color: var(--text-secondary);
}
/* 安全设置区域标题 */
.security-section-header {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--border-light);
}
.security-section-header h2 {
font-size: 1.4rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.5rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.security-section-header h2 i {
color: var(--primary-color);
}
.security-section-header p {
color: var(--text-secondary);
margin: 0;
font-size: 0.9rem;
}
/* 安全卡片样式 */
.security-card {
position: relative;
overflow: hidden;
}
.security-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: var(--primary-color);
border-radius: 4px 0 0 4px;
}
.card-icon-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.card-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.3rem;
}
.card-icon.blue {
background: rgba(61, 124, 244, 0.1);
color: #3d7cf4;
}
.card-icon.orange {
background: rgba(253, 126, 20, 0.1);
color: #fd7e14;
}
.card-icon-header .user-center-section-title {
margin: 0;
padding: 0;
}
.card-icon-header .user-center-section-title::after {
display: none;
}
.card-description {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1.5rem;
line-height: 1.5;
}
/* 现代表单样式 */
.modern-form {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.modern-form .form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.modern-form label {
font-weight: 500;
color: var(--text-primary);
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.modern-form label i {
color: var(--primary-color);
font-size: 0.85rem;
}
.form-input {
width: 100%;
padding: 0.5rem 0.75rem; /* 减小全局输入内边距以降低高度 */
border: 2px solid var(--border-light);
border-radius: var(--radius-md);
font-size: 0.95rem;
transition: all 0.16s ease;
background: var(--container-bg);
box-sizing: border-box;
line-height: 1.1;
}
.form-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(61, 124, 244, 0.1);
}
.form-input::placeholder {
color: var(--text-secondary);
opacity: 0.6;
}
.input-hint {
font-size: 0.8rem;
color: var(--text-secondary);
margin-top: -0.25rem;
}
.password-input-wrapper {
display: flex;
align-items: center;
width: 100%;
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
background: var(--container-bg);
transition: all 0.15s ease;
overflow: hidden;
padding: 0 8px; /* 取消垂直内边距,使用固定输入高度居中 */
}
.password-input-wrapper:focus-within {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(61, 124, 244, 0.08);
}
.password-input-wrapper .form-input {
flex: 1;
border: none !important;
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
padding: 0 12px !important; /* 横向内边距 */
height: 28px !important; /* 固定高度,保持不变 */
line-height: 28px !important; /* 与高度相同,垂直居中占位文本与输入文本 */
font-size: 14px !important;
box-sizing: border-box !important;
vertical-align: middle;
}
.password-input-wrapper .form-input::placeholder {
/* 有时浏览器对 placeholder 的居中不同,确保其 padding 一致 */
color: var(--text-secondary);
opacity: 0.6;
}
.password-input-wrapper .form-input:focus {
border: none !important;
box-shadow: none !important;
}
.password-input-wrapper .toggle-password {
width: 32px;
height: 32px;
background: #3b82f6;
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
font-size: 13px;
padding: 0;
margin-left: 8px;
flex-shrink: 0;
}
.password-input-wrapper .toggle-password:hover {
background: #2563eb;
}
.password-input-wrapper .toggle-password i {
pointer-events: none;
}
/* 密码强度条 */
.password-strength-bar {
height: 4px;
background: var(--border-light);
border-radius: 2px;
overflow: hidden;
margin-top: 0.5rem;
}
.strength-indicator {
height: 100%;
width: 0;
border-radius: 2px;
transition: all 0.3s ease;
}
.strength-indicator.weak {
width: 33%;
background: #dc3545;
}
.strength-indicator.medium {
width: 66%;
background: #ffc107;
}
.strength-indicator.strong {
width: 100%;
background: #28a745;
}
.password-strength-text {
font-size: 0.8rem;
margin-top: 0.5rem;
}
.password-strength-text.weak {
color: #dc3545;
}
.password-strength-text.medium {
color: #ffc107;
}
.password-strength-text.strong {
color: #28a745;
}
.btn-block {
width: 100%;
justify-content: center;
padding: 0.875rem 1.5rem;
font-size: 1rem;
margin-top: 0.5rem;
}
.form-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* 安全提示样式 */
.security-tips {
background: linear-gradient(135deg, rgba(61, 124, 244, 0.05) 0%, rgba(61, 124, 244, 0.1) 100%);
border: 1px solid rgba(61, 124, 244, 0.2);
border-radius: var(--radius-lg);
padding: 1.5rem;
margin-top: 2rem;
}
.tip-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
font-weight: 600;
color: var(--primary-color);
font-size: 1.1rem;
}
.tip-header i {
font-size: 1.3rem;
}
.tip-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
@media (max-width: 768px) {
.tip-list {
grid-template-columns: 1fr;
}
}
.tip-list li {
display: flex;
align-items: flex-start;
gap: 0.5rem;
color: var(--text-secondary);
font-size: 0.9rem;
line-height: 1.4;
}
.tip-list li i {
color: #28a745;
margin-top: 0.2rem;
flex-shrink: 0;
}
/* 用户信息网格布局 */
.user-dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 450px), 1fr));
gap: 2rem;
}
/* 系统使用情况样式 */
.system-usage-stats {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.usage-stat {
width: 100%;
}
.usage-label {
display: flex;
justify-content: space-between;
margin-bottom: 0.6rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.usage-label i {
color: var(--primary-color);
margin-right: 0.5rem;
}
.usage-value {
font-weight: 600;
color: var(--text-primary);
}
.progress-bar-container {
height: 8px;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 4px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, var(--primary-color) 0%, var(--primary-light) 100%);
border-radius: 4px;
}
/* 密码表单样式 */
.password-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.password-input-group {
display: flex;
position: relative;
}
.password-input {
flex: 1;
padding: 0.9rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-primary);
background-color: var(--container-bg);
padding-right: 40px;
}
.password-toggle-btn {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
transition: color 0.2s;
}
.password-toggle-btn:hover {
color: var(--primary-color);
}
.password-hint {
font-size: 0.8rem;
color: var(--text-secondary);
line-height: 1.3;
}
.password-strength-meter {
margin-top: 0.75rem;
}
.strength-bar {
height: 6px;
background-color: #f0f0f0;
border-radius: 3px;
margin-bottom: 0.4rem;
position: relative;
}
.strength-bar::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
border-radius: 3px;
width: 0;
transition: width 0.3s, background-color 0.3s;
}
.strength-text {
font-size: 0.8rem;
}
.password-submit-btn {
align-self: flex-start;
margin-top: 0.5rem;
}
/* 活动列表样式 */
.activity-list {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.activity-item {
display: flex;
align-items: center;
gap: 1rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--border-light);
}
.activity-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.activity-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1rem;
}
.activity-icon.login {
background-color: #4361ee;
}
.activity-icon.container {
background-color: #3a86ff;
}
.activity-icon.password {
background-color: #f72585;
}
.activity-content {
flex: 1;
}
.activity-title {
font-weight: 500;
color: var(--text-primary);
margin-bottom: 0.25rem;
}
.activity-time {
font-size: 0.8rem;
color: var(--text-secondary);
}
.sidebar h2 {
color: var(--text-primary);
padding: 0 1.5rem;
margin-bottom: 2rem;
font-size: 1.3rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.75rem;
position: relative;
}
.sidebar h2::after {
content: '';
position: absolute;
bottom: -10px;
left: 1.5rem;
right: 1.5rem;
height: 1px;
background: linear-gradient(to right, var(--primary-light), transparent);
}
.sidebar h2 i {
color: var(--primary-color);
font-size: 1.5rem;
}
.sidebar ul {
list-style: none;
padding: 0 0.75rem;
margin: 0;
}
.sidebar li {
padding: 0.9rem 1.25rem;
cursor: pointer;
transition: all var(--transition-fast);
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 0.9rem;
font-weight: 500;
margin: 0.35rem 0;
border-radius: var(--radius-md);
border-left: none;
position: relative;
}
.sidebar li:hover {
background: linear-gradient(90deg, rgba(61, 124, 244, 0.1), rgba(99, 102, 241, 0.05));
color: var(--primary-color);
transform: translateX(3px);
}
.sidebar li.active {
background: linear-gradient(90deg, var(--primary-color), #6366f1);
color: white;
font-weight: 600;
box-shadow: 0 4px 15px rgba(61, 124, 244, 0.3);
}
.sidebar li.active::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 60%;
background: white;
border-radius: 0 3px 3px 0;
}
.sidebar li i {
font-size: 1.15rem;
width: 1.5rem;
text-align: center;
transition: transform var(--transition-fast);
}
.sidebar li:hover i {
transform: scale(1.1);
}
.sidebar li.active i {
color: white;
}
.content-area {
flex: 1;
padding: 2rem;
overflow-y: auto;
background: linear-gradient(135deg, var(--background-color) 0%, rgba(247, 250, 255, 0.8) 100%);
}
.content-section {
background-color: var(--container-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 2rem;
margin-bottom: 1.5rem;
display: none;
transform: translateY(10px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.content-section.active {
display: block;
transform: translateY(0);
opacity: 1;
}
.admin-title {
color: var(--text-primary);
font-size: 1.6rem;
font-weight: 600;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-light);
position: relative;
}
.admin-title::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 80px;
height: 3px;
background: var(--primary-color);
border-radius: 2px;
}
.menu-label {
color: var(--text-primary);
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 1.5rem;
position: relative;
padding-bottom: 0.5rem;
}
.menu-label::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60px;
height: 2px;
background: var(--primary-color);
border-radius: 2px;
}
/* 表单元素样式 */
.content-section input[type="text"],
.content-section input[type="url"],
.content-section input[type="password"],
.content-section input[type="number"],
.content-section select {
width: 100%;
max-width: 500px;
padding: 0.9rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background-color: var(--container-bg);
color: var(--text-primary);
font-size: 0.95rem;
margin-bottom: 1.2rem;
transition: all var(--transition-fast);
box-shadow: var(--shadow-sm);
}
.content-section input:focus,
.content-section select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(61, 124, 244, 0.2);
outline: none;
}
.content-section button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 0.9rem 1.4rem;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 0.95rem;
font-weight: 500;
transition: all var(--transition-fast);
margin-top: 0.5rem;
margin-right: 0.5rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.content-section button:hover {
background-color: var(--primary-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.content-section button:active {
transform: translateY(0);
}
.content-section label {
display: block;
margin-bottom: 0.5rem;
color: var(--text-primary);
font-weight: 500;
font-size: 1rem;
}
/* 表格样式 - 增强 Excel 效果 */
.content-section table {
width: 100%;
border-collapse: collapse; /* 合并边框 */
border-spacing: 0;
margin-top: 1.5rem;
margin-bottom: 2rem;
border: 1px solid #ccc; /* 表格外边框 */
font-size: 0.9rem; /* 稍微减小字体 */
box-shadow: 0 2px 5px rgba(0,0,0,0.1); /* 轻微阴影 */
}
.content-section th {
background-color: #f2f2f2; /* Excel 灰色表头背景 */
color: #333;
font-weight: bold; /* 加粗 */
text-align: left;
padding: 0.6rem 0.8rem; /* 调整内边距 */
border: 1px solid #ccc; /* 单元格边框 */
}
.content-section td {
padding: 0.6rem 0.8rem; /* 调整内边距 */
border: 1px solid #ddd; /* 单元格边框,比表头稍浅 */
vertical-align: middle;
background-color: #ffffff;
color: #444; /* 单元格文字颜色 */
word-break: break-word; /* 防止长 ID 撑开单元格 */
}
/* 可选:添加斑马纹效果 */
/*
.content-section tr:nth-child(even) td {
background-color: #f9f9f9;
}
*/
.content-section tr:hover td {
background-color: #e9f5ff; /* 鼠标悬停高亮 */
}
/* 表格头部悬停效果 */
.content-section th:hover {
background-color: #e8e8e8;
}
/* Excel风格的表格滚动条 */
.table-container {
overflow-x: auto;
max-width: 100%;
margin-bottom: 1.5rem;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
/* 操作列样式 */
.action-cell {
text-align: center;
min-width: 120px;
}
/* 状态列样式 */
.status-running {
background-color: #28a745;
color: white;
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
display: inline-block;
text-align: center;
}
.status-stopped, .status-exited {
background-color: #dc3545;
color: white;
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
display: inline-block;
text-align: center;
}
.status-created {
background-color: #17a2b8;
color: white;
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
display: inline-block;
text-align: center;
}
.status-paused {
background-color: #ffc107;
color: #212529;
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
display: inline-block;
text-align: center;
}
.status-unknown {
background-color: #6c757d;
color: white;
padding: 0.3rem 0.6rem;
border-radius: 4px;
font-size: 0.8rem;
display: inline-block;
text-align: center;
}
/* 下拉菜单样式 */
.action-dropdown .dropdown-toggle {
border-color: #d0d0d0;
background-color: #fff;
color: #333;
font-size: 0.9rem;
padding: 0.4rem 0.8rem;
}
.action-dropdown .dropdown-toggle:hover,
.action-dropdown .dropdown-toggle:focus {
background-color: #f0f0f0;
box-shadow: none;
}
.action-dropdown .dropdown-menu {
min-width: 150px;
padding: 0.3rem 0;
border: 1px solid #d0d0d0;
border-radius: 4px;
box-shadow: 0 3px 6px rgba(0,0,0,0.1);
font-size: 0.9rem;
}
.action-dropdown .dropdown-item {
padding: 0.5rem 1rem;
color: #333;
}
.action-dropdown .dropdown-item:hover {
background-color: #f0f7ff;
}
.action-dropdown .dropdown-item i {
width: 1rem;
text-align: center;
margin-right: 0.5rem;
}
.action-dropdown .text-danger {
color: #dc3545 !important;
}
.action-dropdown .dropdown-divider {
margin: 0.3rem 0;
}
/* 登录模态框 */
.login-modal {
display: flex;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background: url('/images/login-bg.jpg') center/cover no-repeat;
justify-content: center;
align-items: center;
animation: fadeIn 0.5s ease-in-out;
}
.login-modal::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(26, 35, 126, 0.9) 0%, rgba(61, 124, 244, 0.85) 50%, rgba(99, 102, 241, 0.85) 100%);
backdrop-filter: blur(8px);
}
.login-content {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.95));
padding: 2.75rem;
border-radius: var(--radius-lg);
width: 400px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
position: relative;
animation: slideUp 0.5s ease-out;
border: 1px solid rgba(255, 255, 255, 0.3);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.login-header h2 {
color: var(--text-primary);
font-size: 1.9rem;
margin-bottom: 2rem;
text-align: center;
font-weight: 700;
position: relative;
padding-bottom: 1rem;
}
.login-header h2::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: linear-gradient(90deg, var(--primary-color), #6366f1);
border-radius: 2px;
}
.login-form input[type="text"],
.login-form input[type="password"] {
width: 100%;
padding: 1rem 1.25rem;
margin: 0.75rem 0;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
box-sizing: border-box;
font-size: 1rem;
background-color: #fafbfc;
color: var(--text-primary);
transition: all var(--transition-fast);
box-shadow: var(--shadow-sm);
}
.login-form input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 4px rgba(61, 124, 244, 0.15);
outline: none;
background-color: white;
}
.login-form button {
width: 100%;
padding: 1rem;
background: linear-gradient(135deg, var(--primary-color), #6366f1);
color: white;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 1.1rem;
font-weight: 600;
margin-top: 1.8rem;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
box-shadow: 0 4px 15px rgba(61, 124, 244, 0.35);
}
.login-form button:hover {
background: linear-gradient(135deg, #5084f5, #7c7ff7);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(61, 124, 244, 0.4);
}
.login-form button:active {
transform: translateY(0);
}
.login-form button:before {
content: "\f2f6";
font-family: "Font Awesome 6 Free";
font-weight: 900;
}
.captcha-container {
display: flex;
align-items: center;
margin: 0.75rem 0;
gap: 0.75rem;
}
.captcha-container input {
flex: 1;
}
.captcha-container span {
padding: 0.85rem 1rem;
background: linear-gradient(135deg, #f0f4ff, #f7f8fc);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
cursor: pointer;
font-size: 0.95rem;
color: var(--text-secondary);
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
}
.captcha-container span:hover {
background: linear-gradient(135deg, #e8eeff, #f0f4ff);
color: var(--primary-color);
border-color: var(--primary-color);
}
/* 加载指示器 */
.loading-spinner {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 5px solid rgba(61, 124, 244, 0.1);
border-top: 5px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
z-index: 9999;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
#loadingIndicator {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--background-color);
color: var(--text-secondary);
font-size: 1.1rem;
}
/* 状态指示样式 */
.disabled {
opacity: 0.5;
pointer-events: none;
}
.status-cell {
position: relative;
min-height: 24px;
}
.status-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
text-align: center;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(61, 124, 244, 0.1);
border-radius: 50%;
border-top: 3px solid var(--primary-color);
animation: spin 1s linear infinite;
}
/* 监控配置部分 */
.monitoring-status {
margin-bottom: 1.5rem;
font-size: 1rem;
color: var(--text-secondary);
background-color: rgba(61, 124, 244, 0.05);
padding: 1rem 1.5rem;
border-radius: var(--radius-md);
display: flex;
align-items: center;
gap: 0.75rem;
}
.monitoring-status:before {
content: "\f021";
font-family: "Font Awesome 6 Free";
font-weight: 900;
color: var(--primary-color);
}
.status-indicator {
font-weight: 600;
}
.config-form {
background-color: var (--container-bg);
padding: 1.8rem;
border-radius: var(--radius-md);
margin-bottom: 1.5rem;
border: 1px solid var(--border-light);
box-shadow: var(--shadow-sm);
}
.form-group {
margin-bottom: 1.2rem;
}
.form-control {
width: 100%;
padding: 0.9rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-primary);
background-color: var(--container-bg);
}
.button-group {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
margin-top: 1.5rem;
}
.btn {
padding: 0.8rem 1.2rem;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 0.95rem;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
/* Fix 4: Remove icon from user center buttons */
.user-center-header .btn-primary,
.user-center-section .btn-primary {
background-color: var(--primary-color);
color: white;
padding: 0.8rem 1.2rem;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 0.95rem;
font-weight: 500;
display: inline-block; /* Change to inline-block */
}
/* Remove the :before pseudo-element for these specific buttons */
.user-center-header .btn-primary:before,
.user-center-section .btn-primary:before {
display: none;
}
.btn-secondary {
background-color: var(--text-secondary);
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-sm);
}
.btn:active {
transform: translateY(0);
}
.section-title {
color: var(--text-primary);
font-size: 1.3rem;
font-weight: 600;
margin: 2rem 0 1.2rem;
position: relative;
padding-left: 1rem;
border-left: 3px solid var(--primary-color);
}
.container-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin-bottom: 1.5rem;
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.container-table th {
background-color: var(--primary-color);
color: white;
padding: 0.9rem 1rem;
text-align: left;
font-weight: 500;
}
.container-table td {
padding: 0.9rem 1rem;
border-bottom: 1px solid var(--border-light);
}
.container-table tr:last-child td {
border-bottom: none;
}
.error-message {
color: var(--danger-color);
margin-top: 0.75rem;
padding: 0.75rem;
border-radius: var (--radius-md);;
background-color: rgba(255, 82, 82, 0.1);
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.error-message:before {
content: "\f071";
font-family: "Font Awesome 6 Free";
font-weight: 900;
}
.success-message {
color: var (--success-color);
margin-top: 0.75rem;
padding: 0.75rem;
border-radius: var (--radius-md);
background-color: rgba(50, 213, 131, 0.1);
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.success-message:before {
content: "\f00c";
font-family: "Font Awesome 6 Free";
font-weight: 900;
}
/* 网络测试部分 */
#network-test .input-group {
margin-bottom: 1.2rem;
max-width: 600px;
background-color: rgba(61, 124, 244, 0.03);
padding: 1.5rem;
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
}
#network-test label {
display: block;
margin-bottom: 0.5rem;
color: var(--text-secondary);
font-weight: 500;
}
#testResults {
margin-top: 1.5rem;
padding: 1rem;
background-color: #1e2532;
color: #e9ecef;
border-radius: var(--radius-md);
white-space: pre-wrap;
font-family: 'JetBrains Mono', monospace;
min-height: 300px;
max-height: 500px;
overflow-y: auto;
border: none;
box-shadow: var(--shadow-md);
position: relative;
}
/* 文档编辑器部分 */
#editor #editorContainer {
margin-top: 2rem;
border-radius: var(--radius-md);
background-color: var(--container-bg);
overflow: hidden;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-light);
}
#documentTitle {
width: 100%;
padding: 1rem;
margin-bottom: 0;
border: 1px solid var(--border-color);
border-radius: var(--radius-md) var(--radius-md) 0 0;
font-size: 1.1rem;
font-weight: 500;
box-shadow: none;
background-color: var(--container-bg);
color: var(--text-primary);
border-bottom: 1px solid var(--border-light);
}
#documentTitle:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(61, 124, 244, 0.2);
outline: none;
}
/* Editor.md 样式覆盖 */
#editor #editorContainer .editormd {
border: 1px solid var(--border-light) !important;
border-top: none !important;
border-radius: 0 0 var(--radius-md) var(--radius-md) !important;
box-shadow: none !important;
margin-top: 0 !important;
width: 100% !important;
height: auto !important;
}
#editor #editorContainer .editormd-toolbar {
background-color: var(--container-bg) !important;
border-bottom: 1px solid var(--border-light) !important;
padding: 0.5rem !important;
width: 100% !important;
box-sizing: border-box !important;
}
#editor #editorContainer .editormd-toolbar button {
background-color: transparent !important;
border: 1px solid var(--border-light) !important;
color: var(--text-primary) !important;
border-radius: var(--radius-sm) !important;
margin: 0 2px !important;
padding: 0.4rem 0.6rem !important;
transition: all 0.2s ease !important;
}
#editor #editorContainer .editormd-toolbar button:hover {
background-color: var(--primary-color) !important;
color: white !important;
border-color: var(--primary-color) !important;
}
#editor #editorContainer .editormd-toolbar button.active {
background-color: var(--primary-color) !important;
color: white !important;
border-color: var(--primary-color) !important;
}
/* 编辑区域和预览区域等宽等高 */
#editor #editorContainer .editormd > .editormd-editor {
width: 50% !important;
float: left !important;
box-sizing: border-box !important;
}
#editor #editorContainer .editormd > .editormd-preview {
width: 50% !important;
float: right !important;
box-sizing: border-box !important;
}
#editor #editorContainer .CodeMirror {
background-color: var(--container-bg) !important;
color: var(--text-primary) !important;
border: none !important;
font-family: 'Monaco', 'Consolas', 'Courier New', monospace !important;
font-size: 14px !important;
line-height: 1.6 !important;
height: 500px !important;
width: 100% !important;
box-sizing: border-box !important;
}
#editor #editorContainer .CodeMirror .CodeMirror-cursor {
border-left: 1px solid var(--text-primary) !important;
}
#editor #editorContainer .CodeMirror .CodeMirror-selected {
background-color: rgba(61, 124, 244, 0.2) !important;
}
#editor #editorContainer .editormd-preview {
background-color: var(--container-bg) !important;
color: var(--text-primary) !important;
border-left: 1px solid var(--border-light) !important;
padding: 1rem !important;
height: 500px !important;
overflow-y: auto !important;
box-sizing: border-box !important;
}
/* 确保容器清除浮动 */
#editor #editorContainer .editormd::after {
content: "" !important;
display: table !important;
clear: both !important;
}
#editor #editorContainer .editormd-preview h1,
#editor #editorContainer .editormd-preview h2,
#editor #editorContainer .editormd-preview h3,
#editor #editorContainer .editormd-preview h4,
#editor #editorContainer .editormd-preview h5,
#editor #editorContainer .editormd-preview h6 {
color: var(--text-primary) !important;
border-bottom: 1px solid var(--border-light) !important;
padding-bottom: 0.5rem !important;
margin-bottom: 1rem !important;
}
#editor #editorContainer .editormd-preview pre {
background-color: rgba(0, 0, 0, 0.05) !important;
border: 1px solid var(--border-light) !important;
border-radius: var(--radius-sm) !important;
padding: 1rem !important;
overflow-x: auto !important;
}
#editor #editorContainer .editormd-preview blockquote {
border-left: 4px solid var(--primary-color) !important;
background-color: rgba(61, 124, 244, 0.05) !important;
margin: 1rem 0 !important;
padding: 0.5rem 1rem !important;
}
#editor #editorContainer .editormd-preview table {
border-collapse: collapse !important;
width: 100% !important;
margin: 1rem 0 !important;
}
#editor #editorContainer .editormd-preview table th,
#editor #editorContainer .editormd-preview table td {
border: 1px solid var(--border-light) !important;
padding: 0.5rem 1rem !important;
text-align: left !important;
}
#editor #editorContainer .editormd-preview table th {
background-color: rgba(61, 124, 244, 0.1) !important;
font-weight: 600 !important;
}
/* 编辑器操作按钮样式 */
.editor-actions {
padding: 1rem !important;
background-color: var(--container-bg) !important;
border-top: 1px solid var(--border-light) !important;
display: flex !important;
gap: 0.75rem !important;
justify-content: flex-end !important;
}
.editor-actions .btn {
padding: 0.75rem 1.5rem !important;
border-radius: var(--radius-md) !important;
font-weight: 500 !important;
transition: all 0.2s ease !important;
}
.editor-actions .btn-primary {
background-color: var(--primary-color) !important;
color: white !important;
border: 1px solid var(--primary-color) !important;
}
.editor-actions .btn-primary:hover {
background-color: var(--primary-dark) !important;
border-color: var(--primary-dark) !important;
transform: translateY(-2px) !important;
box-shadow: var(--shadow-md) !important;
}
.editor-actions .btn-secondary {
background-color: transparent !important;
color: var(--text-secondary) !important;
border: 1px solid var(--border-color) !important;
}
.editor-actions .btn-secondary:hover {
background-color: var(--text-secondary) !important;
color: white !important;
transform: translateY(-2px) !important;
box-shadow: var(--shadow-sm) !important;
}
/* 响应式 Editor.md 样式 */
@media (max-width: 768px) {
#editor #editorContainer .editormd {
height: auto !important;
}
/* 移动端编辑区域和预览区域上下布局 */
#editor #editorContainer .editormd > .editormd-editor {
width: 100% !important;
float: none !important;
margin-bottom: 1px !important;
}
#editor #editorContainer .editormd > .editormd-preview {
width: 100% !important;
float: none !important;
border-left: none !important;
border-top: 1px solid var(--border-light) !important;
}
#editor #editorContainer .CodeMirror {
height: 300px !important;
}
#editor #editorContainer .editormd-preview {
height: 300px !important;
}
#editor #editorContainer .editormd-toolbar {
padding: 0.3rem !important;
flex-wrap: wrap !important;
}
#editor #editorContainer .editormd-toolbar button {
margin: 1px !important;
padding: 0.3rem 0.4rem !important;
font-size: 0.85rem !important;
}
.editor-actions {
flex-direction: column !important;
gap: 0.5rem !important;
}
.editor-actions .btn {
width: 100% !important;
text-align: center !important;
}
}
/* 中等屏幕适配 */
@media (max-width: 1024px) and (min-width: 769px) {
#editor #editorContainer .CodeMirror {
height: 450px !important;
}
#editor #editorContainer .editormd-preview {
height: 450px !important;
}
}
.password-hint {
color: var(--text-secondary);
font-size: 0.9rem;
margin: 0.5rem 0 1rem;
display: block;
background-color: rgba(61, 124, 244, 0.05);
padding: 0.75rem;
border-radius: var(--radius-md);
}
#passwordStrength {
font-size: 0.9rem;
margin: 0.5rem 0;
display: block;
}
/* 警告和确认对话框样式 */
.swal2-popup {
font-size: 1rem !important;
border-radius: var(--radius-lg) !important;
}
.swal2-title {
font-weight: 600 !important;
color: var(--text-primary) !important;
}
.swal2-styled.swal2-confirm {
background-color: var(--primary-color) !important;
border-radius: var(--radius-md) !important;
}
/* 添加条目按钮 */
.add-btn {
background-color: var(--success-color);
color: white;
padding: 0.8rem 1.2rem;
border-radius: var (--radius-md);
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
margin-top: 1rem;
box-shadow: var(--shadow-sm);
}
.add-btn:before {
content: "";
/* 移除 Font Awesome 图标,以避免和 HTML 中的图标重复 */
}
.add-btn:hover {
background-color: #29b873;
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
/* Docker容器操作按钮样式 */
.action-cell {
min-width: 200px;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.action-buttons button {
padding: 3px 8px;
font-size: 0.8rem;
margin-right: 3px;
white-space: nowrap;
}
.action-buttons button i {
margin-right: 3px;
font-size: 0.75rem;
}
/* 改善按钮在小屏幕上的显示 */
@media (max-width: 768px) {
.action-buttons {
flex-direction: column;
}
.action-buttons button {
margin-bottom: 3px;
width: 100%;
}
}
/* 响应式设计 */
@media (max-width: 992px) {
.admin-container {
flex-direction: column;
}
.sidebar {
width: 100%;
padding: 1rem 0;
}
.sidebar ul {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.sidebar li {
padding: 0.5rem 1rem;
border-left: none;
border-bottom: 3px solid transparent;
}
.sidebar li.active {
border-left: none;
border-bottom: 3px solid var(--primary-color);
}
.content-area {
padding: 1rem;
}
.content-section {
padding: 1.5rem;
}
}
@media (max-width: 768px) {
.content-section th,
.content-section td {
padding: 0.75rem 0.5rem;
font-size: 0.9rem;
}
.button-group {
flex-direction: column;
}
.btn, .action-btn {
width: 100%;
margin-bottom: 0.5rem;
}
.action-buttons {
flex-direction: column;
}
.action-buttons button {
margin-bottom: 3px;
width: 100%;
}
}
@media (max-width: 480px) {
.login-content {
width: 90%;
padding: 1.5rem;
}
}
/* Excel风格表格 */
.excel-table {
border-collapse: collapse;
width: 100%;
font-size: 14px;
border: 1px solid #ccc;
}
.excel-table th {
background-color: #f2f2f2;
border: 1px solid #ccc;
padding: 8px;
text-align: center;
position: sticky;
top: 0;
z-index: 10;
}
.excel-table td {
border: 1px solid #ccc;
padding: 6px 8px;
text-align: left;
}
.excel-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.excel-table tr:hover {
background-color: #f0f7ff;
}
/* 容器更新样式 */
.update-progress {
padding: 1rem;
text-align: center;
}
.update-progress p {
margin-bottom: 1rem;
font-size: 1rem;
}
.update-progress strong {
color: #007bff;
font-weight: 600;
}
.progress-status {
margin: 1rem 0;
font-size: 0.9rem;
color: #555;
font-weight: 500;
}
.progress-container {
width: 100%;
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
overflow: hidden;
margin: 1rem 0;
}
.progress-bar {
height: 100%;
background-color: #4CAF50;
width: 0%;
transition: width 0.3s ease-in-out;
border-radius: 5px;
position: relative;
}
/* 修改SweetAlert样式 */
.swal2-popup.update-popup {
border-radius: 10px;
padding: 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.swal2-title.update-title {
font-size: 1.5rem;
font-weight: 600;
color: #333;
}
.swal2-input.update-input {
box-shadow: none;
border: 1px solid #ddd;
border-radius: 5px;
padding: 0.75rem;
font-size: 0.95rem;
}
.swal2-input.update-input:focus {
border-color: #3085d6;
box-shadow: 0 0 0 3px rgba(48, 133, 214, 0.2);
}
/* 容器日志样式 */
.container-logs {
max-height: 70vh;
overflow-y: auto;
background: #1e1e1e;
color: #f0f0f0;
padding: 1rem;
border-radius: 5px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
white-space: pre-wrap;
word-break: break-all;
}
.swal2-logs-container {
max-width: 100%;
padding: 0 !important;
}
.swal2-logs-popup {
max-width: 80% !important;
}
/* 容器状态标签样式 */
.badge.status-running {
background-color: #28a745;
color: white;
padding: 5px 8px;
border-radius: 4px;
display: inline-block;
min-width: 70px;
text-align: center;
}
.badge.status-stopped, .badge.status-exited {
background-color: #dc3545;
color: white;
padding: 5px 8px;
border-radius: 4px;
display: inline-block;
min-width: 70px;
text-align: center;
}
.badge.status-created {
background-color: #17a2b8;
color: white;
padding: 5px 8px;
border-radius: 4px;
display: inline-block;
min-width: 70px;
text-align: center;
}
.badge.status-unknown, .badge.status-paused {
background-color: #6c757d;
color: white;
padding: 5px 8px;
border-radius: 4px;
display: inline-block;
min-width: 70px;
text-align: center;
}
/* 容器表格标题样式 */
.docker-table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.docker-table-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
/* 菜单管理 - 新增行样式 */
#new-menu-item-row input[type="text"],
#new-menu-item-row select {
/* 使用 Bootstrap 的 form-control-sm 已经减小了 padding 和 font-size */
margin-bottom: 0; /* 移除下方外边距,使其在表格行内更紧凑 */
}
#new-menu-item-row .form-control-sm {
padding: 0.3rem 0.6rem; /* 微调内边距 */
font-size: 0.875rem; /* 统一字体大小 */
}
#new-menu-item-row .form-select-sm {
padding: 0.3rem 1.5rem 0.3rem 0.6rem; /* 微调 select 内边距以适应箭头 */
font-size: 0.875rem;
}
.action-buttons-new-menu .btn {
margin-right: 5px; /* 按钮间距 */
min-width: 80px; /* 给按钮一个最小宽度 */
}
.action-buttons-new-menu .btn:last-child {
margin-right: 0;
}
.action-buttons-new-menu .btn i {
margin-right: 4px; /* 图标和文字间距 */
}
/* 使新行单元格垂直居中 */
#new-menu-item-row td {
vertical-align: middle;
}
/* 直接覆盖#testResults.loading的样式防止旋转 */
#network-test #testResults.loading {
animation: none !important;
border: none !important;
display: block !important;
position: relative !important;
text-align: center !important;
padding: 15px !important;
color: var(--text-secondary, #6c757d) !important;
font-size: 1rem !important;
width: auto !important;
height: auto !important;
}
/* 使用:before添加文本内容 */
#network-test #testResults.loading:before {
content: "测试进行中..." !important;
animation: none !important;
border: none !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
color: var(--text-light, #e9ecef) !important;
font-size: 1rem !important;
font-weight: 500 !important;
padding: 0 !important;
background-color: transparent !important;
z-index: 10 !important;
text-align: center !important;
}
#network-test .form-row {
display: flex;
flex-wrap: wrap; /* 允许换行到小屏幕 */
gap: 1.5rem; /* 各个元素之间的间距 */
margin-bottom: 1.5rem;
align-items: flex-end; /* 使得按钮和选择框底部对齐 */
}
#network-test .form-group {
flex: 1; /* 让表单组占据可用空间 */
min-width: 250px; /* 避免在小屏幕上过于挤压 */
background-color: transparent; /* 移除之前 #network-test .input-group 的背景色 */
padding: 0; /* 移除内边距 */
box-shadow: none; /* 移除阴影 */
border-radius: 0;
}
#network-test label {
display: block;
margin-bottom: 0.5rem;
color: var(--text-primary);
font-weight: 500;
font-size: 0.95rem;
}
#network-test .form-control,
#network-test .form-select {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background-color: var(--input-bg, var(--container-bg)); /* 允许自定义输入背景或使用容器背景 */
color: var(--text-primary);
font-size: 0.95rem;
transition: all var(--transition-fast);
box-shadow: var(--shadow-sm);
}
#network-test .form-control:focus,
#network-test .form-select:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(var(--primary-rgb, 61, 124, 244), 0.25); /* 使用RGB变量 */
outline: none;
}
#network-test .test-controls-container {
/* 这个容器包裹选择器和按钮 */
display: flex;
flex-wrap: wrap; /* 允许换行 */
gap: 1rem; /* 控件之间的间距 */
align-items: flex-end; /* 使得按钮和选择框底部对齐 */
margin-bottom: 1.5rem;
}
#network-test .test-controls-container .form-group {
flex-grow: 1;
flex-basis: 200px; /* 给每个控件一个基础宽度 */
}
#network-test .start-test-btn {
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 500;
white-space: nowrap; /* 防止按钮文字换行 */
height: fit-content; /* 与调整后的 select 高度匹配 */
flex-shrink: 0; /* 防止按钮在 flex 布局中被压缩 */
}
#network-test .start-test-btn i {
margin-right: 0.5rem;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border-light);
}
.results-header h3 {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-light, #e9ecef); /* Changed to light color for dark background */
margin: 0;
}
#clearTestResultsBtn {
font-size: 0.85rem;
padding: 0.3rem 0.8rem;
}
#testResultsContainer {
background-color: var(--container-bg-dark, #1e2532); /* 使用变量或默认暗色 */
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
margin-top: 1rem;
padding: 0; /* 移除外层padding让头部和内容区自己控制 */
position: relative; /* Add this to be a positioning context for absolute children if needed by headers etc. */
}
#testResults {
padding: 1rem;
color: var(--text-light, #e9ecef);
font-family: var(--font-mono, 'JetBrains Mono', monospace);
font-size: 0.9rem;
white-space: pre-wrap;
word-break: break-all;
min-height: 200px;
max-height: 400px;
overflow-y: auto;
background-color: transparent;
border-radius: 0 0 var(--radius-md) var(--radius-md);
box-shadow: none;
border: none;
margin-top: 0;
position: relative;
}
/* Use higher specificity and !important to force override */
#network-test #testResults.loading::before {
content: "测试进行中..."; /* Force text content */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Center the text block */
color: var(--text-secondary, #6c757d);
font-size: 1rem;
font-weight: 500;
padding: 0.75rem 1.25rem;
background-color: transparent !important;
border: none !important; /* 移除边框 */
border-radius: var(--radius-md, 0.375rem);
z-index: 10;
text-align: center;
/* Force removal of spinner styles */
animation: none !important;
border-top-color: transparent !important; /* Force override any spinner head */
width: auto !important; /* Force width */
height: auto !important; /* Force height */
box-sizing: border-box; /* Ensure padding is included correctly */
}
/* Ensure keyframes for the spinner are removed or commented out */
/* @keyframes testResultSpinner { to { transform: rotate(360deg); } } */
/* Style for the <pre> tag inside #testResults for consistent output formatting */
#testResults pre {
margin: 0; /* Remove default pre margin */
font-family: inherit; /* Inherit from #testResults (monospace) */
font-size: inherit; /* Inherit from #testResults */
color: inherit; /* Inherit from #testResults */
white-space: pre-wrap; /* Ensure wrapping */
word-break: break-all; /* Ensure long lines break */
background-color: transparent; /* Ensure no pre background interferes */
padding: 0; /* No extra padding for pre, parent div handles it */
}
#testResults pre.text-danger {
color: var(--danger-color, #dc3545); /* Ensure error text is colored */
}
/* Placeholder text styling (if using <p class="text-muted...">) */
#testResults .text-muted.text-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 2rem); /* Consider padding of #testResults */
}
/* 保持容器监控页面已停止容器列表的表头在鼠标悬停时背景色不变 */
#docker-monitoring .container-table th:hover {
background-color: var(--primary-color);
color: white;
}
/* SweetAlert2 弹窗内容文本居中 */
#swal2-html-container, /* 使用 ID 提高特异性 */
.swal2-html-container { /* 保留类选择器作为备用 */
text-align: center !important;
}
#swal2-html-container p,
.swal2-html-container p {
text-align: center !important;
}
#swal2-html-container div,
.swal2-html-container div {
text-align: center !important;
}
/* 下拉菜单样式修复 */
.btn-group {
position: relative;
display: inline-flex;
vertical-align: middle;
}
.dropdown-menu {
position: absolute;
z-index: 1050;
display: none;
min-width: 180px;
width: auto;
padding: 0;
margin: 0.325rem 0 0;
font-size: 0.9rem;
color: var(--text-primary);
text-align: left;
list-style: none;
background-color: #ffffff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
opacity: 0;
visibility: hidden;
transform: translateY(10px) scale(0.98);
transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease;
}
.dropdown-menu.show {
display: block;
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
/* 当下拉菜单在按钮右侧显示时 */
.dropdown-menu.dropdown-menu-right {
transform: translateX(10px) scale(0.98);
}
/* 当下拉菜单在按钮上方显示时 */
.dropdown-menu.dropdown-menu-top {
transform: translateY(-10px) scale(0.98);
}
/* 当下拉菜单在按钮右侧且显示时 */
.dropdown-menu.dropdown-menu-right.show {
transform: translateX(0) scale(1);
}
/* 当下拉菜单在按钮上方且显示时 */
.dropdown-menu.dropdown-menu-top.show {
transform: translateY(0) scale(1);
}
/* 右对齐的下拉菜单 */
.dropdown-menu-end {
right: 0;
left: auto;
}
.dropdown-header {
display: block;
padding: 0.5rem 1rem;
margin-bottom: 0;
font-size: 0.8rem;
color: #6c757d;
white-space: nowrap;
background-color: #f8f9fa;
font-weight: 500;
}
.dropdown-menu .dropdown-item {
display: flex;
align-items: center;
width: 100%;
padding: 0.7rem 1.25rem;
clear: both;
font-weight: 400;
color: #495057;
text-align: inherit;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border: 0;
transition: all 0.2s ease;
}
.dropdown-menu .dropdown-item i {
margin-right: 10px;
width: 18px;
text-align: center;
font-size: 0.95rem;
opacity: 0.8;
}
.dropdown-menu .dropdown-item:hover,
.dropdown-menu .dropdown-item:focus {
color: #1e70eb;
background-color: #f1f7ff;
text-decoration: none;
}
.dropdown-menu .dropdown-item.active,
.dropdown-menu .dropdown-item:active {
background-color: #e8f1ff;
color: #1e70eb;
}
.dropdown-menu .dropdown-item:hover i,
.dropdown-menu .dropdown-item:focus i {
opacity: 1;
}
.dropdown-divider {
height: 0;
margin: 0;
overflow: hidden;
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
/* 美化操作按钮 */
.action-cell .btn-group .btn-primary {
background: linear-gradient(to bottom, #4a7bff, #3d66e3);
border: none;
box-shadow: 0 2px 5px rgba(61, 124, 244, 0.2);
padding: 0.45rem 1rem;
font-weight: 500;
transition: all 0.2s ease;
border-radius: 6px;
}
.action-cell .btn-group .btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(61, 124, 244, 0.3);
background: linear-gradient(to bottom, #5a88ff, #4a7bff);
}
.action-cell .btn-group .btn-primary:active {
transform: translateY(0);
}
/* 右对齐的下拉菜单 */
.dropdown-menu-end {
right: 0;
left: auto;
}
/* 美化表格中的操作列,确保有足够空间显示弹出菜单 */
.action-cell {
min-width: 120px;
position: relative;
}
/* 确保菜单项样式美观 */
.dropdown-item {
display: flex;
align-items: center;
width: 100%;
padding: 0.7rem 1.25rem;
clear: both;
font-weight: 400;
color: #495057;
text-align: inherit;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border: 0;
transition: all 0.2s ease;
}
.dropdown-item i {
margin-right: 10px;
width: 18px;
text-align: center;
font-size: 0.95rem;
opacity: 0.8;
}
.dropdown-item:hover, .dropdown-item:focus {
color: #1e70eb;
background-color: rgba(30, 112, 235, 0.08);
text-decoration: none;
}
.dropdown-item:hover i, .dropdown-item:focus i {
opacity: 1;
}
.dropdown-divider {
height: 0;
margin: 0.5rem 0;
overflow: hidden;
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
/* 菜单项图标美化 */
.dropdown-item .fa-file-alt {
color: #17a2b8;
}
.dropdown-item .fa-info-circle {
color: #6c757d;
}
.dropdown-item .fa-stop {
color: #dc3545;
}
.dropdown-item .fa-play {
color: #28a745;
}
.dropdown-item .fa-sync-alt {
color: #ffc107;
}
.dropdown-item .fa-trash-alt {
color: #dc3545;
}
.dropdown-item .fa-cloud-download-alt {
color: #17a2b8;
}
/* 原生select下拉框样式美化 */
.simple-dropdown {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23495057' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 12px;
width: 100%;
padding: 0.45rem 2rem 0.45rem 0.75rem;
font-size: 0.875rem;
font-weight: 400;
color: #495057;
border: 1px solid #ced4da;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: all 0.2s ease-in-out;
cursor: pointer;
}
.simple-dropdown:focus {
border-color: #80bdff;
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,0.25);
}
.simple-dropdown:hover {
border-color: #adb5bd;
}
.simple-dropdown optgroup {
font-weight: 600;
color: #343a40;
background-color: #f8f9fa;
padding: 5px;
}
.simple-dropdown option {
font-weight: normal;
padding: 8px;
background-color: #fff;
color: #495057;
}
.simple-dropdown option:hover,
.simple-dropdown option:focus {
background-color: #f1f7ff;
color: #1e70eb;
}
/* Action按钮样式保留以便保持兼容 */
.action-cell .btn-group {
display: inline-block;
width: 100%;
}
.action-cell .btn-group .btn-primary {
display: none; /* 隐藏原始按钮由select替代 */
}
/* ==================== 页面美化样式 ==================== */
/* 统一页面头部样式 */
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 2px solid var(--border-light, #e5e7eb);
}
.page-header-content {
flex: 1;
text-align: left;
}
.page-title {
font-size: 1.75rem;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 0.5rem 0;
display: inline-flex;
align-items: center;
gap: 0.75rem;
}
.page-title i {
font-size: 1.5rem;
color: var(--primary-color);
opacity: 0.9;
}
.page-subtitle {
font-size: 0.95rem;
color: var(--text-secondary);
margin: 0;
text-align: left;
padding-left: 0;
}
/* 配置卡片网格 */
.config-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 1.5rem;
}
.monitoring-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 1.5rem;
}
.config-card {
background: var(--container-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
overflow: hidden;
transition: all var(--transition-normal);
border: 1px solid var(--border-light, #e5e7eb);
}
.config-card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
.config-card.full-width {
grid-column: 1 / -1;
}
.config-card-header {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem 1.5rem;
background: linear-gradient(135deg, rgba(61, 124, 244, 0.05) 0%, rgba(61, 124, 244, 0.02) 100%);
border-bottom: 1px solid var(--border-light, #e5e7eb);
}
.config-card-icon {
width: 50px;
height: 50px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: white;
flex-shrink: 0;
}
.config-card-icon.purple {
background: linear-gradient(135deg, #8b5cf6, #a78bfa);
}
.config-card-icon.blue {
background: linear-gradient(135deg, #3d7cf4, #6366f1);
}
.config-card-icon.green {
background: linear-gradient(135deg, #10b981, #34d399);
}
.config-card-icon.red {
background: linear-gradient(135deg, #ef4444, #f87171);
}
.config-card-icon.orange {
background: linear-gradient(135deg, #f59e0b, #fbbf24);
}
.config-card-title h3 {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.25rem 0;
}
.config-card-title p {
font-size: 0.85rem;
color: var(--text-secondary);
margin: 0;
}
.config-card-body {
padding: 1.5rem;
}
/* 现代表单样式 */
.modern-form-group {
margin-bottom: 1.25rem;
}
.modern-form-group label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.modern-form-group label i {
font-size: 0.85rem;
color: var(--primary-color);
opacity: 0.7;
}
.required-badge {
font-size: 0.7rem;
font-weight: 500;
padding: 0.15rem 0.5rem;
background: linear-gradient(135deg, #ef4444, #f87171);
color: white;
border-radius: 4px;
}
.modern-input {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background-color: var(--input-bg, #fff);
color: var(--text-primary);
font-size: 0.95rem;
transition: all var(--transition-fast);
box-shadow: var(--shadow-sm);
}
.modern-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(61, 124, 244, 0.15);
outline: none;
}
.modern-input::placeholder {
color: var(--text-secondary);
opacity: 0.7;
}
.input-hint {
display: block;
font-size: 0.8rem;
color: var(--text-secondary);
margin-top: 0.5rem;
}
/* 带图标的按钮 */
.btn-with-icon {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.65rem 1.25rem;
font-weight: 500;
border-radius: var(--radius-md);
transition: all var(--transition-fast);
}
.btn-with-icon i {
font-size: 0.9rem;
}
/* 按钮组现代样式 */
.button-group-modern {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.5rem;
}
/* 表格容器样式 */
.table-container {
background: white;
border-radius: 16px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.03), 0 2px 4px rgba(0, 0, 0, 0.05);
overflow: hidden;
margin-top: 1.5rem;
}
.table-container .table-responsive {
padding: 0;
}
/* 现代表格样式 - 2024主流设计 */
.modern-table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
.modern-table thead {
background: transparent;
}
.modern-table th {
padding: 14px 20px;
text-align: left;
font-weight: 500;
color: #6b7280;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid #f3f4f6;
white-space: nowrap;
}
.modern-table th:first-child {
padding-left: 24px;
}
.modern-table th:last-child {
padding-right: 24px;
}
.modern-table td {
padding: 16px 20px;
vertical-align: middle;
color: #111827;
font-size: 14px;
border-bottom: 1px solid #f9fafb;
}
.modern-table td:first-child {
padding-left: 24px;
}
.modern-table td:last-child {
padding-right: 24px;
}
.modern-table tbody tr {
transition: background-color 0.1s ease;
}
.modern-table tbody tr:hover {
background-color: #fafbfc;
}
.modern-table tbody tr:last-child td {
border-bottom: none;
}
/* 表格操作按钮样式 - 现代风格 */
.modern-table .action-buttons {
display: flex;
gap: 8px;
justify-content: flex-start;
}
.modern-table .action-btn {
width: 34px;
height: 34px;
border-radius: 8px;
border: none;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
font-size: 14px;
}
.modern-table .action-btn.edit-btn {
background: #f0f9ff;
color: #0ea5e9;
}
.modern-table .action-btn.edit-btn:hover {
background: #0ea5e9;
color: white;
}
.modern-table .action-btn.delete-btn {
background: #fef2f2;
color: #ef4444;
}
.modern-table .action-btn.delete-btn:hover {
background: #ef4444;
color: white;
}
.modern-table .action-btn.view-btn {
background: #f0fdf4;
color: #22c55e;
}
.modern-table .action-btn.view-btn:hover {
background: #22c55e;
color: white;
}
/* 表格序号和链接展示 */
.table-row-num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: #f3f4f6;
color: #6b7280;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.menu-link-display {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: #f8fafc;
border-radius: 6px;
font-size: 13px;
color: #475569;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.menu-link-display i {
color: #94a3b8;
font-size: 11px;
}
/* 表格空状态 */
.table-empty-state {
text-align: center;
padding: 48px 24px;
color: #9ca3af;
}
.table-empty-state i {
font-size: 40px;
margin-bottom: 16px;
opacity: 0.5;
}
.table-empty-state p {
font-size: 14px;
margin: 0;
}
/* 新增菜单行样式 */
#new-menu-item-row,
.new-item-row {
background: #f0fdf4 !important;
}
#new-menu-item-row td,
.new-item-row td {
padding: 14px 20px !important;
border-bottom: 1px dashed #86efac !important;
background: transparent !important;
}
#new-menu-item-row input,
#new-menu-item-row select,
.new-item-row input,
.new-item-row select {
padding: 10px 14px;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
transition: all 0.15s ease;
background: white;
}
#new-menu-item-row input:focus,
#new-menu-item-row select:focus,
.new-item-row input:focus,
.new-item-row select:focus {
border-color: #22c55e;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
outline: none;
}
.action-buttons-new-menu {
display: flex;
gap: 8px;
}
.action-buttons-new-menu .btn {
padding: 8px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.15s ease;
}
.save-new-menu-btn {
background: #22c55e !important;
border: none !important;
color: white !important;
}
.save-new-menu-btn:hover {
background: #16a34a !important;
}
.cancel-new-menu-btn {
background: #f3f4f6 !important;
border: 1px solid #e5e7eb !important;
color: #6b7280 !important;
}
.cancel-new-menu-btn:hover {
background: #ef4444 !important;
border-color: transparent !important;
color: white !important;
}
/* 编辑行样式 */
.editing-row {
background: #fffbeb !important;
}
.editing-row td {
border-bottom: 1px dashed #fbbf24 !important;
background: transparent !important;
}
.editing-row input,
.editing-row select {
padding: 10px 14px;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
transition: all 0.15s ease;
background: white;
}
.editing-row input:focus,
.editing-row select:focus {
border-color: #f59e0b;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1);
outline: none;
}
/* 表格加载状态 */
.table-loading {
text-align: center;
padding: 32px;
color: #9ca3af;
}
.table-loading i {
color: #3b82f6;
margin-right: 8px;
}
/* 文档管理特殊样式 */
#documentTable .doc-title {
font-weight: 500;
color: #111827;
}
#documentTable .doc-path {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
font-size: 13px;
color: #6b7280;
background: #f3f4f6;
padding: 5px 10px;
border-radius: 6px;
}
#documentTable .doc-date,
.doc-date {
font-size: 13px;
color: #6b7280;
}
.doc-title-display {
font-weight: 500;
color: #111827;
font-size: 14px;
}
.doc-status-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 5px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.doc-status-badge.published {
background: #dcfce7;
color: #16a34a;
}
.doc-status-badge.draft {
background: #fefce8;
color: #ca8a04;
}
/* 发布/取消发布按钮 */
.modern-table .action-btn.publish-btn {
background: #f0fdf4;
color: #22c55e;
}
.modern-table .action-btn.publish-btn:hover {
background: #22c55e;
color: white;
}
.modern-table .action-btn.unpublish-btn {
background: #fefce8;
color: #eab308;
}
.modern-table .action-btn.unpublish-btn:hover {
background: #eab308;
color: white;
}
/* 菜单管理表格特殊样式 */
.menu-text-display {
font-weight: 500;
color: #111827;
font-size: 14px;
}
.menu-newtab-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 5px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.menu-newtab-badge.yes {
background: #dcfce7;
color: #16a34a;
}
.menu-newtab-badge.no {
background: #f3f4f6;
color: #6b7280;
}
/* 容器表格专用样式 */
.container-id-cell {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
font-size: 13px;
color: #64748b;
background: #f3f4f6;
padding: 6px 10px;
border-radius: 6px;
display: inline-block;
}
.container-name-cell {
font-weight: 500;
color: #111827;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.container-name-cell::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: #ef4444;
display: inline-block;
}
.container-image-cell {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
font-size: 13px;
color: #0284c7;
background: #f0f9ff;
padding: 6px 10px;
border-radius: 6px;
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.container-status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
}
.container-status-badge.stopped {
background: #fef2f2;
color: #dc2626;
}
.container-status-badge.running {
background: #dcfce7;
color: #16a34a;
}
.container-status-badge.paused {
background: #fefce8;
color: #ca8a04;
}
.container-status-badge i {
font-size: 8px;
}
/* 空容器状态 */
.stopped-containers-empty {
text-align: center;
padding: 48px 24px;
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
border-radius: 0 0 16px 16px;
}
.stopped-containers-empty i {
font-size: 48px;
color: #22c55e;
margin-bottom: 12px;
display: block;
}
.stopped-containers-empty p {
color: #16a34a;
font-weight: 600;
font-size: 15px;
margin: 0;
}
/* 测试控制卡片 */
.test-controls-card {
background: var(--container-bg);
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-md);
border: 1px solid var(--border-light, #e5e7eb);
margin-bottom: 1.5rem;
}
.test-controls-card .test-controls-container {
display: flex;
flex-wrap: wrap;
gap: 1.25rem;
align-items: flex-end;
}
.test-controls-card .form-group {
flex: 1;
min-width: 200px;
}
.test-controls-card .form-group label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.test-controls-card .form-group label i {
font-size: 0.85rem;
color: var(--primary-color);
opacity: 0.7;
}
.test-controls-card .form-select,
.test-controls-card .form-control {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 0.95rem;
transition: all var(--transition-fast);
background-color: var(--input-bg, #fff);
}
.test-controls-card .form-select:focus,
.test-controls-card .form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(61, 124, 244, 0.15);
outline: none;
}
.test-controls-card .start-test-btn {
flex-shrink: 0;
padding: 0.75rem 1.5rem;
font-size: 1rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
/* 测试结果容器美化 */
#testResultsContainer {
background: var(--container-bg-dark, #1e2532);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
overflow: hidden;
}
#testResultsContainer .results-header {
background: linear-gradient(135deg, rgba(61, 124, 244, 0.1) 0%, rgba(99, 102, 241, 0.1) 100%);
padding: 1rem 1.25rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 0;
}
#testResultsContainer .results-header h3 {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-light, #e9ecef);
}
#testResultsContainer .results-header h3 i {
color: var(--primary-color);
}
/* 监控状态徽章 */
.monitoring-status-badge {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--container-bg);
padding: 0.5rem 1rem;
border-radius: var(--radius-md);
font-size: 0.9rem;
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-light, #e5e7eb);
}
.monitoring-status-badge .status-indicator {
font-weight: 600;
}
.monitoring-status-badge .status-indicator.running {
color: var(--success-color);
}
.monitoring-status-badge .status-indicator.stopped {
color: var(--danger-color);
}
/* Docker 监控配置页面样式 */
.monitoring-config-card {
margin-bottom: 1.5rem;
}
.monitoring-form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.monitoring-form-section {
padding: 0;
}
.form-section-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 1.25rem 0;
padding-bottom: 0.75rem;
border-bottom: 2px solid var(--border-light, #e5e7eb);
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-section-title i {
color: var(--primary-color);
font-size: 0.9rem;
}
.monitoring-info-box {
display: flex;
gap: 1rem;
background: linear-gradient(135deg, rgba(61, 124, 244, 0.08), rgba(99, 102, 241, 0.05));
border: 1px solid rgba(61, 124, 244, 0.15);
border-radius: var(--radius-md);
padding: 1rem;
margin-top: 1.25rem;
}
.info-box-icon {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 10px;
background: linear-gradient(135deg, var(--primary-color), #6366f1);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1rem;
}
.info-box-content h5 {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 0.35rem 0;
}
.info-box-content p {
font-size: 0.85rem;
color: var(--text-secondary);
margin: 0;
line-height: 1.5;
}
.monitoring-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-light, #e5e7eb);
}
@media (max-width: 768px) {
.monitoring-form-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.monitoring-actions {
flex-direction: column;
}
.monitoring-actions .btn {
width: 100%;
justify-content: center;
}
}
/* 登录页面表单美化 */
.forgot-password-link {
text-align: center;
margin-top: 1.25rem;
}
.forgot-password-link a {
color: var(--primary-color);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: all var(--transition-fast);
}
.forgot-password-link a:hover {
color: var(--primary-dark);
text-decoration: underline;
}
.form-description {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
text-align: center;
margin-bottom: 1.25rem;
padding: 1rem;
background: linear-gradient(135deg, rgba(61, 124, 244, 0.08), rgba(99, 102, 241, 0.05));
border-radius: var(--radius-md);
color: var(--text-secondary);
font-size: 0.9rem;
border: 1px solid rgba(61, 124, 244, 0.1);
}
.form-description i {
color: var(--primary-color);
font-size: 1rem;
}
.token-display {
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
padding: 1rem;
border-radius: var(--radius-md);
margin-bottom: 1rem;
border: 1px solid rgba(61, 124, 244, 0.2);
display: none;
}
.token-label {
font-size: 0.8rem;
font-weight: 600;
color: var(--primary-color);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.4rem;
}
.token-value {
font-family: 'Courier New', monospace;
font-size: 0.85rem;
word-break: break-all;
background: white;
padding: 0.5rem 0.75rem;
border-radius: 4px;
border: 1px solid rgba(61, 124, 244, 0.1);
}
.password-requirements {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8rem;
color: var(--text-secondary);
margin-bottom: 1rem;
padding: 0.75rem;
background: rgba(245, 158, 11, 0.08);
border-radius: var(--radius-md);
border: 1px solid rgba(245, 158, 11, 0.15);
}
.password-requirements i {
color: #f59e0b;
}
.back-to-login {
text-align: center;
margin-top: 1.25rem;
}
.back-to-login a {
color: var(--primary-color);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.4rem;
transition: all var(--transition-fast);
}
.back-to-login a:hover {
color: var(--primary-dark);
}
.back-to-login a i {
transition: transform var(--transition-fast);
}
.back-to-login a:hover i {
transform: translateX(-3px);
}
/* 响应式调整 */
@media (max-width: 768px) {
.page-header {
flex-direction: column;
gap: 1rem;
}
.page-header .btn-with-icon {
width: 100%;
justify-content: center;
}
.config-cards-grid,
.monitoring-grid {
grid-template-columns: 1fr;
}
.test-controls-card .test-controls-container {
flex-direction: column;
}
.test-controls-card .form-group {
width: 100%;
}
.test-controls-card .start-test-btn {
width: 100%;
justify-content: center;
}
.button-group-modern {
flex-direction: column;
}
.button-group-modern .btn-with-icon {
width: 100%;
justify-content: center;
}
}
</style>
</head>
<body>
<div id="loadingSpinner" class="loading-spinner" style="display: none;"></div>
<div id="loadingIndicator" style="display: flex; justify-content: center; align-items: center; height: 100vh;">
<p>加载中...</p>
</div>
<div class="admin-container" id="adminContainer" style="display: none;">
<div class="sidebar">
<div class="sidebar-header">
<h2><i class="fas fa-cogs"></i>管理面板</h2>
</div>
<div class="user-profile">
<div class="user-avatar">
<i class="fas fa-user"></i>
</div>
<div class="user-info">
<div class="user-name" id="currentUsername">管理员</div>
<div class="user-role">系统管理员</div>
</div>
<div class="user-actions">
<div class="user-action-btn" id="userCenterBtn">个人中心</div>
<div class="user-action-btn logout" id="logoutBtn">退出登录</div>
</div>
</div>
<ul class="sidebar-nav">
<li data-section="dashboard" class="active">
<i class="fas fa-tachometer-alt"></i>控制面板
</li>
<li data-section="basic-config">
<i class="fas fa-wrench"></i>基本配置
</li>
<li data-section="menu-management">
<i class="fas fa-bars"></i>菜单管理
</li>
<li data-section="documentation-management">
<i class="fas fa-file-alt"></i>文档管理
</li>
<li data-section="network-test">
<i class="fas fa-network-wired"></i>网络测试
</li>
<li data-section="docker-status">
<i class="fab fa-docker"></i>容器管理
</li>
<li data-section="docker-monitoring">
<i class="fas fa-chart-line"></i>容器监控
</li>
<li data-section="user-center">
<i class="fas fa-user-cog"></i>用户中心
</li>
</ul>
</div>
<div class="content-area">
<div class="container">
<!-- 控制面板 -->
<div id="dashboard" class="content-section active">
<div class="welcome-banner">
<div class="welcome-content">
<h1 class="welcome-title">欢迎回来,<span id="welcomeUsername">管理员</span></h1>
<p class="welcome-subtitle">这里是 Docker 镜像代理加速系统控制面板</p>
<button class="welcome-action" id="refreshSystemBtn">
<i class="fas fa-sync-alt"></i> 刷新系统状态
</button>
</div>
<div class="docker-status" id="dockerStatusIndicator" style="position: absolute; top: 10px; right: 10px; padding: 5px 10px; border-radius: 4px; font-size: 0.9rem; color: white;">
<i class="fab fa-docker"></i>
<span id="dockerStatusText">正在检查...</span>
</div>
</div>
<div class="dashboard-grid">
<!-- 仪表板卡片将由 systemStatus.initDashboard() 动态生成 -->
</div>
</div>
<!-- 基本配置 -->
<div id="basic-config" class="content-section">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title"><i class="fas fa-cog"></i> 基本配置</h1>
<p class="page-subtitle">配置系统的基本参数和代理地址</p>
</div>
</div>
<div class="config-cards-grid">
<!-- Logo配置卡片 -->
<div class="config-card">
<div class="config-card-header">
<div class="config-card-icon purple">
<i class="fas fa-image"></i>
</div>
<div class="config-card-title">
<h3>Logo 设置</h3>
<p>自定义系统Logo图片</p>
</div>
</div>
<div class="config-card-body">
<div class="modern-form-group">
<label for="logoUrl"><i class="fas fa-link"></i> Logo URL</label>
<input type="url" id="logoUrl" name="logoUrl" class="modern-input" placeholder="请输入Logo图片URL可选">
<span class="input-hint">支持 PNG、JPG、SVG 格式的图片链接</span>
</div>
<button type="button" class="btn btn-primary btn-with-icon" onclick="validateAndSaveConfig('logo')">
<i class="fas fa-save"></i> 保存 Logo
</button>
</div>
</div>
<!-- 代理地址配置卡片 -->
<div class="config-card">
<div class="config-card-header">
<div class="config-card-icon blue">
<i class="fas fa-server"></i>
</div>
<div class="config-card-title">
<h3>代理地址</h3>
<p>配置Docker镜像代理服务器</p>
</div>
</div>
<div class="config-card-body">
<div class="modern-form-group">
<label for="proxyDomain"><i class="fas fa-globe"></i> 代理地址 <span class="required-badge">必填</span></label>
<input type="text" id="proxyDomain" name="proxyDomain" class="modern-input" placeholder="例如: proxy.example.com" required>
<span class="input-hint">填写您的Docker镜像代理服务器地址</span>
</div>
<button type="button" class="btn btn-primary btn-with-icon" onclick="validateAndSaveConfig('proxy')">
<i class="fas fa-save"></i> 保存代理地址
</button>
</div>
</div>
</div>
</div>
<!-- 菜单管理 -->
<div id="menu-management" class="content-section">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title"><i class="fas fa-bars"></i> 菜单管理</h1>
<p class="page-subtitle">管理前台页面的导航菜单项</p>
</div>
<button type="button" class="btn btn-primary btn-with-icon" onclick="menuManager.showNewMenuItemRow()">
<i class="fas fa-plus"></i> 添加菜单项
</button>
</div>
<div class="table-container">
<table id="menuTable" class="modern-table">
<thead>
<!-- 表头将由 menuManager.renderMenuItems() 动态生成 -->
</thead>
<tbody id="menuTableBody">
<!-- 菜单项将在这里动态添加 -->
</tbody>
</table>
</div>
</div>
<!-- 文档管理部分 -->
<div id="documentation-management" class="content-section">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title"><i class="fas fa-file-alt"></i> 文档管理</h1>
<p class="page-subtitle">管理系统帮助文档和使用指南</p>
</div>
<button type="button" class="btn btn-primary btn-with-icon" onclick="documentManager.newDocument()">
<i class="fas fa-plus"></i> 新建文档
</button>
</div>
<div class="table-container">
<table id="documentTable" class="modern-table">
<thead>
<!-- 表头将由 documentManager.renderDocumentList() 动态生成 -->
</thead>
<tbody id="documentTableBody">
<!-- 文档列表将在这里动态添加 -->
</tbody>
</table>
</div>
</div>
<!-- 修改密码部分 - 保留但隐藏,因为用户中心已有此功能 -->
<div id="password-change" class="content-section" style="display: none;">
<h2 class="menu-label">修改密码</h2>
<label for="currentPassword">当前密码</label>
<input type="password" id="currentPassword" name="currentPassword">
<label for="newPassword">新密码</label>
<span class="password-hint" id="passwordHint">密码必须包含至少一个字母、一个数字和一个特殊字符长度在8到16个字符之间</span>
<input type="password" id="newPassword" name="newPassword" oninput="userCenter.checkPasswordStrength()">
<span id="passwordStrength" style="color: red;"></span>
<button type="button" onclick="userCenter.changePassword()">修改密码</button>
</div>
<!-- 网络测试 -->
<div id="network-test" class="content-section">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title"><i class="fas fa-network-wired"></i> 网络测试</h1>
<p class="page-subtitle">测试与Docker镜像仓库的网络连通性</p>
</div>
</div>
<div class="test-controls-card">
<div class="test-controls-container">
<div class="form-group">
<label for="domainSelect"><i class="fas fa-globe"></i> 目标域名</label>
<select id="domainSelect" class="form-select">
<!-- 选项将由 networkTest.initNetworkTest() 动态生成 -->
</select>
</div>
<div class="form-group" id="customDomainContainer" style="display: none;">
<label for="customDomain"><i class="fas fa-edit"></i> 自定义域名</label>
<input type="text" id="customDomain" class="form-control" placeholder="请输入域名,如 example.com">
</div>
<div class="form-group">
<label for="testType"><i class="fas fa-tasks"></i> 测试类型</label>
<select id="testType" class="form-select">
<!-- 选项将由 networkTest.initNetworkTest() 动态生成 -->
</select>
</div>
<button id="startTestBtn" class="btn btn-primary start-test-btn">
<i class="fas fa-play"></i> 开始测试
</button>
</div>
</div>
<div id="testResultsContainer">
<div class="results-header">
<h3><i class="fas fa-terminal"></i> 测试结果</h3>
<button id="clearTestResultsBtn" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-times-circle"></i> 清空结果
</button>
</div>
<div id="testResults">
<!-- 测试结果将显示在这里 -->
</div>
</div>
</div>
<!-- Docker服务状态 -->
<div id="docker-status" class="content-section">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title"><i class="fab fa-docker"></i> Docker 服务状态</h1>
<p class="page-subtitle">查看和管理Docker容器状态和操作</p>
</div>
</div>
<!-- 新增表格容器以支持标题和操作区域 -->
<div class="table-container">
<div id="dockerTableContainer" class="table-responsive">
<table id="dockerStatusTable" class="modern-table excel-table">
<thead></thead>
<tbody id="dockerStatusTableBody"></tbody>
</table>
</div>
</div>
</div>
<!-- Docker监控配置 -->
<div id="docker-monitoring" class="content-section">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title"><i class="fas fa-chart-line"></i> Docker 容器监控</h1>
<p class="page-subtitle">配置容器监控和告警通知</p>
</div>
<div class="monitoring-status-badge">
<span>监控状态:</span>
<span id="monitoringStatus" class="status-indicator">加载中...</span>
</div>
</div>
<!-- 通知配置卡片 - 全宽 -->
<div class="config-card monitoring-config-card">
<div class="config-card-header">
<div class="config-card-icon green">
<i class="fas fa-bell"></i>
</div>
<div class="config-card-title">
<h3>通知配置</h3>
<p>设置告警通知方式和监控参数</p>
</div>
</div>
<div class="config-card-body">
<div class="monitoring-form-grid">
<!-- 左侧:通知方式选择 -->
<div class="monitoring-form-section">
<h4 class="form-section-title"><i class="fas fa-paper-plane"></i> 通知渠道</h4>
<div class="modern-form-group">
<label for="notificationType">通知方式</label>
<select id="notificationType" class="modern-input">
<option value="wechat">企业微信群机器人</option>
<option value="telegram">Telegram Bot</option>
</select>
</div>
<div id="wechatFields">
<div class="modern-form-group">
<label for="webhookUrl"><i class="fas fa-link"></i> Webhook URL</label>
<input type="text" id="webhookUrl" name="webhookUrl" class="modern-input" placeholder="企业微信机器人 Webhook URL">
<span class="input-hint">从企业微信群机器人设置中获取</span>
</div>
</div>
<div id="telegramFields" style="display: none;">
<div class="modern-form-group">
<label for="telegramToken"><i class="fas fa-key"></i> Bot Token</label>
<input type="text" id="telegramToken" name="telegramToken" class="modern-input" placeholder="Telegram Bot Token">
<span class="input-hint">从 @BotFather 获取</span>
</div>
<div class="modern-form-group">
<label for="telegramChatId"><i class="fas fa-comments"></i> Chat ID</label>
<input type="text" id="telegramChatId" name="telegramChatId" class="modern-input" placeholder="Telegram Chat ID">
<span class="input-hint">个人/群组/频道的 Chat ID</span>
</div>
</div>
</div>
<!-- 右侧:监控参数 -->
<div class="monitoring-form-section">
<h4 class="form-section-title"><i class="fas fa-cog"></i> 监控参数</h4>
<div class="modern-form-group">
<label for="monitorInterval"><i class="fas fa-clock"></i> 监控间隔 (秒)</label>
<input type="number" id="monitorInterval" name="monitorInterval" min="1" value="60" class="modern-input">
<span class="input-hint">建议设置 60 秒以上,避免频繁检测</span>
</div>
<div class="monitoring-info-box">
<div class="info-box-icon">
<i class="fas fa-info-circle"></i>
</div>
<div class="info-box-content">
<h5>监控说明</h5>
<p>系统会定期检查容器状态,当发现容器停止运行时,会通过配置的渠道发送告警通知。</p>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="monitoring-actions">
<button class="btn btn-outline-secondary btn-with-icon" id="testNotifyBtn" onclick="app.testNotification()">
<i class="fas fa-vial"></i> 测试通知
</button>
<button class="btn btn-primary btn-with-icon" id="saveConfigBtn" onclick="app.saveMonitoringConfig()">
<i class="fas fa-save"></i> 保存配置
</button>
<button class="btn btn-secondary btn-with-icon" id="toggleMonitoringBtn" onclick="app.toggleMonitoring()">
<i class="fas fa-power-off"></i> 开启/关闭监控
</button>
</div>
</div>
</div>
<!-- 已停止容器卡片 -->
<div class="config-card">
<div class="config-card-header">
<div class="config-card-icon red">
<i class="fas fa-stop-circle"></i>
</div>
<div class="config-card-title">
<h3>已停止的容器</h3>
<p>显示当前处于停止状态的容器列表</p>
</div>
</div>
<div class="config-card-body">
<div class="table-responsive">
<table id="stoppedContainersTable" class="modern-table">
<thead>
<tr>
<th>容器ID</th>
<th>容器名称</th>
<th>镜像名称</th>
<th>运行状态</th>
</tr>
</thead>
<tbody id="stoppedContainersBody"></tbody>
</table>
</div>
</div>
</div>
<div id="messageContainer"></div>
</div>
<!-- 用户中心部分 -->
<div id="user-center" class="content-section">
<div class="user-center-header">
<div>
<h1 class="user-center-title"><i class="fas fa-user-cog"></i> 用户中心</h1>
<p class="user-center-subtitle">管理您的个人信息和账户安全</p>
</div>
<button class="btn btn-danger-outline" id="ucLogoutBtn">
<i class="fas fa-sign-out-alt"></i> 退出登录
</button>
</div>
<!-- 个人资料卡片 -->
<div class="user-profile-card">
<div class="user-profile-avatar">
<i class="fas fa-user-circle"></i>
</div>
<div class="user-profile-info">
<h2 class="user-profile-name" id="profileUsername">管理员</h2>
<p class="user-profile-role">系统管理员</p>
<div class="user-profile-badges">
<span class="user-badge admin"><i class="fas fa-shield-alt"></i> 管理员</span>
<span class="user-badge active"><i class="fas fa-check-circle"></i> 活跃</span>
</div>
</div>
<div class="user-profile-actions">
<button class="btn btn-outline" onclick="userCenter.refreshUserInfo()">
<i class="fas fa-sync-alt"></i> 刷新信息
</button>
</div>
</div>
<!-- 账户统计卡片 - 独立行 -->
<div class="user-stats-row">
<div class="stat-card-enhanced">
<div class="stat-icon-wrapper blue">
<i class="fas fa-sign-in-alt"></i>
</div>
<div class="stat-content">
<div class="stat-value" id="loginCount">--</div>
<div class="stat-label">登录次数</div>
</div>
</div>
<div class="stat-card-enhanced">
<div class="stat-icon-wrapper green">
<i class="fas fa-clock"></i>
</div>
<div class="stat-content">
<div class="stat-value" id="lastLogin">--</div>
<div class="stat-label">上次登录</div>
</div>
</div>
<div class="stat-card-enhanced">
<div class="stat-icon-wrapper purple">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="stat-content">
<div class="stat-value" id="accountAge">--</div>
<div class="stat-label">账户天数</div>
</div>
</div>
<div class="stat-card-enhanced">
<div class="stat-icon-wrapper orange">
<i class="fas fa-shield-alt"></i>
</div>
<div class="stat-content">
<div class="stat-value">安全</div>
<div class="stat-label">账户状态</div>
</div>
</div>
</div>
<!-- 安全设置区域 -->
<div class="security-section-header">
<h2><i class="fas fa-lock"></i> 安全设置</h2>
<p>管理您的账户安全信息,定期修改密码可以提高账户安全性</p>
</div>
<div class="user-dashboard-grid">
<!-- 修改用户名卡片 -->
<div class="user-center-card security-card">
<div class="card-icon-header">
<div class="card-icon blue">
<i class="fas fa-user-edit"></i>
</div>
<h2 class="user-center-section-title">修改用户名</h2>
</div>
<p class="card-description">修改登录用户名,建议使用不易猜测的用户名以提高安全性</p>
<form id="changeUsernameForm" class="modern-form">
<div class="form-group">
<label for="ucNewUsername">
<i class="fas fa-user"></i> 新用户名
</label>
<input type="text" id="ucNewUsername" name="newUsername" placeholder="请输入新用户名" class="form-input">
<span class="input-hint">3-20位只能包含字母、数字和下划线</span>
</div>
<div class="form-group">
<label for="ucUsernamePassword">
<i class="fas fa-key"></i> 当前密码
</label>
<div class="password-input-wrapper">
<input type="password" id="ucUsernamePassword" name="password" placeholder="请输入当前密码验证身份" class="form-input">
<button type="button" class="toggle-password" onclick="userCenter.togglePasswordVisibility('ucUsernamePassword', this)">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<button type="submit" class="btn btn-primary btn-block">
<i class="fas fa-save"></i> 保存新用户名
</button>
</form>
</div>
<!-- 密码修改卡片 -->
<div class="user-center-card security-card">
<div class="card-icon-header">
<div class="card-icon orange">
<i class="fas fa-lock"></i>
</div>
<h2 class="user-center-section-title">修改密码</h2>
</div>
<p class="card-description">定期修改密码可以有效提高账户安全性建议每3个月更换一次</p>
<form id="changePasswordForm" class="modern-form">
<div class="form-group">
<label for="ucCurrentPassword">
<i class="fas fa-key"></i> 当前密码
</label>
<div class="password-input-wrapper">
<input type="password" id="ucCurrentPassword" name="currentPassword" placeholder="请输入当前密码" class="form-input">
<button type="button" class="toggle-password" onclick="userCenter.togglePasswordVisibility('ucCurrentPassword', this)">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<div class="form-group">
<label for="ucNewPassword">
<i class="fas fa-lock"></i> 新密码
</label>
<div class="password-input-wrapper">
<input type="password" id="ucNewPassword" name="newPassword" placeholder="请输入新密码" class="form-input" oninput="userCenter.checkUcPasswordStrength()">
<button type="button" class="toggle-password" onclick="userCenter.togglePasswordVisibility('ucNewPassword', this)">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="password-strength-bar">
<div class="strength-indicator" id="strengthIndicator"></div>
</div>
<span class="input-hint" id="ucPasswordHint">8-16位需包含字母、数字和特殊字符</span>
</div>
<div class="form-group">
<label for="ucConfirmPassword">
<i class="fas fa-check-circle"></i> 确认新密码
</label>
<div class="password-input-wrapper">
<input type="password" id="ucConfirmPassword" name="confirmPassword" placeholder="请再次输入新密码" class="form-input">
<button type="button" class="toggle-password" onclick="userCenter.togglePasswordVisibility('ucConfirmPassword', this)">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary btn-block">
<i class="fas fa-save"></i> 更新密码
</button>
<span id="ucPasswordStrength" class="password-strength-text"></span>
</div>
</form>
</div>
</div>
<!-- 安全提示 -->
<div class="security-tips">
<div class="tip-header">
<i class="fas fa-lightbulb"></i>
<span>安全提示</span>
</div>
<ul class="tip-list">
<li><i class="fas fa-check"></i> 使用强密码,包含大小写字母、数字和特殊字符</li>
<li><i class="fas fa-check"></i> 不要与他人共享您的登录凭据</li>
<li><i class="fas fa-check"></i> 定期更换密码建议每3个月更换一次</li>
<li><i class="fas fa-check"></i> 使用独特的用户名避免使用常见名称如admin、root等</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="login-modal" id="loginModal" style="display: none;">
<div class="login-content">
<div class="login-header">
<h2 id="loginTitle">管理员登录</h2>
</div>
<!-- 登录表单 -->
<form id="loginForm" class="login-form">
<input type="text" id="username" name="username" placeholder="用户名" required>
<input type="password" id="password" name="password" placeholder="密码" required>
<!-- 修复这里的结构问题 - 添加了缺失的开始标签 -->
<div class="captcha-container">
<input type="text" id="captcha" name="captcha" placeholder="验证码" required>
<span id="captchaText" onclick="auth.refreshCaptcha()">点击刷新验证码</span>
</div>
<button type="submit" id="loginButton">登录</button>
<div class="forgot-password-link">
<a href="javascript:void(0)" onclick="auth.showForgotPassword()">忘记密码?</a>
</div>
</form>
<!-- 忘记密码表单 - 步骤1: 验证用户名 -->
<form id="forgotPasswordForm" class="login-form" style="display: none;">
<div class="form-description">
<i class="fas fa-key"></i>
<span>请输入您的用户名来获取密码重置令牌</span>
</div>
<input type="text" id="resetUsername" name="resetUsername" placeholder="用户名" required>
<div class="captcha-container">
<input type="text" id="resetCaptcha" name="resetCaptcha" placeholder="验证码" required>
<span id="resetCaptchaText" onclick="auth.refreshCaptcha()">点击刷新验证码</span>
</div>
<button type="submit" id="getTokenButton">获取重置令牌</button>
<div class="back-to-login">
<a href="javascript:void(0)" onclick="auth.showLoginForm()"><i class="fas fa-arrow-left"></i> 返回登录</a>
</div>
</form>
<!-- 重置密码表单 - 步骤2: 设置新密码 -->
<form id="resetPasswordForm" class="login-form" style="display: none;">
<div class="form-description">
<i class="fas fa-lock"></i>
<span>请设置您的新密码</span>
</div>
<div id="resetTokenDisplay" class="token-display">
<div class="token-label"><i class="fas fa-ticket-alt"></i> 重置令牌</div>
<div class="token-value" id="tokenValue"></div>
</div>
<input type="password" id="resetNewPassword" name="resetNewPassword" placeholder="新密码8-16位含字母、数字和特殊字符" required>
<input type="password" id="resetConfirmPassword" name="resetConfirmPassword" placeholder="确认新密码" required>
<div class="password-requirements">
<i class="fas fa-info-circle"></i>
密码要求8-16位包含字母、数字和特殊字符
</div>
<button type="submit" id="resetPasswordButton">重置密码</button>
<div class="back-to-login">
<a href="javascript:void(0)" onclick="auth.showLoginForm()"><i class="fas fa-arrow-left"></i> 返回登录</a>
</div>
</form>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dragula/3.7.2/dragula.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- 添加 Bootstrap 5 JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<!-- Toast UI Editor - 添加编辑器需要的依赖 -->
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
<!-- 模块化JS文件 - 按依赖顺序加载 -->
<script src="js/core.js"></script>
<script src="js/auth.js"></script>
<script src="js/userCenter.js"></script>
<script src="js/menuManager.js"></script>
<script src="js/documentManager.js"></script>
<script src="js/systemStatus.js"></script>
<script src="js/dockerManager.js"></script>
<script src="js/networkTest.js"></script>
<script src="js/app.js"></script>
<script>
// 在DOM加载完成后初始化登录表单
document.addEventListener('DOMContentLoaded', function() {
const loginForm = document.getElementById('loginForm');
if (loginForm) {
loginForm.addEventListener('submit', function(e) {
e.preventDefault();
auth.login();
});
}
const changePasswordForm = document.getElementById('changePasswordForm');
if (changePasswordForm) {
changePasswordForm.addEventListener('submit', function(e) {
e.preventDefault();
userCenter.changePassword();
});
}
// 设置超时自动显示登录框,避免一直显示"加载中..."
setTimeout(function() {
const loadingIndicator = document.getElementById('loadingIndicator');
if (loadingIndicator && loadingIndicator.style.display !== 'none') {
console.log('超时自动显示登录框');
if (core && typeof core.hideLoadingIndicator === 'function') {
core.hideLoadingIndicator();
}
const loginModal = document.getElementById('loginModal');
if (loginModal) {
loginModal.style.display = 'flex';
if (window.auth && typeof window.auth.refreshCaptcha === 'function') {
window.auth.refreshCaptcha();
}
}
}
}, 3000); // 3秒后如果仍在加载则显示登录框
// 确保saveConfig可用
window.saveConfig = window.app ? window.app.saveConfig : function(data) {
console.error('saveConfig未定义');
};
// 确保validateAndSaveConfig可用
window.validateAndSaveConfig = window.app ? window.app.validateAndSaveConfig : function(type) {
console.error('validateAndSaveConfig未定义');
};
});
</script>
</body>
</html>