diff --git a/web/index.php b/web/index.php new file mode 100644 index 0000000..4c4f95b --- /dev/null +++ b/web/index.php @@ -0,0 +1,494 @@ + + + + + + + + + 初始配置 - VPS管理面板 + + +
+
+

🔧 初始配置

+

请填写以下信息以开始使用

+
+ + +
+ ❌ +
+ + +
+ 💡 提示:这些信息将保存在 config.php 文件中,请妥善保管。
+ 若您需要重置或更改配置,请删除 config.php 文件并重新运行。
+ 配置完成后请以该格式访问服务:example.com/?pass=API_PASS +
+ +
+
+ + +
用于保护管理面板的访问权限
可选:字母(a-z,A-Z)和数字(0-9)
+
+ +
+ + +
您的核云IDC登录账号
+
+ +
+ + +
在核云IDC控制台获取的API密钥
+
+ + +
+
+ + + + + ACCOUNT, + 'password' => API_KEY + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + return null; + } + + $result = json_decode($response, true); + $token = isset($result['jwt']) ? $result['jwt'] : null; + + // 保存新Token到缓存 + if ($token) { + saveTokenToCache($token); + } + + return $token; +} + +// ==================== 功能实现区域 ==================== + +/** + * 发送API请求 + */ +function sendApiRequest($endpoint, $method = 'GET', $data = []) { + $token = getLoginToken(); + if (!$token) { + return ['status' => 500, 'msg' => '获取Token失败,请检查账号和密码配置']; + } + + $url = BASE_URL . $endpoint; + $headers = [ + 'Authorization: JWT ' . $token, + 'Content-Type: application/json' + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + if ($method === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + } elseif ($method === 'PUT') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + } + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + return json_decode($response, true); +} + +/** + * 获取VPS列表 + */ +function getHostList($page = 1, $limit = 100) { + return sendApiRequest("/hosts?page={$page}&limit={$limit}", 'GET'); +} + +/** + * 获取VPS状态 + */ +function getHostStatus($hostId) { + return sendApiRequest("/hosts/{$hostId}/module/status?type=host", 'GET'); +} + +/** + * 操作VPS + */ +function operateHost($hostId, $operation) { + return sendApiRequest("/hosts/{$hostId}/module/{$operation}", 'PUT'); +} + +// ==================== 主逻辑区域 ==================== + +// 验证pass参数 +$pass = isset($_GET['pass']) ? $_GET['pass'] : ''; + +if ($pass !== API_PASS) { + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['status' => 403, 'msg' => '访问密码错误或者未输入密码,请拼接?pass=API_PASS后再尝试访问'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + exit; +} + +// 处理操作请求 +$action = isset($_POST['action']) ? $_POST['action'] : ''; +$hostId = isset($_POST['host_id']) ? intval($_POST['host_id']) : 0; + +$result = null; +if ($action && $hostId > 0) { + if ($action === 'reboot') { + $result = operateHost($hostId, 'hard_reboot'); + } elseif ($action === 'on') { + $result = operateHost($hostId, 'on'); + } elseif ($action === 'off') { + $result = operateHost($hostId, 'off'); + } +} + +// 获取VPS列表和状态 +$hosts = []; +$statusMap = []; +$domainstatus = []; +$totalCount = 0; +$errorMsg = ''; + +$listResult = getHostList(); +if (isset($listResult['status']) && $listResult['status'] === 200) { + $data = $listResult['data']; + $hosts = $data['host']; + $domainstatus = $data['domainstatus']; + $totalCount = $data['total']; + + // 批量获取所有VPS的状态 + foreach ($hosts as $host) { + $statusResult = getHostStatus($host['id']); + if (isset($statusResult['status']) && $statusResult['status'] === 200) { + $statusMap[$host['id']] = $statusResult['data']; + } else { + $statusMap[$host['id']] = ['status' => 'unknown', 'des' => '未知']; + } + } +} else { + $errorMsg = isset($listResult['msg']) ? $listResult['msg'] : '获取VPS列表失败'; +} + +header('Content-Type: text/html; charset=utf-8'); +?> + + + + + + + + VPS管理面板 + + +
+
+

🖥️ 核云IDC VPS管理面板

+

实时查看和管理您的云服务器

+
+ +
+ +
+

❌ 加载失败

+

+
+ + + + +
+

✅ 操作成功

+

VPS # 已成功执行操作

+
+ +
+

❌ 操作失败

+

