p910nd.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. //
  2. // Copyright (c) 2024 Paul Donald <newtwen@gmail.com>
  3. //
  4. 'use strict';
  5. 'require fs';
  6. 'require form';
  7. 'require uci';
  8. 'require ui';
  9. 'require view';
  10. 'require network';
  11. var pkg = {
  12. get Name() { return 'p910nd'; },
  13. get Description() { return _('Port 910n print daemon'); },
  14. get URL() { return 'https://openwrt.org/packages/pkgdata/' + pkg.Name + '/'; },
  15. get pkgMgrURI() { return 'admin/system/package-manager'; },
  16. };
  17. return view.extend({
  18. option_install_kmod_lp: function() {
  19. window.open(L.url(pkg.pkgMgrURI) +
  20. '?query=kmod-lp', '_blank', 'noopener');
  21. },
  22. option_install_kmod_usb: function() {
  23. window.open(L.url(pkg.pkgMgrURI) +
  24. '?query=kmod-usb-printer', '_blank', 'noopener');
  25. },
  26. load: function () {
  27. return Promise.all([
  28. network.getDevices(),
  29. // data[0]
  30. // # find -L /dev -maxdepth 3 -type c -name lp[0-9]
  31. fs.exec_direct('/usr/bin/find', [ '-L', '/dev', '-maxdepth', '3', '-type', 'c', '-name', 'lp[0-9]' ]).catch(function(err) {
  32. ui.addNotification(null, E('p', {}, _('Error executing "find" command: ' + err.message)));
  33. return '';
  34. }).then(data => {
  35. if(data)
  36. return data.trim().split('\n').sort();
  37. else
  38. return null;
  39. }),
  40. // data[1]
  41. fs.exec_direct('/usr/libexec/package-manager-call', [ 'list-installed' ], 'text').catch(function(err) {
  42. ui.addNotification(null, E('p', {}, _('Error calling "opkg list-installed": ' + err.message)));
  43. console.log(err);
  44. return '';
  45. }).then(data => {return data.trim().split('\n').sort().filter((string) => string.includes(': kmod-'));}),
  46. // data[2]
  47. ]);
  48. },
  49. render: function (data) {
  50. var m, d, s, o;
  51. const have_kmod_lp = data[2] ? data[2].find((string) => string.includes('kmod-lp')) ? true: false : false;
  52. const have_kmod_usb_printer = data[2] ? data[2].find((string) => string.includes('kmod-usb-printer')) ? true: false : false;
  53. m = new form.Map(pkg.Name, pkg.Description, _('map ports 9100-9109 to local printers.'));
  54. m.readonly = !L.hasViewPermission();
  55. if ( !have_kmod_usb_printer || !have_kmod_lp ) {
  56. s = m.section(form.NamedSection, 'kmods', 'kmods', _(),
  57. _('One of these kernel modules is needed for p910nd to find your printer.') + '<br />' +
  58. _('It is safe to install both, even if only one is needed.')
  59. );
  60. s.render = L.bind(function(view /*, ... */) {
  61. return form.NamedSection.prototype.render.apply(this, this.varargs(arguments, 1))
  62. .then(L.bind(function(node) {
  63. node.appendChild(E('div', { 'class': 'control-group' }, [
  64. E('button', {
  65. 'class': 'btn cbi-button-action',
  66. 'click': ui.createHandlerFn(view, 'option_install_kmod_lp', this.map),
  67. 'disabled': have_kmod_lp || null,
  68. 'title': _('Parallel port line printer device support'),
  69. }, [ 'kmod-lp' ]),
  70. ' ',
  71. E('button', {
  72. 'class': 'btn cbi-button-action',
  73. 'click': ui.createHandlerFn(view, 'option_install_kmod_usb', this.map),
  74. 'disabled': have_kmod_usb_printer || null,
  75. 'title': _('For USB connected printers'),
  76. }, [ 'kmod-usb-printer' ])
  77. ]));
  78. node.appendChild(E('br'));
  79. node.appendChild(E('br'));
  80. return node;
  81. }, this));
  82. }, s, this);
  83. }
  84. s = m.section(form.GridSection, pkg.Name);
  85. s.addremove = true;
  86. s.nodescriptions = true;
  87. s.anonymous = true;
  88. s.sortable = true;
  89. s.rowcolors = true;
  90. s.addbtntitle = _('Add printer config');
  91. s.modaltitle = _('Settings');
  92. o = s.option(form.Flag, 'enabled', _('Enabled'));
  93. o.rmempty = false;
  94. o = s.option(form.Value, 'device', _('Device'),
  95. _('Note: character device assignment can change upon reboot/reconnect with multiple USB devices.') + '<br />' +
  96. _('Connected %s devices show in this list.').format('<code>lpX</code>'));
  97. o.placeholder = '/dev/usb/lp*';
  98. if (data[1]) {
  99. for (const chardev of data[1]) {
  100. o.value(chardev);
  101. }
  102. }
  103. o.rmempty = true;
  104. o = s.option(form.ListValue, 'port', _('Port'),
  105. _('Local TCP listen port for this printer.') + '<br />' +
  106. _('Be aware: %s also listens on port 9100.').format('<code>node_exporter</code>'));
  107. for (var i = 0; i < 10; i++) {
  108. o.value(i, 9100+i);
  109. }
  110. o.rmempty = true;
  111. o = s.option(form.Value, 'bind', _('Listen IP'),
  112. _('Listen on a specific IP.'));
  113. o.datatype = 'ipaddr("nomask")';
  114. o.placeholder = _('any');
  115. var net_devices = data[0];
  116. net_devices.forEach(net_dev => {
  117. net_dev.getIPAddrs().forEach(addr => o.value(addr.split('/')[0], E([], [addr.split('/')[0], ' (', E('strong', {}, net_dev.getName()), ')'])));
  118. net_dev.getIP6Addrs().forEach(addr => o.value(addr.split('/')[0], E([], [addr.split('/')[0], ' (', E('strong', {}, net_dev.getName()), ')'])));
  119. });
  120. o = s.option(form.Flag, 'bidirectional',
  121. _('Bidirectional mode'),
  122. _('Whether this print port is bi-directional.') + '<br />' +
  123. _('Note: USB hotplug correctly detects this.'));
  124. o.modalonly = true;
  125. o = s.option(form.Flag, 'runas_root', _('Run as root'),
  126. _('Overrides default of %s.').format('<code>user p910nd, group lp</code>'));
  127. o.modalonly = true;
  128. o = s.option(form.Flag, 'mdns', 'mDNS',
  129. _('Whether to advertise this printer via %s.', 'mDNS/Bonjour/ZeroConf').format('mDNS/Bonjour/ZeroConf') + '<br />' +
  130. _('Note: %s only advertises one (the first) printer on this host.', 'mDNS').format('mDNS') + '<br />' +
  131. _('Note: USB hotplug attempts to provide some of the values below.'));
  132. /* See https://developer.apple.com/bonjour/printing-specification/ ( -> bonjourprinting-1.2.1.pdf ) for
  133. * a description of the Bonjour fields a la Apple.
  134. */
  135. o = s.option(form.Value, 'mdns_ty', 'mdns_ty',
  136. _('The %s type element.').format('mDNS') + '<br />' +
  137. _('User readable description of maker and model.'));
  138. o.placeholder = 'HP Color LaserJet CP2025dn';
  139. o.rmempty = true;
  140. o.optional = true;
  141. o.depends({mdns: '1'});
  142. o = s.option(form.Value, 'mdns_note', 'mdns_note',
  143. _('Serves as Location in Apple standards.'));
  144. o.placeholder = _('By the router');
  145. o.rmempty = true;
  146. o.optional = true;
  147. o.modalonly = true;
  148. o.depends({mdns: '1'});
  149. o = s.option(form.Value, 'mdns_product', 'mdns_product',
  150. _('The %s value.').format('<code>IEEE1284 Product</code>') + '<br />' +
  151. _('Note: must be %s.').format('<code>(</code>' + _('enclosed within parentheses') + '<code>)</code>'));
  152. o.placeholder = '(Color LaserJet CP2025dn)';
  153. o.rmempty = true;
  154. o.optional = true;
  155. o.modalonly = true;
  156. o.depends({mdns: '1'});
  157. o.write = function(section_id, value) {
  158. if (value){
  159. if (!value.startsWith('('))
  160. value = '(' + value;
  161. if (!value.endsWith(')'))
  162. value = value + ')';
  163. if (value === '()')
  164. return;
  165. uci.set(pkg.Name, section_id, 'mdns_product', value);
  166. }
  167. }
  168. const string_array_convert = function(string_or_arr, uppercase=false) {
  169. if (string_or_arr && !Array.isArray(string_or_arr)) {
  170. return [...new Set(string_or_arr.split(',')
  171. .map(element => uppercase? element.toUpperCase().trim(): element.trim() )
  172. .filter(el => el!='')
  173. .sort() ) ];
  174. }
  175. if (string_or_arr && Array.isArray(string_or_arr)) {
  176. return string_or_arr
  177. .map(element => uppercase? element.toUpperCase().trim(): element.trim() )
  178. .filter(el => el!='')
  179. .sort()
  180. .join(',');
  181. }
  182. };
  183. o = s.option(form.Value, 'mdns_mfg', 'mdns_mfg',
  184. _('The %s value.').format('<code>IEEE1284 MANUFACTURER/MFG</code>'));
  185. o.placeholder = _('Manufacturer');
  186. const mfrs = string_array_convert('Apple,Brother,Canon,Deli,Epson,\
  187. Hewlett-Packard,HP,Hitachi,Kyocera,Lexmark,OKI,Ricoh,Samsung,Xerox,\
  188. Xprinter,Zebra');
  189. for(var mfr in mfrs){
  190. o.value(mfrs[mfr]);
  191. }
  192. o.rmempty = true;
  193. o.optional = true;
  194. o.modalonly = true;
  195. o.depends({mdns: '1'});
  196. o = s.option(form.Value, 'mdns_mdl', 'mdns_mdl',
  197. _('The %s value.').format('<code>IEEE1284 MODEL/MDL</code>'));
  198. o.placeholder = 'CP2025dn';
  199. o.rmempty = true;
  200. o.optional = true;
  201. o.modalonly = true;
  202. o.depends({mdns: '1'});
  203. o = s.option(form.DynamicList, 'mdns_cmd', 'mdns_cmd',
  204. _('The %s value.').format('<code>IEEE1284 CMD/COMMAND SET</code>') + '<br />' +
  205. _('Some examples: ') +
  206. '<code>ACL</code>, <code>ESC/P</code>, <code>PCL</code>, <code>PDF17</code>,' +
  207. '<code>PJL</code>, <code>POSTSCRIPT</code>, <code>PS</code>' +
  208. '<br />'+
  209. _('Note: Set only CMD languages that your printer understands.'));
  210. o.cfgvalue = function(section_id) {
  211. return string_array_convert(uci.get(pkg.Name, section_id, 'mdns_cmd'));
  212. }
  213. o.rmempty = true;
  214. o.optional = true;
  215. o.modalonly = true;
  216. o.modalonly = true;
  217. o.depends({mdns: '1'});
  218. o.write = function(section_id, value) {
  219. uci.set(pkg.Name, section_id, 'mdns_cmd', string_array_convert(value));
  220. }
  221. const cmds = string_array_convert('ACL,BDC,BIDI-ECP,BSCCe,CAPT,CEZD,CPCA,CPCL,\
  222. D4,D4PX,DESKJET,DOWNLOAD,DPL,DW-PCL,DYN,\
  223. END4,EPL,EPSONFX,ESC/P,ESCPL2,EXT,FWV,GDI,HAPS,\
  224. HBP,HBPL,HIPERMIP,IBMPPR,IPL,LIPSLX,MLC,\
  225. PCL,PCL3,PCL3GUI,PCL5c,PCL5e,PCL6,PCLXL,\
  226. PDF,PDF17,\
  227. PJL,PML,\
  228. POSTSCRIPT,PostScript Emulation,PS,\
  229. PWG_RASTER,RASTER,RCS,URF,URP,XL,XPP,XPS,ZPL');
  230. for(var cmd of cmds){
  231. o.value(cmd);
  232. }
  233. return Promise.all([m.render()]);
  234. }
  235. });