Delete multiple devices in IoTTalk using regular expressions
目前為
// ==UserScript==
// @name IoTTalk Delete Device
// @namespace Fractalism
// @version 1.1.2
// @description Delete multiple devices in IoTTalk using regular expressions
// @author Fractalism
// @match http*://*.iottalk.tw/list_all
// @grant none
// ==/UserScript==
(function () {
'use strict';
// make find-and-delete UI
(function CreateUI() {
var MainUI = document.createElement('div');
document.body.appendChild(MainUI);
MainUI.id = 'main-UI';
MainUI.innerHTML = `
<form action='javascript:void(0)'>
<span>Find and delete</span><br>
<input type='text' id='d_name-input'><br>
<div id='search-div'>
<button id='search-btn'>Search</button>
<button id='hint-message' disabled><b>?</b></button>
</div>
<div id='delete-div' style='display:none'>
<button id='delete-btn'>Delete</button>
<button id='cancel-btn'>Cancel</button>
</div>
<div id='param-div'>
<label><input type='checkbox' id='case_sensitive'><small>Case-sensitive search</small></label><br>
<label><input type='checkbox' id='no_project' checked><small>Match unused devices only</small></label>
</div>
</form>
`;
MainUI.style.position = 'fixed';
MainUI.style.top = '20px'; // pixels
MainUI.style.right = '20px'; // pixels
MainUI.style.backgroundColor = 'white';
MainUI.style.border = '1px black solid';
MainUI.style.padding = '5px 15px 5px 15px';
MainUI.style.borderRadius = '10px';
var d_name_Input = document.getElementById('d_name-input');
d_name_Input.onkeydown = function () {
if (!(event.which == 13 || event.keyCode == 13)) {
switch_display(1);
}
}
var SearchDiv = document.getElementById('search-div');
SearchDiv.style.position = 'relative'; // to position HintButton correctly
SearchDiv.style.width = d_name_Input.clientWidth;
var DeleteDiv = document.getElementById('delete-div');
DeleteDiv.style.position = 'relative';
var ParamsDiv = document.getElementById('param-div');
var SearchButton = document.getElementById('search-btn');
SearchButton.onclick = function () {
var params = {
case_sensitive: document.getElementById('case_sensitive').checked,
no_project: document.getElementById('no_project').checked,
}
find_devices(params);
switch_display(2);
d_name_Input.focus();
};
var DeleteButton = document.getElementById('delete-btn');
DeleteButton.onclick = function () {
var confirm = delete_devices();
if (confirm) {
switch_display(1);
}
};
var CancelButton = document.getElementById('cancel-btn');
CancelButton.onclick = function () {
window.device_matches = [];
switch_display(1);
}
var HintButton = document.getElementById('hint-message');
HintButton.style.position = 'absolute';
//HintButton.style.bottom = '10px';
HintButton.style.right = '0px';
HintButton.style.width = '1.5em';
HintButton.style.height = '1.5em';
HintButton.style.border = '1.5px black solid';
HintButton.style.borderRadius = '50%';
HintButton.style.color = 'black';
HintButton.style.backgroundColor = 'white';
HintButton.style.padding = '0px';
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.`;
var my_style = document.createElement('style');
my_style.innerHTML = `
div#main-UI * {text-align: center; margin: 5px 0px 5px 0px;}
div#main-UI * {font-family: Arial, Helvetica, sans-serif;}
div#main-UI div {margin: 0px auto 0px auto; padding: 0px 0px 0px 0px;}
div#main-UI input[type=checkbox] {margin: 5px 5px 5px 5px;}
div#main-UI #param-div {text-align: left;}
`; // apply to everything inside div
document.head.appendChild(my_style);
// switch to display certain buttons
// mode: 1=search mode, 2=delete mode
function switch_display(mode) {
switch (mode) {
case 1:
SearchDiv.style.display = 'block';
DeleteDiv.style.display = 'none';
ParamsDiv.style.display = 'block';
break;
case 2:
SearchDiv.style.display = 'none';
DeleteDiv.style.display = 'block';
ParamsDiv.style.display = 'none';
break;
default:
}
}
})();
// modify send_delete function for one-by-one deletions without refreshing page
window.send_delete = send_delete_single;
function send_delete_single(url) {
$.ajax({
url: url,
type: 'DELETE',
success: (result) => alert(`Device with MAC address ${url.substr(1)} deleted successfully.`),
error: (jqXHR, textStatus, errorThrown) => alert(`Error deleting ${url.substr(1)}\ntextStatus: ${textStatus}\nerrorThrown: ${errorThrown}`)
});
event.preventDefault(); // don't scroll to top after deleting
}
// for batch deletions (send output to console instead)
function send_delete_batch(url) {
$.ajax({
url: url,
type: 'DELETE',
success: (result) => console.log(`Device with MAC address ${url.substr(1)} deleted successfully.`),
error: (jqXHR, textStatus, errorThrown) => console.log(`Error deleting ${url.substr(1)}\ntextStatus: ${textStatus}\nerrorThrown: ${errorThrown}`)
});
event.preventDefault();
}
// find devices with d_name matching input regex
function find_devices(params) {
console.clear();
var d_name = document.getElementById('d_name-input').value;
console.log('Search d_name: ' + d_name);
var index, node, matches = [],
skipped = [];
for ([index, node] of window.relevantNodes.entries()) {
if (node.nodeType == node.TEXT_NODE && node.nodeValue.search(RegExp(`d_name: .*${d_name}`, params.case_sensitive ? '' : 'i')) != -1) {
if (params.no_project && node.nodeValue.trim().search(/project:$/) != -1 && (node.nextSibling.getAttribute('onclick') && node.nextSibling.getAttribute('onclick').indexOf('open_project(') != -1)) {
skipped.push([node, window.relevantNodes[index - 1]]);
} else {
matches.push([node, window.relevantNodes[index - 1]]); // text node and link to delete device
}
}
}
console.log(`Found ${matches.length+skipped.length} devices (${matches.length} matched, ${skipped.length} skipped)`);
console.groupCollapsed(`Matched devices (${matches.length})`);
for (let i = 0; i < matches.length; ++i) {
let d_name, dm_name;
let lines = matches[i][0].nodeValue.split('\n');
for (let line of lines) {
if (line.search('d_name:') != -1) d_name = line.trim();
if (line.search('dm_name:') != -1) dm_name = line.trim();
}
console.log(`Match ${i+1}:\n ${d_name}\n ${dm_name}\n`, matches[i][1]);
};
console.groupEnd();
console.groupCollapsed(`Skipped devices (${skipped.length})`);
for (let i = 0; i < skipped.length; ++i) {
let d_name, dm_name;
let lines = skipped[i][0].nodeValue.split('\n');
for (let line of lines) {
if (line.search('d_name:') != -1) d_name = line.trim();
if (line.search('dm_name:') != -1) dm_name = line.trim();
}
console.log(`Skip ${i+1}:\n ${d_name}\n ${dm_name}\n`, skipped[i][1]);
}
console.groupEnd();
window.device_matches = matches;
}
function delete_devices() {
var matches = window.device_matches;
if (!confirm(`Delete all ${matches.length} matched devices?`)) return false;
window.send_delete = send_delete_batch;
var index, match;
for ([index, match] of matches.entries()) {
console.log(`Deleting match ${index+1}`);
match[1].onclick();
}
window.send_delete = send_delete_single;
return true;
}
// get all relevant nodes only once at start
(function collect_nodes() {
var relevantNodes = [];
var walker = document.createTreeWalker(document.body.firstElementChild, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, function my_filter(node) {
if (node.nodeType == node.TEXT_NODE && node.nodeValue.indexOf('d_name:') != -1 || node.nodeType == node.ELEMENT_NODE && node.nodeName == "A" && node.innerText == "Delete") {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_SKIP;
}
});
let node;
while (node = walker.nextNode()) {
if (node.nodeName == "HR") break; // scan device part, skip DF-module part
relevantNodes.push(node);
}
window.relevantNodes = relevantNodes;
})();
})();