update 2026-09-07 05:54:25

This commit is contained in:
action committed 2026-09-07 05:54:25 +08:00
1 parent 4a4dfac1a2
commit 344aea7ec2
23 files changed
+208 -582

No files matched your search

@@ -23,7 +23,8 @@ function execInit(be, action) {
}
function restartService(be) {
return execChecked(be.initd, ['restart']);
return execChecked(be.initd, ['restart'])
.then(function() { return waitForService(be, true, 80); });
}
function rejectIfOtherRunning(be, running) {
@@ -59,26 +60,14 @@ function toggleService(be, turnOn) {
.then(function() { return execChecked(be.initd, [action]); });
}
function waitForService(be, turnOn, attempts, readyAfter) {
function waitForService(be, turnOn, attempts) {
return backend.serviceStatus(be.name).then(function(state) {
if (!turnOn && !state.running)
return state;
if (turnOn && state.running)
return backend.serviceReady(be, readyAfter, state.pid).then(function(ready) {
if (ready) {
state.ready = true;
return state;
}
return null;
});
return null;
}).then(function(state) {
if (state)
if (!!state.running === turnOn)
return state;
if (attempts <= 0)
throw new Error(turnOn ? _('Service did not start in time.') : _('Service did not stop in time.'));
return new Promise(function(resolve) { setTimeout(resolve, 250); })
.then(function() { return waitForService(be, turnOn, attempts - 1, readyAfter); });
.then(function() { return waitForService(be, turnOn, attempts - 1); });
});
}
@@ -167,21 +156,6 @@ function renderStatusCard(ctx, listenAddr) {
let busy = false;
let lastError = '';
let refreshGeneration = 0;
const boundaryKey = 'daede.ready-boundary.' + window.location.host + '.' + be.name;
let readyBoundary = null;
try {
const savedBoundary = window.localStorage.getItem(boundaryKey);
if (savedBoundary !== null && /^\d+$/.test(savedBoundary))
readyBoundary = Number(savedBoundary);
} catch (e) {}
const rememberBoundary = function(offset) {
readyBoundary = Number(offset) || 0;
try { window.localStorage.setItem(boundaryKey, String(readyBoundary)); } catch (e) {}
};
const clearBoundary = function() {
readyBoundary = null;
try { window.localStorage.removeItem(boundaryKey); } catch (e) {}
};
const body = E('div', { 'id': 'dd-status-body' }, E('em', {}, _('Collecting data…')));
const card = E('div', { 'class': 'dd-card dd-status-card' }, [
E('h4', { 'class': 'dd-card-title' }, _('Service Status')),
@@ -191,10 +165,7 @@ function renderStatusCard(ctx, listenAddr) {
const render = function(state) {
while (body.firstChild) body.removeChild(body.firstChild);
const starting = !!state.starting;
const badge = starting
? E('span', { 'class': 'dd-badge dd-badge-wait' }, [ E('span', { 'class': 'dd-badge-dot' }), _('Starting…') ])
: state.running
const badge = state.running
? E('span', { 'class': 'dd-badge dd-badge-run' }, [ E('span', { 'class': 'dd-badge-dot' }), _('RUNNING') ])
: E('span', { 'class': 'dd-badge dd-badge-stop' }, [ E('span', { 'class': 'dd-badge-dot' }), _('STOPPED') ]);
@@ -206,12 +177,7 @@ function renderStatusCard(ctx, listenAddr) {
meta.push(E('span', { 'class': 'dd-meta' }, [ E('span', { 'class': 'dd-meta-label' }, 'PID'), state.pid ]));
const swErr = E('span', { 'class': 'dd-meta dd-err', 'style': lastError ? '' : 'display:none' }, lastError);
const sw = E('button', {
'class': 'dd-switch' + (state.running ? ' is-on' : ''),
'type': 'button',
'aria-label': _('Toggle service'),
'disabled': busy ? '' : null
}, [
const sw = E('button', { 'class': 'dd-switch' + (state.running ? ' is-on' : ''), 'type': 'button', 'aria-label': _('Toggle service') }, [
E('span', { 'class': 'dd-switch-knob' })
]);
sw.addEventListener('click', function(ev) {
@@ -219,25 +185,21 @@ function renderStatusCard(ctx, listenAddr) {
if (busy) return;
refreshGeneration++;
busy = true;
sw.disabled = true;
lastError = '';
swErr.style.display = 'none';
const turnOn = !state.running;
/* Keep process liveness separate from data-plane readiness. The process
appears in procd before dae has attached its rules, so show the real
transition until the backend's final startup marker is logged. */
render({ running: turnOn, pid: 0, starting: turnOn });
if (!turnOn)
clearBoundary();
const boundary = turnOn ? backend.serviceLogOffset(be) : Promise.resolve(0);
boundary.then(function(offset) {
if (turnOn)
rememberBoundary(offset);
return toggleService(be, turnOn)
.then(function() { return waitForService(be, turnOn, turnOn ? 240 : 40, offset); });
})
.then(function() {
busy = false;
clearBoundary();
return refresh(true);
/* instant optimistic feedback — the start/stop chain (esp. dae's eBPF
load) takes a few seconds; flip the switch and show a pending label
right away instead of looking frozen */
sw.classList.toggle('is-on', turnOn);
const lbl = sw.parentNode && sw.parentNode.querySelector('.dd-switch-label');
if (lbl) lbl.textContent = '…';
toggleService(be, turnOn)
.then(function() { return waitForService(be, turnOn, 40); })
.then(function() {
busy = false;
return refresh(true);
})
.catch(function(e) {
busy = false;
@@ -252,7 +214,7 @@ function renderStatusCard(ctx, listenAddr) {
E('span', { 'class': 'dd-grow' }),
swErr,
E('span', { 'class': 'dd-switch-wrap' }, [
E('span', { 'class': 'dd-switch-label' }, starting ? _('Starting…') : (state.running ? 'ON' : 'OFF')),
E('span', { 'class': 'dd-switch-label' }, state.running ? 'ON' : 'OFF'),
sw
])
]);
@@ -261,7 +223,7 @@ function renderStatusCard(ctx, listenAddr) {
body.appendChild(E('div', { 'class': 'dd-status-meta' }, meta));
const actions = [];
if (be.hasWebUI && state.running && !starting) {
if (be.hasWebUI && state.running) {
const port = (listenAddr || be.defaultListen).split(':').slice(-1)[0];
actions.push(E('a', {
'class': 'cbi-button cbi-button-action',
@@ -270,23 +232,19 @@ function renderStatusCard(ctx, listenAddr) {
'rel': 'noreferrer noopener'
}, _('Open WebUI')));
}
if (state.running && !starting) {
if (state.running) {
const restart = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Restart'));
restart.addEventListener('click', function(ev) {
ev.preventDefault();
if (busy) return;
busy = true;
restart.disabled = true;
lastError = '';
render({ running: true, pid: 0, starting: true });
backend.serviceLogOffset(be).then(function(offset) {
rememberBoundary(offset);
return restartService(be)
.then(function() { return waitForService(be, true, 240, offset); });
})
.then(function() {
busy = false;
clearBoundary();
return refresh(true);
swErr.style.display = 'none';
restartService(be)
.then(function() {
busy = false;
return refresh(true);
})
.catch(function(e) {
busy = false;
@@ -296,7 +254,7 @@ function renderStatusCard(ctx, listenAddr) {
});
actions.push(restart);
}
if (state.running && !starting && be.name === 'dae') {
if (state.running && be.name === 'dae') {
const hot = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Hot Reload'));
hot.addEventListener('click', function(ev) {
ev.preventDefault();
@@ -305,7 +263,7 @@ function renderStatusCard(ctx, listenAddr) {
});
actions.push(hot);
}
if (state.running && !starting && be.name === 'dae') {
if (state.running && be.name === 'dae') {
const ckBtn = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Test YouTube'));
const ckRes = E('span', { 'class': 'dd-meta', style: 'margin-left:8px;display:none' }, '');
ckBtn.addEventListener('click', function(ev) {
@@ -350,18 +308,7 @@ function renderStatusCard(ctx, listenAddr) {
const generation = refreshGeneration;
return backend.serviceStatus(be.name).then(function(state) {
if (generation !== refreshGeneration || (busy && !force)) return;
if (!state.running) {
clearBoundary();
render(state);
return;
}
return backend.serviceReady(be, readyBoundary, state.pid).then(function(ready) {
if (generation !== refreshGeneration || (busy && !force)) return;
state.starting = !ready;
if (ready)
clearBoundary();
render(state);
});
render(state);
});
};