mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-15 04:44:24 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec44f6fe2e | ||
|
|
37eb246de8 | ||
|
|
47831f020d | ||
|
|
02766f9a2f |
@@ -178,7 +178,7 @@ jobs:
|
|||||||
git_sparse_clone master https://github.com/fengqi/luci-app-uugamebooster \
|
git_sparse_clone master https://github.com/fengqi/luci-app-uugamebooster \
|
||||||
uugamebooster luci-app-uugamebooster
|
uugamebooster luci-app-uugamebooster
|
||||||
git_sparse_clone master https://github.com/kenzok8/small \
|
git_sparse_clone master https://github.com/kenzok8/small \
|
||||||
clashoo luci-app-clashoo luci-app-daede
|
clashoo luci-app-clashoo
|
||||||
) &
|
) &
|
||||||
(
|
(
|
||||||
git_clone https://github.com/muink/luci-app-dnsproxy
|
git_clone https://github.com/muink/luci-app-dnsproxy
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=dae
|
PKG_NAME:=dae
|
||||||
PKG_VERSION:=2026.08.28
|
PKG_VERSION:=2026.08.28
|
||||||
PKG_RELEASE:=3
|
PKG_RELEASE:=4
|
||||||
|
|
||||||
PKG_SOURCE:=dae-src-2026.08.28-d6aca8a10b35.tar.gz
|
PKG_SOURCE:=dae-src-2026.08.28-d6aca8a10b35.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ start_service() {
|
|||||||
procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray" TZ="$(uci -q get system.@system[0].zonename)"
|
procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray" TZ="$(uci -q get system.@system[0].zonename)"
|
||||||
procd_set_param command "$PROG" run
|
procd_set_param command "$PROG" run
|
||||||
procd_append_param command --config "$config_file"
|
procd_append_param command --config "$config_file"
|
||||||
procd_append_param command --disable-timestamp
|
|
||||||
procd_append_param command --logfile "$LOG_DIR/dae.log"
|
procd_append_param command --logfile "$LOG_DIR/dae.log"
|
||||||
procd_append_param command --logfile-maxbackups "$log_maxbackups"
|
procd_append_param command --logfile-maxbackups "$log_maxbackups"
|
||||||
procd_append_param command --logfile-maxsize "$log_maxsize"
|
procd_append_param command --logfile-maxsize "$log_maxsize"
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=filebrowser-q
|
||||||
|
PKG_VERSION:=1.5.6-stable
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
|
PKG_SOURCE_URL:=https://codeload.github.com/gtsteffaniak/filebrowser/tar.gz/v$(PKG_VERSION)?
|
||||||
|
PKG_HASH:=skip
|
||||||
|
PKG_BUILD_DIR:=$(BUILD_DIR)/filebrowser-$(PKG_VERSION)
|
||||||
|
|
||||||
|
PKG_LICENSE:=Apache-2.0
|
||||||
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
PKG_MAINTAINER:=kiddin9
|
||||||
|
|
||||||
|
PKG_BUILD_DEPENDS:=golang/host node/host
|
||||||
|
PKG_BUILD_PARALLEL:=1
|
||||||
|
PKG_BUILD_FLAGS:=no-mips16
|
||||||
|
|
||||||
|
GO_PKG:=github.com/gtsteffaniak/filebrowser/backend
|
||||||
|
GO_PKG_BUILD_DIR:=$(PKG_BUILD_DIR)/backend
|
||||||
|
GO_PKG_LDFLAGS_X:= \
|
||||||
|
$(GO_PKG)/internal/version.Version=v$(PKG_VERSION) \
|
||||||
|
$(GO_PKG)/internal/version.CommitSHA=$(PKG_VERSION)
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||||
|
|
||||||
|
define Package/filebrowser-q
|
||||||
|
SECTION:=utils
|
||||||
|
CATEGORY:=Utilities
|
||||||
|
SUBMENU:=Filesystem
|
||||||
|
TITLE:=FileBrowser Quantum - Modern Web File Manager
|
||||||
|
URL:=https://github.com/gtsteffaniak/filebrowser
|
||||||
|
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/filebrowser-q/description
|
||||||
|
FileBrowser Quantum provides a modern, responsive web-based file management
|
||||||
|
interface with multi-source, real-time search, and enhanced preview features.
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/filebrowser-q/conffiles
|
||||||
|
/etc/filebrowser-q/
|
||||||
|
/etc/config/filebrowser-q
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Prepare
|
||||||
|
$(call Build/Prepare/Default)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
( \
|
||||||
|
pushd $(PKG_BUILD_DIR)/frontend && \
|
||||||
|
npm install && \
|
||||||
|
npm run build ; \
|
||||||
|
)
|
||||||
|
( \
|
||||||
|
cd $(PKG_BUILD_DIR)/backend && \
|
||||||
|
$(GO_PKG_VARS) \
|
||||||
|
go build \
|
||||||
|
-trimpath \
|
||||||
|
-ldflags="-w -s" \
|
||||||
|
-o $(PKG_BUILD_DIR)/filebrowser . ; \
|
||||||
|
)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/filebrowser-q/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/filebrowser $(1)/usr/bin/filebrowser-q
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(INSTALL_CONF) $(CURDIR)/files/filebrowser.config $(1)/etc/config/filebrowser-q
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) $(CURDIR)/files/filebrowser.init $(1)/etc/init.d/filebrowser-q
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,filebrowser-q))
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
server:
|
||||||
|
port: 8989
|
||||||
|
database: "/etc/filebrowser-q/database.db"
|
||||||
|
sources:
|
||||||
|
- path: "/" # Do not use a root "/" directory or include the "/var" folder
|
||||||
|
config:
|
||||||
|
defaultEnabled: true
|
||||||
|
auth:
|
||||||
|
adminUsername: admin
|
||||||
|
adminPassword: "admin"
|
||||||
|
userDefaults:
|
||||||
|
ui:
|
||||||
|
locale: "zhCN"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
config filebrowser 'config'
|
||||||
|
option enabled '0'
|
||||||
|
option listen_port '8989'
|
||||||
|
option root_path '/'
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
|
START=99
|
||||||
|
STOP=10
|
||||||
|
|
||||||
|
CONF="filebrowser-q"
|
||||||
|
PROG="/usr/bin/filebrowser-q"
|
||||||
|
CONF_PATH="/etc/filebrowser-q/config.yaml"
|
||||||
|
PID_FILE="/var/run/filebrowser-q.pid"
|
||||||
|
|
||||||
|
start() {
|
||||||
|
config_load "$CONF"
|
||||||
|
|
||||||
|
local enabled
|
||||||
|
config_get_bool enabled "config" "enabled" "0"
|
||||||
|
[ "$enabled" -eq "1" ] || return 1
|
||||||
|
|
||||||
|
local listen_port root_path root_name
|
||||||
|
config_get listen_port "config" "listen_port" "8787"
|
||||||
|
config_get root_path "config" "root_path" "/"
|
||||||
|
|
||||||
|
# 路径为 / 时 name 设为 root,否则留空
|
||||||
|
root_name=""
|
||||||
|
[ "$root_path" = "/" ] && root_name="root"
|
||||||
|
|
||||||
|
if [ ! -f "$CONF_PATH" ]; then
|
||||||
|
mkdir -p "$(dirname "$CONF_PATH")"
|
||||||
|
cat <<EOF > "$CONF_PATH"
|
||||||
|
server:
|
||||||
|
port: $listen_port
|
||||||
|
database: "/etc/filebrowser-q/database.db"
|
||||||
|
sources:
|
||||||
|
- path: "$root_path"
|
||||||
|
name: "$root_name"
|
||||||
|
config:
|
||||||
|
defaultEnabled: true
|
||||||
|
auth:
|
||||||
|
adminUsername: "admin"
|
||||||
|
adminPassword: "admin"
|
||||||
|
userDefaults:
|
||||||
|
ui:
|
||||||
|
locale: "zhCN"
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
grep -q " name:" "$CONF_PATH" || sed -i "s,.*- path:.*,&\n name: \"\"," "$CONF_PATH"
|
||||||
|
sed -e "s/ port:.*/ port: $listen_port/" \
|
||||||
|
-e "s, - path:.*, - path: \"$root_path\"," \
|
||||||
|
-e "s/ name: \".*\"/ name: \"$root_name\"/" \
|
||||||
|
-i "$CONF_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting filebrowser..."
|
||||||
|
start-stop-daemon -S -q -b -m -p "$PID_FILE" -x "$PROG" -- -c "$CONF_PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
kill -9 `pidof filebrowser-q | sed "s/$$//g"` 2>/dev/null
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
echo "filebrowser stopped"
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ define KernelPackage/mt7603e
|
|||||||
CATEGORY:=Kernel modules
|
CATEGORY:=Kernel modules
|
||||||
TITLE:=MTK wifi AP driver
|
TITLE:=MTK wifi AP driver
|
||||||
DEPENDS:=@TARGET_ramips
|
DEPENDS:=@TARGET_ramips
|
||||||
|
CONFLICTS:=kmod-mt7603
|
||||||
FILES:=$(PKG_BUILD_DIR)/mt7603_wifi_ap/mt7603e.ko
|
FILES:=$(PKG_BUILD_DIR)/mt7603_wifi_ap/mt7603e.ko
|
||||||
SUBMENU:=Wireless Drivers
|
SUBMENU:=Wireless Drivers
|
||||||
MENU:=1
|
MENU:=1
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ define KernelPackage/mt76x2e
|
|||||||
CATEGORY:=Kernel modules
|
CATEGORY:=Kernel modules
|
||||||
TITLE:=MTK MT76x2e wifi AP driver
|
TITLE:=MTK MT76x2e wifi AP driver
|
||||||
DEPENDS:=@TARGET_ramips
|
DEPENDS:=@TARGET_ramips
|
||||||
|
CONFLICTS:=kmod-mt76x2
|
||||||
FILES:=$(PKG_BUILD_DIR)/mt76x2_ap/mt76x2_ap.ko
|
FILES:=$(PKG_BUILD_DIR)/mt76x2_ap/mt76x2_ap.ko
|
||||||
SUBMENU:=Wireless Drivers
|
SUBMENU:=Wireless Drivers
|
||||||
MENU:=1
|
MENU:=1
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-daede
|
PKG_NAME:=luci-app-daede
|
||||||
PKG_VERSION:=1.14.7
|
PKG_VERSION:=1.14.7
|
||||||
PKG_RELEASE:=24
|
PKG_RELEASE:=25
|
||||||
PKG_MAINTAINER:=kenzok8
|
PKG_MAINTAINER:=kenzok8
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|
||||||
@@ -78,6 +78,11 @@ define Package/$(PKG_NAME)/install
|
|||||||
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/proxy-check.sh $(1)/usr/share/luci-app-daede/proxy-check.sh
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/proxy-check.sh $(1)/usr/share/luci-app-daede/proxy-check.sh
|
||||||
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/fetch-clash-yaml.sh $(1)/usr/share/luci-app-daede/fetch-clash-yaml.sh
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/fetch-clash-yaml.sh $(1)/usr/share/luci-app-daede/fetch-clash-yaml.sh
|
||||||
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-backup.sh $(1)/usr/share/luci-app-daede/config-backup.sh
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-backup.sh $(1)/usr/share/luci-app-daede/config-backup.sh
|
||||||
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/config-defaults.sh $(1)/usr/share/luci-app-daede/config-defaults.sh
|
||||||
|
$(INSTALL_DIR) $(1)/usr/share/luci-app-daede/defaults
|
||||||
|
$(INSTALL_DATA) $(CURDIR)/../dae/files/dae.config $(1)/usr/share/luci-app-daede/defaults/dae
|
||||||
|
$(INSTALL_DATA) $(CURDIR)/../daed/files/daed.config $(1)/usr/share/luci-app-daede/defaults/daed
|
||||||
|
$(INSTALL_DATA) ./root/etc/config/daede $(1)/usr/share/luci-app-daede/defaults/daede
|
||||||
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/refresh-index.sh $(1)/usr/share/luci-app-daede/refresh-index.sh
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/refresh-index.sh $(1)/usr/share/luci-app-daede/refresh-index.sh
|
||||||
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/geo-cron.sh $(1)/usr/share/luci-app-daede/geo-cron.sh
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/geo-cron.sh $(1)/usr/share/luci-app-daede/geo-cron.sh
|
||||||
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/daed-sub-update.sh $(1)/usr/share/luci-app-daede/daed-sub-update.sh
|
$(INSTALL_BIN) ./root/usr/share/luci-app-daede/daed-sub-update.sh $(1)/usr/share/luci-app-daede/daed-sub-update.sh
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
'require ui';
|
'require ui';
|
||||||
'require view';
|
'require view';
|
||||||
'require view.daede.backend as backend';
|
'require view.daede.backend as backend';
|
||||||
|
'require view.daede.styles as styles';
|
||||||
|
|
||||||
const MAX_LINES = 5000;
|
const MAX_LINES = 5000;
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ const CSS = [
|
|||||||
/* 拆字段:time="May 25 07:04:59" level=info msg="..." key=val key="val with space" ... */
|
/* 拆字段:time="May 25 07:04:59" level=info msg="..." key=val key="val with space" ... */
|
||||||
const RE_LINE = /^time="([^"]*)"\s+level=(\w+)\s+msg=(?:"((?:[^"\\]|\\.)*)"|(\S+))\s*(.*)$/;
|
const RE_LINE = /^time="([^"]*)"\s+level=(\w+)\s+msg=(?:"((?:[^"\\]|\\.)*)"|(\S+))\s*(.*)$/;
|
||||||
const RE_PREFIXED_LINE = /^\[([^\]]+)\]\s+(DEBUG|INFO|WARN(?:ING)?|ERROR|FATAL|PANIC)\s*(.*)$/i;
|
const RE_PREFIXED_LINE = /^\[([^\]]+)\]\s+(DEBUG|INFO|WARN(?:ING)?|ERROR|FATAL|PANIC)\s*(.*)$/i;
|
||||||
|
const RE_PLAIN_LEVEL_LINE = /^\s*(DEBUG|INFO|WARN(?:ING)?|ERROR|FATAL|PANIC)\s+(.*)$/i;
|
||||||
|
|
||||||
function detectLevel(line) {
|
function detectLevel(line) {
|
||||||
// daed/dae logs use lvl=info / [INFO] / level=warning style
|
// daed/dae logs use lvl=info / [INFO] / level=warning style
|
||||||
@@ -117,13 +119,21 @@ function parseLine(line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m = line.match(RE_PREFIXED_LINE);
|
m = line.match(RE_PREFIXED_LINE);
|
||||||
|
let ts = '', lvl = '', body = '';
|
||||||
|
if (m) {
|
||||||
|
ts = formatTs(m[1]);
|
||||||
|
lvl = m[2];
|
||||||
|
body = m[3] || '';
|
||||||
|
} else {
|
||||||
|
m = line.match(RE_PLAIN_LEVEL_LINE);
|
||||||
if (!m) return null;
|
if (!m) return null;
|
||||||
|
lvl = m[1];
|
||||||
const body = m[3] || '';
|
body = m[2] || '';
|
||||||
|
}
|
||||||
const kvStart = body.search(/(?:^|\s)(?=[A-Za-z_][\w.-]*=)/);
|
const kvStart = body.search(/(?:^|\s)(?=[A-Za-z_][\w.-]*=)/);
|
||||||
return {
|
return {
|
||||||
ts: formatTs(m[1]),
|
ts: ts,
|
||||||
lvl: m[2],
|
lvl: lvl,
|
||||||
msg: kvStart === -1 ? body : body.slice(0, kvStart).trimEnd(),
|
msg: kvStart === -1 ? body : body.slice(0, kvStart).trimEnd(),
|
||||||
kv: kvStart === -1 ? '' : body.slice(kvStart).trim()
|
kv: kvStart === -1 ? '' : body.slice(kvStart).trim()
|
||||||
};
|
};
|
||||||
@@ -135,11 +145,10 @@ function buildLine(ln) {
|
|||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return E('div', { 'class': 'dd-line ' + cls }, ln);
|
return E('div', { 'class': 'dd-line ' + cls }, ln);
|
||||||
}
|
}
|
||||||
const parts = [
|
const parts = [];
|
||||||
E('span', { 'class': 'dd-ts' }, parsed.ts),
|
if (parsed.ts) parts.push(E('span', { 'class': 'dd-ts' }, parsed.ts));
|
||||||
E('span', { 'class': 'dd-lvl ' + lvlClass(parsed.lvl) }, lvlShort(parsed.lvl)),
|
parts.push(E('span', { 'class': 'dd-lvl ' + lvlClass(parsed.lvl) }, lvlShort(parsed.lvl)));
|
||||||
E('span', { 'class': 'dd-msg' }, parsed.msg)
|
parts.push(E('span', { 'class': 'dd-msg' }, parsed.msg));
|
||||||
];
|
|
||||||
if (parsed.kv) parts.push(E('span', { 'class': 'dd-kv' }, parsed.kv));
|
if (parsed.kv) parts.push(E('span', { 'class': 'dd-kv' }, parsed.kv));
|
||||||
return E('div', { 'class': 'dd-line ' + cls }, parts);
|
return E('div', { 'class': 'dd-line ' + cls }, parts);
|
||||||
}
|
}
|
||||||
@@ -158,7 +167,8 @@ return view.extend({
|
|||||||
paused: false,
|
paused: false,
|
||||||
autoScroll: true,
|
autoScroll: true,
|
||||||
filter: '',
|
filter: '',
|
||||||
userScrolled: false
|
userScrolled: false,
|
||||||
|
reading: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const pane = E('div', { 'class': 'dd-log-pane', 'id': 'dd-log-pane' }, [
|
const pane = E('div', { 'class': 'dd-log-pane', 'id': 'dd-log-pane' }, [
|
||||||
@@ -173,19 +183,29 @@ return view.extend({
|
|||||||
|
|
||||||
const meta = E('span', { 'class': 'dd-log-meta' }, '');
|
const meta = E('span', { 'class': 'dd-log-meta' }, '');
|
||||||
|
|
||||||
const cbAuto = E('input', { 'type': 'checkbox', 'checked': 'checked' });
|
const btnAuto = E('button', { 'type': 'button', 'class': 'dd-log-btn dd-log-toggle', 'id': 'dd-log-auto' });
|
||||||
cbAuto.addEventListener('change', function() {
|
const btnPause = E('button', { 'type': 'button', 'class': 'dd-log-btn dd-log-toggle', 'id': 'dd-log-pause' });
|
||||||
state.autoScroll = cbAuto.checked;
|
const syncControls = function() {
|
||||||
|
btnAuto.textContent = state.autoScroll ? '✓ ' + _('Auto-scroll: On') : '○ ' + _('Auto-scroll: Off');
|
||||||
|
btnAuto.setAttribute('aria-pressed', String(state.autoScroll));
|
||||||
|
btnPause.textContent = state.paused ? '▶ ' + _('Resume') : 'Ⅱ ' + _('Pause');
|
||||||
|
btnPause.setAttribute('aria-pressed', String(state.paused));
|
||||||
|
meta.textContent = state.paused ? _('Paused') : '';
|
||||||
|
};
|
||||||
|
btnAuto.addEventListener('click', function() {
|
||||||
|
state.autoScroll = !state.autoScroll;
|
||||||
if (state.autoScroll) {
|
if (state.autoScroll) {
|
||||||
pane.scrollTop = pane.scrollHeight;
|
pane.scrollTop = pane.scrollHeight;
|
||||||
state.userScrolled = false;
|
state.userScrolled = false;
|
||||||
}
|
}
|
||||||
|
syncControls();
|
||||||
});
|
});
|
||||||
|
btnPause.addEventListener('click', function() {
|
||||||
const cbPause = E('input', { 'type': 'checkbox' });
|
state.paused = !state.paused;
|
||||||
cbPause.addEventListener('change', function() {
|
syncControls();
|
||||||
state.paused = cbPause.checked;
|
if (!state.paused) tick();
|
||||||
});
|
});
|
||||||
|
syncControls();
|
||||||
|
|
||||||
const selFilter = E('select', { 'class': 'dd-log-btn' }, [
|
const selFilter = E('select', { 'class': 'dd-log-btn' }, [
|
||||||
E('option', { 'value': '' }, _('All')),
|
E('option', { 'value': '' }, _('All')),
|
||||||
@@ -287,9 +307,11 @@ return view.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
if (state.paused) return Promise.resolve();
|
if (state.paused || state.reading) return Promise.resolve();
|
||||||
|
state.reading = true;
|
||||||
|
|
||||||
return fs.stat(LOG_PATH).then(function(st) {
|
return fs.stat(LOG_PATH).then(function(st) {
|
||||||
|
if (state.paused) return;
|
||||||
const size = st.size || 0;
|
const size = st.size || 0;
|
||||||
if (size === state.lastSize) {
|
if (size === state.lastSize) {
|
||||||
meta.textContent = '%d bytes · live'.format(size);
|
meta.textContent = '%d bytes · live'.format(size);
|
||||||
@@ -299,6 +321,7 @@ return view.extend({
|
|||||||
// File rotated/truncated → full reload
|
// File rotated/truncated → full reload
|
||||||
const rotated = size < state.lastSize;
|
const rotated = size < state.lastSize;
|
||||||
return fs.read_direct(LOG_PATH, 'text').then(function(content) {
|
return fs.read_direct(LOG_PATH, 'text').then(function(content) {
|
||||||
|
if (state.paused) return;
|
||||||
content = content || '';
|
content = content || '';
|
||||||
let delta;
|
let delta;
|
||||||
if (rotated || state.lastContent === '') {
|
if (rotated || state.lastContent === '') {
|
||||||
@@ -323,6 +346,7 @@ return view.extend({
|
|||||||
pane.scrollTop = pane.scrollHeight;
|
pane.scrollTop = pane.scrollHeight;
|
||||||
});
|
});
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
|
if (state.paused) return;
|
||||||
const msg = String(e);
|
const msg = String(e);
|
||||||
if (msg.indexOf('NotFoundError') !== -1 || msg.indexOf('No such') !== -1)
|
if (msg.indexOf('NotFoundError') !== -1 || msg.indexOf('No such') !== -1)
|
||||||
renderEmpty(_('Log file does not exist yet.'));
|
renderEmpty(_('Log file does not exist yet.'));
|
||||||
@@ -331,15 +355,15 @@ return view.extend({
|
|||||||
state.lastSize = -1;
|
state.lastSize = -1;
|
||||||
state.lastContent = '';
|
state.lastContent = '';
|
||||||
meta.textContent = '';
|
meta.textContent = '';
|
||||||
});
|
}).finally(function() { state.reading = false; });
|
||||||
}
|
}
|
||||||
|
|
||||||
poll.add(tick);
|
poll.add(tick);
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const toolbarItems = [
|
const toolbarItems = [
|
||||||
E('label', {}, [ cbAuto, _('Auto-scroll') ]),
|
btnAuto,
|
||||||
E('label', {}, [ cbPause, _('Pause') ]),
|
btnPause,
|
||||||
selFilter,
|
selFilter,
|
||||||
btnClear,
|
btnClear,
|
||||||
btnDownload,
|
btnDownload,
|
||||||
@@ -350,7 +374,7 @@ return view.extend({
|
|||||||
const toolbar = E('div', { 'class': 'dd-log-toolbar' }, toolbarItems);
|
const toolbar = E('div', { 'class': 'dd-log-toolbar' }, toolbarItems);
|
||||||
|
|
||||||
return E('div', { 'class': 'dd-log-wrap' }, [
|
return E('div', { 'class': 'dd-log-wrap' }, [
|
||||||
E('style', {}, CSS),
|
E('style', {}, CSS + styles.CSS),
|
||||||
E('div', { 'class': 'dd-log-card' }, [
|
E('div', { 'class': 'dd-log-card' }, [
|
||||||
E('h4', { 'class': 'dd-log-card-title' }, _('%s Realtime Log').format(ctx.name)),
|
E('h4', { 'class': 'dd-log-card-title' }, _('%s Realtime Log').format(ctx.name)),
|
||||||
toolbar,
|
toolbar,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
'require baseclass';
|
'require baseclass';
|
||||||
|
|
||||||
const CSS = [
|
const CSS = [
|
||||||
|
'.dd-log-toolbar .dd-log-toggle[aria-pressed="true"]{background:rgba(74,160,101,.16);border-color:#4aa065;box-shadow:inset 0 0 0 1px rgba(74,160,101,.2)}',
|
||||||
|
'.dd-log-toolbar .dd-log-toggle:focus-visible{outline:2px solid #4aa065;outline-offset:2px}',
|
||||||
|
|
||||||
'.dd-wrap{padding:4px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}',
|
'.dd-wrap{padding:4px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}',
|
||||||
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:9px 14px;margin-bottom:7px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
|
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:9px 14px;margin-bottom:7px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
|
||||||
/* padding:0 neutralizes Argon's h4{padding:.75rem 1.25rem}, which otherwise
|
/* padding:0 neutralizes Argon's h4{padding:.75rem 1.25rem}, which otherwise
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
'require ui';
|
'require ui';
|
||||||
'require view';
|
'require view';
|
||||||
'require view.daede.backend as backend';
|
'require view.daede.backend as backend';
|
||||||
|
'require view.daede.daed-session as daedSession';
|
||||||
|
|
||||||
const DATA_PATHS = {
|
const DATA_PATHS = {
|
||||||
geoip: '/usr/share/v2ray/geoip.dat',
|
geoip: '/usr/share/v2ray/geoip.dat',
|
||||||
@@ -53,6 +54,9 @@ const CSS = [
|
|||||||
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
|
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
|
||||||
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
|
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
|
||||||
'.dd-up-btn-primary{border-color:#4aa065;color:#4aa065}',
|
'.dd-up-btn-primary{border-color:#4aa065;color:#4aa065}',
|
||||||
|
'.dd-backup-buttons{display:flex;align-items:center;gap:8px}',
|
||||||
|
'.dd-backup-reset{border-color:rgba(217,109,109,.55);color:#d96d6d}',
|
||||||
|
'@media(max-width:640px){.dd-backup-row{grid-template-columns:24px minmax(0,1fr)}.dd-backup-row .dd-up-meta{grid-column:2;white-space:normal}.dd-backup-row>span:nth-child(4){display:none}.dd-backup-buttons{grid-column:2;flex-wrap:wrap}}',
|
||||||
'.dd-geo-row{display:grid;grid-template-columns:96px 1fr;gap:10px;align-items:center;font-size:12px;padding:6px 0}',
|
'.dd-geo-row{display:grid;grid-template-columns:96px 1fr;gap:10px;align-items:center;font-size:12px;padding:6px 0}',
|
||||||
'.dd-geo-row label{opacity:.75;font-weight:600}',
|
'.dd-geo-row label{opacity:.75;font-weight:600}',
|
||||||
'.dd-geo-row input[type=text],.dd-geo-row select{font-size:12px;padding:4px 8px;border:1px solid rgba(128,128,128,.35);border-radius:5px;background:transparent;color:inherit;width:100%}',
|
'.dd-geo-row input[type=text],.dd-geo-row select{font-size:12px;padding:4px 8px;border:1px solid rgba(128,128,128,.35);border-radius:5px;background:transparent;color:inherit;width:100%}',
|
||||||
@@ -202,65 +206,126 @@ return view.extend({
|
|||||||
return runJob('update-pkg.sh', pkg, btn, '/tmp/luci-app-daede.pkg-' + pkg + '.log');
|
return runJob('update-pkg.sh', pkg, btn, '/tmp/luci-app-daede.pkg-' + pkg + '.log');
|
||||||
};
|
};
|
||||||
|
|
||||||
// === Config backup (export / import the whole daede config) ===
|
// Configuration operations share a backend lock and one UI busy state.
|
||||||
const exportBtn = E('button', { 'class': 'dd-up-btn' }, _('Export'));
|
const backupScript = '/usr/share/luci-app-daede/config-backup.sh';
|
||||||
const importBtn = E('button', { 'class': 'dd-up-btn' }, _('Import'));
|
const exportBtn = E('button', { 'class': 'dd-up-btn', 'type': 'button' }, _('Export'));
|
||||||
|
const importBtn = E('button', { 'class': 'dd-up-btn', 'type': 'button' }, _('Import'));
|
||||||
|
const resetBtn = E('button', { 'class': 'dd-up-btn dd-backup-reset', 'type': 'button' }, _('Restore Defaults'));
|
||||||
const fileInput = E('input', { 'type': 'file', 'accept': '.tar.gz,.gz,application/gzip', 'style': 'display:none' });
|
const fileInput = E('input', { 'type': 'file', 'accept': '.tar.gz,.gz,application/gzip', 'style': 'display:none' });
|
||||||
|
let backupBusy = false;
|
||||||
exportBtn.addEventListener('click', function() {
|
const showBackupError = function(e) {
|
||||||
const orig = exportBtn.textContent;
|
ui.addNotification(null, E('p', {}, e.message || String(e)), 'error');
|
||||||
exportBtn.disabled = true; exportBtn.textContent = '...';
|
};
|
||||||
fs.exec('/usr/share/luci-app-daede/config-backup.sh', ['export']).then(function(res) {
|
const configOperation = function(button, operation) {
|
||||||
if (res.code !== 0 || !res.stdout) {
|
if (backupBusy) return Promise.resolve();
|
||||||
logPane.textContent = String(res.stderr || res.stdout || 'export failed').trim();
|
backupBusy = true;
|
||||||
|
const label = button.textContent;
|
||||||
|
[exportBtn, importBtn, resetBtn].forEach(function(b) { b.disabled = true; });
|
||||||
|
button.textContent = _('Working…');
|
||||||
|
return uci.changes().then(function(changes) {
|
||||||
|
if (['dae', 'daed', 'daede'].some(function(name) { return changes && changes[name] && changes[name].length; }))
|
||||||
|
throw new Error(_('Apply or discard pending daede changes first.'));
|
||||||
|
return operation();
|
||||||
|
}).catch(showBackupError).finally(function() {
|
||||||
|
backupBusy = false;
|
||||||
|
[exportBtn, importBtn, resetBtn].forEach(function(b) { b.disabled = false; });
|
||||||
|
button.textContent = label;
|
||||||
|
fileInput.value = '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const confirmBackup = function(message) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
const answer = function(value) { ui.hideModal(); resolve(value); };
|
||||||
|
ui.showModal(_('Confirm configuration operation'), [
|
||||||
|
E('p', {}, message),
|
||||||
|
E('div', { 'class': 'right' }, [
|
||||||
|
E('button', { 'class': 'cbi-button', 'click': function() { answer(false); } }, _('Cancel')),
|
||||||
|
' ',
|
||||||
|
E('button', { 'class': 'cbi-button cbi-button-negative', 'click': function() { answer(true); } }, _('Continue'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const execBackup = function(args) {
|
||||||
|
return fs.exec(backupScript, args).then(function(res) {
|
||||||
|
if (!res || res.code !== 0)
|
||||||
|
throw new Error(String(res && (res.stderr || res.stdout) || _('Configuration operation failed.')).trim());
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const waitBackup = function() {
|
||||||
|
let tries = 0;
|
||||||
|
const check = function() {
|
||||||
|
return fs.read_direct('/tmp/luci-app-daede.backup.log', 'text').then(function(content) {
|
||||||
|
logPane.textContent = content;
|
||||||
logPane.classList.add('show');
|
logPane.classList.add('show');
|
||||||
return;
|
if (/^✗/m.test(content)) throw new Error(_('Configuration operation failed. See the log below.'));
|
||||||
}
|
if (/^✓/m.test(content)) return;
|
||||||
let bin;
|
if (++tries > 90) throw new Error(_('Operation is still running. Check the log before retrying.'));
|
||||||
try {
|
return new Promise(function(resolve) { setTimeout(resolve, 2000); }).then(check);
|
||||||
bin = atob(res.stdout.trim());
|
});
|
||||||
} catch (e) {
|
};
|
||||||
logPane.textContent = _('Export failed: invalid base64 data from server');
|
return check();
|
||||||
logPane.classList.add('show');
|
};
|
||||||
return;
|
const reloadConfig = function(message) {
|
||||||
}
|
daedSession.clear(window.localStorage);
|
||||||
if (!bin || bin.length === 0) {
|
['dae', 'daed', 'daede'].forEach(function(name) { uci.unload(name); });
|
||||||
logPane.textContent = _('Export failed: empty archive');
|
ui.showModal(_('Configuration restored'), [
|
||||||
logPane.classList.add('show');
|
E('p', {}, message),
|
||||||
return;
|
E('div', { 'class': 'right' }, E('button', {
|
||||||
}
|
'class': 'cbi-button cbi-button-positive', 'click': function() { window.location.reload(); }
|
||||||
const arr = new Uint8Array(bin.length);
|
}, _('Reload Page')))
|
||||||
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
]);
|
||||||
const url = URL.createObjectURL(new Blob([arr], { 'type': 'application/gzip' }));
|
};
|
||||||
|
exportBtn.addEventListener('click', async function() {
|
||||||
|
if (!await confirmBackup(_('Export briefly stops running dae/daed services to back up the database safely, then resumes them. Continue?'))) return;
|
||||||
|
configOperation(exportBtn, function() {
|
||||||
|
return execBackup(['export']).then(function(res) {
|
||||||
|
const bin = atob((res.stdout || '').trim());
|
||||||
|
if (!bin) throw new Error(_('Export failed: empty archive'));
|
||||||
|
const bytes = Uint8Array.from(bin, function(c) { return c.charCodeAt(0); });
|
||||||
|
const url = URL.createObjectURL(new Blob([bytes], { 'type': 'application/gzip' }));
|
||||||
const a = E('a', { 'href': url, 'download': 'daede-config-' + stamp() + '.tar.gz' });
|
const a = E('a', { 'href': url, 'download': 'daede-config-' + stamp() + '.tar.gz' });
|
||||||
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
document.body.appendChild(a); a.click(); a.remove();
|
||||||
setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
|
setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
|
||||||
}).catch(function(e) {
|
|
||||||
logPane.textContent = _('Export failed') + ': ' + (e ? (e.message || String(e)) : _('script not found'));
|
|
||||||
logPane.classList.add('show');
|
|
||||||
}).finally(function() {
|
|
||||||
exportBtn.disabled = false; exportBtn.textContent = orig;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
importBtn.addEventListener('click', function() { fileInput.click(); });
|
importBtn.addEventListener('click', function() { fileInput.click(); });
|
||||||
fileInput.addEventListener('change', function(ev) {
|
fileInput.addEventListener('change', async function(ev) {
|
||||||
const file = ev.target.files && ev.target.files[0];
|
const file = ev.target.files && ev.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (!confirm(_('Import overwrites the current daede config and restarts the backend. Continue?'))) {
|
if (file.size > 184320) {
|
||||||
|
showBackupError(new Error(_('Backup exceeds the 180 KiB limit. Use System Backup for larger files.')));
|
||||||
fileInput.value = ''; return;
|
fileInput.value = ''; return;
|
||||||
}
|
}
|
||||||
|
if (!await confirmBackup(_('Import replaces daede settings, nodes, subscriptions and dashboard data. Services stop first; the saved active backend starts only if enabled in the backup. Continue?'))) {
|
||||||
|
fileInput.value = ''; return;
|
||||||
|
}
|
||||||
|
configOperation(importBtn, function() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onerror = function() { reject(new Error(_('Unable to read backup file.'))); };
|
||||||
const b64 = String(e.target.result).split(',')[1] || '';
|
reader.onload = function() { resolve(String(reader.result).split(',')[1] || ''); };
|
||||||
fs.write('/tmp/daede-import.b64', b64).then(function() {
|
|
||||||
return runJob('config-backup.sh', 'import', importBtn, '/tmp/luci-app-daede.backup.log');
|
|
||||||
}).catch(function(e) {
|
|
||||||
logPane.textContent = _('Import failed') + ': ' + (e ? (e.message || String(e)) : _('unknown error'));
|
|
||||||
logPane.classList.add('show');
|
|
||||||
}).finally(function() { fileInput.value = ''; });
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
}).then(function(b64) {
|
||||||
|
// Per-tab uploads cannot overwrite each other before the backend locks.
|
||||||
|
const token = Array.from(window.crypto.getRandomValues(new Uint8Array(16)), function(n) { return n.toString(16).padStart(2, '0'); }).join('');
|
||||||
|
const upload = '/tmp/daede-import.' + token + '.b64';
|
||||||
|
return fs.write(upload, b64).then(function() { return execBackup(['import', upload]); })
|
||||||
|
.finally(function() { return fs.remove(upload).catch(function() {}); });
|
||||||
|
}).then(waitBackup).then(function() {
|
||||||
|
reloadConfig(_('Backup restored. The active backend follows the saved enabled setting.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
resetBtn.addEventListener('click', async function() {
|
||||||
|
if (!await confirmBackup(_('Restore ALL daede defaults? This clears dae/daed settings, nodes, subscriptions and daed dashboard accounts/data. Export a backup first if needed. Both backends will remain disabled. Installed programs and GeoData are kept.'))) return;
|
||||||
|
configOperation(resetBtn, function() {
|
||||||
|
return execBackup(['reset']).then(waitBackup).then(function() {
|
||||||
|
reloadConfig(_('All defaults restored. Both backends are disabled. Configure your nodes and routing before starting manually.'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const refresh = function() {
|
const refresh = function() {
|
||||||
@@ -508,12 +573,15 @@ return view.extend({
|
|||||||
]),
|
]),
|
||||||
E('div', { 'class': 'dd-card' }, [
|
E('div', { 'class': 'dd-card' }, [
|
||||||
E('h4', { 'class': 'dd-card-title' }, _('Config Backup')),
|
E('h4', { 'class': 'dd-card-title' }, _('Config Backup')),
|
||||||
E('div', { 'class': 'dd-up-row' }, [
|
E('div', { 'class': 'dd-up-row dd-backup-row' }, [
|
||||||
E('span', { 'class': 'dd-up-icon dd-up-new' }, '⤓'),
|
E('span', { 'class': 'dd-up-icon dd-up-new' }, '⇩'),
|
||||||
E('span', { 'class': 'dd-up-name' }, _('dae + daed')),
|
E('span', { 'class': 'dd-up-name' }, _('dae + daed')),
|
||||||
E('span', { 'class': 'dd-up-meta' }, _('Back up / restore the whole daede config (kernels excluded)')),
|
E('span', {
|
||||||
exportBtn,
|
'class': 'dd-up-meta',
|
||||||
importBtn
|
'title': _('Backups contain account and subscription credentials. Store them securely.')
|
||||||
|
}, _('Back up / restore the whole daede config (kernels excluded)')),
|
||||||
|
E('span', {}, ''),
|
||||||
|
E('div', { 'class': 'dd-backup-buttons' }, [exportBtn, importBtn, resetBtn])
|
||||||
]),
|
]),
|
||||||
fileInput
|
fileInput
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -1025,6 +1025,12 @@ msgstr "导出"
|
|||||||
msgid "Import"
|
msgid "Import"
|
||||||
msgstr "导入"
|
msgstr "导入"
|
||||||
|
|
||||||
|
msgid "Restore Defaults"
|
||||||
|
msgstr "恢复默认"
|
||||||
|
|
||||||
|
msgid "Backups contain account and subscription credentials. Store them securely."
|
||||||
|
msgstr "备份中包含账号和订阅凭据,请妥善保管。"
|
||||||
|
|
||||||
msgid "Back up / restore the whole daede config (kernels excluded)"
|
msgid "Back up / restore the whole daede config (kernels excluded)"
|
||||||
msgstr "备份 / 还原整个 daede 配置(不含内核)"
|
msgstr "备份 / 还原整个 daede 配置(不含内核)"
|
||||||
|
|
||||||
@@ -1086,3 +1092,75 @@ msgid ""
|
|||||||
"Uplink. auto = detect by default route; on legacy swconfig or multi-WAN set "
|
"Uplink. auto = detect by default route; on legacy swconfig or multi-WAN set "
|
||||||
"it, e.g. eth0.2."
|
"it, e.g. eth0.2."
|
||||||
msgstr "上行 WAN 接口。auto 按默认路由自动检测;老式 swconfig 交换机或多 WAN 时手动指定,例如 eth0.2。"
|
msgstr "上行 WAN 接口。auto 按默认路由自动检测;老式 swconfig 交换机或多 WAN 时手动指定,例如 eth0.2。"
|
||||||
|
|
||||||
|
msgid "Export Backup"
|
||||||
|
msgstr "导出备份"
|
||||||
|
|
||||||
|
msgid "Import Backup"
|
||||||
|
msgstr "导入还原"
|
||||||
|
|
||||||
|
msgid "Restore All Defaults"
|
||||||
|
msgstr "恢复全部默认配置"
|
||||||
|
|
||||||
|
msgid "Working…"
|
||||||
|
msgstr "正在处理…"
|
||||||
|
|
||||||
|
msgid "Apply or discard pending daede changes first."
|
||||||
|
msgstr "请先应用或放弃尚未保存的 daede 修改。"
|
||||||
|
|
||||||
|
msgid "Configuration operation failed."
|
||||||
|
msgstr "配置操作失败。"
|
||||||
|
|
||||||
|
msgid "Configuration operation failed. See the log below."
|
||||||
|
msgstr "配置操作失败,请查看下方日志。"
|
||||||
|
|
||||||
|
msgid "Operation is still running. Check the log before retrying."
|
||||||
|
msgstr "操作仍在进行,请检查日志后再重试。"
|
||||||
|
|
||||||
|
msgid "Configuration restored"
|
||||||
|
msgstr "配置已恢复"
|
||||||
|
|
||||||
|
msgid "Reload Page"
|
||||||
|
msgstr "刷新页面"
|
||||||
|
|
||||||
|
msgid "Export briefly stops running dae/daed services to back up the database safely, then resumes them. Continue?"
|
||||||
|
msgstr "导出时会短暂停止正在运行的 dae/daed,以保证数据库备份完整,完成后恢复运行。是否继续?"
|
||||||
|
|
||||||
|
msgid "Backup exceeds the 180 KiB limit. Use System Backup for larger files."
|
||||||
|
msgstr "备份文件超过 180 KiB 限制,请使用系统备份功能处理较大的文件。"
|
||||||
|
|
||||||
|
msgid "Import replaces daede settings, nodes, subscriptions and dashboard data. Services stop first; the saved active backend starts only if enabled in the backup. Continue?"
|
||||||
|
msgstr "导入将覆盖 daede 设置、节点、订阅和面板数据。服务会先停止,仅在备份中为启用状态时启动对应后端。是否继续?"
|
||||||
|
|
||||||
|
msgid "Unable to read backup file."
|
||||||
|
msgstr "无法读取备份文件。"
|
||||||
|
|
||||||
|
msgid "Backup restored. The active backend follows the saved enabled setting."
|
||||||
|
msgstr "备份已还原,当前后端按备份中的启用状态运行。"
|
||||||
|
|
||||||
|
msgid "Restore ALL daede defaults? This clears dae/daed settings, nodes, subscriptions and daed dashboard accounts/data. Export a backup first if needed. Both backends will remain disabled. Installed programs and GeoData are kept."
|
||||||
|
msgstr "确定恢复 daede 全部默认配置?此操作会清空 dae/daed 设置、节点、订阅以及 daed 面板账号和数据。需要保留时请先导出备份。完成后两个后端均保持关闭,已安装程序和 GeoData 数据文件保留。"
|
||||||
|
|
||||||
|
msgid "All defaults restored. Both backends are disabled. Configure your nodes and routing before starting manually."
|
||||||
|
msgstr "已恢复全部默认配置,两个后端均已关闭。请重新配置节点和路由后手动开启。"
|
||||||
|
|
||||||
|
msgid "Back up settings, nodes, subscriptions and dashboard data. Backups contain credentials; store them securely. Kernels and GeoData are excluded."
|
||||||
|
msgstr "备份包括设置、节点、订阅和面板数据,不包括内核和 GeoData。备份中含有账号和订阅凭据,请妥善保管。"
|
||||||
|
|
||||||
|
msgid "Auto-scroll: On"
|
||||||
|
msgstr "自动滚动:开启"
|
||||||
|
|
||||||
|
msgid "Auto-scroll: Off"
|
||||||
|
msgstr "自动滚动:关闭"
|
||||||
|
|
||||||
|
msgid "Resume"
|
||||||
|
msgstr "继续"
|
||||||
|
|
||||||
|
msgid "Paused"
|
||||||
|
msgstr "已暂停"
|
||||||
|
|
||||||
|
msgid "Confirm configuration operation"
|
||||||
|
msgstr "确认配置操作"
|
||||||
|
|
||||||
|
msgid "Continue"
|
||||||
|
msgstr "继续"
|
||||||
|
|||||||
@@ -1025,6 +1025,12 @@ msgstr "导出"
|
|||||||
msgid "Import"
|
msgid "Import"
|
||||||
msgstr "导入"
|
msgstr "导入"
|
||||||
|
|
||||||
|
msgid "Restore Defaults"
|
||||||
|
msgstr "恢复默认"
|
||||||
|
|
||||||
|
msgid "Backups contain account and subscription credentials. Store them securely."
|
||||||
|
msgstr "备份中包含账号和订阅凭据,请妥善保管。"
|
||||||
|
|
||||||
msgid "Back up / restore the whole daede config (kernels excluded)"
|
msgid "Back up / restore the whole daede config (kernels excluded)"
|
||||||
msgstr "备份 / 还原整个 daede 配置(不含内核)"
|
msgstr "备份 / 还原整个 daede 配置(不含内核)"
|
||||||
|
|
||||||
@@ -1086,3 +1092,75 @@ msgid ""
|
|||||||
"Uplink. auto = detect by default route; on legacy swconfig or multi-WAN set "
|
"Uplink. auto = detect by default route; on legacy swconfig or multi-WAN set "
|
||||||
"it, e.g. eth0.2."
|
"it, e.g. eth0.2."
|
||||||
msgstr "上行 WAN 接口。auto 按默认路由自动检测;老式 swconfig 交换机或多 WAN 时手动指定,例如 eth0.2。"
|
msgstr "上行 WAN 接口。auto 按默认路由自动检测;老式 swconfig 交换机或多 WAN 时手动指定,例如 eth0.2。"
|
||||||
|
|
||||||
|
msgid "Export Backup"
|
||||||
|
msgstr "导出备份"
|
||||||
|
|
||||||
|
msgid "Import Backup"
|
||||||
|
msgstr "导入还原"
|
||||||
|
|
||||||
|
msgid "Restore All Defaults"
|
||||||
|
msgstr "恢复全部默认配置"
|
||||||
|
|
||||||
|
msgid "Working…"
|
||||||
|
msgstr "正在处理…"
|
||||||
|
|
||||||
|
msgid "Apply or discard pending daede changes first."
|
||||||
|
msgstr "请先应用或放弃尚未保存的 daede 修改。"
|
||||||
|
|
||||||
|
msgid "Configuration operation failed."
|
||||||
|
msgstr "配置操作失败。"
|
||||||
|
|
||||||
|
msgid "Configuration operation failed. See the log below."
|
||||||
|
msgstr "配置操作失败,请查看下方日志。"
|
||||||
|
|
||||||
|
msgid "Operation is still running. Check the log before retrying."
|
||||||
|
msgstr "操作仍在进行,请检查日志后再重试。"
|
||||||
|
|
||||||
|
msgid "Configuration restored"
|
||||||
|
msgstr "配置已恢复"
|
||||||
|
|
||||||
|
msgid "Reload Page"
|
||||||
|
msgstr "刷新页面"
|
||||||
|
|
||||||
|
msgid "Export briefly stops running dae/daed services to back up the database safely, then resumes them. Continue?"
|
||||||
|
msgstr "导出时会短暂停止正在运行的 dae/daed,以保证数据库备份完整,完成后恢复运行。是否继续?"
|
||||||
|
|
||||||
|
msgid "Backup exceeds the 180 KiB limit. Use System Backup for larger files."
|
||||||
|
msgstr "备份文件超过 180 KiB 限制,请使用系统备份功能处理较大的文件。"
|
||||||
|
|
||||||
|
msgid "Import replaces daede settings, nodes, subscriptions and dashboard data. Services stop first; the saved active backend starts only if enabled in the backup. Continue?"
|
||||||
|
msgstr "导入将覆盖 daede 设置、节点、订阅和面板数据。服务会先停止,仅在备份中为启用状态时启动对应后端。是否继续?"
|
||||||
|
|
||||||
|
msgid "Unable to read backup file."
|
||||||
|
msgstr "无法读取备份文件。"
|
||||||
|
|
||||||
|
msgid "Backup restored. The active backend follows the saved enabled setting."
|
||||||
|
msgstr "备份已还原,当前后端按备份中的启用状态运行。"
|
||||||
|
|
||||||
|
msgid "Restore ALL daede defaults? This clears dae/daed settings, nodes, subscriptions and daed dashboard accounts/data. Export a backup first if needed. Both backends will remain disabled. Installed programs and GeoData are kept."
|
||||||
|
msgstr "确定恢复 daede 全部默认配置?此操作会清空 dae/daed 设置、节点、订阅以及 daed 面板账号和数据。需要保留时请先导出备份。完成后两个后端均保持关闭,已安装程序和 GeoData 数据文件保留。"
|
||||||
|
|
||||||
|
msgid "All defaults restored. Both backends are disabled. Configure your nodes and routing before starting manually."
|
||||||
|
msgstr "已恢复全部默认配置,两个后端均已关闭。请重新配置节点和路由后手动开启。"
|
||||||
|
|
||||||
|
msgid "Back up settings, nodes, subscriptions and dashboard data. Backups contain credentials; store them securely. Kernels and GeoData are excluded."
|
||||||
|
msgstr "备份包括设置、节点、订阅和面板数据,不包括内核和 GeoData。备份中含有账号和订阅凭据,请妥善保管。"
|
||||||
|
|
||||||
|
msgid "Auto-scroll: On"
|
||||||
|
msgstr "自动滚动:开启"
|
||||||
|
|
||||||
|
msgid "Auto-scroll: Off"
|
||||||
|
msgstr "自动滚动:关闭"
|
||||||
|
|
||||||
|
msgid "Resume"
|
||||||
|
msgstr "继续"
|
||||||
|
|
||||||
|
msgid "Paused"
|
||||||
|
msgstr "已暂停"
|
||||||
|
|
||||||
|
msgid "Confirm configuration operation"
|
||||||
|
msgstr "确认配置操作"
|
||||||
|
|
||||||
|
msgid "Continue"
|
||||||
|
msgstr "继续"
|
||||||
|
|||||||
@@ -79,27 +79,7 @@ fi
|
|||||||
# Idempotent: only add what's missing. Lives in the `dae` UCI package,
|
# Idempotent: only add what's missing. Lives in the `dae` UCI package,
|
||||||
# consumed by gen-dae-config.sh.
|
# consumed by gen-dae-config.sh.
|
||||||
if [ -f /etc/config/dae ]; then
|
if [ -f /etc/config/dae ]; then
|
||||||
if ! uci -q get dae.config.lan_interface >/dev/null 2>&1; then
|
/usr/share/luci-app-daede/config-defaults.sh
|
||||||
uci -q set dae.config.lan_interface='br-lan'
|
|
||||||
fi
|
|
||||||
if ! uci -q show dae | grep -q "=group$"; then
|
|
||||||
g="$(uci add dae group)"
|
|
||||||
uci -q set "dae.$g.name=proxy"
|
|
||||||
uci -q set "dae.$g.policy=min_moving_avg"
|
|
||||||
fi
|
|
||||||
if ! uci -q get dae.routing >/dev/null 2>&1; then
|
|
||||||
uci -q set dae.routing=routing
|
|
||||||
uci -q set dae.routing.private_direct=1
|
|
||||||
uci -q set dae.routing.cn_direct=1
|
|
||||||
uci -q set dae.routing.block_ads=0
|
|
||||||
uci -q set dae.routing.fallback=proxy
|
|
||||||
fi
|
|
||||||
if ! uci -q get dae.dns >/dev/null 2>&1; then
|
|
||||||
uci -q set dae.dns=dns
|
|
||||||
uci -q set dae.dns.cn_upstream='udp://dns.alidns.com:53'
|
|
||||||
uci -q set dae.dns.fallback_upstream='tcp+udp://dns.google:53'
|
|
||||||
uci -q set dae.dns.response_ttl='0'
|
|
||||||
fi
|
|
||||||
# Migrate legacy group filters to the unified source / name_filter fields,
|
# Migrate legacy group filters to the unified source / name_filter fields,
|
||||||
# then drop the old keys so the form shows real, editable values (the old
|
# then drop the old keys so the form shows real, editable values (the old
|
||||||
# fields used to re-populate the new ones and looked un-deletable).
|
# fields used to re-populate the new ones and looked un-deletable).
|
||||||
|
|||||||
Regular → Executable
+240
-61
@@ -1,71 +1,250 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# config-backup.sh export|import — back up/restore the whole daede config
|
# Whole-plugin backup/import/reset. Only fixed configuration paths are managed.
|
||||||
# (dae + daed + active backend). export: prints base64(tar.gz) to stdout.
|
|
||||||
# import: decodes /tmp/daede-import.b64, validates, restores, restarts backend.
|
|
||||||
set -eu
|
set -eu
|
||||||
|
umask 077
|
||||||
|
|
||||||
ACTION="${1:-}"
|
ACTION="${1:-}"
|
||||||
LOG="/tmp/luci-app-daede.backup.log"
|
UPLOAD="${2:-/tmp/daede-import.b64}"
|
||||||
IMPORT_B64="/tmp/daede-import.b64"
|
LOG=/tmp/luci-app-daede.backup.log
|
||||||
MAX_TAR=184320 # 180 KiB tar.gz -> ~240 KiB base64, under the ubus limit
|
LOCK=/tmp/luci-app-daede.config.lock
|
||||||
|
SHARE=/usr/share/luci-app-daede
|
||||||
# relative paths (no leading /) so tar entries match the extraction whitelist
|
MAX_TAR=184320 # Base64 must fit inside the ubus reply (~240 KiB).
|
||||||
WHITELIST="etc/config/dae etc/config/daed etc/config/daede etc/dae/config.dae etc/daed/wing.db"
|
MAX_RAW=16777216
|
||||||
|
FILES="etc/config/dae etc/config/daed etc/config/daede etc/dae/config.dae etc/daed/wing.db etc/daed/wing.db-wal etc/daed/wing.db-shm"
|
||||||
|
WORK=""
|
||||||
|
SNAPSHOT=0
|
||||||
|
MUTATING=0
|
||||||
|
KEEP=0
|
||||||
|
RESTART=""
|
||||||
|
|
||||||
|
fail() { echo "$*" >&2; exit 1; }
|
||||||
b64enc() { ucode -e 'let f=require("fs"); print(b64enc(f.open(ARGV[0],"r").read("all")));' -- "$1"; }
|
b64enc() { ucode -e 'let f=require("fs"); print(b64enc(f.open(ARGV[0],"r").read("all")));' -- "$1"; }
|
||||||
b64dec() { ucode -e 'let f=require("fs"); let r=b64dec(trim(f.open(ARGV[0],"r").read("all"))); if(!r)exit(1); let o=f.open(ARGV[1],"w"); o.write(r); o.close();' -- "$1" "$2"; }
|
b64dec() { ucode -e 'let f=require("fs"); let r=b64dec(trim(f.open(ARGV[0],"r").read("all"))); if(!r)exit(1); let o=f.open(ARGV[1],"w"); if(!o || o.write(r)!=length(r))exit(1); o.close();' -- "$1" "$2"; }
|
||||||
|
# Do not merge pending CLI UCI deltas into snapshots/defaults.
|
||||||
|
config() { /sbin/uci -c /etc/config -t "$WORK/uci" "$@"; }
|
||||||
|
|
||||||
case "$ACTION" in
|
paths() {
|
||||||
export)
|
printf '%s\n' $FILES
|
||||||
exist=""
|
for sub in "$1"/etc/dae/subscriptions/*.sub; do
|
||||||
for p in $WHITELIST; do [ -e "/$p" ] && exist="$exist $p"; done
|
[ -e "$sub" ] || [ -L "$sub" ] || continue
|
||||||
[ -n "$exist" ] || { echo "no config to back up" >&2; exit 1; }
|
name="${sub##*/}"
|
||||||
tmp="$(mktemp)"
|
case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'unexpected subscription filename' ;; esac
|
||||||
( cd / && tar -czf "$tmp" $exist ) 2>/dev/null || { rm -f "$tmp"; echo "tar failed" >&2; exit 1; }
|
printf 'etc/dae/subscriptions/%s\n' "$name"
|
||||||
if [ "$(wc -c < "$tmp")" -gt "$MAX_TAR" ]; then
|
done
|
||||||
rm -f "$tmp"; echo "config too large; use System Backup instead" >&2; exit 2
|
}
|
||||||
|
check_paths() {
|
||||||
|
for dir in /etc/config /etc/dae /etc/daed /etc/dae/subscriptions; do
|
||||||
|
[ ! -L "$dir" ] || fail "refusing symlink: $dir"
|
||||||
|
done
|
||||||
|
paths / > "$WORK/paths"
|
||||||
|
while IFS= read -r p; do
|
||||||
|
[ ! -L "/$p" ] || fail "refusing symlink: /$p"
|
||||||
|
[ ! -e "/$p" ] || [ -f "/$p" ] || fail "not a regular file: /$p"
|
||||||
|
done < "$WORK/paths"
|
||||||
|
}
|
||||||
|
snapshot() {
|
||||||
|
mkdir -p "$WORK/before"
|
||||||
|
while IFS= read -r p; do
|
||||||
|
[ -f "/$p" ] || continue
|
||||||
|
mkdir -p "$WORK/before/${p%/*}"
|
||||||
|
cp -p "/$p" "$WORK/before/$p"
|
||||||
|
done < "$WORK/paths"
|
||||||
|
SNAPSHOT=1
|
||||||
|
}
|
||||||
|
stop_backends() {
|
||||||
|
for svc in dae daed; do
|
||||||
|
[ -x "/etc/init.d/$svc" ] || continue
|
||||||
|
if /etc/init.d/"$svc" running >/dev/null 2>&1; then RESTART="$RESTART $svc"; fi
|
||||||
|
/etc/init.d/"$svc" stop >/dev/null 2>&1 || fail "failed to stop $svc; configuration unchanged"
|
||||||
|
done
|
||||||
|
# procd termination can be asynchronous. Never copy a live SQLite database.
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
if ! pidof dae daed daed-guard >/dev/null 2>&1; then return; fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
fail 'backend still running; configuration unchanged'
|
||||||
|
}
|
||||||
|
disable_backends() {
|
||||||
|
for svc in dae daed; do
|
||||||
|
if [ -f "/etc/config/$svc" ]; then
|
||||||
|
config -q set "$svc.config=$svc" || return 1
|
||||||
|
config -q set "$svc.config.enabled=0" || return 1
|
||||||
|
config -q commit "$svc" || return 1
|
||||||
fi
|
fi
|
||||||
b64enc "$tmp"
|
if [ -x "/etc/init.d/$svc" ]; then
|
||||||
rm -f "$tmp"
|
/etc/init.d/"$svc" disable >/dev/null 2>&1 || return 1
|
||||||
;;
|
/etc/init.d/"$svc" stop >/dev/null 2>&1 || return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
restore_snapshot() {
|
||||||
|
# Remove files created by the failed operation as well as original files.
|
||||||
|
paths / > "$WORK/current" || return 1
|
||||||
|
while IFS= read -r p; do rm -f "/$p" || return 1; done < "$WORK/current"
|
||||||
|
while IFS= read -r p; do
|
||||||
|
[ -f "$WORK/before/$p" ] || continue
|
||||||
|
mkdir -p "/${p%/*}" || return 1
|
||||||
|
cp -p "$WORK/before/$p" "/$p" || return 1
|
||||||
|
done < "$WORK/paths"
|
||||||
|
}
|
||||||
|
sync_cron() {
|
||||||
|
geo=disable; sub=disable
|
||||||
|
[ "$(config -q get daede.config.geo_auto || :)" != 1 ] || geo=enable
|
||||||
|
[ "$(config -q get daed.config.subscribe_auto_update || :)" != 1 ] || sub=enable
|
||||||
|
"$SHARE/geo-cron.sh" "$geo" >/dev/null 2>&1
|
||||||
|
"$SHARE/daed-sub-cron.sh" "$sub" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
finish() {
|
||||||
|
rc=$?
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
if [ "$rc" -ne 0 ] && [ "$MUTATING" = 1 ]; then
|
||||||
|
disable_backends || true
|
||||||
|
if [ "$SNAPSHOT" = 1 ] && ! restore_snapshot; then
|
||||||
|
KEEP=1
|
||||||
|
echo "rollback failed; root-only recovery files retained at $WORK" >&2
|
||||||
|
else
|
||||||
|
echo 'previous configuration restored; backends remain disabled' >&2
|
||||||
|
fi
|
||||||
|
disable_backends || { KEEP=1; echo 'failed to disable backend; check service state' >&2; }
|
||||||
|
sync_cron || true
|
||||||
|
fi
|
||||||
|
if [ "$ACTION" = export ]; then
|
||||||
|
for svc in $RESTART; do
|
||||||
|
/etc/init.d/"$svc" start >/dev/null 2>&1 || { rc=1; echo "failed to resume $svc" >&2; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ "$ACTION" != export ]; then
|
||||||
|
if [ "$rc" = 0 ]; then echo '✓ 完成'; else echo '✗ 失败'; fi
|
||||||
|
fi
|
||||||
|
[ "$KEEP" = 1 ] || rm -rf "$WORK"
|
||||||
|
rmdir "$LOCK"
|
||||||
|
exit "$rc"
|
||||||
|
}
|
||||||
|
validate_archive() {
|
||||||
|
[ "$(wc -c < "$WORK/upload.b64")" -le 245760 ] || fail 'upload too large'
|
||||||
|
b64dec "$WORK/upload.b64" "$WORK/import.gz" || fail 'invalid base64 upload'
|
||||||
|
[ "$(wc -c < "$WORK/import.gz")" -le "$MAX_TAR" ] || fail 'archive too large'
|
||||||
|
gzip -t "$WORK/import.gz" 2>/dev/null || fail 'invalid gzip archive'
|
||||||
|
gzip -dc "$WORK/import.gz" | head -c 16777217 > "$WORK/import.tar"
|
||||||
|
[ "$(wc -c < "$WORK/import.tar")" -le "$MAX_RAW" ] || fail 'unpacked backup exceeds 16 MiB'
|
||||||
|
tar -tf "$WORK/import.tar" > "$WORK/entries" 2>/dev/null || fail 'invalid tar archive'
|
||||||
|
[ -s "$WORK/entries" ] || fail 'empty archive'
|
||||||
|
# Reject links, directories, devices and duplicate entries before extraction.
|
||||||
|
tar -tvf "$WORK/import.tar" > "$WORK/types" 2>/dev/null || fail 'invalid tar headers'
|
||||||
|
if grep -qv '^-' "$WORK/types"; then fail 'only regular files are allowed'; fi
|
||||||
|
[ -z "$(sort "$WORK/entries" | uniq -d)" ] || fail 'duplicate archive entry'
|
||||||
|
while IFS= read -r p; do
|
||||||
|
case "$p" in
|
||||||
|
etc/config/dae|etc/config/daed|etc/config/daede|etc/dae/config.dae|etc/daed/wing.db|etc/daed/wing.db-wal|etc/daed/wing.db-shm) ;;
|
||||||
|
etc/dae/subscriptions/*.sub)
|
||||||
|
name="${p#etc/dae/subscriptions/}"
|
||||||
|
case "${name%.sub}" in ''|*[!A-Za-z0-9_]*) fail 'invalid subscription path' ;; esac ;;
|
||||||
|
*) fail 'unexpected archive entry' ;;
|
||||||
|
esac
|
||||||
|
done < "$WORK/entries"
|
||||||
|
mkdir -p "$WORK/after"
|
||||||
|
tar -xf "$WORK/import.tar" -C "$WORK/after"
|
||||||
|
found=0
|
||||||
|
for svc in dae daed daede; do
|
||||||
|
[ -f "$WORK/after/etc/config/$svc" ] || continue
|
||||||
|
found=1
|
||||||
|
/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" -q export "$svc" >/dev/null || fail "invalid $svc configuration"
|
||||||
|
done
|
||||||
|
[ "$found" = 1 ] || fail 'backup has no UCI configuration'
|
||||||
|
if [ -f "$WORK/after/etc/daed/wing.db-wal" ]; then
|
||||||
|
[ -f "$WORK/after/etc/daed/wing.db" ] || fail 'database WAL has no database'
|
||||||
|
fi
|
||||||
|
ab="$(/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" -q get daede.config.active_backend || echo dae)"
|
||||||
|
case "$ab" in dae|daed) ;; *) fail 'invalid backend in backup' ;; esac
|
||||||
|
[ -x "/etc/init.d/$ab" ] || fail "backup backend $ab is not installed"
|
||||||
|
}
|
||||||
|
run_action() {
|
||||||
|
trap finish EXIT
|
||||||
|
trap 'exit 1' HUP INT TERM
|
||||||
|
check_paths
|
||||||
|
if [ "$ACTION" = import ]; then validate_archive; fi
|
||||||
|
if [ "$ACTION" = reset ]; then
|
||||||
|
mkdir -p "$WORK/after/etc/config"
|
||||||
|
for svc in dae daed daede; do
|
||||||
|
cp "$SHARE/defaults/$svc" "$WORK/after/etc/config/$svc"
|
||||||
|
done
|
||||||
|
"$SHARE/config-defaults.sh" "$WORK/after/etc/config"
|
||||||
|
if [ ! -x /etc/init.d/daed ]; then
|
||||||
|
/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" set daede.config.active_backend=dae
|
||||||
|
/sbin/uci -c "$WORK/after/etc/config" -t "$WORK/uci" commit daede
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
stop_backends
|
||||||
|
snapshot
|
||||||
|
if [ "$ACTION" = export ]; then
|
||||||
|
paths "$WORK/before" > "$WORK/candidates"
|
||||||
|
: > "$WORK/entries"
|
||||||
|
while IFS= read -r p; do [ ! -f "$WORK/before/$p" ] || echo "$p" >> "$WORK/entries"; done < "$WORK/candidates"
|
||||||
|
[ -s "$WORK/entries" ] || fail 'no configuration to export'
|
||||||
|
tar -czf "$WORK/export.gz" -C "$WORK/before" -T "$WORK/entries"
|
||||||
|
[ "$(wc -c < "$WORK/export.gz")" -le "$MAX_TAR" ] || fail 'config too large; use System Backup instead'
|
||||||
|
b64enc "$WORK/export.gz"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
MUTATING=1
|
||||||
|
disable_backends
|
||||||
|
# Old backups may omit an uninstalled backend. Keep its UCI file, but remove
|
||||||
|
# all managed data first so stale WAL/SHM and local subscriptions cannot leak in.
|
||||||
|
while IFS= read -r p; do
|
||||||
|
case "$p" in etc/config/*) continue ;; esac
|
||||||
|
rm -f "/$p"
|
||||||
|
done < "$WORK/paths"
|
||||||
|
paths "$WORK/after" > "$WORK/replacement"
|
||||||
|
while IFS= read -r p; do
|
||||||
|
[ -f "$WORK/after/$p" ] || continue
|
||||||
|
mkdir -p "/${p%/*}"
|
||||||
|
cp "$WORK/after/$p" "/$p"
|
||||||
|
chmod 600 "/$p"
|
||||||
|
done < "$WORK/replacement"
|
||||||
|
if [ "$ACTION" = reset ]; then
|
||||||
|
disable_backends
|
||||||
|
sync_cron
|
||||||
|
echo 'All defaults restored. Backends are disabled; configure before starting manually.'
|
||||||
|
else
|
||||||
|
# Preserve the imported active backend's enabled flag. The other backend
|
||||||
|
# stays disabled even when the archive was made on a dual-backend router.
|
||||||
|
for svc in dae daed; do
|
||||||
|
[ "$svc" = "$ab" ] || [ ! -f "/etc/config/$svc" ] || { config -q set "$svc.config.enabled=0"; config -q commit "$svc"; }
|
||||||
|
done
|
||||||
|
sync_cron
|
||||||
|
if [ "$(config -q get "$ab.config.enabled" || :)" = 1 ]; then
|
||||||
|
/etc/init.d/"$ab" enable
|
||||||
|
/etc/init.d/"$ab" start
|
||||||
|
/etc/init.d/"$ab" running >/dev/null 2>&1 || fail 'restored backend failed to start'
|
||||||
|
else
|
||||||
|
/etc/init.d/"$ab" disable
|
||||||
|
fi
|
||||||
|
echo 'Backup restored. Active backend follows the saved enabled setting.'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
import)
|
case "$ACTION" in export|import|reset) ;; *) fail "usage: $0 export|import [upload]|reset" ;; esac
|
||||||
[ -f "$IMPORT_B64" ] || { echo "no upload found" >&2; exit 1; }
|
# An atomic lock covers the complete operation, including background execution.
|
||||||
(
|
mkdir "$LOCK" 2>/dev/null || fail 'another configuration operation is running'
|
||||||
exec >"$LOG" 2>&1
|
WORK="$(mktemp -d /tmp/daede-config.XXXXXX)" || { rmdir "$LOCK"; exit 1; }
|
||||||
echo "$(date '+%F %T') begin import"
|
mkdir -p "$WORK/uci"
|
||||||
rc=0
|
if [ "$ACTION" = import ]; then
|
||||||
tmp="$(mktemp)"
|
case "$UPLOAD" in
|
||||||
if ! b64dec "$IMPORT_B64" "$tmp"; then
|
/tmp/daede-import.b64) ;;
|
||||||
echo "decode failed"; rc=1
|
/tmp/daede-import.*.b64)
|
||||||
elif ! gzip -t "$tmp" 2>/dev/null; then
|
token="${UPLOAD#/tmp/daede-import.}"; token="${token%.b64}"
|
||||||
echo "not a valid backup archive"; rc=1
|
case "$token" in ''|*[!a-f0-9]*) rm -rf "$WORK"; rmdir "$LOCK"; fail 'invalid upload path' ;; esac ;;
|
||||||
else
|
*) rm -rf "$WORK"; rmdir "$LOCK"; fail 'invalid upload path' ;;
|
||||||
# reject any entry outside the whitelist (path-traversal guard)
|
esac
|
||||||
bad="$(tar -tzf "$tmp" 2>/dev/null | grep -vxE 'etc/config/dae|etc/config/daed|etc/config/daede|etc/dae/config\.dae|etc/daed/wing\.db' | head -1)"
|
if [ ! -f "$UPLOAD" ] || [ -L "$UPLOAD" ]; then rm -rf "$WORK"; rmdir "$LOCK"; fail 'upload missing or unsafe'; fi
|
||||||
if [ -n "$bad" ]; then
|
mv "$UPLOAD" "$WORK/upload.b64" || { rm -rf "$WORK"; rmdir "$LOCK"; exit 1; }
|
||||||
echo "rejected: unexpected entry '$bad'"; rc=1
|
fi
|
||||||
else
|
if [ "$ACTION" = export ]; then
|
||||||
ab="$(uci -q get daede.config.active_backend || echo dae)"
|
run_action
|
||||||
[ -x "/etc/init.d/$ab" ] && /etc/init.d/"$ab" stop 2>/dev/null || true
|
else
|
||||||
if ( cd / && tar -xzf "$tmp" ); then
|
# Truncate before returning, so the frontend cannot mistake an earlier job
|
||||||
echo "restored config"
|
# for this one. The child owns the lock and all cleanup from here on.
|
||||||
else
|
: > "$LOG"
|
||||||
echo "extract failed"; rc=1
|
(run_action) </dev/null >"$LOG" 2>&1 &
|
||||||
fi
|
|
||||||
ab="$(uci -q get daede.config.active_backend || echo "$ab")"
|
|
||||||
[ -x "/etc/init.d/$ab" ] && { /etc/init.d/"$ab" enabled || /etc/init.d/"$ab" enable; /etc/init.d/"$ab" restart 2>/dev/null || /etc/init.d/"$ab" start 2>/dev/null; } || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
rm -f "$tmp" "$IMPORT_B64"
|
|
||||||
if [ "$rc" = 0 ]; then echo "result: config restored, backend restarted"; else echo "result: import failed"; fi
|
|
||||||
if [ "$rc" = 0 ]; then echo "$(date '+%F %T') ✓ 完成"; else echo "$(date '+%F %T') ✗ 失败 (rc=$rc)"; fi
|
|
||||||
) </dev/null >/dev/null 2>&1 &
|
|
||||||
echo "started in background, see $LOG"
|
echo "started in background, see $LOG"
|
||||||
;;
|
fi
|
||||||
|
|
||||||
*)
|
|
||||||
echo "usage: $0 export|import" >&2
|
|
||||||
exit 64
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Shared installation/reset defaults. Optional directory is an isolated UCI tree.
|
||||||
|
set -eu
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
config_dir="$1"
|
||||||
|
mkdir -p "$config_dir/.uci"
|
||||||
|
uci() { /sbin/uci -c "$config_dir" -t "$config_dir/.uci" "$@"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! uci -q get dae.config.lan_interface >/dev/null 2>&1; then
|
||||||
|
uci -q set dae.config.lan_interface='br-lan'
|
||||||
|
fi
|
||||||
|
if ! uci -q show dae | grep -q "=group$"; then
|
||||||
|
g="$(uci add dae group)"
|
||||||
|
uci -q set "dae.$g.name=proxy"
|
||||||
|
uci -q set "dae.$g.policy=min_moving_avg"
|
||||||
|
fi
|
||||||
|
if ! uci -q get dae.routing >/dev/null 2>&1; then
|
||||||
|
uci -q set dae.routing=routing
|
||||||
|
uci -q set dae.routing.private_direct=1
|
||||||
|
uci -q set dae.routing.cn_direct=1
|
||||||
|
uci -q set dae.routing.block_ads=0
|
||||||
|
uci -q set dae.routing.fallback=proxy
|
||||||
|
fi
|
||||||
|
if ! uci -q get dae.dns >/dev/null 2>&1; then
|
||||||
|
uci -q set dae.dns=dns
|
||||||
|
uci -q set dae.dns.cn_upstream='udp://dns.alidns.com:53'
|
||||||
|
uci -q set dae.dns.fallback_upstream='tcp+udp://dns.google:53'
|
||||||
|
uci -q set dae.dns.response_ttl='0'
|
||||||
|
fi
|
||||||
|
uci -q commit dae
|
||||||
@@ -30,8 +30,7 @@
|
|||||||
"/usr/share/luci-app-daede/pkg-info.sh daed": [ "exec" ],
|
"/usr/share/luci-app-daede/pkg-info.sh daed": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/pkg-info.sh luci-app-daede": [ "exec" ],
|
"/usr/share/luci-app-daede/pkg-info.sh luci-app-daede": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/refresh-index.sh": [ "exec" ],
|
"/usr/share/luci-app-daede/refresh-index.sh": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/proxy-check.sh": [ "exec" ],
|
"/usr/share/luci-app-daede/proxy-check.sh": [ "exec" ]
|
||||||
"/usr/share/luci-app-daede/config-backup.sh export": [ "exec" ]
|
|
||||||
},
|
},
|
||||||
"ubus": {
|
"ubus": {
|
||||||
"service": [ "list" ]
|
"service": [ "list" ]
|
||||||
@@ -83,7 +82,11 @@
|
|||||||
"/usr/share/luci-app-daede/gen-dae-config.sh import": [ "exec" ],
|
"/usr/share/luci-app-daede/gen-dae-config.sh import": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/daed-sub-update.sh": [ "exec" ],
|
"/usr/share/luci-app-daede/daed-sub-update.sh": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/daed-sub-cron.sh enable": [ "exec" ],
|
"/usr/share/luci-app-daede/daed-sub-cron.sh enable": [ "exec" ],
|
||||||
"/usr/share/luci-app-daede/daed-sub-cron.sh disable": [ "exec" ]
|
"/usr/share/luci-app-daede/daed-sub-cron.sh disable": [ "exec" ],
|
||||||
|
"/usr/share/luci-app-daede/config-backup.sh export": [ "exec" ],
|
||||||
|
"/usr/share/luci-app-daede/config-backup.sh reset": [ "exec" ],
|
||||||
|
"/usr/share/luci-app-daede/config-backup.sh import /tmp/daede-import.*.b64": [ "exec" ],
|
||||||
|
"/tmp/daede-import.*.b64": [ "write" ]
|
||||||
},
|
},
|
||||||
"uci": [ "dae", "daed", "daede" ]
|
"uci": [ "dae", "daed", "daede" ]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI app for FileBrowser
|
LUCI_TITLE:=LuCI app for FileBrowser Quantum
|
||||||
LUCI_DEPENDS:=+filebrowser
|
LUCI_DEPENDS:=+filebrowser-q
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
+6
-6
@@ -28,8 +28,8 @@ function renderStatus(isRunning, port) {
|
|||||||
return view.extend({
|
return view.extend({
|
||||||
load: async function () {
|
load: async function () {
|
||||||
const promises = await Promise.all([
|
const promises = await Promise.all([
|
||||||
L.resolveDefault(fs.stat('/var/run/filebrowser.pid'), null),
|
L.resolveDefault(fs.stat('/var/run/filebrowser-q.pid'), null),
|
||||||
uci.load('filebrowser')
|
uci.load('filebrowser-q')
|
||||||
]);
|
]);
|
||||||
const data = {
|
const data = {
|
||||||
isRunning: promises[0],
|
isRunning: promises[0],
|
||||||
@@ -42,15 +42,15 @@ return view.extend({
|
|||||||
let m, s, o;
|
let m, s, o;
|
||||||
let webport = (uci.get(data.conf, 'config', 'listen_port') || '8989');
|
let webport = (uci.get(data.conf, 'config', 'listen_port') || '8989');
|
||||||
|
|
||||||
m = new form.Map('filebrowser', _('FileBrowser'),
|
m = new form.Map('filebrowser-q', _('FileBrowser Quantum'),
|
||||||
_('FileBrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files..') + '<br />'+
|
_('The best free self-hosted web-based file manager.') + '<br />'+
|
||||||
_('Default login username is %s and password is %s.').format('<code>admin</code>', '<code>admin</code>'));
|
_('Default login username is %s and password is %s.').format('<code>admin</code>', '<code>admin</code>'));
|
||||||
|
|
||||||
s = m.section(form.TypedSection);
|
s = m.section(form.TypedSection);
|
||||||
s.anonymous = true;
|
s.anonymous = true;
|
||||||
s.render = function() {
|
s.render = function() {
|
||||||
poll.add(function() {
|
poll.add(function() {
|
||||||
return fs.stat('/var/run/filebrowser.pid').then(function(stat) {
|
return fs.stat('/var/run/filebrowser-q.pid').then(function(stat) {
|
||||||
let view = document.getElementById('service_status');
|
let view = document.getElementById('service_status');
|
||||||
if (view) {
|
if (view) {
|
||||||
view.innerHTML = renderStatus(stat, webport);
|
view.innerHTML = renderStatus(stat, webport);
|
||||||
@@ -76,7 +76,7 @@ return view.extend({
|
|||||||
|
|
||||||
o = s.option(form.Value, 'listen_port', _('Listen port'));
|
o = s.option(form.Value, 'listen_port', _('Listen port'));
|
||||||
o.datatype = 'port';
|
o.datatype = 'port';
|
||||||
o.default = '8989';
|
o.default = '8787';
|
||||||
o.rmempty = false;
|
o.rmempty = false;
|
||||||
|
|
||||||
o = s.option(form.Value, 'root_path', _('Root directory'));
|
o = s.option(form.Value, 'root_path', _('Root directory'));
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: zh-Hans\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
msgid "Collecting data..."
|
||||||
|
msgstr "正在收集数据中..."
|
||||||
|
|
||||||
|
msgid "Default login username is %s and password is %s."
|
||||||
|
msgstr "默认登录用户名为 %s,密码为 %s。"
|
||||||
|
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
|
msgid "Listen port"
|
||||||
|
msgstr "监听端口"
|
||||||
|
|
||||||
|
msgid "NOT RUNNING"
|
||||||
|
msgstr "未运行"
|
||||||
|
|
||||||
|
msgid "Open Web Interface"
|
||||||
|
msgstr "打开 Web 界面"
|
||||||
|
|
||||||
|
msgid "RUNNING"
|
||||||
|
msgstr "运行中"
|
||||||
|
|
||||||
|
msgid "Root directory"
|
||||||
|
msgstr "根目录"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"admin/services/filebrowser-q": {
|
||||||
|
"title": "FileBrowser Quantum",
|
||||||
|
"action": {
|
||||||
|
"order": 30,
|
||||||
|
"type": "view",
|
||||||
|
"path": "filebrowser-q"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"acl": [ "luci-app-filebrowser-q" ],
|
||||||
|
"uci": { "filebrowser-q": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"luci-app-filebrowser-q": {
|
||||||
|
"description": "Grant UCI access for luci-app-filebrowser-q",
|
||||||
|
"read": {
|
||||||
|
"ubus": {
|
||||||
|
"service": [ "list" ]
|
||||||
|
},
|
||||||
|
"uci": [ "filebrowser-q" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "filebrowser-q" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "filebrowser-q",
|
||||||
|
"init": "filebrowser-q"
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:63
|
|
||||||
msgid "Collecting data..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:50
|
|
||||||
msgid "Default login username is %s and password is %s."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:82
|
|
||||||
msgid "Disable Command Runner feature"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:69
|
|
||||||
msgid "Enable"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:48
|
|
||||||
#: applications/luci-app-filebrowser-go/root/usr/share/luci/menu.d/luci-app-filebrowser.json:3
|
|
||||||
msgid "FileBrowser"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:49
|
|
||||||
msgid ""
|
|
||||||
"FileBrowser provides a file managing interface within a specified directory "
|
|
||||||
"and it can be used to upload, delete, preview, rename and edit your files.."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/root/usr/share/rpcd/acl.d/luci-app-filebrowser.json:3
|
|
||||||
msgid "Grant UCI access for luci-app-filebrowser"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:73
|
|
||||||
msgid "Listen port"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
|
|
||||||
msgid "NOT RUNNING"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:30
|
|
||||||
msgid "Open Web Interface"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
|
|
||||||
msgid "RUNNING"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:78
|
|
||||||
msgid "Root directory"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
zh_Hans
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Last-Translator: Automatically generated\n"
|
|
||||||
"Language-Team: none\n"
|
|
||||||
"Language: zh-Hans\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:63
|
|
||||||
msgid "Collecting data..."
|
|
||||||
msgstr "正在收集数据中..."
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:50
|
|
||||||
msgid "Default login username is %s and password is %s."
|
|
||||||
msgstr "默认登录用户名为 %s,密码为 %s。"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:82
|
|
||||||
msgid "Disable Command Runner feature"
|
|
||||||
msgstr "禁用命令执行功能"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:69
|
|
||||||
msgid "Enable"
|
|
||||||
msgstr "启用"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:48
|
|
||||||
#: applications/luci-app-filebrowser-go/root/usr/share/luci/menu.d/luci-app-filebrowser.json:3
|
|
||||||
msgid "FileBrowser"
|
|
||||||
msgstr "FileBrowser"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:49
|
|
||||||
msgid ""
|
|
||||||
"FileBrowser provides a file managing interface within a specified directory "
|
|
||||||
"and it can be used to upload, delete, preview, rename and edit your files.."
|
|
||||||
msgstr ""
|
|
||||||
"FileBrowser 提供指定目录下的文件管理界面,可用于上传、删除、预览、重命名和编"
|
|
||||||
"辑文件。"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/root/usr/share/rpcd/acl.d/luci-app-filebrowser.json:3
|
|
||||||
msgid "Grant UCI access for luci-app-filebrowser"
|
|
||||||
msgstr "授予 luci-app-filebrowser 访问 UCI 配置的权限"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:73
|
|
||||||
msgid "Listen port"
|
|
||||||
msgstr "监听端口"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:33
|
|
||||||
msgid "NOT RUNNING"
|
|
||||||
msgstr "未运行"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:30
|
|
||||||
msgid "Open Web Interface"
|
|
||||||
msgstr "打开 Web 界面"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:31
|
|
||||||
msgid "RUNNING"
|
|
||||||
msgstr "运行中"
|
|
||||||
|
|
||||||
#: applications/luci-app-filebrowser-go/htdocs/luci-static/resources/view/filebrowser.js:78
|
|
||||||
msgid "Root directory"
|
|
||||||
msgstr "根目录"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"admin/services/filebrowser": {
|
|
||||||
"title": "FileBrowser",
|
|
||||||
"action": {
|
|
||||||
"order": 30,
|
|
||||||
"type": "view",
|
|
||||||
"path": "filebrowser"
|
|
||||||
},
|
|
||||||
"depends": {
|
|
||||||
"acl": [ "luci-app-filebrowser" ],
|
|
||||||
"uci": { "filebrowser": true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"luci-app-filebrowser": {
|
|
||||||
"description": "Grant UCI access for luci-app-filebrowser",
|
|
||||||
"read": {
|
|
||||||
"ubus": {
|
|
||||||
"service": [ "list" ]
|
|
||||||
},
|
|
||||||
"uci": [ "filebrowser" ]
|
|
||||||
},
|
|
||||||
"write": {
|
|
||||||
"uci": [ "filebrowser" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"config": "filebrowser",
|
|
||||||
"init": "filebrowser"
|
|
||||||
}
|
|
||||||
@@ -272,9 +272,17 @@ o = s:option(ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
|
|||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
o:value("doh", "DoH")
|
o:value("doh", "DoH")
|
||||||
o:value("udp", "UDP")
|
o:value("udp", "UDP")
|
||||||
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
|
if m.is_js_luci then
|
||||||
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
|
if current_node.type == "sing-box" then
|
||||||
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
|
o:value("tls", "TLS(DoT)")
|
||||||
|
o:value("quic", "QUIC(DoQ)")
|
||||||
|
o:value("http3", "HTTP3(DoH3)")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
|
||||||
|
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
|
||||||
|
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
|
||||||
|
end
|
||||||
o:depends("_show_dns_option", "1")
|
o:depends("_show_dns_option", "1")
|
||||||
|
|
||||||
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
|
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
|
||||||
@@ -382,9 +390,8 @@ for k, v in pairs(nodes_table) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--m:appendTemplate("/acl/options", {section = arg[1]})
|
--[[
|
||||||
|
-- Shunt
|
||||||
-- Shunt Start
|
|
||||||
if current_node.protocol == "_shunt" then
|
if current_node.protocol == "_shunt" then
|
||||||
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
|
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
|
||||||
setfenv(shunt_lua, getfenv(1))(m, s, {
|
setfenv(shunt_lua, getfenv(1))(m, s, {
|
||||||
@@ -396,5 +403,6 @@ if current_node.protocol == "_shunt" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
m:appendTemplate("/acl/shunt", { shunt_list = api.jsonc.stringify(shunt_list), section = s.section })
|
m:appendTemplate("/acl/shunt", { shunt_list = api.jsonc.stringify(shunt_list), section = s.section })
|
||||||
|
]]--
|
||||||
|
|
||||||
return api.return_map(m)
|
return api.return_map(m)
|
||||||
|
|||||||
@@ -201,9 +201,17 @@ o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS P
|
|||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
o:value("doh", "DoH")
|
o:value("doh", "DoH")
|
||||||
o:value("udp", "UDP")
|
o:value("udp", "UDP")
|
||||||
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
|
if m.is_js_luci then
|
||||||
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
|
if current_node.type == "sing-box" then
|
||||||
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
|
o:value("tls", "TLS(DoT)")
|
||||||
|
o:value("quic", "QUIC(DoQ)")
|
||||||
|
o:value("http3", "HTTP3(DoH3)")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
o:value("tls", "TLS(DoT)", { _is_singbox = "1" })
|
||||||
|
o:value("quic", "QUIC(DoQ)", { _is_singbox = "1" })
|
||||||
|
o:value("http3", "HTTP3(DoH3)", { _is_singbox = "1" })
|
||||||
|
end
|
||||||
|
|
||||||
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
|
---- DNS over TCP or UDP or TLS (DoT) or QUIC (DoQ)
|
||||||
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
|
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
|
||||||
@@ -259,11 +267,10 @@ o:value("UseIP")
|
|||||||
o:value("UseIPv4")
|
o:value("UseIPv4")
|
||||||
o:value("UseIPv6")
|
o:value("UseIPv6")
|
||||||
|
|
||||||
if current_node.type == "sing-box" then
|
o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
|
||||||
o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
|
o.datatype = "min(1)"
|
||||||
o.datatype = "min(1)"
|
o.default = "30"
|
||||||
o.default = "30"
|
o:depends("_is_singbox", "1")
|
||||||
end
|
|
||||||
|
|
||||||
o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
|
o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
|
||||||
o.rows = 5
|
o.rows = 5
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
<%
|
|
||||||
local map = self.map
|
|
||||||
local api = map.api
|
|
||||||
local config = map.config
|
|
||||||
local section = self.section
|
|
||||||
-%>
|
|
||||||
<script type="text/javascript">
|
|
||||||
//<![CDATA[
|
|
||||||
function setOption(option, val) {
|
|
||||||
const dom = document.getElementById('cbid.<%=config%>.<%=section%>.' + option);
|
|
||||||
if (dom) {
|
|
||||||
dom.value = val;
|
|
||||||
}
|
|
||||||
const combobox_dom = document.getElementById('cbi.combobox.cbid.<%=config%>.<%=section%>.' + option);
|
|
||||||
if (combobox_dom) {
|
|
||||||
combobox_dom.value = val;
|
|
||||||
//combobox_dom.selectedIndex = val;
|
|
||||||
//combobox_dom.dispatchEvent(new Event("change", { bubbles: true }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
function dom_event() {
|
|
||||||
waitForElementId('cbi.combobox.cbid.<%=config%>.<%=section%>.tcp_no_redir_ports', function(el) {
|
|
||||||
const o_val = el.value;
|
|
||||||
el.addEventListener("change", () => {
|
|
||||||
const udp_no_redir_ports = getOption("<%=config%>", "<%=section%>", "udp_no_redir_ports");
|
|
||||||
if (el.value == "1:65535" && udp_no_redir_ports && udp_no_redir_ports.value == "1:65535") {
|
|
||||||
setOption("tcp_no_redir_ports", "");
|
|
||||||
setOption("udp_no_redir_ports", "");
|
|
||||||
setOption("mode", 0);
|
|
||||||
cbi_d_update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
waitForElementId('cbi.combobox.cbid.<%=config%>.<%=section%>.udp_no_redir_ports', function(el) {
|
|
||||||
const o_val = el.value;
|
|
||||||
el.addEventListener("change", () => {
|
|
||||||
const tcp_no_redir_ports = getOption("<%=config%>", "<%=section%>", "tcp_no_redir_ports");
|
|
||||||
if (el.value == "1:65535" && tcp_no_redir_ports && tcp_no_redir_ports.value == "1:65535") {
|
|
||||||
setOption("tcp_no_redir_ports", "");
|
|
||||||
setOption("udp_no_redir_ports", "");
|
|
||||||
setOption("mode", 0);
|
|
||||||
cbi_d_update();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const ori_cbi_d_update = cbi_d_update;
|
|
||||||
cbi_d_update = function() {
|
|
||||||
ori_cbi_d_update();
|
|
||||||
dom_event();
|
|
||||||
};
|
|
||||||
dom_event();
|
|
||||||
});
|
|
||||||
//]]>
|
|
||||||
</script>
|
|
||||||
@@ -1507,15 +1507,17 @@ table td, .table .td {
|
|||||||
|
|
||||||
//Node list option saving logic
|
//Node list option saving logic
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
function onChange(option, value) {
|
function onChange(option, value, refresh) {
|
||||||
ajax.abortAll();
|
ajax.abortAll();
|
||||||
XHR.get('<%=api.url("save_node_list_opt")%>', {
|
XHR.get('<%=api.url("save_node_list_opt")%>', {
|
||||||
option: option,
|
option: option,
|
||||||
value: value
|
value: value
|
||||||
}, function(x) {
|
}, function(x) {
|
||||||
if (x && x.status == 200) {
|
if (x && x.status == 200) {
|
||||||
|
if (refresh) {
|
||||||
document.getElementById("node_list").innerHTML = "";
|
document.getElementById("node_list").innerHTML = "";
|
||||||
loadNodeList();
|
loadNodeList();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert("<%:Error%>");
|
alert("<%:Error%>");
|
||||||
}
|
}
|
||||||
@@ -1527,7 +1529,7 @@ table td, .table .td {
|
|||||||
el.addEventListener("change", () => {
|
el.addEventListener("change", () => {
|
||||||
el.blur();
|
el.blur();
|
||||||
show_node_info = el.checked ? "1" : "0";
|
show_node_info = el.checked ? "1" : "0";
|
||||||
onChange("show_node_info", show_node_info);
|
onChange("show_node_info", show_node_info, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1535,32 +1537,47 @@ table td, .table .td {
|
|||||||
el.addEventListener("change", () => {
|
el.addEventListener("change", () => {
|
||||||
el.blur();
|
el.blur();
|
||||||
auto_detection_time = el.value;
|
auto_detection_time = el.value;
|
||||||
onChange("auto_detection_time", auto_detection_time);
|
onChange("auto_detection_time", auto_detection_time, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
<% if api.is_js_luci() then -%>
|
||||||
|
waitForElement('div[id*="cbid.<%=appname%>"][id*="url_test_url"]', function(el) {
|
||||||
|
el.addEventListener("cbi-dropdown-change", () => {
|
||||||
|
if (el.value && (!el.new_val || el.new_val != el.value)) {
|
||||||
|
onChange("url_test_url", el.value, false);
|
||||||
|
el.new_val = el.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
<% else -%>
|
||||||
waitForElement('select[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
|
waitForElement('select[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
|
||||||
el.addEventListener("change", () => {
|
el.addEventListener("change", () => {
|
||||||
if (el.value) {
|
if (el.value && (!el.new_val || el.new_val != el.value)) {
|
||||||
onChange("url_test_url", el.value);
|
onChange("url_test_url", el.value, false);
|
||||||
|
el.new_val = el.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
waitForElement('input[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
|
waitForElement('input[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
|
||||||
el.addEventListener("change", () => {
|
el.addEventListener("change", () => {
|
||||||
if (el.value) {
|
if (el.value && (!el.new_val || el.new_val != el.value)) {
|
||||||
onChange("url_test_url", el.value);
|
onChange("url_test_url", el.value, false);
|
||||||
|
el.new_val = el.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
<% end -%>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<% if not api.is_js_luci() then -%>
|
||||||
const ori_cbi_d_update = cbi_d_update;
|
const ori_cbi_d_update = cbi_d_update;
|
||||||
cbi_d_update = function() {
|
cbi_d_update = function() {
|
||||||
ori_cbi_d_update();
|
ori_cbi_d_update();
|
||||||
dom_event();
|
dom_event();
|
||||||
};
|
};
|
||||||
|
<% end -%>
|
||||||
dom_event();
|
dom_event();
|
||||||
|
|
||||||
const links = document.querySelectorAll('a');
|
const links = document.querySelectorAll('a');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
. /usr/share/passwall2/utils.sh
|
. /usr/share/passwall2/utils.sh
|
||||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
|
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
|
||||||
LEASE_FILE="/tmp/dhcp.leases"
|
LEASE_FILE=$(config_t_get dnsmasq leasefile "/tmp/dhcp.leases")
|
||||||
HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
|
HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
|
||||||
TMP_FILE="/tmp/dhcp-hosts.tmp"
|
TMP_FILE="/tmp/dhcp-hosts.tmp"
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ fi
|
|||||||
|
|
||||||
reload_dnsmasq_pids() {
|
reload_dnsmasq_pids() {
|
||||||
local pidfile pid
|
local pidfile pid
|
||||||
find $TMP_PATH/acl -type f -name 'dnsmasq.pid' 2>/dev/null | while read pidfile; do
|
find $TMP_PATH/acl -type f -name '*_dnsmasq.pid' 2>/dev/null | while read pidfile; do
|
||||||
if [ -s "$pidfile" ]; then
|
if [ -s "$pidfile" ]; then
|
||||||
read pid < "$pidfile"
|
read pid < "$pidfile"
|
||||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_NAME:=luci-app-ssr-plus
|
PKG_NAME:=luci-app-ssr-plus
|
||||||
PKG_VERSION:=196
|
PKG_VERSION:=196
|
||||||
PKG_RELEASE:=7
|
PKG_RELEASE:=8
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||||
|
|||||||
Submodule
+1
Submodule luci-app-syncthing added at b7a197a93c
@@ -1,22 +0,0 @@
|
|||||||
# Copyright (C) 2020 Gyj1109
|
|
||||||
# 适配 OpenWrt 25.12 修改版
|
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
|
||||||
|
|
||||||
PKG_NAME:=luci-app-syncthing
|
|
||||||
PKG_VERSION:=1.0
|
|
||||||
PKG_RELEASE:=3
|
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI support for Syncthing
|
|
||||||
LUCI_PKGARCH:=all
|
|
||||||
# 适配 25.12:确保安装了 syncthing 后,LuCI 界面能正确拉起
|
|
||||||
LUCI_DEPENDS:=+syncthing
|
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
|
||||||
|
|
||||||
# 明确定义配置文件的归属,这对 25.12 的 apk 管理器非常重要
|
|
||||||
define Package/$(PKG_NAME)/conffiles
|
|
||||||
/etc/config/syncthing
|
|
||||||
endef
|
|
||||||
|
|
||||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# luci-app-syncthing
|
|
||||||
在Potat0000源码基础上进行了汉化优化,官方openwrt23.05.3编译后可使用
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
module("luci.controller.syncthing", package.seeall)
|
|
||||||
|
|
||||||
function index()
|
|
||||||
if not nixio.fs.access("/etc/config/syncthing") then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
entry({"admin", "services", "syncthing"}, cbi("syncthing"), _("文件同步"), 10).dependent = true
|
|
||||||
entry({"admin", "services", "syncthing", "status"}, call("act_status")).leaf = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function act_status()
|
|
||||||
local e = {}
|
|
||||||
e.running = luci.sys.call("pgrep syncthing >/dev/null") == 0
|
|
||||||
luci.http.prepare_content("application/json")
|
|
||||||
luci.http.write_json(e)
|
|
||||||
end
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
require("nixio.fs")
|
|
||||||
|
|
||||||
m = Map("syncthing", translate("Syncthing同步工具"))
|
|
||||||
|
|
||||||
m:section(SimpleSection).template = "syncthing/syncthing_status"
|
|
||||||
|
|
||||||
s = m:section(TypedSection, "syncthing")
|
|
||||||
|
|
||||||
s.anonymous = true
|
|
||||||
|
|
||||||
o = s:option(Flag, "enabled", translate("启用"))
|
|
||||||
o.default = 0
|
|
||||||
o.rmempty = false
|
|
||||||
|
|
||||||
gui_address = s:option(Value, "gui_address", translate("GUI访问地址"))
|
|
||||||
gui_address.description = translate("使用0.0.0.0以监控所有访问。")
|
|
||||||
gui_address.default = "http://0.0.0.0:8384"
|
|
||||||
gui_address.placeholder = "http://0.0.0.0:8384"
|
|
||||||
gui_address.rmempty = false
|
|
||||||
|
|
||||||
home = s:option(Value, "home", translate("配置文件目录"))
|
|
||||||
home.description = translate("只有保存在/etc/syncthing中的配置会自动备份!")
|
|
||||||
home.default = "/etc/syncthing"
|
|
||||||
home.placeholder = "/etc/syncthing"
|
|
||||||
home.rmempty = false
|
|
||||||
|
|
||||||
user = s:option(ListValue, "user", translate("用户"))
|
|
||||||
user.description = translate("默认是syncthing,但这可能会导致权限被拒绝。Syncthing官方不建议以root身份运行。")
|
|
||||||
user:value("", translate("syncthing"))
|
|
||||||
for u in luci.util.execi("cat /etc/passwd | cut -d ':' -f1") do
|
|
||||||
user:value(u)
|
|
||||||
end
|
|
||||||
|
|
||||||
macprocs = s:option(Value, "macprocs", translate("线程限制"))
|
|
||||||
macprocs.description = translate("0表示匹配CPU数量(默认),>0表示显式指定并发数。")
|
|
||||||
macprocs.default = "0"
|
|
||||||
macprocs.placeholder = "0"
|
|
||||||
macprocs.datatype = "range(0,32)"
|
|
||||||
macprocs.rmempty = false
|
|
||||||
|
|
||||||
nice = s:option(Value, "nice", translate("优先级"))
|
|
||||||
nice.description = translate("显式指定优先级值。0是最高,19是最低。(暂时不允许设置负值)")
|
|
||||||
nice.default = "19"
|
|
||||||
nice.placeholder = "19"
|
|
||||||
nice.datatype = "range(0,19)"
|
|
||||||
nice.rmempty = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script type="text/javascript">//<![CDATA[
|
|
||||||
XHR.poll(1, '<%=url([[admin]], [[services]], [[syncthing]], [[status]])%>', null,
|
|
||||||
function (x, data) {
|
|
||||||
var tb = document.getElementById('syncthing_status');
|
|
||||||
if (data && tb) {
|
|
||||||
if (data.running) {
|
|
||||||
var links = '<em><b><font color="green">Syncthing <%:运行中%></font></b></em><input class="btn cbi-button mar-10" type="button" value="<%:打开Syncthing页面%>" onclick="openwebui();" />';
|
|
||||||
tb.innerHTML = links;
|
|
||||||
} else {
|
|
||||||
tb.innerHTML = '<em><b><font color="red">Syncthing <%:未运行%></font></b></em>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function openwebui(){
|
|
||||||
var url = window.location.host+":<%=luci.sys.exec("uci -q get syncthing.syncthing.gui_address"):match(":[0-9]+"):gsub(":", "")%>";
|
|
||||||
window.open('http://'+url,'target','');
|
|
||||||
}
|
|
||||||
//]]>
|
|
||||||
</script>
|
|
||||||
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
|
|
||||||
<fieldset class="cbi-section">
|
|
||||||
<p id="syncthing_status">
|
|
||||||
<em><%:正在收集数据...%></em>
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
touch /etc/config/syncthing
|
|
||||||
|
|
||||||
uci -q batch <<-EOF >/dev/null
|
|
||||||
delete ucitrack.@syncthing[-1]
|
|
||||||
add ucitrack syncthing
|
|
||||||
set ucitrack.@syncthing[-1].exec='/etc/init.d/syncthing stop && /etc/init.d/syncthing start'
|
|
||||||
commit ucitrack
|
|
||||||
EOF
|
|
||||||
# remove LuCI cache
|
|
||||||
rm -f /tmp/luci*
|
|
||||||
exit 0
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"luci-app-syncthing": {
|
|
||||||
"description": "Grant UCI access for luci-app-syncthing",
|
|
||||||
"read": {
|
|
||||||
"uci": [ "syncthing" ]
|
|
||||||
},
|
|
||||||
"write": {
|
|
||||||
"uci": [ "syncthing" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1767,6 +1767,9 @@ static int __init fast_classifier_init(void)
|
|||||||
printk(KERN_ALERT "fast-classifier: starting up\n");
|
printk(KERN_ALERT "fast-classifier: starting up\n");
|
||||||
DEBUG_INFO("SFE CM init\n");
|
DEBUG_INFO("SFE CM init\n");
|
||||||
|
|
||||||
|
/* Initialize state before registering callbacks that can use it. */
|
||||||
|
spin_lock_init(&sc->lock);
|
||||||
|
|
||||||
hash_init(fc_conn_ht);
|
hash_init(fc_conn_ht);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1882,8 +1885,6 @@ static int __init fast_classifier_init(void)
|
|||||||
|
|
||||||
printk(KERN_ALERT "fast-classifier: registered\n");
|
printk(KERN_ALERT "fast-classifier: registered\n");
|
||||||
|
|
||||||
spin_lock_init(&sc->lock);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hook the receive path in the network stack.
|
* Hook the receive path in the network stack.
|
||||||
*/
|
*/
|
||||||
@@ -2005,4 +2006,3 @@ module_exit(fast_classifier_exit)
|
|||||||
|
|
||||||
MODULE_DESCRIPTION("Shortcut Forwarding Engine - Connection Manager");
|
MODULE_DESCRIPTION("Shortcut Forwarding Engine - Connection Manager");
|
||||||
MODULE_LICENSE("Dual BSD/GPL");
|
MODULE_LICENSE("Dual BSD/GPL");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user