secubox-openwrt/package/secubox/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/smart-qos.js
CyberMind-FR fb9722ccd6 feat(bandwidth-manager): Add Smart QoS, Device Groups, and Analytics (Phase 5)
Advanced Bandwidth Manager features v0.5.0

Smart QoS (DPI Integration):
- Real-time application detection via nDPId
- Smart traffic suggestions based on detected patterns
- One-click DPI rule creation for applications
- Gaming, streaming, video conferencing detection
- Heavy downloader identification

Device Groups:
- Create device groups (Family, IoT, Work, Gaming, Kids, Guests)
- Shared quota across group members
- Unified priority assignment per group
- Easy member management via drag-drop UI
- Group usage tracking and visualization

Analytics Dashboard:
- Traffic summary with download/upload totals
- Active client count and per-client averages
- Application traffic breakdown charts
- Protocol distribution pie chart
- Top bandwidth users leaderboard
- Download/upload ratio analysis
- Historical data retention (30 days)
- Period selection (1h, 6h, 24h, 7d, 30d)

Backend Enhancements:
- get_dpi_applications: Fetch detected apps from nDPId
- get_smart_suggestions: AI-powered QoS recommendations
- apply_dpi_rule: Create rules based on app detection
- list_groups/create_group/update_group/delete_group
- add_to_group/remove_from_group: Member management
- get_analytics_summary: Traffic statistics
- get_hourly_data: Historical trends
- record_stats: Cron-based data collection

Menu Additions:
- Smart QoS (order: 10)
- Device Groups (order: 11)
- Analytics (order: 12)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 14:07:54 +01:00

