792 lines
27 KiB
HTML
792 lines
27 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>脚本管理平台</title>
|
||
<!-- 引入Bootstrap和图标库 -->
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||
<style>
|
||
/* 全局样式 */
|
||
body {
|
||
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||
background-color: #f8f9fa;
|
||
padding: 20px;
|
||
margin: 0;
|
||
}
|
||
|
||
/* 主容器样式调整:适配拖拽分隔线 */
|
||
.main-container {
|
||
display: grid;
|
||
/* 初始布局:左侧280px + 分隔线4px + 右侧自适应 */
|
||
grid-template-columns: 540px 4px 1fr;
|
||
gap: 0; /* 取消间隙,避免分隔线与面板之间有空隙 */
|
||
height: calc(100vh - 40px);
|
||
}
|
||
|
||
/* 左侧脚本列表面板 */
|
||
#script-list-panel {
|
||
background: white;
|
||
border-radius: 8px 0 0 8px; /* 左侧圆角,右侧与分隔线贴合 */
|
||
padding: 15px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
overflow-y: auto;
|
||
width: 100%; /* 宽度由grid控制,内部自适应 */
|
||
}
|
||
|
||
/* 右侧面板样式调整 */
|
||
#right-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-left: 15px; /* 右侧与分隔线保持原有的15px间隙 */
|
||
}
|
||
|
||
/* 拖拽分隔线样式 */
|
||
.resize-handle {
|
||
background-color: #e9ecef;
|
||
cursor: col-resize; /* 鼠标悬停时显示水平调整光标 */
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
.resize-handle:hover {
|
||
background-color: #0d6efd; /* 悬停时变蓝色,提示可拖拽 */
|
||
}
|
||
.resize-handle:active {
|
||
background-color: #0b5ed7; /* 拖拽时加深蓝色 */
|
||
}
|
||
|
||
/* 右侧输出区域 */
|
||
#output-area {
|
||
background: #1e1e1e;
|
||
color: #dcdcdc;
|
||
font-family: 'Consolas', 'Microsoft YaHei Mono', monospace;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
overflow-y: auto; /* 保持滚动 */
|
||
height: 500px; /* 固定高度 500px */
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
/* 输出行样式(区分类型) */
|
||
.output-line {
|
||
margin: 2px 0;
|
||
}
|
||
|
||
/* 搜索框样式优化 */
|
||
#script-search {
|
||
font-size: 13px;
|
||
}
|
||
/* 排序下拉框样式 */
|
||
#script-sort {
|
||
width: auto;
|
||
min-width: 160px;
|
||
}
|
||
/* 脚本项的修改时间显示优化 */
|
||
.mode-info .text-xs {
|
||
font-size: 11px;
|
||
line-height: 1.2;
|
||
}
|
||
/* 响应式调整:小屏幕时搜索框与排序控件换行 */
|
||
@media (max-width: 768px) {
|
||
.main-container {
|
||
grid-template-columns: 1fr; /* 单列布局 */
|
||
grid-template-rows: auto 1fr;
|
||
gap: 15px;
|
||
height: auto;
|
||
}
|
||
#resize-handle {
|
||
display: none; /* 小屏幕隐藏分隔线 */
|
||
}
|
||
#right-panel {
|
||
padding-left: 0; /* 取消右侧间隙 */
|
||
}
|
||
#script-list-panel {
|
||
border-radius: 8px; /* 恢复全圆角 */
|
||
}
|
||
}
|
||
|
||
.output-line .timestamp {
|
||
color: #888; /* 时间戳灰色 */
|
||
}
|
||
|
||
.output-line.success {
|
||
color: #4CAF50; /* 成功绿色 */
|
||
}
|
||
|
||
.output-line.error {
|
||
color: #f44336; /* 错误红色 */
|
||
}
|
||
|
||
.output-line.warning {
|
||
color: #ff9800; /* 警告橙色 */
|
||
}
|
||
|
||
.output-line.info {
|
||
color: #2196F3; /* 信息蓝色 */
|
||
}
|
||
|
||
/* 配置面板 */
|
||
#config-panel {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
margin-top: 15px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 脚本列表项 */
|
||
.script-item {
|
||
padding: 12px;
|
||
margin-bottom: 8px;
|
||
border-left: 3px solid transparent;
|
||
border-radius: 4px;
|
||
background: #f8f9fa;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.script-item:hover {
|
||
background: #e9ecef;
|
||
transform: translateX(2px);
|
||
}
|
||
|
||
/* 脚本项激活状态 */
|
||
.script-item.active {
|
||
border-left-color: #0d6efd; /* 蓝色边框 */
|
||
background: #e9ecef;
|
||
}
|
||
|
||
/* 状态指示器(圆形) */
|
||
.status-indicator {
|
||
display: inline-block;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
margin-right: 8px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 状态颜色:运行中(绿)、调度中(蓝)、停止(红) */
|
||
.status-running {
|
||
background: #198754;
|
||
}
|
||
|
||
.status-scheduled {
|
||
background: #0d6efd;
|
||
}
|
||
|
||
.status-stopped {
|
||
background: #dc3545;
|
||
}
|
||
|
||
/* 状态文本样式 */
|
||
.status-text {
|
||
font-size: 12px;
|
||
margin-left: 8px;
|
||
padding: 2px 6px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.status-text.running {
|
||
background: #d1e7dd;
|
||
color: #198754;
|
||
}
|
||
|
||
.status-text.scheduled {
|
||
background: #cfe2ff;
|
||
color: #0d6efd;
|
||
}
|
||
|
||
.status-text.stopped {
|
||
background: #f8d7da;
|
||
color: #dc3545;
|
||
}
|
||
|
||
/* 按钮样式优化 */
|
||
.btn-sm {
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 清空输出按钮 */
|
||
#clear-output {
|
||
position: absolute;
|
||
top: 15px;
|
||
right: 15px;
|
||
z-index: 10;
|
||
}
|
||
|
||
/* 响应式调整(小屏幕单列布局) */
|
||
@media (max-width: 768px) {
|
||
.main-container {
|
||
grid-template-columns: 1fr;
|
||
height: auto;
|
||
}
|
||
|
||
#output-area {
|
||
height: 300px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 页面标题 -->
|
||
<h4 class="mb-3 d-flex justify-content-between align-items-center">
|
||
<span>脚本管理平台</span>
|
||
<small class="text-muted" id="system-status">系统运行中</small>
|
||
</h4>
|
||
|
||
<!-- 主容器 -->
|
||
<div class="main-container">
|
||
<!-- 左侧脚本列表面板 -->
|
||
<div id="script-list-panel" class="panel-resizable">
|
||
<div class="d-flex flex-column gap-3 mb-3">
|
||
<!-- 新增:搜索框 -->
|
||
<div class="input-group input-group-sm">
|
||
<span class="input-group-text">
|
||
<i class="bi bi-search"></i>
|
||
</span>
|
||
<input type="text" id="script-search" class="form-control" placeholder="搜索脚本(名称模糊匹配)"
|
||
oninput="searchScripts()">
|
||
<button class="btn btn-outline-secondary" type="button" onclick="clearSearch()">
|
||
<i class="bi bi-x"></i>
|
||
</button>
|
||
</div>
|
||
<!-- 新增:排序控制 -->
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<h5 class="mb-0">脚本列表</h5>
|
||
<div class="d-flex gap-2">
|
||
<select id="script-sort" class="form-select form-select-sm" onchange="sortScripts()">
|
||
<option value="modify_time_desc">最近修改时间 ↓</option>
|
||
<option value="name_asc">名称 ↓(A-Z)</option>
|
||
<option value="name_desc">名称 ↑(Z-A)</option>
|
||
</select>
|
||
<button class="btn btn-sm btn-outline-secondary" onclick="loadScripts()">
|
||
<i class="bi bi-arrow-clockwise"></i> 刷新
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 脚本列表容器 -->
|
||
<div id="script-list"></div>
|
||
</div>
|
||
<!-- 新增:拖拽分隔线 -->
|
||
<div id="resize-handle" class="resize-handle" title="拖拽调整宽度"></div>
|
||
<!-- 右侧:输出区域 + 配置面板 -->
|
||
<div id="right-panel">
|
||
<!-- 输出控制台(带清空按钮) -->
|
||
<div class="position-relative">
|
||
<button id="clear-output" class="btn btn-sm btn-outline-light" onclick="clearOutput()">
|
||
<i class="bi bi-trash"></i> 清空输出
|
||
</button>
|
||
<div id="output-area"></div>
|
||
</div>
|
||
|
||
<!-- 脚本配置面板 -->
|
||
<div id="config-panel">
|
||
<h5 class="mb-3">脚本配置</h5>
|
||
<form id="config-form">
|
||
<!-- 选择脚本 -->
|
||
<div class="mb-3">
|
||
<label class="form-label">选择脚本 <span class="text-danger">*</span></label>
|
||
<select class="form-select" id="config-script" required>
|
||
<option value="">请选择脚本...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 运行模式 -->
|
||
<div class="mb-3">
|
||
<label class="form-label">运行模式 <span class="text-danger">*</span></label>
|
||
<select class="form-select" id="config-mode" required>
|
||
<option value="single-run">单次运行(执行后自动停止)</option>
|
||
<option value="long-running">长期运行(退出自动重启)</option>
|
||
<option value="interval">定时执行</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 定时执行配置(默认隐藏) -->
|
||
<div id="interval-config" style="display: none;">
|
||
<label class="form-label">执行周期 <span class="text-danger">*</span></label>
|
||
<div class="input-group">
|
||
<input type="number" class="form-control" id="config-interval" value="1" min="1" required>
|
||
<select class="form-select" id="config-unit" required>
|
||
<option value="minutes">分钟</option>
|
||
<option value="hours">小时</option>
|
||
<option value="days">天</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-text">定时任务将按设定周期自动执行</div>
|
||
</div>
|
||
|
||
<!-- 保存配置按钮 -->
|
||
<button type="submit" class="btn btn-primary w-100">
|
||
<i class="bi bi-save"></i> 保存配置
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 引入依赖JS -->
|
||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script>
|
||
// 全局变量
|
||
const OUTPUT_UPDATE_INTERVAL = 2000; // 输出更新间隔(2秒)
|
||
let allScripts = []; // 存储所有脚本对象:[{name: "xxx.bat", modify_time: ...}, ...]
|
||
let filteredScripts = []; // 过滤后的脚本列表
|
||
let currentScript = null; // 当前选中的脚本名称
|
||
let currentSearchKeyword = "";
|
||
|
||
// ====================== 初始化 ======================
|
||
$(function () {
|
||
// 加载脚本列表
|
||
loadScripts();
|
||
// 监听运行模式切换(显示/隐藏定时配置)
|
||
$('#config-mode').change(function () {
|
||
toggleIntervalConfig($(this).val());
|
||
});
|
||
// 监听配置表单提交
|
||
$('#config-form').submit(function (e) {
|
||
e.preventDefault();
|
||
saveConfig();
|
||
});
|
||
// 定时更新输出区域
|
||
setInterval(() => {
|
||
if (currentScript) updateOutput();
|
||
}, OUTPUT_UPDATE_INTERVAL);
|
||
// 初始化拖拽功能
|
||
initResizeHandle();
|
||
});
|
||
|
||
// ====================== 脚本列表相关 ======================
|
||
/**
|
||
* 加载脚本列表(从后端API获取)
|
||
*/
|
||
function loadScripts() {
|
||
$.get('/api/scripts', function(scripts) {
|
||
// 校验后端返回数据格式
|
||
if (!Array.isArray(scripts)) {
|
||
alert('加载脚本失败:后端返回非数组格式');
|
||
allScripts = [];
|
||
filteredScripts = [];
|
||
renderScriptList();
|
||
updateConfigScriptSelect();
|
||
return;
|
||
}
|
||
|
||
// 过滤并验证脚本对象
|
||
allScripts = scripts.filter(item => {
|
||
return typeof item === 'object' && item !== null &&
|
||
'name' in item && typeof item.name === 'string' && item.name.trim() !== '';
|
||
});
|
||
|
||
currentSearchKeyword = "";
|
||
$('#script-search').val("");
|
||
sortScripts(); // 排序后渲染列表
|
||
updateConfigScriptSelect();
|
||
|
||
// 恢复当前选中脚本状态
|
||
if (currentScript) {
|
||
const scriptExists = allScripts.some(s => s.name === currentScript);
|
||
if (scriptExists) {
|
||
$('#config-script').val(currentScript);
|
||
loadConfig(currentScript);
|
||
updateOutput();
|
||
} else {
|
||
currentScript = null;
|
||
$('#output-area').empty();
|
||
}
|
||
}
|
||
}).fail(function(xhr) {
|
||
alert('加载脚本列表失败:' + (xhr.responseJSON?.error || xhr.statusText));
|
||
allScripts = [];
|
||
filteredScripts = [];
|
||
renderScriptList();
|
||
updateConfigScriptSelect();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 搜索脚本(实时模糊匹配)
|
||
*/
|
||
function searchScripts() {
|
||
const keyword = $('#script-search').val().trim().toLowerCase();
|
||
currentSearchKeyword = keyword;
|
||
|
||
if (keyword === "") {
|
||
filteredScripts = [...allScripts];
|
||
} else {
|
||
filteredScripts = allScripts.filter(scriptObj =>
|
||
scriptObj.name.toLowerCase().includes(keyword)
|
||
);
|
||
}
|
||
sortScripts(false);
|
||
}
|
||
|
||
/**
|
||
* 清空搜索
|
||
*/
|
||
function clearSearch() {
|
||
$('#script-search').val("");
|
||
currentSearchKeyword = "";
|
||
filteredScripts = [...allScripts];
|
||
sortScripts(false);
|
||
}
|
||
|
||
/**
|
||
* 排序脚本(支持按修改时间/名称排序)
|
||
* @param {boolean} reload - 是否重新加载全部脚本(默认true)
|
||
*/
|
||
function sortScripts(reload = true) {
|
||
if (reload) {
|
||
filteredScripts = [...allScripts];
|
||
}
|
||
|
||
const sortType = $('#script-sort').val();
|
||
switch (sortType) {
|
||
case "modify_time_desc":
|
||
filteredScripts.sort((a, b) => b.modify_time - a.modify_time);
|
||
break;
|
||
case "name_asc":
|
||
filteredScripts.sort((a, b) => a.name.localeCompare(b.name));
|
||
break;
|
||
case "name_desc":
|
||
filteredScripts.sort((a, b) => b.name.localeCompare(a.name));
|
||
break;
|
||
}
|
||
renderScriptList();
|
||
}
|
||
|
||
/**
|
||
* 渲染脚本列表
|
||
*/
|
||
function renderScriptList() {
|
||
const container = $('#script-list').empty();
|
||
if (filteredScripts.length === 0) {
|
||
const tip = currentSearchKeyword ?
|
||
`<i class="bi bi-search"></i> 未找到包含"${currentSearchKeyword}"的脚本` :
|
||
`<i class="bi bi-folder-open"></i> tasks目录下无脚本文件(支持.bat/.py)`;
|
||
container.append(`
|
||
<div class="text-center text-muted py-3">
|
||
${tip}
|
||
</div>
|
||
`);
|
||
return;
|
||
}
|
||
|
||
// 遍历过滤后的脚本对象数组
|
||
filteredScripts.forEach(scriptObj => {
|
||
const scriptName = scriptObj.name.trim();
|
||
if (!scriptName) return;
|
||
|
||
const modifyTimeStr = scriptObj.modify_time_str || '未知时间';
|
||
getScriptStatus(scriptName, function(status) {
|
||
// 状态样式处理
|
||
const statusClass = status.status === 'running' ? 'status-running' :
|
||
status.status === 'scheduled' ? 'status-scheduled' : 'status-stopped';
|
||
const statusText = status.status === 'running' ? '运行中' :
|
||
status.status === 'scheduled' ? '已调度' : '停止';
|
||
const statusTextClass = `status-text ${status.status}`;
|
||
|
||
// 按钮处理
|
||
let btnClass, btnIcon, btnText, btnClick;
|
||
if (status.running || status.scheduled) {
|
||
btnClass = 'btn-danger';
|
||
btnIcon = 'bi-stop-fill';
|
||
btnText = '停止';
|
||
btnClick = `stopScript('${escapeHtml(scriptName)}')`;
|
||
} else {
|
||
btnClass = 'btn-success';
|
||
btnIcon = 'bi-play-fill';
|
||
btnText = '启动';
|
||
btnClick = `startScript('${escapeHtml(scriptName)}')`;
|
||
}
|
||
|
||
// 构建脚本项HTML
|
||
const scriptItemHtml = `
|
||
<div class="script-item ${currentScript === scriptName ? 'active' : ''}"
|
||
onclick="selectScript('${escapeHtml(scriptName)}')">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<div class="d-flex align-items-center">
|
||
<span class="status-indicator ${statusClass}"></span>
|
||
<span class="script-name">${escapeHtml(scriptName)}</span>
|
||
<span class="${statusTextClass}">${statusText}</span>
|
||
</div>
|
||
<div class="mode-info text-xs mt-1">
|
||
<span class="text-muted">加载模式信息中...</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-sm ${btnClass}"
|
||
onclick="event.stopPropagation(); ${btnClick}">
|
||
<i class="bi ${btnIcon}"></i> ${btnText}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.append(scriptItemHtml);
|
||
|
||
// 获取并更新模式信息
|
||
getScriptMode(scriptName, function(mode) {
|
||
const modeText = mode === 'long-running' ? '长期运行' :
|
||
mode === 'interval' ? '定时执行' : '单次运行';
|
||
const scheduleInfo = status.schedule_info ? ` | ${status.schedule_info}` : '';
|
||
$(`.script-item:has(.script-name:contains('${escapeHtml(scriptName)}')) .mode-info`).html(`
|
||
<div class="d-flex justify-content-between">
|
||
<span class="text-muted">模式:${modeText}${scheduleInfo}</span>
|
||
<span class="text-muted">最近修改:${modifyTimeStr}</span>
|
||
</div>
|
||
`);
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 更新配置面板的脚本下拉框
|
||
*/
|
||
function updateConfigScriptSelect() {
|
||
const select = $('#config-script').empty();
|
||
select.append('<option value="">请选择脚本...</option>');
|
||
|
||
filteredScripts.forEach(scriptObj => {
|
||
if (typeof scriptObj === 'object' && scriptObj !== null &&
|
||
typeof scriptObj.name === 'string' && scriptObj.name.trim()) {
|
||
|
||
const scriptName = scriptObj.name.trim();
|
||
const escapedName = escapeHtml(scriptName);
|
||
select.append(`<option value="${escapedName}">${escapedName}</option>`);
|
||
}
|
||
});
|
||
|
||
// 恢复当前选中状态
|
||
if (currentScript) {
|
||
const escapedScript = escapeHtml(currentScript);
|
||
if (select.find(`option[value="${escapedScript}"]`).length) {
|
||
select.val(escapedScript);
|
||
} else {
|
||
currentScript = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ====================== 脚本操作相关 ======================
|
||
/**
|
||
* 选择脚本
|
||
* @param {string} scriptName - 脚本名称
|
||
*/
|
||
function selectScript(scriptName) {
|
||
currentScript = scriptName;
|
||
$('#config-script').val(scriptName);
|
||
loadConfig(scriptName);
|
||
updateOutput();
|
||
renderScriptList(); // 刷新列表以更新选中状态
|
||
}
|
||
|
||
/**
|
||
* 启动脚本
|
||
* @param {string} scriptName - 脚本名称
|
||
*/
|
||
function startScript(scriptName) {
|
||
$.get(`/api/start/${encodeURIComponent(scriptName)}`)
|
||
.done(function(data) {
|
||
alert(data.msg);
|
||
updateOutput();
|
||
renderScriptList();
|
||
})
|
||
.fail(function(xhr) {
|
||
alert('启动失败:' + (xhr.responseJSON?.error || xhr.statusText));
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 停止脚本
|
||
* @param {string} scriptName - 脚本名称
|
||
*/
|
||
function stopScript(scriptName) {
|
||
$.get(`/api/stop/${encodeURIComponent(scriptName)}`)
|
||
.done(function(data) {
|
||
alert(data.msg);
|
||
updateOutput();
|
||
renderScriptList();
|
||
})
|
||
.fail(function(xhr) {
|
||
alert('停止失败:' + (xhr.responseJSON?.error || xhr.statusText));
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取脚本状态
|
||
* @param {string} scriptName - 脚本名称
|
||
* @param {function} callback - 回调函数
|
||
*/
|
||
function getScriptStatus(scriptName, callback) {
|
||
$.get(`/api/status/${encodeURIComponent(scriptName)}`)
|
||
.done(function(data) {
|
||
callback(data);
|
||
})
|
||
.fail(function() {
|
||
callback({ status: 'stopped', running: false, scheduled: false });
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取脚本运行模式
|
||
* @param {string} scriptName - 脚本名称
|
||
* @param {function} callback - 回调函数
|
||
*/
|
||
function getScriptMode(scriptName, callback) {
|
||
$.get('/api/config')
|
||
.done(function(configs) {
|
||
const mode = configs[scriptName]?.mode || 'single-run';
|
||
callback(mode);
|
||
})
|
||
.fail(function() {
|
||
callback('single-run');
|
||
});
|
||
}
|
||
|
||
// ====================== 配置相关 ======================
|
||
/**
|
||
* 加载脚本配置
|
||
* @param {string} scriptName - 脚本名称
|
||
*/
|
||
function loadConfig(scriptName) {
|
||
$.get('/api/config')
|
||
.done(function(configs) {
|
||
const config = configs[scriptName] || { mode: 'single-run' };
|
||
$('#config-mode').val(config.mode);
|
||
toggleIntervalConfig(config.mode);
|
||
|
||
if (config.mode === 'interval') {
|
||
$('#config-interval').val(config.interval || 1);
|
||
$('#config-unit').val(config.unit || 'hours');
|
||
}
|
||
})
|
||
.fail(function() {
|
||
alert('加载配置失败');
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 保存脚本配置
|
||
*/
|
||
function saveConfig() {
|
||
const scriptName = $('#config-script').val();
|
||
if (!scriptName) {
|
||
alert('请选择脚本');
|
||
return;
|
||
}
|
||
|
||
const mode = $('#config-mode').val();
|
||
const config = { mode };
|
||
|
||
if (mode === 'interval') {
|
||
config.interval = parseInt($('#config-interval').val());
|
||
config.unit = $('#config-unit').val();
|
||
}
|
||
|
||
$.ajax({
|
||
url: '/api/config',
|
||
type: 'POST',
|
||
contentType: 'application/json',
|
||
data: JSON.stringify({ [scriptName]: config }),
|
||
success: function(data) {
|
||
alert(data.msg);
|
||
renderScriptList();
|
||
},
|
||
error: function(xhr) {
|
||
alert('保存失败:' + (xhr.responseJSON?.error || xhr.statusText));
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 显示/隐藏定时配置区域
|
||
* @param {string} mode - 运行模式
|
||
*/
|
||
function toggleIntervalConfig(mode) {
|
||
if (mode === 'interval') {
|
||
$('#interval-config').show();
|
||
} else {
|
||
$('#interval-config').hide();
|
||
}
|
||
}
|
||
|
||
// ====================== 输出相关 ======================
|
||
/**
|
||
* 更新输出区域
|
||
*/
|
||
function updateOutput() {
|
||
if (!currentScript) return;
|
||
|
||
$.get(`/api/status/${encodeURIComponent(currentScript)}`)
|
||
.done(function(data) {
|
||
const outputArea = $('#output-area');
|
||
outputArea.empty();
|
||
|
||
(data.output || []).forEach(line => {
|
||
// 简单的日志类型识别和样式处理
|
||
let className = 'output-line';
|
||
if (line.includes('❌')) className += ' error';
|
||
else if (line.includes('✅')) className += ' success';
|
||
else if (line.includes('⚠️')) className += ' warning';
|
||
else if (line.includes('⏰') || line.includes('ℹ️')) className += ' info';
|
||
|
||
outputArea.append(`<div class="${className}">${escapeHtml(line)}</div>`);
|
||
});
|
||
|
||
// 滚动到底部
|
||
outputArea.scrollTop(outputArea[0].scrollHeight);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 清空输出区域
|
||
*/
|
||
function clearOutput() {
|
||
$('#output-area').empty();
|
||
}
|
||
|
||
// ====================== 工具函数 ======================
|
||
/**
|
||
* HTML特殊字符转义
|
||
* @param {string} unsafe - 待转义的字符串
|
||
* @returns {string} 转义后的字符串
|
||
*/
|
||
function escapeHtml(unsafe) {
|
||
if (!unsafe) return '';
|
||
return unsafe
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
/**
|
||
* 初始化拖拽分隔线功能
|
||
*/
|
||
function initResizeHandle() {
|
||
const handle = $('#resize-handle');
|
||
const leftPanel = $('#script-list-panel');
|
||
let isResizing = false;
|
||
|
||
handle.mousedown(function(e) {
|
||
isResizing = true;
|
||
$(document).mousemove(function(e) {
|
||
if (!isResizing) return;
|
||
// 限制最小宽度为200px
|
||
const newWidth = Math.max(200, e.pageX - leftPanel.offset().left);
|
||
$('.main-container').css('grid-template-columns', `${newWidth}px 4px 1fr`);
|
||
});
|
||
$(document).mouseup(function() {
|
||
isResizing = false;
|
||
});
|
||
e.preventDefault();
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |