luci-proto-modemmanager: add status page
authorLukas Voegl <lvoegl@tdt.de>
Fri, 2 Jun 2023 12:21:43 +0000 (14:21 +0200)
committerFlorian Eckert <fe@dev.tdt.de>
Wed, 5 Jul 2023 06:55:38 +0000 (08:55 +0200)
Signed-off-by: Lukas Voegl <lvoegl@tdt.de>
(cherry picked from commit 54aa70112c1a8310fa4ba20c9e50e16b24f519cc)

protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js [new file with mode: 0644]
protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js [new file with mode: 0644]
protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json [new file with mode: 0644]
protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json

diff --git a/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js b/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/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js b/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/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json b/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" ]
+               }
+       }
+}
index e3d5df7bec0665f1e0d40f15efa95456e1553a0a..dd4da115eb1bedc8ff292cd93ae33f30edf407b6 100644 (file)
@@ -7,7 +7,11 @@
                                "/usr/bin/mmcli -L": [ "exec" ],
                                "/usr/bin/mmcli -m [0-9]": [ "exec" ],
                                "/usr/bin/mmcli -m [0-9][0-9]": [ "exec" ],
-                               "/usr/bin/mmcli -m [0-9][0-9][0-9]": [ "exec" ]
+                               "/usr/bin/mmcli -m [0-9][0-9][0-9]": [ "exec" ],
+                               "/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" ]
                        }
                }
        }