1
This commit is contained in:
parent
37f4450af5
commit
97321f3b28
BIN
favicon.ico
BIN
favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
494
index.php
494
index.php
@ -1,494 +0,0 @@
|
|||||||
<?php
|
|
||||||
// ==================== 全局配置区域 ====================
|
|
||||||
define('CONFIG_FILE', __DIR__ . '/config.php'); // 配置文件路径
|
|
||||||
define('TOKEN_CACHE_FILE', __DIR__ . '/token_cache.php'); // Token缓存文件路径
|
|
||||||
define('TOKEN_EXPIRE_TIME', 3600); // Token过期时间(秒),默认1小时
|
|
||||||
|
|
||||||
// 加载配置文件
|
|
||||||
if (file_exists(CONFIG_FILE)) {
|
|
||||||
require_once CONFIG_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否需要显示配置页面
|
|
||||||
$needConfig = !defined('API_PASS') || !defined('ACCOUNT') || !defined('API_KEY') ||
|
|
||||||
empty(API_PASS) || empty(ACCOUNT) || empty(API_KEY);
|
|
||||||
|
|
||||||
// 处理配置提交
|
|
||||||
if ($needConfig && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['setup_config'])) {
|
|
||||||
$apiPass = trim($_POST['api_pass']);
|
|
||||||
$account = trim($_POST['account']);
|
|
||||||
$apiKey = trim($_POST['api_key']);
|
|
||||||
|
|
||||||
if (empty($apiPass) || empty($account) || empty($apiKey)) {
|
|
||||||
$configError = '所有字段都不能为空!';
|
|
||||||
} else {
|
|
||||||
// 生成配置文件内容
|
|
||||||
$configContent = "<?php\n";
|
|
||||||
$configContent .= "// 核云IDC VPS管理面板配置文件\n";
|
|
||||||
$configContent .= "// 生成时间: " . date('Y-m-d H:i:s') . "\n\n";
|
|
||||||
$configContent .= "define('API_PASS', '" . addslashes($apiPass) . "'); // API访问密码\n";
|
|
||||||
$configContent .= "define('ACCOUNT', '" . addslashes($account) . "'); // 核云IDC账号(手机号或邮箱)\n";
|
|
||||||
$configContent .= "define('API_KEY', '" . addslashes($apiKey) . "'); // 核云IDC API KEY\n";
|
|
||||||
$configContent .= "define('BASE_URL', 'https://www.heyunidc.cn/v1'); // API基础URL\n";
|
|
||||||
|
|
||||||
// 写入配置文件
|
|
||||||
if (file_put_contents(CONFIG_FILE, $configContent)) {
|
|
||||||
// 重新加载配置
|
|
||||||
require_once CONFIG_FILE;
|
|
||||||
$needConfig = false;
|
|
||||||
} else {
|
|
||||||
$configError = '配置文件写入失败,请检查目录权限!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍需配置,显示配置页面
|
|
||||||
if ($needConfig) {
|
|
||||||
header('Content-Type: text/html; charset=utf-8');
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="shortcut icon" href="./static/favicon.ico" type="image/x-icon">
|
|
||||||
<link href="./static/initial.css" rel="stylesheet" type="text/css">
|
|
||||||
<title>初始配置 - VPS管理面板</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="config-container">
|
|
||||||
<div class="config-header">
|
|
||||||
<h1>🔧 初始配置</h1>
|
|
||||||
<p>请填写以下信息以开始使用</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (isset($configError)): ?>
|
|
||||||
<div class="error-message">
|
|
||||||
❌ <?php echo htmlspecialchars($configError); ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="info-box">
|
|
||||||
💡 提示:这些信息将保存在 <code>config.php</code> 文件中,请妥善保管。<br>
|
|
||||||
若您需要重置或更改配置,请删除 <code>config.php</code> 文件并重新运行。<br>
|
|
||||||
配置完成后请以该格式访问服务:example.com/?pass=API_PASS
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST" onsubmit="return validatePassword()">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="api_pass">访问密码 (API_PASS)</label>
|
|
||||||
<input type="text" id="api_pass" name="api_pass" required placeholder="设置一个安全的访问密码" pattern="[a-zA-Z0-9]+" title="只允许字母和数字">
|
|
||||||
<div class="help-text">用于保护管理面板的访问权限<br>可选:字母(a-z,A-Z)和数字(0-9)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="account">核云IDC账号 (ACCOUNT)</label>
|
|
||||||
<input type="text" id="account" name="account" required placeholder="手机号或邮箱">
|
|
||||||
<div class="help-text">您的核云IDC登录账号</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="api_key">API密钥 (API_KEY)</label>
|
|
||||||
<input type="password" id="api_key" name="api_key" required placeholder="输入API KEY">
|
|
||||||
<div class="help-text">在核云IDC控制台获取的API密钥</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" name="setup_config" class="btn-submit">
|
|
||||||
✅ 保存配置并开始使用
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function validatePassword() {
|
|
||||||
var password = document.getElementById('api_pass').value;
|
|
||||||
var pattern = /^[a-zA-Z0-9]+$/;
|
|
||||||
|
|
||||||
if (!pattern.test(password)) {
|
|
||||||
alert('格式不正确!\n\n密码仅允许包含:\n• 字母 (a-z, A-Z)\n• 数字 (0-9)\n\n请勿使用特殊符号或空格');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length < 6) {
|
|
||||||
alert('至少输入6个字符!');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<?php
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Token管理函数 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从PHP缓存文件读取Token
|
|
||||||
*/
|
|
||||||
function getCachedToken() {
|
|
||||||
if (!file_exists(TOKEN_CACHE_FILE)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 包含文件获取变量
|
|
||||||
include TOKEN_CACHE_FILE;
|
|
||||||
|
|
||||||
if (!isset($cached_token) || !isset($cached_timestamp)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查Token是否过期(1小时内有效)
|
|
||||||
$currentTime = time();
|
|
||||||
$tokenAge = $currentTime - $cached_timestamp;
|
|
||||||
|
|
||||||
if ($tokenAge < TOKEN_EXPIRE_TIME) {
|
|
||||||
return $cached_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token已过期,清除缓存
|
|
||||||
@unlink(TOKEN_CACHE_FILE);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存Token到PHP缓存文件
|
|
||||||
*/
|
|
||||||
function saveTokenToCache($token) {
|
|
||||||
$timestamp = time();
|
|
||||||
$expireAt = date('Y-m-d H:i:s', $timestamp + TOKEN_EXPIRE_TIME);
|
|
||||||
|
|
||||||
$content = "<?php\n";
|
|
||||||
$content .= "// Token缓存文件 - 自动生成\n";
|
|
||||||
$content .= "// 生成时间: " . date('Y-m-d H:i:s', $timestamp) . "\n";
|
|
||||||
$content .= "// 过期时间: {$expireAt}\n";
|
|
||||||
$content .= "// 警告: 不要手动修改此文件\n\n";
|
|
||||||
$content .= "\$cached_token = '" . addslashes($token) . "';\n";
|
|
||||||
$content .= "\$cached_timestamp = {$timestamp};\n";
|
|
||||||
|
|
||||||
file_put_contents(TOKEN_CACHE_FILE, $content);
|
|
||||||
|
|
||||||
// 设置文件权限(如果可能)
|
|
||||||
if (function_exists('chmod')) {
|
|
||||||
@chmod(TOKEN_CACHE_FILE, 0600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取登录Token(带缓存)
|
|
||||||
*/
|
|
||||||
function getLoginToken() {
|
|
||||||
// 首先尝试从缓存获取
|
|
||||||
$cachedToken = getCachedToken();
|
|
||||||
if ($cachedToken) {
|
|
||||||
return $cachedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存不存在或已过期,重新获取Token
|
|
||||||
$url = BASE_URL . '/login_api';
|
|
||||||
$data = [
|
|
||||||
'account' => 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');
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="shortcut icon" href="./static/favicon.ico" type="image/x-icon">
|
|
||||||
<link href="./static/style.css" rel="stylesheet" type="text/css">
|
|
||||||
<title>VPS管理面板</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>🖥️ 核云IDC VPS管理面板</h1>
|
|
||||||
<p>实时查看和管理您的云服务器</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<?php if (!empty($errorMsg)): ?>
|
|
||||||
<div class="error-message">
|
|
||||||
<h3>❌ 加载失败</h3>
|
|
||||||
<p><?php echo htmlspecialchars($errorMsg); ?></p>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
|
|
||||||
<?php if (!empty($result)): ?>
|
|
||||||
<?php if (isset($result['status']) && $result['status'] === 200): ?>
|
|
||||||
<div class="success-message">
|
|
||||||
<h3>✅ 操作成功</h3>
|
|
||||||
<p>VPS #<?php echo $hostId; ?> 已成功执行操作</p>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="error-message">
|
|
||||||
<h3>❌ 操作失败</h3>
|
|
||||||
<p><?php echo isset($result['msg']) ? htmlspecialchars($result['msg']) : '未知错误'; ?></p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
$cachedToken = getCachedToken();
|
|
||||||
if ($cachedToken):
|
|
||||||
// 从PHP文件读取时间戳
|
|
||||||
include TOKEN_CACHE_FILE;
|
|
||||||
$remainingTime = TOKEN_EXPIRE_TIME - (time() - $cached_timestamp);
|
|
||||||
$remainingMinutes = floor($remainingTime / 60);
|
|
||||||
?>
|
|
||||||
<div class="token-info">
|
|
||||||
✅ <strong>Token缓存生效中</strong> | 剩余有效期:约 <?php echo $remainingMinutes; ?> 分钟
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="stats-bar">
|
|
||||||
<div class="stats-item">
|
|
||||||
总计:<strong><?php echo $totalCount; ?></strong> 台服务器
|
|
||||||
</div>
|
|
||||||
<div class="stats-item">
|
|
||||||
运行中:<strong><?php
|
|
||||||
$runningCount = 0;
|
|
||||||
foreach ($statusMap as $status) {
|
|
||||||
if ($status['status'] === 'on') $runningCount++;
|
|
||||||
}
|
|
||||||
echo $runningCount;
|
|
||||||
?></strong> 台
|
|
||||||
</div>
|
|
||||||
<div class="stats-item">
|
|
||||||
已关机:<strong><?php
|
|
||||||
$offCount = 0;
|
|
||||||
foreach ($statusMap as $status) {
|
|
||||||
if ($status['status'] === 'off') $offCount++;
|
|
||||||
}
|
|
||||||
echo $offCount;
|
|
||||||
?></strong> 台
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-grid">
|
|
||||||
<?php foreach ($hosts as $host):
|
|
||||||
$statusKey = $host['domainstatus'];
|
|
||||||
$statusInfo = isset($domainstatus[$statusKey]) ? $domainstatus[$statusKey] : ['name' => $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';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<div class="vps-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="vps-id">#<?php echo $host['id']; ?></div>
|
|
||||||
<span class="status-badge" style="background-color: <?php echo $statusInfo['color']; ?>">
|
|
||||||
<?php echo htmlspecialchars($statusInfo['name']); ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">域名</span>
|
|
||||||
<span class="info-value"><?php echo htmlspecialchars($host['domain']); ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">IP地址</span>
|
|
||||||
<span class="info-value"><?php echo htmlspecialchars($host['dedicatedip']); ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">产品名称</span>
|
|
||||||
<span class="info-value"><?php echo htmlspecialchars($host['product_name']); ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">注册日期</span>
|
|
||||||
<span class="info-value"><?php echo $regDate; ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">到期日期</span>
|
|
||||||
<span class="info-value"><?php echo $nextDueDate; ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">金额</span>
|
|
||||||
<span class="info-value">¥<?php echo htmlspecialchars($host['amount']); ?></span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="info-label">计费周期</span>
|
|
||||||
<span class="info-value"><?php echo htmlspecialchars($host['billingcycle']); ?></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="power-status">
|
|
||||||
<span class="power-indicator <?php echo $powerClass; ?>"></span>
|
|
||||||
<span class="power-text"><?php echo htmlspecialchars($powerStatus['des']); ?></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="POST" style="margin: 0;">
|
|
||||||
<input type="hidden" name="host_id" value="<?php echo $host['id']; ?>">
|
|
||||||
<div class="card-actions">
|
|
||||||
<button type="submit" name="action" value="on" class="btn btn-success" onclick="return confirm('确认开机?')">
|
|
||||||
⚡ 开机
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="action" value="off" class="btn btn-danger" onclick="return confirm('确认关机?')">
|
|
||||||
🔴 关机
|
|
||||||
</button>
|
|
||||||
<button type="submit" name="action" value="reboot" class="btn btn-warning" onclick="return confirm('确认硬重启?')" style="grid-column: span 2;">
|
|
||||||
🔄 硬重启
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
121
initial.css
121
initial.css
@ -1,121 +0,0 @@
|
|||||||
* {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
338
style.css
338
style.css
@ -1,338 +0,0 @@
|
|||||||
* {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user