update 2026-09-07 05:54:25

This commit is contained in:
action
2026-09-07 05:54:25 +08:00
parent 4a4dfac1a2
commit 344aea7ec2
23 changed files with 208 additions and 582 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-daede
PKG_VERSION:=1.15
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -23,9 +23,7 @@ const BACKENDS = {
pkg: 'daed',
hasWebUI: true,
useNetns: true,
defaultListen: '0.0.0.0:2023',
readyState: '/var/run/daede-daed.ready',
readyPattern: /Reload: Finished/
defaultListen: '0.0.0.0:2023'
},
dae: {
name: 'dae',
@@ -36,9 +34,7 @@ const BACKENDS = {
pkg: 'dae',
config: '/etc/dae/config.dae',
hasWebUI: false,
useNetns: false,
readyState: '/var/run/daede-dae.ready',
readyPattern: /Total startup time:/
useNetns: false
}
};
@@ -68,56 +64,6 @@ function serviceStatus(name) {
});
}
function serviceLogOffset(be) {
if (!be || !be.log)
return Promise.resolve(0);
return L.resolveDefault(fs.read_direct(be.log, 'text'), '').then(function(content) {
return String(content || '').length;
});
}
function serviceReady(be, afterOffset, pid) {
if (!be || !be.log || !be.readyPattern)
return Promise.resolve(false);
const expectedPid = Number(pid) || 0;
const notFound = function(error) {
const code = String(error && (error.code || error.errno || error.name) || '').toUpperCase();
const message = String(error && (error.message || error) || '').toUpperCase();
return code === 'ENOENT' || code === '-2' || code === 'NOTFOUNDERROR' ||
/ENOENT|NO SUCH FILE|RESOURCE NOT FOUND/.test(message);
};
const readState = be.readyState
? fs.stat(be.readyState).then(function(stat) {
// A successful stat makes the state file authoritative. A subsequent
// read error must fail closed instead of falling back to an old log line.
if (!stat)
return { present: true, error: true };
return fs.read_direct(be.readyState, 'text').then(function(value) {
return { present: true, value: String(value || '').trim() };
}, function() {
return { present: true, error: true };
});
}, function(error) {
return notFound(error) ? { present: false } : { present: true, error: true };
})
: Promise.resolve({ present: true, error: true });
return readState.then(function(result) {
if (result.present)
return !result.error && expectedPid > 0 && result.value === expectedPid + ' ready';
// Compatibility fallback for a luci-app upgrade before the matching dae or
// daed package. It is allowed only when stat explicitly reported ENOENT.
return L.resolveDefault(fs.read_direct(be.log, 'text'), '').then(function(content) {
const text = String(content || '');
const offset = Number(afterOffset) || 0;
return be.readyPattern.test(text.slice(text.length < offset ? 0 : offset));
});
});
}
function detectInstalledBackends() {
return Promise.all([
exists(BACKENDS.dae.initd),
@@ -190,7 +136,5 @@ return baseclass.extend({
detectRunning: detectRunning,
detectBackend: detectBackend,
setActiveBackend: setActiveBackend,
serviceStatus: serviceStatus,
serviceLogOffset: serviceLogOffset,
serviceReady: serviceReady
serviceStatus: serviceStatus
});
@@ -16,7 +16,6 @@ const CSS = [
'.dd-status-row .dd-grow{flex:1 1 auto}',
'.dd-badge{display:inline-flex;align-items:center;gap:5px;padding:2px 10px;border-radius:999px;font-size:10.5px;font-weight:700;letter-spacing:.3px;border:1px solid transparent;line-height:1.3}',
'.dd-badge-run{color:#3da66a;border-color:rgba(61,166,106,.5)}',
'.dd-badge-wait{color:#d6a23d;border-color:rgba(214,162,61,.55)}',
'.dd-badge-stop{color:#d96d6d;border-color:rgba(217,109,109,.55)}',
'.dd-badge-dot{width:6px;height:6px;border-radius:50%;background:currentColor;display:inline-block}',
'.dd-meta{font-size:11.5px;opacity:.7;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace}',
@@ -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);
});
};
@@ -10,8 +10,6 @@
"/etc/dae/example.dae": [ "read" ],
"/var/log/dae/dae.log": [ "read" ],
"/var/log/daed/daed.log": [ "read" ],
"/var/run/daede-dae.ready": [ "read" ],
"/var/run/daede-daed.ready": [ "read" ],
"/usr/share/v2ray/geoip.dat": [ "read" ],
"/usr/share/v2ray/geosite.dat": [ "read" ],
"/sys/kernel/btf/vmlinux": [ "read" ],