feat: add station auto update publishing
Publish the latest station script from the console so online hosts download and reboot immediately while offline hosts update on their next load.
This commit is contained in:
@@ -929,13 +929,16 @@
|
||||
|
||||
<!-- Sidebar: Status -->
|
||||
<div class="sidebar" style="min-width: 200px; display:flex; flex-direction:column; height:100%;">
|
||||
<!-- Version Control (Hidden since we don't use it anymore) -->
|
||||
<div class="version-panel" style="display:none;">
|
||||
<!-- Station Update -->
|
||||
<div class="version-panel">
|
||||
<div class="version-panel-header">
|
||||
<span>Audio Version</span>
|
||||
<button class="btn-icon" onclick="fetchVersion()" title="Refresh Version">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
||||
</button>
|
||||
<span>Station Update</span>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button class="btn" id="publish-update-btn" onclick="publishStationUpdate()" title="Publish Station Update">发布更新</button>
|
||||
<button class="btn-icon" onclick="fetchStationUpdate()" title="Refresh Update Status">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-display" id="current-version">Loading...</div>
|
||||
</div>
|
||||
@@ -1072,6 +1075,7 @@
|
||||
let currentPath = 'root';
|
||||
let stationCodeMap = {};
|
||||
let broadcastingStations = new Set();
|
||||
let currentStationUpdate = null;
|
||||
|
||||
// --- WebSocket ---
|
||||
const connStatus = document.getElementById('conn-status');
|
||||
@@ -1118,6 +1122,11 @@
|
||||
|
||||
// --- Renderers ---
|
||||
function updateState(state) {
|
||||
if (state.stationUpdate) {
|
||||
currentStationUpdate = state.stationUpdate;
|
||||
renderStationUpdate(state.stationUpdate);
|
||||
}
|
||||
|
||||
if (state.stations) {
|
||||
const list = document.getElementById('station-list');
|
||||
list.innerHTML = '';
|
||||
@@ -1136,6 +1145,12 @@
|
||||
const hasCC = Array.isArray(st.tags) && st.tags.includes('CC');
|
||||
const stationTarget = st.target || st.name;
|
||||
const memberCount = Number(st.memberCount || 1);
|
||||
const stationVersion = st.version || '未知版本';
|
||||
const targetVersion = currentStationUpdate?.targetVersion || '';
|
||||
const needsUpdate = !!targetVersion && stationVersion !== targetVersion;
|
||||
const updateBadge = needsUpdate
|
||||
? `<span style="font-size:0.65rem; background:rgba(245, 158, 11, 0.12); color:#fbbf24; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(245, 158, 11, 0.25)">待更新 ${targetVersion}</span>`
|
||||
: '';
|
||||
const div = document.createElement('div');
|
||||
div.className = 'status-item';
|
||||
div.id = `station-item-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
||||
@@ -1145,8 +1160,9 @@
|
||||
<div class="item-title">
|
||||
${st.name}
|
||||
${hasCC ? '<span style="font-size:0.65rem; background:rgba(16, 185, 129, 0.1); color:#34d399; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(16, 185, 129, 0.2)">CC</span>' : ''}
|
||||
${updateBadge}
|
||||
</div>
|
||||
<div class="item-sub">${st.code}${st.platform ? ` - Platform ${st.platform}` : ''}${memberCount > 1 ? ` · Nodes ${memberCount}` : ''}</div>
|
||||
<div class="item-sub">${st.code}${st.platform ? ` - Platform ${st.platform}` : ''}${memberCount > 1 ? ` · Nodes ${memberCount}` : ''} · ${stationVersion}</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div class="status-dot ${dotClass}" id="station-dot-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}" data-online="${isOnline}" data-name="${st.name}"></div>
|
||||
@@ -1688,20 +1704,54 @@
|
||||
|
||||
// Init
|
||||
refreshFiles();
|
||||
fetchVersion();
|
||||
fetchStationUpdate();
|
||||
|
||||
async function fetchVersion() {
|
||||
function renderStationUpdate(info) {
|
||||
const el = document.getElementById('current-version');
|
||||
const localVersion = info?.localVersion || '未知';
|
||||
const targetVersion = info?.targetVersion || '未发布';
|
||||
const publishedAt = info?.publishedAt ? new Date(info.publishedAt).toLocaleString() : '未发布';
|
||||
el.innerHTML = `
|
||||
<div>服务器脚本:${localVersion}</div>
|
||||
<div>已发布版本:${targetVersion}</div>
|
||||
<div style="margin-top:4px;color:var(--text-muted)">发布时间:${publishedAt}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchStationUpdate() {
|
||||
try {
|
||||
const res = await fetch('/api/version');
|
||||
const res = await fetch('/api/station-update');
|
||||
const data = await res.json();
|
||||
if (data.version) {
|
||||
document.getElementById('current-version').textContent = data.version;
|
||||
}
|
||||
currentStationUpdate = data;
|
||||
renderStationUpdate(data);
|
||||
} catch (e) {
|
||||
document.getElementById('current-version').textContent = 'Error';
|
||||
}
|
||||
}
|
||||
|
||||
async function publishStationUpdate() {
|
||||
if (!confirm('发布后,所有在线主机会立即下载最新 startup 并自动重启;离线主机会在下次加载时立即更新。确定继续吗?')) return;
|
||||
|
||||
const btn = document.getElementById('publish-update-btn');
|
||||
const originalText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '发布中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/station-updates/publish', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.message || 'Publish failed');
|
||||
currentStationUpdate = data;
|
||||
renderStationUpdate(data);
|
||||
showToast('更新已发布', `目标版本 ${data.targetVersion || data.localVersion},已通知 ${data.notifiedStations || 0}/${data.onlineStations || 0} 台在线主机。`);
|
||||
} catch (e) {
|
||||
alert(`发布失败:${e.message || e}`);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Resizers ---
|
||||
const resizerV = document.getElementById('resizer-v');
|
||||
const resizerH = document.getElementById('resizer-h');
|
||||
|
||||
Reference in New Issue
Block a user