luci: Add luci-proto-modemmanager librecmc-v6.0
authorRISCi_ATOM <bob@bobcall.me>
Sat, 23 Nov 2024 05:02:20 +0000 (00:02 -0500)
committerRISCi_ATOM <bob@bobcall.me>
Sat, 23 Nov 2024 05:02:20 +0000 (00:02 -0500)
package/luci/protocols/luci-proto-modemmanager/Makefile [new file with mode: 0644]
package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js [new file with mode: 0644]
package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js [new file with mode: 0644]
package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js [new file with mode: 0644]
package/luci/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json [new file with mode: 0644]
package/luci/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json [new file with mode: 0644]

diff --git a/package/luci/protocols/luci-proto-modemmanager/Makefile b/package/luci/protocols/luci-proto-modemmanager/Makefile
new file mode 100644 (file)
index 0000000..c2718dd
--- /dev/null
@@ -0,0 +1,14 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for ModemManager
+LUCI_DEPENDS:=+modemmanager
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js b/package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js
new file mode 100644 (file)
index 0000000..b8558b8
--- /dev/null
@@ -0,0 +1,101 @@
+'use strict';
+'require baseclass';
+'require fs';
+
+return baseclass.extend({
+
+       _mmcliBin: '/usr/bin/mmcli',
+
+       _emptyStringValue: '--',
+
+       _parseIndex: function (dbusPath) {
+               var index = dbusPath.split('/').slice(-1);
+               return parseInt(index);
+       },
+
+       _parseOutput: function (output) {
+               try {
+                       return this._removeEmptyStrings(JSON.parse(output));
+               } catch (err) {
+                       return null;
+               }
+       },
+
+       _removeEmptyStrings: function (obj) {
+               if (obj == null) {
+                       return obj;
+               }
+
+               if (typeof obj == 'string') {
+                       if (obj == this._emptyStringValue) {
+                               obj = null;
+                       }
+               } else if (Array.isArray()) {
+                       obj = obj.map(L.bind(function (it) {
+                               return this._removeEmptyStrings(it);
+                       }, this));
+               } else {
+                       var keys = Object.keys(obj);
+                       keys.forEach(L.bind(function (key) {
+                               obj[key] = this._removeEmptyStrings(obj[key]);
+                       }, this));
+               }
+
+               return obj;
+       },
+
+       getModems: function () {
+               return fs.exec_direct(this._mmcliBin, [ '-L', '-J' ]).then(L.bind(function (res) {
+                       var json = this._parseOutput(res);
+                       if (json == null) {
+                               return [];
+                       }
+                       var modems = json['modem-list'];
+                       var tasks = [];
+
+                       modems.forEach(L.bind(function (modem) {
+                               var index = this._parseIndex(modem);
+                               if (!isNaN(index)) {
+                                       tasks.push(this.getModem(index));
+                               }
+                       }, this));
+                       return Promise.all(tasks);
+               }, this));
+       },
+
+       getModem: function (index) {
+               return fs.exec_direct(this._mmcliBin, [ '-m', index, '-J' ]).then(L.bind(function (modem) {
+                       return this._parseOutput(modem);
+               }, this));
+       },
+
+       getModemSims: function (modem) {
+               var tasks = [];
+               var simSlots = modem.generic['sim-slots'];
+               var sim = modem.generic.sim;
+               if (sim != null && !simSlots.includes(sim)) {
+                       simSlots.push(sim);
+               }
+
+               simSlots.forEach(L.bind(function (modem) {
+                       var index = this._parseIndex(modem);
+                       if (!isNaN(index)) {
+                               tasks.push(this.getSim(index));
+                       }
+               }, this));
+               return Promise.all(tasks);
+       },
+
+       getSim: function (index) {
+               return fs.exec_direct(this._mmcliBin, [ '-i', index, '-J' ]).then(L.bind(function (sim) {
+                       return this._parseOutput(sim);
+               }, this));
+       },
+
+       getModemLocation: function (modem) {
+               var index = this._parseIndex(modem['dbus-path']);
+               return fs.exec_direct(this._mmcliBin, [ '-m', index, '--location-get', '-J' ]).then(L.bind(function (location) {
+                       return this._parseOutput(location);
+               }, this));
+       }
+});
diff --git a/package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js b/package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js
new file mode 100644 (file)
index 0000000..b13dd31
--- /dev/null
@@ -0,0 +1,161 @@
+'use strict';
+'require fs';
+'require form';
+'require network';
+'require modemmanager_helper as helper';
+
+network.registerPatternVirtual(/^mobiledata-.+$/);
+network.registerErrorCode('MM_CONNECT_FAILED', _('Connection attempt failed.'));
+network.registerErrorCode('MM_CONNECT_IN_PROGRESS', _('Modem connection in progress. Please wait. This process will timeout after 2 minutes.'));
+network.registerErrorCode('DEVICE_NOT_MANAGED', _('Device not managed by ModemManager.'));
+network.registerErrorCode('INVALID_BEARER_LIST', _('Invalid bearer list. Possibly too many bearers created.  This protocol supports one and only one bearer.'));
+network.registerErrorCode('UNKNOWN_METHOD', _('Unknown and unsupported connection method.'));
+network.registerErrorCode('DISCONNECT_FAILED', _('Disconnection attempt failed.'));
+network.registerErrorCode('MM_INVALID_ALLOWED_MODES_LIST', _('Unable to set allowed mode list.'));
+network.registerErrorCode('MM_NO_PREFERRED_MODE_CONFIGURED', _('No preferred mode configuration found.'));
+network.registerErrorCode('MM_NO_ALLOWED_MODE_CONFIGURED', _('No allowed mode configuration found.'));
+network.registerErrorCode('MM_FAILED_SETTING_PREFERRED_MODE', _('Unable to set preferred mode.'));
+
+return network.registerProtocol('modemmanager', {
+       getI18n: function() {
+               return _('ModemManager');
+       },
+
+       getIfname: function() {
+               return this._ubus('l3_device') || 'modemmanager-%s'.format(this.sid);
+       },
+
+       getOpkgPackage: function() {
+               return 'modemmanager';
+       },
+
+       isFloating: function() {
+               return true;
+       },
+
+       isVirtual: function() {
+               return true;
+       },
+
+       getDevices: function() {
+               return null;
+       },
+
+       containsDevice: function(ifname) {
+               return (network.getIfnameOf(ifname) == this.getIfname());
+       },
+
+       renderFormOptions: function(s) {
+               var dev = this.getL3Device() || this.getDevice(), o;
+
+               o = s.taboption('general', form.ListValue, '_modem_device', _('Modem device'));
+               o.ucioption = 'device';
+               o.rmempty = false;
+               o.load = function(section_id) {
+                       return helper.getModems().then(L.bind(function(devices) {
+                               for (var i = 0; i < devices.length; i++) {
+                                       var generic = devices[i].modem.generic;
+                                       this.value(generic.device,
+                                               '%s - %s'.format(generic.manufacturer, generic.model));
+                               }
+                               return form.Value.prototype.load.apply(this, [section_id]);
+                       }, this));
+               };
+
+               o = s.taboption('general', form.Value, 'apn', _('APN'));
+               o.validate = function(section_id, value) {
+                       if (value == null || value == '')
+                               return true;
+
+                       if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
+                               return _('Invalid APN provided');
+
+                       return true;
+               };
+
+               o = s.taboption('general', form.Value, 'pincode', _('PIN'));
+               o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
+
+               o = s.taboption('general', form.ListValue, 'auth', _('Authentication Type'));
+               o.value('both', _('PAP/CHAP (both)'));
+               o.value('pap', 'PAP');
+               o.value('chap', 'CHAP');
+               o.value('none', _('None'));
+               o.default = 'none';
+
+               o = s.taboption('general', form.ListValue, 'allowedmode', _('Allowed network technology'),
+                       _('Setting the allowed network technology.'));
+               o.value('2g');
+               o.value('3g');
+               o.value('3g|2g');
+               o.value('4g');
+               o.value('4g|2g');
+               o.value('4g|3g');
+               o.value('4g|3g|2g');
+               o.value('5g');
+               o.value('5g|2g');
+               o.value('5g|3g');
+               o.value('5g|3g|2g');
+               o.value('5g|4g');
+               o.value('5g|4g|2g');
+               o.value('5g|4g|3g');
+               o.value('5g|4g|3g|2g');
+               o.value('',_('any'));
+               o.default = '';
+
+               o = s.taboption('general', form.ListValue, 'preferredmode', _('Preferred network technology'),
+                       _('Setting the preferred network technology.'));
+               o.value('2g');
+               o.value('3g');
+               o.value('4g');
+               o.value('5g');
+               o.value('none', _('None'));
+               o.depends('allowedmode','3g|2g');
+               o.depends('allowedmode','4g|2g');
+               o.depends('allowedmode','4g|3g');
+               o.depends('allowedmode','4g|3g|2g');
+               o.depends('allowedmode','5g|2g');
+               o.depends('allowedmode','5g|3g');
+               o.depends('allowedmode','5g|3g|2g');
+               o.depends('allowedmode','5g|4g');
+               o.depends('allowedmode','5g|4g|2g');
+               o.depends('allowedmode','5g|4g|3g');
+               o.depends('allowedmode','5g|4g|3g|2g');
+
+               o = s.taboption('general', form.Value, 'username', _('PAP/CHAP username'));
+               o.depends('auth', 'pap');
+               o.depends('auth', 'chap');
+               o.depends('auth', 'both');
+
+               o = s.taboption('general', form.Value, 'password', _('PAP/CHAP password'));
+               o.depends('auth', 'pap');
+               o.depends('auth', 'chap');
+               o.depends('auth', 'both');
+               o.password = true;
+
+               o = s.taboption('general', form.ListValue, 'iptype', _('IP Type'));
+               o.value('ipv4v6', _('IPv4/IPv6 (both - defaults to IPv4)'))
+               o.value('ipv4', _('IPv4 only'));
+               o.value('ipv6', _('IPv6 only'));
+               o.default = 'ipv4v6';
+
+               o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
+               o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
+               o.datatype    = 'max(9200)';
+
+               o = s.taboption('general', form.Value, 'signalrate', _('Signal Refresh Rate'), _("In seconds"));
+               o.datatype = 'uinteger';
+               
+               s.taboption('general', form.Value, 'metric', _('Gateway metric'));
+               
+               s.taboption('advanced', form.Flag, 'debugmode', _('Enable Debugmode'));
+
+               o = s.taboption('advanced', form.ListValue, 'loglevel', _('Log output level'));
+               o.value('ERR', _('Error'))
+               o.value('WARN', _('Warning'));
+               o.value('INFO', _('Info'));
+               o.value('DEBUG', _('Debug'));
+               o.default = 'ERR';
+               
+       }
+});
diff --git a/package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js b/package/luci/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js
new file mode 100644 (file)
index 0000000..3db7905
--- /dev/null
@@ -0,0 +1,181 @@
+'use strict';
+'require ui';
+'require view';
+'require poll';
+'require dom';
+'require modemmanager_helper as helper';
+
+return view.extend({
+       load: function() {
+               return helper.getModems().then(function (modems) {
+                       return Promise.all(modems.filter(function (modem){
+                               return modem != null;
+                       }).map(function (modem) {
+                               return helper.getModemSims(modem.modem).then(function (sims) {
+                                       modem.sims = sims.filter(function (sim) {
+                                               return sim != null;
+                                       });
+
+                                       return helper.getModemLocation(modem.modem).then(function (location) {
+                                               modem.location = location;
+                                               return modem;
+                                       });
+                               });
+                       }));
+               });
+       },
+
+       pollData: function (container) {
+               poll.add(L.bind(function () {
+                       return this.load().then(L.bind(function (modems) {
+                               dom.content(container, this.renderContent(modems));
+                       }, this));
+               }, this));
+       },
+
+       renderSections: function (name, sections) {
+               if (sections.length == 0) {
+                       sections.push(E('div', { 'class': 'cbi-section' }, [
+                               E('span', {}, _('Section %s is empty.').format(name))
+                       ]));
+               }
+
+               return E('div', { 'class': 'cbi-section' }, [
+                       E('h1', {}, name),
+                       ...sections
+               ]);
+       },
+
+       renderSection: function (name, table) {
+               var rowNodes = table.filter(function (row) {
+                       return row[1] != null;
+               }).map(function (row) {
+                       return E('tr', { 'class': 'tr' }, [
+                               E('td', { 'class': 'td', 'width': '33%' }, E('strong', {}, [row[0]])),
+                               E('td', { 'class': 'td' }, [row[1]])
+                       ]);
+               });
+
+               var tableNode;
+               if (rowNodes.length == 0) {
+                       tableNode = E('div', { 'class': 'cbi-section' }, [
+                               E('span', {}, _('Section %s is empty.').format(name))
+                       ])
+               } else {
+                       tableNode = E('table', { 'class': 'table', }, rowNodes);
+               }
+
+               return E('div', { 'class': 'cbi-section' }, [
+                       E('h2', {}, [name]),
+                       tableNode
+               ]);
+       },
+
+       renderContent: function (modems) {
+               var node = E('div', {}, E('div'));
+
+               modems.forEach(L.bind(function (modem) {
+                       var generic = modem.modem.generic;
+                       var modem3gpp = modem.modem['3gpp'];
+
+                       var modemSection = this.renderSection(_('Modem Info'), [
+                               [_('Manufacturer'), generic.manufacturer],
+                               [_('Model'), generic.model],
+                               [_('Revision'), generic.revision],
+                               [E('abbr', { 'title': _('International Mobile Station Equipment Identity') }, [
+                                       _('IMEI')
+                               ]), modem3gpp.imei],
+                               [_('Device Identifier'), generic['device-identifier']],
+                               [_('Power State'), generic['power-state']],
+                               [_('State'), generic.state],
+                               [_('Failed Reason'), generic['state-failed-reason']]
+                       ]);
+
+                       var ownNumbersStr = generic['own-numbers'].join(', ');
+                       var accessTechnologiesStr = generic['access-technologies'].join(', ');
+                       var signalQualityValue = parseInt(generic['signal-quality'].value);
+                       var networkSection = this.renderSection(_('Network Registration'), [
+                               [_('Own Numbers'), ownNumbersStr],
+                               [_('Access Technologies'), accessTechnologiesStr],
+                               [_('Operator') , modem3gpp['operator-name']],
+                               [_('Operator Code'), modem3gpp['operator-code']],
+                               [_('Registration State'), modem3gpp['registration-state']],
+                               [_('Packet Service State'), modem3gpp['packet-service-state']],
+                               [_('Signal Quality'), E('div', { 'class': 'cbi-progressbar', 'title': '%d %'.format(signalQualityValue) }, [
+                                       E('div', { 'style': 'width: %d%%'.format(signalQualityValue) })
+                               ])]
+                       ]);
+
+                       var location3gpp = {};
+                       if (modem.location != null) {
+                               location3gpp = modem.location.modem.location['3gpp'];
+                       }
+                       var locationSection = this.renderSection(_('Cell Location'), [
+                               [E('abbr', { 'title': _('Cell ID') }, [
+                                       'CID'
+                               ]), location3gpp.cid],
+                               [E('abbr', { 'title': _('Location Area Code') }, [
+                                       'LAC'
+                               ]), location3gpp.lac],
+                               [E('abbr', { 'title': _('Mobile Country Code') }, [
+                                       'MCC'
+                               ]), location3gpp.mcc],
+                               [E('abbr', { 'title': _('Mobile Network Code') }, [
+                                       'MNC'
+                               ]), location3gpp.mnc],
+                               [E('abbr', { 'title': _('Tracking Area Code') }, [
+                                       'TAC'
+                               ]), location3gpp.tac]
+                       ]);
+
+                       var simTables = modem.sims.map(function (sim) {
+                               var properties = sim.sim.properties;
+                               return [
+                                       [_('Active'), properties.active],
+                                       [_('Operator Name'), properties['operator-name']],
+                                       [E('abbr', { 'title': _('Integrated Circuit Card Identifier') }, [
+                                               'ICCID'
+                                       ]), properties.iccid],
+                                       [E('abbr', { 'title': _('International Mobile Subscriber Identity') }, [
+                                                       'IMSI'
+                                       ]), properties.imsi]
+                               ];
+                       });
+                       var simSubSections = simTables.map(L.bind(function (table, index) {
+                               return this.renderSection(_('SIM %d').format(index + 1), table)
+                       }, this));
+                       var simSection = this.renderSections(_('SIMs'), simSubSections);
+
+                       var sections = [
+                               E('div', { 'class': 'cbi-map-descr'}, []),
+                               modemSection,
+                               networkSection,
+                               locationSection,
+                               simSection
+                       ].filter(function (section) {
+                               return section != null;
+                       });
+                       node.firstElementChild.appendChild(E('div', { 'data-tab': generic.device, 'data-tab-title': generic.device }, sections));
+               }, this));
+               ui.tabs.initTabGroup(node.firstElementChild.childNodes);
+
+               return node;
+       },
+
+       render: function (modems) {
+               var content = E([], [
+                       E('h2', {}, [_('Mobile Service')]),
+                       E('div')
+               ]);
+               var container = content.lastElementChild;
+
+               dom.content(container, this.renderContent(modems));
+               this.pollData(container);
+
+               return content;
+       },
+
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null
+});
diff --git a/package/luci/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json b/package/luci/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json
new file mode 100644 (file)
index 0000000..7104f22
--- /dev/null
@@ -0,0 +1,13 @@
+{
+       "admin/status/modemmanager": {
+               "title": "Mobile Service",
+               "order": 10,
+               "action": {
+                       "type": "view",
+                       "path": "modemmanager/status"
+               },
+               "depends": {
+                       "acl": [ "luci-proto-modemmanager" ]
+               }
+       }
+}
diff --git a/package/luci/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json b/package/luci/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json
new file mode 100644 (file)
index 0000000..cde3e9c
--- /dev/null
@@ -0,0 +1,14 @@
+{
+       "luci-proto-modemmanager": {
+               "description": "Grant access to mmcli",
+               "read": {
+                       "cgi-io": [ "exec" ],
+                       "file": {
+                               "/usr/bin/mmcli -L -J": [ "exec" ],
+                               "/usr/bin/mmcli -m [0-9]* -J": [ "exec" ],
+                               "/usr/bin/mmcli -i [0-9]* -J": [ "exec" ],
+                               "/usr/bin/mmcli -m [0-9]* --location-get -J": [ "exec" ]
+                       }
+               }
+       }
+}