feeds.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. 'use strict';
  2. 'require view';
  3. 'require form';
  4. 'require fs';
  5. 'require ui';
  6. /*
  7. include custom CSS
  8. */
  9. document.querySelector('head').appendChild(E('link', {
  10. 'rel': 'stylesheet',
  11. 'type': 'text/css',
  12. 'href': L.resource('view/banip/custom.css')
  13. }));
  14. /*
  15. observe DOM changes
  16. */
  17. const observer = new MutationObserver(function (mutations) {
  18. if (mutations) {
  19. const inputs = document.querySelectorAll('input');
  20. inputs.forEach(function (input) {
  21. input.setAttribute('autocomplete', 'off')
  22. input.setAttribute('autocorrect', 'off')
  23. input.setAttribute('autocapitalize', 'off')
  24. input.setAttribute('spellcheck', false)
  25. })
  26. const labels = document.querySelectorAll('label[for^="widget.cbid.json"][for$="name"]');
  27. labels.forEach(function (label) {
  28. label.setAttribute("style", "font-weight: bold !important; color: #595 !important;");
  29. })
  30. L.resolveDefault(fs.stat('/etc/banip/banip.custom.feeds'), '').then(function (stat) {
  31. const buttons = document.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload');
  32. if (buttons[1] && buttons[2] && stat.size === 0) {
  33. buttons[1].removeAttribute('disabled');
  34. buttons[2].removeAttribute('disabled');
  35. } else if (buttons[0] && buttons[3] && buttons[4] && stat.size > 0) {
  36. buttons[0].removeAttribute('disabled');
  37. buttons[3].removeAttribute('disabled');
  38. buttons[4].removeAttribute('disabled');
  39. }
  40. });
  41. }
  42. });
  43. const targetNode = document.getElementById('view');
  44. const observerConfig = {
  45. childList: true,
  46. subtree: true,
  47. attributes: false,
  48. characterData: false
  49. };
  50. observer.observe(targetNode, observerConfig);
  51. /*
  52. button handling
  53. */
  54. function handleEdit(ev) {
  55. if (ev === 'upload') {
  56. return ui.uploadFile('/etc/banip/banip.custom.feeds').then(function () {
  57. L.resolveDefault(fs.read_direct('/etc/banip/banip.custom.feeds', 'json'), "").then(function (data) {
  58. if (data) {
  59. let dataLength = Object.keys(data).length || 0;
  60. if (dataLength > 0) {
  61. for (let i = 0; i < dataLength; i++) {
  62. let feed = Object.keys(data)[i];
  63. let descr = data[feed].descr;
  64. if (feed && descr) {
  65. continue;
  66. }
  67. fs.write('/etc/banip/banip.custom.feeds', null).then(function () {
  68. ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
  69. });
  70. return;
  71. }
  72. } else {
  73. fs.write('/etc/banip/banip.custom.feeds', null).then(function () {
  74. ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
  75. });
  76. return;
  77. }
  78. location.reload();
  79. } else {
  80. fs.write('/etc/banip/banip.custom.feeds', null).then(function () {
  81. ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
  82. });
  83. }
  84. });
  85. }).catch(function () { });
  86. }
  87. if (ev === 'download') {
  88. return fs.read_direct('/etc/banip/banip.custom.feeds', 'blob').then(function (blob) {
  89. let url = window.URL.createObjectURL(blob),
  90. date = new Date(),
  91. name = 'banip.custom.feeds_%04d-%02d-%02d.json'.format(date.getFullYear(), date.getMonth() + 1, date.getDate()),
  92. link = E('a', { 'style': 'display:none', 'href': url, 'download': name });
  93. document.body.appendChild(link);
  94. link.click();
  95. document.body.removeChild(link);
  96. window.URL.revokeObjectURL(url);
  97. }).catch(function () { });
  98. }
  99. if (ev === 'create') {
  100. return fs.read_direct('/etc/banip/banip.feeds', 'json').then(function (content) {
  101. fs.write('/etc/banip/banip.custom.feeds', JSON.stringify(content)).then(function () {
  102. location.reload();
  103. });
  104. });
  105. }
  106. if (ev === 'clear') {
  107. return fs.write('/etc/banip/banip.custom.feeds', null).then(function () {
  108. location.reload();
  109. });
  110. }
  111. if (ev === 'save') {
  112. const invalid = document.querySelectorAll('.cbi-input-invalid');
  113. if (invalid.length > 0) {
  114. document.body.scrollTop = document.documentElement.scrollTop = 0;
  115. return ui.addNotification(null, E('p', _('Invalid input values, unable to save modifications.')), 'error');
  116. }
  117. }
  118. let sumSubElements = [], exportJson;
  119. const nodeKeys = document.querySelectorAll('[id^="widget.cbid.json"][id$="name"]');
  120. for (let i = 0; i < nodeKeys.length; i++) {
  121. let subElements = {};
  122. let elements = document.querySelectorAll('[id^="widget.cbid.json.' + nodeKeys[i].id.split('.')[3] + '\."]');
  123. for (const element of elements) {
  124. let key = element.id.split('.')[4];
  125. let value = element.value || "";
  126. if (value === "") {
  127. continue;
  128. }
  129. switch (key) {
  130. case 'url_4':
  131. subElements.url_4 = value;
  132. break;
  133. case 'rule_4':
  134. subElements.rule_4 = value;
  135. break;
  136. case 'url_6':
  137. subElements.url_6 = value;
  138. break;
  139. case 'rule_6':
  140. subElements.rule_6 = value;
  141. break;
  142. case 'descr':
  143. subElements.descr = value;
  144. break;
  145. case 'flag':
  146. subElements.flag = value;
  147. break;
  148. }
  149. }
  150. if (nodeKeys[i].value !== "" && subElements.descr !== "") {
  151. sumSubElements.push(nodeKeys[i].value, subElements);
  152. }
  153. }
  154. if (sumSubElements.length > 0) {
  155. exportJson = JSON.stringify(sumSubElements).replace(/^\[/, '{\n').replace(/\}]$/, '\n\t}\n}\n').replace(/,{"/g, ':{\n\t"').replace(/"},"/g, '"\n\t},\n"').replace(/","/g, '",\n\t"');
  156. }
  157. return fs.write('/etc/banip/banip.custom.feeds', exportJson).then(function () {
  158. location.reload();
  159. });
  160. }
  161. return view.extend({
  162. load: function () {
  163. return L.resolveDefault(fs.read_direct('/etc/banip/banip.custom.feeds', 'json'), "");
  164. },
  165. render: function (data) {
  166. let m, s, o, feed, url_4, url_6, rule_4, rule_6, descr, flag;
  167. m = new form.JSONMap(data, _('Custom Feed Editor'), _('With this editor you can upload your local custom feed file or fill up an initial one (a 1:1 copy of the version shipped with the package). \
  168. The file is located at \'/etc/banip/banip.custom.feeds\'. \
  169. Then you can edit this file, delete entries, add new ones or make a local backup. To go back to the maintainers version just clear the custom feed file.'));
  170. for (let i = 0; i < Object.keys(m.data.data).length; i++) {
  171. feed = Object.keys(m.data.data)[i];
  172. url_4 = m.data.data[feed].url_4;
  173. rule_4 = m.data.data[feed].rule_4;
  174. url_6 = m.data.data[feed].url_6;
  175. rule_6 = m.data.data[feed].rule_6;
  176. descr = m.data.data[feed].descr;
  177. flag = m.data.data[feed].flag;
  178. s = m.section(form.TypedSection, feed, null);
  179. s.addremove = true;
  180. s.anonymous = true;
  181. o = s.option(form.Value, 'name', _('Feed Name'));
  182. o.ucioption = '.name';
  183. o.datatype = 'and(minlength(3),maxlength(15))';
  184. o.validate = function (section_id, value) {
  185. if (!value) {
  186. return _('Empty field not allowed');
  187. }
  188. if (!value.match(/^[a-z0-9]+$/)) {
  189. return _('Invalid characters');
  190. }
  191. return true;
  192. }
  193. o = s.option(form.Value, 'url_4', _('URLv4'));
  194. o.validate = function (section_id, value) {
  195. if (!value) {
  196. return true;
  197. }
  198. if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-\?\&\+_@%=:~#]+$/)) {
  199. return _('Protocol/URL format not supported');
  200. }
  201. return true;
  202. }
  203. o = s.option(form.Value, 'rule_4', _('Rulev4'));
  204. o = s.option(form.Value, 'url_6', _('URLv6'));
  205. o.validate = function (section_id, value) {
  206. if (!value) {
  207. return true;
  208. }
  209. if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-\?\&\+_@%=:~#]+$/)) {
  210. return _('Protocol/URL format not supported');
  211. }
  212. return true;
  213. }
  214. o = s.option(form.Value, 'rule_6', _('Rulev6'));
  215. o = s.option(form.Value, 'descr', _('Description'));
  216. o.datatype = 'and(minlength(3),maxlength(30))';
  217. o.validate = function (section_id, value) {
  218. if (!value) {
  219. return _('Empty field not allowed');
  220. }
  221. return true;
  222. }
  223. o = s.option(form.Value, 'flag', _('Flag'));
  224. o.validate = function (section_id, value) {
  225. if (!value) {
  226. return true;
  227. }
  228. if (!value.match(/^(\bgz\b|\btcp\b|\budp\b|\b[0-9\-]+\b| )*$/)) {
  229. return _('Flag not supported');
  230. }
  231. return true;
  232. }
  233. }
  234. s = m.section(form.NamedSection, 'global');
  235. s.render = L.bind(function () {
  236. return E('div', { class: 'right' }, [
  237. E('button', {
  238. 'class': 'btn cbi-button cbi-button-action',
  239. 'id': 'btnDownload',
  240. 'disabled': 'disabled',
  241. 'click': ui.createHandlerFn(this, function () {
  242. return handleEdit('download');
  243. })
  244. }, [_('Download Custom Feeds')]),
  245. '\xa0',
  246. E('button', {
  247. 'class': 'btn cbi-button cbi-button-action',
  248. 'id': 'btnUpload',
  249. 'disabled': 'disabled',
  250. 'click': ui.createHandlerFn(this, function () {
  251. return handleEdit('upload');
  252. })
  253. }, [_('Upload Custom Feeds')]),
  254. '\xa0',
  255. E('button', {
  256. 'class': 'btn cbi-button cbi-button-action important',
  257. 'id': 'btnCreate',
  258. 'disabled': 'disabled',
  259. 'click': ui.createHandlerFn(this, function () {
  260. return handleEdit('create');
  261. })
  262. }, [_('Fill Custom Feeds')]),
  263. '\xa0',
  264. E('button', {
  265. 'class': 'btn cbi-button cbi-button-negative important',
  266. 'id': 'btnClear',
  267. 'disabled': 'disabled',
  268. 'click': ui.createHandlerFn(this, function () {
  269. return handleEdit('clear');
  270. })
  271. }, [_('Clear Custom Feeds')]),
  272. '\xa0',
  273. E('button', {
  274. 'class': 'btn cbi-button cbi-button-positive important',
  275. 'id': 'btnSave',
  276. 'disabled': 'disabled',
  277. 'click': ui.createHandlerFn(this, function () {
  278. return handleEdit('save');
  279. })
  280. }, [_('Save Custom Feeds')]),
  281. '\xa0'
  282. ])
  283. });
  284. return m.render();
  285. },
  286. handleSaveApply: null,
  287. handleSave: null,
  288. handleReset: null
  289. });