Files
AAS_NewGeneration/html/webpage/assets/js/script.js
2025-10-27 16:02:57 +07:00

619 lines
21 KiB
JavaScript

/**
* List of voice types available
* @type {string[]}
*/
window.voiceTypes = [];
/**
* List of categories available
* @type {string[]}
*/
window.categories = [];
/**
* List of languages available
* @type {string[]}
*/
window.languages = [];
/**
* List of scheduled days available
* @type {string[]}
*/
window.scheduledays = []
/**
* @typedef {Object} BroadcastZone
* @property {number} index
* @property {string} description
* @property {String} SoundChannel
* @property {String} Box
* @property {String} Relay
*/
/**
* List of broadcast zones available
* @type {BroadcastZone[]}
*/
window.BroadcastZoneList ??= [];
/**
* @typedef {Object} MessageBank
* @property {number} index
* @property {string} description
* @property {string} language
* @property {number} aNN_ID
* @property {string} voice_Type
* @property {string} message_Detail
* @property {string} message_TAGS
*/
/**
* List of Messagebank data loaded from server
* @type {MessageBank[]}
*/
window.messagebankdata ??= [];
/**
* Reload message bank from server
* @param {string} APIURL API URL endpoint, default "MessageBank/"
* @param {function|null} cbOK callback on success, default null
*/
function reloadMessageBank(APIURL = "MessageBank/", cbOK = null) {
window.messagebankdata ??= [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
//console.log("Message bank data loaded : ", okdata);
if (Array.isArray(okdata)) {
window.messagebankdata.push(...okdata);
window.selectedmessagerow = null;
//fill_messagebanktablebody(window.messagebankdata);
if (cbOK) cbOK();
}
}, (errdata) => {
//console.error("Error loading messagebank : ", errdata);
alert("Error loading messagebank : " + errdata.message);
});
}
/**
* Reload broadcast zones from server
* @param {String} APIURL API URL endpoint (default "BroadcastZones/")
* @param {Function} cbOK callback function on success
*/
function reloadBroadcastZones(APIURL = "BroadcastZones/", cbOK = null) {
window.BroadcastZoneList = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadBroadcastZones : ", okdata)
window.BroadcastZoneList.push(...okdata);
if (cbOK) cbOK();
//fill_broadcastzonetablebody(window.BroadcastZoneList);
} else console.log("reloadBroadcastZones: okdata is not array");
}, (errdata) => {
alert("Error loading broadcast zones : " + errdata.message);
});
}
/**
* Create a list item element
* @param {String} text Text Content for the list item
* @param {String} className Specific class name for the list item
* @returns {JQuery<HTMLElement>}
*/
function ListItem(text, className = "") {
return $('<li>').addClass(className).text(text);
}
/**
* WebSocket connection
* @type {WebSocket}
*/
window.ws = null;
/**
* Send a command to the WebSocket server.
* @param {String} command command to send
* @param {String} data data to send
*/
function sendCommand(command, data) {
if (window.ws.readyState === WebSocket.OPEN) {
window.ws.send(JSON.stringify({ command, data }));
}
}
/**
* Fetch API helper function
* @param {string} endpoint Endpoint URL
* @param {string} method Method (GET, POST, etc.)
* @param {Object} headers Headers to include in the request
* @param {Object} body Body of the request
* @param {Function} cbOK Callback function for successful response
* @param {Function} cbError Callback function for error response
*/
function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) {
let url = window.location.origin + "/api/" + endpoint;
let options = {
method: method,
headers: headers
}
if (body !== null) {
options.body = JSON.stringify(body);
if (!options.headers['Content-Type']) {
options.headers['Content-Type'] = 'application/json';
}
}
fetch(url, options)
.then(async (response) => {
if (!response.ok) {
let msg;
try {
let _xxx = await response.json();
msg = _xxx.message || response.statusText;
} catch {
msg = await response.statusText;
}
throw new Error(msg);
}
return response.json();
})
.then(data => {
cbOK(data);
})
.catch(error => {
cbError(error);
});
}
/**
* Fetch asset file from /assets/img/*
* @param {String} url the filename to fetch, relative to /assets/img/
* @param {Function} cbOK callback function on success, will receive the object URL
* @param {Function} cbError callback function on error, will receive the error object
*/
function fetchImg(url, cbOK, cbError) {
url = "/assets/img/" + url;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.blob();
})
.then(blob => {
const url = URL.createObjectURL(blob);
cbOK(url);
})
.catch(error => {
cbError(error);
});
}
/**
* Reload voice types from server
* @param {Function|null} cbOK callback on success, default null
*/
function getVoiceTypes(cbOK = null) {
window.voiceTypes = [];
fetchAPI("VoiceType", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) {
window.voiceTypes = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + voiceTypes.length + " voice types : " + voiceTypes.join(", "));
if (cbOK) cbOK();
} else console.log("getVoiceTypes: okdata is not array");
}, (errdata) => {
alert("Error loading voice types : " + errdata.message);
});
}
/**
* Reload categories from server
* @param {Function|null} cbOK callback on success, default null
*/
function getCategories(cbOK = null) {
window.categories = [];
fetchAPI("Category", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) {
window.categories = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + categories.length + " categories : " + categories.join(", "));
if (cbOK) cbOK();
} else console.log("getCategories: okdata is not array");
}, (errdata) => {
alert("Error loading categories : " + errdata.message);
});
}
/**
* Reload languages from server
* @param {Function|null} cbOK callback on success, default null
*/
function getLanguages(cbOK = null) {
window.languages = [];
fetchAPI("Language", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) {
window.languages = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + languages.length + " languages : " + languages.join(", ") );
if (cbOK) cbOK();
} else console.log("getLanguages: okdata is not array");
}, (errdata) => {
alert("Error loading languages : " + errdata.message);
});
}
/**
* Reload scheduled days from server
*/
function getScheduledDays() {
window.scheduledays = [];
fetchAPI("ScheduleDay", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) {
window.scheduledays = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + scheduledays.length + " scheduled days : " + scheduledays.join(", ") );
} else console.log("getScheduledDays: okdata is not array");
}, (errdata) => {
alert("Error loading scheduled days : " + errdata.message);
});
}
/**
* Clear database mechanism
* @param {String} APIURL API URL endpoint
* @param {String} whattoclear what to clear
* @param {Function} cbOK callback function on success
* @param {Function} cbError callback function on error
*/
function DoClear(APIURL, whattoclear, cbOK, cbError) {
if (confirm(`Are you sure want to clear ${whattoclear} ? This procedure is not reversible`)) {
fetchAPI(APIURL + "List", "DELETE", {}, null, (okdata) => {
cbOK(okdata);
}, (errdata) => {
cbError(errdata);
});
}
}
/**
* Export mechanism to XLSX file
* @param {String} APIURL API URL endpoint
* @param {String} filename target filename
* @param {Object} queryParams additional query parameters as object
*/
function DoExport(APIURL, filename, queryParams = {}) {
// send GET request to APIURL + "ExportXLSX"
// reply Content-Type is application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
// reply Content-Disposition: attachment; filename=filename
// Use fetch to download the XLSX file as a blob and trigger download
let url = "/api/" + APIURL + "ExportXLSX";
if (queryParams && Object.keys(queryParams).length > 0) {
url += "?" + new URLSearchParams(queryParams).toString();
}
fetch(url, {
method: "GET",
headers: {}
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok ' + response.statusText);
return response.blob();
})
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
})
.catch(error => {
alert("Error export to " + filename + ": " + error.message);
});
return; // prevent the rest of the function from running
}
/**
* Import mechanism from XLSX file
* @param {String} APIURL API URL endpoint
* @param {Function<Object>} cbOK function that accept object data
* @param {Function<Error>} cbError function that accept error object
*/
function DoImport(APIURL, cbOK, cbError) {
// Open file selection dialog that accepts only .xlsx files
// then upload to server using fetchAPI at "api/ImportXLSX" with POST method
let fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.xlsx';
fileInput.onchange = e => {
let file = e.target.files[0];
if (file) {
let formData = new FormData();
formData.append('file', file);
fetch("/api/" + APIURL + "ImportXLSX", { method: 'POST', body: formData })
.then(response => {
if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); }
return response.json();
})
.then(data => {
cbOK(data);
})
.catch(error => {
cbError(error);
});
} else {
cbError(new Error("No file selected"));
}
};
fileInput.click();
fileInput.remove();
}
window.greencircle = null;
window.redcircle = null;
/**
* App entry point
*/
$(document).ready(function () {
document.title = "Automatic Announcement System"
if (window.greencircle === null) {
fetchImg('green_circle.png', (url) => { window.greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); });
}
if (window.redcircle === null) {
fetchImg('red_circle.png', (url) => { window.redcircle = url; }, (err) => { console.error("Error loading red_circle.png : ", err); });
}
const wsURL = window.location.pathname + '/ws'
if (chrome && chrome.runtime && chrome.runtime.lastError) {
alert("Runtime error: " + chrome.runtime.lastError.message);
return;
}
// reset status indicators
function resetStatusIndicators() {
$('#onlineindicator').attr('src', window.redcircle);
$('#cpustatus').text("CPU : N/A");
$('#ramstatus').text("RAM : N/A");
$('#diskstatus').text("Disk : N/A");
$('#networkstatus').text("Network : N/A");
$('#datetimetext').text("Date/Time : N/A");
}
resetStatusIndicators();
getVoiceTypes();
getCategories();
getLanguages();
getScheduledDays();
reloadBroadcastZones();
reloadMessageBank();
// reconnect handle
let ws_reconnect;
function reconnect() {
if (window.ws && window.ws.readyState === WebSocket.OPEN) return;
const s = new WebSocket(wsURL);
s.addEventListener('open', () => {
console.log('WebSocket connection established');
$('#onlineindicator').attr('src', window.greencircle);
if (ws_reconnect) {
// stop reconnect attempts
clearTimeout(ws_reconnect);
ws_reconnect = null;
}
window.dispatchEvent(new Event('ws_connected'));
});
s.addEventListener('close', () => {
console.log('WebSocket connection closed');
window.dispatchEvent(new Event('ws_disconnected'));
resetStatusIndicators();
if (!ws_reconnect) {
clearTimeout(ws_reconnect);
ws_reconnect = null;
}
ws_reconnect = setTimeout(reconnect, 5000); // try to reconnect every 5 seconds
});
s.addEventListener('message', (event) => {
if ($('#onlineindicator').attr('src') !== window.greencircle) {
$('#onlineindicator').attr('src', window.greencircle);
}
let rep = JSON.parse(event.data);
window.dispatchEvent(new CustomEvent('ws_message', { detail: rep }));
let cmd = rep.reply
let data = rep.data;
if (cmd && cmd.length > 0) {
switch (cmd) {
case "getCPUStatus":
$('#cpustatus').text("CPU : " + data)
break;
case "getMemoryStatus":
$('#ramstatus').text("RAM : " + data)
break;
case "getDiskStatus":
$('#diskstatus').text("Disk : " + data)
break;
case "getNetworkStatus":
let result = "";
let json = JSON.parse(data);
if (Array.isArray(json) && json.length > 0) {
json.forEach((net) => {
if (net){
if (net.displayName && net.displayName.length>0){
if (net.ipV4addr && Array.isArray(net.ipV4addr) && net.ipV4addr.length>0){
if (result.length > 0) result += "\n"
result += `${net.displayName} (${net.ipV4addr.join(";")}) TX:${(net.txSpeed / 1024).toFixed(1)} KB/s RX:${(net.rxSpeed / 1024).toFixed(1)} KB/s`
}
}
}
})
} else result = "N/A";
$('#networkstatus').text(result)
break;
case "getSystemTime":
$('#datetimetext').text(data)
break;
}
}
});
window.ws = s;
}
reconnect();
window.addEventListener('beforeunload', () => {
try{
window.ws?.close(1000, "Client closed connection");
} catch (error) {
console.error("Error closing WebSocket connection:", error);
}
});
setInterval(() => {
sendCommand("getCPUStatus", "")
sendCommand("getMemoryStatus", "")
sendCommand("getDiskStatus", "")
sendCommand("getNetworkStatus", "")
sendCommand("getSystemTime", "")
}, 1000)
let sidemenu = new bootstrap.Offcanvas('#offcanvas-menu');
$('#showmenu').click(() => { sidemenu.show(); })
$('#homelink').click(() => {
sidemenu.hide();
$('#content').load('overview.html', function (response, status, xhr) {
if (status === "success") {
console.log("Overview content loaded successfully");
}
});
});
$('#soundbanklink').click(() => {
sidemenu.hide();
$('#content').load('soundbank.html', function (response, status, xhr) {
if (status === "success") {
console.log("Soundbank content loaded successfully");
// pindah soundbank.js
} else {
console.error("Error loading soundbank content : ", xhr.status, xhr.statusText);
}
});
})
$('#messagebanklink').click(() => {
sidemenu.hide();
$('#content').load('messagebank.html', function (response, status, xhr) {
if (status === "success") {
console.log("Messagebank content loaded successfully");
// pindah messagebank.js
} else {
console.error("Error loading messagebank content : ", xhr.status, xhr.statusText);
}
});
})
$('#languagelink').click(() => {
sidemenu.hide();
$('#content').load('language.html', function (response, status, xhr) {
if (status === "success") {
console.log("Language content loaded successfully");
// pindah languagelink.js
} else {
console.error("Error loading language content : ", xhr.status, xhr.statusText);
}
});
})
$('#broadcastzonelink').click(() => {
sidemenu.hide();
$('#content').load('broadcastzones.html', function (response, status, xhr) {
if (status === "success") {
console.log("Broadcast Zone content loaded successfully");
// pindah ke broadcastzones.js
} else {
console.error("Error loading broadcast zone content : ", xhr.status, xhr.statusText);
}
});
})
$('#timerlink').click(() => {
sidemenu.hide();
$('#content').load('timer.html', function (response, status, xhr) {
if (status === "success") {
console.log("Timer content loaded successfully");
// pindah ke schedulebank.js
} else {
console.error("Error loading timer content : ", xhr.status, xhr.statusText);
}
});
})
$('#loglink').click(() => {
sidemenu.hide();
$('#content').load('log.html', function (response, status, xhr) {
if (status === "success") {
console.log("Log content loaded successfully");
// pindah ke log.js
} else {
console.error("Error loading log content:", xhr.status, xhr.statusText);
}
});
})
$('#usermanagement').click(() => {
sidemenu.hide();
$('#content').load('usermanagement.html', function (response, status, xhr) {
if (status === "success") {
console.log("User Management content loaded successfully");
// pindah ke usermanagement.js
} else {
console.error("Error loading user management content:", xhr.status, xhr.statusText);
}
});
});
$('#settinglink').click(() => {
sidemenu.hide();
$('#content').load('setting.html', function (response, status, xhr) {
if (status === "success") {
console.log("Setting content loaded successfully");
//sendCommand("getSetting", "");
} else {
console.error("Error loading setting content:", xhr.status, xhr.statusText);
}
});
})
$('#filemanagement').click(() => {
sidemenu.hide();
$('#content').load('filemanagement.html', function (response, status, xhr) {
if (status === "success") {
console.log("File Management content loaded successfully");
// pindah ke filemanagement.js
}
});
});
$('#logoutlink').click(() => {
//window.location.href = "login.html"
fetch("/logout", {method: 'GET'})
.then(response => {
if (response.ok) {
window.location.href = "login.html";
} else {
alert("Logout failed: " + response.statusText);
}
})
.catch(error => {
alert("Logout error: " + error.message);
});
});
});