IoTTalk Delete Device

Delete multiple devices in IoTTalk using regular expressions

  1. // ==UserScript==
  2. // @name IoTTalk Delete Device
  3. // @namespace Fractalism
  4. // @version 1.1.2
  5. // @description Delete multiple devices in IoTTalk using regular expressions
  6. // @author Fractalism
  7. // @match http*://*.iottalk.tw/list_all
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. // make find-and-delete UI
  15. (function CreateUI() {
  16. var MainUI = document.createElement('div');
  17. document.body.appendChild(MainUI);
  18.  
  19. MainUI.id = 'main-UI';
  20. MainUI.innerHTML = `
  21. <form action='javascript:void(0)'>
  22. <span>Find and delete</span><br>
  23. <input type='text' id='d_name-input'><br>
  24. <div id='search-div'>
  25. <button id='search-btn'>Search</button>
  26. <button id='hint-message' disabled><b>?</b></button>
  27. </div>
  28. <div id='delete-div' style='display:none'>
  29. <button id='delete-btn'>Delete</button>
  30. <button id='cancel-btn'>Cancel</button>
  31. </div>
  32. <div id='param-div'>
  33. <label><input type='checkbox' id='case_sensitive'><small>Case-sensitive search</small></label><br>
  34. <label><input type='checkbox' id='no_project' checked><small>Match unused devices only</small></label>
  35. </div>
  36. </form>
  37. `;
  38.  
  39. MainUI.style.position = 'fixed';
  40. MainUI.style.top = '20px'; // pixels
  41. MainUI.style.right = '20px'; // pixels
  42. MainUI.style.backgroundColor = 'white';
  43. MainUI.style.border = '1px black solid';
  44. MainUI.style.padding = '5px 15px 5px 15px';
  45. MainUI.style.borderRadius = '10px';
  46.  
  47. var d_name_Input = document.getElementById('d_name-input');
  48. d_name_Input.onkeydown = function () {
  49. if (!(event.which == 13 || event.keyCode == 13)) {
  50. switch_display(1);
  51. }
  52. }
  53.  
  54. var SearchDiv = document.getElementById('search-div');
  55. SearchDiv.style.position = 'relative'; // to position HintButton correctly
  56. SearchDiv.style.width = d_name_Input.clientWidth;
  57.  
  58. var DeleteDiv = document.getElementById('delete-div');
  59. DeleteDiv.style.position = 'relative';
  60.  
  61. var ParamsDiv = document.getElementById('param-div');
  62.  
  63. var SearchButton = document.getElementById('search-btn');
  64. SearchButton.onclick = function () {
  65. var params = {
  66. case_sensitive: document.getElementById('case_sensitive').checked,
  67. no_project: document.getElementById('no_project').checked,
  68. }
  69. find_devices(params);
  70. switch_display(2);
  71. d_name_Input.focus();
  72. };
  73.  
  74. var DeleteButton = document.getElementById('delete-btn');
  75. DeleteButton.onclick = function () {
  76. var confirm = delete_devices();
  77. if (confirm) {
  78. switch_display(1);
  79. }
  80. };
  81.  
  82. var CancelButton = document.getElementById('cancel-btn');
  83. CancelButton.onclick = function () {
  84. window.device_matches = [];
  85. switch_display(1);
  86. }
  87.  
  88. var HintButton = document.getElementById('hint-message');
  89. HintButton.style.position = 'absolute';
  90. //HintButton.style.bottom = '10px';
  91. HintButton.style.right = '0px';
  92. HintButton.style.width = '1.5em';
  93. HintButton.style.height = '1.5em';
  94. HintButton.style.border = '1.5px black solid';
  95. HintButton.style.borderRadius = '50%';
  96. HintButton.style.color = 'black';
  97. HintButton.style.backgroundColor = 'white';
  98. HintButton.style.padding = '0px';
  99. HintButton.title = `Hint:\n> Type part of the d_name or use a regex, check the console for results.\n> Search empty string to get all devices on the server.\n> Right click on an <a> element in a match and select "Scroll into view" to see the device on the webpage.`;
  100.  
  101. var my_style = document.createElement('style');
  102. my_style.innerHTML = `
  103. div#main-UI * {text-align: center; margin: 5px 0px 5px 0px;}
  104. div#main-UI * {font-family: Arial, Helvetica, sans-serif;}
  105. div#main-UI div {margin: 0px auto 0px auto; padding: 0px 0px 0px 0px;}
  106. div#main-UI input[type=checkbox] {margin: 5px 5px 5px 5px;}
  107. div#main-UI #param-div {text-align: left;}
  108. `; // apply to everything inside div
  109.  
  110. document.head.appendChild(my_style);
  111.  
  112.  
  113. // switch to display certain buttons
  114. // mode: 1=search mode, 2=delete mode
  115. function switch_display(mode) {
  116. switch (mode) {
  117. case 1:
  118. SearchDiv.style.display = 'block';
  119. DeleteDiv.style.display = 'none';
  120. ParamsDiv.style.display = 'block';
  121. break;
  122. case 2:
  123. SearchDiv.style.display = 'none';
  124. DeleteDiv.style.display = 'block';
  125. ParamsDiv.style.display = 'none';
  126. break;
  127. default:
  128. }
  129. }
  130. })();
  131.  
  132.  
  133.  
  134. // modify send_delete function for one-by-one deletions without refreshing page
  135. window.send_delete = send_delete_single;
  136.  
  137. function send_delete_single(url) {
  138. $.ajax({
  139. url: url,
  140. type: 'DELETE',
  141. success: (result) => alert(`Device with MAC address ${url.substr(1)} deleted successfully.`),
  142. error: (jqXHR, textStatus, errorThrown) => alert(`Error deleting ${url.substr(1)}\ntextStatus: ${textStatus}\nerrorThrown: ${errorThrown}`)
  143. });
  144. event.preventDefault(); // don't scroll to top after deleting
  145. }
  146.  
  147. // for batch deletions (send output to console instead)
  148. function send_delete_batch(url) {
  149. $.ajax({
  150. url: url,
  151. type: 'DELETE',
  152. success: (result) => console.log(`Device with MAC address ${url.substr(1)} deleted successfully.`),
  153. error: (jqXHR, textStatus, errorThrown) => console.log(`Error deleting ${url.substr(1)}\ntextStatus: ${textStatus}\nerrorThrown: ${errorThrown}`)
  154. });
  155. event.preventDefault();
  156. }
  157.  
  158.  
  159.  
  160. // find devices with d_name matching input regex
  161. function find_devices(params) {
  162. console.clear();
  163. var d_name = document.getElementById('d_name-input').value;
  164. console.log('Search d_name: ' + d_name);
  165. var index, node, matches = [],
  166. skipped = [];
  167. for ([index, node] of window.relevantNodes.entries()) {
  168. if (node.nodeType == node.TEXT_NODE && node.nodeValue.search(RegExp(`d_name: .*${d_name}`, params.case_sensitive ? '' : 'i')) != -1) {
  169. if (params.no_project && node.nodeValue.trim().search(/project:$/) != -1 && (node.nextSibling.getAttribute('onclick') && node.nextSibling.getAttribute('onclick').indexOf('open_project(') != -1)) {
  170. skipped.push([node, window.relevantNodes[index - 1]]);
  171. } else {
  172. matches.push([node, window.relevantNodes[index - 1]]); // text node and link to delete device
  173. }
  174. }
  175. }
  176. console.log(`Found ${matches.length+skipped.length} devices (${matches.length} matched, ${skipped.length} skipped)`);
  177. console.groupCollapsed(`Matched devices (${matches.length})`);
  178. for (let i = 0; i < matches.length; ++i) {
  179. let d_name, dm_name;
  180. let lines = matches[i][0].nodeValue.split('\n');
  181. for (let line of lines) {
  182. if (line.search('d_name:') != -1) d_name = line.trim();
  183. if (line.search('dm_name:') != -1) dm_name = line.trim();
  184. }
  185. console.log(`Match ${i+1}:\n ${d_name}\n ${dm_name}\n`, matches[i][1]);
  186. };
  187. console.groupEnd();
  188. console.groupCollapsed(`Skipped devices (${skipped.length})`);
  189. for (let i = 0; i < skipped.length; ++i) {
  190. let d_name, dm_name;
  191. let lines = skipped[i][0].nodeValue.split('\n');
  192. for (let line of lines) {
  193. if (line.search('d_name:') != -1) d_name = line.trim();
  194. if (line.search('dm_name:') != -1) dm_name = line.trim();
  195. }
  196. console.log(`Skip ${i+1}:\n ${d_name}\n ${dm_name}\n`, skipped[i][1]);
  197. }
  198. console.groupEnd();
  199. window.device_matches = matches;
  200. }
  201.  
  202. function delete_devices() {
  203. var matches = window.device_matches;
  204. if (!confirm(`Delete all ${matches.length} matched devices?`)) return false;
  205. window.send_delete = send_delete_batch;
  206. var index, match;
  207. for ([index, match] of matches.entries()) {
  208. console.log(`Deleting match ${index+1}`);
  209. match[1].onclick();
  210. }
  211. window.send_delete = send_delete_single;
  212. return true;
  213. }
  214.  
  215. // get all relevant nodes only once at start
  216. (function collect_nodes() {
  217. var relevantNodes = [];
  218. var walker = document.createTreeWalker(document.body.firstElementChild, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, function my_filter(node) {
  219. if (node.nodeType == node.TEXT_NODE && node.nodeValue.indexOf('d_name:') != -1 || node.nodeType == node.ELEMENT_NODE && node.nodeName == "A" && node.innerText == "Delete") {
  220. return NodeFilter.FILTER_ACCEPT;
  221. } else {
  222. return NodeFilter.FILTER_SKIP;
  223. }
  224. });
  225. let node;
  226. while (node = walker.nextNode()) {
  227. if (node.nodeName == "HR") break; // scan device part, skip DF-module part
  228. relevantNodes.push(node);
  229. }
  230. window.relevantNodes = relevantNodes;
  231. })();
  232. })();