mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-14 20:34:11 +08:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b5d12b8ec | ||
|
|
51ed2622f8 |
@@ -1,14 +1,14 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
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"
|
||||
DB_PATH="/etc/filebrowser-q/database.db"
|
||||
|
||||
start() {
|
||||
start_service() {
|
||||
config_load "$CONF"
|
||||
|
||||
local enabled
|
||||
@@ -19,7 +19,6 @@ start() {
|
||||
config_get listen_port "config" "listen_port" "8787"
|
||||
config_get root_path "config" "root_path" "/"
|
||||
|
||||
# 路径为 / 时 name 设为 root,否则留空
|
||||
root_name=""
|
||||
[ "$root_path" = "/" ] && root_name="root"
|
||||
|
||||
@@ -28,7 +27,7 @@ start() {
|
||||
cat <<EOF > "$CONF_PATH"
|
||||
server:
|
||||
port: $listen_port
|
||||
database: "/etc/filebrowser-q/database.db"
|
||||
database: "$DB_PATH"
|
||||
sources:
|
||||
- path: "$root_path"
|
||||
name: "$root_name"
|
||||
@@ -49,12 +48,20 @@ EOF
|
||||
-i "$CONF_PATH"
|
||||
fi
|
||||
|
||||
echo "Starting filebrowser..."
|
||||
start-stop-daemon -S -q -b -m -p "$PID_FILE" -x "$PROG" -- -c "$CONF_PATH"
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG"
|
||||
procd_append_param command -c "$CONF_PATH"
|
||||
|
||||
procd_set_param limits core="unlimited"
|
||||
procd_set_param limits nofile="1000000 1000000"
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param respawn
|
||||
|
||||
procd_close_instance
|
||||
|
||||
}
|
||||
|
||||
stop() {
|
||||
kill -9 `pidof filebrowser-q | sed "s/$$//g"` 2>/dev/null
|
||||
rm -f "$PID_FILE"
|
||||
echo "filebrowser stopped"
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$CONF"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
|
||||
|
||||
PKG_MAINTAINER:=timsaya
|
||||
|
||||
PKG_VERSION:=0.12.10
|
||||
PKG_VERSION:=0.12.11
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
@@ -470,6 +470,26 @@ var callSetDefaultRateLimit = rpc.declare({
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetTrafficQuotas = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'getTrafficQuotas',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callSetTrafficQuota = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'setTrafficQuota',
|
||||
params: ['mac', 'minute_bytes', 'hourly_bytes', 'daily_bytes', 'weekly_bytes', 'monthly_bytes', 'total_bytes'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callDeleteTrafficQuota = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'deleteTrafficQuota',
|
||||
params: ['mac'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
@@ -1903,6 +1923,74 @@ return view.extend({
|
||||
.schedule-rules-info {
|
||||
}
|
||||
|
||||
.traffic-quota-section {
|
||||
margin-top: 18px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid ${scheme === 'dark' ? 'rgba(255,255,255,0.16)' : 'rgba(0,0,0,0.12)'};
|
||||
}
|
||||
.traffic-quota-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.traffic-quota-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.traffic-quota-input-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.traffic-quota-input-row .form-input { min-width: 0; flex: 1; }
|
||||
.traffic-quota-input-row .cbi-input-select { width: 82px; flex-shrink: 0; }
|
||||
.traffic-quota-usage {
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
background: ${scheme === 'dark' ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'};
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.traffic-quota-usage-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.traffic-quota-status {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
background: #10b981;
|
||||
}
|
||||
.traffic-quota-status.blocked { background: #ef4444; }
|
||||
.traffic-quota-status.unconfigured { background: #6b7280; }
|
||||
.traffic-quota-cell {
|
||||
margin-top: 6px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px dashed ${scheme === 'dark' ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.14)'};
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.traffic-quota-blocked-badge {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
color: #fff;
|
||||
background: #ef4444;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.traffic-quota-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* 统计区域样式 */
|
||||
.traffic-stats-container {
|
||||
display: flex;
|
||||
@@ -2904,7 +2992,7 @@ return view.extend({
|
||||
E('th', {}, _('Device Info')),
|
||||
E('th', {}, _('LAN Traffic')),
|
||||
E('th', {}, _('WAN Traffic')),
|
||||
E('th', {}, _('Rate Limit')),
|
||||
E('th', {}, _('Rules / Quota')),
|
||||
E('th', {}, _('Actions'))
|
||||
])
|
||||
]),
|
||||
@@ -3171,7 +3259,30 @@ return view.extend({
|
||||
});
|
||||
}
|
||||
|
||||
// 创建限速设置模态框
|
||||
function createQuotaInput(period, label) {
|
||||
return E('div', { 'class': 'form-group', 'style': 'margin-bottom: 0;' }, [
|
||||
E('label', { 'class': 'form-label' }, label),
|
||||
E('div', { 'class': 'traffic-quota-input-row' }, [
|
||||
E('input', {
|
||||
'type': 'number',
|
||||
'min': '0',
|
||||
'step': '0.01',
|
||||
'class': 'form-input',
|
||||
'id': 'traffic-quota-' + period + '-value',
|
||||
'placeholder': '0'
|
||||
}),
|
||||
E('select', { 'class': 'cbi-input-select', 'id': 'traffic-quota-' + period + '-unit' }, [
|
||||
E('option', { 'value': '1' }, 'B'),
|
||||
E('option', { 'value': '1024' }, 'KB'),
|
||||
E('option', { 'value': '1048576' }, 'MB'),
|
||||
E('option', { 'value': '1073741824' }, 'GB'),
|
||||
E('option', { 'value': '1099511627776' }, 'TB')
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
// 创建设备设置模态框
|
||||
var bandixModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'rate-limit-bandix-modal' }, [
|
||||
E('div', { 'class': 'modal-content bandix-modal' }, [
|
||||
E('div', { 'class': 'bandix-modal-body', }, [
|
||||
@@ -3211,6 +3322,46 @@ return view.extend({
|
||||
|
||||
document.body.appendChild(bandixModal);
|
||||
|
||||
// 创建独立的流量配额模态框
|
||||
var trafficQuotaModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'traffic-quota-bandix-modal' }, [
|
||||
E('div', { 'class': 'modal-content bandix-modal' }, [
|
||||
E('div', { 'class': 'bandix-modal-header' }, [
|
||||
E('h3', { 'class': 'bandix-modal-title' }, _('Traffic Quota'))
|
||||
]),
|
||||
E('div', { 'class': 'bandix-modal-body' }, [
|
||||
E('div', { 'class': 'device-summary', 'id': 'traffic-quota-device-summary' }),
|
||||
E('div', { 'class': 'traffic-quota-section', 'style': 'margin-top: 0; padding-top: 0; border-top: none;' }, [
|
||||
E('div', { 'class': 'traffic-quota-header' }, [
|
||||
E('div', { 'style': 'font-weight: 600;' }, _('Quota Status')),
|
||||
E('span', { 'class': 'traffic-quota-status', 'id': 'traffic-quota-status' }, _('Not configured'))
|
||||
]),
|
||||
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7;' },
|
||||
_('WAN upload and download are counted together. Enter 0 for unlimited.')),
|
||||
E('div', { 'class': 'traffic-quota-grid' }, [
|
||||
createQuotaInput('minute', _('Minute Quota')),
|
||||
createQuotaInput('hourly', _('Hourly Quota')),
|
||||
createQuotaInput('daily', _('Daily Quota')),
|
||||
createQuotaInput('weekly', _('Weekly Quota')),
|
||||
createQuotaInput('monthly', _('Monthly Quota')),
|
||||
createQuotaInput('total', _('Lifetime Quota'))
|
||||
]),
|
||||
E('div', { 'class': 'traffic-quota-usage', 'id': 'traffic-quota-usage' }, [
|
||||
E('div', { 'style': 'opacity: 0.7;' }, _('No quota usage data'))
|
||||
]),
|
||||
E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px;' }, [
|
||||
E('button', { 'class': 'cbi-button cbi-button-negative', 'id': 'traffic-quota-delete-btn', 'style': 'display: none;' }, _('Delete Quota')),
|
||||
E('button', { 'class': 'cbi-button cbi-button-positive', 'id': 'traffic-quota-save-btn' }, _('Save Quota'))
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'bandix-modal-footer' }, [
|
||||
E('button', { 'class': 'cbi-button cbi-button-reset', 'id': 'traffic-quota-modal-close' }, _('Close'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
document.body.appendChild(trafficQuotaModal);
|
||||
|
||||
// 创建添加规则模态框
|
||||
var addRuleModal = E('div', { 'class': 'bandix-modal-overlay', 'id': 'add-rule-bandix-modal' }, [
|
||||
E('div', { 'class': 'modal-content bandix-modal' }, [
|
||||
@@ -3946,9 +4097,17 @@ return view.extend({
|
||||
|
||||
// 模态框事件处理
|
||||
var currentDevice = null;
|
||||
var currentQuotaDevice = null;
|
||||
var quotaFormDirty = false;
|
||||
var editingRule = null;
|
||||
var showRateLimitModal;
|
||||
|
||||
trafficQuotaModal.querySelectorAll('.traffic-quota-input-row input, .traffic-quota-input-row select').forEach(function (input) {
|
||||
input.addEventListener(input.tagName === 'SELECT' ? 'change' : 'input', function () {
|
||||
quotaFormDirty = true;
|
||||
});
|
||||
});
|
||||
|
||||
// 显示模态框
|
||||
showRateLimitModal = function (device) {
|
||||
currentDevice = device;
|
||||
@@ -3994,6 +4153,33 @@ return view.extend({
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function showTrafficQuotaModal(device) {
|
||||
currentQuotaDevice = device;
|
||||
quotaFormDirty = false;
|
||||
var modal = document.getElementById('traffic-quota-bandix-modal');
|
||||
var deviceSummary = document.getElementById('traffic-quota-device-summary');
|
||||
var modalContent = [
|
||||
E('div', { 'class': 'device-summary-name' }, device.host || device.ip4),
|
||||
E('div', { 'class': 'device-summary-details' }, device.ip4 + ' (' + device.mac + ')')
|
||||
];
|
||||
var modalBadges = buildDeviceUplinkChBadges(device);
|
||||
if (modalBadges.length) {
|
||||
modalContent.push(E('div', { 'class': 'device-summary-badges device-uplink-badges-wrap' }, modalBadges));
|
||||
}
|
||||
deviceSummary.innerHTML = E('div', {}, modalContent).innerHTML;
|
||||
renderTrafficQuotaForm(getTrafficQuotaForDevice(device.mac), false);
|
||||
modal.classList.add('show');
|
||||
loadTrafficQuotaForm();
|
||||
}
|
||||
|
||||
function hideTrafficQuotaModal() {
|
||||
document.getElementById('traffic-quota-bandix-modal').classList.remove('show');
|
||||
setTimeout(function () {
|
||||
currentQuotaDevice = null;
|
||||
quotaFormDirty = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 加载定时限速规则列表
|
||||
function loadScheduleRules() {
|
||||
if (!currentDevice) return;
|
||||
@@ -4163,8 +4349,60 @@ return view.extend({
|
||||
// 绑定 hostname 保存按钮事件
|
||||
document.getElementById('hostname-save-btn').addEventListener('click', saveHostname);
|
||||
|
||||
document.getElementById('traffic-quota-save-btn').addEventListener('click', function () {
|
||||
if (!currentQuotaDevice) return;
|
||||
var mac = currentQuotaDevice.mac;
|
||||
var values = ['minute', 'hourly', 'daily', 'weekly', 'monthly', 'total'].map(readQuotaInput);
|
||||
if (values.some(function (value) { return value === null; })) {
|
||||
ui.addNotification(null, E('p', {}, _('Quota values must be non-negative and within the supported range.')), 'error');
|
||||
return;
|
||||
}
|
||||
var button = this;
|
||||
button.disabled = true;
|
||||
setTrafficQuotaFormDisabled(true);
|
||||
callSetTrafficQuota(mac, values[0], values[1], values[2], values[3], values[4], values[5]).then(function (result) {
|
||||
if (result && result.success === false) throw new Error(result.error || _('Failed to save traffic quota'));
|
||||
return fetchAllTrafficQuotas(true);
|
||||
}).then(function () {
|
||||
if (currentQuotaDevice && currentQuotaDevice.mac === mac) {
|
||||
quotaFormDirty = false;
|
||||
renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), false);
|
||||
}
|
||||
updateDeviceData();
|
||||
}).catch(function (error) {
|
||||
ui.addNotification(null, E('p', {}, _('Failed to save traffic quota') + ': ' + (error.message || error)), 'error');
|
||||
}).finally(function () {
|
||||
button.disabled = false;
|
||||
setTrafficQuotaFormDisabled(false);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('traffic-quota-delete-btn').addEventListener('click', function () {
|
||||
if (!currentQuotaDevice) return;
|
||||
var mac = currentQuotaDevice.mac;
|
||||
showConfirmDialog(
|
||||
_('Delete Traffic Quota'),
|
||||
_('Delete this quota and all of its accumulated usage?'),
|
||||
function () {
|
||||
callDeleteTrafficQuota(mac).then(function (result) {
|
||||
if (result && result.success === false) throw new Error(result.error || _('Failed to delete traffic quota'));
|
||||
return fetchAllTrafficQuotas(true);
|
||||
}).then(function () {
|
||||
if (currentQuotaDevice && currentQuotaDevice.mac === mac) {
|
||||
quotaFormDirty = false;
|
||||
renderTrafficQuotaForm(null, false);
|
||||
}
|
||||
updateDeviceData();
|
||||
}).catch(function (error) {
|
||||
ui.addNotification(null, E('p', {}, _('Failed to delete traffic quota') + ': ' + (error.message || error)), 'error');
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// 绑定关闭按钮事件
|
||||
document.getElementById('bandix-modal-close').addEventListener('click', hideRateLimitModal);
|
||||
document.getElementById('traffic-quota-modal-close').addEventListener('click', hideTrafficQuotaModal);
|
||||
|
||||
// 历史趋势:状态与工具
|
||||
var latestDevices = [];
|
||||
@@ -4175,6 +4413,154 @@ return view.extend({
|
||||
var allScheduleRules = []; // 存储所有设备的定时限速规则
|
||||
var isScheduleRulesLoading = false; // 防止轮询重入
|
||||
|
||||
var allTrafficQuotas = [];
|
||||
var trafficQuotasRequest = null;
|
||||
var trafficQuotasLoadError = null;
|
||||
|
||||
function fetchAllTrafficQuotas(forceRefresh) {
|
||||
if (trafficQuotasRequest) {
|
||||
if (!forceRefresh) return trafficQuotasRequest;
|
||||
return trafficQuotasRequest.then(function () {
|
||||
return fetchAllTrafficQuotas(true);
|
||||
});
|
||||
}
|
||||
trafficQuotasRequest = callGetTrafficQuotas().then(function (res) {
|
||||
if (!res || res.success === false || res.status === 'error') {
|
||||
throw new Error((res && (res.error || res.message)) || _('Failed to load traffic quotas'));
|
||||
}
|
||||
var quotas = [];
|
||||
if (res && res.data && Array.isArray(res.data.quotas)) {
|
||||
quotas = res.data.quotas;
|
||||
} else if (res && Array.isArray(res.quotas)) {
|
||||
quotas = res.quotas;
|
||||
}
|
||||
allTrafficQuotas = quotas;
|
||||
trafficQuotasLoadError = null;
|
||||
return quotas;
|
||||
}).catch(function (error) {
|
||||
console.error('Failed to fetch traffic quotas:', error);
|
||||
allTrafficQuotas = [];
|
||||
trafficQuotasLoadError = error && error.message ? error.message : _('Failed to load traffic quotas');
|
||||
return [];
|
||||
}).finally(function () {
|
||||
trafficQuotasRequest = null;
|
||||
});
|
||||
return trafficQuotasRequest;
|
||||
}
|
||||
|
||||
function getTrafficQuotaForDevice(mac) {
|
||||
for (var i = 0; i < allTrafficQuotas.length; i++) {
|
||||
if (allTrafficQuotas[i] && allTrafficQuotas[i].mac === mac) {
|
||||
return allTrafficQuotas[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function quotaPeriods(quota) {
|
||||
return [
|
||||
{ key: 'minute', label: _('Minute'), limit: quota.minute_bytes || 0, used: quota.minute_used_bytes || 0 },
|
||||
{ key: 'hourly', label: _('Hourly'), limit: quota.hourly_bytes || 0, used: quota.hourly_used_bytes || 0 },
|
||||
{ key: 'daily', label: _('Daily'), limit: quota.daily_bytes || 0, used: quota.daily_used_bytes || 0 },
|
||||
{ key: 'weekly', label: _('Weekly'), limit: quota.weekly_bytes || 0, used: quota.weekly_used_bytes || 0 },
|
||||
{ key: 'monthly', label: _('Monthly'), limit: quota.monthly_bytes || 0, used: quota.monthly_used_bytes || 0 },
|
||||
{ key: 'total', label: _('Lifetime'), limit: quota.total_bytes || 0, used: quota.total_used_bytes || 0 }
|
||||
];
|
||||
}
|
||||
|
||||
function setQuotaInput(period, bytes) {
|
||||
var valueInput = document.getElementById('traffic-quota-' + period + '-value');
|
||||
var unitSelect = document.getElementById('traffic-quota-' + period + '-unit');
|
||||
if (!valueInput || !unitSelect) return;
|
||||
var units = [1099511627776, 1073741824, 1048576, 1024, 1];
|
||||
var unit = period === 'minute' ? 1048576 : 1073741824;
|
||||
if (bytes > 0) {
|
||||
for (var i = 0; i < units.length; i++) {
|
||||
if (bytes >= units[i] && bytes % units[i] === 0) {
|
||||
unit = units[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
unitSelect.value = String(unit);
|
||||
valueInput.value = bytes > 0 ? String(bytes / unit) : '0';
|
||||
}
|
||||
|
||||
function readQuotaInput(period) {
|
||||
var value = Number(document.getElementById('traffic-quota-' + period + '-value').value || 0);
|
||||
var unit = Number(document.getElementById('traffic-quota-' + period + '-unit').value || 1);
|
||||
var bytes = Math.round(value * unit);
|
||||
if (!Number.isFinite(bytes) || bytes < 0 || !Number.isSafeInteger(bytes)) return null;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function setTrafficQuotaFormDisabled(disabled) {
|
||||
trafficQuotaModal.querySelectorAll('.traffic-quota-input-row input, .traffic-quota-input-row select').forEach(function (input) {
|
||||
input.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
function renderTrafficQuotaForm(quota, preserveInputs) {
|
||||
if (!preserveInputs) {
|
||||
var periods = ['minute', 'hourly', 'daily', 'weekly', 'monthly', 'total'];
|
||||
periods.forEach(function (period) {
|
||||
setQuotaInput(period, quota ? (quota[period + '_bytes'] || 0) : 0);
|
||||
});
|
||||
}
|
||||
|
||||
var status = document.getElementById('traffic-quota-status');
|
||||
var usage = document.getElementById('traffic-quota-usage');
|
||||
var deleteBtn = document.getElementById('traffic-quota-delete-btn');
|
||||
if (!status || !usage || !deleteBtn) return;
|
||||
|
||||
status.classList.toggle('blocked', !!(quota && quota.blocked));
|
||||
status.classList.toggle('unconfigured', !quota);
|
||||
status.textContent = !quota ? (trafficQuotasLoadError ? _('Unavailable') : _('Not configured')) : (quota.blocked ? _('Blocked') : _('Active'));
|
||||
deleteBtn.style.display = quota ? '' : 'none';
|
||||
usage.innerHTML = '';
|
||||
if (!quota) {
|
||||
usage.appendChild(E('div', { 'style': 'opacity: 0.7;' }, trafficQuotasLoadError || _('Usage starts when a quota is first saved.')));
|
||||
return;
|
||||
}
|
||||
quotaPeriods(quota).forEach(function (period) {
|
||||
usage.appendChild(E('div', { 'class': 'traffic-quota-usage-row' }, [
|
||||
E('span', {}, period.label),
|
||||
E('span', {}, formatSize(period.used) + ' / ' + (period.limit > 0 ? formatSize(period.limit) : _('Unlimited')))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
function loadTrafficQuotaForm() {
|
||||
if (!currentQuotaDevice) return Promise.resolve();
|
||||
var mac = currentQuotaDevice.mac;
|
||||
renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), quotaFormDirty);
|
||||
return fetchAllTrafficQuotas().then(function () {
|
||||
if (currentQuotaDevice && currentQuotaDevice.mac === mac) {
|
||||
renderTrafficQuotaForm(getTrafficQuotaForDevice(mac), quotaFormDirty);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildTrafficQuotaCell(mac) {
|
||||
var quota = getTrafficQuotaForDevice(mac);
|
||||
if (!quota) return null;
|
||||
var periods = quotaPeriods(quota).filter(function (period) { return period.limit > 0; });
|
||||
var mostUsed = null;
|
||||
periods.forEach(function (period) {
|
||||
var ratio = period.used / period.limit;
|
||||
if (!mostUsed || ratio > mostUsed.ratio) mostUsed = { period: period, ratio: ratio };
|
||||
});
|
||||
var text = quota.blocked ? _('Quota exceeded') : _('Quota active');
|
||||
if (mostUsed) {
|
||||
text = mostUsed.period.label + ': ' + formatSize(mostUsed.period.used) + ' / ' + formatSize(mostUsed.period.limit);
|
||||
}
|
||||
return E('div', {
|
||||
'class': 'traffic-quota-cell',
|
||||
'style': quota.blocked ? 'color: #ef4444; font-weight: 600;' : '',
|
||||
'title': quota.blocked ? _('WAN traffic is blocked because a quota was reached.') : _('Traffic Quota')
|
||||
}, text);
|
||||
}
|
||||
|
||||
// 获取所有定时限速规则
|
||||
function fetchAllScheduleRules() {
|
||||
if (isScheduleRulesLoading) return Promise.resolve();
|
||||
@@ -5862,7 +6248,7 @@ return view.extend({
|
||||
createSortableHeader(_('Device Info'), 'online'),
|
||||
createSplitHeader(_('LAN Traffic'), 'lan_speed', 'lan_traffic'),
|
||||
createSplitHeader(_('WAN Traffic'), 'wan_speed', 'wan_traffic'),
|
||||
E('th', {}, _('Schedule Rules')),
|
||||
E('th', {}, _('Rules / Quota')),
|
||||
E('th', {}, _('Actions'))
|
||||
])
|
||||
]),
|
||||
@@ -5890,9 +6276,9 @@ return view.extend({
|
||||
// 填充数据
|
||||
filteredDevices.forEach(function (device) {
|
||||
var isOnline = isDeviceOnline(device);
|
||||
var deviceQuota = getTrafficQuotaForDevice(device.mac);
|
||||
|
||||
// 默认使用窄主题样式
|
||||
var buttonText = '⚙';
|
||||
var buttonText = 'R';
|
||||
|
||||
var actionButton = E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
@@ -5903,6 +6289,15 @@ return view.extend({
|
||||
showRateLimitModal(device);
|
||||
});
|
||||
|
||||
var quotaButton = E('button', {
|
||||
'class': 'cbi-button cbi-button-neutral',
|
||||
'title': _('Traffic Quota')
|
||||
}, 'Q');
|
||||
|
||||
quotaButton.addEventListener('click', function () {
|
||||
showTrafficQuotaModal(device);
|
||||
});
|
||||
|
||||
var deleteButton = E('button', {
|
||||
'class': 'cbi-button cbi-button-reset',
|
||||
'title': _('Delete Device')
|
||||
@@ -5935,7 +6330,10 @@ return view.extend({
|
||||
E('span', {
|
||||
'class': 'device-status ' + (isOnline ? 'online' : 'offline')
|
||||
}),
|
||||
device.host || '-'
|
||||
device.host || '-',
|
||||
deviceQuota && deviceQuota.blocked
|
||||
? E('span', { 'class': 'traffic-quota-blocked-badge' }, _('Quota blocked'))
|
||||
: ''
|
||||
]),
|
||||
E('div', { 'class': 'device-ip' }, [
|
||||
device.conn ? E('span', {
|
||||
@@ -6149,12 +6547,14 @@ return view.extend({
|
||||
};
|
||||
}
|
||||
|
||||
return E('td', {}, rulesInfo);
|
||||
var quotaCell = buildTrafficQuotaCell(device.mac);
|
||||
return E('td', {}, quotaCell ? [rulesInfo, quotaCell] : rulesInfo);
|
||||
})(),
|
||||
|
||||
// 操作
|
||||
E('td', { 'class': 'device-actions' }, [
|
||||
actionButton,
|
||||
quotaButton,
|
||||
deleteButton
|
||||
])
|
||||
]);
|
||||
@@ -6193,6 +6593,16 @@ return view.extend({
|
||||
});
|
||||
return cardActionBtn;
|
||||
})(),
|
||||
(function () {
|
||||
var cardQuotaBtn = E('button', {
|
||||
'class': 'cbi-button cbi-button-neutral',
|
||||
'title': _('Traffic Quota')
|
||||
}, 'Q');
|
||||
cardQuotaBtn.addEventListener('click', function () {
|
||||
showTrafficQuotaModal(device);
|
||||
});
|
||||
return cardQuotaBtn;
|
||||
})(),
|
||||
(function () {
|
||||
var cardDeleteBtn = E('button', {
|
||||
'class': 'cbi-button cbi-button-reset',
|
||||
@@ -6279,6 +6689,14 @@ return view.extend({
|
||||
rulesContent
|
||||
]);
|
||||
})(),
|
||||
(function () {
|
||||
var quotaCell = buildTrafficQuotaCell(device.mac);
|
||||
if (!quotaCell) return '';
|
||||
return E('div', { 'class': 'device-card-section' }, [
|
||||
E('div', { 'class': 'device-card-section-label' }, _('Traffic Quota')),
|
||||
quotaCell
|
||||
]);
|
||||
})(),
|
||||
// LAN流量(直接显示,不需要展开/收起)
|
||||
E('div', { 'class': 'device-card-section', 'style': 'margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0, 0, 0, 0.1);' }, [
|
||||
E('div', { 'class': 'device-card-section-label' }, _('LAN Traffic')),
|
||||
@@ -7499,12 +7917,25 @@ return view.extend({
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
// 轮询获取流量配额状态(每1秒)
|
||||
poll.add(function () {
|
||||
return fetchAllTrafficQuotas().then(function () {
|
||||
if (window.__bandixRenderTable) window.__bandixRenderTable();
|
||||
if (currentQuotaDevice) {
|
||||
renderTrafficQuotaForm(getTrafficQuotaForDevice(currentQuotaDevice.mac), quotaFormDirty);
|
||||
}
|
||||
});
|
||||
}, 1);
|
||||
|
||||
// Traffic Statistics 不再自动刷新,改为手动查询
|
||||
|
||||
// 立即执行一次,不等待轮询
|
||||
updateDeviceData();
|
||||
refreshWhitelistStatus();
|
||||
fetchAllScheduleRules();
|
||||
fetchAllTrafficQuotas().then(function () {
|
||||
if (window.__bandixRenderTable) window.__bandixRenderTable();
|
||||
});
|
||||
|
||||
// 初始化时间范围查询功能
|
||||
setTimeout(function () {
|
||||
|
||||
@@ -1258,3 +1258,93 @@ msgstr "ID de programa ancla de egreso TCX"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "Se usa cuando tc_order es before/after. Debe ser un ID de programa de egreso valido en la misma interfaz."
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "Cuota de tráfico"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "Estado de la cuota"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "Cuota por minuto"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "Cuota por hora"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "Cuota diaria"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "Cuota semanal"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "Cuota mensual"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "Cuota total acumulada"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "La subida y la descarga WAN se contabilizan juntas. Introduzca 0 para no establecer límite."
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "Sin configurar"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "Bloqueado"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "Activa"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "No hay datos de uso de la cuota"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "Eliminar cuota"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "Guardar cuota"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "El uso comienza a contabilizarse al guardar una cuota por primera vez."
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "Minuto"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "Total acumulado"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "Cuota excedida"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "Cuota activa"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "El tráfico WAN está bloqueado porque se alcanzó una cuota."
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "Bloqueado por cuota"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "Los valores de cuota deben ser no negativos y estar dentro del rango admitido."
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "No se pudo guardar la cuota de tráfico"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "Eliminar cuota de tráfico"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "¿Eliminar esta cuota y todo su uso acumulado?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "No se pudo eliminar la cuota de tráfico"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "Reglas / Cuota"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "No se pudieron cargar las cuotas de tráfico"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "No disponible"
|
||||
|
||||
@@ -1258,3 +1258,93 @@ msgstr "ID du programme d ancrage egress TCX"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "Utilise lorsque tc_order est before/after. Doit etre un ID de programme egress valide sur la meme interface."
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "Quota de trafic"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "État du quota"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "Quota par minute"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "Quota par heure"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "Quota journalier"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "Quota hebdomadaire"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "Quota mensuel"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "Quota total cumulé"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "Le trafic montant et descendant WAN est comptabilisé ensemble. Saisissez 0 pour un trafic illimité."
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "Non configuré"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "Bloqué"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "Actif"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "Aucune donnée d'utilisation du quota"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "Supprimer le quota"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "Enregistrer le quota"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "Le suivi de l'utilisation commence lors du premier enregistrement d'un quota."
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "Minute"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "Total cumulé"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "Quota dépassé"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "Quota actif"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "Le trafic WAN est bloqué car un quota a été atteint."
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "Blocage par quota"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "Les valeurs de quota doivent être positives ou nulles et respecter la plage prise en charge."
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "Impossible d'enregistrer le quota de trafic"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "Supprimer le quota de trafic"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "Supprimer ce quota et toutes ses données d'utilisation cumulées ?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "Impossible de supprimer le quota de trafic"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "Règles / Quota"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "Impossible de charger les quotas de trafic"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "Indisponible"
|
||||
|
||||
@@ -1258,3 +1258,93 @@ msgstr "TCX egressアンカープログラムID"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "tc_orderがbefore/afterのときに使用します。同じインターフェース上の有効なegressプログラムIDである必要があります。"
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "トラフィッククォータ"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "クォータの状態"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "1分あたりのクォータ"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "1時間あたりのクォータ"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "1日あたりのクォータ"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "1週間あたりのクォータ"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "1か月あたりのクォータ"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "累計クォータ"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "WANのアップロードとダウンロードは合算されます。無制限にするには0を入力してください。"
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "未設定"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "ブロック中"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "有効"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "クォータ使用量データはありません"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "クォータを削除"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "クォータを保存"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "クォータを初めて保存した時点から使用量の計測を開始します。"
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "1分"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "累計"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "クォータ超過"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "クォータ有効"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "クォータに達したためWANトラフィックがブロックされています。"
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "クォータによりブロック"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "クォータ値は0以上で、サポートされている範囲内である必要があります。"
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "トラフィッククォータを保存できませんでした"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "トラフィッククォータを削除"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "このクォータと累積された使用量をすべて削除しますか?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "トラフィッククォータを削除できませんでした"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "ルール / クォータ"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "トラフィッククォータを読み込めませんでした"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "利用不可"
|
||||
|
||||
@@ -1258,3 +1258,93 @@ msgstr "ID programu kotwicy TCX egress"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "Uzywane, gdy tc_order to before/after. Musi to byc prawidlowe ID programu egress na tym samym interfejsie."
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "Limit transferu"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "Stan limitu"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "Limit minutowy"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "Limit godzinowy"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "Limit dzienny"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "Limit tygodniowy"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "Limit miesięczny"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "Łączny limit"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "Ruch wysyłany i pobierany przez WAN jest sumowany. Wpisz 0, aby wyłączyć limit."
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "Nie skonfigurowano"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "Zablokowano"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "Aktywny"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "Brak danych o wykorzystaniu limitu"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "Usuń limit"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "Zapisz limit"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "Naliczanie użycia rozpoczyna się po pierwszym zapisaniu limitu."
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "Minuta"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "Łącznie"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "Limit przekroczony"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "Limit aktywny"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "Ruch WAN jest zablokowany, ponieważ osiągnięto limit."
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "Blokada przez limit"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "Wartości limitu muszą być nieujemne i mieścić się w obsługiwanym zakresie."
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "Nie udało się zapisać limitu transferu"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "Usuń limit transferu"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "Usunąć ten limit i wszystkie dane o naliczonym użyciu?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "Nie udało się usunąć limitu transferu"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "Reguły / Limit"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "Nie udało się wczytać limitów transferu"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "Niedostępne"
|
||||
|
||||
@@ -1258,3 +1258,93 @@ msgstr "ID якорной программы TCX egress"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "Используется, когда tc_order имеет значение before/after. Должен быть корректный ID egress-программы на том же интерфейсе."
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "Квота трафика"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "Состояние квоты"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "Минутная квота"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "Часовая квота"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "Суточная квота"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "Недельная квота"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "Месячная квота"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "Общая квота"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "Входящий и исходящий трафик WAN учитываются вместе. Введите 0, чтобы отключить ограничение."
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "Не настроено"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "Заблокировано"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "Активна"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "Нет данных об использовании квоты"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "Удалить квоту"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "Сохранить квоту"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "Учет трафика начинается после первого сохранения квоты."
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "Минута"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "Всего"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "Квота превышена"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "Квота активна"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "Трафик WAN заблокирован, так как достигнута квота."
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "Блокировка по квоте"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "Значения квоты должны быть неотрицательными и находиться в поддерживаемом диапазоне."
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "Не удалось сохранить квоту трафика"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "Удалить квоту трафика"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "Удалить эту квоту и все накопленные данные об использовании?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "Не удалось удалить квоту трафика"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "Правила / Квота"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "Не удалось загрузить квоты трафика"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "Недоступно"
|
||||
|
||||
@@ -1285,3 +1285,93 @@ msgstr "TCX 出站锚点程序 ID"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "当 tc_order 为 before/after 时使用。必须是同一接口上的有效出站程序 ID。"
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "流量配额"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "配额状态"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "每小时配额"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "每天配额"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "每周配额"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "每月配额"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "累计总量配额"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "WAN 上传和下载合并计算;输入 0 表示不限量。"
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "未配置"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "已阻断"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "生效中"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "暂无配额用量数据"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "删除配额"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "保存配额"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "首次保存配额后开始累计用量。"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "累计"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "配额已用尽"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "配额生效中"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "因达到流量配额,WAN 流量已被阻断。"
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "配额阻断"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "配额必须是支持范围内的非负数。"
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "保存流量配额失败"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "删除流量配额"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "确定删除此配额及其全部累计用量吗?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "删除流量配额失败"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "规则 / 配额"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "加载流量配额失败"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "不可用"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "每分钟配额"
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "每分钟"
|
||||
|
||||
@@ -1258,3 +1258,93 @@ msgstr "TCX 出站錨點程式 ID"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "當 tc_order 為 before/after 時使用。必須是同一介面上的有效出站程式 ID。"
|
||||
|
||||
msgid "Traffic Quota"
|
||||
msgstr "流量配額"
|
||||
|
||||
msgid "Quota Status"
|
||||
msgstr "配額狀態"
|
||||
|
||||
msgid "Minute Quota"
|
||||
msgstr "每分鐘配額"
|
||||
|
||||
msgid "Hourly Quota"
|
||||
msgstr "每小時配額"
|
||||
|
||||
msgid "Daily Quota"
|
||||
msgstr "每天配額"
|
||||
|
||||
msgid "Weekly Quota"
|
||||
msgstr "每週配額"
|
||||
|
||||
msgid "Monthly Quota"
|
||||
msgstr "每月配額"
|
||||
|
||||
msgid "Lifetime Quota"
|
||||
msgstr "累計總量配額"
|
||||
|
||||
msgid "WAN upload and download are counted together. Enter 0 for unlimited."
|
||||
msgstr "WAN 上傳和下載合併計算;輸入 0 表示不限量。"
|
||||
|
||||
msgid "Not configured"
|
||||
msgstr "未設定"
|
||||
|
||||
msgid "Blocked"
|
||||
msgstr "已阻斷"
|
||||
|
||||
msgid "Active"
|
||||
msgstr "生效中"
|
||||
|
||||
msgid "No quota usage data"
|
||||
msgstr "暫無配額用量資料"
|
||||
|
||||
msgid "Delete Quota"
|
||||
msgstr "刪除配額"
|
||||
|
||||
msgid "Save Quota"
|
||||
msgstr "儲存配額"
|
||||
|
||||
msgid "Usage starts when a quota is first saved."
|
||||
msgstr "首次儲存配額後開始累計用量。"
|
||||
|
||||
msgid "Minute"
|
||||
msgstr "每分鐘"
|
||||
|
||||
msgid "Lifetime"
|
||||
msgstr "累計"
|
||||
|
||||
msgid "Quota exceeded"
|
||||
msgstr "配額已用盡"
|
||||
|
||||
msgid "Quota active"
|
||||
msgstr "配額生效中"
|
||||
|
||||
msgid "WAN traffic is blocked because a quota was reached."
|
||||
msgstr "因達到流量配額,WAN 流量已被阻斷。"
|
||||
|
||||
msgid "Quota blocked"
|
||||
msgstr "配額阻斷"
|
||||
|
||||
msgid "Quota values must be non-negative and within the supported range."
|
||||
msgstr "配額必須是支援範圍內的非負數。"
|
||||
|
||||
msgid "Failed to save traffic quota"
|
||||
msgstr "儲存流量配額失敗"
|
||||
|
||||
msgid "Delete Traffic Quota"
|
||||
msgstr "刪除流量配額"
|
||||
|
||||
msgid "Delete this quota and all of its accumulated usage?"
|
||||
msgstr "確定刪除此配額及其全部累計用量嗎?"
|
||||
|
||||
msgid "Failed to delete traffic quota"
|
||||
msgstr "刪除流量配額失敗"
|
||||
|
||||
msgid "Rules / Quota"
|
||||
msgstr "規則 / 配額"
|
||||
|
||||
msgid "Failed to load traffic quotas"
|
||||
msgstr "載入流量配額失敗"
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr "無法使用"
|
||||
|
||||
@@ -19,6 +19,7 @@ readonly BANDIX_TRAFFIC_USAGE_INCREMENTS_API="$BANDIX_API_BASE/api/traffic/usage
|
||||
readonly BANDIX_RATE_LIMIT_WHITELIST_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist"
|
||||
readonly BANDIX_RATE_LIMIT_WHITELIST_ENABLED_API="$BANDIX_API_BASE/api/traffic/rate_limit/whitelist/enabled"
|
||||
readonly BANDIX_RATE_LIMIT_DEFAULT_API="$BANDIX_API_BASE/api/traffic/rate_limit/default"
|
||||
readonly BANDIX_TRAFFIC_QUOTAS_API="$BANDIX_API_BASE/api/traffic/quotas"
|
||||
|
||||
# 通用函数:创建简单的JSON响应
|
||||
make_value() {
|
||||
@@ -134,6 +135,71 @@ set_default_rate_limit() {
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
get_traffic_quotas() {
|
||||
local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null)
|
||||
if [ $? -ne 0 ] || [ -z "$response" ]; then
|
||||
make_error "Failed to connect to Bandix service"
|
||||
return
|
||||
fi
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
set_traffic_quota() {
|
||||
local mac="$1"
|
||||
local minute_bytes="$2"
|
||||
local hourly_bytes="$3"
|
||||
local daily_bytes="$4"
|
||||
local weekly_bytes="$5"
|
||||
local monthly_bytes="$6"
|
||||
local total_bytes="$7"
|
||||
|
||||
if [ -z "$mac" ]; then
|
||||
make_error "MAC address is required"
|
||||
return
|
||||
fi
|
||||
for value in "$minute_bytes" "$hourly_bytes" "$daily_bytes" "$weekly_bytes" "$monthly_bytes" "$total_bytes"; do
|
||||
case "$value" in
|
||||
''|*[!0-9]*)
|
||||
make_error "Quota values must be non-negative integers"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
local mac_escaped=$(escape_json_string "$mac")
|
||||
local request_data="{
|
||||
\"mac\": \"$mac_escaped\",
|
||||
\"minute_bytes\": $minute_bytes,
|
||||
\"hourly_bytes\": $hourly_bytes,
|
||||
\"daily_bytes\": $daily_bytes,
|
||||
\"weekly_bytes\": $weekly_bytes,
|
||||
\"monthly_bytes\": $monthly_bytes,
|
||||
\"total_bytes\": $total_bytes
|
||||
}"
|
||||
local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null)
|
||||
if [ $? -eq 0 ] && [ -n "$response" ] && echo "$response" | grep -q '"status":[[:space:]]*"success"'; then
|
||||
make_success "Traffic quota saved successfully"
|
||||
else
|
||||
make_error "Failed to save traffic quota"
|
||||
fi
|
||||
}
|
||||
|
||||
delete_traffic_quota() {
|
||||
local mac="$1"
|
||||
if [ -z "$mac" ]; then
|
||||
make_error "MAC address is required"
|
||||
return
|
||||
fi
|
||||
local mac_escaped=$(escape_json_string "$mac")
|
||||
local request_data="{\"mac\": \"$mac_escaped\"}"
|
||||
local response=$(curl -s --connect-timeout 3 --max-time 10 -X DELETE -H "Content-Type: application/json" -d "$request_data" "$BANDIX_TRAFFIC_QUOTAS_API" 2>/dev/null)
|
||||
if [ $? -eq 0 ] && [ -n "$response" ] && echo "$response" | grep -q '"status":[[:space:]]*"success"'; then
|
||||
make_success "Traffic quota deleted successfully"
|
||||
else
|
||||
make_error "Failed to delete traffic quota"
|
||||
fi
|
||||
}
|
||||
|
||||
# 删除设备
|
||||
delete_device() {
|
||||
local mac="$1"
|
||||
@@ -1349,6 +1415,23 @@ case "$1" in
|
||||
json_add_int "wan_tx_rate_limit"
|
||||
json_close_object
|
||||
|
||||
json_add_object "getTrafficQuotas"
|
||||
json_close_object
|
||||
|
||||
json_add_object "setTrafficQuota"
|
||||
json_add_string "mac"
|
||||
json_add_int "minute_bytes"
|
||||
json_add_int "hourly_bytes"
|
||||
json_add_int "daily_bytes"
|
||||
json_add_int "weekly_bytes"
|
||||
json_add_int "monthly_bytes"
|
||||
json_add_int "total_bytes"
|
||||
json_close_object
|
||||
|
||||
json_add_object "deleteTrafficQuota"
|
||||
json_add_string "mac"
|
||||
json_close_object
|
||||
|
||||
json_dump
|
||||
json_cleanup
|
||||
;;
|
||||
@@ -1793,6 +1876,57 @@ case "$1" in
|
||||
fi
|
||||
set_default_rate_limit "$wan_rx_rate_limit" "$wan_tx_rate_limit"
|
||||
;;
|
||||
getTrafficQuotas)
|
||||
get_traffic_quotas
|
||||
;;
|
||||
setTrafficQuota)
|
||||
mac=""
|
||||
minute_bytes=""
|
||||
hourly_bytes=""
|
||||
daily_bytes=""
|
||||
weekly_bytes=""
|
||||
monthly_bytes=""
|
||||
total_bytes=""
|
||||
input=""
|
||||
if read -t 1 -r input; then :; fi
|
||||
if [ -n "$input" ]; then
|
||||
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
|
||||
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
|
||||
minute_bytes="$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)"
|
||||
[ -z "$minute_bytes" ] && minute_bytes="$(echo "$input" | jsonfilter -e '$.minute_bytes' 2>/dev/null)"
|
||||
hourly_bytes="$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)"
|
||||
[ -z "$hourly_bytes" ] && hourly_bytes="$(echo "$input" | jsonfilter -e '$.hourly_bytes' 2>/dev/null)"
|
||||
daily_bytes="$(echo "$input" | jsonfilter -e '$[3]' 2>/dev/null)"
|
||||
[ -z "$daily_bytes" ] && daily_bytes="$(echo "$input" | jsonfilter -e '$.daily_bytes' 2>/dev/null)"
|
||||
weekly_bytes="$(echo "$input" | jsonfilter -e '$[4]' 2>/dev/null)"
|
||||
[ -z "$weekly_bytes" ] && weekly_bytes="$(echo "$input" | jsonfilter -e '$.weekly_bytes' 2>/dev/null)"
|
||||
monthly_bytes="$(echo "$input" | jsonfilter -e '$[5]' 2>/dev/null)"
|
||||
[ -z "$monthly_bytes" ] && monthly_bytes="$(echo "$input" | jsonfilter -e '$.monthly_bytes' 2>/dev/null)"
|
||||
total_bytes="$(echo "$input" | jsonfilter -e '$[6]' 2>/dev/null)"
|
||||
[ -z "$total_bytes" ] && total_bytes="$(echo "$input" | jsonfilter -e '$.total_bytes' 2>/dev/null)"
|
||||
else
|
||||
mac="$3"
|
||||
minute_bytes="$4"
|
||||
hourly_bytes="$5"
|
||||
daily_bytes="$6"
|
||||
weekly_bytes="$7"
|
||||
monthly_bytes="$8"
|
||||
total_bytes="$9"
|
||||
fi
|
||||
set_traffic_quota "$mac" "$minute_bytes" "$hourly_bytes" "$daily_bytes" "$weekly_bytes" "$monthly_bytes" "$total_bytes"
|
||||
;;
|
||||
deleteTrafficQuota)
|
||||
mac=""
|
||||
input=""
|
||||
if read -t 1 -r input; then :; fi
|
||||
if [ -n "$input" ]; then
|
||||
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
|
||||
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
|
||||
else
|
||||
mac="$3"
|
||||
fi
|
||||
delete_traffic_quota "$mac"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"installUpdate",
|
||||
"getTrafficUsageRanking",
|
||||
"getTrafficUsageIncrements",
|
||||
"getRateLimitWhitelist"
|
||||
"getRateLimitWhitelist",
|
||||
"getTrafficQuotas"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
@@ -64,7 +65,10 @@
|
||||
"addRateLimitWhitelist",
|
||||
"deleteRateLimitWhitelist",
|
||||
"setDefaultRateLimit",
|
||||
"updateScheduleLimit"
|
||||
"updateScheduleLimit",
|
||||
"getTrafficQuotas",
|
||||
"setTrafficQuota",
|
||||
"deleteTrafficQuota"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
|
||||
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-clashoo
|
||||
PKG_VERSION:=1.30.0
|
||||
PKG_RELEASE:=11
|
||||
PKG_RELEASE:=12
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
@@ -75,7 +75,7 @@ define Package/luci-i18n-clashoo-zh-cn
|
||||
SECTION:=luci
|
||||
CATEGORY:=LuCI
|
||||
TITLE:=luci-app-clashoo - Simplified Chinese (zh-cn)
|
||||
DEPENDS:=+luci-app-clashoo
|
||||
DEPENDS:=luci-app-clashoo
|
||||
DEFAULT:=LUCI_LANG_zh_Hans||(ALL&&m)
|
||||
HIDDEN:=1
|
||||
PKGARCH:=all
|
||||
|
||||
@@ -3361,9 +3361,9 @@ return { success: false, error: 'missing_yq', message: '缺少 yq,无法注入
|
||||
if (!access(exploded, "r")) {
|
||||
system("cp -f " + shell_quote(tmp_tpl) + " " + shell_quote(exploded));
|
||||
}
|
||||
/* proxy-providers 和 rule-providers 两处都注入(兼容不同模板格式) */
|
||||
/* 只替换 proxy-providers.url,rule-providers 指向规则集保持原样 */
|
||||
let inject_cmd = "URL=" + shell_quote(sub_url)
|
||||
+ " yq e '.[\"proxy-providers\"][].url = env(URL) | .[\"rule-providers\"][].url = env(URL)' "
|
||||
+ " yq e '.[\"proxy-providers\"][].url = env(URL)' "
|
||||
+ shell_quote(exploded) + " > " + shell_quote(tmp_out);
|
||||
if (system(inject_cmd) != 0 || !access(tmp_out, "r")) {
|
||||
system("rm -f " + shell_quote(exploded));
|
||||
|
||||
@@ -6,9 +6,21 @@
|
||||
'require view';
|
||||
'require fs';
|
||||
|
||||
const callServiceList = rpc.declare({
|
||||
object: 'service',
|
||||
method: 'list',
|
||||
params: ['name'],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
function getServiceStatus() {
|
||||
return L.resolveDefault(callServiceList('filebrowser-q'), {}).then(function(res) {
|
||||
let isRunning = false;
|
||||
try {
|
||||
isRunning = res['filebrowser-q']['instances']['instance1']['running'];
|
||||
} catch (e) { }
|
||||
return isRunning;
|
||||
});
|
||||
}
|
||||
|
||||
function renderStatus(isRunning, port) {
|
||||
@@ -26,16 +38,8 @@ function renderStatus(isRunning, port) {
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: async function () {
|
||||
const promises = await Promise.all([
|
||||
L.resolveDefault(fs.stat('/var/run/filebrowser-q.pid'), null),
|
||||
uci.load('filebrowser-q')
|
||||
]);
|
||||
const data = {
|
||||
isRunning: promises[0],
|
||||
conf: promises[1]
|
||||
};
|
||||
return data;
|
||||
load() {
|
||||
return uci.load('filebrowser-q');
|
||||
},
|
||||
|
||||
render(data) {
|
||||
@@ -50,23 +54,16 @@ return view.extend({
|
||||
s.anonymous = true;
|
||||
s.render = function() {
|
||||
poll.add(function() {
|
||||
return fs.stat('/var/run/filebrowser-q.pid').then(function(stat) {
|
||||
return L.resolveDefault(getServiceStatus()).then(function(res) {
|
||||
let view = document.getElementById('service_status');
|
||||
if (view) {
|
||||
view.innerHTML = renderStatus(stat, webport);
|
||||
}
|
||||
}).catch(function() {
|
||||
let view = document.getElementById('service_status');
|
||||
if (view) {
|
||||
view.innerHTML = renderStatus(null, webport);
|
||||
}
|
||||
view.innerHTML = renderStatus(res, webport);
|
||||
});
|
||||
});
|
||||
|
||||
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
|
||||
E('p', { id: 'service_status' }, _('Collecting data...'))
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'filebrowser');
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#!/bin/sh
|
||||
[ "$ACTION" = ifup ] && /etc/init.d/mosdns restart
|
||||
[ "$ACTION" = "ifup" ] || exit 0
|
||||
[ "$INTERFACE" = "wan" ] && /etc/init.d/mosdns reload
|
||||
|
||||
@@ -47,6 +47,11 @@ function waitForElement(selector, callback) {
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function waitForElementId(id, callback) {
|
||||
waitForElement("#" + CSS.escape(id), callback);
|
||||
//waitForElement('[id="' + id + '"]', callback);
|
||||
}
|
||||
|
||||
function get_current_url() {
|
||||
return window.location.origin + window.location.pathname;
|
||||
}
|
||||
|
||||
@@ -192,10 +192,15 @@ o.rmempty = false
|
||||
o:depends({ _hide_node_option = "1", ['!reverse'] = true })
|
||||
|
||||
o = s:option(ListValue, "node", "<a style='color: red'>" .. translate("Proxy Node") .. "</a>")
|
||||
o.default = ""
|
||||
o.group = {}
|
||||
o:depends({ _hide_node_option = false, use_global_config = false })
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o.group = {}
|
||||
|
||||
current_node_id = o:formvalue(arg[1])
|
||||
if not current_node_id then
|
||||
current_node_id = m:get(arg[1], "node")
|
||||
end
|
||||
current_node = current_node_id and m:get(current_node_id) or {}
|
||||
|
||||
o = s:option(DummyValue, "_acl_node_bool", "")
|
||||
o.template = m:template_path("/cbi/hidevalue")
|
||||
@@ -519,6 +524,8 @@ o.description = desc .. "</ul>"
|
||||
o:depends({dns_shunt = "dnsmasq", tcp_proxy_mode = "proxy", chn_list = "direct"})
|
||||
|
||||
local o_node = s.fields["node"]
|
||||
local shunt_list = {}
|
||||
|
||||
for k, v in pairs(socks_list) do
|
||||
o_node:value(v.id, v["remark"])
|
||||
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
@@ -528,7 +535,7 @@ for k, v in pairs(nodes_table) do
|
||||
s.fields["dns_mode"]:depends({ _acl_node_bool = "1" })
|
||||
break
|
||||
end
|
||||
if v.protocol == "_shunt" then
|
||||
if v.protocol and v.protocol == "_shunt" then
|
||||
if v.type == "Xray" and has_xray then
|
||||
o_node:value(v.id, v["remark"])
|
||||
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
@@ -544,6 +551,7 @@ for k, v in pairs(nodes_table) do
|
||||
s.fields["_node_sel_shunt"]:depends({ node = v.id })
|
||||
s.fields["remote_rewrite_ttl"]:depends({ _acl_node_bool = "1", node = v.id })
|
||||
end
|
||||
shunt_list[#shunt_list + 1] = v
|
||||
else
|
||||
o_node:value(v.id, v["remark"])
|
||||
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
@@ -552,4 +560,19 @@ end
|
||||
|
||||
m:appendTemplate("/acl/config_footer", {section = arg[1]})
|
||||
|
||||
--[[
|
||||
-- Shunt
|
||||
if current_node.protocol == "_shunt" then
|
||||
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
|
||||
setfenv(shunt_lua, getfenv(1))(m, s, {
|
||||
s_cfgid = s.section,
|
||||
node_id = current_node_id,
|
||||
node = current_node,
|
||||
verify_option = s.fields["node"]
|
||||
})
|
||||
end
|
||||
|
||||
m:appendTemplate("/acl/shunt", { shunt_list = api.jsonc.stringify(shunt_list), section = s.section })
|
||||
]]--
|
||||
|
||||
return api.return_map(m)
|
||||
|
||||
@@ -139,7 +139,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
|
||||
tips.cfgvalue = function(t, n)
|
||||
return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first."))
|
||||
end
|
||||
tips:depends({ node = "", ["!reverse"] = true })
|
||||
tips:depends("_node", "1")
|
||||
for k, v in pairs(shunt_list) do
|
||||
tips:depends("node", v.id)
|
||||
end
|
||||
@@ -153,7 +153,7 @@ o = s:taboption("Main", Value, "node_socks_port", translate("Node") .. " Socks "
|
||||
o.default = 1070
|
||||
o.placeholder = 1070
|
||||
o.datatype = "range(1,65535)"
|
||||
o:depends({ node = "", ["!reverse"] = true })
|
||||
o:depends("_node", "1")
|
||||
--[[
|
||||
if has_singbox or has_xray then
|
||||
o = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
|
||||
@@ -163,7 +163,12 @@ end
|
||||
]]--
|
||||
o = s:taboption("Main", Flag, "node_socks_bind_local", translate("Node") .. " Socks " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
o.default = "1"
|
||||
o:depends({ node = "", ["!reverse"] = true })
|
||||
o:depends("_node", "1")
|
||||
|
||||
o = s:taboption("Main", DummyValue, "_node", "")
|
||||
o.template = m:template_path("/cbi/hidevalue")
|
||||
o.value = "1"
|
||||
o:depends({ node = "", ['!reverse'] = true })
|
||||
|
||||
-- Node → DNS Depends Settings
|
||||
o = s:taboption("Main", DummyValue, "_node_sel_shunt", "")
|
||||
@@ -747,7 +752,7 @@ for k, v in pairs(nodes_table) do
|
||||
if #normal_list == 0 and #iface_list == 0 then
|
||||
break
|
||||
end
|
||||
if v.protocol == "_shunt" then
|
||||
if v.protocol and v.protocol == "_shunt" then
|
||||
if has_singbox or has_xray then
|
||||
o_node:value(v.id, v["remark"])
|
||||
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
|
||||
@@ -4,6 +4,7 @@ if not data.node_id or not data.node then
|
||||
return
|
||||
end
|
||||
|
||||
local api = m.api
|
||||
local s_cfgid = data.s_cfgid
|
||||
local current_node_id = data.node_id
|
||||
local node_list = data.node_list or api.get_node_list()
|
||||
|
||||
@@ -13,7 +13,7 @@ jsonc = require "luci.jsonc"
|
||||
i18n = require "luci.i18n"
|
||||
|
||||
appname = "passwall"
|
||||
curl_args = { "-skfL", "--connect-timeout 3", "--retry 3" }
|
||||
curl_args = { "-skfL", "--connect-timeout 3", "--retry 3", "-H 'Accept: */*'" }
|
||||
command_timeout = 300
|
||||
OPENWRT_ARCH = nil
|
||||
DISTRIB_ARCH = nil
|
||||
|
||||
@@ -1196,7 +1196,7 @@ function gen_config(var)
|
||||
format = format,
|
||||
path = _type == "local" and w or nil,
|
||||
url = _type == "remote" and w or nil,
|
||||
http_client = _type == "remote" and "remote_http_client" or nil,
|
||||
http_client = (_type == "remote" and version_ge_1_14_0) and "remote_http_client" or nil,
|
||||
--update_interval = _type == "remote" and "1d" or nil,
|
||||
}
|
||||
end
|
||||
@@ -2281,7 +2281,7 @@ function gen_config(var)
|
||||
-- 实验性
|
||||
experimental = experimental,
|
||||
-- HTTP Client
|
||||
http_clients = http_clients
|
||||
http_clients = version_ge_1_14_0 and http_clients or nil,
|
||||
}
|
||||
table.insert(outbounds, {
|
||||
type = "direct",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<%
|
||||
local map = self.map
|
||||
local api = map.api
|
||||
local appname = map.config
|
||||
local section = self.section
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
const shunt_list = JSON.parse('<%=self.shunt_list%>');
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const id = "cbid.<%=appname%>.<%=section%>.node";
|
||||
let node = null;
|
||||
const onChange = (e) => {
|
||||
const new_val = e.target.value;
|
||||
const new_hasItem = shunt_list.some(element => element.id == new_val);
|
||||
if (new_hasItem) {
|
||||
XHR.get('<%=api.url("update_config")%>', {
|
||||
id: "<%=section%>",
|
||||
data: JSON.stringify({
|
||||
node: new_val,
|
||||
mode: "1"
|
||||
})
|
||||
}, function(x, data) {
|
||||
if (x && x.status == 200 && data.code == 1) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
document.getElementById("cbi-<%=appname%>-shunt_option_list")?.style.setProperty("display", "none");
|
||||
}
|
||||
};
|
||||
const check = () => {
|
||||
const el = document.getElementById(id);
|
||||
if (el === node) return;
|
||||
if (node && !el) {
|
||||
document.getElementById("cbi-<%=appname%>-shunt_option_list")?.style.setProperty("display", "none");
|
||||
} else {
|
||||
let o_hasItem = shunt_list.some(element => element.id == el.value);
|
||||
if (o_hasItem) {
|
||||
document.getElementById("cbi-<%=appname%>-shunt_option_list")?.style.setProperty("display", "");
|
||||
}
|
||||
}
|
||||
node = el;
|
||||
if (node) {
|
||||
node.addEventListener("change", onChange);
|
||||
}
|
||||
};
|
||||
const observer = new MutationObserver(check);
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
check();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
@@ -421,10 +421,10 @@ table td, .table .td {
|
||||
<%- else %>
|
||||
(function() {
|
||||
if (typeof(cbi_t_switch) === "function") {
|
||||
var old_switch = cbi_t_switch;
|
||||
const ori_cbi_t_switch = cbi_t_switch;
|
||||
cbi_t_switch = function(section, tab) {
|
||||
dechecked_all_node();
|
||||
return old_switch(section, tab);
|
||||
return ori_cbi_t_switch(section, tab);
|
||||
};
|
||||
}
|
||||
})();
|
||||
@@ -1516,37 +1516,71 @@ table td, .table .td {
|
||||
|
||||
//Node list option saving logic
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function onChange(option, value) {
|
||||
function onChange(option, value, refresh) {
|
||||
ajax.abortAll();
|
||||
XHR.get('<%=api.url("save_node_list_opt")%>', {
|
||||
option: option,
|
||||
value: value
|
||||
}, function(x) {
|
||||
if (x && x.status == 200) {
|
||||
if (refresh) {
|
||||
document.getElementById("node_list").innerHTML = "";
|
||||
loadNodeList();
|
||||
}
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dom_event() {
|
||||
waitForElement('input[type="checkbox"][name*="<%=appname%>"][name*="show_node_info"]', function(el) {
|
||||
el.addEventListener("change", () => {
|
||||
el.blur();
|
||||
show_node_info = el.checked ? "1" : "0";
|
||||
onChange("show_node_info", show_node_info);
|
||||
onChange("auto_detection_time", auto_detection_time, true);
|
||||
});
|
||||
});
|
||||
|
||||
waitForElement('select[name*="<%=appname%>"][name*="auto_detection_time"]', function(el) {
|
||||
<% 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) {
|
||||
el.addEventListener("change", () => {
|
||||
el.blur();
|
||||
auto_detection_time = el.value;
|
||||
onChange("auto_detection_time", auto_detection_time);
|
||||
if (el.value && (!el.new_val || el.new_val != el.value)) {
|
||||
onChange("url_test_url", el.value, false);
|
||||
el.new_val = el.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
waitForElement('input[id*="<%=appname%>"][id*="url_test_url"]', function(el) {
|
||||
el.addEventListener("change", () => {
|
||||
if (el.value && (!el.new_val || el.new_val != 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;
|
||||
cbi_d_update = function() {
|
||||
ori_cbi_d_update();
|
||||
dom_event();
|
||||
};
|
||||
<% end -%>
|
||||
dom_event();
|
||||
|
||||
const links = document.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
|
||||
@@ -111,7 +111,7 @@ api.uci_foreach_c("haproxy_config", function(t)
|
||||
if server_node.type ~= "Socks" then
|
||||
local relay_port = server_node.port
|
||||
local new_port = api.get_new_port()
|
||||
local config_file = string.format("haproxy_%s_%s.json", t[".name"], new_port)
|
||||
local config_file = string.format("%s_%s.json", t[".name"], new_port)
|
||||
sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
|
||||
appname,
|
||||
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s",
|
||||
|
||||
@@ -1060,7 +1060,7 @@ add_firewall_rule() {
|
||||
$ipt_n -A PSW_OUTPUT $(dst $IPSET_LAN) -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT $(dst $IPSET_VPS) -j RETURN
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ipt_n -A PSW_OUTPUT $(dst $IPSET_WHITE) -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
||||
|
||||
$ipt_n -N PSW_DNS
|
||||
if [ $(config_n_get @global[0] dns_redirect "1") = "0" ]; then
|
||||
@@ -1115,7 +1115,7 @@ add_firewall_rule() {
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && $ipt_m -A PSW_OUTPUT $(dst $IPSET_BLOCK) -j MARK --set-mark 88
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ipt_m -A PSW_OUTPUT $(dst $IPSET_WHITE) -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -m conntrack --ctdir REPLY -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
||||
|
||||
ip rule add fwmark ${FWMARK} table 999 priority 999
|
||||
ip route add local 0.0.0.0/0 dev lo table 999
|
||||
@@ -1130,7 +1130,7 @@ add_firewall_rule() {
|
||||
$ip6t_n -A PSW_OUTPUT $(dst $IPSET_LAN6) -j RETURN
|
||||
$ip6t_n -A PSW_OUTPUT $(dst $IPSET_VPS6) -j RETURN
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && $ip6t_n -A PSW_OUTPUT $(dst $IPSET_WHITE6) -j RETURN
|
||||
$ip6t_n -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
$ip6t_n -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
||||
}
|
||||
|
||||
$ip6t_n -N PSW_DNS
|
||||
@@ -1167,7 +1167,7 @@ add_firewall_rule() {
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW" "-p tcp -m socket -j PSW_DIVERT"
|
||||
|
||||
$ip6t_m -N PSW_OUTPUT
|
||||
$ip6t_m -A PSW_OUTPUT -m mark --mark 255 -j RETURN
|
||||
$ip6t_m -A PSW_OUTPUT -m mark --mark 0xff/0xff -j RETURN
|
||||
$ip6t_m -A PSW_OUTPUT $(dst $IPSET_LAN6) -j RETURN
|
||||
$ip6t_m -A PSW_OUTPUT $(dst $IPSET_VPS6) -j RETURN
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && $ip6t_m -A PSW_OUTPUT $(dst $IPSET_BLOCK6) -j MARK --set-mark 88
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
. /usr/share/passwall/utils.sh
|
||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
|
||||
LEASE_FILE="/tmp/dhcp.leases"
|
||||
LEASE_FILE=$(uci -q get "dhcp.@dnsmasq[0].leasefile")
|
||||
LEASE_FILE=${LEASE_FILE:="/tmp/dhcp.leases"}
|
||||
HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
|
||||
TMP_FILE="/tmp/dhcp-hosts.tmp"
|
||||
|
||||
|
||||
@@ -1178,7 +1178,7 @@ add_firewall_rule() {
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && nft_rule_dual "PSW_OUTPUT_MANGLE" "ip daddr" "$NFTSET_BLOCK" "counter reject"
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && nft_rule_dual "PSW_OUTPUT_MANGLE" "ip daddr" "$NFTSET_WHITE" "counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE ct direction reply counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE meta mark 255 counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE meta mark and 0xff == 0xff counter return"
|
||||
|
||||
# jump chains
|
||||
nft "add rule $NFTABLE_NAME mangle_prerouting counter jump PSW_DIVERT"
|
||||
@@ -1199,7 +1199,7 @@ add_firewall_rule() {
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT ip daddr @$NFTSET_VPS counter return"
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && nft_rule_dual "PSW_OUTPUT_NAT" "ip daddr" "$NFTSET_BLOCK" "counter reject"
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && nft_rule_dual "PSW_OUTPUT_NAT" "ip daddr" "$NFTSET_WHITE" "counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT meta mark 255 counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_NAT meta mark and 0xff == 0xff counter return"
|
||||
}
|
||||
|
||||
#icmp ipv6-icmp redirect
|
||||
@@ -1239,7 +1239,7 @@ add_firewall_rule() {
|
||||
[ "${USE_BLOCK_LIST}" = "1" ] && nft_rule_dual "PSW_OUTPUT_MANGLE_V6" "ip6 daddr" "$NFTSET_BLOCK6" "counter reject"
|
||||
[ "${USE_DIRECT_LIST}" = "1" ] && nft_rule_dual "PSW_OUTPUT_MANGLE_V6" "ip6 daddr" "$NFTSET_WHITE6" "counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 ct direction reply counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 meta mark 255 counter return"
|
||||
nft "add rule $NFTABLE_NAME PSW_OUTPUT_MANGLE_V6 meta mark and 0xff == 0xff counter return"
|
||||
|
||||
[ -n "$IPT_APPEND_DNS" ] && {
|
||||
local local_dns dns_address dns_port
|
||||
|
||||
@@ -103,6 +103,7 @@ local function curl(url, file)
|
||||
"--connect-timeout 3",
|
||||
"--max-time 300",
|
||||
"--speed-limit 51200 --speed-time 15",
|
||||
"-H 'Accept: */*'",
|
||||
'-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"',
|
||||
"--dump-header -",
|
||||
"-w '\\n%{http_code}'"
|
||||
|
||||
@@ -1708,6 +1708,7 @@ local function curl(url, file, ua, mode)
|
||||
"-fskL",
|
||||
"--retry 3",
|
||||
"--connect-timeout 3",
|
||||
"-H 'Accept: */*'",
|
||||
"-H 'Accept-Encoding: identity'",
|
||||
"--dump-header -",
|
||||
"-w '\\n%{http_code}'"
|
||||
|
||||
@@ -40,9 +40,10 @@ config_n_get() {
|
||||
echo "${ret:=$3}"
|
||||
}
|
||||
|
||||
config_t_set() {
|
||||
config_t_get() {
|
||||
local index=${4:-0}
|
||||
local ret=$(uci -q set "${CONFIG}.@${1}[${index}].${2}=${3}" 2>/dev/null)
|
||||
local ret=$(uci -q get "${CONFIG}.@${1}[${index}].${2}" 2>/dev/null)
|
||||
echo "${ret:=${3}}"
|
||||
}
|
||||
|
||||
first_type() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
. /usr/share/passwall2/utils.sh
|
||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
|
||||
LEASE_FILE=$(config_t_get dnsmasq leasefile "/tmp/dhcp.leases")
|
||||
LEASE_FILE=$(uci -q get dhcp.@dnsmasq[0].leasefile || echo "/tmp/dhcp.leases")
|
||||
HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
|
||||
TMP_FILE="/tmp/dhcp-hosts.tmp"
|
||||
|
||||
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=bandix
|
||||
PKG_VERSION:=0.12.9
|
||||
PKG_VERSION:=0.12.10
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
@@ -13,7 +13,7 @@ include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/rust/rust-values.mk
|
||||
|
||||
# 二进制文件的文件名和URL
|
||||
RUST_BANDIX_VERSION:=0.12.9
|
||||
RUST_BANDIX_VERSION:=0.12.10
|
||||
RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ PKG_SOURCE_URL:=https://github.com/timsaya/bandix/releases/download/v$(RUST_BAND
|
||||
|
||||
# 修正二进制文件校验值
|
||||
ifeq ($(ARCH),aarch64)
|
||||
PKG_HASH:=17803eeb6964187626e6af2e2b0a3b081d87a8017ff2b6505ba313b0c7b8ba7f
|
||||
PKG_HASH:=653e554a95ff1a336def82b9b4899466b6fc62a6e8fd42ff786eaedebfe0b911
|
||||
else ifeq ($(ARCH),arm) # update.sh skip this tag, do not delete this comment
|
||||
# Ref https://github.com/openwrt/packages/blob/master/lang/rust/rust-values.mk
|
||||
|
||||
@@ -39,28 +39,28 @@ else ifeq ($(ARCH),arm) # update.sh skip this tag, do not delete this comment
|
||||
endif
|
||||
|
||||
ifeq ($(ARM_CPU_TYPE),arm-armel)
|
||||
PKG_HASH:=cbf9cf705218211e0a5cd1427049632aa40086831feee2994e9d4b7b2cf46ab4
|
||||
PKG_HASH:=4b6c96715fbda3e2e60c41aa72286fb10da09997a05ed98311cf377c22848e65
|
||||
else ifeq ($(ARM_CPU_TYPE),arm-armhf)
|
||||
PKG_HASH:=2901c5f98e77fce574bcaef1aa8c20acd58ca341f3f3bd5f3bc80d745946f632
|
||||
PKG_HASH:=4280c24049fb25743a8876b7437e4954fbeb00e58ce940c509cdcc70b7cea576
|
||||
else ifeq ($(ARM_CPU_TYPE),armv5te-armel)
|
||||
PKG_HASH:=390840c939cec718570cd8cbf50ed130fd02d7f75b80d74af251bd1ecd0f43a8
|
||||
PKG_HASH:=7ba5247c674ace48c88e9ea65c4cb6ab754a5e5dd1d4cd2911442018545c33a8
|
||||
else ifeq ($(ARM_CPU_TYPE),armv7-armel)
|
||||
PKG_HASH:=965f9e88398f42cf5535612b58629b89c058db46afaf3c814929a7cfc9fa4e3b
|
||||
PKG_HASH:=6c7f4454b3100fc448149caf8cb97dbcb75b284bab93b53dc885a370c496cc29
|
||||
else ifeq ($(ARM_CPU_TYPE),armv7-armhf)
|
||||
PKG_HASH:=f47df37959f73b2188f89faf8c9a3217966ca4c1389f695886f263d28911e8fb
|
||||
PKG_HASH:=41377aa71c2a2da4481112c45b0129f167b96bc4b0c55d3f77309b4b82001891
|
||||
else
|
||||
PKG_HASH:=dummy
|
||||
endif
|
||||
else ifeq ($(ARCH),mips)
|
||||
PKG_HASH:=318587b09b37d1ab8af96466e5191ce32c4dbc92294f5c55826ebe1a88c7bcab
|
||||
PKG_HASH:=a08fae7eae3c852a8b6c310276c881f135eabf86c18f689edd134800dc270e71
|
||||
else ifeq ($(ARCH),mipsel)
|
||||
PKG_HASH:=fd1b12b7cd2034aa8afa89901d0d38784528f10921b6c0f806dac9dcbf523085
|
||||
PKG_HASH:=499134680d7667fd256425d0f86b846dc4d00023b2315c48ed5337a7dae65926
|
||||
else ifeq ($(ARCH),powerpc64)
|
||||
PKG_HASH:=24dab3f89f8930cb925ec1a20d73111e09bae7e6a87954b56a681395660f6ed4
|
||||
PKG_HASH:=f40f51453c274774bdf64a170fe3ea1d87cf3761ba4a679ca8f08bdd2819f2e4
|
||||
else ifeq ($(ARCH),riscv64)
|
||||
PKG_HASH:=916bf42c60c49df5c3ee6f4a60ce542d2b080788445b9bf47f3322eb9a5b66cf
|
||||
PKG_HASH:=4b18915eab8e3435fb6cc1d50298ad84708b14921fb1b60713f37864140aa1ab
|
||||
else ifeq ($(ARCH),x86_64)
|
||||
PKG_HASH:=4909cf2e80861d082c4d4870950259d8c6f14e606f9dfa16c92514f178f3ae40
|
||||
PKG_HASH:=aeab01d678f92f176ba41d08363765ac53719bdaaff26bbd0e93c4fdc12bea20
|
||||
else
|
||||
PKG_HASH:=dummy
|
||||
endif
|
||||
|
||||
@@ -21,13 +21,13 @@ define Download/geoip
|
||||
HASH:=1cba1f0982cf62502fa079c66047c3d0c608196da5b3305671e68f60e917a482
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20260904020013
|
||||
GEOSITE_VER:=20260906035830
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=f82f26c015f9726c763d96a5f658e5b31b285dc094a985e718051e421f350ed6
|
||||
HASH:=8d401b06cde720cdfa9eb0e03751cc73ded08c6d53fa80f92d3f24798382eb92
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202608310142
|
||||
|
||||
Reference in New Issue
Block a user