Release 0.9.0
This commit is contained in:
528
bot/modules/webserver/static/system.js
Normal file
528
bot/modules/webserver/static/system.js
Normal file
@@ -0,0 +1,528 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
|
||||
// based on https://stackoverflow.com/a/56279295/8967590
|
||||
Audio.prototype.play = (function(play) {
|
||||
return function () {
|
||||
let audio = this;
|
||||
let promise = play.apply(audio, arguments);
|
||||
if (promise !== undefined) {
|
||||
promise.catch(_ => {
|
||||
console.log("autoplay of audiofile failed :(");
|
||||
});
|
||||
}
|
||||
};
|
||||
}) (Audio.prototype.play);
|
||||
|
||||
let audio_files = [];
|
||||
|
||||
function load_audio_files() {
|
||||
audio_files["computer_work_beep"] = new Audio('/static/lcars/audio/computer_work_beep.mp3');
|
||||
audio_files["computer_error"] = new Audio('/static/lcars/audio/computer_error.mp3');
|
||||
audio_files["keyok1"] = new Audio('/static/lcars/audio/keyok1.mp3');
|
||||
audio_files["keyok1"].volume = 0.05;
|
||||
audio_files["input_ok_2_clean"] = new Audio('/static/lcars/audio/input_ok_2_clean.mp3');
|
||||
audio_files["processing"] = new Audio('/static/lcars/audio/processing.mp3');
|
||||
audio_files["processing"].volume = 0.25;
|
||||
audio_files["computerbeep_11"] = new Audio('/static/lcars/audio/computerbeep_11.mp3');
|
||||
audio_files["computerbeep_11"].volume = 0.5;
|
||||
audio_files["computerbeep_38"] = new Audio('/static/lcars/audio/computerbeep_38.mp3');
|
||||
audio_files["computerbeep_38"].volume = 0.1;
|
||||
audio_files["computerbeep_65"] = new Audio('/static/lcars/audio/computerbeep_65.mp3');
|
||||
audio_files["alarm01"] = new Audio('/static/lcars/audio/alarm01.mp3');
|
||||
audio_files["alarm03"] = new Audio('/static/lcars/audio/alarm03.mp3');
|
||||
audio_files["alert12"] = new Audio('/static/lcars/audio/alert12.mp3');
|
||||
}
|
||||
|
||||
function play_audio_file(identifier) {
|
||||
try {
|
||||
if (audio_files[identifier].readyState === 4) { // 4 = HAVE_ENOUGH_DATA
|
||||
if (!audio_files[identifier].ended) {
|
||||
audio_files[identifier].currentTime = 0;
|
||||
audio_files[identifier].play();
|
||||
} else {
|
||||
audio_files[identifier].play();
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.error("[AUDIO] Failed to play audio file:", identifier, err);
|
||||
}
|
||||
}
|
||||
|
||||
/* found on https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript/49041392#49041392
|
||||
* slightly modified
|
||||
*/
|
||||
let index; // cell index
|
||||
let toggleBool; // sorting asc, desc
|
||||
window.sorting = function sorting(th, tbody, index) {
|
||||
function compareCells(a, b) {
|
||||
let aVal = a.cells[index].innerText.replace(/,/g, '');
|
||||
let bVal = b.cells[index].innerText.replace(/,/g, '');
|
||||
|
||||
if (toggleBool) {
|
||||
let temp = aVal;
|
||||
aVal = bVal;
|
||||
bVal = temp;
|
||||
}
|
||||
|
||||
if (aVal.match(/^[0-9]+$/) && bVal.match(/^[0-9]+$/)) {
|
||||
return parseFloat(aVal) - parseFloat(bVal);
|
||||
} else {
|
||||
if (aVal < bVal) {
|
||||
return -1;
|
||||
} else if (aVal > bVal) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.index = index;
|
||||
toggleBool = !toggleBool;
|
||||
|
||||
let datas = [];
|
||||
for (let i = 0; i < tbody.rows.length; i++) {
|
||||
datas[i] = tbody.rows[i];
|
||||
}
|
||||
|
||||
// sort by cell[index]
|
||||
datas.sort(compareCells);
|
||||
for (let i = 0; i < tbody.rows.length; i++) {
|
||||
// rearrange table rows by sorted rows
|
||||
tbody.appendChild(datas[i]);
|
||||
}
|
||||
};
|
||||
|
||||
/* found on https://stackoverflow.com/a/21648508/8967590
|
||||
* slightly modified to only return the rgb value and getting rid of type-warnings
|
||||
*/
|
||||
function hexToRgb(hex){
|
||||
let char;
|
||||
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
|
||||
char = hex.substring(1).split('');
|
||||
if (char.length === 3) {
|
||||
char = [char[0], char[0], char[1], char[1], char[2], char[2]];
|
||||
}
|
||||
char = '0x' + char.join('');
|
||||
return [(char >> 16) & 255, (char >> 8) & 255, char & 255].join(', ');
|
||||
} else {
|
||||
alert(hex);
|
||||
throw new Error('Bad Hex');
|
||||
}
|
||||
}
|
||||
|
||||
let lcars_colors = [];
|
||||
function load_lcars_colors() {
|
||||
/* https://davidwalsh.name/css-variables-javascript */
|
||||
lcars_colors["lcars-pale-canary"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-pale-canary').trim()
|
||||
);
|
||||
lcars_colors["lcars-tanoi"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-tanoi').trim()
|
||||
);
|
||||
lcars_colors["lcars-golden-tanoi"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-golden-tanoi').trim()
|
||||
);
|
||||
lcars_colors["lcars-neon-carrot"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-neon-carrot').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-eggplant"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-eggplant').trim()
|
||||
);
|
||||
lcars_colors["lcars-lilac"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-lilac').trim()
|
||||
);
|
||||
lcars_colors["lcars-anakiwa"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-anakiwa').trim()
|
||||
);
|
||||
lcars_colors["lcars-mariner"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-mariner').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-bahama-blue"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-bahama-blue').trim()
|
||||
);
|
||||
lcars_colors["lcars-blue-bell"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-blue-bell').trim()
|
||||
);
|
||||
lcars_colors["lcars-melrose"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-melrose').trim()
|
||||
);
|
||||
lcars_colors["lcars-hopbush"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-hopbush').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-chestnut-rose"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-chestnut-rose').trim()
|
||||
);
|
||||
lcars_colors["lcars-orange-peel"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-orange-peel').trim()
|
||||
);
|
||||
lcars_colors["lcars-atomic-tangerine"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-atomic-tangerine').trim()
|
||||
);
|
||||
lcars_colors["lcars-danub"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-danub').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-indigo"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-indigo').trim()
|
||||
);
|
||||
lcars_colors["lcars-lavender-purple"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-lavender-purple').trim()
|
||||
);
|
||||
lcars_colors["lcars-cosmic"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-cosmic').trim()
|
||||
);
|
||||
lcars_colors["lcars-red-damask"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-red-damask').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-medium-carmine"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-medium-carmine').trim()
|
||||
);
|
||||
lcars_colors["lcars-bourbon"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-bourbon').trim()
|
||||
);
|
||||
lcars_colors["lcars-sandy-brown"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-sandy-brown').trim()
|
||||
);
|
||||
lcars_colors["lcars-periwinkle"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-periwinkle').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-dodger-pale"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-dodger-pale').trim()
|
||||
);
|
||||
lcars_colors["lcars-dodger-soft"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-dodger-soft').trim()
|
||||
);
|
||||
lcars_colors["lcars-near-blue"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-near-blue').trim()
|
||||
);
|
||||
lcars_colors["lcars-navy-blue"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-navy-blue').trim()
|
||||
);
|
||||
|
||||
lcars_colors["lcars-husk"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-husk').trim()
|
||||
);
|
||||
lcars_colors["lcars-rust"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-rust').trim()
|
||||
);
|
||||
lcars_colors["lcars-tamarillo"] = hexToRgb(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--lcars-tamarillo').trim()
|
||||
);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/38311629/8967590
|
||||
$.fn.setClass = function(classes) {
|
||||
this.attr('class', classes);
|
||||
return this;
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/a/46308265,
|
||||
// slightly modified for better readability
|
||||
$.fn.selectText = function(){
|
||||
let element = this[0], range, selection;
|
||||
if (document.body.createTextRange) {
|
||||
range = document.body.createTextRange();
|
||||
range.moveToElementText(element);
|
||||
range.select();
|
||||
document.execCommand('copy');
|
||||
} else if (window.getSelection) {
|
||||
selection = window.getSelection();
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.upsert = function(target_element_id, htmlString) {
|
||||
// upsert - find or create new element
|
||||
let $el = $(this).find(target_element_id);
|
||||
if ($el.length === 0) {
|
||||
// didn't exist, create and add to caller
|
||||
$el = $(htmlString);
|
||||
$(this).prepend($el);
|
||||
}
|
||||
return $el;
|
||||
};
|
||||
|
||||
let flash = function(elements, color=false) {
|
||||
let opacity = 40;
|
||||
if (color === false) {
|
||||
color = lcars_colors["lcars-tanoi"]; // has to be in this format since we use rgba
|
||||
}
|
||||
let interval = setInterval(function() {
|
||||
opacity -= 2.5;
|
||||
if (opacity <= 0) {
|
||||
clearInterval(interval);
|
||||
$(elements).removeAttr('style');
|
||||
} else {
|
||||
$(elements).css({
|
||||
"background-color": "rgba(" + color + ", " + (opacity / 50) + ")"
|
||||
});
|
||||
}
|
||||
}, 20)
|
||||
};
|
||||
|
||||
//connect to the socket server.
|
||||
window.socket = io.connect(
|
||||
'http://' + document.domain + ':' + location.port, {
|
||||
'sync disconnect on unload': true
|
||||
}
|
||||
);
|
||||
|
||||
window.socket.on('connected', function() {
|
||||
window.socket.emit('ding');
|
||||
});
|
||||
|
||||
let start_time = (new Date).getTime();
|
||||
const PING_TIMEOUT_THRESHOLD = 5000; // Only log if ping takes >5 seconds
|
||||
|
||||
window.setInterval(function() {
|
||||
start_time = (new Date).getTime();
|
||||
socket.emit('ding');
|
||||
play_audio_file("processing");
|
||||
// No log for normal ping - would be spam (every 10 seconds)
|
||||
}, 10000);
|
||||
|
||||
window.socket.on('dong', function() {
|
||||
let latency = (new Date).getTime() - start_time;
|
||||
play_audio_file("keyok1");
|
||||
|
||||
// Only log slow pings
|
||||
if (latency > PING_TIMEOUT_THRESHOLD) {
|
||||
console.warn("[PING] Slow response: " + latency + "ms (threshold: " + PING_TIMEOUT_THRESHOLD + "ms)");
|
||||
}
|
||||
});
|
||||
|
||||
// Session conflict handling
|
||||
window.socket.on('session_conflict', function(data) {
|
||||
console.log('[SESSION] Conflict detected:', data);
|
||||
play_audio_file("alert12");
|
||||
|
||||
let message = data.message + '\n\nAktive Sessions: ' + data.existing_sessions;
|
||||
if (confirm(message)) {
|
||||
// User wants to take over
|
||||
console.log('[SESSION] Taking over existing session');
|
||||
window.socket.emit('session_takeover_accept');
|
||||
} else {
|
||||
// User declined
|
||||
console.log('[SESSION] Takeover declined');
|
||||
window.socket.emit('session_takeover_decline');
|
||||
}
|
||||
});
|
||||
|
||||
window.socket.on('session_accepted', function() {
|
||||
console.log('[SESSION] Session accepted');
|
||||
play_audio_file("computerbeep_11");
|
||||
});
|
||||
|
||||
window.socket.on('session_declined', function(data) {
|
||||
console.log('[SESSION] Session declined:', data.message);
|
||||
play_audio_file("computer_error");
|
||||
alert(data.message);
|
||||
// Browser will be disconnected by server
|
||||
});
|
||||
|
||||
window.socket.on('session_taken_over', function(data) {
|
||||
console.log('[SESSION] Session taken over by another browser');
|
||||
play_audio_file("alarm01");
|
||||
alert(data.message);
|
||||
// Connection will be closed by server
|
||||
// Show a visual indicator that session is no longer active
|
||||
document.body.style.opacity = '0.5';
|
||||
document.body.style.pointerEvents = 'none';
|
||||
});
|
||||
|
||||
load_audio_files();
|
||||
load_lcars_colors();
|
||||
|
||||
window.socket.on('data', function(data) {
|
||||
try {
|
||||
// Log event for debugging (can be disabled in production)
|
||||
if (window.socketDebugMode) {
|
||||
console.log('[SOCKET] Received event:', data.data_type, data);
|
||||
}
|
||||
|
||||
if ([
|
||||
"element_content",
|
||||
"widget_content",
|
||||
"modal_content",
|
||||
"remove_table_row",
|
||||
"table_row",
|
||||
"table_row_content"
|
||||
].includes(data["data_type"])) {
|
||||
/* target element needs to be present for these operations */
|
||||
|
||||
// Validate data structure before accessing
|
||||
if (!data["target_element"]) {
|
||||
console.error('[SOCKET] Missing target_element in data:', data);
|
||||
return false;
|
||||
}
|
||||
|
||||
let target_element_id = data["target_element"]["id"];
|
||||
if (target_element_id == null) {
|
||||
console.warn('[SOCKET] target_element.id is null for data_type:', data["data_type"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data["data_type"] === "widget_content") {
|
||||
/* widget content requires a selector, in case the widget is not yet rendered in the browser
|
||||
* with the help of the selector, we can create it in the right place
|
||||
*/
|
||||
let html_string = '<div id="' + target_element_id + '" class="widget"></div>';
|
||||
let selector = data["target_element"]["selector"];
|
||||
let target_element = $(selector).upsert(
|
||||
'#' + target_element_id,
|
||||
html_string
|
||||
);
|
||||
|
||||
if (data["method"] === "update") {
|
||||
target_element.html(data["payload"]);
|
||||
} else if (data["method"] === "append") {
|
||||
target_element.append(data["payload"]);
|
||||
} else if (data["method"] === "prepend") {
|
||||
play_audio_file("computerbeep_38");
|
||||
let target_table = $('#' + target_element_id + ' ' + data["target_element"]["type"]);
|
||||
/* prepend adds a row on top */
|
||||
target_table.prepend(data["payload"]);
|
||||
let $entries = target_table.find('tr');
|
||||
if ($entries.length >= 50) {
|
||||
$entries.last().remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data["data_type"] === "element_content") {
|
||||
let target_element = document.getElementById(target_element_id);
|
||||
if (target_element == null) {
|
||||
return false;
|
||||
}
|
||||
if (data["method"] === "update") {
|
||||
if (target_element.innerHTML !== data["payload"]) {
|
||||
target_element.innerHTML = data["payload"];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (data["method"] === "replace") {
|
||||
// Note: After outerHTML replacement, target_element reference becomes invalid
|
||||
// Flash BEFORE replacing, or flash the parent element
|
||||
let parent = target_element.parentElement;
|
||||
target_element.outerHTML = data["payload"];
|
||||
if (parent) {
|
||||
// Flash the new element by finding it in the parent
|
||||
let new_element = document.getElementById(target_element_id);
|
||||
if (new_element) {
|
||||
flash(new_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data["data_type"] === "modal_content") {
|
||||
let target_element = document.getElementById(target_element_id);
|
||||
if (target_element == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let modal_container = target_element.parentElement;
|
||||
modal_container.classList.toggle("open");
|
||||
|
||||
$(target_element).html(data["payload"])
|
||||
}
|
||||
if (data["data_type"] === "table_row") {
|
||||
/* the whole row will be swapped out, not very economic ^^
|
||||
* can be suitable for smaller widgets, not needing the hassle of sub-element id's and stuff
|
||||
* table_row content requires a selector, in case the row is not yet rendered in the browser
|
||||
* with the help of the selector, we can create it in the right place
|
||||
*/
|
||||
play_audio_file("processing");
|
||||
let parent_element = $(data["target_element"]["selector"]);
|
||||
|
||||
let target_element = parent_element.find("#" + target_element_id);
|
||||
|
||||
if (target_element.length === 0) {
|
||||
/* If the row doesn't exist, append it */
|
||||
parent_element.append(data["payload"]);
|
||||
} else {
|
||||
target_element.replaceWith(data["payload"]);
|
||||
}
|
||||
}
|
||||
if (data["data_type"] === "table_row_content") {
|
||||
play_audio_file("keyok1");
|
||||
let parent_element = $('#' + target_element_id);
|
||||
if (parent_element.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (data["target_element"]["class"].length >= 1) {
|
||||
parent_element.setClass(data["target_element"]["class"]);
|
||||
} else {
|
||||
parent_element[0].removeAttribute("class");
|
||||
}
|
||||
|
||||
let elements_to_update = data["payload"];
|
||||
$.each(elements_to_update, function (key, value) {
|
||||
if ($.type(value) === 'object') {
|
||||
$.each(value, function (sub_key, sub_value) {
|
||||
let element_to_update = $('#' + target_element_id + '_' + key + '_' + sub_key);
|
||||
if (element_to_update.length !== 0 && element_to_update.text() !== sub_value.toString()) {
|
||||
element_to_update.html(sub_value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let element_to_update = $('#' + target_element_id + '_' + key);
|
||||
if (element_to_update.length !== 0 && element_to_update.text() !== value.toString()) {
|
||||
element_to_update.html(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (data["data_type"] === "remove_table_row") {
|
||||
let target_element = document.getElementById(target_element_id);
|
||||
if (target_element && target_element.parentElement) {
|
||||
target_element.parentElement.removeChild(target_element);
|
||||
} else {
|
||||
console.warn('[SOCKET] Cannot remove table row - element not found:', target_element_id);
|
||||
}
|
||||
}
|
||||
} else if (data["data_type"] === "status_message") {
|
||||
/* this does not require any website containers. we simply play sounds and echo logs */
|
||||
if (data['status']) {
|
||||
let json = data["status"];
|
||||
if (json["status"]) {
|
||||
let status = json["status"];
|
||||
let action = data["payload"][0];
|
||||
if (status === "success") {
|
||||
play_audio_file("computerbeep_11");
|
||||
} else if (status === "fail") {
|
||||
play_audio_file("computer_error");
|
||||
flash(document.body, lcars_colors["lcars-chestnut-rose"])
|
||||
}
|
||||
console.log(
|
||||
"received status\n\"" + status + ":" + json["uuid4"] + "\"\n" +
|
||||
"for action\n\"" + action + "\""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Catch any errors to prevent handler from breaking
|
||||
console.error('[SOCKET ERROR] Failed to process event:', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
data_type: data ? data.data_type : 'unknown',
|
||||
data: data
|
||||
});
|
||||
|
||||
// Play error sound to alert user
|
||||
play_audio_file("computer_error");
|
||||
|
||||
// Flash screen red to indicate error
|
||||
flash(document.body, lcars_colors["lcars-chestnut-rose"]);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user