400 lines
12 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require poll';
'require rpc';
'require ui';
var callGetDpiApplications = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'get_dpi_applications',
expect: { applications: [], dpi_source: 'none' }
});
var callGetSmartSuggestions = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'get_smart_suggestions',
expect: { suggestions: [] }
});
var callApplyDpiRule = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'apply_dpi_rule',
params: ['app_name', 'priority', 'limit_down', 'limit_up'],
expect: { success: false, message: '' }
});
var callGetClasses = rpc.declare({
object: 'luci.bandwidth-manager',
method: 'get_classes',
expect: { classes: [] }
});
return view.extend({
applications: [],
suggestions: [],
classes: [],
dpiSource: 'none',
load: function() {
return Promise.all([
callGetDpiApplications(),
callGetSmartSuggestions(),
callGetClasses()
]);
},
render: function(data) {
var self = this;
var dpiData = data[0] || { applications: [], dpi_source: 'none' };
var suggestionsData = data[1] || { suggestions: [] };
var classesData = data[2] || { classes: [] };
this.applications = dpiData.applications || [];
this.dpiSource = dpiData.dpi_source || 'none';
this.suggestions = suggestionsData.suggestions || [];
this.classes = classesData.classes || [];
document.body.setAttribute('data-secubox-app', 'bandwidth');
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', { 'class': 'cbi-map-title' }, 'Smart QoS'),
E('div', { 'class': 'cbi-map-descr' },
'AI-powered traffic classification using Deep Packet Inspection'),
// DPI Status
this.renderDpiStatus(),
// Smart Suggestions
this.renderSuggestions(),
// Detected Applications
this.renderApplications()
]);
poll.add(L.bind(this.pollData, this), 10);
return view;
},
pollData: function() {
var self = this;
return Promise.all([
callGetDpiApplications(),
callGetSmartSuggestions()
]).then(function(data) {
self.applications = (data[0] && data[0].applications) || [];
self.dpiSource = (data[0] && data[0].dpi_source) || 'none';
self.suggestions = (data[1] && data[1].suggestions) || [];
var statusEl = document.getElementById('dpi-status-container');
var suggestionsEl = document.getElementById('suggestions-container');
var appsEl = document.getElementById('apps-container');
if (statusEl) {
statusEl.innerHTML = '';
statusEl.appendChild(self.renderDpiStatusContent());
}
if (suggestionsEl) {
suggestionsEl.innerHTML = '';
suggestionsEl.appendChild(self.renderSuggestionsContent());
}
if (appsEl) {
appsEl.innerHTML = '';
appsEl.appendChild(self.renderApplicationsContent());
}
});
},
renderDpiStatus: function() {
return E('div', { 'class': 'cbi-section', 'id': 'dpi-status-container' }, [
this.renderDpiStatusContent()
]);
},
renderDpiStatusContent: function() {
var statusColor, statusText, statusIcon;
switch (this.dpiSource) {
case 'ndpid':
statusColor = '#22c55e';
statusText = 'nDPId Active';
statusIcon = '\u2713';
break;
case 'netifyd':
statusColor = '#3b82f6';
statusText = 'Netifyd Active';
statusIcon = '\u2713';
break;
default:
statusColor = '#ef4444';
statusText = 'No DPI Engine';
statusIcon = '\u2717';
}
return E('div', {
'style': 'display: flex; align-items: center; gap: 1rem; padding: 1rem; background: var(--cyber-bg-secondary, #141419); border-radius: 8px; border-left: 4px solid ' + statusColor
}, [
E('div', {
'style': 'width: 48px; height: 48px; background: ' + statusColor + '20; color: ' + statusColor + '; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 1.5rem;'
}, statusIcon),
E('div', {}, [
E('div', { 'style': 'font-weight: 600; color: ' + statusColor }, statusText),
E('div', { 'style': 'font-size: 0.875rem; color: var(--cyber-text-secondary, #a1a1aa);' },
this.dpiSource !== 'none'
? 'Deep Packet Inspection is analyzing your network traffic'
: 'Install nDPId or netifyd to enable application detection')
]),
E('div', { 'style': 'margin-left: auto; font-size: 0.875rem;' }, [
E('span', { 'style': 'color: var(--cyber-text-secondary);' }, 'Detected Apps: '),
E('strong', { 'style': 'color: var(--cyber-text-primary);' }, this.applications.length.toString())
])
]);
},
renderSuggestions: function() {
return E('div', { 'class': 'cbi-section' }, [
E('h3', { 'class': 'cbi-section-title' }, 'Smart Suggestions'),
E('div', { 'id': 'suggestions-container' }, [
this.renderSuggestionsContent()
])
]);
},
renderSuggestionsContent: function() {
var self = this;
if (this.suggestions.length === 0) {
return E('div', {
'style': 'padding: 2rem; text-align: center; color: var(--cyber-text-secondary, #a1a1aa); background: var(--cyber-bg-secondary, #141419); border-radius: 8px;'
}, [
E('div', { 'style': 'font-size: 2rem; margin-bottom: 0.5rem;' }, '\ud83d\udd0d'),
'Analyzing traffic patterns...',
E('br'),
this.dpiSource === 'none'
? 'Enable a DPI engine to get smart suggestions'
: 'No optimization suggestions at this time'
]);
}
var typeIcons = {
gaming: '\ud83c\udfae',
streaming: '\ud83c\udfa5',
videoconf: '\ud83d\udcf9',
downloads: '\u2b07\ufe0f'
};
var typeColors = {
gaming: '#8b5cf6',
streaming: '#ec4899',
videoconf: '#3b82f6',
downloads: '#f59e0b'
};
return E('div', { 'style': 'display: grid; gap: 1rem;' },
this.suggestions.map(function(suggestion) {
var icon = typeIcons[suggestion.type] || '\ud83d\udca1';
var color = typeColors[suggestion.type] || '#667eea';
return E('div', {
'style': 'display: flex; align-items: flex-start; gap: 1rem; padding: 1rem; background: var(--cyber-bg-secondary, #141419); border-radius: 8px; border: 1px solid var(--cyber-border-subtle, rgba(255,255,255,0.08));'
}, [
E('div', {
'style': 'width: 44px; height: 44px; background: ' + color + '20; color: ' + color + '; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; flex-shrink: 0;'
}, icon),
E('div', { 'style': 'flex: 1;' }, [
E('div', { 'style': 'font-weight: 600; margin-bottom: 0.25rem;' }, suggestion.title),
E('div', { 'style': 'font-size: 0.875rem; color: var(--cyber-text-secondary, #a1a1aa); margin-bottom: 0.5rem;' }, suggestion.description),
E('div', { 'style': 'display: flex; gap: 0.5rem; font-size: 0.75rem;' }, [
E('span', {
'style': 'padding: 0.25rem 0.5rem; background: var(--cyber-bg-tertiary, rgba(255,255,255,0.05)); border-radius: 4px;'
}, 'Priority: ' + suggestion.priority),
E('span', {
'style': 'padding: 0.25rem 0.5rem; background: var(--cyber-bg-tertiary, rgba(255,255,255,0.05)); border-radius: 4px;'
}, suggestion.affected_devices + ' device(s)')
])
]),
E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'flex-shrink: 0;',
'click': function() { self.applySuggestion(suggestion); }
}, 'Apply')
]);
})
);
},
renderApplications: function() {
return E('div', { 'class': 'cbi-section' }, [
E('h3', { 'class': 'cbi-section-title' }, 'Detected Applications'),
E('div', { 'id': 'apps-container' }, [
this.renderApplicationsContent()
])
]);
},
renderApplicationsContent: function() {
var self = this;
if (this.applications.length === 0) {
return E('div', {
'style': 'padding: 2rem; text-align: center; color: var(--cyber-text-secondary, #a1a1aa); background: var(--cyber-bg-secondary, #141419); border-radius: 8px;'
}, 'No applications detected');
}
// Sort by bytes
var sortedApps = this.applications.slice().sort(function(a, b) {
return (b.total_bytes || 0) - (a.total_bytes || 0);
});
return E('div', { 'style': 'overflow-x: auto;' }, [
E('table', { 'class': 'table cbi-section-table', 'style': 'width: 100%;' }, [
E('thead', {}, [
E('tr', { 'class': 'tr cbi-section-table-titles' }, [
E('th', { 'class': 'th' }, 'Application'),
E('th', { 'class': 'th' }, 'Flows'),
E('th', { 'class': 'th' }, 'Traffic'),
E('th', { 'class': 'th' }, 'Actions')
])
]),
E('tbody', {},
sortedApps.slice(0, 20).map(function(app) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'style': 'font-weight: 500;' }, app.name || 'Unknown'),
E('td', { 'class': 'td' }, (app.flow_count || 0).toString()),
E('td', { 'class': 'td' }, self.formatBytes(app.total_bytes || 0)),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'font-size: 0.75rem; padding: 0.25rem 0.5rem;',
'click': function() { self.showRuleDialog(app); }
}, 'Create Rule')
])
]);
})
)
])
]);
},
applySuggestion: function(suggestion) {
var self = this;
ui.showModal('Apply Suggestion', [
E('p', {}, suggestion.description),
E('p', {}, 'This will create a QoS rule with priority ' + suggestion.priority + '.'),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'cbi-button',
'click': ui.hideModal
}, 'Cancel'),
' ',
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': function() {
ui.hideModal();
// Apply rule based on suggestion type
var appName = '';
switch (suggestion.type) {
case 'gaming':
appName = 'Gaming';
break;
case 'streaming':
appName = 'Streaming';
break;
case 'videoconf':
appName = 'Video Conferencing';
break;
case 'downloads':
appName = 'Downloads';
break;
}
callApplyDpiRule(appName, suggestion.priority, 0, 0).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, res.message), 'success');
} else {
ui.addNotification(null, E('p', {}, res.message || 'Failed to apply rule'), 'error');
}
});
}
}, 'Apply')
])
]);
},
showRuleDialog: function(app) {
var self = this;
var prioritySelect = E('select', { 'class': 'cbi-input-select', 'id': 'rule-priority' },
this.classes.map(function(c) {
return E('option', { 'value': c.priority }, c.priority + ' - ' + c.name);
})
);
ui.showModal('Create QoS Rule for ' + app.name, [
E('div', { 'style': 'margin-bottom: 1rem;' }, [
E('label', { 'style': 'display: block; margin-bottom: 0.5rem;' }, 'Priority Class'),
prioritySelect
]),
E('div', { 'style': 'margin-bottom: 1rem;' }, [
E('label', { 'style': 'display: block; margin-bottom: 0.5rem;' }, 'Download Limit (Kbps, 0 = unlimited)'),
E('input', {
'type': 'number',
'class': 'cbi-input-text',
'id': 'rule-limit-down',
'value': '0',
'min': '0'
})
]),
E('div', { 'style': 'margin-bottom: 1rem;' }, [
E('label', { 'style': 'display: block; margin-bottom: 0.5rem;' }, 'Upload Limit (Kbps, 0 = unlimited)'),
E('input', {
'type': 'number',
'class': 'cbi-input-text',
'id': 'rule-limit-up',
'value': '0',
'min': '0'
})
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'cbi-button',
'click': ui.hideModal
}, 'Cancel'),
' ',
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': function() {
var priority = parseInt(document.getElementById('rule-priority').value) || 5;
var limitDown = parseInt(document.getElementById('rule-limit-down').value) || 0;
var limitUp = parseInt(document.getElementById('rule-limit-up').value) || 0;
ui.hideModal();
callApplyDpiRule(app.name, priority, limitDown, limitUp).then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', {}, 'Rule created for ' + app.name), 'success');
} else {
ui.addNotification(null, E('p', {}, res.message || 'Failed to create rule'), 'error');
}
});
}
}, 'Create Rule')
])
]);
},
formatBytes: function(bytes) {
if (!bytes || bytes === 0) return '0 B';
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = 0;
while (bytes >= 1024 && i < units.length - 1) {
bytes /= 1024;
i++;
}
return bytes.toFixed(1) + ' ' + units[i];
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});