diff --git a/html/webpage/assets/js/broadcastzones.js b/html/webpage/assets/js/broadcastzones.js
new file mode 100644
index 0000000..b668f74
--- /dev/null
+++ b/html/webpage/assets/js/broadcastzones.js
@@ -0,0 +1,302 @@
+/**
+ * @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[]}
+ */
+let BroadcastZoneList = [];
+
+/**
+ * Currently selected broadcast zone row in the table
+ * @type {JQuery|null}
+ */
+let selectedBroadcastZoneRow = null;
+
+/**
+ * Fill broadcast zone table body with values
+ * @param {BroadcastZone[]} vv values to fill
+ */
+function fill_broadcastzonetablebody(vv) {
+ $('#broadcastzonetablebody').empty();
+ if (!Array.isArray(vv) || vv.length === 0) return;
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.description} |
+ ${item.soundChannel} |
+ ${item.box} |
+ ${item.relay} |
+
`;
+ $('#broadcastzonetablebody').append(row);
+ let $addedrow = $('#broadcastzonetablebody tr:last');
+ $addedrow.click(function () {
+ if (selectedBroadcastZoneRow) {
+ selectedBroadcastZoneRow.find('td').css('background-color', '');
+ if (selectedBroadcastZoneRow.is($(this))) {
+ selectedBroadcastZoneRow = null;
+ $('#btnRemove').prop('disabled', true);
+ $('#btnEdit').prop('disabled', true);
+ return;
+ }
+ }
+ $(this).find('td').css('background-color', '#ffeeba');
+ selectedBroadcastZoneRow = $(this);
+ $('#btnRemove').prop('disabled', false);
+ $('#btnEdit').prop('disabled', false);
+ });
+ });
+ $('#tablesize').text("Table Size: " + vv.length);
+}
+
+/**
+ * Reload broadcast zones from server
+ * @param {String} APIURL API URL endpoint (default "BroadcastZones/")
+ */
+function reloadBroadcastZones(APIURL = "BroadcastZones/") {
+ BroadcastZoneList = [];
+ fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
+ if (Array.isArray(okdata)) {
+ //console.log("reloadBroadcastZones : ", okdata)
+ BroadcastZoneList = okdata;
+ fill_broadcastzonetablebody(BroadcastZoneList);
+ } else console.log("reloadBroadcastZones: okdata is not array");
+ }, (errdata) => {
+ alert("Error loading broadcast zones : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log("broadcastzones.js loaded successfully");
+ selectedBroadcastZoneRow = null;
+ let $btnClear = $('#btnClear');
+ let $btnAdd = $('#btnAdd');
+ let $btnEdit = $('#btnEdit');
+ let $btnRemove = $('#btnRemove');
+ let $btnExport = $('#btnExport');
+ let $btnImport = $('#btnImport');
+ $btnEdit.prop('disabled', true);
+ $btnRemove.prop('disabled', true);
+ let APIURL_BroadcastZone = "BroadcastZones/";
+
+
+ let $broadcastzonemodal = $('#broadcastzonemodal');
+ let $broadcastzoneindex = $broadcastzonemodal.find('#broadcastzoneindex');
+ let $broadcastzonedescription = $broadcastzonemodal.find('#broadcastzonedescription');
+ let $broadcastzonesoundchannel = $broadcastzonemodal.find('#broadcastzonesoundchannel');
+ let $broadcastzonebox = $broadcastzonemodal.find('#broadcastzonebox');
+
+
+
+ let $findzone = $('#findzone');
+ $findzone.on('input', function () {
+ let searchTerm = $findzone.val().trim().toLowerCase();
+ if (searchTerm.length > 0) {
+ selectedBroadcastZoneRow = null;
+ let filtered = broadcastzonedata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.box.toLowerCase().includes(searchTerm) || item.soundChannel.toLowerCase().includes(searchTerm) || item.relay.toLowerCase().includes(searchTerm));
+ fill_broadcastzonetablebody(filtered);
+ } else {
+ selectedBroadcastZoneRow = null;
+ fill_broadcastzonetablebody(broadcastzonedata);
+ }
+ });
+
+ /**
+ * Find Checkbox for relays 1 to 32
+ * Checkbox id is 01 to 32 with leading zero for 1 to 9
+ * @param {number} id 1 - 32
+ * @returns JQuery
+ */
+ function cbRelay(id) {
+ return $broadcastzonemodal.find('#R' + (id < 10 ? '0' : '') + id);
+ }
+
+ /**
+ * Clear broadcast zone modal to default state
+ */
+ function clearBroadcastZoneModal() {
+ $broadcastzoneindex.prop('disabled', true).val('');
+ $broadcastzonedescription.val('');
+ $broadcastzonesoundchannel.val('');
+ $broadcastzonebox.val('');
+ for (let i = 1; i <= 32; i++) {
+ cbRelay(i).prop('checked', false);
+ }
+ }
+
+ reloadBroadcastZones(APIURL_BroadcastZone);
+
+ $btnClear.click(() => {
+ DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => {
+ reloadBroadcastZones(APIURL_BroadcastZone);
+ alert("Success clear broadcast zones: " + okdata.message);
+ }, (errdata) => {
+ alert("Error clear broadcast zones: " + errdata.message);
+ });
+ });
+
+ $btnAdd.click(() => {
+ $broadcastzonemodal.modal('show');
+ clearBroadcastZoneModal();
+
+ $broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () {
+
+ let description = $broadcastzonedescription.val().trim();
+ let soundChannel = $broadcastzonesoundchannel.val().trim();
+ let box = $broadcastzonebox.val().trim();
+ let relayArray = [];
+ for (let i = 1; i <= 32; i++) {
+ if (cbRelay(i).is(':checked')) {
+ relayArray.push(i);
+ }
+ }
+ let relay = relayArray.join(';');
+ if (description.length === 0) {
+ alert("Description cannot be empty");
+ return;
+ }
+ if (soundChannel.length === 0) {
+ alert("Sound Channel cannot be empty");
+ return;
+ }
+ if (box.length === 0) {
+ alert("Box cannot be empty");
+ return;
+ }
+ if (relayArray.length === 0) {
+ alert("At least one relay must be selected");
+ return;
+ }
+ let bz = {
+ description: description,
+ SoundChannel: soundChannel,
+ Box: box,
+ Relay: relay
+ };
+ fetchAPI(APIURL_BroadcastZone + "Add", "POST", bz, null, (okdata) => {
+ reloadBroadcastZones(APIURL_BroadcastZone);
+ alert("Success add new broadcast zone: " + okdata.message);
+ }, (errdata) => {
+ alert("Error add new broadcast zone: " + errdata.message);
+ });
+
+ $broadcastzonemodal.modal('hide');
+ });
+ $broadcastzonemodal.off('click.broadcastzoneclose').on('click.broadcastzoneclose', '#broadcastzoneclose', function () {
+ $broadcastzonemodal.modal('hide');
+ });
+ });
+
+ $btnRemove.click(() => {
+ if (selectedBroadcastZoneRow) {
+ let cells = selectedBroadcastZoneRow.find('td');
+ /** @type {BroadcastZone} */
+ let bz = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ SoundChannel: cells.eq(2).text(),
+ Box: cells.eq(3).text(),
+ Relay: cells.eq(4).text()
+ };
+ if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) {
+ fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => {
+ reloadBroadcastZones(APIURL_BroadcastZone);
+ alert("Success delete broadcast zone: " + okdata.message);
+ }, (errdata) => {
+ alert("Error delete broadcast zone: " + errdata.message);
+ });
+ }
+ }
+ });
+
+ $btnEdit.click(() => {
+ if (selectedBroadcastZoneRow) {
+ let cells = selectedBroadcastZoneRow.find('td');
+ /** @type {BroadcastZone} */
+ let bz = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ SoundChannel: cells.eq(2).text(),
+ Box: cells.eq(3).text(),
+ Relay: cells.eq(4).text()
+ };
+ if (confirm(`Are you sure to edit broadcast zone [${bz.index}] Description=${bz.description} SoundChannel=${bz.SoundChannel} Box=${bz.Box} Relay=${bz.Relay}?`)) {
+ $broadcastzonemodal.modal('show');
+ clearBroadcastZoneModal();
+ $broadcastzoneindex.val(bz.index);
+ $broadcastzonedescription.val(bz.description);
+ $broadcastzonesoundchannel.val(bz.SoundChannel);
+ $broadcastzonebox.val(bz.Box);
+ if (bz.Relay) {
+ bz.Relay.split(';').forEach(relayId => {
+ let id = parseInt(relayId, 10);
+ cbRelay(id).prop('checked', true);
+ });
+ }
+ $broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () {
+ let description = $broadcastzonedescription.val().trim();
+ let soundChannel = $broadcastzonesoundchannel.val().trim();
+ let box = $broadcastzonebox.val().trim();
+ let relayArray = [];
+ for (let i = 1; i <= 32; i++) {
+ if (cbRelay(i).is(':checked')) {
+ relayArray.push(i);
+ }
+ }
+ let relay = relayArray.join(';');
+ if (description.length === 0) {
+ alert("Description cannot be empty");
+ return;
+ }
+ if (soundChannel.length === 0) {
+ alert("Sound Channel cannot be empty");
+ return;
+ }
+ if (box.length === 0) {
+ alert("Box cannot be empty");
+ return;
+ }
+ if (relayArray.length === 0) {
+ alert("At least one relay must be selected");
+ return;
+ }
+ let bzUpdate = {
+ description: description,
+ SoundChannel: soundChannel,
+ Box: box,
+ Relay: relay
+ };
+ fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", bzUpdate, null, (okdata) => {
+ reloadBroadcastZones(APIURL_BroadcastZone);
+ alert("Success edit broadcast zone: " + okdata.message);
+ }, (errdata) => {
+ alert("Error edit broadcast zone: " + errdata.message);
+ });
+ $broadcastzonemodal.modal('hide');
+ });
+ $broadcastzonemodal.off('click.broadcastzoneclose').on('click.broadcastzoneclose', '#broadcastzoneclose', function () {
+ $broadcastzonemodal.modal('hide');
+ });
+ }
+ }
+ });
+
+ $btnExport.click(() => {
+ DoExport(APIURL_BroadcastZone, "broadcastzones.xlsx", {});
+ });
+
+ $btnImport.click(() => {
+ DoImport(APIURL_BroadcastZone, (okdata) => {
+ reloadBroadcastZones(APIURL_BroadcastZone);
+ alert("Success import broadcast zones: " + okdata.message);
+ }, (errdata) => {
+ alert("Error importing broadcast zones from XLSX: " + errdata.message);
+ });
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/assets/js/languagelink.js b/html/webpage/assets/js/languagelink.js
new file mode 100644
index 0000000..638a498
--- /dev/null
+++ b/html/webpage/assets/js/languagelink.js
@@ -0,0 +1,272 @@
+/**
+ * @typedef {Object} LanguageBank
+ * @property {number} index
+ * @property {string} tag
+ * @property {string} language
+ *
+ */
+
+/** List of Languagebank data loaded from server
+ * @type {LanguageBank[]}
+ */
+let languagebankdata = [];
+/**
+ * Currently selected languagebank row in the table
+ * @type {JQuery|null}
+ */
+let selectedlanguagerow = null;
+
+/**
+ * Fill languagebank table body with values
+ * @param {LanguageBank[]} vv values to fill
+ */
+function fill_languagebanktablebody(vv) {
+ $('#languagebanktablebody').empty();
+ if (!Array.isArray(vv) || vv.length === 0) return;
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.tag} |
+ ${item.language} |
+
`;
+ $('#languagebanktablebody').append(row);
+ let $addedrow = $('#languagebanktablebody tr:last');
+ $addedrow.click(function () {
+ if (selectedlanguagerow) {
+ selectedlanguagerow.find('td').css('background-color', '');
+ if (selectedlanguagerow.is($(this))) {
+ selectedlanguagerow = null;
+ $('#btnRemove').prop('disabled', true);
+ $('#btnEdit').prop('disabled', true);
+ return;
+ }
+ }
+ $addedrow.find('td').css('background-color', '#ffeeba');
+ selectedlanguagerow = $addedrow;
+ $('#btnRemove').prop('disabled', false);
+ $('#btnEdit').prop('disabled', false);
+ });
+ });
+ $('#tablesize').text("Table Size: " + vv.length);
+}
+
+/**
+ * Reload language bank from server
+ * @param {string} APIURL API URL endpoint, default "LanguageLink/"
+ */
+function reloadLanguageBank(APIURL = "LanguageLink/") {
+ languagebankdata = [];
+ fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
+ if (Array.isArray(okdata)) {
+ languagebankdata = okdata;
+ selectedlanguagerow = null;
+ fill_languagebanktablebody(languagebankdata);
+ }
+ }, (errdata) => {
+ alert("Error loading languagebank : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log('languagebank.js loaded');
+
+ $('#languagebanktablebody').empty();
+ selectedlanguagerow = null;
+ let $btnClear = $('#btnClear');
+ let $btnAdd = $('#btnAdd');
+ let $btnRemove = $('#btnRemove');
+ let $btnEdit = $('#btnEdit');
+ let $btnExport = $('#btnExport');
+ let $btnImport = $('#btnImport');
+ $btnRemove.prop('disabled', true);
+ $btnEdit.prop('disabled', true);
+ let APIURL = "LanguageLink/";
+ let $findlanguage = $('#findlanguage');
+ let $modal = $('#languagemodal');
+ let $langid = $modal.find('#languagelinkindex');
+ let $langtag = $modal.find('#languagelinktag');
+ let $cbInd = $modal.find('#langId');
+ let $cbLocal = $modal.find('#langLocal');
+ let $cbEn = $modal.find('#langEn');
+ let $cbArb = $modal.find('#langArb');
+ let $cbJap = $modal.find('#langJap');
+ let $cbChi = $modal.find('#langChi');
+
+ function clearLanguageModal() {
+ $langid.prop('disabled', true).val('');
+ $langtag.val('');
+ $cbInd.prop('checked', false);
+ $cbLocal.prop('checked', false);
+ $cbEn.prop('checked', false);
+ $cbArb.prop('checked', false);
+ $cbJap.prop('checked', false);
+ $cbChi.prop('checked', false);
+ }
+
+ $findlanguage.on('input', function () {
+ let searchTerm = $findlanguage.val().toLowerCase();
+ if (searchTerm.length > 0) {
+ selectedlanguagerow = null;
+ let filtered = languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm));
+ fill_languagebanktablebody(filtered);
+ } else {
+ selectedlanguagerow = null;
+ fill_languagebanktablebody(languagebankdata);
+ }
+ });
+
+ reloadLanguageBank(APIURL);
+ $btnClear.click(() => {
+ DoClear(APIURL, "LanguageLink", (okdata) => {
+ reloadLanguageBank(APIURL);
+ alert("Success clear languageLink : " + okdata.message);
+ }, (errdata) => {
+ alert("Error clear languageLink : " + errdata.message);
+ });
+
+ });
+ $btnAdd.click(() => {
+ // show modal with id 'languagemodal'
+ $modal.modal('show');
+ clearLanguageModal();
+
+ // save button click event
+ $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () {
+ const tag = $langtag.val();
+ const langs = [];
+ if ($cbInd.is(':checked')) langs.push('INDONESIA');
+ if ($cbLocal.is(':checked')) langs.push('LOCAL');
+ if ($cbEn.is(':checked')) langs.push('ENGLISH');
+ if ($cbArb.is(':checked')) langs.push('ARABIC');
+ if ($cbJap.is(':checked')) langs.push('JAPANESE');
+ if ($cbChi.is(':checked')) langs.push('CHINESE');
+
+ if (tag.length === 0) {
+ alert("Tag cannot be empty");
+ return;
+ }
+ if (langs.length === 0) {
+ alert("At least one language must be selected");
+ return;
+ }
+
+ const langString = langs.join(';');
+ let ll = {
+ tag: tag,
+ language: langString
+ }
+ fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
+ alert("Success add language : " + okdata.message);
+ reloadLanguageBank(APIURL);
+ }, (errdata) => {
+ alert("Error add language : " + errdata.message);
+ });
+ $modal.modal('hide');
+
+
+ });
+ // close button click event
+ $modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () {
+ $modal.modal('hide');
+ });
+ });
+ $btnRemove.click(() => {
+ if (selectedlanguagerow) {
+ let cells = selectedlanguagerow.find('td');
+ /** @type {Language} */
+ let ll = {
+ index: cells.eq(0).text(),
+ tag: cells.eq(1).text(),
+ language: cells.eq(2).text()
+ }
+ if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
+ fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => {
+ reloadLanguageBank(APIURL);
+ alert("Success delete language : " + okdata.message);
+ }, (errdata) => {
+ alert("Error delete language : " + errdata.message);
+ });
+ }
+ }
+ });
+ $btnEdit.click(() => {
+ if (selectedlanguagerow) {
+ let cells = selectedlanguagerow.find('td');
+ /** @type {Language} */
+ let ll = {
+ index: cells.eq(0).text(),
+ tag: cells.eq(1).text(),
+ language: cells.eq(2).text()
+ }
+ if (confirm(`Are you sure to edit language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
+
+ clearLanguageModal();
+ $langid.val(ll.index);
+ $langtag.val(ll.tag);
+ let langs = ll.language.toUpperCase().split(';');
+ $cbInd.prop('checked', langs.includes('INDONESIA'));
+ $cbLocal.prop('checked', langs.includes('LOCAL'));
+ $cbEn.prop('checked', langs.includes('ENGLISH'));
+ $cbArb.prop('checked', langs.includes('ARABIC'));
+ $cbJap.prop('checked', langs.includes('JAPANESE'));
+ $cbChi.prop('checked', langs.includes('CHINESE'));
+ $modal.modal('show');
+ // save button click event
+ $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () {
+ const tag = $langtag.val();
+ const langs = [];
+ if ($cbInd.is(':checked')) langs.push('INDONESIA');
+ if ($cbLocal.is(':checked')) langs.push('LOCAL');
+ if ($cbEn.is(':checked')) langs.push('ENGLISH');
+ if ($cbArb.is(':checked')) langs.push('ARABIC');
+ if ($cbJap.is(':checked')) langs.push('JAPANESE');
+ if ($cbChi.is(':checked')) langs.push('CHINESE');
+ if (tag.length === 0) {
+ alert("Tag cannot be empty");
+ return;
+ }
+ if (langs.length === 0) {
+ alert("At least one language must be selected");
+ return;
+ }
+ const langString = langs.join(';');
+ if (ll.tag === tag && ll.language === langString) {
+ alert("No changes detected");
+ $modal.modal('hide');
+ return;
+ }
+
+ ll.tag = tag;
+ ll.language = langString;
+ fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => {
+ reloadLanguageBank(APIURL);
+ alert("Success edit language : " + okdata.message);
+ }, (errdata) => {
+ alert("Error edit language : " + errdata.message);
+ });
+
+ $modal.modal('hide');
+ });
+ // close button click event
+ $modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () {
+ $modal.modal('hide');
+ });
+
+
+ }
+ }
+ });
+ $btnExport.click(() => {
+ DoExport(APIURL, "languagebank.xlsx", {});
+
+ });
+ $btnImport.click(() => {
+ DoImport(APIURL, (okdata) => {
+ reloadLanguageBank(APIURL);
+ alert("Success import languagebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error importing languagebank from XLSX : " + errdata.message);
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/assets/js/log.js b/html/webpage/assets/js/log.js
new file mode 100644
index 0000000..8401eb6
--- /dev/null
+++ b/html/webpage/assets/js/log.js
@@ -0,0 +1,97 @@
+/**
+ * @typedef {Object} Log
+ * @property {number} index
+ * @property {string} datenya
+ * @property {string} timenya
+ * @property {string} machine
+ * @property {string} description
+ */
+
+/** List of Log data loaded from server
+ * @type {Log[]}
+ */
+let logdata = [];
+
+/**
+ * Fill log table body with values
+ * @param {Log[]} vv values to fill
+ */
+function fill_logtablebody(vv) {
+ $('#logtablebody').empty();
+ $('#btnExport').prop('disabled', true);
+ $('#searchfilter').prop('disabled', true);
+ if (!Array.isArray(vv) || vv.length === 0) return;
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.datenya} |
+ ${item.timenya} |
+ ${item.machine} |
+ ${item.description} |
+
`;
+ $('#logtablebody').append(row);
+ });
+ $('#tablesize').text("Table Size: " + vv.length);
+ $('#btnExport').prop('disabled', false);
+ $('#searchfilter').prop('disabled', false);
+}
+
+/**
+ * Reload logs from server with date and filter
+ * @param {String} APIURL API URL endpoint , default "Log/"
+ * @param {String} date date in format dd-mm-yyyy
+ * @param {String} filter log filter text
+ */
+function reloadLogs(APIURL = "Log/", date, filter) {
+ const params = new URLSearchParams({
+ date: date,
+ filter: filter
+ })
+ fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => {
+ if (Array.isArray(okdata)) {
+ logdata = okdata;
+ fill_logtablebody(okdata);
+ }
+ }, (errdata) => {
+ alert("Error loading logs : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log("log.js ready");
+ const $logdate = $('#logdate');
+ const $searchfilter = $('#searchfilter');
+ const $logtable = $('#logtablebody')
+ const $btnExport = $('#btnExport');
+ let selectedlogdate = "";
+ let logfilter = "";
+ let APIURL = "Log/";
+ $logtable.empty();
+
+
+ if (!$logdate.val()) {
+ const today = new Date();
+ const dd = String(today.getDate()).padStart(2, '0');
+ const mm = String(today.getMonth() + 1).padStart(2, '0');
+ const yyyy = today.getFullYear();
+ $logdate.val(`${yyyy}-${mm}-${dd}`);
+ selectedlogdate = `${dd}-${mm}-${yyyy}`;
+ reloadLogs(APIURL, selectedlogdate, logfilter);
+ }
+ $logdate.off('change').on('change', function () {
+ const selected = $(this).val();
+ if (selected) {
+ const [year, month, day] = selected.split('-');
+ selectedlogdate = `${day}-${month}-${year}`;
+ reloadLogs(APIURL, selectedlogdate, logfilter);
+ }
+ });
+
+ $searchfilter.off('input').on('input', function () {
+ logfilter = $(this).val();
+ reloadLogs(APIURL, selectedlogdate, logfilter);
+ });
+ $btnExport.off('click').on('click', function () {
+ DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter });
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/assets/js/messagebank.js b/html/webpage/assets/js/messagebank.js
new file mode 100644
index 0000000..17f48cb
--- /dev/null
+++ b/html/webpage/assets/js/messagebank.js
@@ -0,0 +1,452 @@
+/**
+ * @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[]}
+ */
+let messagebankdata = [];
+/**
+ * Currently selected messagebank row in the table
+ * @type {JQuery|null}
+ */
+let selectedmessagerow = null;
+
+/**
+ * Fill messagebank table body with values
+* @param {MessageBank[]} vv values to fill
+*/
+function fill_messagebanktablebody(vv) {
+ $('#messagebanktablebody').empty();
+ if (!Array.isArray(vv) || vv.length === 0) return;
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.description} |
+ ${item.language} |
+ ${item.aNN_ID} |
+ ${item.voice_Type} |
+ ${item.message_Detail} |
+ ${item.message_TAGS} |
+
`;
+ $('#messagebanktablebody').append(row);
+ let $addedrow = $('#messagebanktablebody tr:last');
+ $addedrow.click(function () {
+ if (selectedmessagerow) {
+ selectedmessagerow.find('td').css('background-color', '');
+ if (selectedmessagerow.is($(this))) {
+ selectedmessagerow = null;
+ $('#btnRemove').prop('disabled', true);
+ $('#btnEdit').prop('disabled', true);
+ return;
+ }
+ }
+ $addedrow.find('td').css('background-color', '#ffeeba');
+ selectedmessagerow = $addedrow;
+ $('#btnRemove').prop('disabled', false);
+ $('#btnEdit').prop('disabled', false);
+ });
+ });
+
+ $('#tablesize').text("Table Size: " + vv.length);
+}
+
+/**
+* Reload message bank from server
+* @param {string} APIURL API URL endpoint, default "MessageBank/"
+*/
+function reloadMessageBank(APIURL = "MessageBank/") {
+ messagebankdata = [];
+ fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
+ if (Array.isArray(okdata)) {
+ messagebankdata = okdata;
+ selectedmessagerow = null;
+ fill_messagebanktablebody(messagebankdata);
+ }
+ }, (errdata) => {
+ alert("Error loading messagebank : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log("messagebank.js loaded");
+ $('#messagebanktablebody').empty();
+ selectedmessagerow = null;
+ let $btnClear = $('#btnClear');
+ let $btnAdd = $('#btnAdd');
+ let $btnRemove = $('#btnRemove');
+ let $btnEdit = $('#btnEdit');
+ let $btnExport = $('#btnExport');
+ let $btnImport = $('#btnImport');
+ $btnRemove.prop('disabled', true);
+ $btnEdit.prop('disabled', true);
+ let APIURL = "MessageBank/";
+ let $findmessage = $('#findmessage');
+
+ // modal for add / edit messagebank
+ let $modal = $('#messagebankmodal');
+ // text input, disabled by default
+ let $messageindex = $modal.find('#messageindex');
+ // text input
+ let $messagedescription = $modal.find('#messagedescription');
+ // select input, options loaded from languages[]
+ let $messagelanguage = $modal.find('#messagelanguage');
+ // number input from 1 to 100
+ let $messageannid = $modal.find('#messageannid');
+ // select input, options loaded from voiceTypes[]
+ let $messagevoicetype = $modal.find('#messagevoicetype');
+ // list of available categories and phrases
+ let $messageavailablevariables = $modal.find('#messageavailablevariables');
+ // list of selected categories and phrases
+ let $messageselectedvariables = $modal.find('#messageselectedvariables');
+ // for clearing messageselectedvariables
+ let $btnclearlist = $modal.find('#btnclearlist');
+ // for removing selected item from messageselectedvariables
+ let $btnremovefromlist = $modal.find('#btnremovefromlist');
+ // for adding selected item from messageavailablevariables to messageselectedvariables
+ let $btnaddtolist = $modal.find('#btnaddtolist');
+
+ /**
+ * Refill messageavailablevariables options from categories[]
+ * and soundbankdata with category "Phrase" if messagelanguage and messagevoicetype are selected
+ */
+ function refill_messageavailablevariables() {
+ $messageavailablevariables.empty();
+ categories.forEach(cat => {
+ $messageavailablevariables.append(ListItem(`{${cat}}`));
+ });
+ if ($messagelanguage.val() && $messagevoicetype.val()) {
+ soundbankdata
+ .filter(sb => sb.language.toLowerCase() === $messagelanguage.val().toLowerCase())
+ .filter(sb => sb.voiceType.toLowerCase() === $messagevoicetype.val().toLowerCase())
+ .filter(sb => sb.category.toLowerCase() === "phrase")
+ .forEach(sb => {
+ $messageavailablevariables.append(ListItem(`[${sb.Description}]`));
+ });
+ }
+
+ }
+
+ /**
+ * Clear message modal to default state
+ */
+ function clearMessageModal() {
+ $messageindex.val('').prop('disabled', true);
+ $messagedescription.val('');
+ // fill messagelanguage options from languages[]
+ $messagelanguage.empty();
+ languages.forEach(lang => {
+ $messagelanguage.append(new Option(lang, lang));
+ });
+ $messagelanguage.val(null);
+ $messagelanguage.on('change', function () {
+ refill_messageavailablevariables();
+ });
+ // set default annid to 1
+ $messageannid.val(1);
+ // fill messagevoicetype options from voiceTypes[]
+ $messagevoicetype.empty();
+ voiceTypes.forEach(vt => {
+ $messagevoicetype.append(new Option(vt, vt));
+ });
+ $messagevoicetype.val(null);
+ $messagevoicetype.on('change', function () {
+ refill_messageavailablevariables();
+ });
+
+ refill_messageavailablevariables();
+ $messageselectedvariables.empty();
+
+ // event on btnclearlist
+ $btnclearlist.off('click').on('click', function () {
+ if ($messageselectedvariables.children().length > 0) {
+ if (confirm("Are you sure want to clear selected variables list?")) {
+ $messageselectedvariables.empty();
+ }
+ }
+ });
+
+ // event on btnremovefromlist
+ $btnremovefromlist.off('click').on('click', function () {
+ let $selected = $messageselectedvariables.find('option:selected');
+ if ($selected.length > 0) {
+ $selected.remove();
+ }
+
+ });
+
+ // event on btnaddtolist
+ $btnaddtolist.off('click').on('click', function () {
+ let $selected = $messageavailablevariables.find('option:selected');
+ if ($selected.length > 0) {
+ $selected.each(function () {
+ $messageselectedvariables.append($(this).clone());
+ });
+ }
+ });
+ }
+
+
+ $findmessage.on('input', function () {
+ let searchTerm = $findmessage.val().toLowerCase();
+ if (searchTerm.length > 0) {
+ selectedmessagerow = null;
+ let filtered = messagebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.message_Detail.toLowerCase().includes(searchTerm) || item.message_TAGS.toLowerCase().includes(searchTerm));
+ fill_messagebanktablebody(filtered);
+ } else {
+ selectedmessagerow = null;
+ fill_messagebanktablebody(messagebankdata);
+ }
+ });
+
+
+ reloadMessageBank(APIURL);
+ $btnClear.click(() => {
+ DoClear(APIURL, "Messagebank", (okdata) => {
+ reloadMessageBank(APIURL);
+ alert("Success clear messagebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error clear messagebank : " + errdata.message);
+ });
+
+ });
+ $btnAdd.click(() => {
+
+ $modal.modal('show');
+ clearMessageModal();
+
+ // event on Click save button
+ $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () {
+ let description = $messagedescription.val().trim();
+ let language = $messagelanguage.val();
+ let annid = parseInt($messageannid.val());
+ let voicetype = $messagevoicetype.val();
+ let messagedetail = "";
+ let messagetags = "";
+
+ // iterate messageselectedvariables children
+ $messageselectedvariables.children().each(function () {
+ let val = $(this).text().trim();
+ if (val.length > 0) {
+ if (val.startsWith('[') && val.endsWith(']')) {
+ // categories
+ messagetags += (messagetags.length > 0 ? " " : "") + val;
+ messagedetail += (messagedetail.length > 0 ? " " : "") + val;
+ } else {
+ // phrases
+ // find in soundbankdata by description with specified language and voicetype
+ let sb = soundbankdata
+ .filter(sb => sb.language.toLowerCase() === language.toLowerCase())
+ .filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase())
+ .find(sb => sb.Description.toLowerCase() === val.toLowerCase());
+ if (sb) {
+ messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description;
+ messagetags += (messagetags.length > 0 ? " " : "") + sb.tag;
+ }
+ }
+ }
+ });
+
+ if (description.length === 0) {
+ alert("Description cannot be empty");
+ return;
+ }
+ if (!language) {
+ alert("Language cannot be empty");
+ return;
+ }
+ if (isNaN(annid) || annid < 1 || annid > 100) {
+ alert("ANN_ID must be a number between 1 and 100");
+ return;
+ }
+ if (!voicetype) {
+ alert("Voice Type cannot be empty");
+ return;
+ }
+ if (messagedetail.length === 0 || messagetags.length === 0) {
+ alert("Message haven't been constructed, please add categories and phrases");
+ return;
+ }
+
+
+ let mb = {
+ Description: description,
+ Language: language,
+ ANN_ID: annid,
+ Voice_Type: voicetype,
+ Message_Detail: messagedetail,
+ Message_TAGS: messagetags
+ };
+ // send to server using fetchAPI
+ fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => {
+ reloadMessageBank(APIURL);
+ alert("Success add new messagebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error add new messagebank : " + errdata.message);
+ });
+
+ $modal.modal('hide');
+ });
+ // event on Click close button
+ $modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () {
+ $modal.modal('hide');
+ });
+ });
+ $btnRemove.click(() => {
+ if (selectedmessagerow) {
+ let cells = selectedmessagerow.find('td');
+ /** @type {MessageBank} */
+ let mb = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ language: cells.eq(2).text(),
+ aNN_ID: parseInt(cells.eq(3).text()),
+ voice_Type: cells.eq(4).text(),
+ message_Detail: cells.eq(5).text(),
+ message_TAGS: cells.eq(6).text()
+ }
+
+ if (confirm(`Are you sure to delete messagebank [${mb.index}] Description=${mb.description}? ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
+ fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => {
+ reloadMessageBank(APIURL);
+ alert("Success delete messagebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error delete messagebank : " + errdata.message);
+ });
+ }
+ }
+ });
+ $btnEdit.click(() => {
+ if (selectedmessagerow) {
+ let cells = selectedmessagerow.find('td');
+ /** @type {MessageBank} */
+ let mb = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ language: cells.eq(2).text(),
+ aNN_ID: parseInt(cells.eq(3).text()),
+ voice_Type: cells.eq(4).text(),
+ message_Detail: cells.eq(5).text(),
+ message_TAGS: cells.eq(6).text()
+ }
+ if (confirm(`Are you sure to edit messagebank [${mb.index}] Description=${mb.description} ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
+ $modal.modal('show');
+
+ clearMessageModal();
+ // Fill modal fields with selected messagebank data
+ $messageindex.val(mb.index).prop('disabled', true);
+ $messagedescription.val(mb.description);
+ // Fill messagelanguage options and select current
+ $messagelanguage.empty();
+ languages.forEach(lang => {
+ $messagelanguage.append(new Option(lang, lang));
+ });
+ $messagelanguage.val(mb.language);
+ // Fill messagevoicetype options and select current
+ $messagevoicetype.empty();
+ voiceTypes.forEach(vt => {
+ $messagevoicetype.append(new Option(vt, vt));
+ });
+ $messagevoicetype.val(mb.voice_Type);
+ // Set annid
+ $messageannid.val(mb.aNN_ID);
+ // Refill message available variables
+ refill_messageavailablevariables();
+ // Fill messageselectedvariables from message_Detail and message_TAGS
+ $messageselectedvariables.empty();
+ if (mb.message_Detail) {
+ mb.message_Detail.split(' ').forEach(val => {
+ $messageselectedvariables.append(ListItem(val));
+ });
+ }
+
+ // Save button event
+ $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () {
+ let description = $messagedescription.val().trim();
+ let language = $messagelanguage.val();
+ let annid = parseInt($messageannid.val());
+ let voicetype = $messagevoicetype.val();
+ let messagedetail = "";
+ let messagetags = "";
+ $messageselectedvariables.children().each(function () {
+ let val = $(this).text().trim();
+ if (val.length > 0) {
+ if (val.startsWith('[') && val.endsWith(']')) {
+ messagetags += (messagetags.length > 0 ? " " : "") + val;
+ messagedetail += (messagedetail.length > 0 ? " " : "") + val;
+ } else {
+ let sb = soundbankdata
+ .filter(sb => sb.language.toLowerCase() === language.toLowerCase())
+ .filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase())
+ .find(sb => sb.Description && sb.Description.toLowerCase() === val.toLowerCase());
+ if (sb) {
+ messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description;
+ messagetags += (messagetags.length > 0 ? " " : "") + sb.tag;
+ }
+ }
+ }
+ });
+ if (description.length === 0) {
+ alert("Description cannot be empty");
+ return;
+ }
+ if (!language) {
+ alert("Language cannot be empty");
+ return;
+ }
+ if (isNaN(annid) || annid < 1 || annid > 100) {
+ alert("ANN_ID must be a number between 1 and 100");
+ return;
+ }
+ if (!voicetype) {
+ alert("Voice Type cannot be empty");
+ return;
+ }
+ if (messagedetail.length === 0 || messagetags.length === 0) {
+ alert("Message haven't been constructed, please add categories and phrases");
+ return;
+ }
+ let mbUpdate = {
+ Description: description,
+ Language: language,
+ ANN_ID: annid,
+ Voice_Type: voicetype,
+ Message_Detail: messagedetail,
+ Message_TAGS: messagetags
+ };
+ fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => {
+ reloadMessageBank(APIURL);
+ alert("Success edit messagebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error edit messagebank : " + errdata.message);
+ });
+ $modal.modal('hide');
+ });
+ // Close button event
+ $modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () {
+ $modal.modal('hide');
+ });
+
+ }
+ }
+ });
+ $btnExport.click(() => {
+ DoExport(APIURL, "messagebank.xlsx", {});
+ });
+ $btnImport.click(() => {
+ DoImport(APIURL, (okdata) => {
+ reloadMessageBank(APIURL);
+ alert("Success import messagebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error importing messagebank from XLSX : " + errdata.message);
+ });
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/assets/js/schedulebank.js b/html/webpage/assets/js/schedulebank.js
new file mode 100644
index 0000000..419ffb6
--- /dev/null
+++ b/html/webpage/assets/js/schedulebank.js
@@ -0,0 +1,405 @@
+/**
+ * @typedef {Object} ScheduleBank
+ * @property {number} index
+ * @property {string} description
+ * @property {string} day
+ * @property {string} time
+ * @property {string} soundpath
+ * @property {number} repeat
+ * @property {boolean} enable
+ * @property {string} broadcastZones
+ * @property {string} language
+ */
+
+/** List of Schedulebank data loaded from server
+ * @type {ScheduleBank[]}
+ */
+let schedulebankdata = [];
+/**
+ * Currently selected schedulebank row in the table
+ * @type {JQuery|null}
+ */
+let selectedschedulerow = null;
+
+/**
+ * Fill schedulebank table body with values
+ * @param {ScheduleBank[]} vv values to fill
+ */
+function fill_schedulebanktablebody(vv) {
+ $('#schedulebanktablebody').empty();
+ if (!Array.isArray(vv) || vv.length === 0) return;
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.description} |
+ ${item.day} |
+ ${item.time} |
+ ${item.soundpath} |
+ ${item.repeat} |
+ ${item.enable} |
+ ${item.broadcastZones} |
+ ${item.language} |
+
`;
+ $('#schedulebanktablebody').append(row);
+ let $addedrow = $('#schedulebanktablebody tr:last');
+ $addedrow.click(function () {
+ if (selectedschedulerow) {
+ selectedschedulerow.find('td').css('background-color', '');
+ if (selectedschedulerow.is($(this))) {
+ selectedschedulerow = null;
+ $('#btnRemove').prop('disabled', true);
+ $('#btnEdit').prop('disabled', true);
+ return;
+ }
+ }
+ $addedrow.find('td').css('background-color', '#ffeeba');
+ selectedschedulerow = $addedrow;
+ $('#btnRemove').prop('disabled', false);
+ $('#btnEdit').prop('disabled', false);
+ });
+ });
+ $('#tablesize').text("Table Size: " + vv.length);
+}
+
+/**
+ * Reload timer bank from server
+ * @param {string} APIURL API URL endpoint, default "ScheduleBank/"
+ */
+function reloadTimerBank(APIURL = "ScheduleBank/") {
+ schedulebankdata = [];
+ fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
+ if (Array.isArray(okdata)) {
+ schedulebankdata = okdata;
+ selectedschedulerow = null;
+ fill_schedulebanktablebody(schedulebankdata);
+ }
+ }, (errdata) => {
+ alert("Error loading schedulebank : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log("schedulebank.js loaded successfully");
+ $('#schedulebanktablebody').empty();
+ selectedschedulerow = null;
+ let $btnClear = $('#btnClear');
+ let $btnAdd = $('#btnAdd');
+ let $btnEdit = $('#btnEdit');
+ let $btnRemove = $('#btnRemove');
+ let $btnExport = $('#btnExport');
+ let $btnImport = $('#btnImport');
+ $btnEdit.prop('disabled', true);
+ $btnRemove.prop('disabled', true);
+ let APIURL = "ScheduleBank/";
+
+ let $schedulemodal = $('#schedulemodal');
+ // text input
+ let $scheduleid = $schedulemodal.find('#scheduleid');
+ // text input
+ let $scheduledescription = $schedulemodal.find('#scheduledescription');
+ // number input 0-23
+ let $schedulehour = $schedulemodal.find('#schedulehour');
+ // number input 0-59
+ let $scheduleminute = $schedulemodal.find('#scheduleminute');
+ // text input
+ let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath');
+ // number input 0-5
+ let $schedulerepeat = $schedulemodal.find('#schedulerepeat');
+ // checkbox
+ let $scheduleenable = $schedulemodal.find('#scheduleenable');
+ // div for list of checkboxes
+ let $schedulezones = $schedulemodal.find('#schedulezones');
+ // radio button for everyday
+ let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday');
+ // radio button for weekdays
+ let $schedulesunday = $schedulemodal.find('#schedulesunday');
+ let $schedulemonday = $schedulemodal.find('#schedulemonday');
+ let $scheduletuesday = $schedulemodal.find('#scheduletuesday');
+ let $schedulewednesday = $schedulemodal.find('#schedulewednesday');
+ let $schedulethursday = $schedulemodal.find('#schedulethursday');
+ let $schedulefriday = $schedulemodal.find('#schedulefriday');
+ let $schedulesaturday = $schedulemodal.find('#schedulesaturday');
+ // radio button for specific date
+ let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
+ // date input
+ let $scheduledate = $schedulemodal.find('#scheduledate');
+
+ $schedulespecialdate.on('change', function () {
+ if ($(this).is(':checked')) {
+ $scheduledate.prop('disabled', false);
+ } else {
+ $scheduledate.prop('disabled', true);
+ }
+ });
+
+ function clearScheduleModal() {
+ $scheduleid.prop('disabled', true).val('');
+ $scheduledescription.val('');
+ $schedulehour.val('0');
+ $scheduleminute.val('0');
+ $schedulesoundpath.val('');
+ $schedulerepeat.val('0');
+ $scheduleenable.prop('checked', true);
+ $scheduleeveryday.prop('checked', false);
+ $schedulesunday.prop('checked', false);
+ $schedulemonday.prop('checked', false);
+ $scheduletuesday.prop('checked', false);
+ $schedulewednesday.prop('checked', false);
+ $schedulethursday.prop('checked', false);
+ $schedulefriday.prop('checked', false);
+ $schedulesaturday.prop('checked', false);
+ $schedulespecialdate.prop('checked', false);
+ $scheduledate.prop('disabled', true).val('');
+
+
+ }
+
+ let $findschedule = $('#findschedule');
+ $findschedule.on('input', function () {
+ let searchTerm = $findschedule.val().toLowerCase();
+ if (searchTerm.length > 0) {
+ selectedtimerow = null;
+ let filtered = schedulebankdata.filter(item =>
+ item.description.toLowerCase().includes(searchTerm)
+ || item.soundpath.toLowerCase().includes(searchTerm)
+ || item.broadcastZones.toLowerCase().includes(searchTerm));
+ fill_schedulebanktablebody(filtered);
+ } else {
+ selectedtimerow = null;
+ fill_schedulebanktablebody(schedulebankdata);
+ }
+ });
+
+
+ reloadTimerBank(APIURL);
+ $btnClear.click(() => {
+ DoClear(APIURL, "Timerbank", (okdata) => {
+ reloadTimerBank(APIURL);
+ alert("Success clear schedulebank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error clear schedulebank : " + errdata.message);
+ });
+
+ });
+ $btnAdd.click(() => {
+ $schedulemodal.modal('show');
+ clearScheduleModal();
+
+ $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
+ $schedulemodal.modal('hide');
+ });
+ $schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () {
+ // Gather form values
+ const Description = $scheduledescription.val();
+ const Soundpath = $schedulesoundpath.val();
+ const Repeat = parseInt($schedulerepeat.val(), 10);
+ const Enable = $scheduleenable.is(':checked');
+ // Collect selected days
+ let Day = "";
+ if ($scheduleeveryday.is(':checked')) {
+ Day = "Everyday";
+ } else if ($schedulespecialdate.is(':checked')) {
+ Day = $scheduledate.val();
+ } else {
+
+ if ($schedulesunday.is(':checked')) Day = "Sunday";
+ if ($schedulemonday.is(':checked')) Day = "Monday";
+ if ($scheduletuesday.is(':checked')) Day = "Tuesday";
+ if ($schedulewednesday.is(':checked')) Day = "Wednesday";
+ if ($schedulethursday.is(':checked')) Day = "Thursday";
+ if ($schedulefriday.is(':checked')) Day = "Friday";
+ if ($schedulesaturday.is(':checked')) Day = "Saturday";
+ }
+ // Broadcast zones (assuming comma-separated string)
+ const BroadcastZones = $schedulezones.val();
+
+ // Validate required fields
+ if (!Description || !Soundpath || Day === "") {
+ alert("Description, sound path, and day are required.");
+ return;
+ }
+
+ // Format time as HH:mm
+ const hour = parseInt($schedulehour.val(), 10);
+ const minute = parseInt($scheduleminute.val(), 10);
+ const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
+
+ // Prepare object
+ const scheduleObj = {
+ Description,
+ Day,
+ Time,
+ Soundpath,
+ Repeat,
+ Enable,
+ BroadcastZones
+ };
+
+ fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => {
+ alert("Success add schedule: " + okdata.message);
+ reloadTimerBank(APIURL);
+ }, (errdata) => {
+ alert("Error add schedule: " + errdata.message);
+ });
+
+ $schedulemodal.modal('hide');
+ });
+ });
+ $btnRemove.click(() => {
+ if (selectedtimerow) {
+ let cells = selectedtimerow.find('td');
+ /** @type {ScheduleBank} */
+ let sr = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ day: cells.eq(2).text(),
+ time: cells.eq(3).text(),
+ soundpath: cells.eq(4).text(),
+ repeat: cells.eq(5).text(),
+ enable: cells.eq(6).text(),
+ broadcastZones: cells.eq(7).text(),
+ language: cells.eq(8).text()
+ }
+ if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) {
+ fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => {
+ reloadTimerBank(APIURL);
+ alert("Success delete schedule : " + okdata.message);
+ }, (errdata) => {
+ alert("Error delete schedule : " + errdata.message);
+ });
+ }
+ }
+ });
+ $btnEdit.click(() => {
+ if (selectedtimerow) {
+ let cells = selectedtimerow.find('td');
+ /** @type {ScheduleBank} */
+ let sr = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ day: cells.eq(2).text(),
+ time: cells.eq(3).text(),
+ soundpath: cells.eq(4).text(),
+ repeat: cells.eq(5).text(),
+ enable: cells.eq(6).text(),
+ broadcastZones: cells.eq(7).text(),
+ language: cells.eq(8).text()
+ }
+ if (confirm(`Are you sure to edit schedule [${sr.index}] Description=${sr.description}?`)) {
+ $schedulemodal.modal('show');
+ // fill the form with existing data
+ $scheduleid.val(sr.index);
+ $scheduledescription.val(sr.description);
+ let [hour, minute] = sr.time.split(':').map(num => parseInt(num, 10));
+ $schedulehour.val(hour.toString());
+ $scheduleminute.val(minute.toString());
+ $schedulesoundpath.val(sr.soundpath);
+ $schedulerepeat.val(sr.repeat.toString());
+ $scheduleenable.prop('checked', sr.enable.toLowerCase() === 'true');
+ switch (sr.day) {
+ case 'Everyday':
+ $scheduleeveryday.click();
+ break;
+ case 'Sunday':
+ $schedulesunday.click();
+ break;
+ case 'Monday':
+ $schedulemonday.click();
+ break;
+ case 'Tuesday':
+ $scheduletuesday.click();
+ break;
+ case 'Wednesday':
+ $schedulewednesday.click();
+ break;
+ case 'Thursday':
+ $schedulethursday.click();
+ break;
+ case 'Friday':
+ $schedulefriday.click();
+ break;
+ case 'Saturday':
+ $schedulesaturday.click();
+ break;
+ default:
+ // check if the day is in format dd/mm/yyyy
+ // and set the special date radio button and date input
+ if (/^\d{2}\/\d{2}\/\d{4}$/.test(sr.day)) {
+ $schedulespecialdate.click();
+ $scheduledate.val(sr.day);
+ }
+ }
+
+ $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
+ $schedulemodal.modal('hide');
+ });
+ $schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () {
+ // Gather form values
+ const Description = $scheduledescription.val();
+ const Soundpath = $schedulesoundpath.val();
+ const Repeat = parseInt($schedulerepeat.val(), 10);
+ const Enable = $scheduleenable.is(':checked');
+ // Collect selected days
+ let Day = "";
+ if ($scheduleeveryday.is(':checked')) {
+ Day = "Everyday";
+ } else if ($schedulespecialdate.is(':checked')) {
+ Day = $scheduledate.val();
+ } else {
+ if ($schedulesunday.is(':checked')) Day = "Sunday";
+ if ($schedulemonday.is(':checked')) Day = "Monday";
+ if ($scheduletuesday.is(':checked')) Day = "Tuesday";
+ if ($schedulewednesday.is(':checked')) Day = "Wednesday";
+ if ($schedulethursday.is(':checked')) Day = "Thursday";
+ if ($schedulefriday.is(':checked')) Day = "Friday";
+ if ($schedulesaturday.is(':checked')) Day = "Saturday";
+ }
+ // Broadcast zones (assuming comma-separated string)
+ const BroadcastZones = $schedulezones.val();
+
+ // Validate required fields
+ if (!Description || !Soundpath || Day === "") {
+ alert("Description, sound path, and day are required.");
+ return;
+ }
+
+ // Format time as HH:mm
+ const hour = parseInt($schedulehour.val(), 10);
+ const minute = parseInt($scheduleminute.val(), 10);
+ const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
+
+ // Prepare object
+ const scheduleObj = {
+ Description,
+ Day,
+ Time,
+ Soundpath,
+ Repeat,
+ Enable,
+ BroadcastZones
+ };
+
+ fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => {
+ alert("Success edit schedule: " + okdata.message);
+ reloadTimerBank(APIURL);
+ }, (errdata) => {
+ alert("Error edit schedule: " + errdata.message);
+ });
+
+ $schedulemodal.modal('hide');
+ });
+ }
+ }
+ });
+ $btnExport.click(() => {
+ DoExport(APIURL, "schedulebank.xlsx", {});
+ });
+ $btnImport.click(() => {
+ DoImport(APIURL, (okdata) => {
+ reloadTimerBank(APIURL);
+ alert("Success import schedulebank from XLSX : " + okdata.message);
+ }, (errdata) => {
+ alert("Error importing schedulebank from XLSX : " + errdata.message);
+ });
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/assets/js/script.js b/html/webpage/assets/js/script.js
index 7d45816..8f99afa 100644
--- a/html/webpage/assets/js/script.js
+++ b/html/webpage/assets/js/script.js
@@ -1,124 +1,3 @@
-/**
- * @typedef {Object} SoundBank
- * @property {number} index
- * @property {string} description
- * @property {string} tag
- * @property {string} category
- * @property {string} language
- * @property {string} voiceType
- * @property {string} path
- */
-
-/**
- * @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
- */
-
-/**
- * @typedef {Object} LanguageBank
- * @property {number} index
- * @property {string} tag
- * @property {string} language
- * @
- */
-
-/**
- * @typedef {Object} ScheduleBank
- * @property {number} index
- * @property {string} description
- * @property {string} day
- * @property {string} time
- * @property {string} soundpath
- * @property {number} repeat
- * @property {boolean} enable
- * @property {string} broadcastZones
- * @property {string} language
- */
-
-/**
- * @typedef {Object} Log
- * @property {number} index
- * @property {string} datenya
- * @property {string} timenya
- * @property {string} machine
- * @property {string} description
- */
-
-/**
- * @typedef {Object} Select2item
- * @property {number} id
- * @property {string} text
- */
-
-/**
- * @typedef {Object} BroadcastZone
- * @property {number} index
- * @property {string} description
- * @property {String} SoundChannel
- * @property {String} Box
- * @property {String} Relay
- */
-
-/**
- * List of Soundbank data loaded from server
- * @type {SoundBank[]}
- */
-let soundbankdata = [];
-/**
- * Currently selected soundbank row in the table
- * @type {JQuery|null}
- */
-let selectedsoundrow = null;
-/**
- * List of Messagebank data loaded from server
- * @type {MessageBank[]}
- */
-let messagebankdata = [];
-/**
- * Currently selected messagebank row in the table
- * @type {JQuery|null}
- */
-let selectedmessagerow = null;
-/** List of Languagebank data loaded from server
- * @type {LanguageBank[]}
- */
-let languagebankdata = [];
-/**
- * Currently selected languagebank row in the table
- * @type {JQuery|null}
- */
-let selectedlanguagerow = null;
-/** List of Schedulebank data loaded from server
- * @type {ScheduleBank[]}
- */
-let schedulebankdata = [];
-/**
- * Currently selected schedulebank row in the table
- * @type {JQuery|null}
- */
-let selectedschedulerow = null;
-/** List of Log data loaded from server
- * @type {Log[]}
- */
-let logdata = [];
-/**
- * List of sound files in the soundbank directory, that ends with .wav or .mp3
- * @type {string[]}
- */
-let soundbankfiles = [];
-/**
- * Select2 data source
- * See https://select2.org/data-sources/formats
- * @type {Select2item[]}
- */
-let select2data = [];
-
/**
* List of voice types available
* @type {string[]}
@@ -141,195 +20,16 @@ let languages = [];
*/
let scheduledays = []
-/**
- * List of broadcast zones available
- * @type {BroadcastZone[]}
- */
-let BroadcastZoneList = [];
-
/**
* 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}
*/
-function ListItem(text, className=""){
+function ListItem(text, className = "") {
return $('- ').addClass(className).text(text);
}
-/**
- * Fill soundbank table body with values
- * @param {SoundBank[]} vv values to fill
- */
-function fill_soundbanktablebody(vv) {
- $('#soundbanktablebody').empty();
- if (!Array.isArray(vv) || vv.length === 0) return;
- vv.forEach(item => {
- const row = `
- | ${item.index} |
- ${item.description} |
- ${item.tag} |
- ${item.category} |
- ${item.language} |
- ${item.voiceType} |
- ${item.path} |
-
`;
- $('#soundbanktablebody').append(row);
- let $addedrow = $('#soundbanktablebody tr:last');
- $addedrow.on('click', function () {
- if (selectedsoundrow) {
- selectedsoundrow.find('td').css('background-color', '');
- if (selectedsoundrow.is($(this))) {
- selectedsoundrow = null;
- $('#btnRemove').prop('disabled', true);
- $('#btnEdit').prop('disabled', true);
- return;
- }
- }
- $(this).find('td').css('background-color', '#ffeeba');
- selectedsoundrow = $(this);
- $('#btnRemove').prop('disabled', false);
- $('#btnEdit').prop('disabled', false);
- });
- });
-
-
- $('#tablesize').text("Table Size: " + vv.length);
-}
-
-/**
- * Fill messagebank table body with values
-* @param {MessageBank[]} vv values to fill
-*/
-function fill_messagebanktablebody(vv) {
- $('#messagebanktablebody').empty();
- if (!Array.isArray(vv) || vv.length === 0) return;
- vv.forEach(item => {
- const row = `
- | ${item.index} |
- ${item.description} |
- ${item.language} |
- ${item.aNN_ID} |
- ${item.voice_Type} |
- ${item.message_Detail} |
- ${item.message_TAGS} |
-
`;
- $('#messagebanktablebody').append(row);
- let $addedrow = $('#messagebanktablebody tr:last');
- $addedrow.click(function () {
- if (selectedmessagerow) {
- selectedmessagerow.find('td').css('background-color', '');
- if (selectedmessagerow.is($(this))) {
- selectedmessagerow = null;
- $('#btnRemove').prop('disabled', true);
- $('#btnEdit').prop('disabled', true);
- return;
- }
- }
- $addedrow.find('td').css('background-color', '#ffeeba');
- selectedmessagerow = $addedrow;
- $('#btnRemove').prop('disabled', false);
- $('#btnEdit').prop('disabled', false);
- });
- });
-
- $('#tablesize').text("Table Size: " + vv.length);
-}
-
-/**
- * Fill languagebank table body with values
- * @param {LanguageBank[]} vv values to fill
- */
-function fill_languagebanktablebody(vv) {
- $('#languagebanktablebody').empty();
- if (!Array.isArray(vv) || vv.length === 0) return;
- vv.forEach(item => {
- const row = `
- | ${item.index} |
- ${item.tag} |
- ${item.language} |
-
`;
- $('#languagebanktablebody').append(row);
- let $addedrow = $('#languagebanktablebody tr:last');
- $addedrow.click(function () {
- if (selectedlanguagerow) {
- selectedlanguagerow.find('td').css('background-color', '');
- if (selectedlanguagerow.is($(this))) {
- selectedlanguagerow = null;
- $('#btnRemove').prop('disabled', true);
- $('#btnEdit').prop('disabled', true);
- return;
- }
- }
- $addedrow.find('td').css('background-color', '#ffeeba');
- selectedlanguagerow = $addedrow;
- $('#btnRemove').prop('disabled', false);
- $('#btnEdit').prop('disabled', false);
- });
- });
- $('#tablesize').text("Table Size: " + vv.length);
-}
-
-/**
- * Fill schedulebank table body with values
- * @param {ScheduleBank[]} vv values to fill
- */
-function fill_schedulebanktablebody(vv) {
- $('#schedulebanktablebody').empty();
- if (!Array.isArray(vv) || vv.length === 0) return;
- vv.forEach(item => {
- const row = `
- | ${item.index} |
- ${item.description} |
- ${item.day} |
- ${item.time} |
- ${item.soundpath} |
- ${item.repeat} |
- ${item.enable} |
- ${item.broadcastZones} |
- ${item.language} |
-
`;
- $('#schedulebanktablebody').append(row);
- let $addedrow = $('#schedulebanktablebody tr:last');
- $addedrow.click(function () {
- if (selectedschedulerow) {
- selectedschedulerow.find('td').css('background-color', '');
- if (selectedschedulerow.is($(this))) {
- selectedschedulerow = null;
- $('#btnRemove').prop('disabled', true);
- $('#btnEdit').prop('disabled', true);
- return;
- }
- }
- $addedrow.find('td').css('background-color', '#ffeeba');
- selectedschedulerow = $addedrow;
- $('#btnRemove').prop('disabled', false);
- $('#btnEdit').prop('disabled', false);
- });
- });
- $('#tablesize').text("Table Size: " + vv.length);
-}
-
-/**
- * Fill log table body with values
- * @param {Log[]} vv values to fill
- */
-function fill_logtablebody(vv) {
- $('#logtablebody').empty();
- if (!Array.isArray(vv) || vv.length === 0) return;
- vv.forEach(item => {
- const row = `
- | ${item.index} |
- ${item.datenya} |
- ${item.timenya} |
- ${item.machine} |
- ${item.description} |
-
`;
- $('#logtablebody').append(row);
- });
- $('#tablesize').text("Table Size: " + vv.length);
-}
-
/**
* WebSocket connection
* @type {WebSocket}
@@ -368,151 +68,45 @@ function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) {
options.headers['Content-Type'] = 'application/json';
}
}
- //console.log("About to fetch from " + url + " with options", options);
fetch(url, options)
.then(response => {
- //console.log("fetchAPI: received response", response);
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json();
})
.then(data => {
- //console.log("fetchAPI: received data", data);
cbOK(data);
})
.catch(error => {
- // console.error('There was a problem with the fetch operation:', error);
cbError(error);
});
}
/**
- * Reload sound bank from server
- * @param {String} APIURL API URL endpoint, default "SoundBank/"
+ * 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 reloadSoundBank(APIURL = "SoundBank/") {
- soundbankdata = [];
- fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
- if (Array.isArray(okdata)) {
- soundbankdata = okdata;
- selectedsoundrow = null;
- fill_soundbanktablebody(soundbankdata);
- }
- }, (errdata) => {
- alert("Error loading soundbank : " + errdata.message);
- });
+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 message bank from server
-* @param {string} APIURL API URL endpoint, default "MessageBank/"
-*/
-function reloadMessageBank(APIURL = "MessageBank/") {
- messagebankdata = [];
- fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
- if (Array.isArray(okdata)) {
- messagebankdata = okdata;
- selectedmessagerow = null;
- fill_messagebanktablebody(messagebankdata);
- }
- }, (errdata) => {
- alert("Error loading messagebank : " + errdata.message);
- });
-}
-
-/**
- * Reload language bank from server
- * @param {string} APIURL API URL endpoint, default "LanguageLink/"
- */
-function reloadLanguageBank(APIURL = "LanguageLink/") {
- languagebankdata = [];
- fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
- if (Array.isArray(okdata)) {
- languagebankdata = okdata;
- selectedlanguagerow = null;
- fill_languagebanktablebody(languagebankdata);
- }
- }, (errdata) => {
- alert("Error loading languagebank : " + errdata.message);
- });
-}
-
-/**
- * Reload timer bank from server
- * @param {string} APIURL API URL endpoint, default "ScheduleBank/"
- */
-function reloadTimerBank(APIURL = "ScheduleBank/") {
- schedulebankdata = [];
- fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
- if (Array.isArray(okdata)) {
- schedulebankdata = okdata;
- selectedschedulerow = null;
- fill_schedulebanktablebody(schedulebankdata);
- }
- }, (errdata) => {
- alert("Error loading schedulebank : " + errdata.message);
- });
-}
-
-/**
- * Reload logs from server with date and filter
- * @param {String} APIURL API URL endpoint , default "Log/"
- * @param {String} date date in format dd-mm-yyyy
- * @param {String} filter log filter text
- */
-function reloadLogs(APIURL = "Log/", date, filter) {
- const params = new URLSearchParams({
- date: date,
- filter: filter
- })
- fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => {
- //console.log("Logs data received", okdata);
- if (Array.isArray(okdata)) {
- logdata = okdata;
- fill_logtablebody(okdata);
- }
- }, (errdata) => {
- alert("Error loading logs : " + errdata.message);
- });
-}
-
-/**
- * Reload soundbank files from server
- * @param {String} APIURL API URL endpoint (default "SoundBank/")
- */
-function reloadSoundbankFiles(APIURL = "SoundBank/") {
- soundbankfiles = [];
- fetchAPI(APIURL + "ListFiles", "GET", {}, null, (okdata) => {
- // okdata is a string contains elements separated by semicolon ;
- if (Array.isArray(okdata)) {
- soundbankfiles = okdata.filter(item => item.trim().length > 0);
- // refill select2data
- select2data = soundbankfiles.map((item, index) => ({ id: index + 1, text: item }));
- } else console.log("reloadSoundbankFiles: okdata is not array");
- }, (errdata) => {
- alert("Error loading soundbank files : " + errdata.message);
- });
-}
-
-/**
- * Reload broadcast zones from server
- * @param {String} APIURL API URL endpoint (default "BroadcastZones/")
- */
-function reloadBroadcastZones(APIURL = "BroadcastZones/") {
- BroadcastZoneList = [];
- fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
- if (Array.isArray(okdata)) {
- BroadcastZoneList = okdata;
- fill_broadcastzonetablebody(BroadcastZoneList);
- } else console.log("reloadBroadcastZones: okdata is not array");
- }, (errdata) => {
- alert("Error loading broadcast zones : " + errdata.message);
- });
-}
-
-
-
/**
* Reload voice types from server
*/
@@ -672,25 +266,59 @@ function DoImport(APIURL, cbOK, cbError) {
fileInput.remove();
}
+let $onlineindicator = null;
+let $cpustatus = null;
+let $ramstatus = null;
+let $diskstatus = null;
+let $networkstatus = null;
+let $datetimetext = null;
+let greencircle = null;
+let redcircle = null;
/**
* App entry point
*/
$(document).ready(function () {
document.title = "Automatic Announcement System"
- $('#onlineindicator').attr('src', '/assets/img/red_circle.png');
+ fetchImg('green_circle.png', (url) => { greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); });
+ fetchImg('red_circle.png', (url) => { 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;
+ }
+ $onlineindicator = $('#onlineindicator');
+ $cpustatus = $('#cpustatus');
+ $ramstatus = $('#ramstatus');
+ $diskstatus = $('#diskstatus');
+ $networkstatus = $('#networkstatus');
+ $datetimetext = $('#datetimetext');
+
+
+ // reset status indicators
+ function resetStatusIndicators() {
+ $onlineindicator.attr('src', 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();
- reloadSoundbankFiles();
+
- // Initialize WebSocket connection
- ws = new WebSocket(window.location.pathname + '/ws');
+ // Initialize WebSocket connection
+ ws = new WebSocket(wsURL);
ws.onopen = () => {
console.log('WebSocket connection established');
- $('#onlineindicator').attr('src', '/assets/img/green_circle.png');
+ $onlineindicator.attr('src', greencircle);
};
ws.onmessage = (event) => {
let rep = JSON.parse(event.data);
@@ -699,19 +327,19 @@ $(document).ready(function () {
if (cmd && cmd.length > 0) {
switch (cmd) {
case "getCPUStatus":
- $('#cpustatus').text("CPU Usage: " + data)
+ $cpustatus.text("CPU : " + data)
break;
case "getMemoryStatus":
- $('#ramstatus').text("Memory Usage: " + data)
+ $ramstatus.text("RAM : " + data)
break;
case "getDiskStatus":
- $('#diskstatus').text("Disk Usage: " + data)
+ $diskstatus.text("Disk : " + data)
break;
case "getNetworkStatus":
- $('#networkstatus').text("Network Usage: " + data)
+ $networkstatus.text("Network : " + data)
break;
case "getSystemTime":
- $('#datetimetext').text(data)
+ $datetimetext.text(data)
break;
}
@@ -719,8 +347,10 @@ $(document).ready(function () {
};
ws.onclose = () => {
console.log('WebSocket connection closed');
- $('#onlineindicator').attr('src', '/assets/img/red_circle.png');
+ resetStatusIndicators();
+
};
+
// ws.onerror = (error) => {
// console.error('WebSocket error:', error);
// };
@@ -751,159 +381,8 @@ $(document).ready(function () {
$('#content').load('soundbank.html', function (response, status, xhr) {
if (status === "success") {
console.log("Soundbank content loaded successfully");
- // initialize first state of buttons and text input
- $('#soundbanktablebody').empty();
- selectedsoundrow = null;
- let $btnClear = $('#btnClear');
- let $btnAdd = $('#btnAdd');
- let $btnRemove = $('#btnRemove');
- let $btnEdit = $('#btnEdit');
- let $btnExport = $('#btnExport');
- let $btnImport = $('#btnImport');
- $btnRemove.prop('disabled', true);
- $btnEdit.prop('disabled', true);
- let APIURL = "SoundBank/";
- let $modal = $('#soundbankmodal');
- let $modalindex = $modal.find('#modalindex');
- let $modaldescription = $modal.find('#modaldescription');
- let $modaltag = $modal.find('#modaltag');
- let $modalcategory = $modal.find('#modalcategory');
- let $modallanguage = $modal.find('#modallanguage');
- let $modalvoicetype = $modal.find('#modalvoicetype');
- let $modalpath = $modal.find('#modalpath');
-
- /**
- * Clear soundbank modal inputs
- */
- function clearSoundbankModal() {
- $modalindex.val('').prop('disabled', true);
- $modaldescription.val('');
- $modaltag.val('');
- // fill modalcategory options from categories[]
- $modalcategory.empty();
- categories.forEach(cat => {
- $modalcategory.append(new Option(cat, cat));
- });
- $modalcategory.val(null);
- // fill modallanguage options from languages[]
- $modallanguage.empty();
- languages.forEach(lang => {
- $modallanguage.append(new Option(lang, lang));
- });
- $modallanguage.val(null);
- // fill modalvoicetype options from voiceTypes[]
- $modalvoicetype.empty();
- voiceTypes.forEach(vt => {
- $modalvoicetype.append(new Option(vt, vt));
- });
- $modalvoicetype.val(null);
- // fill modalpath options from soundbankfiles[]
- // TODO read https://jeesite.com/front/jquery-select2/4.0/index.htm
- console.log("select2data has " + select2data.length + " items");
- $('#modalpath').select2({
- data: select2data
- })
- }
-
- reloadSoundBank(APIURL);
- $('#findsoundbank').on('input', function () {
- let searchTerm = $(this).val().trim().toLowerCase();
- if (searchTerm.length > 0) {
- selectedsoundrow = null;
- let filtered = soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm));
- fill_soundbanktablebody(filtered);
- } else {
- selectedsoundrow = null;
- fill_soundbanktablebody(soundbankdata);
- }
- });
- $btnClear.click(() => {
- DoClear(APIURL, "Soundbank", (okdata) => {
- reloadSoundBank(APIURL);
- alert("Success clear soundbank : " + okdata.message);
- }, (errdata) => {
- alert("Error clear soundbank : " + errdata.message);
- });
-
- });
- $btnAdd.click(() => {
-
- $modal.modal('show');
- clearSoundbankModal();
- // event on Click save button
- $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
- //TODO Add soundbank save process here
- $modal.modal('hide');
- });
- // event on Click close button
- $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
- $modal.modal('hide');
- });
- });
- $btnRemove.click(() => {
- if (selectedsoundrow) {
- let cells = selectedsoundrow.find('td');
- /** @type {SoundBank} */
- let sb = {
- index: cells.eq(0).text(),
- description: cells.eq(1).text(),
- tag: cells.eq(2).text(),
- category: cells.eq(3).text(),
- language: cells.eq(4).text(),
- voiceType: cells.eq(5).text(),
- path: cells.eq(6).text()
- }
- if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
- fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => {
- reloadSoundBank(APIURL);
- alert("Success delete soundbank : " + okdata.message);
- }, (errdata) => {
- alert("Error delete soundbank : " + errdata.message);
- });
- }
- }
- });
- $btnEdit.click(() => {
- if (selectedsoundrow) {
- let cells = selectedsoundrow.find('td');
- /** @type {SoundBank} */
- let sb = {
- index: cells.eq(0).text(),
- description: cells.eq(1).text(),
- tag: cells.eq(2).text(),
- category: cells.eq(3).text(),
- language: cells.eq(4).text(),
- voiceType: cells.eq(5).text(),
- path: cells.eq(6).text()
- }
- if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
- $modal.modal('show');
- $modal.off('hidden.bs.modal').on('hidden.bs.modal', function () {
-
- // event on Click save button
- $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
- //TODO Add soundbank save process here
- $modal.modal('hide');
- });
- // event on Click close button
- $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
- $modal.modal('hide');
- });
- });
- }
- }
- });
- $btnExport.click(() => {
- DoExport(APIURL, "soundbank.xlsx", {});
- });
- $btnImport.click(() => {
- DoImport(APIURL, (okdata) => {
- reloadSoundBank(APIURL);
- alert("Success import soundbank : " + okdata.message);
- }, (errdata) => {
- alert("Error importing soundbank from XLSX : " + errdata.message);
- });
- });
+ // pindah soundbank.js
+
} else {
console.error("Error loading soundbank content : ", xhr.status, xhr.statusText);
}
@@ -915,378 +394,8 @@ $(document).ready(function () {
$('#content').load('messagebank.html', function (response, status, xhr) {
if (status === "success") {
console.log("Messagebank content loaded successfully");
- // initialize first state of buttons and text input
- $('#messagebanktablebody').empty();
- selectedmessagerow = null;
- let $btnClear = $('#btnClear');
- let $btnAdd = $('#btnAdd');
- let $btnRemove = $('#btnRemove');
- let $btnEdit = $('#btnEdit');
- let $btnExport = $('#btnExport');
- let $btnImport = $('#btnImport');
- $btnRemove.prop('disabled', true);
- $btnEdit.prop('disabled', true);
- let APIURL = "MessageBank/";
- let $findmessage = $('#findmessage');
-
- // modal for add / edit messagebank
- let $modal = $('#messagebankmodal');
- // text input, disabled by default
- let $messageindex = $modal.find('#messageindex');
- // text input
- let $messagedescription = $modal.find('#messagedescription');
- // select input, options loaded from languages[]
- let $messagelanguage = $modal.find('#messagelanguage');
- // number input from 1 to 100
- let $messageannid = $modal.find('#messageannid');
- // select input, options loaded from voiceTypes[]
- let $messagevoicetype = $modal.find('#messagevoicetype');
- // list of available categories and phrases
- let $messageavailablevariables = $modal.find('#messageavailablevariables');
- // list of selected categories and phrases
- let $messageselectedvariables = $modal.find('#messageselectedvariables');
- // for clearing messageselectedvariables
- let $btnclearlist = $modal.find('#btnclearlist');
- // for removing selected item from messageselectedvariables
- let $btnremovefromlist = $modal.find('#btnremovefromlist');
- // for adding selected item from messageavailablevariables to messageselectedvariables
- let $btnaddtolist = $modal.find('#btnaddtolist');
-
- /**
- * Refill messageavailablevariables options from categories[]
- * and soundbankdata with category "Phrase" if messagelanguage and messagevoicetype are selected
- */
- function refill_messageavailablevariables() {
- $messageavailablevariables.empty();
- categories.forEach(cat => {
- $messageavailablevariables.append(ListItem(`{${cat}}`));
- });
- if ($messagelanguage.val() && $messagevoicetype.val()) {
- soundbankdata
- .filter(sb => sb.language.toLowerCase() === $messagelanguage.val().toLowerCase())
- .filter(sb => sb.voiceType.toLowerCase() === $messagevoicetype.val().toLowerCase())
- .filter(sb => sb.category.toLowerCase() === "phrase")
- .forEach(sb => {
- $messageavailablevariables.append(ListItem(`[${sb.Description}]`));
- });
- }
-
- }
-
- /**
- * Clear message modal to default state
- */
- function clearMessageModal() {
- $messageindex.val('').prop('disabled', true);
- $messagedescription.val('');
- // fill messagelanguage options from languages[]
- $messagelanguage.empty();
- languages.forEach(lang => {
- $messagelanguage.append(new Option(lang, lang));
- });
- $messagelanguage.val(null);
- $messagelanguage.on('change', function () {
- refill_messageavailablevariables();
- });
- // set default annid to 1
- $messageannid.val(1);
- // fill messagevoicetype options from voiceTypes[]
- $messagevoicetype.empty();
- voiceTypes.forEach(vt => {
- $messagevoicetype.append(new Option(vt, vt));
- });
- $messagevoicetype.val(null);
- $messagevoicetype.on('change', function () {
- refill_messageavailablevariables();
- });
-
- refill_messageavailablevariables();
- $messageselectedvariables.empty();
-
- // event on btnclearlist
- $btnclearlist.off('click').on('click', function () {
- if ($messageselectedvariables.children().length > 0) {
- if (confirm("Are you sure want to clear selected variables list?")) {
- $messageselectedvariables.empty();
- }
- }
- });
-
- // event on btnremovefromlist
- $btnremovefromlist.off('click').on('click', function () {
- let $selected = $messageselectedvariables.find('option:selected');
- if ($selected.length > 0) {
- $selected.remove();
- }
-
- });
-
- // event on btnaddtolist
- $btnaddtolist.off('click').on('click', function () {
- let $selected = $messageavailablevariables.find('option:selected');
- if ($selected.length > 0) {
- $selected.each(function () {
- $messageselectedvariables.append($(this).clone());
- });
- }
- });
- }
-
-
- $findmessage.on('input', function () {
- let searchTerm = $findmessage.val().toLowerCase();
- if (searchTerm.length > 0) {
- selectedmessagerow = null;
- let filtered = messagebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.message_Detail.toLowerCase().includes(searchTerm) || item.message_TAGS.toLowerCase().includes(searchTerm));
- fill_messagebanktablebody(filtered);
- } else {
- selectedmessagerow = null;
- fill_messagebanktablebody(messagebankdata);
- }
- });
-
-
- reloadMessageBank(APIURL);
- $btnClear.click(() => {
- DoClear(APIURL, "Messagebank", (okdata) => {
- reloadMessageBank(APIURL);
- alert("Success clear messagebank : " + okdata.message);
- }, (errdata) => {
- alert("Error clear messagebank : " + errdata.message);
- });
-
- });
- $btnAdd.click(() => {
-
- $modal.modal('show');
- clearMessageModal();
-
- // event on Click save button
- $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () {
- let description = $messagedescription.val().trim();
- let language = $messagelanguage.val();
- let annid = parseInt($messageannid.val());
- let voicetype = $messagevoicetype.val();
- let messagedetail = "";
- let messagetags = "";
-
- // iterate messageselectedvariables children
- $messageselectedvariables.children().each(function () {
- let val = $(this).text().trim();
- if (val.length > 0) {
- if (val.startsWith('[') && val.endsWith(']')) {
- // categories
- messagetags += (messagetags.length > 0 ? " " : "") + val;
- messagedetail += (messagedetail.length > 0 ? " " : "") + val;
- } else {
- // phrases
- // find in soundbankdata by description with specified language and voicetype
- let sb = soundbankdata
- .filter(sb => sb.language.toLowerCase() === language.toLowerCase())
- .filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase())
- .find(sb => sb.Description.toLowerCase() === val.toLowerCase());
- if (sb) {
- messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description;
- messagetags += (messagetags.length > 0 ? " " : "") + sb.tag;
- }
- }
- }
- });
-
- if (description.length === 0) {
- alert("Description cannot be empty");
- return;
- }
- if (!language) {
- alert("Language cannot be empty");
- return;
- }
- if (isNaN(annid) || annid < 1 || annid > 100) {
- alert("ANN_ID must be a number between 1 and 100");
- return;
- }
- if (!voicetype) {
- alert("Voice Type cannot be empty");
- return;
- }
- if (messagedetail.length === 0 || messagetags.length === 0) {
- alert("Message haven't been constructed, please add categories and phrases");
- return;
- }
-
-
- let mb = {
- Description: description,
- Language: language,
- ANN_ID: annid,
- Voice_Type: voicetype,
- Message_Detail: messagedetail,
- Message_TAGS: messagetags
- };
- // send to server using fetchAPI
- fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => {
- reloadMessageBank(APIURL);
- alert("Success add new messagebank : " + okdata.message);
- }, (errdata) => {
- alert("Error add new messagebank : " + errdata.message);
- });
-
- $modal.modal('hide');
- });
- // event on Click close button
- $modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () {
- $modal.modal('hide');
- });
- });
- $btnRemove.click(() => {
- if (selectedmessagerow) {
- let cells = selectedmessagerow.find('td');
- /** @type {MessageBank} */
- let mb = {
- index: cells.eq(0).text(),
- description: cells.eq(1).text(),
- language: cells.eq(2).text(),
- aNN_ID: parseInt(cells.eq(3).text()),
- voice_Type: cells.eq(4).text(),
- message_Detail: cells.eq(5).text(),
- message_TAGS: cells.eq(6).text()
- }
-
- if (confirm(`Are you sure to delete messagebank [${mb.index}] Description=${mb.description}? ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
- fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => {
- reloadMessageBank(APIURL);
- alert("Success delete messagebank : " + okdata.message);
- }, (errdata) => {
- alert("Error delete messagebank : " + errdata.message);
- });
- }
- }
- });
- $btnEdit.click(() => {
- if (selectedmessagerow) {
- let cells = selectedmessagerow.find('td');
- /** @type {MessageBank} */
- let mb = {
- index: cells.eq(0).text(),
- description: cells.eq(1).text(),
- language: cells.eq(2).text(),
- aNN_ID: parseInt(cells.eq(3).text()),
- voice_Type: cells.eq(4).text(),
- message_Detail: cells.eq(5).text(),
- message_TAGS: cells.eq(6).text()
- }
- if (confirm(`Are you sure to edit messagebank [${mb.index}] Description=${mb.description} ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
- $modal.modal('show');
-
- clearMessageModal();
- // Fill modal fields with selected messagebank data
- $messageindex.val(mb.index).prop('disabled', true);
- $messagedescription.val(mb.description);
- // Fill messagelanguage options and select current
- $messagelanguage.empty();
- languages.forEach(lang => {
- $messagelanguage.append(new Option(lang, lang));
- });
- $messagelanguage.val(mb.language);
- // Fill messagevoicetype options and select current
- $messagevoicetype.empty();
- voiceTypes.forEach(vt => {
- $messagevoicetype.append(new Option(vt, vt));
- });
- $messagevoicetype.val(mb.voice_Type);
- // Set annid
- $messageannid.val(mb.aNN_ID);
- // Refill message available variables
- refill_messageavailablevariables();
- // Fill messageselectedvariables from message_Detail and message_TAGS
- $messageselectedvariables.empty();
- if (mb.message_Detail) {
- mb.message_Detail.split(' ').forEach(val => {
- $messageselectedvariables.append(ListItem(val));
- });
- }
-
- // Save button event
- $modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () {
- let description = $messagedescription.val().trim();
- let language = $messagelanguage.val();
- let annid = parseInt($messageannid.val());
- let voicetype = $messagevoicetype.val();
- let messagedetail = "";
- let messagetags = "";
- $messageselectedvariables.children().each(function () {
- let val = $(this).text().trim();
- if (val.length > 0) {
- if (val.startsWith('[') && val.endsWith(']')) {
- messagetags += (messagetags.length > 0 ? " " : "") + val;
- messagedetail += (messagedetail.length > 0 ? " " : "") + val;
- } else {
- let sb = soundbankdata
- .filter(sb => sb.language.toLowerCase() === language.toLowerCase())
- .filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase())
- .find(sb => sb.Description && sb.Description.toLowerCase() === val.toLowerCase());
- if (sb) {
- messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description;
- messagetags += (messagetags.length > 0 ? " " : "") + sb.tag;
- }
- }
- }
- });
- if (description.length === 0) {
- alert("Description cannot be empty");
- return;
- }
- if (!language) {
- alert("Language cannot be empty");
- return;
- }
- if (isNaN(annid) || annid < 1 || annid > 100) {
- alert("ANN_ID must be a number between 1 and 100");
- return;
- }
- if (!voicetype) {
- alert("Voice Type cannot be empty");
- return;
- }
- if (messagedetail.length === 0 || messagetags.length === 0) {
- alert("Message haven't been constructed, please add categories and phrases");
- return;
- }
- let mbUpdate = {
- Description: description,
- Language: language,
- ANN_ID: annid,
- Voice_Type: voicetype,
- Message_Detail: messagedetail,
- Message_TAGS: messagetags
- };
- fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => {
- reloadMessageBank(APIURL);
- alert("Success edit messagebank : " + okdata.message);
- }, (errdata) => {
- alert("Error edit messagebank : " + errdata.message);
- });
- $modal.modal('hide');
- });
- // Close button event
- $modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () {
- $modal.modal('hide');
- });
-
- }
- }
- });
- $btnExport.click(() => {
- DoExport(APIURL, "messagebank.xlsx", {});
- });
- $btnImport.click(() => {
- DoImport(APIURL, (okdata) => {
- reloadMessageBank(APIURL);
- alert("Success import messagebank : " + okdata.message);
- }, (errdata) => {
- alert("Error importing messagebank from XLSX : " + errdata.message);
- });
- });
+ // pindah messagebank.js
+
} else {
console.error("Error loading messagebank content : ", xhr.status, xhr.statusText);
}
@@ -1298,539 +407,32 @@ $(document).ready(function () {
$('#content').load('language.html', function (response, status, xhr) {
if (status === "success") {
console.log("Language content loaded successfully");
- // initialize first state of buttons and text input
- $('#languagebanktablebody').empty();
- selectedlanguagerow = null;
- let $btnClear = $('#btnClear');
- let $btnAdd = $('#btnAdd');
- let $btnRemove = $('#btnRemove');
- let $btnEdit = $('#btnEdit');
- let $btnExport = $('#btnExport');
- let $btnImport = $('#btnImport');
- $btnRemove.prop('disabled', true);
- $btnEdit.prop('disabled', true);
- let APIURL = "LanguageLink/";
- let $findlanguage = $('#findlanguage');
- let $modal = $('#languagemodal');
- let $langid = $modal.find('#languagelinkindex');
- let $langtag = $modal.find('#languagelinktag');
- let $cbInd = $modal.find('#langId');
- let $cbLocal = $modal.find('#langLocal');
- let $cbEn = $modal.find('#langEn');
- let $cbArb = $modal.find('#langArb');
- let $cbJap = $modal.find('#langJap');
- let $cbChi = $modal.find('#langChi');
-
- function clearLanguageModal() {
- $langid.prop('disabled', true).val('');
- $langtag.val('');
- $cbInd.prop('checked', false);
- $cbLocal.prop('checked', false);
- $cbEn.prop('checked', false);
- $cbArb.prop('checked', false);
- $cbJap.prop('checked', false);
- $cbChi.prop('checked', false);
- }
-
- $findlanguage.on('input', function () {
- let searchTerm = $findlanguage.val().toLowerCase();
- if (searchTerm.length > 0) {
- selectedlanguagerow = null;
- let filtered = languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm));
- fill_languagebanktablebody(filtered);
- } else {
- selectedlanguagerow = null;
- fill_languagebanktablebody(languagebankdata);
- }
- });
-
- reloadLanguageBank(APIURL);
- $btnClear.click(() => {
- DoClear(APIURL, "LanguageLink", (okdata) => {
- reloadLanguageBank(APIURL);
- alert("Success clear languageLink : " + okdata.message);
- }, (errdata) => {
- alert("Error clear languageLink : " + errdata.message);
- });
-
- });
- $btnAdd.click(() => {
- // show modal with id 'languagemodal'
- $modal.modal('show');
- clearLanguageModal();
-
- // save button click event
- $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () {
- const tag = $langtag.val();
- const langs = [];
- if ($cbInd.is(':checked')) langs.push('INDONESIA');
- if ($cbLocal.is(':checked')) langs.push('LOCAL');
- if ($cbEn.is(':checked')) langs.push('ENGLISH');
- if ($cbArb.is(':checked')) langs.push('ARABIC');
- if ($cbJap.is(':checked')) langs.push('JAPANESE');
- if ($cbChi.is(':checked')) langs.push('CHINESE');
-
- if (tag.length === 0) {
- alert("Tag cannot be empty");
- return;
- }
- if (langs.length === 0) {
- alert("At least one language must be selected");
- return;
- }
-
- const langString = langs.join(';');
- let ll = {
- tag: tag,
- language: langString
- }
- fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
- alert("Success add language : " + okdata.message);
- reloadLanguageBank(APIURL);
- }, (errdata) => {
- alert("Error add language : " + errdata.message);
- });
- $modal.modal('hide');
-
-
- });
- // close button click event
- $modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () {
- $modal.modal('hide');
- });
- });
- $btnRemove.click(() => {
- if (selectedlanguagerow) {
- let cells = selectedlanguagerow.find('td');
- /** @type {Language} */
- let ll = {
- index: cells.eq(0).text(),
- tag: cells.eq(1).text(),
- language: cells.eq(2).text()
- }
- if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
- fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => {
- reloadLanguageBank(APIURL);
- alert("Success delete language : " + okdata.message);
- }, (errdata) => {
- alert("Error delete language : " + errdata.message);
- });
- }
- }
- });
- $btnEdit.click(() => {
- if (selectedlanguagerow) {
- let cells = selectedlanguagerow.find('td');
- /** @type {Language} */
- let ll = {
- index: cells.eq(0).text(),
- tag: cells.eq(1).text(),
- language: cells.eq(2).text()
- }
- if (confirm(`Are you sure to edit language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
-
- clearLanguageModal();
- $langid.val(ll.index);
- $langtag.val(ll.tag);
- let langs = ll.language.toUpperCase().split(';');
- $cbInd.prop('checked', langs.includes('INDONESIA'));
- $cbLocal.prop('checked', langs.includes('LOCAL'));
- $cbEn.prop('checked', langs.includes('ENGLISH'));
- $cbArb.prop('checked', langs.includes('ARABIC'));
- $cbJap.prop('checked', langs.includes('JAPANESE'));
- $cbChi.prop('checked', langs.includes('CHINESE'));
- $modal.modal('show');
- // save button click event
- $modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () {
- const tag = $langtag.val();
- const langs = [];
- if ($cbInd.is(':checked')) langs.push('INDONESIA');
- if ($cbLocal.is(':checked')) langs.push('LOCAL');
- if ($cbEn.is(':checked')) langs.push('ENGLISH');
- if ($cbArb.is(':checked')) langs.push('ARABIC');
- if ($cbJap.is(':checked')) langs.push('JAPANESE');
- if ($cbChi.is(':checked')) langs.push('CHINESE');
- if (tag.length === 0) {
- alert("Tag cannot be empty");
- return;
- }
- if (langs.length === 0) {
- alert("At least one language must be selected");
- return;
- }
- const langString = langs.join(';');
- if (ll.tag === tag && ll.language === langString) {
- alert("No changes detected");
- $modal.modal('hide');
- return;
- }
-
- ll.tag = tag;
- ll.language = langString;
- fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => {
- reloadLanguageBank(APIURL);
- alert("Success edit language : " + okdata.message);
- }, (errdata) => {
- alert("Error edit language : " + errdata.message);
- });
-
- $modal.modal('hide');
- });
- // close button click event
- $modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () {
- $modal.modal('hide');
- });
-
-
- }
- }
- });
- $btnExport.click(() => {
- DoExport(APIURL, "languagebank.xlsx", {});
-
- });
- $btnImport.click(() => {
- DoImport(APIURL, (okdata) => {
- reloadLanguageBank(APIURL);
- alert("Success import languagebank : " + okdata.message);
- }, (errdata) => {
- alert("Error importing languagebank from XLSX : " + errdata.message);
- });
-
- });
+ // 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");
- // initialize first state of buttons and text input
- $('#schedulebanktablebody').empty();
- selectedschedulerow = null;
- let $btnClear = $('#btnClear');
- let $btnAdd = $('#btnAdd');
- let $btnEdit = $('#btnEdit');
- let $btnRemove = $('#btnRemove');
- let $btnExport = $('#btnExport');
- let $btnImport = $('#btnImport');
- $btnEdit.prop('disabled', true);
- $btnRemove.prop('disabled', true);
- let APIURL = "ScheduleBank/";
-
- let $schedulemodal = $('#schedulemodal');
- // text input
- let $scheduleid = $schedulemodal.find('#scheduleid');
- // text input
- let $scheduledescription = $schedulemodal.find('#scheduledescription');
- // number input 0-23
- let $schedulehour = $schedulemodal.find('#schedulehour');
- // number input 0-59
- let $scheduleminute = $schedulemodal.find('#scheduleminute');
- // text input
- let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath');
- // number input 0-5
- let $schedulerepeat = $schedulemodal.find('#schedulerepeat');
- // checkbox
- let $scheduleenable = $schedulemodal.find('#scheduleenable');
- // div for list of checkboxes
- let $schedulezones = $schedulemodal.find('#schedulezones');
- // radio button for everyday
- let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday');
- // radio button for weekdays
- let $schedulesunday = $schedulemodal.find('#schedulesunday');
- let $schedulemonday = $schedulemodal.find('#schedulemonday');
- let $scheduletuesday = $schedulemodal.find('#scheduletuesday');
- let $schedulewednesday = $schedulemodal.find('#schedulewednesday');
- let $schedulethursday = $schedulemodal.find('#schedulethursday');
- let $schedulefriday = $schedulemodal.find('#schedulefriday');
- let $schedulesaturday = $schedulemodal.find('#schedulesaturday');
- // radio button for specific date
- let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
- // date input
- let $scheduledate = $schedulemodal.find('#scheduledate');
-
- $schedulespecialdate.on('change', function () {
- if ($(this).is(':checked')) {
- $scheduledate.prop('disabled', false);
- } else {
- $scheduledate.prop('disabled', true);
- }
- });
-
- function clearScheduleModal() {
- $scheduleid.prop('disabled', true).val('');
- $scheduledescription.val('');
- $schedulehour.val('0');
- $scheduleminute.val('0');
- $schedulesoundpath.val('');
- $schedulerepeat.val('0');
- $scheduleenable.prop('checked', true);
- $scheduleeveryday.prop('checked', false);
- $schedulesunday.prop('checked', false);
- $schedulemonday.prop('checked', false);
- $scheduletuesday.prop('checked', false);
- $schedulewednesday.prop('checked', false);
- $schedulethursday.prop('checked', false);
- $schedulefriday.prop('checked', false);
- $schedulesaturday.prop('checked', false);
- $schedulespecialdate.prop('checked', false);
- $scheduledate.prop('disabled', true).val('');
-
-
- }
-
- let $findschedule = $('#findschedule');
- $findschedule.on('input', function () {
- let searchTerm = $findschedule.val().toLowerCase();
- if (searchTerm.length > 0) {
- selectedtimerow = null;
- let filtered = schedulebankdata.filter(item =>
- item.description.toLowerCase().includes(searchTerm)
- || item.soundpath.toLowerCase().includes(searchTerm)
- || item.broadcastZones.toLowerCase().includes(searchTerm));
- fill_schedulebanktablebody(filtered);
- } else {
- selectedtimerow = null;
- fill_schedulebanktablebody(schedulebankdata);
- }
- });
-
-
- reloadTimerBank(APIURL);
- $btnClear.click(() => {
- DoClear(APIURL, "Timerbank", (okdata) => {
- reloadTimerBank(APIURL);
- alert("Success clear schedulebank : " + okdata.message);
- }, (errdata) => {
- alert("Error clear schedulebank : " + errdata.message);
- });
-
- });
- $btnAdd.click(() => {
- $schedulemodal.modal('show');
- clearScheduleModal();
-
- $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
- $schedulemodal.modal('hide');
- });
- $schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () {
- // Gather form values
- const Description = $scheduledescription.val();
- const Soundpath = $schedulesoundpath.val();
- const Repeat = parseInt($schedulerepeat.val(), 10);
- const Enable = $scheduleenable.is(':checked');
- // Collect selected days
- let Day = "";
- if ($scheduleeveryday.is(':checked')) {
- Day = "Everyday";
- } else if ($schedulespecialdate.is(':checked')) {
- Day = $scheduledate.val();
- } else {
-
- if ($schedulesunday.is(':checked')) Day = "Sunday";
- if ($schedulemonday.is(':checked')) Day = "Monday";
- if ($scheduletuesday.is(':checked')) Day = "Tuesday";
- if ($schedulewednesday.is(':checked')) Day = "Wednesday";
- if ($schedulethursday.is(':checked')) Day = "Thursday";
- if ($schedulefriday.is(':checked')) Day = "Friday";
- if ($schedulesaturday.is(':checked')) Day = "Saturday";
- }
- // Broadcast zones (assuming comma-separated string)
- const BroadcastZones = $schedulezones.val();
-
- // Validate required fields
- if (!Description || !Soundpath || Day === "") {
- alert("Description, sound path, and day are required.");
- return;
- }
-
- // Format time as HH:mm
- const hour = parseInt($schedulehour.val(), 10);
- const minute = parseInt($scheduleminute.val(), 10);
- const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
-
- // Prepare object
- const scheduleObj = {
- Description,
- Day,
- Time,
- Soundpath,
- Repeat,
- Enable,
- BroadcastZones
- };
-
- fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => {
- alert("Success add schedule: " + okdata.message);
- reloadTimerBank(APIURL);
- }, (errdata) => {
- alert("Error add schedule: " + errdata.message);
- });
-
- $schedulemodal.modal('hide');
- });
- });
- $btnRemove.click(() => {
- if (selectedtimerow) {
- let cells = selectedtimerow.find('td');
- /** @type {ScheduleBank} */
- let sr = {
- index: cells.eq(0).text(),
- description: cells.eq(1).text(),
- day: cells.eq(2).text(),
- time: cells.eq(3).text(),
- soundpath: cells.eq(4).text(),
- repeat: cells.eq(5).text(),
- enable: cells.eq(6).text(),
- broadcastZones: cells.eq(7).text(),
- language: cells.eq(8).text()
- }
- if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) {
- fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => {
- reloadTimerBank(APIURL);
- alert("Success delete schedule : " + okdata.message);
- }, (errdata) => {
- alert("Error delete schedule : " + errdata.message);
- });
- }
- }
- });
- $btnEdit.click(() => {
- if (selectedtimerow) {
- let cells = selectedtimerow.find('td');
- /** @type {ScheduleBank} */
- let sr = {
- index: cells.eq(0).text(),
- description: cells.eq(1).text(),
- day: cells.eq(2).text(),
- time: cells.eq(3).text(),
- soundpath: cells.eq(4).text(),
- repeat: cells.eq(5).text(),
- enable: cells.eq(6).text(),
- broadcastZones: cells.eq(7).text(),
- language: cells.eq(8).text()
- }
- if (confirm(`Are you sure to edit schedule [${sr.index}] Description=${sr.description}?`)) {
- $schedulemodal.modal('show');
- // fill the form with existing data
- $scheduleid.val(sr.index);
- $scheduledescription.val(sr.description);
- let [hour, minute] = sr.time.split(':').map(num => parseInt(num, 10));
- $schedulehour.val(hour.toString());
- $scheduleminute.val(minute.toString());
- $schedulesoundpath.val(sr.soundpath);
- $schedulerepeat.val(sr.repeat.toString());
- $scheduleenable.prop('checked', sr.enable.toLowerCase() === 'true');
- switch (sr.day) {
- case 'Everyday':
- $scheduleeveryday.click();
- break;
- case 'Sunday':
- $schedulesunday.click();
- break;
- case 'Monday':
- $schedulemonday.click();
- break;
- case 'Tuesday':
- $scheduletuesday.click();
- break;
- case 'Wednesday':
- $schedulewednesday.click();
- break;
- case 'Thursday':
- $schedulethursday.click();
- break;
- case 'Friday':
- $schedulefriday.click();
- break;
- case 'Saturday':
- $schedulesaturday.click();
- break;
- default:
- // check if the day is in format dd/mm/yyyy
- // and set the special date radio button and date input
- if (/^\d{2}\/\d{2}\/\d{4}$/.test(sr.day)) {
- $schedulespecialdate.click();
- $scheduledate.val(sr.day);
- }
- }
-
- $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
- $schedulemodal.modal('hide');
- });
- $schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () {
- // Gather form values
- const Description = $scheduledescription.val();
- const Soundpath = $schedulesoundpath.val();
- const Repeat = parseInt($schedulerepeat.val(), 10);
- const Enable = $scheduleenable.is(':checked');
- // Collect selected days
- let Day = "";
- if ($scheduleeveryday.is(':checked')) {
- Day = "Everyday";
- } else if ($schedulespecialdate.is(':checked')) {
- Day = $scheduledate.val();
- } else {
- if ($schedulesunday.is(':checked')) Day = "Sunday";
- if ($schedulemonday.is(':checked')) Day = "Monday";
- if ($scheduletuesday.is(':checked')) Day = "Tuesday";
- if ($schedulewednesday.is(':checked')) Day = "Wednesday";
- if ($schedulethursday.is(':checked')) Day = "Thursday";
- if ($schedulefriday.is(':checked')) Day = "Friday";
- if ($schedulesaturday.is(':checked')) Day = "Saturday";
- }
- // Broadcast zones (assuming comma-separated string)
- const BroadcastZones = $schedulezones.val();
-
- // Validate required fields
- if (!Description || !Soundpath || Day === "") {
- alert("Description, sound path, and day are required.");
- return;
- }
-
- // Format time as HH:mm
- const hour = parseInt($schedulehour.val(), 10);
- const minute = parseInt($scheduleminute.val(), 10);
- const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
-
- // Prepare object
- const scheduleObj = {
- Description,
- Day,
- Time,
- Soundpath,
- Repeat,
- Enable,
- BroadcastZones
- };
-
- fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => {
- alert("Success edit schedule: " + okdata.message);
- reloadTimerBank(APIURL);
- }, (errdata) => {
- alert("Error edit schedule: " + errdata.message);
- });
-
- $schedulemodal.modal('hide');
- });
- }
- }
- });
- $btnExport.click(() => {
- DoExport(APIURL, "schedulebank.xlsx", {});
- });
- $btnImport.click(() => {
- DoImport(APIURL, (okdata) => {
- reloadTimerBank(APIURL);
- alert("Success import schedulebank from XLSX : " + okdata.message);
- }, (errdata) => {
- alert("Error importing schedulebank from XLSX : " + errdata.message);
- });
- });
+ // pindah ke schedulebank.js
+
} else {
console.error("Error loading timer content : ", xhr.status, xhr.statusText);
}
@@ -1841,41 +443,7 @@ $(document).ready(function () {
$('#content').load('log.html', function (response, status, xhr) {
if (status === "success") {
console.log("Log content loaded successfully");
- const $logdate = $('#logdate');
- const $searchfilter = $('#searchfilter');
- const $logtable = $('#logtablebody')
- const $btnExport = $('#btnExport');
- let selectedlogdate = "";
- let logfilter = "";
- let APIURL = "Log/";
- $logtable.empty();
-
-
- if (!$logdate.val()) {
- const today = new Date();
- const dd = String(today.getDate()).padStart(2, '0');
- const mm = String(today.getMonth() + 1).padStart(2, '0');
- const yyyy = today.getFullYear();
- $logdate.val(`${yyyy}-${mm}-${dd}`);
- selectedlogdate = `${dd}-${mm}-${yyyy}`;
- reloadLogs(APIURL, selectedlogdate, logfilter);
- }
- $logdate.off('change').on('change', function () {
- const selected = $(this).val();
- if (selected) {
- const [year, month, day] = selected.split('-');
- selectedlogdate = `${day}-${month}-${year}`;
- reloadLogs(APIURL, selectedlogdate, logfilter);
- }
- });
-
- $searchfilter.off('input').on('input', function () {
- logfilter = $(this).val();
- reloadLogs(APIURL, selectedlogdate, logfilter);
- });
- $btnExport.off('click').on('click', function () {
- DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter });
- });
+ // pindah ke log.js
} else {
console.error("Error loading log content:", xhr.status, xhr.statusText);
diff --git a/html/webpage/assets/js/soundbank.js b/html/webpage/assets/js/soundbank.js
new file mode 100644
index 0000000..3ec96d8
--- /dev/null
+++ b/html/webpage/assets/js/soundbank.js
@@ -0,0 +1,274 @@
+/**
+ * @typedef {Object} SoundBank
+ * @property {number} index
+ * @property {string} description
+ * @property {string} tag
+ * @property {string} category
+ * @property {string} language
+ * @property {string} voiceType
+ * @property {string} path
+ */
+
+/**
+ * @typedef {Object} Select2item
+ * @property {number} id
+ * @property {string} text
+ */
+
+/**
+ * List of Soundbank data loaded from server
+ * @type {SoundBank[]}
+ */
+let soundbankdata = [];
+/**
+ * Currently selected soundbank row in the table
+ * @type {JQuery|null}
+ */
+let selectedsoundrow = null;
+
+/**
+ * List of sound files in the soundbank directory, that ends with .wav or .mp3
+ * @type {string[]}
+ */
+let soundbankfiles = [];
+
+/**
+ * Select2 data source
+ * See https://select2.org/data-sources/formats
+ * @type {Select2item[]}
+ */
+let select2data = [];
+
+/**
+ * Reload sound bank from server
+ * @param {String} APIURL API URL endpoint, default "SoundBank/"
+ */
+function reloadSoundBank(APIURL = "SoundBank/") {
+ soundbankdata = [];
+ fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
+
+ if (Array.isArray(okdata)) {
+ soundbankdata = okdata;
+ selectedsoundrow = null;
+ fill_soundbanktablebody(soundbankdata);
+ }
+ }, (errdata) => {
+ alert("Error loading soundbank : " + errdata.message);
+ });
+}
+
+/**
+ * Fill soundbank table body with values
+ * @param {SoundBank[]} vv values to fill
+ */
+function fill_soundbanktablebody(vv) {
+ $('#soundbanktablebody').empty();
+ if (!Array.isArray(vv) || vv.length === 0) return;
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.description} |
+ ${item.tag} |
+ ${item.category} |
+ ${item.language} |
+ ${item.voiceType} |
+ ${item.path} |
+
`;
+ $('#soundbanktablebody').append(row);
+ let $addedrow = $('#soundbanktablebody tr:last');
+ $addedrow.on('click', function () {
+ if (selectedsoundrow) {
+ selectedsoundrow.find('td').css('background-color', '');
+ if (selectedsoundrow.is($(this))) {
+ selectedsoundrow = null;
+ $('#btnRemove').prop('disabled', true);
+ $('#btnEdit').prop('disabled', true);
+ return;
+ }
+ }
+ $(this).find('td').css('background-color', '#ffeeba');
+ selectedsoundrow = $(this);
+ $('#btnRemove').prop('disabled', false);
+ $('#btnEdit').prop('disabled', false);
+ });
+ });
+
+
+ $('#tablesize').text("Table Size: " + vv.length);
+}
+
+
+/**
+ * Reload soundbank files from server
+ * @param {String} APIURL API URL endpoint (default "SoundBank/")
+ */
+function reloadSoundbankFiles(APIURL = "SoundBank/") {
+ soundbankfiles = [];
+ fetchAPI(APIURL + "ListFiles", "GET", {}, null, (okdata) => {
+ // okdata is a string contains elements separated by semicolon ;
+ if (Array.isArray(okdata)) {
+ soundbankfiles = okdata.filter(item => item.trim().length > 0);
+ // refill select2data
+ select2data = soundbankfiles.map((item, index) => ({ id: index + 1, text: item }));
+ } else console.log("reloadSoundbankFiles: okdata is not array");
+ }, (errdata) => {
+ alert("Error loading soundbank files : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log("soundbank.js loaded successfully");
+ reloadSoundbankFiles();
+ $('#soundbanktablebody').empty();
+ selectedsoundrow = null;
+ let $btnClear = $('#btnClear');
+ let $btnAdd = $('#btnAdd');
+ let $btnRemove = $('#btnRemove');
+ let $btnEdit = $('#btnEdit');
+ let $btnExport = $('#btnExport');
+ let $btnImport = $('#btnImport');
+ $btnRemove.prop('disabled', true);
+ $btnEdit.prop('disabled', true);
+ let APIURL = "SoundBank/";
+ let $modal = $('#soundbankmodal');
+ let $modalindex = $modal.find('#modalindex');
+ let $modaldescription = $modal.find('#modaldescription');
+ let $modaltag = $modal.find('#modaltag');
+ let $modalcategory = $modal.find('#modalcategory');
+ let $modallanguage = $modal.find('#modallanguage');
+ let $modalvoicetype = $modal.find('#modalvoicetype');
+ let $modalpath = $modal.find('#modalpath');
+
+ /**
+ * Clear soundbank modal inputs
+ */
+ function clearSoundbankModal() {
+ $modalindex.val('').prop('disabled', true);
+ $modaldescription.val('');
+ $modaltag.val('');
+ // fill modalcategory options from categories[]
+ $modalcategory.empty();
+ categories.forEach(cat => {
+ $modalcategory.append(new Option(cat, cat));
+ });
+ $modalcategory.val(null);
+ // fill modallanguage options from languages[]
+ $modallanguage.empty();
+ languages.forEach(lang => {
+ $modallanguage.append(new Option(lang, lang));
+ });
+ $modallanguage.val(null);
+ // fill modalvoicetype options from voiceTypes[]
+ $modalvoicetype.empty();
+ voiceTypes.forEach(vt => {
+ $modalvoicetype.append(new Option(vt, vt));
+ });
+ $modalvoicetype.val(null);
+ // fill modalpath options from soundbankfiles[]
+ // TODO read https://jeesite.com/front/jquery-select2/4.0/index.htm
+ console.log("select2data has " + select2data.length + " items");
+ $('#modalpath').select2({
+ data: select2data
+ })
+ }
+
+ reloadSoundBank(APIURL);
+ $('#findsoundbank').on('input', function () {
+ let searchTerm = $(this).val().trim().toLowerCase();
+ if (searchTerm.length > 0) {
+ selectedsoundrow = null;
+ let filtered = soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm));
+ fill_soundbanktablebody(filtered);
+ } else {
+ selectedsoundrow = null;
+ fill_soundbanktablebody(soundbankdata);
+ }
+ });
+ $btnClear.click(() => {
+ DoClear(APIURL, "Soundbank", (okdata) => {
+ reloadSoundBank(APIURL);
+ alert("Success clear soundbank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error clear soundbank : " + errdata.message);
+ });
+
+ });
+ $btnAdd.click(() => {
+
+ $modal.modal('show');
+ clearSoundbankModal();
+ // event on Click save button
+ $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
+ //TODO Add soundbank save process here
+ $modal.modal('hide');
+ });
+ // event on Click close button
+ $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
+ $modal.modal('hide');
+ });
+ });
+ $btnRemove.click(() => {
+ if (selectedsoundrow) {
+ let cells = selectedsoundrow.find('td');
+ /** @type {SoundBank} */
+ let sb = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ tag: cells.eq(2).text(),
+ category: cells.eq(3).text(),
+ language: cells.eq(4).text(),
+ voiceType: cells.eq(5).text(),
+ path: cells.eq(6).text()
+ }
+ if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
+ fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => {
+ reloadSoundBank(APIURL);
+ alert("Success delete soundbank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error delete soundbank : " + errdata.message);
+ });
+ }
+ }
+ });
+ $btnEdit.click(() => {
+ if (selectedsoundrow) {
+ let cells = selectedsoundrow.find('td');
+ /** @type {SoundBank} */
+ let sb = {
+ index: cells.eq(0).text(),
+ description: cells.eq(1).text(),
+ tag: cells.eq(2).text(),
+ category: cells.eq(3).text(),
+ language: cells.eq(4).text(),
+ voiceType: cells.eq(5).text(),
+ path: cells.eq(6).text()
+ }
+ if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
+ $modal.modal('show');
+ $modal.off('hidden.bs.modal').on('hidden.bs.modal', function () {
+
+ // event on Click save button
+ $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
+ //TODO Add soundbank save process here
+ $modal.modal('hide');
+ });
+ // event on Click close button
+ $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
+ $modal.modal('hide');
+ });
+ });
+ }
+ }
+ });
+ $btnExport.click(() => {
+ DoExport(APIURL, "soundbank.xlsx", {});
+ });
+ $btnImport.click(() => {
+ DoImport(APIURL, (okdata) => {
+ reloadSoundBank(APIURL);
+ alert("Success import soundbank : " + okdata.message);
+ }, (errdata) => {
+ alert("Error importing soundbank from XLSX : " + errdata.message);
+ });
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/assets/js/soundchannel.js b/html/webpage/assets/js/soundchannel.js
new file mode 100644
index 0000000..ba0114f
--- /dev/null
+++ b/html/webpage/assets/js/soundchannel.js
@@ -0,0 +1,181 @@
+/**
+ * @typedef {Object} SoundChannel
+ * @property {number} index - The index of the sound channel.
+ * @property {string} channel - The name of the sound channel.
+ * @property {string} ip - The IP address associated with the sound channel.
+ */
+
+/**
+ * @type {SoundChannel[]}
+ */
+let soundChannels = [];
+
+// Currently selected sound channel row in the table
+let selectedSoundChannel = null;
+
+/**
+ * Fills the sound channel table body with the provided data.
+ * @param {SoundChannel[]} vv Sound channel data to populate the table.
+ */
+function fill_soundchanneltablebody(vv) {
+ const $tbody = $('#soundchanneltablebody');
+ const $btnEditSoundChannel = $('#btnEditSoundChannel');
+ const $tablesizeSoundChannel = $('#tablesizeSoundChannel');
+ $tbody.empty();
+ $tablesizeSoundChannel.text('Table Length : N/A');
+ if (!Array.isArray(vv) || vv.length === 0) return;
+
+ vv.forEach(item => {
+ const row = `
+ | ${item.index} |
+ ${item.channel} |
+ ${item.ip} |
+
`;
+ $tbody.append(row);
+ let $addedrow = $('#soundchanneltablebody tr:last');
+ $addedrow.click(function () {
+ if (selectedSoundChannel) {
+ selectedSoundChannel.find('td').css('background-color', '');
+ if (selectedSoundChannel.is($(this))) {
+ selectedSoundChannel = null;
+ $btnEditSoundChannel.prop('disabled', true);
+ return;
+ }
+ }
+ $(this).find('td').css('background-color', '#ffeeba');
+ selectedSoundChannel = $(this);
+ $btnEditSoundChannel.prop('disabled', false);
+ });
+ });
+ $tablesizeSoundChannel.text("Table Size: " + vv.length);
+}
+
+/**
+ * Reload sound channels from server
+ * @param {String} APIURL API URL endpoint (default "SoundChannel/")
+ */
+function reloadSoundChannel(APIURL = "SoundChannel/") {
+ SoundChannelList = [];
+ fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
+ if (Array.isArray(okdata)) {
+ //console.log("reloadSoundChannel : ", okdata)
+ SoundChannelList = okdata;
+ fill_soundchanneltablebody(SoundChannelList);
+ } else console.log("reloadSoundChannel: okdata is not array");
+ }, (errdata) => {
+ alert("Error loading sound channels : " + errdata.message);
+ });
+}
+
+$(document).ready(function () {
+ console.log("soundchannel.js loaded successfully");
+ let $soundchannelmodal = $('#soundchannelmodal');
+ let $soundchannelindex = $soundchannelmodal.find('#soundchannelindex');
+ let $soundchanneldescription = $soundchannelmodal.find('#soundchanneldescription');
+ let $soundchannelip = $soundchannelmodal.find('#soundchannelip');
+
+ let $btnReinitializeSoundChannel = $('#btnReinitializeSoundChannel');
+ let $btnEditSoundChannel = $('#btnEditSoundChannel');
+ let $btnExportSoundChannel = $('#btnExportSoundChannel');
+ let $btnImportSoundChannel = $('#btnImportSoundChannel');
+ let $findsoundchannel = $('#findsoundchannel');
+ $btnEditSoundChannel.prop('disabled', true);
+ let API_SoundChannel = "SoundChannel/";
+
+ $findsoundchannel.on('input', function () {
+ let searchTerm = $(this).val().toLowerCase();
+ if (searchTerm.length==0){
+ fill_soundchanneltablebody(SoundChannelList);
+ } else {
+ let filteredChannels = SoundChannelList.filter(channel =>
+ channel.index.toString().includes(searchTerm) ||
+ channel.description.toLowerCase().includes(searchTerm) ||
+ channel.ip.toLowerCase().includes(searchTerm)
+ );
+ fill_soundchanneltablebody(filteredChannels);
+ }
+ });
+
+ /**
+ * Clear sound channel modal inputs
+ */
+ function clearSoundChannelModal() {
+ $soundchannelindex.val('');
+ $soundchanneldescription.val('');
+ $soundchannelip.val('');
+ }
+
+ reloadSoundChannel(API_SoundChannel);
+ $btnReinitializeSoundChannel.click(() => {
+ DoClear(API_SoundChannel, "SoundChannels", (okdata) => {
+ reloadSoundChannel(API_SoundChannel);
+ alert("Success clear sound channels: " + okdata.message);
+ }, (errdata) => {
+ alert("Error clear sound channels: " + errdata.message);
+ });
+ });
+ $btnEditSoundChannel.click(() => {
+ if (selectedSoundChannel) {
+ let cells = selectedSoundChannel.find('td');
+ /** @type {SoundChannel} */
+ let sc = {
+ index: parseInt(cells.eq(0).text(), 10),
+ description: cells.eq(1).text(),
+ ip: cells.eq(2).text()
+ };
+ if (confirm(`Are you sure to edit sound channel [${sc.index}] Description=${sc.description} IP=${sc.ip}?`)) {
+ $soundchannelmodal.modal('show');
+ clearSoundChannelModal();
+ $soundchannelindex.val(sc.index).prop('disabled', true);
+ $soundchanneldescription.val(sc.description);
+ $soundchannelip.val(sc.ip);
+
+ // Handle save changes
+ $soundchannelmodal.off('click.soundchannelsave').on('click.soundchannelsave', '#soundchannelsave', function () {
+ let newsc = {
+ index: parseInt($soundchannelindex.val(), 10),
+ description: $soundchanneldescription.val(),
+ ip: $soundchannelip.val()
+ };
+ if (newsc.description.trim().length === 0) {
+ alert("Description cannot be empty");
+ return;
+ }
+ if (newsc.ip.trim().length === 0) {
+ alert("IP cannot be empty");
+ return;
+ }
+ if (newsc.description===sc.description && newsc.ip===sc.ip){
+ alert("No changes detected");
+ return;
+ }
+
+ fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", newsc, null, (okdata) => {
+ reloadSoundChannel(API_SoundChannel);
+ alert("Success edit sound channel: " + okdata.message);
+ }, (errdata) => {
+ alert("Error edit sound channel: " + errdata.message);
+ });
+ $soundchannelmodal.modal('hide');
+
+
+ });
+ $soundchannelmodal.off('click.soundchannelclose').on('click.soundchannelclose', '#soundchannelclose', function () {
+ $soundchannelmodal.modal('hide');
+ });
+ }
+ }
+ });
+ $btnExportSoundChannel.click(() => {
+ DoExport(API_SoundChannel, "soundchannels.xlsx", {});
+ });
+
+ $btnImportSoundChannel.click(() => {
+ DoImport(API_SoundChannel, (okdata) => {
+ reloadSoundChannel(API_SoundChannel);
+ alert("Success import sound channels: " + okdata.message);
+ }, (errdata) => {
+ alert("Error importing sound channels from XLSX: " + errdata.message);
+ });
+ });
+});
\ No newline at end of file
diff --git a/html/webpage/broadcastzones.html b/html/webpage/broadcastzones.html
new file mode 100644
index 0000000..de96e45
--- /dev/null
+++ b/html/webpage/broadcastzones.html
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+ AAS_NewGen_03Sept25
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | No |
+ Description |
+ IP Address |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | No |
+ Description |
+ SoundChannel |
+ Box |
+ Relay |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/html/webpage/home.html b/html/webpage/home.html
index bb3c291..16b89c5 100644
--- a/html/webpage/home.html
+++ b/html/webpage/home.html
@@ -4,7 +4,7 @@
- AAS_NewGen_03Sept25
+ AAS NewGeneration 17092025
@@ -48,6 +48,12 @@
Timer
+