mirror of
https://github.com/halfwaystudent/douyin-sparkflow.git
synced 2026-09-06 16:07:22 +08:00
Sync deployed SparkFlow reliability updates
This commit is contained in:
@@ -3,19 +3,216 @@
|
||||
{% block nav_key %}logs{% endblock %}
|
||||
{% block title %}运行日志 | 抖音多账号续火花控制台{% endblock %}
|
||||
{% block page_title %}运行日志{% endblock %}
|
||||
{% block page_subtitle %}查看最近任务输出和服务诊断信息{% endblock %}
|
||||
{% block page_subtitle %}登录同步 · 发送过程 · 容器状态{% endblock %}
|
||||
|
||||
{% block topbar_actions %}
|
||||
<a class="ghost-button" href="/">⌂ 返回首页</a>
|
||||
<a class="ghost-button" href="/ops/send-console">✦ 发送控制台</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.logs-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.logs-toolbar__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.logs-meta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--text-soft);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.logs-meta .dot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--fire-grad);
|
||||
box-shadow: 0 0 0 4px rgba(255, 122, 61, 0.14);
|
||||
}
|
||||
.logs-meta .dot.live {
|
||||
background: linear-gradient(135deg, #18a558, #4fd08a);
|
||||
box-shadow: 0 0 0 4px rgba(24, 165, 88, 0.16);
|
||||
animation: livePulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes livePulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.45; }
|
||||
}
|
||||
.toggle-switch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
}
|
||||
.toggle-track {
|
||||
width: 42px;
|
||||
height: 24px;
|
||||
border-radius: 999px;
|
||||
background: var(--border-strong);
|
||||
position: relative;
|
||||
transition: background 0.18s ease;
|
||||
flex: 0 0 42px;
|
||||
}
|
||||
.toggle-track::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 5px rgba(20, 35, 70, 0.18);
|
||||
transition: transform 0.18s ease;
|
||||
}
|
||||
.toggle-switch input { display: none; }
|
||||
.toggle-switch input:checked + .toggle-track {
|
||||
background: linear-gradient(180deg, var(--primary), var(--primary-strong));
|
||||
}
|
||||
.toggle-switch input:checked + .toggle-track::after {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
.logs-code {
|
||||
margin: 0;
|
||||
border-radius: var(--radius-md);
|
||||
background: #0f1728;
|
||||
color: #d8e2fb;
|
||||
padding: 18px 20px;
|
||||
overflow: auto;
|
||||
font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.75;
|
||||
min-height: 420px;
|
||||
max-height: 70vh;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
tab-size: 2;
|
||||
}
|
||||
.logs-code:empty::before {
|
||||
content: "暂无日志输出";
|
||||
color: #5f6f8c;
|
||||
}
|
||||
.logs-empty {
|
||||
padding: 60px 24px;
|
||||
text-align: center;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px dashed var(--border-strong);
|
||||
background: var(--empty-bg);
|
||||
color: var(--text-soft);
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
<h2>详细日志</h2>
|
||||
<p class="muted compact">
|
||||
展示最近的任务输出,便于检查登录同步、发送过程和容器状态。
|
||||
</p>
|
||||
<p class="muted compact">展示最近的任务输出,便于检查登录同步、发送过程和容器状态。</p>
|
||||
</div>
|
||||
<a class="link-button" href="/settings">返回系统设置</a>
|
||||
</div>
|
||||
<pre class="code-block tall">{{ log_tail or "暂无日志" }}</pre>
|
||||
|
||||
<div class="logs-toolbar">
|
||||
<div class="logs-toolbar__left">
|
||||
<span class="logs-meta"><span class="dot" id="logs-live-dot"></span><span id="logs-status-text">最近更新:刚刚</span></span>
|
||||
<span class="logs-meta"><span class="dot"></span><span id="logs-line-count">{{ (log_tail|trim).splitlines()|length }} 行</span></span>
|
||||
</div>
|
||||
<div class="logs-toolbar__left">
|
||||
<label class="toggle-switch" title="每 5 秒自动刷新日志">
|
||||
<input type="checkbox" id="logs-autorefresh">
|
||||
<span class="toggle-track"></span>
|
||||
<span>自动刷新</span>
|
||||
</label>
|
||||
<button type="button" class="ghost-button" id="logs-refresh-btn">↻ 刷新</button>
|
||||
<a class="ghost-button" id="logs-download-btn" href="data:text/plain;charset=utf-8,{{ log_tail|urlencode }}">⬇ 下载日志</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if log_tail and log_tail.strip() %}
|
||||
<pre class="logs-code" id="logs-code">{{ log_tail }}</pre>
|
||||
{% else %}
|
||||
<div class="logs-empty" id="logs-empty">当前没有日志输出。任务运行后这里会实时显示。</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const codeEl = document.getElementById("logs-code");
|
||||
const emptyEl = document.getElementById("logs-empty");
|
||||
const statusEl = document.getElementById("logs-status-text");
|
||||
const lineEl = document.getElementById("logs-line-count");
|
||||
const liveDot = document.getElementById("logs-live-dot");
|
||||
const toggle = document.getElementById("logs-autorefresh");
|
||||
const refreshBtn = document.getElementById("logs-refresh-btn");
|
||||
let timer = null;
|
||||
|
||||
const fmtTime = () => {
|
||||
const d = new Date();
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `最近更新:${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
||||
};
|
||||
|
||||
const renderBody = (text) => {
|
||||
const has = !!(text && text.trim());
|
||||
if (has) {
|
||||
if (emptyEl) emptyEl.style.display = "none";
|
||||
if (codeEl) {
|
||||
codeEl.style.display = "";
|
||||
codeEl.textContent = text;
|
||||
}
|
||||
if (lineEl) lineEl.textContent = `${text.trim().split(/\r?\n/).length} 行`;
|
||||
} else {
|
||||
if (codeEl) codeEl.style.display = "none";
|
||||
if (emptyEl) emptyEl.style.display = "";
|
||||
if (lineEl) lineEl.textContent = "0 行";
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
try {
|
||||
const res = await fetch("/ops/logs", { credentials: "same-origin", headers: { "Accept": "text/html" } });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const html = await res.text();
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
const fetched = doc.getElementById("logs-code");
|
||||
const fetchedEmpty = doc.getElementById("logs-empty");
|
||||
if (fetched) {
|
||||
renderBody(fetched.textContent);
|
||||
} else if (fetchedEmpty) {
|
||||
renderBody("");
|
||||
}
|
||||
if (statusEl) statusEl.textContent = fmtTime();
|
||||
} catch (err) {
|
||||
if (statusEl) statusEl.textContent = `刷新失败:${err.message}`;
|
||||
}
|
||||
};
|
||||
|
||||
const setLive = (on) => {
|
||||
if (liveDot) liveDot.classList.toggle("live", on);
|
||||
if (timer) { clearInterval(timer); timer = null; }
|
||||
if (on) {
|
||||
timer = setInterval(refresh, 5000);
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
|
||||
if (toggle) toggle.addEventListener("change", () => setLive(toggle.checked));
|
||||
if (refreshBtn) refreshBtn.addEventListener("click", refresh);
|
||||
if (statusEl) statusEl.textContent = fmtTime();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user