secubox-openwrt/package/secubox/luci-app-bandwidth-manager/htdocs/luci-static/resources/view/bandwidth-manager/profiles.js
CyberMind-FR 1bbd345cee refactor(luci): Mass KissTheme UI rework across all LuCI apps
Convert 90+ LuCI view files from legacy cbi-button-* classes to
KissTheme kiss-btn-* classes for consistent dark theme styling.

Pattern conversions applied:
- cbi-button-positive → kiss-btn-green
- cbi-button-negative/remove → kiss-btn-red
- cbi-button-apply → kiss-btn-cyan
- cbi-button-action → kiss-btn-blue
- cbi-button (plain) → kiss-btn

Also replaced hardcoded colors (#080, #c00, #888, etc.) with
CSS variables (--kiss-green, --kiss-red, --kiss-muted, etc.)
for proper dark theme compatibility.

Apps updated include: ai-gateway, auth-guardian, bandwidth-manager,
cloner, config-advisor, crowdsec-dashboard, dns-provider, exposure,
glances, haproxy, hexojs, iot-guard, jellyfin, ksm-manager,
mac-guardian, magicmirror2, master-link, meshname-dns, metablogizer,
metabolizer, mqtt-bridge, netdata-dashboard, picobrew, routes-status,
secubox-admin, secubox-mirror, secubox-p2p, secubox-security-threats,
service-registry, simplex, streamlit, system-hub, tor-shield,
traffic-shaper, vhost-manager, vortex-dns, vortex-firewall,
webradio, wireguard-dashboard, zigbee2mqtt, zkp, and more.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-12 11:09:34 +01:00

597 lines
24 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require ui';
'require rpc';
'require poll';
'require bandwidth-manager/api as API';
'require secubox/kiss-theme';
var PROFILE_ICONS = {
'gamepad': { icon: '🎮', color: 'purple' },
'play': { icon: '▶️', color: 'cyan' },
'cpu': { icon: '🔌', color: 'green' },
'briefcase': { icon: '💼', color: 'blue' },
'child': { icon: '👶', color: 'orange' },
'tag': { icon: '🏷️', color: 'purple' },
'shield': { icon: '🛡️', color: 'red' },
'star': { icon: '⭐', color: 'orange' },
'home': { icon: '🏠', color: 'cyan' },
'globe': { icon: '🌐', color: 'purple' }
};
return L.view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
profiles: [],
assignments: [],
clients: [],
load: function() {
return Promise.all([
API.listProfiles(),
API.getBuiltinProfiles(),
API.listProfileAssignments(),
API.getUsageRealtime(),
API.listGroups()
]);
},
renderStats: function() {
var c = KissTheme.colors;
var builtinCount = this.profiles.filter(function(p) { return p.builtin; }).length;
var customCount = this.profiles.length - builtinCount;
return [
KissTheme.stat(this.profiles.length, 'Total Profiles', c.blue),
KissTheme.stat(builtinCount, 'Built-in', c.purple),
KissTheme.stat(customCount, 'Custom', c.green),
KissTheme.stat(this.assignments.length, 'Assignments', c.cyan)
];
},
render: function(data) {
var profiles = (data[0] && data[0].profiles) || [];
var builtinProfiles = (data[1] && data[1].profiles) || [];
this.assignments = (data[2] && data[2].assignments) || [];
this.clients = (data[3] && data[3].clients) || [];
var groups = (data[4] && data[4].groups) || [];
var self = this;
this.profiles = builtinProfiles.concat(profiles);
var content = [
// Header
E('div', { 'style': 'margin-bottom: 24px;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 16px;' }, [
E('h2', { 'style': 'font-size: 24px; font-weight: 700; margin: 0;' }, _('Device Profiles')),
KissTheme.badge(this.profiles.length + ' profiles', 'blue')
]),
E('p', { 'style': 'color: var(--kiss-muted); margin: 8px 0 0 0;' },
_('Create and manage device profiles with custom QoS settings, bandwidth limits, and latency modes'))
]),
// Stats
E('div', { 'class': 'kiss-grid kiss-grid-4', 'style': 'margin: 20px 0;' },
this.renderStats()),
// Quick Actions
KissTheme.card('Quick Actions',
E('div', { 'style': 'display: flex; gap: 12px;' }, [
E('button', {
'class': 'kiss-btn kiss-btn-green',
'click': function() { self.showCreateProfileModal(self.clients, groups); }
}, '+ ' + _('Create Profile')),
E('button', {
'class': 'kiss-btn kiss-btn-blue',
'click': function() { self.showAssignModal(self.profiles, self.clients); }
}, '📱 ' + _('Assign to Device'))
])
),
// Profiles Grid
this.renderProfilesGrid(),
// Assignments Section
this.renderAssignmentsSection()
];
return KissTheme.wrap(content, 'admin/services/bandwidth-manager/profiles');
},
renderProfilesGrid: function() {
var self = this;
if (this.profiles.length === 0) {
return KissTheme.card('Profiles',
E('p', { 'style': 'color: var(--kiss-muted); text-align: center; padding: 30px;' },
_('No profiles available')));
}
var grid = E('div', { 'style': 'display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px;' },
this.profiles.map(function(profile) {
return self.renderProfileCard(profile);
})
);
return KissTheme.card(
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('span', {}, 'All Profiles'),
KissTheme.badge(this.profiles.length + ' total', 'purple')
]),
grid
);
},
renderProfileCard: function(profile) {
var self = this;
var iconInfo = PROFILE_ICONS[profile.icon] || PROFILE_ICONS['tag'];
var assignedCount = this.assignments.filter(function(a) {
return a.profile === profile.id;
}).length;
var features = [];
if (profile.latency_mode === 'ultra') features.push(E('span', {}, ' Ultra'));
if (profile.isolate) features.push(E('span', {}, '🔒 Isolated'));
if (profile.content_filter) features.push(E('span', {}, '🛡 Filtered'));
return E('div', {
'style': 'background: var(--kiss-bg); border: 1px solid var(--kiss-line); border-radius: 12px; padding: 16px; border-left: 4px solid var(--kiss-' + iconInfo.color + ');'
}, [
// Header
E('div', { 'style': 'display: flex; align-items: center; gap: 12px; margin-bottom: 12px;' }, [
E('div', {
'style': 'width: 44px; height: 44px; background: var(--kiss-bg2); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 24px;'
}, iconInfo.icon),
E('div', { 'style': 'flex: 1;' }, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
E('span', { 'style': 'font-weight: 600;' }, profile.name),
profile.builtin ? KissTheme.badge('Built-in', 'blue') : ''
]),
E('div', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, profile.description || _('No description'))
])
]),
// Stats
E('div', { 'style': 'display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 12px; background: var(--kiss-bg2); border-radius: 8px; margin-bottom: 12px; text-align: center;' }, [
E('div', {}, [
E('div', { 'style': 'font-size: 16px; font-weight: 700; color: var(--kiss-purple);' }, String(profile.priority)),
E('div', { 'style': 'font-size: 10px; color: var(--kiss-muted); text-transform: uppercase;' }, _('Priority'))
]),
E('div', {}, [
E('div', { 'style': 'font-size: 16px; font-weight: 700; color: var(--kiss-cyan);' }, profile.limit_down > 0 ? this.formatSpeed(profile.limit_down) : ''),
E('div', { 'style': 'font-size: 10px; color: var(--kiss-muted); text-transform: uppercase;' }, _('Down'))
]),
E('div', {}, [
E('div', { 'style': 'font-size: 16px; font-weight: 700; color: var(--kiss-green);' }, profile.limit_up > 0 ? this.formatSpeed(profile.limit_up) : ''),
E('div', { 'style': 'font-size: 10px; color: var(--kiss-muted); text-transform: uppercase;' }, _('Up'))
]),
E('div', {}, [
E('div', { 'style': 'font-size: 16px; font-weight: 700; color: var(--kiss-orange);' }, String(assignedCount)),
E('div', { 'style': 'font-size: 10px; color: var(--kiss-muted); text-transform: uppercase;' }, _('Devices'))
])
]),
// Features
features.length > 0 ? E('div', { 'style': 'display: flex; gap: 8px; margin-bottom: 12px; font-size: 12px; color: var(--kiss-muted);' }, features) : '',
// Actions
E('div', { 'style': 'display: flex; gap: 8px; flex-wrap: wrap;' }, [
E('button', {
'class': 'kiss-btn',
'style': 'padding: 6px 12px; font-size: 12px;',
'click': function() { self.showProfileDetails(profile, self.assignments.filter(function(a) { return a.profile === profile.id; })); }
}, _('View')),
!profile.builtin ? E('button', {
'class': 'kiss-btn kiss-btn-blue',
'style': 'padding: 6px 12px; font-size: 12px;',
'click': function() { self.showEditProfileModal(profile); }
}, _('Edit')) : '',
E('button', {
'class': 'kiss-btn kiss-btn-purple',
'style': 'padding: 6px 12px; font-size: 12px;',
'click': function() { self.cloneProfile(profile); }
}, _('Clone')),
!profile.builtin ? E('button', {
'class': 'kiss-btn kiss-btn-red',
'style': 'padding: 6px 12px; font-size: 12px;',
'click': function() { self.deleteProfile(profile); }
}, _('Delete')) : ''
].filter(Boolean))
]);
},
renderAssignmentsSection: function() {
var self = this;
if (this.assignments.length === 0) {
return '';
}
var rows = this.assignments.map(function(assignment) {
var profile = self.profiles.find(function(p) { return p.id === assignment.profile; });
var iconInfo = profile ? (PROFILE_ICONS[profile.icon] || PROFILE_ICONS['tag']) : PROFILE_ICONS['tag'];
return E('tr', {}, [
E('td', { 'style': 'font-weight: 500;' }, assignment.hostname || 'Unknown'),
E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, assignment.mac),
E('td', { 'style': 'color: var(--kiss-muted);' }, assignment.ip || '-'),
E('td', {}, [
E('span', { 'style': 'display: flex; align-items: center; gap: 6px;' }, [
E('span', {}, iconInfo.icon),
assignment.profile_name || assignment.profile
])
]),
E('td', {}, [
E('button', {
'class': 'kiss-btn kiss-btn-red',
'style': 'padding: 4px 10px; font-size: 11px;',
'click': function() { self.removeAssignment(assignment.mac); }
}, '')
])
]);
});
return KissTheme.card(
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('span', {}, _('Device Assignments')),
KissTheme.badge(this.assignments.length + ' devices', 'cyan')
]),
E('table', { 'class': 'kiss-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Device')),
E('th', {}, _('MAC')),
E('th', {}, _('IP')),
E('th', {}, _('Profile')),
E('th', { 'style': 'width: 60px;' }, '')
])
]),
E('tbody', {}, rows)
])
);
},
showCreateProfileModal: function(clients, groups) {
var self = this;
var inputStyle = 'width: 100%; padding: 8px 12px; background: var(--kiss-bg); border: 1px solid var(--kiss-line); border-radius: 6px; color: var(--kiss-text);';
var body = E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Profile Name')),
E('input', { 'type': 'text', 'id': 'profile-name', 'style': inputStyle, 'placeholder': _('e.g., Gaming PC') })
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Description')),
E('input', { 'type': 'text', 'id': 'profile-desc', 'style': inputStyle, 'placeholder': _('Optional description') })
]),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Icon')),
E('select', { 'id': 'profile-icon', 'style': inputStyle },
Object.keys(PROFILE_ICONS).map(function(key) {
return E('option', { 'value': key }, PROFILE_ICONS[key].icon + ' ' + key);
})
)
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Color')),
E('input', { 'type': 'color', 'id': 'profile-color', 'value': '#8b5cf6', 'style': 'width: 100%; height: 38px; border-radius: 6px; border: 1px solid var(--kiss-line);' })
])
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Priority (1-8, lower = higher priority)')),
E('input', { 'type': 'number', 'id': 'profile-priority', 'style': inputStyle, 'min': '1', 'max': '8', 'value': '5' })
]),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Download Limit (kbit/s)')),
E('input', { 'type': 'number', 'id': 'profile-limit-down', 'style': inputStyle, 'min': '0', 'value': '0', 'placeholder': '0 = unlimited' })
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Upload Limit (kbit/s)')),
E('input', { 'type': 'number', 'id': 'profile-limit-up', 'style': inputStyle, 'min': '0', 'value': '0', 'placeholder': '0 = unlimited' })
])
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Latency Mode')),
E('select', { 'id': 'profile-latency', 'style': inputStyle }, [
E('option', { 'value': 'normal' }, _('Normal')),
E('option', { 'value': 'low' }, _('Low Latency')),
E('option', { 'value': 'ultra' }, _('Ultra Low Latency (Gaming)'))
])
]),
E('div', {}, [
E('label', { 'style': 'display: flex; align-items: center; gap: 8px; cursor: pointer;' }, [
E('input', { 'type': 'checkbox', 'id': 'profile-isolate' }),
_('Network Isolation (block LAN communication)')
])
])
]);
ui.showModal(_('Create New Profile'), [
body,
E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px;' }, [
E('button', { 'class': 'kiss-btn', 'click': ui.hideModal }, _('Cancel')),
E('button', { 'class': 'kiss-btn kiss-btn-green', 'click': function() { self.createProfile(); } }, _('Create Profile'))
])
]);
},
createProfile: function() {
var self = this;
var name = document.getElementById('profile-name').value;
var description = document.getElementById('profile-desc').value;
var icon = document.getElementById('profile-icon').value;
var color = document.getElementById('profile-color').value;
var priority = parseInt(document.getElementById('profile-priority').value) || 5;
var limit_down = parseInt(document.getElementById('profile-limit-down').value) || 0;
var limit_up = parseInt(document.getElementById('profile-limit-up').value) || 0;
var latency_mode = document.getElementById('profile-latency').value;
var isolate = document.getElementById('profile-isolate').checked ? '1' : '0';
if (!name) {
ui.addNotification(null, E('p', _('Profile name is required')), 'error');
return;
}
API.createProfile(name, description, icon, color, priority, limit_down, limit_up, latency_mode, '', isolate, '').then(function(result) {
if (result.success) {
ui.hideModal();
ui.addNotification(null, E('p', _('Profile created successfully')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.message || _('Failed to create profile')), 'error');
}
});
},
showEditProfileModal: function(profile) {
var self = this;
var inputStyle = 'width: 100%; padding: 8px 12px; background: var(--kiss-bg); border: 1px solid var(--kiss-line); border-radius: 6px; color: var(--kiss-text);';
var body = E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Profile Name')),
E('input', { 'type': 'text', 'id': 'edit-profile-name', 'style': inputStyle, 'value': profile.name })
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Description')),
E('input', { 'type': 'text', 'id': 'edit-profile-desc', 'style': inputStyle, 'value': profile.description || '' })
]),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Icon')),
E('select', { 'id': 'edit-profile-icon', 'style': inputStyle },
Object.keys(PROFILE_ICONS).map(function(key) {
return E('option', { 'value': key, 'selected': key === profile.icon }, PROFILE_ICONS[key].icon + ' ' + key);
})
)
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Color')),
E('input', { 'type': 'color', 'id': 'edit-profile-color', 'value': profile.color || '#8b5cf6', 'style': 'width: 100%; height: 38px; border-radius: 6px; border: 1px solid var(--kiss-line);' })
])
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Priority (1-8)')),
E('input', { 'type': 'number', 'id': 'edit-profile-priority', 'style': inputStyle, 'min': '1', 'max': '8', 'value': String(profile.priority) })
]),
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Download Limit (kbit/s)')),
E('input', { 'type': 'number', 'id': 'edit-profile-limit-down', 'style': inputStyle, 'min': '0', 'value': String(profile.limit_down) })
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Upload Limit (kbit/s)')),
E('input', { 'type': 'number', 'id': 'edit-profile-limit-up', 'style': inputStyle, 'min': '0', 'value': String(profile.limit_up) })
])
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Latency Mode')),
E('select', { 'id': 'edit-profile-latency', 'style': inputStyle }, [
E('option', { 'value': 'normal', 'selected': profile.latency_mode === 'normal' }, _('Normal')),
E('option', { 'value': 'low', 'selected': profile.latency_mode === 'low' }, _('Low Latency')),
E('option', { 'value': 'ultra', 'selected': profile.latency_mode === 'ultra' }, _('Ultra Low Latency'))
])
]),
E('div', {}, [
E('label', { 'style': 'display: flex; align-items: center; gap: 8px; cursor: pointer;' }, [
E('input', { 'type': 'checkbox', 'id': 'edit-profile-isolate', 'checked': profile.isolate }),
_('Network Isolation')
])
])
]);
ui.showModal(_('Edit Profile: ') + profile.name, [
body,
E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px;' }, [
E('button', { 'class': 'kiss-btn', 'click': ui.hideModal }, _('Cancel')),
E('button', { 'class': 'kiss-btn kiss-btn-green', 'click': function() { self.updateProfile(profile.id); } }, _('Save Changes'))
])
]);
},
updateProfile: function(profileId) {
var self = this;
var name = document.getElementById('edit-profile-name').value;
var description = document.getElementById('edit-profile-desc').value;
var icon = document.getElementById('edit-profile-icon').value;
var color = document.getElementById('edit-profile-color').value;
var priority = parseInt(document.getElementById('edit-profile-priority').value) || 5;
var limit_down = parseInt(document.getElementById('edit-profile-limit-down').value) || 0;
var limit_up = parseInt(document.getElementById('edit-profile-limit-up').value) || 0;
var latency_mode = document.getElementById('edit-profile-latency').value;
var isolate = document.getElementById('edit-profile-isolate').checked ? '1' : '0';
API.updateProfile(profileId, name, description, icon, color, priority, limit_down, limit_up, latency_mode, '', isolate, '', '1').then(function(result) {
if (result.success) {
ui.hideModal();
ui.addNotification(null, E('p', _('Profile updated successfully')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.message || _('Failed to update profile')), 'error');
}
});
},
deleteProfile: function(profile) {
var self = this;
if (!confirm(_('Delete profile "%s"? This will also remove all device assignments.').format(profile.name))) {
return;
}
API.deleteProfile(profile.id).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Profile deleted')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.message || _('Failed to delete profile')), 'error');
}
});
},
cloneProfile: function(profile) {
var self = this;
var newName = prompt(_('Enter name for cloned profile:'), profile.name + ' (Copy)');
if (!newName) return;
API.cloneProfile(profile.id, newName).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Profile cloned successfully')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.message || _('Failed to clone profile')), 'error');
}
});
},
showAssignModal: function(profiles, clients) {
var self = this;
var inputStyle = 'width: 100%; padding: 8px 12px; background: var(--kiss-bg); border: 1px solid var(--kiss-line); border-radius: 6px; color: var(--kiss-text);';
var body = E('div', { 'style': 'display: flex; flex-direction: column; gap: 16px;' }, [
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Select Device')),
E('select', { 'id': 'assign-device', 'style': inputStyle },
clients.map(function(client) {
return E('option', { 'value': client.mac }, (client.hostname || 'Unknown') + ' (' + client.mac + ')');
})
)
]),
E('div', {}, [
E('label', { 'style': 'display: block; margin-bottom: 6px; font-weight: 500;' }, _('Select Profile')),
E('select', { 'id': 'assign-profile', 'style': inputStyle },
profiles.map(function(profile) {
var iconInfo = PROFILE_ICONS[profile.icon] || PROFILE_ICONS['tag'];
return E('option', { 'value': profile.id }, iconInfo.icon + ' ' + profile.name);
})
)
])
]);
ui.showModal(_('Assign Profile to Device'), [
body,
E('div', { 'style': 'display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px;' }, [
E('button', { 'class': 'kiss-btn', 'click': ui.hideModal }, _('Cancel')),
E('button', { 'class': 'kiss-btn kiss-btn-green', 'click': function() { self.assignProfile(); } }, _('Assign'))
])
]);
},
assignProfile: function() {
var mac = document.getElementById('assign-device').value;
var profileId = document.getElementById('assign-profile').value;
API.assignProfileToDevice(mac, profileId, 0, 0).then(function(result) {
if (result.success) {
ui.hideModal();
ui.addNotification(null, E('p', _('Profile assigned to device')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.message || _('Failed to assign profile')), 'error');
}
});
},
removeAssignment: function(mac) {
if (!confirm(_('Remove profile assignment for this device?'))) {
return;
}
API.removeProfileAssignment(mac).then(function(result) {
if (result.success) {
ui.addNotification(null, E('p', _('Assignment removed')), 'success');
window.location.reload();
} else {
ui.addNotification(null, E('p', result.message || _('Failed to remove assignment')), 'error');
}
});
},
showProfileDetails: function(profile, assignments) {
var iconInfo = PROFILE_ICONS[profile.icon] || PROFILE_ICONS['tag'];
var detailRows = [
{ label: _('Priority'), value: String(profile.priority) },
{ label: _('Download Limit'), value: profile.limit_down > 0 ? this.formatSpeed(profile.limit_down) : _('Unlimited') },
{ label: _('Upload Limit'), value: profile.limit_up > 0 ? this.formatSpeed(profile.limit_up) : _('Unlimited') },
{ label: _('Latency Mode'), value: profile.latency_mode || 'normal' },
{ label: _('Network Isolation'), value: profile.isolate ? _('Yes') : _('No') },
{ label: _('Assigned Devices'), value: String(assignments.length) }
];
var details = E('div', {}, [
// Header
E('div', { 'style': 'display: flex; align-items: center; gap: 16px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--kiss-line);' }, [
E('div', {
'style': 'width: 64px; height: 64px; background: var(--kiss-bg2); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 32px;'
}, iconInfo.icon),
E('div', {}, [
E('h3', { 'style': 'margin: 0 0 4px 0; font-size: 20px;' }, profile.name),
E('p', { 'style': 'margin: 0; color: var(--kiss-muted);' }, profile.description || _('No description'))
])
]),
// Details table
E('div', { 'style': 'display: flex; flex-direction: column; gap: 8px;' },
detailRows.map(function(row) {
return E('div', {
'style': 'display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--kiss-line);'
}, [
E('span', { 'style': 'color: var(--kiss-muted);' }, row.label),
E('span', { 'style': 'font-weight: 500;' }, row.value)
]);
})
)
]);
if (assignments.length > 0) {
details.appendChild(E('h4', { 'style': 'margin: 20px 0 12px 0;' }, _('Assigned Devices:')));
details.appendChild(E('div', { 'style': 'display: flex; flex-direction: column; gap: 6px;' },
assignments.map(function(a) {
return E('div', {
'style': 'padding: 8px 12px; background: var(--kiss-bg); border-radius: 6px; font-family: monospace; font-size: 13px;'
}, (a.hostname || 'Unknown') + ' - ' + a.mac);
})
));
}
ui.showModal(_('Profile Details'), [
details,
E('div', { 'style': 'display: flex; justify-content: flex-end; margin-top: 20px;' }, [
E('button', { 'class': 'kiss-btn', 'click': ui.hideModal }, _('Close'))
])
]);
},
formatSpeed: function(kbits) {
if (kbits >= 1000000) {
return (kbits / 1000000).toFixed(1) + ' Gbit/s';
} else if (kbits >= 1000) {
return (kbits / 1000).toFixed(1) + ' Mbit/s';
}
return kbits + ' kbit/s';
}
});