working!
This commit is contained in:
1 parent
cfe0ee5ddd
commit
50774bfbf7
2 files changed
+171
-159
No files matched your search
+74
-78
@@ -2,7 +2,6 @@
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require fs';
|
||||
|
||||
return view.extend({
|
||||
@@ -19,153 +18,150 @@ return view.extend({
|
||||
|
||||
m = new form.Map('usbgadget',
|
||||
_('USB Gadget Configuration'),
|
||||
_('Configure USB gadget functions to make OpenWrt appear as various USB devices when connected to a host computer.'));
|
||||
_('Configure USB gadget functions. Changes take effect after saving and restarting the service.'));
|
||||
|
||||
// Device Configuration Section
|
||||
// =================================================================
|
||||
// Device Settings
|
||||
// =================================================================
|
||||
s = m.section(form.TypedSection, 'device', _('Device Settings'));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable USB Gadget'),
|
||||
_('Enable USB gadget mode. Disable to use USB port in host mode (for USB storage, etc.)'));
|
||||
_('Disable to use USB port in host mode'));
|
||||
o.rmempty = false;
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Value, 'gadget_name', _('Gadget Name'),
|
||||
_('Internal gadget identifier. Can be any string.'));
|
||||
o = s.option(form.Value, 'gadget_name', _('Gadget Name'));
|
||||
o.placeholder = 'g1';
|
||||
o.default = 'g1';
|
||||
|
||||
o = s.option(form.Value, 'manufacturer', _('Manufacturer String'),
|
||||
_('USB manufacturer string shown to the host computer'));
|
||||
o = s.option(form.Value, 'manufacturer', _('Manufacturer'));
|
||||
o.placeholder = 'OpenWrt';
|
||||
o.default = 'OpenWrt';
|
||||
|
||||
o = s.option(form.Value, 'product', _('Product String'),
|
||||
_('USB product string shown to the host computer'));
|
||||
o = s.option(form.Value, 'product', _('Product'));
|
||||
o.placeholder = 'USB Gadget';
|
||||
o.default = 'USB Gadget';
|
||||
|
||||
o = s.option(form.Value, 'vendor_id', _('Vendor ID'),
|
||||
_('USB Vendor ID in hexadecimal format (0x1d6b = Linux Foundation)'));
|
||||
o = s.option(form.Value, 'vendor_id', _('Vendor ID'));
|
||||
o.placeholder = '0x1d6b';
|
||||
o.default = '0x1d6b';
|
||||
o.validate = function(section_id, value) {
|
||||
if (!/^0x[0-9a-fA-F]{4}$/.test(value))
|
||||
return _('Must be a 4-digit hexadecimal number (e.g., 0x1d6b)');
|
||||
if (value && !/^0x[0-9a-fA-F]{4}$/.test(value))
|
||||
return _('Must be hex format: 0x1234');
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.option(form.Value, 'product_id', _('Product ID'),
|
||||
_('USB Product ID in hexadecimal format'));
|
||||
o = s.option(form.Value, 'product_id', _('Product ID'));
|
||||
o.placeholder = '0x0104';
|
||||
o.default = '0x0104';
|
||||
o.validate = function(section_id, value) {
|
||||
if (!/^0x[0-9a-fA-F]{4}$/.test(value))
|
||||
return _('Must be a 4-digit hexadecimal number (e.g., 0x0104)');
|
||||
if (value && !/^0x[0-9a-fA-F]{4}$/.test(value))
|
||||
return _('Must be hex format: 0x1234');
|
||||
return true;
|
||||
};
|
||||
|
||||
o = s.option(form.Value, 'device_version', _('Device Version'),
|
||||
_('USB device version in BCD format'));
|
||||
o.placeholder = '0x0100';
|
||||
o.default = '0x0100';
|
||||
|
||||
if (udc_devices.length > 0) {
|
||||
o = s.option(form.ListValue, 'udc_device', _('UDC Device'),
|
||||
_('USB Device Controller. Leave empty for auto-detection.'));
|
||||
o = s.option(form.ListValue, 'udc_device', _('UDC Device'));
|
||||
o.value('', _('Auto-detect'));
|
||||
udc_devices.forEach(function(dev) {
|
||||
o.value(dev.name, dev.name);
|
||||
});
|
||||
}
|
||||
|
||||
// USB Functions Section
|
||||
s = m.section(form.TypedSection, 'function', _('USB Functions'),
|
||||
_('Enable one or more USB functions. Only one network function (RNDIS/ECM/NCM) should be enabled at a time.'));
|
||||
s.anonymous = false;
|
||||
s.addremove = false;
|
||||
|
||||
s.tab('network', _('Network Functions'));
|
||||
s.tab('serial', _('Serial Console'));
|
||||
s.tab('storage', _('Mass Storage'));
|
||||
|
||||
// =================================================================
|
||||
// RNDIS
|
||||
o = s.taboption('network', form.Flag, 'enabled', _('Enable'));
|
||||
o.modalonly = false;
|
||||
o.depends('.name', 'rndis');
|
||||
// =================================================================
|
||||
s = m.section(form.NamedSection, 'rndis', 'function',
|
||||
_('RNDIS (Windows Ethernet)'),
|
||||
_('Windows-compatible USB ethernet. Works on all Windows versions.'));
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable RNDIS'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('network', form.DummyValue, '_rndis_desc', _('Description'));
|
||||
o.depends('.name', 'rndis');
|
||||
o = s.option(form.DummyValue, '_desc', _('Description'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return _('RNDIS - Windows compatible USB ethernet (plug-and-play on all Windows versions)');
|
||||
return _('Plug-and-play on Windows. Automatically adds to LAN bridge.');
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// ECM
|
||||
o = s.taboption('network', form.Flag, 'enabled', _('Enable'));
|
||||
o.depends('.name', 'ecm');
|
||||
// =================================================================
|
||||
s = m.section(form.NamedSection, 'ecm', 'function',
|
||||
_('ECM (macOS/Linux Ethernet)'),
|
||||
_('For macOS ≤10.14 and Linux. Do not enable with NCM.'));
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable ECM'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('network', form.DummyValue, '_ecm_desc', _('Description'));
|
||||
o.depends('.name', 'ecm');
|
||||
o = s.option(form.DummyValue, '_desc', _('Description'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return _('ECM - CDC Ethernet for macOS (≤10.14) and Linux. Do not enable with NCM.');
|
||||
return _('CDC Ethernet for macOS and Linux. Disable RNDIS when using.');
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// NCM
|
||||
o = s.taboption('network', form.Flag, 'enabled', _('Enable'));
|
||||
o.depends('.name', 'ncm');
|
||||
// =================================================================
|
||||
s = m.section(form.NamedSection, 'ncm', 'function',
|
||||
_('NCM (Modern Ethernet)'),
|
||||
_('Best performance. For Windows 11+, macOS ≥10.15, Linux.'));
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable NCM'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('network', form.DummyValue, '_ncm_desc', _('Description'));
|
||||
o.depends('.name', 'ncm');
|
||||
o = s.option(form.DummyValue, '_desc', _('Description'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return _('NCM - Modern high-performance USB ethernet for Windows 11+, macOS ≥10.15, Linux. Best performance.');
|
||||
return _('Modern high-speed USB ethernet. Best choice for new systems.');
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// ACM Serial
|
||||
o = s.taboption('serial', form.Flag, 'enabled', _('Enable Serial Console'));
|
||||
o.depends('.name', 'acm');
|
||||
// =================================================================
|
||||
s = m.section(form.NamedSection, 'acm', 'function',
|
||||
_('ACM (Serial Console)'),
|
||||
_('Provides /dev/ttyACM0 on host. Access: screen /dev/ttyACM0 115200'));
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Serial Console'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('serial', form.Flag, 'shell', _('Enable Login Shell'),
|
||||
_('Provide login shell on serial console. Disable for raw TTY mode.'));
|
||||
o.depends('.name', 'acm');
|
||||
o = s.option(form.Flag, 'shell', _('Enable Login Shell'),
|
||||
_('Provide shell access. Disable for raw TTY.'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.taboption('serial', form.DummyValue, '_acm_info', _('Connection Info'));
|
||||
o.depends('.name', 'acm');
|
||||
o = s.option(form.DummyValue, '_info', _('Connection Info'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return _('Access via /dev/ttyACM0 on Linux/macOS or COMx on Windows. Use: screen /dev/ttyACM0 115200');
|
||||
return '<strong>Linux/macOS:</strong> screen /dev/ttyACM0 115200<br>' +
|
||||
'<strong>Windows:</strong> Use PuTTY on COM port';
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// Mass Storage
|
||||
o = s.taboption('storage', form.Flag, 'enabled', _('Enable Mass Storage'));
|
||||
o.depends('.name', 'ums');
|
||||
// =================================================================
|
||||
s = m.section(form.NamedSection, 'ums', 'function',
|
||||
_('Mass Storage'),
|
||||
_('Expose storage image as USB drive.'));
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enable Mass Storage'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('storage', form.Value, 'image_path', _('Image File Path'),
|
||||
_('Path to storage image file. Created automatically if it doesn\'t exist.'));
|
||||
o.depends('.name', 'ums');
|
||||
o = s.option(form.Value, 'image_path', _('Image Path'),
|
||||
_('File will be created automatically if it doesn\'t exist.'));
|
||||
o.default = '/var/lib/usb-gadget/storage.img';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.taboption('storage', form.Value, 'image_size', _('Image Size'),
|
||||
_('Size of image file when created (e.g., 512M, 1G, 2G)'));
|
||||
o.depends('.name', 'ums');
|
||||
o.default = '512M';
|
||||
o = s.option(form.Value, 'image_size', _('Image Size'),
|
||||
_('Used when creating (e.g., 512M, 1G, 2G)'));
|
||||
o.placeholder = '512M';
|
||||
o.default = '512M';
|
||||
|
||||
o = s.taboption('storage', form.Flag, 'readonly', _('Read-Only Mode'),
|
||||
_('Mount storage as read-only on the host'));
|
||||
o.depends('.name', 'ums');
|
||||
o = s.option(form.Flag, 'readonly', _('Read-Only'),
|
||||
_('Mount as read-only on the host computer'));
|
||||
o.default = '0';
|
||||
|
||||
return m.render();
|
||||
},
|
||||
|
||||
handleSaveApply: function(ev, mode) {
|
||||
return this.super('handleSaveApply', [ev, mode]).then(function() {
|
||||
return fs.exec('/etc/init.d/usb-gadget', ['restart']);
|
||||
});
|
||||
}
|
||||
});
|
||||
+97
-81
@@ -1,26 +1,16 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require poll';
|
||||
|
||||
var callServiceList = rpc.declare({
|
||||
object: 'service',
|
||||
method: 'list',
|
||||
params: ['name'],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('usbgadget'),
|
||||
L.resolveDefault(fs.exec('/etc/init.d/usb-gadget', ['status']), null),
|
||||
L.resolveDefault(fs.list('/sys/class/udc'), []),
|
||||
L.resolveDefault(callServiceList('usb-gadget'), {})
|
||||
L.resolveDefault(fs.list('/sys/class/udc'), [])
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -28,43 +18,11 @@ return view.extend({
|
||||
var status_result = data[1];
|
||||
var udc_devices = data[2];
|
||||
|
||||
var enabled = uci.get('usbgadget', 'usb', 'enabled') == '1';
|
||||
var enabled = uci.get('usbgadget', 'usb', 'enabled');
|
||||
var gadget_name = uci.get('usbgadget', 'usb', 'gadget_name') || 'g1';
|
||||
var manufacturer = uci.get('usbgadget', 'usb', 'manufacturer') || 'OpenWrt';
|
||||
|
||||
var m, s, o;
|
||||
|
||||
// Crear un mapa simple para mostrar información
|
||||
m = new form.JSONMap({},
|
||||
_('USB Gadget Status'),
|
||||
_('View current USB gadget configuration and status'));
|
||||
|
||||
s = m.section(form.NamedSection, '_status', 'status');
|
||||
|
||||
// Status Overview
|
||||
o = s.option(form.DummyValue, '_status', _('Gadget Status'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return enabled ?
|
||||
'<span style="color:green">●</span> ' + _('Active (Device Mode)') :
|
||||
'<span style="color:red">●</span> ' + _('Disabled (Host Mode)');
|
||||
};
|
||||
|
||||
o = s.option(form.DummyValue, '_gadget_name', _('Gadget Name'));
|
||||
o.cfgvalue = function() { return gadget_name; };
|
||||
|
||||
// UDC Controller
|
||||
if (udc_devices.length > 0) {
|
||||
o = s.option(form.DummyValue, '_udc', _('USB Device Controller'));
|
||||
o.cfgvalue = function() {
|
||||
return udc_devices.map(function(dev) {
|
||||
return dev.name;
|
||||
}).join(', ');
|
||||
};
|
||||
}
|
||||
|
||||
// Enabled Functions
|
||||
o = s.option(form.DummyValue, '_functions', _('Enabled Functions'));
|
||||
o.cfgvalue = function() {
|
||||
// Obtener funciones habilitadas
|
||||
var functions = [];
|
||||
['rndis', 'ecm', 'ncm', 'acm', 'ums'].forEach(function(func) {
|
||||
if (uci.get('usbgadget', func, 'enabled') == '1') {
|
||||
@@ -72,59 +30,117 @@ return view.extend({
|
||||
functions.push(desc);
|
||||
}
|
||||
});
|
||||
return functions.length > 0 ? functions.join(', ') : _('None');
|
||||
};
|
||||
|
||||
// Service Controls
|
||||
o = s.option(form.Button, '_restart', _('Service Control'));
|
||||
o.inputtitle = _('Restart Service');
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = function() {
|
||||
return fs.exec('/etc/init.d/usb-gadget', ['restart'])
|
||||
// Construir array de filas de la tabla
|
||||
var tableRows = [
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left', 'width': '33%' }, E('strong', {}, _('Gadget Status'))),
|
||||
E('td', { 'class': 'td left', 'id': 'status_value' },
|
||||
enabled == '1' ?
|
||||
E('span', { 'style': 'color:green' }, '● ' + _('Active (Device Mode)')) :
|
||||
E('span', { 'style': 'color:red' }, '● ' + _('Disabled (Host Mode)'))
|
||||
)
|
||||
]),
|
||||
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, E('strong', {}, _('Gadget Name'))),
|
||||
E('td', { 'class': 'td left' }, gadget_name)
|
||||
]),
|
||||
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, E('strong', {}, _('Manufacturer'))),
|
||||
E('td', { 'class': 'td left' }, manufacturer)
|
||||
]),
|
||||
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, E('strong', {}, _('Enabled Functions'))),
|
||||
E('td', { 'class': 'td left' },
|
||||
functions.length > 0 ? functions.join(', ') : E('em', {}, _('None')))
|
||||
])
|
||||
];
|
||||
|
||||
// Añadir fila UDC solo si hay dispositivos
|
||||
if (udc_devices.length > 0) {
|
||||
tableRows.splice(3, 0, E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, E('strong', {}, _('USB Controller'))),
|
||||
E('td', { 'class': 'td left' }, udc_devices.map(function(d) { return d.name; }).join(', '))
|
||||
]));
|
||||
}
|
||||
|
||||
var viewElements = [
|
||||
E('h2', {}, _('USB Gadget Status')),
|
||||
E('div', { 'class': 'cbi-map-descr' },
|
||||
_('View current USB gadget configuration and status')),
|
||||
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Current Status')),
|
||||
E('table', { 'class': 'table' }, tableRows)
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Service Control')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-apply',
|
||||
'click': function() {
|
||||
ui.showModal(_('Restarting service...'), [
|
||||
E('p', { 'class': 'spinning' }, _('Please wait...'))
|
||||
]);
|
||||
|
||||
fs.exec('/etc/init.d/usb-gadget', ['restart'])
|
||||
.then(function() {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null,
|
||||
E('p', _('USB Gadget service restarted')),
|
||||
E('p', _('USB Gadget service restarted successfully')),
|
||||
'info');
|
||||
})
|
||||
.catch(function(e) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null,
|
||||
E('p', _('Failed to restart service: %s').format(e.message)),
|
||||
E('p', _('Failed to restart: %s').format(e.message)),
|
||||
'error');
|
||||
});
|
||||
};
|
||||
}
|
||||
}, _('Restart Service'))
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
];
|
||||
|
||||
// Detailed Status Output
|
||||
// Añadir output detallado solo si existe
|
||||
if (status_result && status_result.stdout) {
|
||||
s = m.section(form.NamedSection, '_details', 'details',
|
||||
_('Detailed Status'));
|
||||
|
||||
o = s.option(form.DummyValue, '_output', _('Status Output'));
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
return '<pre style="background:#f0f0f0; padding:10px; overflow:auto; max-height:400px">' +
|
||||
status_result.stdout + '</pre>';
|
||||
};
|
||||
viewElements.push(
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', {}, _('Detailed Output')),
|
||||
E('pre', {
|
||||
'style': 'background:#f9f9f9; padding:10px; overflow:auto; max-height:400px; border:1px solid #ddd'
|
||||
}, status_result.stdout)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return m.render().then(function(mapNode) {
|
||||
var view = E('div', { 'class': 'cbi-map' }, viewElements);
|
||||
|
||||
// Polling cada 5 segundos
|
||||
poll.add(function() {
|
||||
return Promise.all([
|
||||
uci.load('usbgadget'),
|
||||
L.resolveDefault(fs.exec('/etc/init.d/usb-gadget', ['status']), null)
|
||||
]).then(function(pollData) {
|
||||
var newStatus = pollData[1];
|
||||
var statusNode = mapNode.querySelector('[data-name="_status"] .td.right');
|
||||
if (statusNode) {
|
||||
var nowEnabled = uci.get('usbgadget', 'usb', 'enabled') == '1';
|
||||
statusNode.innerHTML = nowEnabled ?
|
||||
'<span style="color:green">●</span> ' + _('Active (Device Mode)') :
|
||||
'<span style="color:red">●</span> ' + _('Disabled (Host Mode)');
|
||||
return uci.load('usbgadget').then(function() {
|
||||
var newEnabled = uci.get('usbgadget', 'usb', 'enabled');
|
||||
var statusEl = document.getElementById('status_value');
|
||||
if (statusEl) {
|
||||
statusEl.innerHTML = '';
|
||||
statusEl.appendChild(
|
||||
newEnabled == '1' ?
|
||||
E('span', { 'style': 'color:green' }, '● ' + _('Active (Device Mode)')) :
|
||||
E('span', { 'style': 'color:red' }, '● ' + _('Disabled (Host Mode)'))
|
||||
);
|
||||
}
|
||||
});
|
||||
}, 5);
|
||||
|
||||
return mapNode;
|
||||
});
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
|
||||
Reference in new issue
Block a user