diff --git a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.css b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.css
deleted file mode 100644
index a99733e18..000000000
--- a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.css
+++ /dev/null
@@ -1,515 +0,0 @@
-#chartjs-tooltip {
- opacity: 0;
- position: absolute;
- background: rgba(0, 0, 0, .7);
- color: white;
- padding: 3px;
- border-radius: 3px;
- transition: all .1s ease;
- pointer-events: none;
- transform: translate(-50%, 0);
- z-index: 20000;
-}
-
-#chartjs-tooltip.above {
- transform: translate(-50%, -100%);
-}
-
-#chartjs-tooltip.above:before {
- border: solid;
- border-color: #111 transparent;
- border-color: rgba(0, 0, 0, .8) transparent;
- border-width: 8px 8px 0 8px;
- bottom: 1em;
- content: "";
- display: block;
- left: 50%;
- top: 100%;
- position: absolute;
- z-index: 99;
- transform: translate(-50%, 0);
-}
-
-.pie label {
- font-weight: bold;
- font-size: 14px;
- display: block;
- margin-bottom: 10px;
- text-align: center;
-}
-
-.kpi ul {
- list-style: none;
-}
-
-.kpi li {
- margin: 10px;
- display: none;
-}
-
-.kpi big {
- font-weight: bold;
-}
-
-.head {
- text-align: center;
- position: relative;
- display: flex;
- flex-wrap: wrap;
- white-space: normal;
-}
-
-.head .pie {
- /* min-width: 200px; */
- padding: 5px;
- flex: 1 1 30%;
-}
-
-.cbi-tooltip .head .pie {
- min-width: 100px;
-}
-
-.head .kpi {
- padding: 5px;
- font-size: smaller;
- text-align: left;
- align-self: center;
- flex: 1 0 33%;
- min-width: 150px;
- display: flex;
- justify-content: center;
-}
-
-.head .kpi ul {
- margin: 0;
-}
-
-.td.double > span {
- display: block;
-}
-
-.cbi-tooltip {
- box-shadow: 0 0 5px #000;
-}
-
-@media screen and (max-width: 992px) {
- .td.hide-xs {
- display: none;
- }
-
- .td.double:not(.hide-xs) > span {
- white-space: nowrap;
- text-align: left;
- }
-
- .td.double:not(.hide-xs) > span:first-child::before {
- content: "IPv4: ";
- font-weight: bold;
- }
-
- .td.double:not(.hide-xs) > span:last-child::before {
- content: "IPv6: ";
- font-weight: bold;
- }
-}
-
-.form-group {
- display: flex;
- align-items: center; /* 垂直居中 */
- margin-bottom: 15px; /* 项间距 */
- gap: 10px; /* Label 和 Input 间距 */
-}
-
-/* 标签固定最小宽度 + 右对齐 */
-.form-label {
- flex: 0 0 120px; /* 不伸缩、不收缩、基础宽度120px */
- text-align: right;
- font-weight: bold;
- color: #333;
-}
-
-.th-sort-asc::after {
- content: " ▲";
-}
-
-.th-sort-desc::after {
- content: " ▼";
-}
-
-.table .th {
- cursor: pointer;
-}
-
-.table .th:hover {
- background-color: #f0f0f0;
-}
-
-/* L7 View Specific Styles */
-.l7-view-container #l7-error-message { color: red; background-color: #ffefef; border: 1px solid red; padding: 10px; margin-bottom: 10px; display: none; }
-.l7-view-container .dashboard-container { display: flex; flex-direction: column; gap: 20px; margin-bottom: 20px; }
-.l7-view-container .line-chart-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; }
-.l7-view-container .kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; }
-.l7-view-container .kpi-card { background-color: #f9f9f9; border-radius: 8px; padding: 15px; text-align: center; border: 1px solid #e0e0e0; }
-.l7-view-container .kpi-card big { display: block; font-size: 1.8em; font-weight: bold; color: #3771c8; }
-.l7-view-container .kpi-card-label { font-size: 0.9em; color: #666; }
-.l7-view-container .chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
-.l7-view-container .chart-card { background-color: #ffffff; border-radius: 8px; padding: 20px; border: 1px solid #e0e0e0; }
-.l7-view-container .chart-card h4 { margin-top: 0; margin-bottom: 15px; text-align: center; font-size: 1.1em; }
-.l7-view-container .l7-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 20px;
- padding: 15px 20px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 8px;
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
- color: white;
-}
-.l7-view-container .l7-controls-left, .l7-view-container .l7-controls-right {
- display: flex;
- align-items: center;
- gap: 15px;
-}
-
-/* Display View Specific Styles */
-.display-view-container .dashboard-container { display: flex; flex-direction: column; gap: 20px; margin-bottom: 20px; }
-.display-view-container .kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; }
-.display-view-container .kpi-card { background-color: #f9f9f9; border-radius: 8px; padding: 15px; text-align: center; border: 1px solid #e0e0e0; }
-.display-view-container .kpi-card big { display: block; font-size: 1.8em; font-weight: bold; color: #3771c8; }
-.display-view-container .kpi-card-label { font-size: 0.9em; color: #666; }
-.display-view-container .chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
-.display-view-container .chart-card { background-color: #ffffff; border-radius: 8px; padding: 20px; border: 1px solid #e0e0e0; }
-.display-view-container .chart-card h4 { margin-top: 0; margin-bottom: 15px; text-align: center; font-size: 1.1em; }
-.display-view-container .display-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 15px 20px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 8px;
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
- margin-top: 20px;
-}
-
-/* Control Groups */
-.control-group {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 8px 12px;
- background-color: rgba(255, 255, 255, 0.15);
- border-radius: 6px;
- backdrop-filter: blur(10px);
-}
-
-.control-icon {
- font-size: 1.2em;
- display: inline-block;
-}
-
-.control-label {
- font-weight: 500;
- color: white;
- margin: 0;
- white-space: nowrap;
-}
-
-.control-input {
- border-radius: 4px;
- border: 1px solid rgba(255, 255, 255, 0.3);
- background-color: rgba(255, 255, 255, 0.9);
- padding: 6px 12px;
- transition: all 0.3s ease;
-}
-
-.control-input:focus {
- background-color: white;
- border-color: #667eea;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
- outline: none;
-}
-
-.control-buttons {
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-.status-group {
- background-color: rgba(255, 255, 255, 0.2);
-}
-
-.last-updated-text {
- color: rgba(255, 255, 255, 0.95);
- font-size: 0.9em;
- font-weight: 500;
-}
-
-/* Enhanced Select Styling */
-.cbi-input-select {
- padding: 6px 30px 6px 12px;
- border-radius: 4px;
- border: 1px solid rgba(255, 255, 255, 0.3);
- background-color: rgba(255, 255, 255, 0.9);
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23667eea' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 8px center;
- background-size: 12px;
- appearance: none;
- cursor: pointer;
- transition: all 0.3s ease;
- font-weight: 500;
-}
-
-.cbi-input-select:hover {
- background-color: white;
- border-color: #667eea;
-}
-
-.cbi-input-select:focus {
- background-color: white;
- border-color: #667eea;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
- outline: none;
-}
-
-/* Button Enhancements for Controls */
-.l7-controls .cbi-button,
-.display-controls .cbi-button {
- padding: 8px 16px;
- border-radius: 6px;
- font-weight: 500;
- transition: all 0.3s ease;
- border: 2px solid transparent;
-}
-
-.cbi-button-action {
- background: rgba(255, 193, 7, 0.9);
- color: #333;
- border-color: rgba(255, 193, 7, 0.3);
-}
-
-.cbi-button-action:hover {
- background: #ffc107;
- transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(255, 193, 7, 0.4);
-}
-
-.cbi-button-positive {
- background: rgba(40, 167, 69, 0.9);
- color: white;
- border-color: rgba(40, 167, 69, 0.3);
-}
-
-.cbi-button-positive:hover {
- background: #28a745;
- transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(40, 167, 69, 0.4);
-}
-
-/* Responsive Controls */
-@media screen and (max-width: 768px) {
- .l7-controls,
- .display-controls {
- flex-direction: column;
- gap: 15px;
- }
-
- .l7-controls-left,
- .l7-controls-right,
- .control-buttons {
- width: 100%;
- justify-content: center;
- }
-
- .control-group {
- justify-content: center;
- }
-}
-
-/* Table Cell Icon Styles */
-.activity-indicator {
- font-size: 12px;
- display: inline-block;
- margin-right: 2px;
-}
-
-.icon, .btn-icon, .th-icon {
- display: inline-block;
- font-style: normal;
- margin-right: 4px;
-}
-
-.th-icon {
- opacity: 0.7;
-}
-
-/* Cell Specific Styles */
-.sid-cell, .id-cell, .host-cell, .hostname-cell, .protocol-cell {
- display: inline-flex;
- align-items: center;
- gap: 4px;
-}
-
-.speed-cell {
- display: inline-flex;
- align-items: center;
- justify-content: flex-end;
- gap: 4px;
- font-weight: 500;
- min-width: 100px;
-}
-
-.speed-cell.download {
- color: #28a745;
-}
-
-.speed-cell.upload {
- color: #007bff;
-}
-
-.volume-cell {
- display: inline-flex;
- align-items: center;
- justify-content: flex-end;
- gap: 4px;
- min-width: 90px;
-}
-
-.volume-cell.download {
- color: #17a2b8;
-}
-
-.volume-cell.upload {
- color: #6610f2;
-}
-
-.packet-cell {
- display: inline-flex;
- align-items: center;
- justify-content: flex-end;
- gap: 4px;
- color: #6c757d;
- min-width: 80px;
-}
-
-/* Data value with monospace font for better alignment */
-.data-value {
- font-family: 'Courier New', Consolas, monospace;
- font-size: 0.95em;
- letter-spacing: 0.5px;
-}
-
-/* Table header enhancements */
-.table .th {
- font-weight: 600;
- padding: 10px 8px;
- white-space: nowrap;
-}
-
-.table .td {
- padding: 8px;
- vertical-align: middle;
-}
-
-.protocol-icon.l7::before {
- content: '';
- display: inline-block;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: #5470c6;
- margin-right: 4px;
-}
-
-.protocol-icon.domain::before {
- content: '';
- display: inline-block;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: #91cc75;
- margin-right: 4px;
-}
-
-/* Button Enhancements */
-.cbi-button {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- transition: all 0.2s ease;
-}
-
-.cbi-button:hover {
- transform: translateY(-1px);
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
-}
-
-.cbi-button-edit {
- background-color: #ffc107;
- border-color: #ffc107;
-}
-
-.cbi-button-edit:hover {
- background-color: #e0a800;
- border-color: #e0a800;
-}
-
-.cbi-button-remove {
- background-color: #dc3545;
- border-color: #dc3545;
- color: white;
-}
-
-.cbi-button-remove:hover {
- background-color: #c82333;
- border-color: #bd2130;
-}
-
-.cbi-button-add {
- background-color: #28a745;
- border-color: #28a745;
- color: white;
-}
-
-.cbi-button-add:hover:not(:disabled) {
- background-color: #218838;
- border-color: #1e7e34;
-}
-
-.cbi-button-add:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-/* Table Row Hover Effects */
-.table .tr:not(.table-titles):not(.placeholder):hover {
- background-color: #f8f9fa;
- transition: background-color 0.2s ease;
-}
-
-/* Active/Inactive Status Styles */
-.activity-indicator[title*="Active"] {
- animation: pulse 2s ease-in-out infinite;
-}
-
-@keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.5; }
-}
-
-/* Responsive Icon Handling */
-@media screen and (max-width: 768px) {
- .icon, .btn-icon {
- margin-right: 2px;
- }
-
- .btn-icon + span {
- display: none;
- }
-
- .th-icon {
- margin-right: 0;
- }
-}
diff --git a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js
index 426085294..1ac0fa375 100644
--- a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js
+++ b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js
@@ -30,219 +30,6 @@ var callGetWanMac = rpc.declare({
expect: { result: {} }
});
-// Generate Device ID: AW + 7 random digits + 12-char MAC (total 21 chars)
-function generateDeviceId(macAddress) {
- var macPart = macAddress.replace(/-/g, ''); // Remove hyphens from MAC
- var randomPart = '';
- for (var i = 0; i < 7; i++) {
- randomPart += Math.floor(Math.random() * 10);
- }
- return 'AW' + randomPart + macPart;
-}
-
-// Generate Location ID: 14 digits (AAAAAABBBCCCCC)
-// AAAAAA: Administrative division code (GB/T 2260) - using Beijing as default (110101)
-// BBB: Service type (100 for commercial internet service locations, positions 7-9)
-// CCCCC: 5-digit random sequence number (positions 10-14)
-function generateLocationId() {
- var adminCode = '110101'; // Default to Beijing Dongcheng District (positions 1-6)
- var serviceType = '100'; // Commercial internet service location (positions 7-9)
- var sequenceNum = '';
-
- // Generate 5-digit random sequence number (positions 10-14)
- for (var i = 0; i < 5; i++) {
- sequenceNum += Math.floor(Math.random() * 10);
- }
-
- return adminCode + serviceType + sequenceNum; // 6 + 3 + 5 = 14 digits
-}
-
-// Auto-fill helper functions
-function showAutoFillModal() {
- ui.showModal(_('Auto Fill'), [
- E('div', { 'class': 'cbi-section' }, [
- E('div', { 'class': 'cbi-section-descr' }, _('Getting device information and location data, please wait...')),
- E('div', { 'id': 'auto-fill-progress' }, [
- E('ul', { 'style': 'margin: 10px 0;' }, [
- E('li', { 'id': 'mac-status' }, _('Getting WAN MAC address...')),
- E('li', { 'id': 'device-id-status' }, _('Generating Device ID...')),
- E('li', { 'id': 'location-id-status' }, _('Generating Location ID...')),
- E('li', { 'id': 'location-status' }, _('Getting location coordinates...'))
- ])
- ])
- ])
- ]);
-}
-
-function updateMacStatus(macAddr, isSuccess, errorMsg, isFallback) {
- var statusElement = document.getElementById('mac-status');
- if (isSuccess) {
- var prefix = isFallback ? _('✓ WAN MAC address obtained (fallback): ') : _('✓ WAN MAC address obtained: ');
- var suffix = isFallback ? '' : _(' (interface: ') + arguments[4] + _(')');
- statusElement.innerHTML = prefix + macAddr + suffix;
- } else {
- statusElement.innerHTML = _('✗ Failed to get WAN MAC address: ') + (errorMsg || _('Unknown error'));
- }
-}
-
-function generateAndUpdateIds(macAddr) {
- var deviceId = generateDeviceId(macAddr);
- var locationId = generateLocationId();
-
- document.getElementById('device-id-status').innerHTML = _('✓ Device ID generated: ') + deviceId;
- document.getElementById('location-id-status').innerHTML = _('✓ Location ID generated: ') + locationId;
-
- return { deviceId: deviceId, locationId: locationId };
-}
-
-function getMacAddressPromise(results) {
- return callGetWanMac().then(function (response) {
- if (response && response.status === 'success') {
- var macAddr = response.mac.toUpperCase().replace(/:/g, '-');
- results.macAddress = macAddr;
- updateMacStatus(macAddr, true, null, false, response.interface);
-
- var ids = generateAndUpdateIds(macAddr);
- results.deviceId = ids.deviceId;
- results.locationId = ids.locationId;
- } else {
- return getMacAddressFallback(results);
- }
- }).catch(function (error) {
- return getMacAddressFallback(results);
- });
-}
-
-function getMacAddressFallback(results) {
- document.getElementById('mac-status').innerHTML = _('RPC failed, trying fallback...');
-
- return fs.exec('/bin/sh', ['-c', 'ip route | grep default | awk \'{print $5}\' | head -n1 | xargs -I {} cat /sys/class/net/{}/address 2>/dev/null || echo "failed"']).then(function (response) {
- if (response.code === 0 && response.stdout && response.stdout.trim() !== 'failed') {
- var macAddr = response.stdout.trim().toUpperCase().replace(/:/g, '-');
- results.macAddress = macAddr;
- updateMacStatus(macAddr, true, null, true);
-
- var ids = generateAndUpdateIds(macAddr);
- results.deviceId = ids.deviceId;
- results.locationId = ids.locationId;
- } else {
- updateMacStatus(null, false, _('Unknown error'));
- document.getElementById('device-id-status').innerHTML = _('✗ Cannot generate Device ID without MAC address');
- }
- }).catch(function (error) {
- updateMacStatus(null, false, error.message);
- document.getElementById('device-id-status').innerHTML = _('✗ Cannot generate Device ID due to MAC error');
- });
-}
-
-function formatCoordinate(coord) {
- var num = parseFloat(coord).toFixed(6);
- if (num >= 0) {
- return num.padStart(10, '0');
- } else {
- return '-' + Math.abs(num).toFixed(6).padStart(9, '0');
- }
-}
-
-function getLocationPromise(results) {
- return callGetLocation().then(function (response) {
- if (response && response.status === 'success') {
- var lat = formatCoordinate(response.lat);
- var lon = formatCoordinate(response.lon);
-
- results.longitude = lon;
- results.latitude = lat;
- document.getElementById('location-status').innerHTML = _('✓ Location obtained: ') + lat + ', ' + lon;
- } else {
- var errorMsg = (response && response.message) ? response.message : _('Unknown error');
- document.getElementById('location-status').innerHTML = _('✗ Failed to get location: ') + errorMsg;
- }
- }).catch(function (error) {
- return getLocationFallback(results);
- });
-}
-
-function getLocationFallback(results) {
- document.getElementById('location-status').innerHTML = _('RPC failed, trying direct method...');
-
- return fs.exec('/bin/sh', ['-c', 'curl -s --connect-timeout 10 --max-time 30 "https://ipapi.co/json" 2>/dev/null || curl -s --connect-timeout 10 --max-time 30 "http://ipinfo.io/json" 2>/dev/null || echo "failed"']).then(function (response) {
- if (response.code === 0 && response.stdout && response.stdout.trim() !== 'failed') {
- try {
- var data = JSON.parse(response.stdout);
- var lat, lon;
-
- if (data.latitude && data.longitude) {
- lat = formatCoordinate(data.latitude);
- lon = formatCoordinate(data.longitude);
- } else if (data.loc) {
- var coords = data.loc.split(',');
- lat = formatCoordinate(coords[0]);
- lon = formatCoordinate(coords[1]);
- }
-
- if (lat && lon) {
- results.longitude = lon;
- results.latitude = lat;
- document.getElementById('location-status').innerHTML = _('✓ Location obtained (fallback): ') + lat + ', ' + lon;
- } else {
- document.getElementById('location-status').innerHTML = _('✗ No valid coordinates found');
- }
- } catch (e) {
- document.getElementById('location-status').innerHTML = _('✗ Failed to parse location data');
- }
- } else {
- document.getElementById('location-status').innerHTML = _('✗ Failed to get location data');
- }
- }).catch(function (fallbackError) {
- document.getElementById('location-status').innerHTML = _('✗ Error getting location: ') + fallbackError.message;
- });
-}
-
-function fillFormFields(results) {
- var successCount = 0;
- var totalFields = 0;
- var messages = [];
- var fieldMappings = [
- { result: 'macAddress', selector: 'ap_mac_address', label: _('MAC Address: ') },
- { result: 'deviceId', selector: 'ap_device_id', label: _('Device ID: ') },
- { result: 'locationId', selector: 'location_id', label: _('Location ID: ') },
- { result: 'longitude', selector: 'ap_longitude', label: _('Longitude: ') },
- { result: 'latitude', selector: 'ap_latitude', label: _('Latitude: ') }
- ];
-
- fieldMappings.forEach(function (mapping) {
- if (results[mapping.result]) {
- var field = document.querySelector('input[data-name="' + mapping.selector + '"]') ||
- document.querySelector('input[name*="' + mapping.selector + '"]') ||
- document.querySelector('input[id*="' + mapping.selector + '"]') ||
- document.querySelector('#cbid\\.wifidogx\\.default\\.' + mapping.selector);
-
- if (field) {
- field.value = results[mapping.result];
- field.dispatchEvent(new Event('input', { bubbles: true }));
- field.dispatchEvent(new Event('change', { bubbles: true }));
- successCount++;
- messages.push(mapping.label + results[mapping.result]);
- }
- totalFields++;
- }
- });
-
- return { successCount: successCount, totalFields: totalFields, messages: messages };
-}
-
-function showAutoFillResult(stats) {
- ui.hideModal();
-
- if (stats.successCount === stats.totalFields && stats.totalFields > 0) {
- ui.addNotification(null, E('p', _('Auto fill completed successfully! All fields have been filled.') + '
' + stats.messages.join('
')), 'info');
- } else if (stats.successCount > 0) {
- ui.addNotification(null, E('p', _('Auto fill partially completed.') + ' ' + stats.successCount + '/' + stats.totalFields + ' ' + _('fields filled successfully.') + '
' + stats.messages.join('
')), 'warning');
- } else {
- ui.addNotification(null, E('p', _('Auto fill failed. No fields could be filled. Please check your network connection and try again.')), 'error');
- }
-}
-
function getServiceStatus() {
return L.resolveDefault(callServiceList('wifidogx'), {}).then(function (res) {
var isRunning = false;
@@ -253,6 +40,25 @@ function getServiceStatus() {
});
}
+function generateDeviceId(macAddress) {
+ var macPart = macAddress.replace(/-/g, '');
+ var randomPart = '';
+ for (var i = 0; i < 7; i++) {
+ randomPart += Math.floor(Math.random() * 10);
+ }
+ return 'AW' + randomPart + macPart;
+}
+
+function generateLocationId() {
+ var adminCode = '110101';
+ var serviceType = '100';
+ var sequenceNum = '';
+ for (var i = 0; i < 5; i++) {
+ sequenceNum += Math.floor(Math.random() * 10);
+ }
+ return adminCode + serviceType + sequenceNum;
+}
+
function renderStatus(isRunning) {
var renderHTML = "";
var spanTemp = '%s %s';
@@ -284,7 +90,6 @@ return view.extend({
s.tab('gateway', _('Gateway Settings'));
s.tab('advanced', _('Advanced Settings'));
s.tab('rule', _('Rule Settings'));
- s.tab('qos', _('QoS Settings'));
s.tab('location', _('Authentication Location Settings'));
// basic settings
@@ -368,7 +173,7 @@ return view.extend({
o = s.taboption('basic', form.Flag, 'disable_portal_auth', _('Disable Portal Authentication'),
_('When enabled, users can access the internet without portal authentication. Firewall redirect rules will not be created. Use this mode for pure traffic statistics without captive portal.'));
o.rmempty = false;
- o.default = '1';
+ o.default = '0';
o = s.taboption('basic', form.ListValue, 'log_level', _('Log Level'),
_('The log level of the apfree-wifidog.'));
@@ -380,7 +185,7 @@ return view.extend({
o.value(2, _('Critical'));
o.value(1, _('Alert'));
o.value(0, _('Emergency'));
- o.defaulValue = 0;
+ o.default = 0;
o.optional = false;
// gateway settings
@@ -392,7 +197,7 @@ return view.extend({
o = ss.option(form.Flag, 'gateway_auth_enabled', _('Auth Enabled'),
_('Enable the authentication of the gateway.'));
o.rmempty = false;
- o.defaulValue = true;
+ o.default = true;
o = ss.option(widgets.DeviceSelect, 'gateway_name', _('Gateway Name'));
o.rmempty = false;
@@ -425,14 +230,14 @@ return view.extend({
o.datatype = 'uinteger';
o.rmempty = false;
o.optional = false;
- o.defaulValue = 60;
+ o.default = 60;
o = s.taboption('advanced', form.Value, 'client_timeout', _('Client Timeout'),
_('The timeout of the client.'));
o.datatype = 'uinteger';
o.rmempty = false;
o.optional = false;
- o.defaulValue = 5;
+ o.default = 5;
o = s.taboption('advanced', form.Flag, 'wired_passed', _('Wired Passed'),
_('Wired users do not need to authenticate to access the internet.'));
@@ -441,23 +246,30 @@ return view.extend({
o = s.taboption('advanced', form.Flag, 'apple_cna', _('Apple CNA'),
_('Enable Apple Captive Network Assistant.'));
o.rmempty = false;
- o.defaulValue = false;
+ o.default = false;
o = s.taboption('advanced', form.Flag, 'js_filter', _('JS Filter'),
_('Enable JS redirect.'));
o.rmempty = false;
- o.defaulValue = true;
+ o.default = true;
o = s.taboption('advanced', form.Flag, 'enable_anti_nat', _('Enable Anti NAT'),
_('Enable Anti NAT devices.'));
o.rmempty = false;
- o.defaulValue = false;
+ o.default = false;
+
+ o = s.taboption('advanced', form.Value, 'privileged_ops_secret', _('Privileged Ops Secret'),
+ _('Local secret required for AWAS cloud platform to dynamically authorize remote OTA, reboot and system changes. Change this from the default on production devices.'));
+ o.password = true;
+ o.rmempty = false;
+ o.optional = false;
+ o.default = 'chawrt@2026';
o = s.taboption('advanced', form.Value, 'ttl_value', _('TTL Value'),
_('The TTL value of the gateway support.'));
o.datatype = 'string';
o.rmempty = false;
- o.defaulValue = '64,128';
+ o.default = '64,128';
o.depends('enable_anti_nat', '1');
o = s.taboption('advanced', form.Value, 'anti_nat_permit_macs', _('Anti NAT Permit MAC'),
@@ -466,17 +278,87 @@ return view.extend({
o.rmempty = true;
o.depends('enable_anti_nat', '1');
- o = s.taboption('advanced', form.Flag, 'enable_event_log', _('Enable Internet Access Log'),
- _('Enable logging of internet access events and user activities.'));
- o.rmempty = false;
- o.defaulValue = false;
-
// Auth Server Settings
o = s.taboption('auth', form.SectionValue, '_auth', form.GridSection, 'auth');
ss = o.subsection;
ss.addremove = true;
ss.nodescriptions = true;
+ ss.handleAdd = function (ev, name) {
+ var self = this;
+ if (name) {
+ var longconnSections = uci.sections('wifidogx', 'longconn');
+ for (var i = 0; i < longconnSections.length; i++) {
+ if (longconnSections[i]['.name'] === name) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(name)), 'error');
+ return Promise.resolve();
+ }
+ }
+ var authSections = uci.sections('wifidogx', 'auth');
+ for (var i = 0; i < authSections.length; i++) {
+ if (authSections[i]['.name'] === name) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(name)), 'error');
+ return Promise.resolve();
+ }
+ }
+ }
+ return this.super('handleAdd', [ev, name]);
+ };
+
+ ss.handleRename = function (section_id, ev) {
+ var self = this;
+ var oldName = section_id;
+ var nameEl = E('input', {
+ 'type': 'text',
+ 'class': 'cbi-input-text',
+ 'id': 'auth-rename-input',
+ 'value': oldName,
+ 'style': 'width: 100%;'
+ });
+ ui.showModal(_('Rename Authentication Server'), [
+ E('div', { 'class': 'cbi-section' }, [
+ E('div', { 'class': 'cbi-section-descr' }, _('Enter a new unique name for this authentication server profile.')),
+ E('div', { 'class': 'cbi-section-table' }, [
+ E('div', { 'class': 'tr' }, [
+ E('div', { 'class': 'td' }, _('Name')),
+ E('div', { 'class': 'td' }, [nameEl])
+ ])
+ ])
+ ]),
+ E('div', { 'class': 'right' }, [
+ E('button', { 'class': 'btn', 'click': function () { ui.hideModal(); } }, _('Cancel')),
+ E('button', { 'class': 'btn cbi-button cbi-button-positive', 'click': function () {
+ var newName = nameEl.value.trim();
+ if (!newName) {
+ ui.addNotification(null, E('p', _('Please enter a name.')), 'error');
+ return;
+ }
+ if (newName === oldName) {
+ ui.hideModal();
+ return;
+ }
+ var longconnSections = uci.sections('wifidogx', 'longconn');
+ for (var i = 0; i < longconnSections.length; i++) {
+ if (longconnSections[i]['.name'] === newName) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(newName)), 'error');
+ return;
+ }
+ }
+ var authSections = uci.sections('wifidogx', 'auth');
+ for (var i = 0; i < authSections.length; i++) {
+ if (authSections[i]['.name'] === newName && authSections[i]['.name'] !== oldName) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(newName)), 'error');
+ return;
+ }
+ }
+ ui.hideModal();
+ uci.rename('wifidogx', section_id, newName);
+ self.map.save(null, true);
+ }}, _('Rename'))
+ ])
+ ]);
+ };
+
o = ss.option(form.Value, 'auth_server_hostname', _('Auth Server Hostname'),
_('The domain or IP address of the authentication server.'));
o.rmempty = false;
@@ -499,29 +381,6 @@ return view.extend({
o.optional = false;
o.default = '/wifidog/';
-
- // QoS settings
- o = s.taboption('qos', form.Flag, 'enable_qos', _('Enable Global QoS'),
- _('Enable Global QoS.'));
- o.rmempty = false;
- o.defaulValue = false;
-
- o = s.taboption('qos', form.Value, 'qos_up', _('Global QoS Up'),
- _('The global QoS up value(Mbps).'));
- o.datatype = 'uinteger';
- o.rmempty = true;
- o.optional = true;
- o.defaulValue = 0;
- o.depends('enable_qos', '1');
-
- o = s.taboption('qos', form.Value, 'qos_down', _('Global QoS Down'),
- _('The global QoS down value(Mbps).'));
- o.datatype = 'uinteger';
- o.rmempty = true;
- o.optional = true;
- o.defaulValue = 0;
- o.depends('enable_qos', '1');
-
// Authentication Location Settings
// Add auto-fill button
o = s.taboption('location', form.Button, '_auto_fill', _('Auto Fill All Fields'),
@@ -544,11 +403,8 @@ return view.extend({
getLocationPromise(results)
];
- // Wait for all promises to complete and fill form fields
Promise.all(promises).then(function () {
- setTimeout(function () {
- fillFormFields(results);
- }, 2000);
+ fillFormFields(results);
});
};
@@ -583,10 +439,11 @@ return view.extend({
results.locationId = generateLocationId();
document.getElementById('location-id-status').innerHTML = _('✓ Location ID generated: ') + results.locationId;
} else {
+ document.getElementById('mac-status').innerHTML = _('RPC returned error, trying fallback...');
return getMacAddressFallback(results);
}
}).catch(function (error) {
- document.getElementById('mac-status').innerHTML = _('✗ RPC call failed, trying fallback...');
+ document.getElementById('mac-status').innerHTML = _('RPC call failed, trying fallback...');
return getMacAddressFallback(results);
});
}
@@ -625,8 +482,8 @@ return view.extend({
formatAndSetCoordinates(results, lat, lon);
document.getElementById('location-status').innerHTML = _('✓ Location obtained: ') + results.latitude + ', ' + results.longitude;
} else {
- var errorMsg = (response && response.message) ? response.message : 'Unknown error';
- document.getElementById('location-status').innerHTML = _('✗ Failed to get location: ') + errorMsg;
+ document.getElementById('location-status').innerHTML = _('RPC returned error, trying fallback...');
+ return getLocationFallback(results);
}
}).catch(function (error) {
document.getElementById('location-status').innerHTML = _('RPC failed, trying direct method...');
@@ -741,8 +598,6 @@ return view.extend({
o.rmempty = false;
o.datatype = 'string';
o.optional = false;
- o.depends('auth_server_mode', 'cloud');
- o.depends('auth_server_mode', 'bypass');
o.placeholder = '110101100123456';
o.validate = function (section_id, value) {
if (!value || value === '')
@@ -766,7 +621,8 @@ return view.extend({
// Validate service type code (7-9 digits)
var serviceType = value.substring(6, 9);
- if (serviceType !== '100' && serviceType !== '2' + value.charAt(7) + value.charAt(8) && serviceType !== '3' + value.charAt(7) + value.charAt(8)) {
+ var firstDigit = serviceType.charAt(0);
+ if (serviceType !== '100' && firstDigit !== '2' && firstDigit !== '3') {
return _('Service type code (7-9 digits) must be "100" for commercial internet service locations, "2XX" for non-commercial locations, or "3XX" for WiFi wireless collection terminals');
}
@@ -936,6 +792,81 @@ return view.extend({
ss.addremove = true;
ss.nodescriptions = true;
+ ss.handleAdd = function (ev, name) {
+ var self = this;
+ if (name) {
+ var authSections = uci.sections('wifidogx', 'auth');
+ for (var i = 0; i < authSections.length; i++) {
+ if (authSections[i]['.name'] === name) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(name)), 'error');
+ return Promise.resolve();
+ }
+ }
+ var longconnSections = uci.sections('wifidogx', 'longconn');
+ for (var i = 0; i < longconnSections.length; i++) {
+ if (longconnSections[i]['.name'] === name) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(name)), 'error');
+ return Promise.resolve();
+ }
+ }
+ }
+ return this.super('handleAdd', [ev, name]);
+ };
+
+ ss.handleRename = function (section_id, ev) {
+ var self = this;
+ var oldName = section_id;
+ var nameEl = E('input', {
+ 'type': 'text',
+ 'class': 'cbi-input-text',
+ 'id': 'longconn-rename-input',
+ 'value': oldName,
+ 'style': 'width: 100%;'
+ });
+ ui.showModal(_('Rename Long Connection Profile'), [
+ E('div', { 'class': 'cbi-section' }, [
+ E('div', { 'class': 'cbi-section-descr' }, _('Enter a new unique name for this long connection profile.')),
+ E('div', { 'class': 'cbi-section-table' }, [
+ E('div', { 'class': 'tr' }, [
+ E('div', { 'class': 'td' }, _('Name')),
+ E('div', { 'class': 'td' }, [nameEl])
+ ])
+ ])
+ ]),
+ E('div', { 'class': 'right' }, [
+ E('button', { 'class': 'btn', 'click': function () { ui.hideModal(); } }, _('Cancel')),
+ E('button', { 'class': 'btn cbi-button cbi-button-positive', 'click': function () {
+ var newName = nameEl.value.trim();
+ if (!newName) {
+ ui.addNotification(null, E('p', _('Please enter a name.')), 'error');
+ return;
+ }
+ if (newName === oldName) {
+ ui.hideModal();
+ return;
+ }
+ var authSections = uci.sections('wifidogx', 'auth');
+ for (var i = 0; i < authSections.length; i++) {
+ if (authSections[i]['.name'] === newName) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(newName)), 'error');
+ return;
+ }
+ }
+ var longconnSections = uci.sections('wifidogx', 'longconn');
+ for (var i = 0; i < longconnSections.length; i++) {
+ if (longconnSections[i]['.name'] === newName && longconnSections[i]['.name'] !== oldName) {
+ ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(newName)), 'error');
+ return;
+ }
+ }
+ ui.hideModal();
+ uci.rename('wifidogx', section_id, newName);
+ self.map.save(null, true);
+ }}, _('Rename'))
+ ])
+ ]);
+ };
+
o = ss.option(form.ListValue, 'long_conn_mode', _('Connection Mode'),
_('The type of persistent connection to the remote management server.'));
o.value('ws', _('WebSocket (ws://)'));
@@ -1011,7 +942,7 @@ return view.extend({
// rule settings
o = s.taboption('rule', form.DynamicList, 'trusted_wildcard_domains', _('Trusted Wildcard Domains'),
- _('The trusted wildcard domains of the gateway'));
+ _('Requires aw-bpf DNS XDP (dns_ringbuf_portal). Patterns like .example.com; resolved IPs are added to the captive portal firewall (inet wifidogx), not inet awbpf.'));
o.rmempty = true;
o.optional = true;
o.datatype = 'wildcard';
@@ -1025,7 +956,7 @@ return view.extend({
o.placeholder = 'www.example.com';
o = s.taboption('rule', form.DynamicList, 'trusted_macs', _('Trusted MACs'),
- _('The trusted wildcard domains of the gateway.'));
+ _('The trusted MAC addresses of the gateway.'));
o.rmempty = true;
o.optional = true;
o.datatype = 'macaddr';
diff --git a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/display.js b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/display.js
deleted file mode 100644
index 644dc872a..000000000
--- a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/display.js
+++ /dev/null
@@ -1,650 +0,0 @@
-'use strict';
-'require view';
-'require fs';
-'require ui';
-'require poll';
-'require rpc';
-'require dom';
-'require uci';
-
-// Global variables from original display.js
-var chartRegistry = {};
-var hostNames = {}; // mac => hostname
-var hostInfo = {}; // ip => mac
-var hostNameMacSectionId = "";
-var isPaused = false;
-var lastUpdated = null;
-
-// Line chart variables (from l7.js)
-var downloadLineChart = {}, uploadLineChart = {};
-var lineCategories = { ipv4: [], ipv6: [], mac: [] };
-var downloadSeriesData = { ipv4: {}, ipv6: {}, mac: {} };
-var uploadSeriesData = { ipv4: {}, ipv6: {}, mac: {} };
-
-// Color palette for chart series
-var colorPalette = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
-
-var resizeListenerAdded = false;
-
-// Pre-fill with 60 empty points for a smooth start
-['ipv4', 'ipv6', 'mac'].forEach(function(type) {
- for (var i = 0; i < 60; i++) {
- lineCategories[type].push('');
- }
-});
-
-// Helper to convert hex to rgba (from l7.js)
-function hexToRgba(hex, opacity) {
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
- return result ?
- 'rgba(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ', ' + opacity + ')' :
- null;
-};
-
-return view.extend({
- // --- Core Data Logic from display.js ---
-
- loadHostNames: async function() {
- try {
- await uci.sections('hostnames', "hostname", function (params) {
- hostNameMacSectionId = params['.name'];
- for (var key in params) {
- if (key.startsWith('.')) continue;
- var macAddr = key.split('_').join(':');
- hostNames[macAddr] = params[key];
- }
- });
-
- const dhcpLeases = await fs.exec_direct('/usr/bin/awk', ['-F', ' ', '{print $2, $3, $4}', '/tmp/dhcp.leases'], 'text');
- dhcpLeases.split('\n').forEach(function(line) {
- if (line === '') return;
- const [mac, ip, hostname] = line.split(' ');
- if (!hostNames.hasOwnProperty(mac)) {
- hostNames[mac] = hostname;
- }
- });
-
- const arp = await fs.exec_direct('/usr/bin/awk', ['-F', ' ', '{print $1, $4}', '/proc/net/arp'], 'text');
- arp.split('\n').forEach(function(line, i) {
- if (i === 0 || line === '') return;
- const [ip, mac] = line.split(' ');
- hostInfo[ip] = mac;
- });
-
- } catch (e) {
- console.error('Error getting host names:', e);
- }
- },
-
- loadHostSpeedData: async function() {
- var self = this;
- try {
- const results = await Promise.all([
- fs.exec_direct('/usr/bin/aw-bpfctl', ['ipv4', 'json'], 'json'),
- fs.exec_direct('/usr/bin/aw-bpfctl', ['ipv6', 'json'], 'json'),
- fs.exec_direct('/usr/bin/aw-bpfctl', ['mac', 'json'], 'json')
- ]);
-
- const defaultData = {status: "success", data: []};
- const ipv4Data = results[0] || defaultData;
- const ipv6Data = results[1] || defaultData;
- const macData = results[2] || defaultData;
-
- ipv4Data.data.forEach(function(item) {
- const mac = hostInfo[item.ip];
- if (mac) {
- item.mac = mac;
- item.hostname = hostNames[mac];
- }
- });
- macData.data.forEach(function(item) {
- const mac = item.mac;
- if (mac) {
- item.hostname = hostNames[mac];
- }
- });
-
- self.renderHostSpeed(ipv4Data, "ipv4");
- self.renderHostSpeed(ipv6Data, "ipv6");
- self.renderHostSpeed(macData, "mac");
-
- lastUpdated = new Date();
- var timestampEl = document.getElementById('display-last-updated');
- if (timestampEl) {
- timestampEl.textContent = _('Last updated: %s').format(lastUpdated.toLocaleTimeString());
- }
-
- } catch (e) {
- console.error('Error polling data:', e);
- }
- },
-
- pollData: function() {
- poll.add(L.bind(async function() {
- if (isPaused) return;
- await this.loadHostNames();
- await this.loadHostSpeedData();
- }, this), 5);
- },
-
- // --- UI Rendering and Interaction (New structure based on l7.js) ---
-
- pie: function(id, data, valueFormatter) {
- var total = data.reduce(function(n, d) { return n + d.value; }, 0);
- data.sort(function(a, b) { return b.value - a.value; });
-
- if (total === 0) {
- data = [{ value: 1, color: '#cccccc', name: _('no traffic') }];
- }
-
- data.forEach(function(d, i) {
- if (!d.color) {
- var hue = (i * 137.508) % 360;
- d.color = 'hsl(' + hue + ', 75%, 55%)';
- }
- });
-
- var option = {
- tooltip: {
- trigger: 'item',
- formatter: function(params) {
- if (valueFormatter) {
- // 将 ECharts params 对象转换为自定义格式
- return valueFormatter({
- name: params.name,
- value: params.value,
- percent: params.percent.toFixed(2)
- });
- }
- return params.name + ': ' + params.value + ' (' + params.percent.toFixed(2) + '%)';
- }
- },
- series: [{
- type: 'pie',
- radius: ['25%', '80%'],
- avoidLabelOverlap: false,
- padAngle: 10,
- itemStyle: { borderRadius: 10, borderColor: '#fff', borderWidth: 2 },
- label: { show: false, position: 'center' },
- emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
- labelLine: { show: false },
- data: data.map(function(d) {
- return { value: d.value, name: d.label || d.name, itemStyle: { color: d.color } };
- })
- }]
- };
-
- var dom = typeof id === 'string' ? document.getElementById(id) : id;
- if (!chartRegistry[id]) {
- chartRegistry[id] = echarts.init(dom);
- }
- chartRegistry[id].setOption(option, true);
- return chartRegistry[id];
- },
-
- updateStackedLineCharts: function(type, perHostDownload, perHostUpload) {
- var now = new Date().toLocaleTimeString();
- lineCategories[type].push(now);
- lineCategories[type].shift();
-
- var processChartData = function(seriesData, perHostData) {
- var allHosts = Object.keys(seriesData);
- Object.keys(perHostData).forEach(function(host) {
- if (allHosts.indexOf(host) === -1) {
- allHosts.push(host);
- }
- });
-
- allHosts.forEach(function(host) {
- if (!seriesData[host]) {
- seriesData[host] = Array(59).fill(0);
- }
- var rate = perHostData[host] || 0;
- seriesData[host].push(rate);
- seriesData[host].shift();
- });
-
- return Object.keys(seriesData).map(function(host, index) {
- var color = colorPalette[index % colorPalette.length];
- return {
- name: host,
- type: 'line',
- stack: 'Total',
- smooth: true,
- lineStyle: { width: 1, color: color },
- showSymbol: false,
- itemStyle: { color: color },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: hexToRgba(color, 0.5) },
- { offset: 1, color: hexToRgba(color, 0) }
- ])
- },
- data: seriesData[host]
- };
- });
- };
-
- var downloadChartSeries = processChartData(downloadSeriesData[type], perHostDownload);
- var uploadChartSeries = processChartData(uploadSeriesData[type], perHostUpload);
-
- var legendData = downloadChartSeries.map(function(s) { return s.name; });
-
- if (downloadLineChart[type]) {
- downloadLineChart[type].setOption({
- legend: { data: legendData, type: 'scroll', top: 0, left: 'center' },
- series: downloadChartSeries,
- xAxis: { data: lineCategories[type] }
- });
- }
-
- if (uploadLineChart[type]) {
- uploadLineChart[type].setOption({
- legend: { data: legendData, type: 'scroll', top: 0, left: 'center' },
- series: uploadChartSeries,
- xAxis: { data: lineCategories[type] }
- });
- }
- },
-
- renderHostSpeed: function(data, type) {
- if (!data || data.status !== "success" || !Array.isArray(data.data)) return;
-
- var rows = [];
- var txRateData = [], rxRateData = [];
- var txVolumeData = [], rxVolumeData = [];
- var tx_rate_total = 0, rx_rate_total = 0;
- var tx_bytes_total = 0, rx_bytes_total = 0;
- var perHostTxRate = {};
- var perHostRxRate = {};
-
- data.data.forEach(item => {
- if (!item || !item.incoming || !item.outgoing) return;
-
- var host = item.ip || item.mac || '';
- var hostname = item.hostname || hostNames[item.mac] || '';
- var displayName = hostname || host;
-
- // 判断连接是否活跃
- var isActive = item.incoming.rate > 0 || item.outgoing.rate > 0;
- var activityIcon = isActive ? '🟢' : '⚪';
-
- rows.push([
- E('span', { 'class': 'host-cell' }, [
- E('span', { 'class': 'activity-indicator', 'title': isActive ? _('Active') : _('Inactive') }, activityIcon),
- E('span', {}, ' ' + host)
- ]),
- E('span', { 'class': 'hostname-cell' }, [
- E('span', { 'class': 'icon' }, hostname ? '👤' : '❓'),
- E('span', {}, ' ' + (hostname || _('Unknown')))
- ]),
- E('span', { 'class': 'speed-cell download' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mBps'.format(item.incoming.rate))
- ]),
- E('span', { 'class': 'volume-cell download' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mB'.format(item.incoming.total_bytes))
- ]),
- E('span', { 'class': 'packet-cell download' }, [
- E('span', { 'class': 'data-value' }, '%1000.2mP'.format(item.incoming.total_packets))
- ]),
- E('span', { 'class': 'speed-cell upload' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mBps'.format(item.outgoing.rate))
- ]),
- E('span', { 'class': 'volume-cell upload' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mB'.format(item.outgoing.total_bytes))
- ]),
- E('span', { 'class': 'packet-cell upload' }, [
- E('span', { 'class': 'data-value' }, '%1000.2mP'.format(item.outgoing.total_packets))
- ]),
- E('div', { 'class': 'button-container' }, [
- E('button', {
- 'class': 'btn cbi-button cbi-button-edit',
- 'style': 'margin-right: 5px;',
- 'click': ui.createHandlerFn(this, () => this.handleEditSpeed(host, item.mac, hostname, type))
- }, [
- E('span', { 'class': 'btn-icon' }, '✏️'),
- E('span', {}, ' ' + _('Edit'))
- ]),
- E('button', {
- 'class': 'btn cbi-button cbi-button-remove',
- 'click': ui.createHandlerFn(this, () => this.handleDeleteHost(host, type))
- }, [
- E('span', { 'class': 'btn-icon' }, '🗑️'),
- E('span', {}, ' ' + _('Delete'))
- ])
- ])
- ]);
- rx_rate_total += item.outgoing.rate;
- tx_rate_total += item.incoming.rate;
- rx_bytes_total += item.outgoing.total_bytes;
- tx_bytes_total += item.incoming.total_bytes;
-
- rxRateData.push({ value: item.outgoing.rate, label: displayName });
- txRateData.push({ value: item.incoming.rate, label: displayName });
- rxVolumeData.push({ value: item.outgoing.total_bytes, label: displayName });
- txVolumeData.push({ value: item.incoming.total_bytes, label: displayName });
-
- perHostTxRate[displayName] = (perHostTxRate[displayName] || 0) + item.incoming.rate;
- perHostRxRate[displayName] = (perHostRxRate[displayName] || 0) + item.outgoing.rate;
- });
-
- this.updateStackedLineCharts(type, perHostTxRate, perHostRxRate);
-
- var table = document.getElementById(type + '-speed-data');
- cbi_update_table(table, rows, E('em', _('No data recorded yet.')));
-
- this.pie(type + '-tx-rate-pie', txRateData, (p) => `${p.name}: ${'%1024.2mBps'.format(p.value)} (${p.percent}%)`);
- this.pie(type + '-rx-rate-pie', rxRateData, (p) => `${p.name}: ${'%1024.2mBps'.format(p.value)} (${p.percent}%)`);
- this.pie(type + '-tx-volume-pie', txVolumeData, (p) => `${p.name}: ${'%1024.2mB'.format(p.value)} (${p.percent}%)`);
- this.pie(type + '-rx-volume-pie', rxVolumeData, (p) => `${p.name}: ${'%1024.2mB'.format(p.value)} (${p.percent}%)`);
-
- var hostEl = document.getElementById(type + '-host-val');
- if (hostEl) hostEl.textContent = data.data.length;
-
- var txRateEl = document.getElementById(type + '-tx-rate-val');
- if (txRateEl) txRateEl.textContent = '%1024.2mBps'.format(tx_rate_total);
-
- var rxRateEl = document.getElementById(type + '-rx-rate-val');
- if (rxRateEl) rxRateEl.textContent = '%1024.2mBps'.format(rx_rate_total);
-
- var txVolEl = document.getElementById(type + '-tx-volume-val');
- if (txVolEl) txVolEl.textContent = '%1024.2mB'.format(tx_bytes_total);
-
- var rxVolEl = document.getElementById(type + '-rx-volume-val');
- if (rxVolEl) rxVolEl.textContent = '%1024.2mB'.format(rx_bytes_total);
- },
-
- // --- Interaction Handlers from display.js ---
-
- handleDeleteHost: function(host, type) {
- ui.showModal(_('Delete Host'), [
- E('p', _('Are you sure you want to delete this host?')),
- E('div', { 'class': 'right' }, [
- E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
- E('button', { 'class': 'btn cbi-button-negative', 'click': ui.createHandlerFn(this, async () => {
- try {
- await fs.exec_direct('/usr/bin/aw-bpfctl', [type, 'del', host], 'text');
- this.loadHostSpeedData();
- ui.hideModal();
- } catch (e) {
- ui.addNotification(null, E('p', _('Error: ') + e.message));
- ui.hideModal();
- }
- })}, _('Delete'))
- ])
- ]);
- },
-
- handleEditSpeed: function(host, mac, hostname, type) {
- fs.exec_direct('/usr/bin/aw-bpfctl', [type, 'json'], 'json').then(L.bind(res => {
- let rate_limit_dl = 0, rate_limit_ul = 0;
- if (res && res.status === 'success' && Array.isArray(res.data)) {
- const item = res.data.find(d => (d.ip === host || d.mac === host));
- if (item) {
- rate_limit_dl = (item.incoming.incoming_rate_limit || 0) / 1024 / 1024;
- rate_limit_ul = (item.outgoing.outgoing_rate_limit || 0) / 1024 / 1024;
- }
- }
- this.displaySpeedLimitDialog(host, mac, hostname, type, rate_limit_dl, rate_limit_ul);
- }, this)).catch(e => {
- console.error('Error getting speed limit:', e);
- this.displaySpeedLimitDialog(host, mac, hostname, type, 0, 0);
- });
- },
-
- displaySpeedLimitDialog: function(host, mac, hostname, type, dl, ul) {
- const inputDom = E('input', { type: 'text', id: 'host-name', class: 'cbi-input-text', value: hostname, disabled: !mac });
-
- ui.showModal(_('Edit Speed Limit'), [
- E('div', { 'class': 'form-group' }, [ E('label', { 'class': 'form-label' }, _('Host')), E('span',{}, host) ]),
- E('div', { 'class': 'form-group' }, [ E('label', { 'class': 'form-label' }, _('Hostname')), inputDom ]),
- E('div', { 'class': 'form-group' }, [
- E('label', { 'class': 'form-label' }, _('Download Limit')),
- E('input', { type: 'number', id: 'dl-rate', class: 'cbi-input-number', min: '0', value: dl }),
- E('span',{}, " Mbps")
- ]),
- E('div', { 'class': 'form-group' }, [
- E('label', { 'class': 'form-label' }, _('Upload Limit')),
- E('input', { type: 'number', id: 'ul-rate', class: 'cbi-input-number', min: '0', value: ul }),
- E('span',{}, " Mbps")
- ]),
- E('div', { 'class': 'cbi-page-actions right' }, [
- E('button', { 'class': 'btn cbi-button cbi-button-neutral', 'click': ui.hideModal }, _('Cancel')),
- E('button', { 'class': 'btn cbi-button cbi-button-positive', 'click': ui.createHandlerFn(this, async ev => {
- const dl_val = document.getElementById('dl-rate').value;
- const ul_val = document.getElementById('ul-rate').value;
- const newName = document.getElementById('host-name').value;
- try {
- if (mac && newName !== hostname) {
- hostNames[mac] = newName;
- await uci.set('hostnames', hostNameMacSectionId, mac.split(':').join('_'), newName);
- await uci.save('hostnames');
- await uci.apply('hostnames');
- }
- await fs.exec_direct('/usr/bin/aw-bpfctl', [type, 'update', host, "downrate", dl_val*1024*1024 || '0', "uprate", ul_val*1024*1024 || '0']);
- this.loadHostSpeedData();
- ui.addNotification(null, E('p',_('Speed limit updated')));
- ui.hideModal();
- } catch (e) {
- ui.addNotification(null, E('p', _('Error: ') + e.message));
- }
- })}, _('Save'))
- ])
- ]);
- },
-
- validateData: function(value, type) {
- if (typeof value !== 'string') return false;
- const ipv4Regex = /^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
- const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(([0-9a-fA-F]{1,4}:){0,6}::([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})$/i;
- const macRegex = /^([0-9A-Fa-f]{2}([-:]))([0-9A-Fa-f]{2}\2){4}[0-9A-Fa-f]{2}$|^([0-9A-Fa-f]{12})$/i;
- return (type === 'ipv4') ? ipv4Regex.test(value) : (type === 'ipv6') ? ipv6Regex.test(value) : macRegex.test(value);
- },
-
- createAddControls: function(type, placeholder) {
- const input = E('input', {
- type: 'text',
- class: 'cbi-input-text control-input',
- style: (type === 'ipv6') ? 'width:320px' : 'width:180px',
- placeholder: _(placeholder)
- });
- const addBtn = E('button', {
- class: 'btn cbi-button cbi-button-add',
- disabled: true
- }, [
- E('span', { 'class': 'btn-icon' }, '➕'),
- E('span', {}, ' ' + _('Add'))
- ]);
- const refreshBtn = E('button', {
- class: 'btn cbi-button cbi-button-action',
- click: () => this.loadHostSpeedData()
- }, [
- E('span', { 'class': 'btn-icon' }, '🔄'),
- E('span', {}, ' ' + _('Refresh'))
- ]);
-
- input.addEventListener('input', () => { addBtn.disabled = (input.value.trim() === ''); });
- addBtn.addEventListener('click', ui.createHandlerFn(this, async () => {
- const value = input.value.trim();
- if (!this.validateData(value, type)) {
- return ui.addNotification(null, E('p', _('Data format error')));
- }
- try {
- await fs.exec_direct('/usr/bin/aw-bpfctl', [type, 'add', value]);
- this.loadHostSpeedData();
- ui.addNotification(null, E('p',_('Updated successfully!')));
- input.value = '';
- addBtn.disabled = true;
- } catch (e) {
- ui.addNotification(null, E('p', _('Error: ') + e.message));
- }
- }));
-
- return E('div', { 'class': 'display-controls' }, [
- E('div', { 'class': 'control-group' }, [
- E('span', { 'class': 'control-icon' }, '🖥️'),
- E('label', { 'class': 'control-label' }, _('Add Host:')),
- input
- ]),
- E('div', { 'class': 'control-buttons' }, [
- addBtn,
- refreshBtn,
- E('div', { 'class': 'control-group status-group' }, [
- E('span', { 'class': 'control-icon' }, '🕐'),
- E('span', { 'id': 'display-last-updated', 'class': 'last-updated-text' }, _('Ready'))
- ])
- ])
- ]);
- },
-
- initializeUI: function() {
- if (window.echarts) {
- var self = this;
- ['ipv4', 'ipv6', 'mac'].forEach(function(type) {
- var dlChartEl = document.getElementById(type + '-download-speed-line-chart');
- var ulChartEl = document.getElementById(type + '-upload-speed-line-chart');
- if (!dlChartEl || !ulChartEl) return;
-
- var baseChartOption = {
- tooltip: {
- trigger: 'axis',
- formatter: function (params) {
- if (!params || params.length === 0) {
- return null;
- }
- var tooltipContent = params[0].axisValueLabel + '
';
- params.sort(function(a, b) { return b.value - a.value; });
- params.forEach(function(item) {
- if (item.value > 0) {
- tooltipContent += item.marker + ' ' + item.seriesName + ': ' + '%1024.2mBps'.format(item.value) + '
';
- }
- });
- return tooltipContent;
- }
- },
- grid: { left: '3%', right: '4%', bottom: '10%', top: '50px', containLabel: true },
- xAxis: { type: 'category', boundaryGap: false, data: lineCategories[type] },
- yAxis: { type: 'value', axisLabel: { formatter: function(val) { return '%1024.2mBps'.format(val); } } },
- series: []
- };
-
- downloadLineChart[type] = echarts.init(dlChartEl);
- downloadLineChart[type].setOption(baseChartOption);
-
- uploadLineChart[type] = echarts.init(ulChartEl);
- uploadLineChart[type].setOption(baseChartOption);
- });
-
- // 添加窗口大小变化监听器,使图表能够响应式调整
- if (!resizeListenerAdded) {
- var resizeTimer = null;
- var resizeHandler = function() {
- // 使用防抖,避免频繁触发 resize
- if (resizeTimer) {
- clearTimeout(resizeTimer);
- }
- resizeTimer = setTimeout(function() {
- // 调整折线图大小
- ['ipv4', 'ipv6', 'mac'].forEach(function(type) {
- if (downloadLineChart[type]) {
- downloadLineChart[type].resize();
- }
- if (uploadLineChart[type]) {
- uploadLineChart[type].resize();
- }
- });
- // 调整饼图大小
- Object.keys(chartRegistry).forEach(function(chartId) {
- if (chartRegistry[chartId]) {
- chartRegistry[chartId].resize();
- }
- });
- }, 200);
- };
-
- window.addEventListener('resize', resizeHandler);
- resizeListenerAdded = true;
- }
-
- this.pollData();
- } else {
- setTimeout(this.initializeUI.bind(this), 50);
- }
- },
-
- // --- Main Render Function (New) ---
-
- render: function() {
- var self = this;
-
- const createTab = (type, title, placeholder) => {
- return E('div', { 'class': 'cbi-section', 'data-tab': type, 'data-tab-title': _(title) }, [
- E('div', { 'class': 'dashboard-container' }, [
- E('div', { 'class': 'line-chart-row' }, [
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Real-time Download Speed')]),
- E('div', { id: type + '-download-speed-line-chart', style: 'width: 100%; height: 350px;' })
- ]),
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Real-time Upload Speed')]),
- E('div', { id: type + '-upload-speed-line-chart', style: 'width: 100%; height: 350px;' })
- ])
- ]),
- E('div', { 'class': 'kpi-row' }, [
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: type + '-host-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Hosts')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: type + '-tx-rate-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Download Speed')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: type + '-rx-rate-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Upload Speed')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: type + '-tx-volume-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Download Total')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: type + '-rx-volume-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Upload Total')) ])
- ]),
- E('div', { 'class': 'chart-grid' }, [
- E('div', { 'class': 'chart-card' }, [ E('h4', [_('Download Speed / Host')]), E('div', { id: type + '-tx-rate-pie', style: 'width:100%; height:300px;' }) ]),
- E('div', { 'class': 'chart-card' }, [ E('h4', [_('Upload Speed / Host')]), E('div', { id: type + '-rx-rate-pie', style: 'width:100%; height:300px;' }) ]),
- E('div', { 'class': 'chart-card' }, [ E('h4', [_('Download Total')]), E('div', { id: type + '-tx-volume-pie', style: 'width:100%; height:300px;' }) ]),
- E('div', { 'class': 'chart-card' }, [ E('h4', [_('Upload Total')]), E('div', { id: type + '-rx-volume-pie', style: 'width:100%; height:300px;' }) ])
- ])
- ]),
- E('table', { 'class': 'table', 'id': type + '-speed-data' }, [
- E('tr', { 'class': 'tr table-titles' }, [
- E('th', { 'class': 'th left' }, [ E('span', { 'class': 'th-icon' }, '🖥️'), ' ', _('Host') ]),
- E('th', { 'class': 'th left' }, [ E('span', { 'class': 'th-icon' }, '👤'), ' ', _('Hostname') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '⬇️'), ' ', _('Download Speed') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📦'), ' ', _('Download Total') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📨'), ' ', _('Download Packets') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '⬆️'), ' ', _('Upload Speed') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📦'), ' ', _('Upload Total') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📨'), ' ', _('Upload Packets') ]),
- E('th', { 'class': 'th center' }, [ E('span', { 'class': 'th-icon' }, '⚙️'), ' ', _('Actions') ])
- ]),
- E('tr', { 'class': 'tr placeholder' }, [ E('td', { 'class': 'td', 'colspan': '9' }, [ E('em', { 'class': 'spinning' }, [ _('Collecting data...') ]) ]) ])
- ]),
- self.createAddControls(type, placeholder)
- ]);
- };
-
- var tabContainer = E('div', {}, [
- createTab('ipv4', 'IPv4', 'Please enter a valid IPv4 address'),
- createTab('ipv6', 'IPv6', 'Please enter a valid IPv6 address'),
- createTab('mac', 'MAC', 'Please enter a valid MAC address')
- ]);
-
- var node = E([], [
- E('link', { 'rel': 'stylesheet', 'href': L.resource('view/wifidogx.css') }),
- E('script', { 'type': 'text/javascript', 'src': L.resource('echarts.min.js') }),
- E('div', { 'class': 'l7-view-container' }, [
- E('h2', [ _('Auth User Speed Monitor') ]),
- tabContainer
- ])
- ]);
-
- ui.tabs.initTabGroup(tabContainer.childNodes);
-
- setTimeout(() => this.initializeUI(), 0);
-
- return node;
- },
-
- handleSave: null,
- handleSaveApply: null,
- handleReset: null
-});
diff --git a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/status.js b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/status.js
index 6be79ea1a..445daf4bc 100644
--- a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/status.js
+++ b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx/status.js
@@ -266,14 +266,14 @@ return view.extend({
}
}
+ // Initial update
+ updateStatus();
+
// Poll status every 5 seconds
L.Poll.add(function() {
return updateStatus();
}, 5);
- // Initial update
- updateStatus();
-
return container;
},
diff --git a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/xdpi/l7.js b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/xdpi/l7.js
deleted file mode 100644
index 781382bb5..000000000
--- a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/xdpi/l7.js
+++ /dev/null
@@ -1,695 +0,0 @@
-'use strict';
-'require view';
-'require fs';
-'require ui';
-'require poll';
-'require rpc';
-'require dom';
-
-var chartRegistry = {};
-var downloadLineChart, uploadLineChart;
-
-// Data structures for stacked line charts
-var lineCategories = [];
-var downloadSeriesData = {};
-var uploadSeriesData = {};
-
-// Color palette for chart series
-var colorPalette = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
-
-var currentSortInfo = {
- table: null,
- column: null,
- reverse: false
-};
-var sidLookupTable = {};
-var isPaused = false;
-var lastUpdated = null;
-var pollActive = false;
-var lastSIDData = null;
-var resizeListenerAdded = false;
-var resizeTimer = null;
-
-// Pre-fill with 60 empty points for a smooth start
-for (var i = 0; i < 60; i++) {
- lineCategories.push('');
-}
-
-// Helper to convert hex to rgba
-function hexToRgba(hex, opacity) {
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
- return result ?
- 'rgba(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ', ' + opacity + ')' :
- null;
-};
-
-return view.extend({
- load: function() {
- return Promise.all([
- this.loadSIDData(),
- this.loadL7ProtoData()
- ]);
- },
-
- showError: function(message) {
- var errorEl = document.getElementById('l7-error-message');
- if (errorEl) {
- errorEl.textContent = message;
- errorEl.style.display = 'block';
- }
- },
-
- hideError: function() {
- var errorEl = document.getElementById('l7-error-message');
- if (errorEl) {
- errorEl.style.display = 'none';
- }
- },
-
- loadSIDData: function() {
- var self = this;
- return fs.exec_direct('/usr/bin/aw-bpfctl', ['sid', 'json'], 'json').then(function(result) {
- self.hideError();
- lastSIDData = result;
- return result;
- }).catch(function(error) {
- console.error('Error loading SID data:', error);
- self.showError(_('Error loading SID data: %s').format(error.message));
- return { status: 'error', data: [] };
- });
- },
-
- loadL7ProtoData: function() {
- var self = this;
- return fs.exec_direct('/usr/bin/aw-bpfctl', ['l7', 'json'], 'json').then(function(result) {
- self.hideError();
- return result;
- }).catch(function(error) {
- console.error('Error loading L7 protocol data:', error);
- self.showError(_('Error loading L7 protocol data: %s').format(error.message));
- return { status: 'error', data: [] };
- });
- },
-
- updateStackedLineCharts: function(perServiceDownload, perServiceUpload) {
- var now = new Date().toLocaleTimeString();
- lineCategories.push(now);
- lineCategories.shift();
-
- var processChartData = function(seriesData, perServiceData) {
- var allServices = Object.keys(seriesData);
- Object.keys(perServiceData).forEach(function(service) {
- if (allServices.indexOf(service) === -1) {
- allServices.push(service);
- }
- });
-
- allServices.forEach(function(service) {
- if (!seriesData[service]) {
- seriesData[service] = Array(59).fill(0);
- }
- var rate = perServiceData[service] || 0;
- seriesData[service].push(rate);
- seriesData[service].shift();
- });
-
- return Object.keys(seriesData).map(function(service, index) {
- var color = colorPalette[index % colorPalette.length];
- return {
- name: service,
- type: 'line',
- stack: 'Total',
- smooth: true,
- lineStyle: { width: 1, color: color },
- showSymbol: false,
- itemStyle: { color: color },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
- { offset: 0, color: hexToRgba(color, 0.5) },
- { offset: 1, color: hexToRgba(color, 0) }
- ])
- },
- data: seriesData[service]
- };
- });
- };
-
- var downloadChartSeries = processChartData(downloadSeriesData, perServiceDownload);
- var uploadChartSeries = processChartData(uploadSeriesData, perServiceUpload);
-
- var legendData = downloadChartSeries.map(function(s) { return s.name; });
-
- if (downloadLineChart) {
- downloadLineChart.setOption({
- legend: { data: legendData, type: 'scroll', top: 0, left: 'center' },
- series: downloadChartSeries,
- xAxis: { data: lineCategories }
- });
- }
-
- if (uploadLineChart) {
- uploadLineChart.setOption({
- legend: { data: legendData, type: 'scroll', top: 0, left: 'center' },
- series: uploadChartSeries,
- xAxis: { data: lineCategories }
- });
- }
- },
-
- pie: function(id, data, valueFormatter) {
- var total = data.reduce(function(n, d) { return n + d.value; }, 0);
-
- data.sort(function(a, b) { return b.value - a.value; });
-
- if (total === 0) {
- data = [{ value: 1, color: '#cccccc', name: _('no traffic') }];
- }
-
- data.forEach(function(d, i) {
- if (!d.color) {
- var hue = (i * 137.508) % 360;
- d.color = 'hsl(' + hue + ', 75%, 55%)';
- }
- });
-
- var option = {
- tooltip: {
- trigger: 'item',
- formatter: function(params) {
- if (valueFormatter) {
- // 将 ECharts params 对象转换为自定义格式
- return valueFormatter({
- name: params.name,
- value: params.value,
- percent: params.percent.toFixed(2)
- });
- }
- return params.name + ': ' + params.value + ' (' + params.percent.toFixed(2) + '%)';
- }
- },
- series: [{
- type: 'pie',
- radius: ['25%', '80%'],
- avoidLabelOverlap: false,
- padAngle: 10,
- itemStyle: { borderRadius: 10, borderColor: '#fff', borderWidth: 2 },
- label: { show: false, position: 'center' },
- emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
- labelLine: { show: false },
- data: data.map(function(d) {
- return { value: d.value, name: d.label || d.name, itemStyle: { color: d.color } };
- })
- }]
- };
-
- var dom = typeof id === 'string' ? document.getElementById(id) : id;
-
- if (!chartRegistry[id]) {
- chartRegistry[id] = echarts.init(dom);
- }
-
- chartRegistry[id].setOption(option, true);
-
- return chartRegistry[id];
- },
-
- sortTable: function(table, column) {
- var tbody = table.querySelector('tbody');
- if (!tbody) return;
- var rows = Array.from(tbody.querySelectorAll('tr:not(.table-titles):not(.placeholder)'));
- var reverse = (currentSortInfo.table === table && currentSortInfo.column === column) ? !currentSortInfo.reverse : false;
-
- table.querySelectorAll('th').forEach(function(th) {
- th.classList.remove('th-sort-asc', 'th-sort-desc');
- });
-
- var th = table.querySelector('th:nth-child(' + (column + 1) + ')');
- th.classList.add(reverse ? 'th-sort-desc' : 'th-sort-asc');
-
- rows.sort(function(row1, row2) {
- var a = row1.cells[column].getAttribute('data-value') || row1.cells[column].textContent;
- var b = row2.cells[column].getAttribute('data-value') || row2.cells[column].textContent;
-
- if (!isNaN(a) && !isNaN(b)) { a = Number(a); b = Number(b); }
-
- if (a < b) return reverse ? 1 : -1;
- if (a > b) return reverse ? -1 : 1;
- return 0;
- });
-
- currentSortInfo.table = table;
- currentSortInfo.column = column;
- currentSortInfo.reverse = reverse;
-
- rows.forEach(function(row) { tbody.removeChild(row); });
- rows.forEach(function(row) { tbody.appendChild(row); });
- },
-
- formatMbps: function(bits) {
- if (typeof bits !== 'number') return '0.00 Mbps';
- return (bits / 1024 / 1024).toFixed(2) + ' Mbps';
- },
-
- formatMB: function(bytes) {
- if (typeof bytes !== 'number') return '0.00 MB';
- return (bytes / 1024 / 1024).toFixed(2) + ' MB';
- },
-
- renderSIDData: function(data) {
- var rows = [];
- var txRateData = [], rxRateData = [];
- var txVolumeData = [], rxVolumeData = [];
- var tx_rate_total = 0, rx_rate_total = 0;
- var tx_bytes_total = 0, rx_bytes_total = 0;
- var perServiceTxRate = {};
- var perServiceRxRate = {};
- var self = this;
- var allItems = [];
-
- if (data && data.status === 'success' && Array.isArray(data.data)) {
- allItems = data.data;
- var listSizeEl = document.getElementById('sid-size-select');
- var listSize = listSizeEl ? parseInt(listSizeEl.value, 10) : 10;
-
- var activeConnections = allItems.filter(function(item) { return item.incoming.rate > 0 || item.outgoing.rate > 0; });
- var inactiveConnections = allItems.filter(function(item) { return item.incoming.rate === 0 && item.outgoing.rate === 0; });
-
- activeConnections.sort(function(a, b) { return (b.incoming.rate + b.outgoing.rate) - (a.incoming.rate + a.outgoing.rate); });
- inactiveConnections.sort(function(a, b) { return b.incoming.total_bytes - a.incoming.total_bytes; });
-
- var displayData = activeConnections;
- if (displayData.length < listSize) {
- displayData = displayData.concat(inactiveConnections.slice(0, listSize - displayData.length));
- }
-
- if (displayData.length > listSize) {
- displayData = displayData.slice(0, listSize);
- }
-
- displayData.forEach(function(item) {
- var domainOrL7Proto = 'unknown';
- var lookupInfo = sidLookupTable[item.sid];
-
- if (lookupInfo) {
- domainOrL7Proto = lookupInfo.name;
- } else if (item.sid_type === 'Domain' && item.domain && item.domain !== 'unknown') {
- domainOrL7Proto = item.domain;
- } else if (item.sid_type === 'L7' && item.l7_proto_desc && item.l7_proto_desc !== 'unknown') {
- domainOrL7Proto = item.l7_proto_desc;
- }
-
- // 判断连接是否活跃
- var isActive = item.incoming.rate > 0 || item.outgoing.rate > 0;
- var activityIcon = isActive ? '🟢' : '⚪';
-
- rows.push([
- E('span', { 'class': 'sid-cell' }, [
- E('span', { 'class': 'activity-indicator', 'title': isActive ? _('Active') : _('Inactive') }, activityIcon),
- E('span', {}, ' ' + item.sid)
- ]),
- E('span', { 'class': 'protocol-cell' }, [
- E('span', { 'class': 'protocol-icon' }, '🌐'),
- E('span', {}, ' ' + domainOrL7Proto)
- ]),
- [ item.incoming.rate, E('span', { 'class': 'speed-cell download' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mbps'.format(item.incoming.rate))
- ])],
- [ item.incoming.total_bytes, E('span', { 'class': 'volume-cell download' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mB'.format(item.incoming.total_bytes))
- ])],
- [ item.incoming.total_packets, E('span', { 'class': 'packet-cell download' }, [
- E('span', { 'class': 'data-value' }, '%1000.2mP'.format(item.incoming.total_packets))
- ])],
- [ item.outgoing.rate, E('span', { 'class': 'speed-cell upload' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mbps'.format(item.outgoing.rate))
- ])],
- [ item.outgoing.total_bytes, E('span', { 'class': 'volume-cell upload' }, [
- E('span', { 'class': 'data-value' }, '%1024.2mB'.format(item.outgoing.total_bytes))
- ])],
- [ item.outgoing.total_packets, E('span', { 'class': 'packet-cell upload' }, [
- E('span', { 'class': 'data-value' }, '%1000.2mP'.format(item.outgoing.total_packets))
- ])]
- ]);
-
- txRateData.push({ value: item.incoming.rate, label: domainOrL7Proto });
- rxRateData.push({ value: item.outgoing.rate, label: domainOrL7Proto });
- txVolumeData.push({ value: item.incoming.total_bytes, label: domainOrL7Proto });
- rxVolumeData.push({ value: item.outgoing.total_bytes, label: domainOrL7Proto });
-
- perServiceTxRate[domainOrL7Proto] = (perServiceTxRate[domainOrL7Proto] || 0) + item.incoming.rate;
- perServiceRxRate[domainOrL7Proto] = (perServiceRxRate[domainOrL7Proto] || 0) + item.outgoing.rate;
- });
-
- allItems.forEach(function(item) {
- tx_rate_total += item.incoming.rate;
- rx_rate_total += item.outgoing.rate;
- tx_bytes_total += item.incoming.total_bytes;
- rx_bytes_total += item.outgoing.total_bytes;
- });
- }
-
- this.updateStackedLineCharts(perServiceTxRate, perServiceRxRate);
-
- var table = document.getElementById('sid-data');
- cbi_update_table('#sid-data', rows, E('em', _('No data recorded yet.')));
-
- var headers = table.querySelectorAll('th');
-
- if (!table.hasAttribute('data-sort-initialized')) {
- headers.forEach(function(header, index) {
- header.style.cursor = 'pointer';
- header.addEventListener('click', function() { self.sortTable(table, index); });
- });
- table.setAttribute('data-sort-initialized', 'true');
- }
-
- table.querySelectorAll('tr:not(.table-titles):not(.placeholder)').forEach(function(row, rowIndex) {
- if (!rows[rowIndex]) return;
- Array.from(row.cells).forEach(function(cell, cellIndex) {
- if (Array.isArray(rows[rowIndex][cellIndex])) {
- cell.setAttribute('data-value', rows[rowIndex][cellIndex][0]);
- }
- });
- });
-
- this.pie('sid-tx-rate-pie', txRateData, function(p) { return p.name + ': ' + self.formatMbps(p.value) + ' (' + p.percent + '%)'; });
- this.pie('sid-rx-rate-pie', rxRateData, function(p) { return p.name + ': ' + self.formatMbps(p.value) + ' (' + p.percent + '%)'; });
- this.pie('sid-tx-volume-pie', txVolumeData, function(p) { return p.name + ': ' + self.formatMB(p.value) + ' (' + p.percent + '%)'; });
- this.pie('sid-rx-volume-pie', rxVolumeData, function(p) { return p.name + ': ' + self.formatMB(p.value) + ' (' + p.percent + '%)'; });
-
- var sidTotalEl = document.getElementById('sid-total-val');
- if(sidTotalEl) sidTotalEl.textContent = allItems.length;
-
- var txRateEl = document.getElementById('sid-tx-rate-val');
- if(txRateEl) txRateEl.textContent = '%1024.2mbps'.format(tx_rate_total);
-
- var rxRateEl = document.getElementById('sid-rx-rate-val');
- if(rxRateEl) rxRateEl.textContent = '%1024.2mbps'.format(rx_rate_total);
-
- var txVolEl = document.getElementById('sid-tx-volume-val');
- if(txVolEl) txVolEl.textContent = '%1024.2mB'.format(tx_bytes_total);
-
- var rxVolEl = document.getElementById('sid-rx-volume-val');
- if(rxVolEl) rxVolEl.textContent = '%1024.2mB'.format(rx_bytes_total);
-
- lastUpdated = new Date();
- var timestampEl = document.getElementById('last-updated');
- if (timestampEl) {
- timestampEl.textContent = _('Last updated: %s').format(lastUpdated.toLocaleTimeString());
- }
- },
-
- renderL7ProtoData: function(data) {
- var rows = [];
- var self = this;
-
- sidLookupTable = {};
-
- if (data && data.status === 'success' && data.data) {
- if (Array.isArray(data.data.protocols)) {
- data.data.protocols.forEach(function(item) {
- sidLookupTable[item.sid] = { type: 'protocol', name: item.protocol };
- rows.push([
- E('span', { 'class': 'id-cell' }, item.id),
- E('span', { 'class': 'protocol-cell' }, [
- E('span', { 'class': 'protocol-icon l7' }, '🔌'),
- E('span', {}, ' ' + item.protocol)
- ]),
- E('span', { 'class': 'sid-cell' }, item.sid)
- ]);
- });
- }
-
- if (Array.isArray(data.data.domains)) {
- data.data.domains.forEach(function(item) {
- sidLookupTable[item.sid] = { type: 'domain', name: item.domain };
- rows.push([
- E('span', { 'class': 'id-cell' }, item.id),
- E('span', { 'class': 'protocol-cell' }, [
- E('span', { 'class': 'protocol-icon domain' }, '🌍'),
- E('span', {}, ' ' + item.domain)
- ]),
- E('span', { 'class': 'sid-cell' }, item.sid)
- ]);
- });
- }
- }
-
- var table = document.getElementById('l7proto-data');
- var headers = table.querySelectorAll('th');
-
- if (!table.hasAttribute('data-sort-initialized')) {
- headers.forEach(function(header, index) {
- header.style.cursor = 'pointer';
- header.addEventListener('click', function() { self.sortTable(table, index); });
- });
- table.setAttribute('data-sort-initialized', 'true');
- }
-
- cbi_update_table('#l7proto-data', rows, E('em', _('No data recorded yet.')));
- },
-
- pollL7Data: function() {
- if (pollActive) return;
-
- var self = this;
- pollActive = true;
-
- self.loadL7ProtoData().then(function(l7data) {
- self.renderL7ProtoData(l7data);
- return self.loadSIDData();
- }).then(function(sidData){
- self.renderSIDData(sidData);
- });
-
- poll.add(function() {
- if (isPaused) return Promise.resolve();
-
- return self.loadL7ProtoData().then(function(data) {
- self.renderL7ProtoData(data);
- }).then(function() {
- return self.loadSIDData().then(function(data) {
- self.renderSIDData(data);
- });
- });
- }, 5);
- },
-
- initializeUI: function() {
- if (window.echarts) {
- var dlChartEl = document.getElementById('download-speed-line-chart');
- var ulChartEl = document.getElementById('upload-speed-line-chart');
- if (!dlChartEl || !ulChartEl) return;
-
- var baseChartOption = {
- tooltip: {
- trigger: 'axis',
- formatter: function (params) {
- if (!params || params.length === 0) {
- return null;
- }
- var tooltipContent = params[0].axisValueLabel + '
';
- params.sort(function(a, b) { return b.value - a.value; });
- params.forEach(function(item) {
- if (item.value > 0) {
- tooltipContent += item.marker + ' ' + item.seriesName + ': ' + '%1024.2mbps'.format(item.value) + '
';
- }
- });
- return tooltipContent;
- }
- },
- grid: { left: '3%', right: '4%', bottom: '10%', top: '50px', containLabel: true },
- xAxis: { type: 'category', boundaryGap: false, data: lineCategories },
- yAxis: { type: 'value', axisLabel: { formatter: function(val) { return '%1024.2mbps'.format(val); } } },
- series: []
- };
-
-
- downloadLineChart = echarts.init(dlChartEl);
- downloadLineChart.setOption(baseChartOption);
-
- uploadLineChart = echarts.init(ulChartEl);
- uploadLineChart.setOption(baseChartOption);
-
- // 添加窗口大小变化监听器,使图表能够响应式调整
- if (!resizeListenerAdded) {
- var resizeTimer = null;
- var resizeHandler = function() {
- // 使用防抖,避免频繁触发 resize
- if (resizeTimer) {
- clearTimeout(resizeTimer);
- }
- resizeTimer = setTimeout(function() {
- // 调整折线图大小
- if (downloadLineChart) {
- downloadLineChart.resize();
- }
- if (uploadLineChart) {
- uploadLineChart.resize();
- }
- // 调整饼图大小
- Object.keys(chartRegistry).forEach(function(chartId) {
- if (chartRegistry[chartId]) {
- chartRegistry[chartId].resize();
- }
- });
- }, 200);
- };
-
- window.addEventListener('resize', resizeHandler);
- resizeListenerAdded = true;
- }
-
- this.pollL7Data();
- } else {
- setTimeout(this.initializeUI.bind(this), 50);
- }
-}, render: function() {
- var self = this;
-
- var controls = E('div', { 'class': 'l7-controls' }, [
- E('div', { 'class': 'l7-controls-left' }, [
- E('div', { 'class': 'control-group' }, [
- E('span', { 'class': 'control-icon' }, '📊'),
- E('label', { 'for': 'sid-size-select', 'class': 'control-label' }, _('Show entries:')),
- E('select', {
- 'id': 'sid-size-select',
- 'class': 'cbi-input-select',
- 'change': ui.createHandlerFn(this, function() {
- if (lastSIDData) {
- self.renderSIDData(lastSIDData);
- }
- })
- }, [
- E('option', { 'value': '10' }, '10'),
- E('option', { 'value': '15' }, '15'),
- E('option', { 'value': '20' }, '20'),
- E('option', { 'value': '25' }, '25'),
- E('option', { 'value': '50' }, '50')
- ])
- ])
- ]),
- E('div', { 'class': 'l7-controls-right' }, [
- E('div', { 'class': 'control-group' }, [
- E('span', { 'class': 'control-icon' }, '🕐'),
- E('span', { 'id': 'last-updated', 'class': 'last-updated-text' }, _('Last updated: never'))
- ]),
- E('button', {
- 'class': 'cbi-button cbi-button-action',
- 'id': 'pause-resume-btn',
- 'click': function(ev) {
- isPaused = !isPaused;
- var btn = ev.target;
- if (isPaused) {
- btn.innerHTML = '▶️ ' + _('Resume');
- btn.classList.remove('cbi-button-action');
- btn.classList.add('cbi-button-positive');
- } else {
- btn.innerHTML = '⏸️ ' + _('Pause');
- btn.classList.remove('cbi-button-positive');
- btn.classList.add('cbi-button-action');
- }
- }
- }, [
- E('span', { 'class': 'btn-icon' }, '⏸️'),
- E('span', {}, ' ' + _('Pause'))
- ])
- ])
- ]);
-
- var tabContainer = E('div', {}, [
- E('div', { 'class': 'cbi-section', 'data-tab': 'sid', 'data-tab-title': _('L7 SID Data') }, [
- E('div', { 'class': 'dashboard-container' }, [
- E('div', { 'class': 'line-chart-row' }, [
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Real-time Download Speed')]),
- E('div', { id: 'download-speed-line-chart', style: 'width: 100%; height: 350px;' })
- ]),
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Real-time Upload Speed')]),
- E('div', { id: 'upload-speed-line-chart', style: 'width: 100%; height: 350px;' })
- ])
- ]),
- E('div', { 'class': 'kpi-row' }, [
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: 'sid-total-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('L7 Protocol Data')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: 'sid-tx-rate-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Download Speed')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: 'sid-rx-rate-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Upload Speed')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: 'sid-tx-volume-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Download Total')) ]),
- E('div', { 'class': 'kpi-card' }, [ E('big', { id: 'sid-rx-volume-val' }, '0'), E('span', { 'class': 'kpi-card-label' }, _('Upload Total')) ])
- ]),
- E('div', { 'class': 'chart-grid' }, [
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Download Speed / SID')]),
- E('div', { id: 'sid-tx-rate-pie', style: 'width: 100%; height: 300px;' })
- ]),
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Upload Speed / SID')]),
- E('div', { id: 'sid-rx-rate-pie', style: 'width: 100%; height: 300px;' })
- ]),
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Download Total')]),
- E('div', { id: 'sid-tx-volume-pie', style: 'width: 100%; height: 300px;' })
- ]),
- E('div', { 'class': 'chart-card' }, [
- E('h4', [_('Upload Total')]),
- E('div', { id: 'sid-rx-volume-pie', style: 'width: 100%; height: 300px;' })
- ])
- ])
- ]),
- E('table', { 'class': 'table', 'id': 'sid-data' }, [
- E('tr', { 'class': 'tr table-titles' }, [
- E('th', { 'class': 'th left' }, [ E('span', { 'class': 'th-icon' }, '🆔'), ' ', _('SID') ]),
- E('th', { 'class': 'th left' }, [ E('span', { 'class': 'th-icon' }, '🌐'), ' ', _('Domain&L7Protocol') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '⬇️'), ' ', _('Download Speed (Bit/s)') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📦'), ' ', _('Download (Bytes)') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📨'), ' ', _('Download (Packets)') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '⬆️'), ' ', _('Upload Speed (Bit/s)') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📦'), ' ', _('Upload (Bytes)') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '📨'), ' ', _('Upload (Packets)') ])
- ]),
- E('tr', { 'class': 'tr placeholder' }, [
- E('td', { 'class': 'td', 'colspan': '8' }, [
- E('em', { 'class': 'spinning' }, [ _('Collecting data...') ])
- ])
- ])
- ]),
- controls
- ]),
- E('div', { 'class': 'cbi-section', 'data-tab': 'l7proto', 'data-tab-title': _('L7 Protocol Data') }, [
- E('table', { 'class': 'table', 'id': 'l7proto-data' }, [
- E('tr', { 'class': 'tr table-titles' }, [
- E('th', { 'class': 'th left' }, [ E('span', { 'class': 'th-icon' }, '#️⃣'), ' ', _('ID') ]),
- E('th', { 'class': 'th left' }, [ E('span', { 'class': 'th-icon' }, '🌐'), ' ', _('Domain&L7Protocol') ]),
- E('th', { 'class': 'th right' }, [ E('span', { 'class': 'th-icon' }, '🔑'), ' ', _('SID') ])
- ]),
- E('tr', { 'class': 'tr placeholder' }, [
- E('td', { 'class': 'td', 'colspan': '3' }, [
- E('em', { 'class': 'spinning' }, [ _('Collecting data...') ])
- ])
- ])
- ])
- ])
- ]);
-
- var node = E([], [
- E('link', { 'rel': 'stylesheet', 'href': L.resource('view/wifidogx.css') }),
- E('script', { 'type': 'text/javascript', 'src': L.resource('echarts.min.js') }),
-
- E('div', { 'class': 'l7-view-container' }, [
- E('h2', [ _('L7 Data Monitor') ]),
- E('div', { 'id': 'l7-error-message' }),
- tabContainer
- ])
- ]);
-
- ui.tabs.initTabGroup(tabContainer.childNodes);
-
- setTimeout(this.initializeUI.bind(this), 0);
-
- return node;
- },
-
- handleSave: null,
- handleSaveApply: null,
- handleReset: null
-});
\ No newline at end of file
diff --git a/luci-app-apfree-wifidog/po/zh_Hans/apfree-wifidog.po b/luci-app-apfree-wifidog/po/zh_Hans/apfree-wifidog.po
index 868c0241d..d44cd5fec 100644
--- a/luci-app-apfree-wifidog/po/zh_Hans/apfree-wifidog.po
+++ b/luci-app-apfree-wifidog/po/zh_Hans/apfree-wifidog.po
@@ -15,6 +15,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+msgid "Settings"
+msgstr "设置"
+
msgid "apfree-wifidog"
msgstr "门户认证"
@@ -165,6 +168,9 @@ msgstr "信任的通配符域名"
msgid "The trusted wildcard domains of the gateway."
msgstr "网关的信任通配符域名"
+msgid "Requires aw-bpf DNS XDP (dns_ringbuf_portal). Patterns like .example.com; resolved IPs are added to the captive portal firewall (inet wifidogx), not inet awbpf."
+msgstr "依赖 aw-bpf 的 DNS XDP(dns_ringbuf_portal)。格式如 .example.com;解析到的 IP 写入门户防火墙 inet wifidogx,不会写入 inet awbpf。"
+
msgid "App White List"
msgstr "应用白名单"
@@ -228,6 +234,12 @@ msgstr "启用防NAT"
msgid "Enable Anti NAT devices."
msgstr "启用防NAT设备"
+msgid "Privileged Ops Secret"
+msgstr "特权管控秘钥"
+
+msgid "Local secret required for AWAS cloud platform to dynamically authorize remote OTA, reboot and system changes. Change this from the default on production devices."
+msgstr "AWAS云端管理平台动态授权远程OTA升级、重启及系统配置变更所需的本地安全秘钥。生产环境请修改默认值。"
+
msgid "TTL Value"
msgstr "TTL值"
@@ -1093,3 +1105,48 @@ msgstr "MQTT认证的用户名。"
msgid "The password for MQTT authentication."
msgstr "MQTT认证的密码。"
+msgid "Add Authentication Server"
+msgstr "添加认证服务器"
+
+msgid "Enter a unique name for this authentication server profile."
+msgstr "请输入认证服务器配置文件的唯一名称。"
+
+msgid "Add Long Connection Profile"
+msgstr "添加长连接配置"
+
+msgid "Enter a unique name for this long connection profile."
+msgstr "请输入长连接配置文件的唯一名称。"
+
+msgid "Rename Authentication Server"
+msgstr "重命名认证服务器"
+
+msgid "Enter a new unique name for this authentication server profile."
+msgstr "请输入认证服务器配置文件的新名称。"
+
+msgid "Rename Long Connection Profile"
+msgstr "重命名长连接配置"
+
+msgid "Enter a new unique name for this long connection profile."
+msgstr "请输入长连接配置文件的新名称。"
+
+msgid "Please enter a name."
+msgstr "请输入名称。"
+
+msgid "The name \"%s\" already exists in Auth Server profiles. Please choose a different name."
+msgstr "名称\"%s\"已存在于认证服务器配置中,请选择其他名称。"
+
+msgid "The name \"%s\" already exists in Long Connection profiles. Please choose a different name."
+msgstr "名称\"%s\"已存在于长连接配置中,请选择其他名称。"
+
+msgid "Created new authentication server profile \"%s\"."
+msgstr "已创建新的认证服务器配置\"%s\"。"
+
+msgid "Created new long connection profile \"%s\"."
+msgstr "已创建新的长连接配置\"%s\"。"
+
+msgid "Renamed authentication server profile from \"%s\" to \"%s\"."
+msgstr "已将认证服务器配置从\"%s\"重命名为\"%s\"。"
+
+msgid "Renamed long connection profile from \"%s\" to \"%s\"."
+msgstr "已将长连接配置从\"%s\"重命名为\"%s\"。"
+
diff --git a/luci-app-apfree-wifidog/root/etc/uci-defaults/luci-app-apfree-wifidog.sh b/luci-app-apfree-wifidog/root/etc/uci-defaults/luci-app-apfree-wifidog.sh
deleted file mode 100644
index cf3a7cbdd..000000000
--- a/luci-app-apfree-wifidog/root/etc/uci-defaults/luci-app-apfree-wifidog.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-[ -f "/etc/config/hostnames" ] || {
- echo 'config hostname' > /etc/config/hostnames
-}
-
-exit 0
diff --git a/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx b/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx
index ff8d2e55c..9c4794010 100755
--- a/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx
+++ b/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx
@@ -70,7 +70,7 @@ case "$1" in
json_init
json_add_object "result"
json_add_string "status" "fail"
- json_add_string "message" "WAN interface not found. Available interfaces: $(ls /sys/class/net/ | tr '\n' ' ')"
+ json_add_string "message" "WAN interface not found"
json_close_object
json_dump
fi
diff --git a/luci-app-apfree-wifidog/root/usr/share/luci/menu.d/luci-app-apfree-wifidog.json b/luci-app-apfree-wifidog/root/usr/share/luci/menu.d/luci-app-apfree-wifidog.json
index ba3b75c70..2add1a4d8 100644
--- a/luci-app-apfree-wifidog/root/usr/share/luci/menu.d/luci-app-apfree-wifidog.json
+++ b/luci-app-apfree-wifidog/root/usr/share/luci/menu.d/luci-app-apfree-wifidog.json
@@ -3,56 +3,27 @@
"title": "apfree-wifidog",
"order": 1,
"action": {
- "type": "view",
- "path": "wifidogx"
+ "type": "firstchild",
+ "recurse": true
},
"depends": {
"acl": [ "luci-app-apfree-wifidog" ]
}
},
- "admin/QoS": {
- "title": "QoS",
- "order": 20,
- "action": {
- "type": "firstchild",
- "recurse": true
- }
- },
- "admin/QoS/wifidogx": {
- "title": "Auth User",
- "order": 2,
- "action": {
- "type": "alias",
- "path": "admin/QoS/wifidogx/display"
- },
- "depends": {
- "acl": [
- "luci-app-apfree-wifidog"
- ]
- }
- },
- "admin/QoS/wifidogx/display": {
- "title": "Display",
+ "admin/services/wifidogx/config": {
+ "title": "Settings",
"order": 10,
"action": {
"type": "view",
- "path": "wifidogx/display"
+ "path": "wifidogx"
}
},
- "admin/QoS/wifidogx/status": {
+ "admin/services/wifidogx/status": {
"title": "Status",
"order": 20,
"action": {
"type": "view",
"path": "wifidogx/status"
}
- },
- "admin/QoS/xdpi": {
- "title": "L7 application",
- "order": 30,
- "action": {
- "type": "view",
- "path": "xdpi/l7"
- }
}
}
diff --git a/luci-app-apfree-wifidog/root/usr/share/rpcd/acl.d/luci-app-apfree-wifidog.json b/luci-app-apfree-wifidog/root/usr/share/rpcd/acl.d/luci-app-apfree-wifidog.json
index 78975c66c..115acc1ae 100644
--- a/luci-app-apfree-wifidog/root/usr/share/rpcd/acl.d/luci-app-apfree-wifidog.json
+++ b/luci-app-apfree-wifidog/root/usr/share/rpcd/acl.d/luci-app-apfree-wifidog.json
@@ -8,14 +8,12 @@
},
"file": {
"/etc/init.d/wifidogx": [ "exec" ],
- "/usr/bin/wdctlx": [ "exec" ],
- "/usr/bin/aw-bpfctl": [ "exec" ],
- "/usr/bin/awk": [ "exec" ]
+ "/usr/bin/wdctlx": [ "exec" ]
},
- "uci": ["wifidogx", "hostnames", "network"]
+ "uci": ["wifidogx", "network"]
},
"write": {
- "uci": ["wifidogx", "hostnames"],
+ "uci": ["wifidogx"],
"file": {
"/etc/wifidogx/*": [ "write" ]
}
diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile
index b70561826..961c0b53d 100644
--- a/luci-app-passwall/Makefile
+++ b/luci-app-passwall/Makefile
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.9.9
-PKG_RELEASE:=263
+PKG_RELEASE:=264
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall/root/usr/share/passwall/app.sh b/luci-app-passwall/root/usr/share/passwall/app.sh
index ab5bce439..76df7aa55 100755
--- a/luci-app-passwall/root/usr/share/passwall/app.sh
+++ b/luci-app-passwall/root/usr/share/passwall/app.sh
@@ -1483,23 +1483,21 @@ acl_app() {
esac
}
}
- dns_cache_key="${dns_mode}_${remote_dns}_${v2ray_dns_mode:-none}_${remote_dns_client_ip:-0}_${remote_fakedns:-0}_${remote_rewrite_ttl:-30}"
- ([ "$v2ray_dns_mode" = "doh" ] || [ "$v2ray_dns_mode" = "http3" ]) && {
- dns_cache_key="${dns_mode}_${remote_dns_doh:-https://1.1.1.1/dns-query}_${v2ray_dns_mode:-doh}_${remote_dns_client_ip:-0}_${remote_fakedns:-0}_${remote_rewrite_ttl:-30}"
- }
+
+ local dns_cache_str="${dns_mode}_${v2ray_dns_mode}_${remote_dns}_${remote_dns_doh}_${remote_dns_client_ip}_${remote_fakedns}_${remote_rewrite_ttl}"
+ dns_cache_key="$(echo -n "${dns_cache_str}" | md5sum | cut -d " " -f1)"
if [ "$remote_fakedns" = "1" ] || ([ "$protocol" = "_shunt" ] && [ "$(config_n_get $node fakedns)" = "1" ]); then
use_fakedns=1
fi
run_dns() {
- local _dns_port
- [ -n $1 ] && _dns_port=$1
- [ -z ${_dns_port} ] && {
+ local dns_fwd_port=$1
+ [ -z "$dns_fwd_port" ] && {
dns_port=$(get_new_port $(expr $dns_port + 1))
- _dns_port=$dns_port
+ dns_fwd_port=$dns_port
if [ "$dns_mode" = "dns2socks" ]; then
- run_dns2socks flag=acl_${sid} socks_address=127.0.0.1 socks_port=$socks_port listen_address=0.0.0.0 listen_port=${_dns_port} dns=$remote_dns cache=1
+ run_dns2socks flag=acl_${sid} socks_address=127.0.0.1 socks_port=$socks_port listen_address=0.0.0.0 listen_port=$dns_fwd_port dns=$remote_dns cache=1
elif [ "$dns_mode" = "sing-box" ] || [ "$dns_mode" = "xray" ]; then
config_file=$TMP_ACL_PATH/${node}_SOCKS_${socks_port}_DNS.json
remote_dns_doh=${remote_dns_doh:-https://1.1.1.1/dns-query}
@@ -1507,10 +1505,15 @@ acl_app() {
[ "${dns_mode}" = "sing-box" ] && type="singbox"
dnsmasq_filter_proxy_ipv6=0
remote_dns_query_strategy="UseIP"
- [ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4"
- run_${type} flag=acl_${sid} type=$dns_mode dns_socks_address=127.0.0.1 dns_socks_port=$socks_port dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns} remote_dns_doh="${remote_dns_doh}" remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip} config_file=$config_file
+ [ "$filter_proxy_ipv6" = "1" ] && {
+ remote_dns_query_strategy="UseIPv4"
+ set_cache_var "node_${node}_${dns_cache_key}_ipv6" "0"
+ }
+ run_${type} flag=acl_${sid} type=$dns_mode dns_socks_address=127.0.0.1 dns_socks_port=$socks_port dns_listen_port=$dns_fwd_port \
+ remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns} remote_dns_doh="${remote_dns_doh}" \
+ remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip} config_file=$config_file
fi
- set_cache_var "node_${node}_$(echo -n "${dns_cache_key}" | md5sum | cut -d " " -f1)" "${_dns_port}"
+ set_cache_var "node_${node}_${dns_cache_key}" "$dns_fwd_port"
}
[ "$dns_shunt" = "chinadns-ng" ] && [ -n "$(first_type chinadns-ng)" ] && {
@@ -1539,7 +1542,7 @@ acl_app() {
_flag="$sid" \
_listen_port=${chinadns_port} \
_dns_local=${_chinadns_local_dns} \
- _dns_trust=127.0.0.1#${_dns_port} \
+ _dns_trust=127.0.0.1#${dns_fwd_port} \
_no_ipv6_trust=${filter_proxy_ipv6} \
_use_direct_list=${use_direct_list} \
_use_proxy_list=${use_proxy_list} \
@@ -1556,33 +1559,33 @@ acl_app() {
use_default_dns="chinadns_ng"
}
+ ([ "$filter_proxy_ipv6" = "1" ] && [ "$dnsmasq_filter_proxy_ipv6" = "1" ]) && {
+ dnsmasq_filter_proxy_ipv6=$(get_cache_var "node_${node}_${dns_cache_key}_ipv6")
+ dnsmasq_filter_proxy_ipv6=${dnsmasq_filter_proxy_ipv6:-1}
+ }
+
dnsmasq_port=$(get_new_port $(expr $dnsmasq_port + 1))
local dnsmasq_conf=${acl_path}/dnsmasq.conf
local dnsmasq_conf_path=${acl_path}/dnsmasq.d
lua $APP_PATH/helper_dnsmasq.lua add_rule -FLAG ${sid} -TMP_DNSMASQ_PATH ${dnsmasq_conf_path} -DNSMASQ_CONF_FILE ${dnsmasq_conf} \
-LISTEN_PORT ${dnsmasq_port} -DEFAULT_DNS ${DEFAULT_DNS} -LOCAL_DNS $LOCAL_DNS \
-USE_DIRECT_LIST "${use_direct_list}" -USE_PROXY_LIST "${use_proxy_list}" -USE_BLOCK_LIST "${use_block_list}" -USE_GFW_LIST "${use_gfw_list}" -CHN_LIST "${chn_list}" \
- -TUN_DNS "127.0.0.1#${_dns_port}" -USE_DEFAULT_DNS "${use_default_dns:-direct}" -CHINADNS_DNS ${_china_ng_listen:-0} \
+ -TUN_DNS "127.0.0.1#${dns_fwd_port}" -USE_DEFAULT_DNS "${use_default_dns:-direct}" -CHINADNS_DNS ${_china_ng_listen:-0} \
-NODE $node -DEFAULT_PROXY_MODE ${tcp_proxy_mode} -NO_PROXY_IPV6 ${dnsmasq_filter_proxy_ipv6:-0} -NFTFLAG ${nftflag:-0} \
-NO_LOGIC_LOG 1
ln_run "$(first_type dnsmasq)" "dnsmasq_${sid}" "/dev/null" -C ${dnsmasq_conf} -x ${acl_path}/dnsmasq.pid
set_cache_var "ACL_${sid}_dns_port" "${dnsmasq_port}"
- set_cache_var "node_${node}_$(echo -n "${tcp_proxy_mode}_${dns_cache_key}" | md5sum | cut -d " " -f1)" "${dnsmasq_port}"
#dhcp.leases to hosts
$APP_PATH/lease2hosts.sh > /dev/null 2>&1 &
}
- _redir_port=$(get_cache_var "node_${node}_redir_port")
- _socks_port=$(get_cache_var "node_${node}_socks_port")
+ local _redir_port=$(get_cache_var "node_${node}_redir_port")
+ local _socks_port=$(get_cache_var "node_${node}_socks_port")
+ local _dns_port
if [ -n "${_socks_port}" ] && [ -n "${_redir_port}" ]; then
socks_port=${_socks_port}
node_port=${_redir_port}
- _dnsmasq_port=$(get_cache_var "node_${node}_$(echo -n "${tcp_proxy_mode}_${dns_cache_key}" | md5sum | cut -d " " -f1)")
- if [ -z "${_dnsmasq_port}" ]; then
- _dns_port=$(get_cache_var "node_${node}_$(echo -n "${dns_cache_key}" | md5sum | cut -d " " -f1)")
- run_dns ${_dns_port}
- else
- [ -n "${_dnsmasq_port}" ] && set_cache_var "ACL_${sid}_dns_port" "${_dnsmasq_port}"
- fi
+ _dns_port=$(get_cache_var "node_${node}_${dns_cache_key}")
+ run_dns ${_dns_port}
else
socks_port=$(get_new_port $(expr $socks_port + 1))
set_cache_var "node_${node}_socks_port" "${socks_port}"
@@ -1600,7 +1603,10 @@ acl_app() {
_dns_port=$dns_port
dnsmasq_filter_proxy_ipv6=0
remote_dns_query_strategy="UseIP"
- [ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4"
+ [ "$filter_proxy_ipv6" = "1" ] && {
+ remote_dns_query_strategy="UseIPv4"
+ set_cache_var "node_${node}_${dns_cache_key}_ipv6" "0"
+ }
remote_dns_doh=${remote_dns_doh:-https://1.1.1.1/dns-query}
_extra_param="${_extra_param} dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns}"
_extra_param="${_extra_param} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_fakedns=${remote_fakedns:-0} remote_dns_client_ip=${remote_dns_client_ip}"
diff --git a/luci-app-passwall/root/usr/share/passwall/iptables.sh b/luci-app-passwall/root/usr/share/passwall/iptables.sh
index 452f922fd..511f48b5f 100755
--- a/luci-app-passwall/root/usr/share/passwall/iptables.sh
+++ b/luci-app-passwall/root/usr/share/passwall/iptables.sh
@@ -417,7 +417,7 @@ load_acl() {
[ "$_ipv4" != "1" ] && $ip6t_n -A PSW_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j REDIRECT --to-ports ${dns_redirect} 2>/dev/null
$ipt_n -A PSW_DNS $(comment "$remarks") -p tcp ${_ipt_source} --dport 53 -j REDIRECT --to-ports ${dns_redirect}
[ "$_ipv4" != "1" ] && $ip6t_n -A PSW_DNS $(comment "$remarks") -p tcp ${_ipt_source} --dport 53 -j REDIRECT --to-ports ${dns_redirect} 2>/dev/null
- [ -z "$(get_cache_var "ACL_${sid}_default")" ] && echolog " - ${msg}使用与全局配置不相同节点,已将DNS强制重定向到专用 DNS 服务器。"
+ [ -z "$(get_cache_var "ACL_${sid}_default")" ] && echolog " - ${msg}与全局配置不同节点,DNS 重定向到专用 DNS 服务器 [${dns_redirect}]。"
fi
[ -n "$tcp_port" ] || [ -n "$udp_port" ] && {
diff --git a/luci-app-passwall/root/usr/share/passwall/nftables.sh b/luci-app-passwall/root/usr/share/passwall/nftables.sh
index 8065b8feb..e0234ee24 100755
--- a/luci-app-passwall/root/usr/share/passwall/nftables.sh
+++ b/luci-app-passwall/root/usr/share/passwall/nftables.sh
@@ -478,7 +478,7 @@ load_acl() {
#nft "add rule $NFTABLE_NAME PSW_DNS ip protocol tcp ${_ipt_source} tcp dport 53 counter redirect to :${dns_redirect} comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW_DNS meta l4proto udp ${_ipt_source} udp dport 53 counter redirect to :${dns_redirect} comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW_DNS meta l4proto tcp ${_ipt_source} tcp dport 53 counter redirect to :${dns_redirect} comment \"$remarks\""
- [ -z "$(get_cache_var "ACL_${sid}_default")" ] && echolog " - ${msg}使用与全局配置不相同节点,已将DNS强制重定向到专用 DNS 服务器。"
+ [ -z "$(get_cache_var "ACL_${sid}_default")" ] && echolog " - ${msg}与全局配置不同节点,DNS 重定向到专用 DNS 服务器 [${dns_redirect}]。"
fi
[ -n "$tcp_port" ] || [ -n "$udp_port" ] && {
diff --git a/luci-app-ss-redir/Makefile b/luci-app-ss-redir/Makefile
deleted file mode 100644
index c2a8c17cf..000000000
--- a/luci-app-ss-redir/Makefile
+++ /dev/null
@@ -1,14 +0,0 @@
-# This is free software, licensed under the Apache License, Version 2.0 .
-#
-
-include $(TOPDIR)/rules.mk
-
-LUCI_TITLE:=LuCI Support for ss-redir
-LUCI_DEPENDS:=+luci-base +ss-redir
-
-PKG_LICENSE:=Apache-2.0
-PKG_MAINTAINER:=Dengfeng Liu
-
-include $(TOPDIR)/feeds/luci/luci.mk
-
-# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-ss-redir/README.md b/luci-app-ss-redir/README.md
deleted file mode 100644
index b1a434d11..000000000
--- a/luci-app-ss-redir/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-## luci-app-ss-redir
-
-该项目为ss-redir的luci配置界面
-
-### 视频演示
-
-
-
-## 如何集成到openwrt中编译
-
-1. 复制仓库中的文件到如下目录,并执行安装
-
-```
-feeds/luci/applications/luci-app-ss-redir/
-./scripts/feeds install luci -a
-```
-
-2. 选择路径
-
-`make menuconfig`
-
-LuCI > 3. Applications > luci-app-ss-redir
-
-3. 编译openwrt固件
-
-```
-make -j4
-```
-
-4. 单独编译
-
-```
-make package/luci-app-ss-redir/compile
-```
-
-## 联系方式
-
-QQ群:331230369
diff --git a/luci-app-ss-redir/htdocs/luci-static/resources/view/ss-redir.js b/luci-app-ss-redir/htdocs/luci-static/resources/view/ss-redir.js
deleted file mode 100644
index 34fd7643f..000000000
--- a/luci-app-ss-redir/htdocs/luci-static/resources/view/ss-redir.js
+++ /dev/null
@@ -1,157 +0,0 @@
-'use strict';
-'require view';
-'require ui';
-'require form';
-'require rpc';
-'require uci';
-'require tools.widgets as widgets';
-'require tools.github as github';
-
-var methods = [
- // aead
- 'aes-128-gcm',
- 'aes-192-gcm',
- 'aes-256-gcm',
- 'chacha20-ietf-poly1305',
- 'xchacha20-ietf-poly1305',
- // stream
- 'table',
- 'rc4',
- 'rc4-md5',
- 'aes-128-cfb',
- 'aes-192-cfb',
- 'aes-256-cfb',
- 'aes-128-ctr',
- 'aes-192-ctr',
- 'aes-256-ctr',
- 'bf-cfb',
- 'camellia-128-cfb',
- 'camellia-192-cfb',
- 'camellia-256-cfb',
- 'salsa20',
- 'chacha20',
- 'chacha20-ietf',
-];
-
-var callServiceList = rpc.declare({
- object: 'service',
- method: 'list',
- params: ['name'],
- expect: { '': {} }
-});
-
-function getServiceStatus() {
- return L.resolveDefault(callServiceList('ss-redir'), {}).then(function (res) {
- var isRunning = false;
- try {
- isRunning = res['ss-redir']['instances']['instance1']['running'];
- } catch (e) { }
- return isRunning;
- });
-}
-
-function renderStatus(isRunning) {
- var renderHTML = "";
- var spanTemp = '%s %s';
-
- if (isRunning) {
- renderHTML += String.format(spanTemp, 'green', _("ss-redir "), _("running..."));
- } else {
- renderHTML += String.format(spanTemp, 'red', _("ss-redir "), _("not running..."));
- }
-
- return renderHTML;
-}
-
-return view.extend({
- load: function() {
- return uci.load('kcptun');
- },
- render: function() {
- var m, s, o;
-
- m = new form.Map('ss-redir', _('ss-redir'));
- m.description = github.luci_desc('ss-redir redirect tcp service to kcptun client process.', 'liudf0716', 'ss-redir');
-
- // add kcptun-client status section and option
- s = m.section(form.NamedSection, '_status');
- s.anonymous = true;
- s.render = function (section_id) {
- L.Poll.add(function () {
- return L.resolveDefault(getServiceStatus()).then(function(res) {
- var view = document.getElementById("service_status");
- view.innerHTML = renderStatus(res);
- });
- });
-
- return E('div', { class: 'cbi-map' },
- E('fieldset', { class: 'cbi-section'}, [
- E('p', { id: 'service_status' },
- _('Collecting data ...'))
- ])
- );
- }
-
- s = m.section(form.NamedSection, 'server', 'ss_redir');
- s.dynamic = true;
-
- // add two tabs
- s.tab('ss-redir', _('ss-redir Settings'));
- s.tab('server', _('Shadowsocks Server Settings'));
-
- // ss-redir settings
- o = s.taboption('ss-redir', form.Flag, "enabled", _("Enable"),
- _("Enable ss-redir service"));
- o.rmempty = false;
-
- // server settings
- o = s.taboption('ss-redir', form.Value, "server", _("Server Address"),
- _("The address of kcptun client listening on. default: 127.0.0.1"));
- o.datatype = "host";
- o.rmempty = false;
- o.titleref = L.url('admin', 'vpn', 'kcptun');
-
- o = s.taboption('ss-redir', form.Value, "server_port", _("Server Port"),
- _("The port of kcptun client listening on. default: 12948"));
- o.datatype = "port";
- o.rmempty = false;
-
- o = s.taboption('ss-redir', form.Value, "password", _("Password"),
- _("The password of shadowsocks server"));
- o.password = true;
- o.rmempty = false;
-
- o = s.taboption('ss-redir', form.ListValue, "encrypt", _("Encryption Method"),
- _("The encryption method of shadowsocks server"));
- methods.forEach(function (method) {
- o.value(method, method);
- });
- o.rmempty = false;
-
- // add a textarea to show the config file content
- o = s.taboption('server', form.DummyValue, "_config", _("Server Command"),
- _("Copy the command to run ss-server on the server side"));
- o.rawhtml = true;
- o.cfgvalue = function(section_id) {
- var server = uci.get('ss-redir', section_id, 'server');
- var password = uci.get('ss-redir', section_id, 'password');
- var encrypt = uci.get('ss-redir', section_id, 'encrypt');
- var kcp_port = uci.get('ss-redir', section_id, 'kcp_port') || 6441;
-
- var config = "ss-server -s " + server + " -p " + kcp_port + " -k " + password + " -m " + encrypt;
- return "";
- }
- // add a button to copy the command
- o = s.taboption('server', form.Button, "_copy", _("Copy Command"));
- o.inputstyle = 'apply';
- o.inputtitle = _("Copy the command to clipboard");
- o.onclick = function(ev) {
- var textarea = document.querySelector('textarea');
- textarea.select();
- document.execCommand('copy');
- ui.addNotification(null, E('p', _('Copied to clipboard')), 'info');
- };
-
- return m.render();
- }
-});
diff --git a/luci-app-ss-redir/po/templates/ss-redir.pot b/luci-app-ss-redir/po/templates/ss-redir.pot
deleted file mode 100644
index e69de29bb..000000000
diff --git a/luci-app-ss-redir/po/zh-cn b/luci-app-ss-redir/po/zh-cn
deleted file mode 120000
index 8d69574dd..000000000
--- a/luci-app-ss-redir/po/zh-cn
+++ /dev/null
@@ -1 +0,0 @@
-zh_Hans
\ No newline at end of file
diff --git a/luci-app-ss-redir/po/zh_Hans/ss-redir.po b/luci-app-ss-redir/po/zh_Hans/ss-redir.po
deleted file mode 100644
index 1d57c1fad..000000000
--- a/luci-app-ss-redir/po/zh_Hans/ss-redir.po
+++ /dev/null
@@ -1,51 +0,0 @@
-#: ss-redir package
-msgid "ss-redir"
-msgstr "SS转发服务"
-
-msgid "ss-redir redirect tcp service to kcptun client process."
-msgstr "SS转发服务,将TCP流量重定向到本地KCP隧道,要结合本地KCP隧道服务一起使用。"
-
-msgid "Shadowsocks Server Settings"
-msgstr "Shadowsocks服务器设置"
-
-msgid "ss-redir Settings"
-msgstr "SS转发服务设置"
-
-msgid "The address of kcptun client listening on. default: 127.0.0.1"
-msgstr "KCP隧道客户端监听地址,默认: 127.0.0.1"
-
-msgid "Server Address"
-msgstr "服务器地址"
-
-msgid "The port of kcptun client listening on. default: 12948"
-msgstr "KCP隧道客户端监听端口,默认: 12948"
-
-msgid "The password of shadowsocks server"
-msgstr "Shadowsocks服务器密码"
-
-msgid "Server Port"
-msgstr "服务器端口"
-
-msgid "Encryption Method"
-msgstr "加密方式"
-
-msgid "The encryption method of shadowsocks server"
-msgstr "Shadowsocks服务器的加密方式"
-
-msgid "Enable ss-redir service"
-msgstr "启用SS转发服务"
-
-msgid "Server Command"
-msgstr "服务器端命令"
-
-msgid "Copy the command to run ss-server on the server side"
-msgstr "复制在服务器端运行ss-server的命令"
-
-msgid "Copy Command"
-msgstr "复制命令"
-
-msgid "Compy the command to clipboard"
-msgstr "复制服务器端命令到剪贴板"
-
-msgid "Copied to clipboard"
-msgstr "已复制到剪贴板"
\ No newline at end of file
diff --git a/luci-app-ss-redir/root/usr/share/luci/menu.d/luci-app-ss-redir.json b/luci-app-ss-redir/root/usr/share/luci/menu.d/luci-app-ss-redir.json
deleted file mode 100644
index a4c12b893..000000000
--- a/luci-app-ss-redir/root/usr/share/luci/menu.d/luci-app-ss-redir.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "admin/vpn/ss-redir": {
- "title": "ss-redir",
- "action": {
- "type": "view",
- "path": "ss-redir"
- },
- "depends": {
- "uci": { "ss-redir": true }
- }
- }
-}
\ No newline at end of file
diff --git a/luci-app-ss-redir/root/usr/share/rpcd/acl.d/luci-app-ss-redir.json b/luci-app-ss-redir/root/usr/share/rpcd/acl.d/luci-app-ss-redir.json
deleted file mode 100644
index 8ec797424..000000000
--- a/luci-app-ss-redir/root/usr/share/rpcd/acl.d/luci-app-ss-redir.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "luci-app-ss-redir": {
- "description": "Grant access to LuCI app ss-redir",
- "read": {
- "ubus": {
- "service": [ "list" ]
- },
- "uci": ["ss-redir"]
- },
- "write": {
- "uci": ["ss-redir"]
- }
- }
-}
diff --git a/luci-app-yt-dlp/Makefile b/luci-app-yt-dlp/Makefile
deleted file mode 100644
index e190d9a36..000000000
--- a/luci-app-yt-dlp/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-# This is free software, licensed under the Apache License, Version 2.0
-
-include $(TOPDIR)/rules.mk
-
-LUCI_TITLE:=LuCI Support for yt-dlp
-LUCI_DEPENDS:=+yt-dlp
-
-PKG_LICENSE:=Apache-2.0
-PKG_MAINTAINER:=Dengfeng Liu
-
-include $(TOPDIR)/feeds/luci/luci.mk
-
-# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-yt-dlp/README.md b/luci-app-yt-dlp/README.md
deleted file mode 100644
index 0d98e1362..000000000
--- a/luci-app-yt-dlp/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-## luci-app-yt-dlp
-该项目是yt-dlp的luci界面,通过该界面可以下载youtube、Twitter、B站等yt-dlp支持下载的视频网站。
-
-### 视频演示
-
-
-
-
-## 如何集成到openwrt中编译
-
-1. 复制仓库中的文件到如下目录,并执行安装
-
-```
-feeds/luci/applications/luci-app-yt-dlp/
-./scripts/feeds install luci -a
-```
-
-2. 选择路径
-
-`make menuconfig`
-
-LuCI > 3. Applications > luci-app-yt-dlp
-
-3. 编译openwrt固件
-
-```
-make -j4
-```
-
-4. 单独编译
-
-```
-make package/luci-app-yt-dlp/compile
-```
-
-## 联系方式
-
-QQ群:331230369
diff --git a/luci-app-yt-dlp/htdocs/luci-static/resources/view/yt-dlp.js b/luci-app-yt-dlp/htdocs/luci-static/resources/view/yt-dlp.js
deleted file mode 100644
index f1b88c035..000000000
--- a/luci-app-yt-dlp/htdocs/luci-static/resources/view/yt-dlp.js
+++ /dev/null
@@ -1,171 +0,0 @@
-'use strict';
-'require view';
-'require dom';
-'require fs';
-'require ui';
-'require uci';
-'require network';
-'require rpc';
-'require tools.github as github';
-
-return view.extend({
- load: function() {
- return L.resolveDefault(fs.list('/tmp/yt-dlp/'), null);
- },
-
- updateTable: function(table, files) {
- var rows = [];
-
- if (files == null)
- return;
-
- for(var i = 0; i < files.length; i++) {
- var file = files[i];
-
- rows.push([
- file.name,
- file.size,
- E('span', { 'class': 'diag-action' }, [
- E('button', {
- 'class': 'cbi-button cbi-button-action important',
- 'click': ui.createHandlerFn(this, 'handleDownloadVideo', file.name)
- }, _('Download')),
- E('button', {
- 'class': 'cbi-button cbi-button-action cbi-button-negative',
- 'click': ui.createHandlerFn(this, 'handleDeleteVideo', file.name)
- }, _('Delete'))
- ]),
- ]);
- }
-
- cbi_update_table(table, rows, E('em', _('No information available')));
- },
-
- handleYtdlp: function(ev) {
- var exec = '/usr/bin/yt-dlp',
- addr = ev.currentTarget.parentNode.previousSibling.value,
- args = ['-o', "/tmp/yt-dlp/%(title)s.%(ext)s", addr];
-
- if (addr == '') {
- ui.addNotification(null, E('p', _('Please enter a valid URL')));
- return;
- }
-
- var buttons = document.querySelectorAll('.diag-action > .cbi-button');
-
- for (var i = 0; i < buttons.length; i++)
- buttons[i].setAttribute('disabled', 'true');
-
- return fs.exec(exec, args).then(L.bind(function(res) {
- var out = document.querySelector('.command-output');
- out.style.display = '';
-
- dom.content(out, [ res.stdout || '', res.stderr || '' ]);
- if (res.stdout) {
- L.resolveDefault(fs.list('/tmp/yt-dlp/'), null).then(L.bind(function(files) {
- var table = document.getElementById('videos');
- this.updateTable('#videos', files);
- }, this));
- }
- }, this)).catch(function(err) {
- ui.addNotification(null, E('p', [ err ]));
- return null
- }).finally(function() {
- for (var i = 0; i < buttons.length; i++)
- buttons[i].removeAttribute('disabled');
- });
- },
-
- handleDeleteVideo: function(filename, ev) {
- var file = '/tmp/yt-dlp/' + filename;
- return fs.remove(file).then(L.bind(function(res) {
- L.resolveDefault(fs.list('/tmp/yt-dlp/'), null).then(L.bind(function(files) {
- var table = document.getElementById('videos');
- this.updateTable("#videos", files);
- }, this));
- }, this)).catch(function(err) {
- ui.addNotification(null, E('p', [ err ]));
- return null
- });
- },
-
-
- handleDownloadVideo: function(filename, ev) {
- var file = '/tmp/yt-dlp/' + filename;
- var form = E('form', {
- 'method': 'post',
- 'action': L.env.cgi_base + '/cgi-download',
- 'enctype': 'application/x-www-form-urlencoded'
- }, [
- E('input', { 'type': 'hidden', 'name': 'sessionid', 'value': rpc.getSessionID() }),
- E('input', { 'type': 'hidden', 'name': 'path', 'value': file }),
- E('input', { 'type': 'hidden', 'name': 'filename', 'value': filename })
- ]);
-
- ev.currentTarget.parentNode.appendChild(form);
-
- form.submit();
- form.parentNode.removeChild(form);
- },
-
- clearInput: function(ev) {
- document.getElementById('video_url').value = '';
- },
-
- render: function(files) {
- var video_url;
- var tbl_videos = E('table', { 'class': 'table', 'id': 'videos' }, [
- E('tr', { 'class': 'tr table-titles' }, [
- E('th', { 'class': 'th', 'style': 'width:60%' }, _('Name')),
- E('th', { 'class': 'th', 'style': 'width;20%' }, _('Size')),
- E('th', { 'class': 'th', 'style': 'width:20%' }, _('Action')),
- ])
- ]);
- var description = github.luci_desc('Download video from Youtube, Facebook, Twitter, etc.', 'liudf0716', 'yt-dlp')
-
- var v = E([], [
- E('h2', {}, [ _('Download Video') ]),
- E('div', { 'class': 'cbi-section-descr' }, description ),
- E('table', { 'class': 'table' }, [
- E('tr', { 'class': 'tr' }, [
- E('td', { 'class': 'td left' }, [
- E('input', {
- 'style': 'margin:5px 0; width: 50% !important;',
- 'type': 'text',
- 'id': 'video_url',
- 'value': video_url,
- 'placeholder': _('Enter video URL')
- }),
- E('span', { 'class': 'diag-action' }, [
- E('button', {
- 'class': 'cbi-button cbi-button-action',
- 'click': ui.createHandlerFn(this, 'handleYtdlp')
- }, [ _('Yt-dlp Online') ])
- ]),
- E('span', { 'class': 'diag-action' }, [
- E('button', {
- 'class': 'cbi-button cbi-button-action cbi-button-negative',
- 'click': ui.createHandlerFn(this, 'clearInput')
- }, [ _('Clear Input') ])
- ]),]
- ),
- ])
- ]),
- E('pre', { 'class': 'command-output', 'style': 'display:none' }),
- E('div', { 'class': 'cbi-section' }, [
- E('div', { 'class': 'left' }, [
- E('h3', _('Download Video Details')),
- tbl_videos
- ])
- ])
- ]);
-
- this.updateTable(tbl_videos, files);
-
- return v;
- },
-
- handleSaveApply: null,
- handleSave: null,
- handleReset: null
-});
diff --git a/luci-app-yt-dlp/luci-app-yt-dlp_intro.png b/luci-app-yt-dlp/luci-app-yt-dlp_intro.png
deleted file mode 100644
index 76a877143..000000000
Binary files a/luci-app-yt-dlp/luci-app-yt-dlp_intro.png and /dev/null differ
diff --git a/luci-app-yt-dlp/po/zh-cn b/luci-app-yt-dlp/po/zh-cn
deleted file mode 120000
index 8d69574dd..000000000
--- a/luci-app-yt-dlp/po/zh-cn
+++ /dev/null
@@ -1 +0,0 @@
-zh_Hans
\ No newline at end of file
diff --git a/luci-app-yt-dlp/po/zh_Hans/yt-dlp.po b/luci-app-yt-dlp/po/zh_Hans/yt-dlp.po
deleted file mode 100644
index 22ff3225b..000000000
--- a/luci-app-yt-dlp/po/zh_Hans/yt-dlp.po
+++ /dev/null
@@ -1,29 +0,0 @@
-msgid "Download Video"
-msgstr "短视频下载"
-
-msgid "Download Video Details"
-msgstr "下载视频详情"
-
-msgid "Download video from Youtube, Facebook, Twitter, etc."
-msgstr "从各大网站下载视频,支持B站,Youtube, Twitter等"
-
-msgid "Yt-dlp Online"
-msgstr "下载视频"
-
-msgid "Please enter a valid URL"
-msgstr "请输入有效的URL"
-
-msgid "yt-dlp"
-msgstr "短视频下载"
-
-msgid "Action"
-msgstr "操作"
-
-msgid "Download"
-msgstr "下载到本地"
-
-msgid "Clear Input"
-msgstr "清空输入"
-
-msgid "Enter video URL"
-msgstr "输入视频URL"
\ No newline at end of file
diff --git a/luci-app-yt-dlp/root/usr/libexec/rpcd/yt-dlp b/luci-app-yt-dlp/root/usr/libexec/rpcd/yt-dlp
deleted file mode 100755
index ee1f3b4eb..000000000
--- a/luci-app-yt-dlp/root/usr/libexec/rpcd/yt-dlp
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-
-ytdlp() {
- url=$1
- output=$2
- /usr/bin/yt-dlp $url -o $output
-}
-
-handleRequest() {
- action=$1
-
- if [ "$action" = "list" ]; then
- echo "{\"download\":{\"description\":\"Download video\",\"params\":{\"url\":{\"type\":\"string\",\"description\":\"Video URL\"}}}}"
- elif [ "$action" = "call" ]; then
- method=$2
-
- if [ "$method" = "download" ]; then
- output="/tmp/yt-dlp-output.mp4"
- read url
- ytdlp "$url" "$output"
- result="{\"result\":\"ok\",\"data\":{\"url\":\"$url\",\"output\":\"$output\"}}"
- echo "$result"
- else
- errorResult="{\"result\":\"error\",\"message\":\"Unknown method\"}"
- echo "$errorResult"
- fi
- fi
-}
-
-handleRequest "$@"
diff --git a/luci-app-yt-dlp/root/usr/share/luci/menu.d/luci-app-yt-dlp.json b/luci-app-yt-dlp/root/usr/share/luci/menu.d/luci-app-yt-dlp.json
deleted file mode 100644
index 2997954ea..000000000
--- a/luci-app-yt-dlp/root/usr/share/luci/menu.d/luci-app-yt-dlp.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "admin/services/yt-dlp": {
- "title": "yt-dlp",
- "action": {
- "type": "view",
- "path": "yt-dlp"
- },
- "depends": {
- "acl": [ "luci-app-yt-dlp" ]
- }
- }
-}
diff --git a/luci-app-yt-dlp/root/usr/share/rpcd/acl.d/luci-app-yt-dlp.json b/luci-app-yt-dlp/root/usr/share/rpcd/acl.d/luci-app-yt-dlp.json
deleted file mode 100644
index 7a42309e7..000000000
--- a/luci-app-yt-dlp/root/usr/share/rpcd/acl.d/luci-app-yt-dlp.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "luci-app-yt-dlp": {
- "description": "Grant access to LuCI app yt-dlp",
- "read": {
- "ubus": {
- "file": [ "exec", "read", "stat" ],
- "luci": [ "isFfmpegOrWhispercppRunning" ]
- },
- "file": {
- "/tmp/yt-dlp/*": [ "read" ]
- },
- "cgi-io": [ "download" ]
- },
- "write": {
- "cgi-io": [ "upload"],
- "file": {
- "/usr/bin/yt-dlp": ["exec", "write" ],
- "/tmp/yt-dlp/*": [ "write", "remove" ]
- },
- "ubus": {
- "file": [ "exec", "remove", "write" ],
- "yt-dlp": [ "download" ]
- }
- }
- }
-}
diff --git a/luci-theme-footstrap/Makefile b/luci-theme-footstrap/Makefile
index 24f086470..e822d80b5 100644
--- a/luci-theme-footstrap/Makefile
+++ b/luci-theme-footstrap/Makefile
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
FOOTSTRAP_VERSION?=
ifneq ($(FOOTSTRAP_VERSION),)
PKG_VERSION:=$(FOOTSTRAP_VERSION)
-PKG_RELEASE:=65
+PKG_RELEASE:=66
endif
LUCI_TITLE:=Footstrap Theme
diff --git a/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js b/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js
index ae11e3dc2..4ab69bf81 100644
--- a/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js
+++ b/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js
@@ -885,13 +885,44 @@ function lateDrift(ref, grow, floorShrink) {
/* fresh distrust starts the recovery count at 0 too — a streak from a PREVIOUS
* spell of distrust proves nothing about this one */
if (++_lateMisses >= LATE_MISS_LIMIT) { _engineTrusted = false; _lateHits = 0; }
+ /* MIRROR OF task refill2's WRITE-PATH FIX, ON THE NO-WRITE PATH — task nine.
+ * A miss here means the OFFSET did not fully move; it does not mean this tick's
+ * geometry is unknown. Leaving `_rest` as `run()`'s own mid-transition capture (the
+ * DOM already changed, nothing had compensated yet) makes THAT stale snapshot the
+ * `ref.at`/`was` the NEXT tick measures against, same as the write path used to
+ * before `rememberRest(true)` was added there. `seen` and `el`'s rect, just read,
+ * ARE the true current position — uncorrected, but real — so the next comparison
+ * should start from here, not from before this tick began. Measured, `../tmp/
+ * task-nine/`: without this, `firefox owrt2410 @390 top overview` counted a SECOND
+ * phantom miss off the stale baseline and tripped `_engineTrusted` false while
+ * REPEAT's own mark never moved (misses [true,true,false]) — e0b6db4's fault on the
+ * other side of the same comparison. */
+ rememberRest(true);
return;
}
/* else: within table-row rounding — drift is still whatever it was (< 1 per the guard
* above), so the plain `drift < 1` return two lines down is what fires, unwritten and
* uncounted: the engine did the job. */
}
- if (Math.abs(drift) < 1) return; /* the engine put it back */
+ if (Math.abs(drift) < 1) {
+ /* SAME MIRROR AS ABOVE, FOR THE "engine already got it right" EXIT — task nine.
+ * Gated on `grow` OR `floorShrink`, not unconditional: a tick where this floored box
+ * neither grew nor shrank pays nothing extra here (P5), and `run()`'s own synchronous
+ * reference is only ever wrong relative to what THIS tick's mutation did. `grow` alone
+ * is not enough — REPEAT's own SHRINK leg (the pad it removes between refills) is a
+ * real box change `grow` reads as <=1 (growth witness is deliberately one-sided,
+ * task detector), so gating on `grow > 1` alone left the SAME hole one level down: the
+ * shrink between refill 1 and refill 2 left `_rest` at run()'s mid-transition capture,
+ * and refill 2 read ITS drift against that stale baseline. Where it did change (either
+ * way) and the engine handled it without a write, `run()`'s snapshot is still what the
+ * NEXT tick's `lateDrift()` would measure against — masking a real residual as "small"
+ * until it surfaces as a flat, un-recovered offset. Measured, `../tmp/task-nine/`:
+ * gating on `grow > 1` alone left `/admin/network/dhcp @390` at 47-59px off on the
+ * second of three back-to-back refills, unchanged — chromium/firefox/webkit alike,
+ * `_engineTrusted` true throughout. */
+ if (grow > 1 || floorShrink > 1) rememberRest(true);
+ return; /* the engine put it back */
+ }
if (Math.abs(drift) > (window.innerHeight || 800)) return;
const sc = scroller();
const at = sc ? sc.scrollTop : window.scrollY;
diff --git a/ss-redir/Makefile b/ss-redir/Makefile
deleted file mode 100644
index 578216a0e..000000000
--- a/ss-redir/Makefile
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Copyright (C) 2022 Dengfeng Liu
-#
-# This is free software, licensed under the GNU General Public License v3.
-# See /LICENSE for more information.
-#
-
-include $(TOPDIR)/rules.mk
-
-PKG_NAME:=ss-redir
-PKG_VERSION:=0.1.0
-PKG_RELEASE:=1
-
-PKG_MAINTAINER:=Dengfeng Liu
-PKG_LICENSE:=GPL-3.0-or-later
-PKG_LICENSE_FILES:=COPYING
-
-include $(INCLUDE_DIR)/package.mk
-
-define Package/ss-redir
- SUBMENU:=Web Servers/Proxies
- SECTION:=net
- CATEGORY:=Network
- TITLE:= simplify ss-redir usage
- DEPENDS:=+shadowsocks-libev-ss-redir
-endef
-
-define Package/ss-redir/description
- simplify ss-redir usage
-endef
-
-define Build/Compile
-endef
-
-define Package/ss-redir/conffiles
- /etc/config/ss-redir
-endef
-
-define Package/ss-redir/install
- $(INSTALL_DIR) $(1)/etc/init.d
- $(INSTALL_DIR) $(1)/etc/uci-defaults
- $(INSTALL_DIR) $(1)/usr/share/ss-redir
- $(INSTALL_DIR) $(1)/etc/config
- $(INSTALL_BIN) ./files/ss-redir.init $(1)/etc/init.d/ss-redir
- $(INSTALL_BIN) ./files/ss-redir.conf $(1)/etc/config/ss-redir
- $(INSTALL_DATA) ./files/firewall4.include $(1)/usr/share/ss-redir/firewall.include
- $(INSTALL_BIN) ./files/ss-redir.defaults.nftables $(1)/etc/uci-defaults/99-ss-redir
-endef
-
-$(eval $(call BuildPackage,ss-redir))
diff --git a/ss-redir/files/firewall4.include b/ss-redir/files/firewall4.include
deleted file mode 100644
index 916b43913..000000000
--- a/ss-redir/files/firewall4.include
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-/etc/init.d/ss-redir enabled && /etc/init.d/ss-redir restart
-exit 0
diff --git a/ss-redir/files/ss-redir.conf b/ss-redir/files/ss-redir.conf
deleted file mode 100644
index 716f801dd..000000000
--- a/ss-redir/files/ss-redir.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-config ss_redir 'server'
- option enabled '0'
- option server '127.0.0.1'
- option server_port '12948'
- option password 'password'
- option encrypt 'aes-256-cfb'
diff --git a/ss-redir/files/ss-redir.defaults.nftables b/ss-redir/files/ss-redir.defaults.nftables
deleted file mode 100644
index ff6ab60f8..000000000
--- a/ss-redir/files/ss-redir.defaults.nftables
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-
-uci -q batch <<-EOT
- delete firewall.ss_redir
- set firewall.ss_redir=include
- set firewall.ss_redir.type=script
- set firewall.ss_redir.path=/usr/share/ss-redir/firewall.include
- commit firewall
-EOT
-
-exit 0
diff --git a/ss-redir/files/ss-redir.init b/ss-redir/files/ss-redir.init
deleted file mode 100644
index 8c7487550..000000000
--- a/ss-redir/files/ss-redir.init
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/bin/sh /etc/rc.common
-#
-# Copyright (C) 2023 Dengfeng Liu
-#
-# This is free software, licensed under the GNU General Public License v3.
-# See /LICENSE for more information.
-#
-
-USE_PROCD=1
-START=99
-
-CONF="ss-redir"
-PROG="/usr/bin/ss-redir"
-GO_SS2="/usr/bin/go-ss2"
-CONFILE="/tmp/ss-redir.json"
-
-mk_ss_redir_conf() {
- local stream_methods='"table", "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "salsa20", "chacha20", "chacha20-ietf"'
- local aead_methods='"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305"'
-
- uci_validate_section ss-redir ss_redir "server" \
- 'server:host:127.0.0.1' \
- 'server_port:port:12948' \
- 'password:string' \
- "encrypt:or($stream_methods, $aead_methods)" \
- 'local_address:host:0.0.0.0' \
- 'local_port:port:1088' \
- 'fast_open:bool:1' \
- 'ipv6_first:bool:0' \
- 'no_delay:bool:0' \
- 'reuse_port:bool:1' \
- 'mode:or("tcp_only", "udp_only", "tcp_and_udp"):tcp_only' \
- 'mtu:uinteger:1350' \
- 'timeout:uinteger:60'
-
- json_init
- json_add_string server $server
- json_add_int server_port $server_port
- json_add_string method $encrypt
- json_add_string password $password
- json_add_string local_address $local_address
- json_add_int local_port $local_port
- json_add_string mode $mode
- json_add_int mtu $mtu
- json_add_int timeout $timeout
- json_add_boolean fast_open $fast_open
- json_add_boolean ipv6_first $ipv6_first
- json_add_boolean no_delay $no_delay
- json_add_boolean reuse_port $reuse_port
- json_dump -i >"$CONFILE"
-}
-
-add_ss_redir_rule() {
- nft add set inet fw4 ss-redir_set { type ipv4_addr\; }
- nft add set inet fw4 ss-redir_set6 { type ipv6_addr\; }
- nft add set inet fw4 all_proxy_set { type ipv4_addr\; }
- nft add set inet fw4 all_proxy_set6 { type ipv6_addr\; }
- nft add chain inet fw4 ss-redir_chain
- nft add rule inet fw4 dstnat iifname br-lan jump ss-redir_chain
- nft add rule inet fw4 ss-redir_chain ip saddr @all_proxy_set meta l4proto tcp redirect to 1080
- nft add rule inet fw4 ss-redir_chain ip6 saddr @all_proxy_set6 meta l4proto tcp redirect to 1080
- nft add rule inet fw4 ss-redir_chain ip daddr @ss-redir_set meta l4proto tcp redirect to 1088
- nft add rule inet fw4 ss-redir_chain ip daddr @ss-redir_set meta l4proto udp redirect to 1088
- nft add rule inet fw4 ss-redir_chain ip6 daddr @ss-redir_set6 meta l4proto udp redirect to 1088
- nft add rule inet fw4 ss-redir_chain ip6 daddr @ss-redir_set6 meta l4proto tcp redirect to 1088
-
-}
-
-clear_ss_redir_rule() {
- if [ -z "$(nft -a list chain inet fw4 dstnat | grep ss-redir_chain)" ]; then
- echo "no ss-redir rule"
- return
- fi
- # get the handle of the ss-redir_chain in dstnat
- handle_num=$(nft -a list chain inet fw4 dstnat | grep ss-redir_chain | awk '{print $7}')
- if [ -z "$handle_num" ]; then
- echo "cant get the handle of ss-redir_chain in dstnat"
- return
- fi
- nft delete rule inet fw4 dstnat handle $handle_num
- nft flush chain inet fw4 ss-redir_chain
- nft delete chain inet fw4 ss-redir_chain
- nft delete set inet fw4 ss-redir_set
- nft delete set inet fw4 ss-redir_set6
- nft delete set inet fw4 all_proxy_set
- nft delete set inet fw4 all_proxy_set6
-}
-
-ss_redir_rule() {
- clear_ss_redir_rule
- add_ss_redir_rule
-}
-
-get_proxy_command() {
- if [ -x "$GO_SS2" ]; then
- echo "$GO_SS2 -c 'ss://$encrypt:$password@$server:$server_port' -redir :$local_port"
- else
- echo "$PROG -c $CONFILE"
- fi
-}
-
-start_service() {
- config_load "$CONF"
- local enabled
-
- config_get_bool enabled "server" "enabled" "0"
- [ "$enabled" -eq "1" ] || {
- echo "service does not enabled"
- clear_ss_redir_rule
- return 1
- }
-
- mk_ss_redir_conf
- [ ! -f $CONFILE ] && return 1
-
-
- procd_open_instance
- if [ -x "$GO_SS2" ]; then
- procd_set_param command "$GO_SS2" -c "ss://$encrypt:$password@$server:$server_port" -verbose -redir ":$local_port"
- procd_set_param stdout 1
- procd_set_param stderr 1
- else
- procd_set_param command "$PROG" -c "$CONFILE"
- fi
- procd_set_param respawn
- procd_close_instance
-
- ss_redir_rule
-}
-
-reload_service() {
- stop
- start
-}
-
-service_triggers() {
- procd_add_reload_trigger "$CONF"
-}
\ No newline at end of file
diff --git a/torrserver/Makefile b/torrserver/Makefile
index dc887fabe..665fa777a 100644
--- a/torrserver/Makefile
+++ b/torrserver/Makefile
@@ -8,12 +8,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=torrserver
-PKG_VERSION:=MatriX.144.1
-PKG_RELEASE:=19
+PKG_VERSION:=MatriX.144
+PKG_RELEASE:=20
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
-PKG_SOURCE_VERSION:=309a5917c12764c82791b555fb61a60f8e2fee44
+PKG_SOURCE_VERSION:=c416a8f2b89b244c779f03f1f86e2f0f690c2668
PKG_MIRROR_HASH:=skip
PKG_MAINTAINER:=Konstantine Shevlakov