Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "LiteCron Web",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/webapp.py",
"cwd": "${workspaceFolder}/src",
"console": "integratedTerminal",
"env": {
"WEBUI_PORT": "5000"
}
}
]
}
4 changes: 2 additions & 2 deletions compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ services:
ports:
- "5000:5000"
volumes:
- ./config.yml:/app/config.yml:ro
- ./tasks:/app/tasks:ro
- ./config.yml:/app/config.yml
- ./tasks:/app/tasks
- ./logs:/app/logs
- ./data:/app/data
networks:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 定时任务容器依赖
PyYAML==6.0.1
ruamel.yaml>=0.18.0
requests>=2.28.0
curl_cffi>=0.5.0

Expand Down
20 changes: 7 additions & 13 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ const elements = {
taskList: document.getElementById('task-list'),
taskCount: document.getElementById('task-count'),
enabledCount: document.getElementById('enabled-count'),
updateTime: document.getElementById('update-time'),


// 按钮
btnLogs: document.getElementById('btn-logs'),
btnClean: document.getElementById('btn-clean'),
Expand Down Expand Up @@ -173,16 +172,11 @@ async function loadTasks() {
</td>
</tr>
`;
// 更新时间
elements.updateTime.textContent = new Date().toLocaleString();
return;
}

renderTasks();

// 更新时间
elements.updateTime.textContent = new Date().toLocaleString();


} catch (error) {
console.error('加载任务失败:', error);
elements.taskList.innerHTML = `
Expand Down Expand Up @@ -250,16 +244,16 @@ function renderTasks() {
</td>
<td>
<div class="task-actions">
<button class="btn-action btn-run"
onclick="runTask('${task.name}')"
<button class="btn-action btn-run"
onclick="runTask('${task.name}')"
${!task.enabled ? 'disabled' : ''}
title="立即执行">
执行
▶ 执行
</button>
<button class="btn-action btn-toggle"
<button class="btn-action btn-toggle ${task.enabled ? 'disable' : 'enable'}"
onclick="toggleTask('${task.name}', ${!task.enabled})"
title="${task.enabled ? '禁用' : '启用'}">
${task.enabled ? '⏸' : '☑️'}
${task.enabled ? '⏸ 禁用' : '▶ 启用'}
</button>
</div>
</td>
Expand Down
97 changes: 86 additions & 11 deletions src/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,30 @@ body {
gap: 12px;
}

.logo-icon {
font-size: 2rem;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}

.logo h1 {
font-size: 1.75rem;
font-weight: 700;
}

.logo h1 a {
text-decoration: none;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-info));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
transition: opacity var(--transition-fast);
}

.logo h1 a:hover {
opacity: 0.8;
}

.version-badge {
display: inline-flex;
align-items: center;
color: var(--text-muted);
font-size: inherit;
font-weight: inherit;
}

.header-stats {
Expand Down Expand Up @@ -449,44 +461,97 @@ body {
/* 操作按钮 */
.task-actions {
display: flex;
gap: 8px;
gap: 6px;
}

.btn-action {
padding: 8px 14px;
padding: 6px 12px;
font-size: 0.8125rem;
border-radius: var(--radius-sm);
border: none;
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 4px;
}

.btn-run {
background: var(--accent-primary);
background: linear-gradient(135deg, var(--accent-primary), #2563eb);
color: var(--text-inverse);
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
position: relative;
overflow: hidden;
}

.btn-run::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s ease;
}

.btn-run:hover::before {
left: 100%;
}

.btn-run:hover {
background: var(--accent-primary-hover);
transform: scale(1.05);
background: linear-gradient(135deg, #2563eb, #1d4ed8);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
}

.btn-run:disabled {
background: var(--text-muted);
box-shadow: none;
}

.btn-toggle {
background: var(--bg-primary);
background: var(--bg-card);
color: var(--text-secondary);
border: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
}

.btn-toggle:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--accent-primary);
transform: translateY(-1px);
}

.btn-toggle.enable {
color: var(--accent-success);
border-color: rgba(16, 185, 129, 0.3);
}

.btn-toggle.enable:hover {
background: rgba(16, 185, 129, 0.1);
border-color: var(--accent-success);
color: var(--accent-success);
}

.btn-toggle.disable {
color: var(--accent-warning);
border-color: rgba(245, 158, 11, 0.3);
}

.btn-toggle.disable:hover {
background: rgba(245, 158, 11, 0.1);
border-color: var(--accent-warning);
color: var(--accent-warning);
}

.btn-action:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}

/* 加载状态 */
Expand Down Expand Up @@ -525,6 +590,16 @@ body {
font-size: 0.875rem;
}

.footer a {
color: var(--text-muted);
text-decoration: none;
}

.footer a:hover {
color: var(--text-secondary);
text-decoration: underline;
}

/* 模态框 */
.modal {
display: none;
Expand Down
5 changes: 2 additions & 3 deletions src/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
<header class="header">
<div class="header-content">
<div class="logo">
<!-- <span class="logo-icon">⏰</span> -->
<h1>LiteCron</h1>
<h1><a href="https://github.com/pdone/lite-cron" target="_blank" rel="noopener noreferrer">LiteCron</a></h1>
</div>
<div class="header-stats">
<div class="stat-item">
Expand Down Expand Up @@ -80,7 +79,7 @@ <h2 class="card-title">

<!-- 页脚 -->
<footer class="footer">
<p>LiteCron Web 管理界面 v{{ app_version }} · <span id="update-time"></span></p>
<p>LiteCron WebUI · 当前版本 v{{ app_version }} · <a href="https://github.com/pdone/lite-cron/releases" target="_blank" rel="noopener noreferrer">查看最新版本</a></p>
</footer>
</div>

Expand Down
34 changes: 29 additions & 5 deletions src/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,44 @@ def load_config() -> Optional[Dict[str, Any]]:


def save_config(config: Dict[str, Any]) -> bool:
"""保存 YAML 配置文件"""
"""保存 YAML 配置文件,保留原始格式和注释"""
try:
import yaml
from ruamel.yaml import YAML
ry = YAML()
ry.preserve_quotes = True

with open(CONFIG_FILE, "r", encoding="utf-8") as f:
doc = ry.load(f)

_deep_merge(doc, config)

with open(CONFIG_FILE, "w", encoding="utf-8") as f:
yaml.dump(
config, f, allow_unicode=True, sort_keys=False, default_flow_style=False
)
ry.dump(doc, f)

return True
except Exception as e:
log_error(f"保存配置失败: {e}")
return False


def _deep_merge(base, update):
"""深度合并 update 到 base,保留 base 的 ruamel 格式对象"""
for key, value in update.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
_deep_merge(base[key], value)
elif key in base and isinstance(base[key], list) and isinstance(value, list):
for i, item in enumerate(value):
if i < len(base[key]):
if isinstance(base[key][i], dict) and isinstance(item, dict):
_deep_merge(base[key][i], item)
else:
base[key][i] = item
else:
base[key].append(item)
else:
base[key] = value


def get_container_status() -> Dict[str, Any]:
"""获取容器状态"""
try:
Expand Down
Loading