



这个代码实现了一个功能丰富的 Shell 命令执行工具,提供了命令执行、脚本管理、定时任务调度等功能。下面是对其核心功能的详细介绍:
主要功能模块
- **用户认证系统**
- 支持用户名密码登录(默认用户:admin/admin123)
- 基于会话的身份验证机制
- 可配置是否需要认证
- **命令执行模块**
- 提供文本框输入任意 Shell 命令
- 实时显示命令执行结果和返回状态
- 记录命令执行时间
- 限制最大输出大小(1MB)防止内存溢出
-
**脚本管理系统**
- 保存常用命令为脚本文件
- 加载已保存的脚本
- 删除不需要的脚本
- 显示脚本列表(按修改时间排序)
- 支持上传和下载脚本文件
-
**定时任务管理**
- 基于 cron 表达式的定时任务设置
- 提供常用 cron 预设模板(每分钟、每小时等)
- 显示定时任务列表及状态
- 计算并显示下次执行时间
- 添加/删除定时任务
- 检测任务运行状态(运行中/待执行)
-
**日志与历史记录**
- 记录所有命令执行日志
- 显示最近 5 条执行历史
- 日志包含时间、IP、用户、状态和命令内容
-
**界面与用户体验**
- 深色/浅色主题切换(保存用户偏好)
- 响应式设计,适配不同屏幕尺寸
- 命令文本框快捷操作(清空、全选)
- 直观的视觉反馈(成功/失败状态提示)
安全特性
尽管代码中强调"完全取消命令限制",但仍包含以下安全措施:
- 基于会话的用户认证
- 脚本名称的正则表达式验证
- 日志记录所有操作
- 输出内容大小限制
- 定时任务添加时的格式验证
- 明确的安全警告提示
技术实现细节
-
**后端技术**
- 使用 PHP 实现所有功能
- 通过 `exec()` 函数执行 Shell 命令
- 使用 `crontab` 命令管理定时任务
- 基于文件系统的脚本存储
-
**前端技术**
- 纯 HTML/CSS/JavaScript 实现
- 原生 JavaScript 处理所有交互逻辑
- 本地存储保存主题偏好
- 表格展示数据列表
-
**依赖管理**
- 使用 Composer 管理第三方依赖
- 支持 `mtdowling/cron-expression` 库解析 cron 表达式
这个工具非常适合开发和测试环境使用,但由于其高风险特性,强烈建议不要在生产环境或可公开访问的服务器上部署。其次这个工具需要安装 `mtdowling/cron-expression` 库来解析和计算 cron 表达式的下次执行时间。你可以通过 Composer 来安装这个依赖。
安装步骤
- 首先确保你的服务器上已经安装了 Composer。如果没有安装,可以按照 官方文档 进行安装。
- 在项目根目录下创建一个 `composer.json` 文件,内容如下:
{ "require": { "mtdowling/cron-expression": "^1.3" }}
- 在终端中执行以下命令安装依赖:
composer install
这将会在项目目录下创建一个 `vendor` 目录,并安装所需的库。
工具已有的自动加载支持
注意到代码中已经有自动加载 Composer 依赖的部分:
// 自动加载Composer依赖if (file_exists(__DIR__ . '/vendor/autoload.php')) { require_once __DIR__ . '/vendor/autoload.php';}
所以只要按照上述步骤安装了库,工具就能正常使用 cron 表达式解析功能了。
功能说明
安装这个库后,定时任务管理模块将能够:
- 准确计算每个 cron 表达式的下次执行时间
- 在任务列表中显示状态信息(运行中/待执行)
- 提供更友好的定时任务配置体验
再次提醒,这个工具完全取消了命令限制,存在极高的安全风险,请务必仅在受信任的环境中使用。
<?php
// shell_executor.php
session_start();
date_default_timezone_set('Asia/Shanghai');
// 配置参数(完全取消命令限制,极度危险!)
$config = [
'scripts_dir' => __DIR__ . '/scripts/', // 脚本存储目录
'max_output_size' => 1024 * 1024, // 1MB
'log_file' => __DIR__ . '/shell_execution.log',
'require_auth' => true,
'username' => 'admin',
'password' => password_hash('admin123', PASSWORD_DEFAULT),
];
// 自动加载Composer依赖
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
}
// 创建脚本目录(如果不存在)
if (!file_exists($config['scripts_dir'])) {
mkdir($config['scripts_dir'], 0755, true);
}
// 认证处理
if ($config['require_auth'] && !isset($_SESSION['authenticated'])) {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username'], $_POST['password'])) {
if ($_POST['username'] === $config['username'] && password_verify($_POST['password'], $config['password'])) {
$_SESSION['authenticated'] = true;
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
} else {
$auth_error = '用户名或密码错误';
}
} else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Shell执行工具 - 登录</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; }
.login-box { padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.error { color: red; margin-bottom: 10px; }
input { width: 100%; padding: 8px; margin-bottom: 10px; box-sizing: border-box; }
button { width: 100%; padding: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<div class="login-box">
<h2>Shell执行工具登录</h2>
<?php if (isset($auth_error)): ?>
<div class="error"><?php echo htmlspecialchars($auth_error); ?></div>
<?php endif; ?>
<form method="post">
<input type="text" name="username" placeholder="用户名" required>
<input type="password" name="password" placeholder="密码" required>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
<?php
exit;
}
}
// 记录日志
function log_action($command, $output, $success) {
global $config;
$log_entry = date('Y-m-d H:i:s') . ' - ' .
(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'CLI') . ' - ' .
(isset($_SESSION['authenticated']) ? $_SESSION['username'] : 'guest') . ' - ' .
($success ? 'SUCCESS' : 'ERROR') . ' - ' .
$command . PHP_EOL;
// 限制日志中输出内容的大小
$output = substr($output, 0, 1024);
$log_entry .= "Output: " . $output . PHP_EOL . str_repeat('-', 50) . PHP_EOL;
file_put_contents($config['log_file'], $log_entry, FILE_APPEND);
}
// 脚本管理功能
$script_message = '';
$scripts = [];
// 保存脚本
if (isset($_POST['save_script'])) {
$script_name = trim($_POST['script_name']);
$script_content = trim($_POST['command']);
if (empty($script_name)) {
$script_message = "错误: 请输入脚本名称";
} elseif (preg_match('/[^a-zA-Z0-9_\-\.]/', $script_name)) {
$script_message = "错误: 脚本名称只能包含字母、数字、下划线、连字符和点";
} else {
$script_path = $config['scripts_dir'] . $script_name . '.sh';
if (file_put_contents($script_path, $script_content) !== false) {
$script_message = "脚本已保存: $script_name.sh";
} else {
$script_message = "错误: 无法保存脚本";
}
}
}
// 删除脚本
if (isset($_GET['delete_script'])) {
$script_name = basename($_GET['delete_script']);
$script_path = $config['scripts_dir'] . $script_name;
if (file_exists($script_path) && is_writable($script_path)) {
unlink($script_path);
$script_message = "脚本已删除: $script_name";
} else {
$script_message = "错误: 无法删除脚本";
}
}
// 加载脚本
if (isset($_GET['load_script'])) {
$script_name = basename($_GET['load_script']);
$script_path = $config['scripts_dir'] . $script_name;
if (file_exists($script_path) && is_readable($script_path)) {
$_POST['command'] = file_get_contents($script_path);
} else {
$script_message = "错误: 无法加载脚本";
}
}
// 获取脚本列表
if ($handle = opendir($config['scripts_dir'])) {
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != ".." && pathinfo($entry, PATHINFO_EXTENSION) == 'sh') {
$scripts[] = [
'name' => pathinfo($entry, PATHINFO_FILENAME),
'path' => $entry,
'size' => filesize($config['scripts_dir'] . $entry),
'mtime' => filemtime($config['scripts_dir'] . $entry)
];
}
}
closedir($handle);
// 按修改时间排序(最新的在前)
usort($scripts, function($a, $b) {
return $b['mtime'] - $a['mtime'];
});
}
// 定时任务管理功能
$cron_message = '';
$cron_jobs = [];
// 定时任务预设模板
$cron_presets = [
'every_minute' => ['label' => '每分钟', 'expression' => '* * * * *'],
'every_5_minutes' => ['label' => '每5分钟', 'expression' => '*/5 * * * *'],
'every_10_minutes' => ['label' => '每10分钟', 'expression' => '*/10 * * * *'],
'every_30_minutes' => ['label' => '每30分钟', 'expression' => '*/30 * * * *'],
'every_hour' => ['label' => '每小时', 'expression' => '0 * * * *'],
'every_2_hours' => ['label' => '每2小时', 'expression' => '0 */2 * * *'],
'every_6_hours' => ['label' => '每6小时', 'expression' => '0 */6 * * *'],
'daily_midnight' => ['label' => '每天午夜', 'expression' => '0 0 * * *'],
'daily_morning' => ['label' => '每天早上6点', 'expression' => '0 6 * * *'],
'daily_evening' => ['label' => '每天晚上8点', 'expression' => '0 20 * * *'],
'weekly' => ['label' => '每周日午夜', 'expression' => '0 0 * * 0'],
'monthly' => ['label' => '每月1日午夜', 'expression' => '0 0 1 * *'],
];
// 获取当前定时任务列表
function get_cron_jobs() {
exec('crontab -l 2>&1', $output, $return_var);
if ($return_var !== 0) return [];
$jobs = [];
foreach ($output as $line) {
$line = trim($line);
if (!empty($line) && strpos($line, '#') !== 0) { // 过滤注释和空行
$jobs[] = $line;
}
}
return $jobs;
}
// 计算cron表达式的下次执行时间
function get_next_run_time($cron_expr) {
// 解析cron表达式
$parts = explode(' ', $cron_expr);
if (count($parts) != 5) return '无效表达式';
list($minutes, $hours, $days, $months, $weeks) = $parts;
// 使用cron-expression库计算下次执行时间
// 注意: 实际环境中需要安装mtdowling/cron-expression库
// composer require mtdowling/cron-expression
try {
if (class_exists('\Cron\CronExpression')) {
$cron = \Cron\CronExpression::factory("$minutes $hours $days $months $weeks");
return $cron->getNextRunDate()->format('Y-m-d H:i:s');
} else {
return '需要安装cron-expression库';
}
} catch (Exception $e) {
return '计算失败';
}
}
// 检查任务是否正在运行
function is_job_running($command) {
// 简单检查进程是否存在
$escaped_cmd = escapeshellarg('%' . $command . '%');
exec("ps aux | grep $escaped_cmd | grep -v grep", $output);
return count($output) > 0;
}
// 添加定时任务
if (isset($_POST['add_cron'])) {
$schedule = trim($_POST['cron_schedule']);
$command = trim($_POST['command']);
if (empty($schedule) || empty($command)) {
$cron_message = "错误: 定时规则和命令不能为空";
} else {
// 验证cron表达式格式(简易验证)
if (!preg_match('/^(\*|(\d+|\*\/\d+)) (\*|(\d+|\*\/\d+)) (\*|(\d+|\*\/\d+)) (\*|(\d+|\*\/\d+)) (\*|(\d+|\*\/\d+))$/', $schedule)) {
$cron_message = "错误: 定时规则格式无效(分 时 日 月 周)";
} else {
// 备份当前crontab
$current_cron = implode("\n", get_cron_jobs()) . "\n";
// 添加新任务(带注释标识为该工具添加)
$new_cron = $current_cron . "# Added by ShellExecutor\n" . $schedule . " " . $command . "\n";
// 写入crontab
$tmp_file = tempnam(sys_get_temp_dir(), 'cron');
file_put_contents($tmp_file, $new_cron);
exec("crontab " . $tmp_file, $output, $return_var);
unlink($tmp_file);
if ($return_var === 0) {
$cron_message = "定时任务添加成功";
} else {
$cron_message = "错误: 无法添加定时任务(返回码: $return_var)";
}
}
}
}
// 删除定时任务
if (isset($_GET['delete_cron'])) {
$cron_index = intval($_GET['delete_cron']);
$current_jobs = get_cron_jobs();
if (isset($current_jobs[$cron_index])) {
// 排除要删除的任务
$new_jobs = [];
foreach ($current_jobs as $i => $job) {
if ($i !== $cron_index) $new_jobs[] = $job;
}
// 写入新的crontab
$tmp_file = tempnam(sys_get_temp_dir(), 'cron');
file_put_contents($tmp_file, implode("\n", $new_jobs) . "\n");
exec("crontab " . $tmp_file, $output, $return_var);
unlink($tmp_file);
if ($return_var === 0) {
$cron_message = "定时任务删除成功";
} else {
$cron_message = "错误: 无法删除定时任务(返回码: $return_var)";
}
} else {
$cron_message = "错误: 定时任务不存在";
}
}
// 获取当前定时任务
$cron_jobs = get_cron_jobs();
// 执行命令
$result = ['output' => '', 'success' => false, 'execution_time' => 0];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['command']) && !isset($_POST['save_script']) && !isset($_POST['add_cron'])) {
$command = trim($_POST['command']);
// 完全取消所有命令限制,直接执行用户输入的命令
$start_time = microtime(true);
exec($command . ' 2>&1', $output_lines, $return_var);
$end_time = microtime(true);
$result['execution_time'] = round($end_time - $start_time, 3);
$result['output'] = implode("\n", $output_lines);
$result['success'] = ($return_var === 0);
// 限制输出大小
if (strlen($result['output']) > $config['max_output_size']) {
$result['output'] = substr($result['output'], 0, $config['max_output_size']) . "\n\n... 输出内容过大,已截断";
}
log_action($command, $result['output'], $result['success']);
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Shell执行工具</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; }
.header { background-color: #333; color: white; padding: 15px; border-radius: 5px 5px 0 0; }
.content { background-color: white; padding: 20px; border-radius: 0 0 5px 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
.command-box { margin-bottom: 20px; }
textarea { width: 100%; height: 150px; padding: 10px; margin-bottom: 10px; box-sizing: border-box; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; }
.button-group { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
button, input[type="submit"] { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover, input[type="submit"]:hover { background-color: #45a049; }
.btn-danger { background-color: #f44336; }
.btn-danger:hover { background-color: #d32f2f; }
.btn-primary { background-color: #2196F3; }
.btn-primary:hover { background-color: #0b7dda; }
.btn-secondary { background-color: #607D8B; }
.btn-secondary:hover { background-color: #4b636e; }
.btn-cron { background-color: #FF9800; }
.btn-cron:hover { background-color: #F57C00; }
.result-box { margin-top: 20px; }
.result-header { font-weight: bold; margin-bottom: 10px; }
.result-output { background-color: #f9f9f9; border: 1px solid #ddd; padding: 10px; min-height: 100px; max-height: 400px; overflow: auto; font-family: monospace; white-space: pre-wrap; }
.status { margin-top: 10px; padding: 5px; border-radius: 3px; }
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
.footer { margin-top: 20px; text-align: center; color: #666; font-size: 0.9em; }
.history { margin-top: 20px; }
.history table { width: 100%; border-collapse: collapse; }
.history th, .history td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.history th { background-color: #f2f2f2; }
.scripts { margin-top: 20px; }
.scripts table { width: 100%; border-collapse: collapse; }
.scripts th, .scripts td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.scripts th { background-color: #f2f2f2; }
.script-form { margin-top: 10px; }
.script-form input[type="text"] { padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 200px; margin-right: 10px; }
.message { padding: 10px; margin-bottom: 10px; border-radius: 4px; }
.message-success { background-color: #d4edda; color: #155724; }
.message-error { background-color: #f8d7da; color: #721c24; }
.upload-form { margin-top: 10px; }
.upload-form input[type="file"] { margin-right: 10px; }
.cron-form { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 10px; }
.cron-form input[type="text"] { padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.cron-examples { margin-top: 5px; font-size: 0.9em; color: #666; }
.cron-table { margin-top: 10px; width: 100%; border-collapse: collapse; }
.cron-table th, .cron-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.cron-table th { background-color: #f2f2f2; }
.preset-buttons { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 5px; }
.preset-btn { padding: 5px 10px; background-color: #e0e0e0; border: none; border-radius: 4px; cursor: pointer; }
.preset-btn:hover { background-color: #d0d0d0; }
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.9em;
font-weight: bold;
}
.status-active {
background-color: #d4edda;
color: #155724;
}
.status-inactive {
background-color: #f8d7da;
color: #721c24;
}
.status-pending {
background-color: #fff3cd;
color: #856404;
}
/* 深色主题样式 */
.dark-theme {
background-color: #1e1e1e;
color: #e0e0e0;
}
.dark-theme .content {
background-color: #2d2d2d;
color: #e0e0e0;
}
.dark-theme .header {
background-color: #1a1a1a;
}
.dark-theme textarea,
.dark-theme input,
.dark-theme select {
background-color: #3a3a3a;
color: #e0e0e0;
border-color: #555;
}
.dark-theme .result-output {
background-color: #3a3a3a;
color: #e0e0e0;
border-color: #555;
}
.dark-theme .scripts table,
.dark-theme .history table,
.dark-theme .cron-table {
color: #e0e0e0;
}
.dark-theme .scripts th,
.dark-theme .history th,
.dark-theme .cron-table th {
background-color: #3a3a3a;
}
.dark-theme .scripts td,
.dark-theme .history td,
.dark-theme .cron-table td {
border-color: #555;
}
.dark-theme .preset-btn {
background-color: #4a4a4a;
color: #e0e0e0;
}
.dark-theme .preset-btn:hover {
background-color: #5a5a5a;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Shell执行工具</h1>
</div>
<div class="content">
<?php if (!empty($script_message)): ?>
<div class="message <?php echo strpos($script_message, '错误') !== false ? 'message-error' : 'message-success'; ?>">
<?php echo htmlspecialchars($script_message); ?>
</div>
<?php endif; ?>
<?php if (!empty($cron_message)): ?>
<div class="message <?php echo strpos($cron_message, '错误') !== false ? 'message-error' : 'message-success'; ?>">
<?php echo htmlspecialchars($cron_message); ?>
</div>
<?php endif; ?>
<div class="button-group">
<button id="clear-btn">清空命令</button>
<button id="select-all-btn">全选命令</button>
<button id="toggle-theme-btn">切换主题</button>
</div>
<div class="command-box">
<form method="post" enctype="multipart/form-data">
<textarea name="command" placeholder="输入要执行的Shell命令..."><?php echo isset($_POST['command']) ? htmlspecialchars($_POST['command']) : ''; ?></textarea>
<div class="button-group">
<input type="submit" value="执行命令">
<div class="script-form">
<input type="text" name="script_name" placeholder="脚本名称">
<input type="submit" name="save_script" value="保存为脚本" class="btn-primary">
</div>
<div class="upload-form">
<input type="file" name="upload_script" accept=".sh">
<input type="submit" name="upload" value="导入脚本" class="btn-secondary">
</div>
</div>
<!-- 改进的定时任务表单 -->
<div class="cron-form">
<select id="cron_preset" style="padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
<option value="">-- 选择预设定时规则 --</option>
<?php foreach ($cron_presets as $key => $preset): ?>
<option value="<?php echo htmlspecialchars($preset['expression']); ?>"><?php echo htmlspecialchars($preset['label']); ?></option>
<?php endforeach; ?>
</select>
<input type="text" name="cron_schedule" id="cron_schedule" placeholder="定时规则 (分 时 日 月 周)" style="flex: 1;">
<input type="submit" name="add_cron" value="添加定时任务" class="btn-cron">
</div>
<div class="preset-buttons">
<strong>常用预设:</strong>
<?php foreach (array_slice($cron_presets, 0, 6) as $key => $preset): ?>
<button type="button" class="preset-btn" data-cron="<?php echo htmlspecialchars($preset['expression']); ?>"><?php echo htmlspecialchars($preset['label']); ?></button>
<?php endforeach; ?>
</div>
<div class="cron-examples">
<p>或者手动输入定时规则(高级用户):</p>
<ul>
<li><code>* * * * *</code> - 每分钟执行一次</li>
<li><code>0 * * * *</code> - 每小时执行一次</li>
<li><code>0 0 * * *</code> - 每天凌晨执行一次</li>
</ul>
</div>
</form>
</div>
<?php if (isset($result['output'])): ?>
<div class="result-box">
<div class="result-header">
执行结果 (耗时: <?php echo $result['execution_time']; ?> 秒)
</div>
<div class="result-output">
<?php echo htmlspecialchars($result['output']); ?>
</div>
<div class="status <?php echo $result['success'] ? 'success' : 'error'; ?>">
<?php echo $result['success'] ? '命令执行成功 (返回码: 0)' : '命令执行失败 (返回码: ' . (isset($return_var) ? $return_var : '未知') . ')'; ?>
</div>
</div>
<?php endif; ?>
<!-- 改进的定时任务列表 -->
<div class="scripts">
<h3>定时任务列表</h3>
<?php if (!empty($cron_jobs)): ?>
<table class="cron-table">
<tr>
<th>序号</th>
<th>定时规则</th>
<th>命令</th>
<th>下次执行时间</th>
<th>状态</th>
<th>操作</th>
</tr>
<?php foreach ($cron_jobs as $index => $job): ?>
<tr>
<td><?php echo $index + 1; ?></td>
<td><?php echo htmlspecialchars(implode(' ', array_slice(explode(' ', $job), 0, 5))); ?></td>
<td><?php echo htmlspecialchars(implode(' ', array_slice(explode(' ', $job), 5))); ?></td>
<td>
<?php
$schedule = implode(' ', array_slice(explode(' ', $job), 0, 5));
echo get_next_run_time($schedule);
?>
</td>
<td>
<?php
$command = implode(' ', array_slice(explode(' ', $job), 5));
$is_running = is_job_running($command);
$next_run = get_next_run_time($schedule);
if ($next_run === '无效表达式' || $next_run === '计算失败' || $next_run === '需要安装cron-expression库') {
echo '<span class="status-badge status-inactive">无效</span>';
} elseif ($is_running) {
echo '<span class="status-badge status-active">运行中</span>';
} else {
echo '<span class="status-badge status-pending">待执行</span>';
}
?>
</td>
<td>
<a href="?delete_cron=<?php echo $index; ?>" class="btn-danger" onclick="return confirm('确定要删除这个定时任务吗?')">删除</a>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php else: ?>
<p>暂无定时任务</p>
<?php endif; ?>
</div>
<div class="scripts">
<h3>已保存的脚本</h3>
<?php if (!empty($scripts)): ?>
<table>
<tr>
<th>名称</th>
<th>大小</th>
<th>修改时间</th>
<th>操作</th>
</tr>
<?php foreach ($scripts as $script): ?>
<tr>
<td><?php echo htmlspecialchars($script['name']); ?></td>
<td><?php echo round($script['size'] / 1024, 2); ?> KB</td>
<td><?php echo date('Y-m-d H:i:s', $script['mtime']); ?></td>
<td>
<a href="?load_script=<?php echo urlencode($script['path']); ?>" class="btn-primary">加载</a>
<a href="?delete_script=<?php echo urlencode($script['path']); ?>" class="btn-danger" onclick="return confirm('确定要删除这个脚本吗?')">删除</a>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php else: ?>
<p>暂无保存的脚本</p>
<?php endif; ?>
</div>
<div class="history">
<h3>最近执行历史</h3>
<?php
if (file_exists($config['log_file'])) {
$log_lines = array_reverse(file($config['log_file'], FILE_IGNORE_NEW_LINES));
$history = [];
foreach ($log_lines as $line) {
if (preg_match('/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) - (.*?) - (.*?) - (SUCCESS|ERROR) - (.*)$/', $line, $matches)) {
$history[] = [
'time' => $matches[1],
'ip' => $matches[2],
'user' => $matches[3],
'status' => $matches[4],
'command' => $matches[5]
];
if (count($history) >= 5) break;
}
}
if (!empty($history)) {
echo '<table>';
echo '<tr><th>时间</th><th>IP地址</th><th>用户</th><th>状态</th><th>命令</th></tr>';
foreach ($history as $entry) {
$status_class = $entry['status'] === 'SUCCESS' ? 'success' : 'error';
echo '<tr>';
echo '<td>' . htmlspecialchars($entry['time']) . '</td>';
echo '<td>' . htmlspecialchars($entry['ip']) . '</td>';
echo '<td>' . htmlspecialchars($entry['user']) . '</td>';
echo '<td class="' . $status_class . '">' . htmlspecialchars($entry['status']) . '</td>';
echo '<td>' . htmlspecialchars($entry['command']) . '</td>';
echo '</tr>';
}
echo '</table>';
} else {
echo '<p>暂无执行历史</p>';
}
} else {
echo '<p>日志文件不存在或无法访问</p>';
}
?>
</div>
</div>
<div class="footer">
<p><strong>警告:此工具已完全取消所有命令限制,存在致命安全风险!</strong></p>
<p>请勿在任何与公共网络连接的服务器上使用此工具,可能导致服务器被入侵和数据泄露!</p>
<?php if ($config['require_auth']): ?>
<p><a href="?logout=1">退出登录</a></p>
<?php endif; ?>
</div>
</div>
<script>
// 清空命令文本框
document.getElementById('clear-btn').addEventListener('click', function() {
document.querySelector('textarea[name="command"]').value = '';
});
// 全选命令文本框内容
document.getElementById('select-all-btn').addEventListener('click', function() {
const textarea = document.querySelector('textarea[name="command"]');
textarea.select();
});
// 切换深色/浅色主题
document.getElementById('toggle-theme-btn').addEventListener('click', function() {
document.body.classList.toggle('dark-theme');
// 保存主题偏好到localStorage
const isDarkTheme = document.body.classList.contains('dark-theme');
localStorage.setItem('shellExecutorTheme', isDarkTheme ? 'dark' : 'light');
});
// 加载保存的主题偏好
document.addEventListener('DOMContentLoaded', function() {
const savedTheme = localStorage.getItem('shellExecutorTheme');
if (savedTheme === 'dark') {
document.body.classList.add('dark-theme');
}
});
// 预设按钮点击事件
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', function() {
const cronExpr = this.getAttribute('data-cron');
document.getElementById('cron_schedule').value = cronExpr;
});
});
// 预设选择器变更事件
document.getElementById('cron_preset').addEventListener('change', function() {
const selectedValue = this.value;
if (selectedValue) {
document.getElementById('cron_schedule').value = selectedValue;
}
});
// 文件上传处理
document.querySelector('input[name="upload_script"]').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.querySelector('textarea[name="command"]').value = e.target.result;
};
reader.readAsText(file);
}
});
</script>
</body>
</html>
下载
下载地址:
Shell 命令执行工具.rar