+
+ + + + +
+ ✅ Token缓存生效中 | 剩余有效期:约 分钟 +
+ + +
+
+ 总计: 台服务器 +
+
+ 运行中: 台 +
+
+ 已关机: 台 +
+
+ +
+ $statusKey, 'color' => '#999']; + $powerStatus = isset($statusMap[$host['id']]) ? $statusMap[$host['id']] : ['status' => 'unknown', 'des' => '未知']; + + $regDate = date('Y-m-d', $host['regdate']); + $nextDueDate = date('Y-m-d', $host['nextduedate']); + + $powerClass = 'power-unknown'; + if ($powerStatus['status'] === 'on') { + $powerClass = 'power-on'; + } elseif ($powerStatus['status'] === 'off') { + $powerClass = 'power-off'; + } + ?> +
+
+
#
+ + + +
+ +
+
+ 域名 + +
+
+ IP地址 + +
+
+ 产品名称 + +
+
+ 注册日期 + +
+
+ 到期日期 + +
+
+ 金额 + ¥ +
+
+ 计费周期 + +
+
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/web/static/favicon.ico b/web/static/favicon.ico new file mode 100644 index 0000000..edab233 Binary files /dev/null and b/web/static/favicon.ico differ diff --git a/web/static/initial.css b/web/static/initial.css new file mode 100644 index 0000000..70d47b1 --- /dev/null +++ b/web/static/initial.css @@ -0,0 +1,121 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.config-container { + background: white; + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + padding: 40px; + max-width: 500px; + width: 100%; +} + +.config-header { + text-align: center; + margin-bottom: 30px; +} + +.config-header h1 { + color: #2d3748; + font-size: 24px; + margin-bottom: 10px; +} + +.config-header p { + color: #718096; + font-size: 14px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + color: #4a5568; + font-weight: 600; + margin-bottom: 8px; + font-size: 14px; +} + +.form-group input { + width: 100%; + padding: 12px 16px; + border: 2px solid #e2e8f0; + border-radius: 8px; + font-size: 14px; + transition: all 0.3s; +} + +.form-group input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.form-group .help-text { + color: #a0aec0; + font-size: 12px; + margin-top: 6px; +} + +.error-message { + background: #fed7d7; + border-left: 4px solid #f56565; + color: #c53030; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 20px; + font-size: 14px; +} + +.btn-submit { + width: 100%; + padding: 14px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s; +} + +.btn-submit:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4); +} + +.btn-submit:active { + transform: translateY(0); +} + +.info-box { + background: #ebf8ff; + border-left: 4px solid #4299e1; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 20px; + font-size: 13px; + color: #2c5282; +} + +@media (max-width: 480px) { + .config-container { + padding: 30px 20px; + } +} \ No newline at end of file diff --git a/web/static/style.css b/web/static/style.css new file mode 100644 index 0000000..9e955c0 --- /dev/null +++ b/web/static/style.css @@ -0,0 +1,338 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + min-height: 100vh; + padding: 10px; +} + +.container { + max-width: 1400px; + margin: 0 auto; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 16px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 25px 20px; + text-align: center; +} + +.header h1 { + font-size: 24px; + margin-bottom: 8px; + font-weight: 600; +} + +.header p { + opacity: 0.9; + font-size: 13px; +} + +.content { + padding: 20px; +} + +.token-info { + background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); + border-left: 4px solid #00bcd4; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 20px; + font-size: 13px; + color: #006064; +} + +.token-info strong { + color: #00838f; +} + +.stats-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + border-radius: 10px; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 10px; +} + +.stats-item { + font-size: 14px; + color: #2d3748; +} + +.stats-item strong { + color: #667eea; + font-size: 18px; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 20px; +} + +.vps-card { + background: white; + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: all 0.3s ease; + border: 1px solid #e2e8f0; +} + +.vps-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 12px; + border-bottom: 2px solid #f0f0f0; +} + +.vps-id { + font-size: 18px; + font-weight: 600; + color: #2d3748; +} + +.status-badge { + display: inline-block; + padding: 6px 14px; + border-radius: 20px; + color: white; + font-size: 12px; + font-weight: 600; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.card-body { + margin-bottom: 15px; +} + +.info-row { + display: flex; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid #f7fafc; + font-size: 13px; +} + +.info-row:last-child { + border-bottom: none; +} + +.info-label { + color: #718096; + font-weight: 500; +} + +.info-value { + color: #2d3748; + font-weight: 600; + text-align: right; + max-width: 60%; + word-break: break-all; +} + +.power-status { + display: flex; + align-items: center; + gap: 8px; + padding: 10px; + background: #f7fafc; + border-radius: 8px; + margin-bottom: 15px; +} + +.power-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + display: inline-block; +} + +.power-on { + background: #48bb78; + box-shadow: 0 0 8px #48bb78; +} + +.power-off { + background: #e53e3e; + box-shadow: 0 0 8px #e53e3e; +} + +.power-unknown { + background: #a0aec0; +} + +.power-text { + font-size: 14px; + font-weight: 600; + color: #2d3748; +} + +.card-actions { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; +} + +.btn { + padding: 10px 16px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + text-decoration: none; + color: white; + transition: all 0.3s; + text-align: center; + display: block; +} + +.btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.btn:active { + transform: translateY(0); +} + +.btn-success { + background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); +} + +.btn-warning { + background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%); +} + +.btn-danger { + background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%); +} + +.btn-info { + background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%); +} + +.error-message { + background: linear-gradient(135deg, #fed7d7 0%, #feb2b2 100%); + border-left: 4px solid #f56565; + color: #c53030; + padding: 20px; + border-radius: 8px; + text-align: center; +} + +.success-message { + background: linear-gradient(135deg, #c6f6d5 0%, #9ae6b4 100%); + border-left: 4px solid #48bb78; + color: #22543d; + padding: 20px; + border-radius: 8px; + text-align: center; + margin-bottom: 20px; +} + +.loading { + text-align: center; + padding: 40px; + color: #718096; +} + +.loading-spinner { + display: inline-block; + width: 40px; + height: 40px; + border: 4px solid #e2e8f0; + border-top-color: #667eea; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-bottom: 15px; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* 响应式设计 */ +@media (max-width: 768px) { + body { + padding: 5px; + } + + .header { + padding: 20px 15px; + } + + .header h1 { + font-size: 20px; + } + + .content { + padding: 15px; + } + + .card-grid { + grid-template-columns: 1fr; + gap: 15px; + } + + .vps-card { + padding: 15px; + } + + .card-actions { + grid-template-columns: 1fr; + } + + .stats-bar { + flex-direction: column; + align-items: flex-start; + } + + .info-row { + flex-direction: column; + gap: 4px; + } + + .info-value { + max-width: 100%; + text-align: left; + } +} + +@media (max-width: 480px) { + .header h1 { + font-size: 18px; + } + + .vps-id { + font-size: 16px; + } + + .btn { + padding: 8px 12px; + font-size: 12px; + } +} \ No newline at end of file