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

1909 lines
62 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>
<!-- 引入 Markdown 编辑器 -->
<link rel="stylesheet" href="https://uicdn.toast.com/editor/3.0.0/toastui-editor.min.css" />
<script src="https://uicdn.toast.com/editor/3.0.0/toastui-editor.min.js"></script>
<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>
<!-- 自定义样式 -->
<link rel="stylesheet" href="css/admin.css">
<style>
/* 管理面板特定样式 */
.admin-container {
display: flex;
min-height: 100vh;
background-color: var(--background-color);
}
.sidebar {
width: 280px;
background-color: var(--container-bg);
box-shadow: var(--shadow-md);
padding: 1.5rem 0;
z-index: 10;
transition: all 0.3s ease;
}
/* 文档管理新建文档徽章 */
.new-badge {
display: inline-block;
padding: 2px 8px;
background-color: #28a745;
color: white;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
/* 用户信息部分样式 */
.user-profile {
padding: 1rem 1.5rem;
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.user-avatar {
width: 70px;
height: 70px;
border-radius: 50%;
background-color: var(--primary-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
color: var(--primary-color);
margin-bottom: 0.8rem;
box-shadow: var(--shadow-sm);
}
.user-info {
text-align: center;
margin-bottom: 0.5rem;
}
.user-name {
font-weight: 600;
font-size: 1.1rem;
color: var(--text-primary);
margin-bottom: 0.25rem;
}
.user-role {
color: var(--text-secondary);
font-size: 0.85rem;
margin-bottom: 0.5rem;
}
.user-actions {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.user-action-btn {
padding: 0.4rem 0.8rem;
font-size: 0.85rem;
border-radius: var(--radius-sm);
background-color: rgba(61, 124, 244, 0.1);
color: var(--primary-color);
cursor: pointer;
transition: all var(--transition-fast);
}
.user-action-btn:hover {
background-color: var(--primary-color);
color: white;
}
.user-action-btn.logout {
background-color: rgba(255, 82, 82, 0.1);
color: var(--danger-color);
}
.user-action-btn.logout:hover {
background-color: var(--danger-color);
color: white;
}
/* 仪表盘卡片样式 */
.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-md);
box-shadow: var(--shadow-sm);
padding: 1.5rem;
transition: all var(--transition-fast);
border: 1px solid var(--border-light);
position: relative;
overflow: hidden;
}
.dashboard-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-md);
border-color: var(--primary-light);
}
.dashboard-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background-color: var(--primary-color);
}
.card-icon {
width: 50px;
height: 50px;
border-radius: 12px;
background-color: rgba(61, 124, 244, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
color: var(--primary-color);
font-size: 1.4rem;
}
.card-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.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%, var(--primary-dark) 100%);
border-radius: var(--radius-lg);
padding: 2rem;
margin-bottom: 2rem;
color: white;
position: relative;
overflow: hidden;
box-shadow: var(--shadow-md);
}
.welcome-content {
position: relative;
z-index: 1;
}
.welcome-title {
font-size: 1.8rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.welcome-subtitle {
font-size: 1rem;
margin-bottom: 1.5rem;
opacity: 0.9;
}
.welcome-action {
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: var(--radius-md);
font-weight: 500;
cursor: pointer;
transition: all var(--transition-fast);
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.welcome-action:hover {
background-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.welcome-banner::after {
content: '';
position: absolute;
top: -50%;
right: -10%;
width: 300px;
height: 300px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
.welcome-banner::before {
content: '';
position: absolute;
bottom: -30%;
left: -5%;
width: 200px;
height: 200px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
/* 用户中心样式 */
.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;
}
.user-center-section {
margin-bottom: 2rem;
}
.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: 2rem;
}
.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;
}
.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;
}
.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;
margin: 0;
}
.sidebar li {
padding: 1rem 1.5rem;
cursor: pointer;
transition: all var(--transition-fast);
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 1rem;
font-weight: 500;
margin: 0.25rem 0;
border-left: 3px solid transparent;
}
.sidebar li:hover {
background-color: rgba(61, 124, 244, 0.08);
color: var(--primary-color);
}
.sidebar li.active {
background-color: rgba(61, 124, 244, 0.1);
color: var(--primary-color);
border-left: 3px solid var(--primary-color);
font-weight: 600;
}
.sidebar li i {
font-size: 1.2rem;
width: 1.5rem;
text-align: center;
}
.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.85) 0%, rgba(61, 124, 244, 0.85) 100%);
backdrop-filter: blur(5px);
}
.login-content {
background-color: var(--container-bg);
padding: 2.5rem;
border-radius: var(--radius-lg);
width: 380px;
box-shadow: var(--shadow-lg);
position: relative;
animation: slideUp 0.5s ease-out;
border: 1px solid rgba(255, 255, 255, 0.2);
}
@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.8rem;
margin-bottom: 2rem;
text-align: center;
font-weight: 600;
position: relative;
padding-bottom: 1rem;
}
.login-header h2::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 2px;
background: var(--primary-color);
}
.login-form input[type="text"],
.login-form input[type="password"] {
width: 100%;
padding: 1rem 1.2rem;
margin: 0.75rem 0;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
box-sizing: border-box;
font-size: 1rem;
background-color: var (--container-bg);
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 3px rgba(61, 124, 244, 0.2);
outline: none;
}
.login-form button {
width: 100%;
padding: 1rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: var (--radius-md);
cursor: pointer;
font-size: 1.1rem;
font-weight: 500;
margin-top: 1.8rem;
transition: all var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.login-form button:hover {
background-color: var(--primary-dark);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.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;
background-color: var(--background-color);
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;
}
.captcha-container span:hover {
background-color: var(--border-light);
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;
}
/* 文档编辑器部分 */
#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: 1rem;
border: 1px solid var(--border-color);
border-radius: var (--radius-md);;
font-size: 1.1rem;
font-weight: 500;
box-shadow: var(--shadow-sm);
}
.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;
}
</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="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>
<h2><i class="fas fa-cogs"></i>管理面板</h2>
<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 class="dashboard-card">
<h3 class="card-title">最近容器操作</h3>
<table id="recentActivitiesTable">
<!-- 活动表内容将由 systemStatus.refreshSystemStatus() 动态更新 -->
</table>
</div>
</div>
<!-- 基本配置 -->
<div id="basic-config" class="content-section">
<h1 class="admin-title">基本配置</h1>
<div class="config-form">
<div class="form-group">
<label for="logoUrl">Logo URL: (可选)</label>
<input type="url" id="logoUrl" name="logoUrl" class="form-control">
</div>
<button type="button" class="btn btn-primary" onclick="saveConfig({logo: document.getElementById('logoUrl').value})">保存 Logo</button>
<div class="form-group" style="margin-top: 2rem;">
<label for="proxyDomain">Docker镜像代理地址: (必填)</label>
<input type="text" id="proxyDomain" name="proxyDomain" class="form-control" required>
</div>
<button type="button" class="btn btn-primary" onclick="saveConfig({proxyDomain: document.getElementById('proxyDomain').value})">保存代理地址</button>
</div>
</div>
<!-- 菜单管理 -->
<div id="menu-management" class="content-section">
<h2 class="menu-label">菜单项管理</h2>
<table id="menuTable">
<thead>
<!-- 表头将由 menuManager.renderMenuItems() 动态生成 -->
</thead>
<tbody id="menuTableBody">
<!-- 菜单项将在这里动态添加 -->
</tbody>
</table>
<button type="button" class="add-btn" onclick="menuManager.showNewMenuItemRow()">
<i class="fas fa-plus"></i> 添加菜单项
</button>
</div>
<!-- 文档管理部分 -->
<div id="documentation-management" class="content-section">
<h2 class="menu-label">文档管理</h2>
<div class="action-bar" style="margin-bottom: 15px;">
<button type="button" class="btn btn-primary" onclick="documentManager.newDocument()">
<i class="fas fa-plus"></i> 新建文档
</button>
</div>
<table id="documentTable">
<thead>
<!-- 表头将由 documentManager.renderDocumentList() 动态生成 -->
</thead>
<tbody id="documentTableBody">
<!-- 文档列表将在这里动态添加 -->
</tbody>
</table>
<div id="editorContainer" style="display: none;">
<input type="text" id="documentTitle" placeholder="请输入文档标题" autocomplete="off">
<div id="editor">
<!-- 编辑器将在这里初始化 -->
</div>
<div class="editor-actions">
<button type="button" class="btn btn-secondary" onclick="documentManager.cancelEdit()">取消</button>
<button type="button" class="btn btn-primary" onclick="documentManager.saveDocument()">保存文档</button>
</div>
</div>
</div>
<!-- 修改密码部分 -->
<div id="password-change" class="content-section">
<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">
<h1 class="admin-title">网络测试</h1>
<div class="input-group">
<label for="domainSelect">目标域名:</label>
<select id="domainSelect" class="form-control">
<!-- 选项将由 networkTest.initNetworkTest() 动态生成 -->
</select>
</div>
<div class="input-group">
<label for="testType">测试类型:</label>
<select id="testType">
<!-- 选项将由 networkTest.initNetworkTest() 动态生成 -->
</select>
</div>
<button class="btn btn-primary">开始测试</button>
<div id="testResults" style="margin-top: 20px; white-space: pre-wrap; font-family: monospace;"></div>
</div>
<!-- Docker服务状态 -->
<div id="docker-status" class="content-section">
<div class="section-heading">
<h2 class="section-title">Docker 服务状态</h2>
<div class="section-description">
查看和管理Docker容器状态和操作
</div>
</div>
<!-- 新增表格容器以支持标题和操作区域 -->
<div id="dockerTableContainer" class="table-responsive">
<table id="dockerStatusTable" class="excel-table">
<thead></thead>
<tbody id="dockerStatusTableBody"></tbody>
</table>
</div>
</div>
<!-- Docker监控配置 -->
<div id="docker-monitoring" class="content-section">
<h1 class="admin-title">Docker 容器监控</h1>
<div class="monitoring-status">
<span>监控状态:</span>
<span id="monitoringStatus" class="status-indicator">加载中...</span>
</div>
<div class="config-form">
<div class="form-group">
<label for="notificationType">通知方式:</label>
<select id="notificationType" class="form-control">
<option value="wechat">企业微信群机器人</option>
<option value="telegram">Telegram Bot</option>
</select>
</div>
<div id="wechatFields">
<div class="form-group">
<label for="webhookUrl">企业微信机器人 Webhook URL:</label>
<input type="text" id="webhookUrl" name="webhookUrl" class="form-control">
</div>
</div>
<div id="telegramFields" style="display: none;">
<div class="form-group">
<label for="telegramToken">Telegram Bot Token:</label>
<input type="text" id="telegramToken" name="telegramToken" class="form-control">
</div>
<div class="form-group">
<label for="telegramChatId">Telegram Chat ID:</label>
<input type="text" id="telegramChatId" name="telegramChatId" class="form-control">
</div>
</div>
<div class="form-group">
<label for="monitorInterval">监控间隔 (秒):</label>
<input type="number" id="monitorInterval" name="monitorInterval" min="1" value="60" class="form-control">
</div>
<div class="button-group">
<button class="btn btn-secondary" id="testNotifyBtn" onclick="app.testNotification()">测试通知</button>
<button class="btn btn-primary" id="saveConfigBtn" onclick="app.saveMonitoringConfig()">保存配置</button>
<button class="btn btn-secondary" id="toggleMonitoringBtn" onclick="app.toggleMonitoring()">开启/关闭监控</button>
</div>
</div>
<div id="messageContainer"></div>
<h2 class="section-title">已停止的容器</h2>
<div class="container-list">
<table id="stoppedContainersTable" class="container-table">
<thead>
<tr>
<th>容器 ID</th>
<th>名称</th>
<th>状态</th>
</tr>
</thead>
<tbody id="stoppedContainersBody"></tbody>
</table>
</div>
</div>
<!-- 用户中心部分 -->
<div id="user-center" class="content-section">
<div class="user-center-header">
<div>
<h1 class="user-center-title">用户中心</h1>
<p class="user-center-subtitle">管理您的个人信息和账户安全</p>
</div>
<button class="btn btn-primary" id="ucLogoutBtn" style="display: inline-block;">退出登录</button>
</div>
<div class="user-center-card">
<div class="user-center-section">
<h2 class="user-center-section-title">账户信息</h2>
<div class="user-stats">
<div class="stat-card">
<div class="stat-value" id="loginCount">--</div>
<div class="stat-label">登录次数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="lastLogin">--</div>
<div class="stat-label">上次登录</div>
</div>
<div class="stat-card">
<div class="stat-value" id="accountAge">--</div>
<div class="stat-label">账户天数</div>
</div>
</div>
<!-- 移除用户详细信息部分 -->
</div>
<div class="user-center-section">
<h2 class="user-center-section-title">修改密码</h2>
<form id="changePasswordForm">
<label for="ucCurrentPassword">当前密码</label>
<input type="password" id="ucCurrentPassword" name="currentPassword">
<label for="ucNewPassword">新密码</label>
<span class="password-hint" id="ucPasswordHint">密码必须包含至少一个字母、一个数字和一个特殊字符长度在8到16个字符之间</span>
<input type="password" id="ucNewPassword" name="newPassword" oninput="userCenter.checkUcPasswordStrength()">
<label for="ucConfirmPassword">确认新密码</label>
<input type="password" id="ucConfirmPassword" name="confirmPassword">
<span id="ucPasswordStrength" style="color: red;"></span>
<button type="submit" class="btn btn-primary">修改密码</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="login-modal" id="loginModal" style="display: none;">
<div class="login-content">
<div class="login-header">
<h2>管理员登录</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>
</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未定义');
};
});
</script>
</body>
</html>