Compare commits

...

7 Commits

Author SHA1 Message Date
cf69c72f3c commit 06/10/2025 2025-10-06 08:30:09 +07:00
20dbc12b02 commit 02/10/2025 2025-10-02 16:03:28 +07:00
3768f4263b commit 02/10/2025 2025-10-02 13:41:26 +07:00
1e7adeba25 commit 02/10/2025 2025-10-02 13:17:52 +07:00
rdkartono
83a6ee9fd0 Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration 2025-10-01 15:52:31 +07:00
rdkartono
e0f3ac2094 Commit 30/09/2025 2025-10-01 15:50:33 +07:00
c55db5e4f7 commit 01/10/2025 2025-10-01 13:57:20 +07:00
38 changed files with 1867 additions and 857 deletions

View File

@@ -60,3 +60,20 @@
align-items: center; align-items: center;
} }
.pad-relay {
padding-left: 1.5rem;
padding-top: 0.5rem;
}
.pad-time {
margin: 0.7rem auto;
}
.pad-day {
margin-top: 0.5rem;
}
.class100 {
width: 100% !important;
}

View File

@@ -1,5 +1,6 @@
body { body {
background-color: #f8f9fd; background-color: #f8f9fd;
/*background-color: #edf1fb;*/
overflow-x: hidden; overflow-x: hidden;
width: 100%; width: 100%;
} }
@@ -13,8 +14,9 @@ body {
} }
.search { .search {
display: flex; /*display: flex;*/
align-items: center; /*align-items: center;*/
margin-top: -0.2rem;
} }
.text-header { .text-header {
@@ -188,3 +190,68 @@ nav-item:focus {
border: none; border: none;
} }
.pad-card {
padding-top: 1rem;
}
.pad-search {
padding-top: 0.5rem;
}
.pad-row-search {
margin-bottom: 0.7rem;
}
.bg-heading1 {
background-color: #c6d8ee;
color: #2d3578;
}
.bg-heading2 {
background-color: #dce5f4;
color: #2d3578;
}
.bg-heading3 {
background-color: #e9ecf8;
color: #2d3578;
}
.accordion-item {
/*background: rgba(255, 255, 255, 0.55);*/
/*backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);*/
/*border-radius: 16px;*/
/*box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.12), -8px -8px 16px rgba(255, 255, 255, 0.6);*/
/*transition: all 0.3s ease;*/
}
.accordion-item.active {
/*background: rgba(45, 53, 120, 0.15);*/
/*box-shadow: inset 4px 4px 10px rgba(45, 53, 120, 0.25), inset -4px -4px 10px rgba(255, 255, 255, 0.8);*/
}
.bg-accordion {
border: white 2px;
/*border-radius: 10px;*/
background: #f8f9fd;
}
.card-channel {
border-radius: 12px;
background: #ffffff;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: #ffffff solid 2px !important;
/*padding-left: 2px;*/
/*padding-right: 5px;*/
padding-top: 12px;
padding-bottom: 12px;
}
.pad-accordion {
border-radius: 40px;
border: white solid 3px;
}

View File

@@ -1,23 +1,10 @@
/**
* @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 * Currently selected broadcast zone row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
*/ */
let selectedBroadcastZoneRow = null; window.selectedBroadcastZoneRow = null;
/** /**
* Fill broadcast zone table body with values * Fill broadcast zone table body with values
@@ -31,23 +18,23 @@ function fill_broadcastzonetablebody(vv) {
<td>${item.index}</td> <td>${item.index}</td>
<td>${item.description}</td> <td>${item.description}</td>
<td>${item.soundChannel}</td> <td>${item.soundChannel}</td>
<td>${item.box}</td> <td>${item.id}</td>
<td>${item.relay}</td> <td>${item.bp}</td>
</tr>`; </tr>`;
$('#broadcastzonetablebody').append(row); $('#broadcastzonetablebody').append(row);
let $addedrow = $('#broadcastzonetablebody tr:last'); let $addedrow = $('#broadcastzonetablebody tr:last');
$addedrow.click(function () { $addedrow.click(function () {
if (selectedBroadcastZoneRow) { if (window.selectedBroadcastZoneRow) {
selectedBroadcastZoneRow.find('td').css('background-color', ''); window.selectedBroadcastZoneRow.find('td').css('background-color', '');
if (selectedBroadcastZoneRow.is($(this))) { if (window.selectedBroadcastZoneRow.is($(this))) {
selectedBroadcastZoneRow = null; window.selectedBroadcastZoneRow = null;
$('#btnRemove').prop('disabled', true); $('#btnRemove').prop('disabled', true);
$('#btnEdit').prop('disabled', true); $('#btnEdit').prop('disabled', true);
return; return;
} }
} }
$(this).find('td').css('background-color', '#ffeeba'); $(this).find('td').css('background-color', '#ffeeba');
selectedBroadcastZoneRow = $(this); window.selectedBroadcastZoneRow = $(this);
$('#btnRemove').prop('disabled', false); $('#btnRemove').prop('disabled', false);
$('#btnEdit').prop('disabled', false); $('#btnEdit').prop('disabled', false);
}); });
@@ -55,26 +42,11 @@ function fill_broadcastzonetablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#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 () { $(document).ready(function () {
console.log("broadcastzones.js loaded successfully"); console.log("broadcastzones.js loaded successfully");
selectedBroadcastZoneRow = null; window.selectedBroadcastZoneRow = null;
let $btnClear = $('#btnClear'); let $btnClear = $('#btnClear');
let $btnAdd = $('#btnAdd'); let $btnAdd = $('#btnAdd');
let $btnEdit = $('#btnEdit'); let $btnEdit = $('#btnEdit');
@@ -98,12 +70,12 @@ $(document).ready(function () {
$findzone.on('input', function () { $findzone.on('input', function () {
let searchTerm = $findzone.val().trim().toLowerCase(); let searchTerm = $findzone.val().trim().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
selectedBroadcastZoneRow = null; window.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)); let filtered = window.BroadcastZoneList.filter(item => item.description.toLowerCase().includes(searchTerm) || item.id.toLowerCase().includes(searchTerm) || item.soundChannel.toLowerCase().includes(searchTerm) || item.bp.toLowerCase().includes(searchTerm));
fill_broadcastzonetablebody(filtered); fill_broadcastzonetablebody(filtered);
} else { } else {
selectedBroadcastZoneRow = null; window.selectedBroadcastZoneRow = null;
fill_broadcastzonetablebody(broadcastzonedata); fill_broadcastzonetablebody(window.BroadcastZoneList);
} }
}); });
@@ -140,11 +112,14 @@ $(document).ready(function () {
} }
} }
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone, () => {
fill_broadcastzonetablebody(window.BroadcastZoneList);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => { DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone);
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success clear broadcast zones: " + okdata.message); alert("Success clear broadcast zones: " + okdata.message);
}, (errdata) => { }, (errdata) => {
alert("Error clear broadcast zones: " + errdata.message); alert("Error clear broadcast zones: " + errdata.message);
@@ -190,8 +165,10 @@ $(document).ready(function () {
Relay: relay Relay: relay
}; };
fetchAPI(APIURL_BroadcastZone + "Add", "POST", {}, bz, (okdata) => { fetchAPI(APIURL_BroadcastZone + "Add", "POST", {}, bz, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone, () => {
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success add new broadcast zone: " + okdata.message); alert("Success add new broadcast zone: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error add new broadcast zone: " + errdata.message); alert("Error add new broadcast zone: " + errdata.message);
}); });
@@ -204,8 +181,8 @@ $(document).ready(function () {
}); });
$btnRemove.click(() => { $btnRemove.click(() => {
if (selectedBroadcastZoneRow) { if (window.selectedBroadcastZoneRow) {
let cells = selectedBroadcastZoneRow.find('td'); let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */ /** @type {BroadcastZone} */
let bz = { let bz = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -216,8 +193,10 @@ $(document).ready(function () {
}; };
if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) { if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) {
fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone, () => {
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success delete broadcast zone: " + okdata.message); alert("Success delete broadcast zone: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete broadcast zone: " + errdata.message); alert("Error delete broadcast zone: " + errdata.message);
}); });
@@ -226,8 +205,8 @@ $(document).ready(function () {
}); });
$btnEdit.click(() => { $btnEdit.click(() => {
if (selectedBroadcastZoneRow) { if (window.selectedBroadcastZoneRow) {
let cells = selectedBroadcastZoneRow.find('td'); let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */ /** @type {BroadcastZone} */
let bz = { let bz = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -236,15 +215,15 @@ $(document).ready(function () {
Box: cells.eq(3).text(), Box: cells.eq(3).text(),
Relay: cells.eq(4).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}?`)) { if (confirm(`Are you sure to edit broadcast zone [${bz.index}] Description=${bz.description} SoundChannel=${bz.SoundChannel} Box=${bz.id} Relay=${bz.bp}?`)) {
$broadcastzonemodal.modal('show'); $broadcastzonemodal.modal('show');
clearBroadcastZoneModal(); clearBroadcastZoneModal();
$broadcastzoneindex.val(bz.index); $broadcastzoneindex.val(bz.index);
$broadcastzonedescription.val(bz.description); $broadcastzonedescription.val(bz.description);
$broadcastzonesoundchannel.val(bz.SoundChannel); $broadcastzonesoundchannel.val(bz.SoundChannel);
$broadcastzonebox.val(bz.Box); $broadcastzonebox.val(bz.id);
if (bz.Relay) { if (bz.bp) {
bz.Relay.split(';').forEach(relayId => { bz.bp.split(';').forEach(relayId => {
let id = parseInt(relayId, 10); let id = parseInt(relayId, 10);
cbRelay(id).prop('checked', true); cbRelay(id).prop('checked', true);
}); });
@@ -283,8 +262,10 @@ $(document).ready(function () {
Relay: relay Relay: relay
}; };
fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", {}, bzUpdate, (okdata) => { fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", {}, bzUpdate, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone, () => {
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success edit broadcast zone: " + okdata.message); alert("Success edit broadcast zone: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit broadcast zone: " + errdata.message); alert("Error edit broadcast zone: " + errdata.message);
}); });
@@ -303,8 +284,10 @@ $(document).ready(function () {
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL_BroadcastZone, (okdata) => { DoImport(APIURL_BroadcastZone, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone, () => {
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success import broadcast zones: " + okdata.message); alert("Success import broadcast zones: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing broadcast zones from XLSX: " + errdata.message); alert("Error importing broadcast zones from XLSX: " + errdata.message);
}); });

View File

@@ -1,20 +1,9 @@
/**
* @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 * Currently selected languagebank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
*/ */
let selectedlanguagerow = null; window.selectedlanguagerow = null;
/** /**
* Fill languagebank table body with values * Fill languagebank table body with values
@@ -32,17 +21,17 @@ function fill_languagebanktablebody(vv) {
$('#languagebanktablebody').append(row); $('#languagebanktablebody').append(row);
let $addedrow = $('#languagebanktablebody tr:last'); let $addedrow = $('#languagebanktablebody tr:last');
$addedrow.click(function () { $addedrow.click(function () {
if (selectedlanguagerow) { if (window.selectedlanguagerow) {
selectedlanguagerow.find('td').css('background-color', ''); window.selectedlanguagerow.find('td').css('background-color', '');
if (selectedlanguagerow.is($(this))) { if (window.selectedlanguagerow.is($(this))) {
selectedlanguagerow = null; window.selectedlanguagerow = null;
$('#btnRemove').prop('disabled', true); $('#btnRemove').prop('disabled', true);
$('#btnEdit').prop('disabled', true); $('#btnEdit').prop('disabled', true);
return; return;
} }
} }
$addedrow.find('td').css('background-color', '#ffeeba'); $addedrow.find('td').css('background-color', '#ffeeba');
selectedlanguagerow = $addedrow; window.selectedlanguagerow = $addedrow;
$('#btnRemove').prop('disabled', false); $('#btnRemove').prop('disabled', false);
$('#btnEdit').prop('disabled', false); $('#btnEdit').prop('disabled', false);
}); });
@@ -50,28 +39,13 @@ function fill_languagebanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#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 () { $(document).ready(function () {
console.log('languagebank.js loaded'); console.log('languagebank.js loaded');
$('#languagebanktablebody').empty(); $('#languagebanktablebody').empty();
selectedlanguagerow = null; window.selectedlanguagerow = null;
let $btnClear = $('#btnClear'); let $btnClear = $('#btnClear');
let $btnAdd = $('#btnAdd'); let $btnAdd = $('#btnAdd');
let $btnRemove = $('#btnRemove'); let $btnRemove = $('#btnRemove');
@@ -106,20 +80,24 @@ $(document).ready(function () {
$findlanguage.on('input', function () { $findlanguage.on('input', function () {
let searchTerm = $findlanguage.val().toLowerCase(); let searchTerm = $findlanguage.val().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
selectedlanguagerow = null; window.selectedlanguagerow = null;
let filtered = languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm)); let filtered = window.languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm));
fill_languagebanktablebody(filtered); fill_languagebanktablebody(filtered);
} else { } else {
selectedlanguagerow = null; window.selectedlanguagerow = null;
fill_languagebanktablebody(languagebankdata); fill_languagebanktablebody(window.languagebankdata);
} }
}); });
reloadLanguageBank(APIURL); reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "LanguageLink", (okdata) => { DoClear(APIURL, "LanguageLink", (okdata) => {
reloadLanguageBank(APIURL); reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
alert("Success clear languageLink : " + okdata.message); alert("Success clear languageLink : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear languageLink : " + errdata.message); alert("Error clear languageLink : " + errdata.message);
}); });
@@ -156,8 +134,10 @@ $(document).ready(function () {
language: langString language: langString
} }
fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => { fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
alert("Success add language : " + okdata.message); alert("Success add language : " + okdata.message);
reloadLanguageBank(APIURL); });
}, (errdata) => { }, (errdata) => {
alert("Error add language : " + errdata.message); alert("Error add language : " + errdata.message);
}); });
@@ -171,8 +151,8 @@ $(document).ready(function () {
}); });
}); });
$btnRemove.click(() => { $btnRemove.click(() => {
if (selectedlanguagerow) { if (window.selectedlanguagerow) {
let cells = selectedlanguagerow.find('td'); let cells = window.selectedlanguagerow.find('td');
/** @type {Language} */ /** @type {Language} */
let ll = { let ll = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -181,8 +161,10 @@ $(document).ready(function () {
} }
if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) { if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => {
reloadLanguageBank(APIURL); reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
alert("Success delete language : " + okdata.message); alert("Success delete language : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete language : " + errdata.message); alert("Error delete language : " + errdata.message);
}); });
@@ -190,8 +172,8 @@ $(document).ready(function () {
} }
}); });
$btnEdit.click(() => { $btnEdit.click(() => {
if (selectedlanguagerow) { if (window.selectedlanguagerow) {
let cells = selectedlanguagerow.find('td'); let cells = window.selectedlanguagerow.find('td');
/** @type {Language} */ /** @type {Language} */
let ll = { let ll = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -239,8 +221,10 @@ $(document).ready(function () {
ll.tag = tag; ll.tag = tag;
ll.language = langString; ll.language = langString;
fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => { fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => {
reloadLanguageBank(APIURL); reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
alert("Success edit language : " + okdata.message); alert("Success edit language : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit language : " + errdata.message); alert("Error edit language : " + errdata.message);
}); });
@@ -262,8 +246,10 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadLanguageBank(APIURL); reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
alert("Success import languagebank : " + okdata.message); alert("Success import languagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing languagebank from XLSX : " + errdata.message); alert("Error importing languagebank from XLSX : " + errdata.message);
}); });

View File

@@ -10,7 +10,7 @@
/** List of Log data loaded from server /** List of Log data loaded from server
* @type {Log[]} * @type {Log[]}
*/ */
let logdata = []; window.logdata = [];
/** /**
* Fill log table body with values * Fill log table body with values
@@ -18,9 +18,11 @@ let logdata = [];
*/ */
function fill_logtablebody(vv) { function fill_logtablebody(vv) {
$('#logtablebody').empty(); $('#logtablebody').empty();
if (!Array.isArray(vv) || vv.length === 0) {
$('#btnExport').prop('disabled', true); $('#btnExport').prop('disabled', true);
$('#searchfilter').prop('disabled', true); return;
if (!Array.isArray(vv) || vv.length === 0) return; }
vv.forEach(item => { vv.forEach(item => {
const row = `<tr> const row = `<tr>
<td>${item.index}</td> <td>${item.index}</td>
@@ -33,7 +35,6 @@ function fill_logtablebody(vv) {
}); });
$('#tablesize').text("Table Size: " + vv.length); $('#tablesize').text("Table Size: " + vv.length);
$('#btnExport').prop('disabled', false); $('#btnExport').prop('disabled', false);
$('#searchfilter').prop('disabled', false);
} }
/** /**
@@ -47,10 +48,11 @@ function reloadLogs(APIURL = "Log/", date, filter) {
date: date, date: date,
filter: filter filter: filter
}) })
window.logdata = [];
fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => { fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) { if (Array.isArray(okdata)) {
logdata = okdata; window.logdata.push(...okdata);
fill_logtablebody(okdata); fill_logtablebody(window.logdata);
} }
}, (errdata) => { }, (errdata) => {
alert("Error loading logs : " + errdata.message); alert("Error loading logs : " + errdata.message);
@@ -59,26 +61,22 @@ function reloadLogs(APIURL = "Log/", date, filter) {
$(document).ready(function () { $(document).ready(function () {
console.log("log.js ready"); console.log("log.js ready");
const $logdate = $('#logdate');
const $searchfilter = $('#searchfilter');
const $logtable = $('#logtablebody')
const $btnExport = $('#btnExport');
let selectedlogdate = ""; let selectedlogdate = "";
let logfilter = ""; let logfilter = "";
let APIURL = "Log/"; let APIURL = "Log/";
$logtable.empty(); $('#logtablebody').empty();
if (!$logdate.val()) { if (!$('#logdate').val()) {
const today = new Date(); const today = new Date();
const dd = String(today.getDate()).padStart(2, '0'); const dd = String(today.getDate()).padStart(2, '0');
const mm = String(today.getMonth() + 1).padStart(2, '0'); const mm = String(today.getMonth() + 1).padStart(2, '0');
const yyyy = today.getFullYear(); const yyyy = today.getFullYear();
$logdate.val(`${yyyy}-${mm}-${dd}`); $('#logdate').val(`${yyyy}-${mm}-${dd}`);
selectedlogdate = `${dd}-${mm}-${yyyy}`; selectedlogdate = `${dd}-${mm}-${yyyy}`;
reloadLogs(APIURL, selectedlogdate, logfilter); reloadLogs(APIURL, selectedlogdate, logfilter);
} }
$logdate.off('change').on('change', function () { $('#logdate').off('change').on('change', function () {
const selected = $(this).val(); const selected = $(this).val();
if (selected) { if (selected) {
const [year, month, day] = selected.split('-'); const [year, month, day] = selected.split('-');
@@ -87,11 +85,11 @@ $(document).ready(function () {
} }
}); });
$searchfilter.off('input').on('input', function () { $('#searchfilter').off('input').on('input', function () {
logfilter = $(this).val(); logfilter = $(this).val();
reloadLogs(APIURL, selectedlogdate, logfilter); reloadLogs(APIURL, selectedlogdate, logfilter);
}); });
$btnExport.off('click').on('click', function () { $('#btnExport').off('click').on('click', function () {
DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter }); DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter });
}); });
}); });

View File

@@ -1,24 +1,9 @@
/**
* @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 * Currently selected messagebank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
*/ */
let selectedmessagerow = null; window.selectedmessagerow = null;
/** /**
* Fill messagebank table body with values * Fill messagebank table body with values
@@ -40,17 +25,17 @@ function fill_messagebanktablebody(vv) {
$('#messagebanktablebody').append(row); $('#messagebanktablebody').append(row);
let $addedrow = $('#messagebanktablebody tr:last'); let $addedrow = $('#messagebanktablebody tr:last');
$addedrow.click(function () { $addedrow.click(function () {
if (selectedmessagerow) { if (window.selectedmessagerow) {
selectedmessagerow.find('td').css('background-color', ''); window.selectedmessagerow.find('td').css('background-color', '');
if (selectedmessagerow.is($(this))) { if (window.selectedmessagerow.is($(this))) {
selectedmessagerow = null; window.selectedmessagerow = null;
$('#btnRemove').prop('disabled', true); $('#btnRemove').prop('disabled', true);
$('#btnEdit').prop('disabled', true); $('#btnEdit').prop('disabled', true);
return; return;
} }
} }
$addedrow.find('td').css('background-color', '#ffeeba'); $addedrow.find('td').css('background-color', '#ffeeba');
selectedmessagerow = $addedrow; window.selectedmessagerow = $addedrow;
$('#btnRemove').prop('disabled', false); $('#btnRemove').prop('disabled', false);
$('#btnEdit').prop('disabled', false); $('#btnEdit').prop('disabled', false);
}); });
@@ -59,27 +44,12 @@ function fill_messagebanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#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 () { $(document).ready(function () {
console.log("messagebank.js loaded"); console.log("messagebank.js loaded");
$('#messagebanktablebody').empty(); $('#messagebanktablebody').empty();
selectedmessagerow = null; window.selectedmessagerow = null;
let $btnClear = $('#btnClear'); let $btnClear = $('#btnClear');
let $btnAdd = $('#btnAdd'); let $btnAdd = $('#btnAdd');
let $btnRemove = $('#btnRemove'); let $btnRemove = $('#btnRemove');
@@ -198,21 +168,25 @@ $(document).ready(function () {
$findmessage.on('input', function () { $findmessage.on('input', function () {
let searchTerm = $findmessage.val().toLowerCase(); let searchTerm = $findmessage.val().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
selectedmessagerow = null; window.selectedmessagerow = null;
let filtered = messagebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.message_Detail.toLowerCase().includes(searchTerm) || item.message_TAGS.toLowerCase().includes(searchTerm)); let filtered = window.messagebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.message_Detail.toLowerCase().includes(searchTerm) || item.message_TAGS.toLowerCase().includes(searchTerm));
fill_messagebanktablebody(filtered); fill_messagebanktablebody(filtered);
} else { } else {
selectedmessagerow = null; window.selectedmessagerow = null;
fill_messagebanktablebody(messagebankdata); fill_messagebanktablebody(window.messagebankdata);
} }
}); });
reloadMessageBank(APIURL); reloadMessageBank(APIURL, () => {
fill_messagebanktablebody(window.messagebankdata);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "Messagebank", (okdata) => { DoClear(APIURL, "Messagebank", (okdata) => {
reloadMessageBank(APIURL); reloadMessageBank(APIURL, () => {
fill_messagebanktablebody(window.messagebankdata);
alert("Success clear messagebank : " + okdata.message); alert("Success clear messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear messagebank : " + errdata.message); alert("Error clear messagebank : " + errdata.message);
}); });
@@ -287,8 +261,10 @@ $(document).ready(function () {
}; };
// send to server using fetchAPI // send to server using fetchAPI
fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => { fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => {
reloadMessageBank(APIURL); reloadMessageBank(APIURL, () => {
fill_messagebanktablebody(window.messagebankdata);
alert("Success add new messagebank : " + okdata.message); alert("Success add new messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error add new messagebank : " + errdata.message); alert("Error add new messagebank : " + errdata.message);
}); });
@@ -301,8 +277,8 @@ $(document).ready(function () {
}); });
}); });
$btnRemove.click(() => { $btnRemove.click(() => {
if (selectedmessagerow) { if (window.selectedmessagerow) {
let cells = selectedmessagerow.find('td'); let cells = window.selectedmessagerow.find('td');
/** @type {MessageBank} */ /** @type {MessageBank} */
let mb = { let mb = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -316,8 +292,10 @@ $(document).ready(function () {
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} `)) { 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) => { fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => {
reloadMessageBank(APIURL); reloadMessageBank(APIURL, () => {
fill_messagebanktablebody(window.messagebankdata);
alert("Success delete messagebank : " + okdata.message); alert("Success delete messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete messagebank : " + errdata.message); alert("Error delete messagebank : " + errdata.message);
}); });
@@ -325,8 +303,8 @@ $(document).ready(function () {
} }
}); });
$btnEdit.click(() => { $btnEdit.click(() => {
if (selectedmessagerow) { if (window.selectedmessagerow) {
let cells = selectedmessagerow.find('td'); let cells = window.selectedmessagerow.find('td');
/** @type {MessageBank} */ /** @type {MessageBank} */
let mb = { let mb = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -423,8 +401,10 @@ $(document).ready(function () {
Message_TAGS: messagetags Message_TAGS: messagetags
}; };
fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => { fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => {
reloadMessageBank(APIURL); reloadMessageBank(APIURL, () => {
fill_messagebanktablebody(window.messagebankdata);
alert("Success edit messagebank : " + okdata.message); alert("Success edit messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit messagebank : " + errdata.message); alert("Error edit messagebank : " + errdata.message);
}); });
@@ -443,8 +423,11 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadMessageBank(APIURL); reloadMessageBank(APIURL, () => {
fill_messagebanktablebody(window.messagebankdata);
alert("Success import messagebank : " + okdata.message); alert("Success import messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing messagebank from XLSX : " + errdata.message); alert("Error importing messagebank from XLSX : " + errdata.message);
}); });

View File

@@ -1,25 +1,9 @@
/**
* @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 * Currently selected schedulebank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
*/ */
let selectedschedulerow = null; window.selectedschedulerow = null;
/** /**
* Fill schedulebank table body with values * Fill schedulebank table body with values
@@ -61,22 +45,7 @@ function fill_schedulebanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#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 () { $(document).ready(function () {
console.log("schedulebank.js loaded successfully"); console.log("schedulebank.js loaded successfully");
@@ -101,7 +70,7 @@ $(document).ready(function () {
let $schedulehour = $schedulemodal.find('#schedulehour'); let $schedulehour = $schedulemodal.find('#schedulehour');
// number input 0-59 // number input 0-59
let $scheduleminute = $schedulemodal.find('#scheduleminute'); let $scheduleminute = $schedulemodal.find('#scheduleminute');
// text input // select for messagebank
let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath'); let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath');
// number input 0-5 // number input 0-5
let $schedulerepeat = $schedulemodal.find('#schedulerepeat'); let $schedulerepeat = $schedulemodal.find('#schedulerepeat');
@@ -137,8 +106,17 @@ $(document).ready(function () {
$scheduledescription.val(''); $scheduledescription.val('');
$schedulehour.val('0'); $schedulehour.val('0');
$scheduleminute.val('0'); $scheduleminute.val('0');
$schedulesoundpath.val('');
$schedulerepeat.val('0'); $schedulesoundpath.empty();
if (Array.isArray(window.messagebankdata) && window.messagebankdata.length > 0) {
window.messagebankdata.forEach(item => {
let str = item.description+" ["+item.aNN_ID+"]";
let option = `<option value="${str}">${str}</option>`;
if ($schedulesoundpath.find(`option[value="${str}"]`).length === 0) $schedulesoundpath.append(option); // check if $schedulesoundpath already has this option
})
}
$schedulerepeat.val('1');
$scheduleenable.prop('checked', true); $scheduleenable.prop('checked', true);
$scheduleeveryday.prop('checked', false); $scheduleeveryday.prop('checked', false);
$schedulesunday.prop('checked', false); $schedulesunday.prop('checked', false);
@@ -158,24 +136,28 @@ $(document).ready(function () {
$findschedule.on('input', function () { $findschedule.on('input', function () {
let searchTerm = $findschedule.val().toLowerCase(); let searchTerm = $findschedule.val().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
selectedtimerow = null; window.selectedschedulerow = null;
let filtered = schedulebankdata.filter(item => let filtered = window.schedulebankdata.filter(item =>
item.description.toLowerCase().includes(searchTerm) item.description.toLowerCase().includes(searchTerm)
|| item.soundpath.toLowerCase().includes(searchTerm) || item.soundpath.toLowerCase().includes(searchTerm)
|| item.broadcastZones.toLowerCase().includes(searchTerm)); || item.broadcastZones.toLowerCase().includes(searchTerm));
fill_schedulebanktablebody(filtered); fill_schedulebanktablebody(filtered);
} else { } else {
selectedtimerow = null; window.selectedschedulerow = null;
fill_schedulebanktablebody(schedulebankdata); fill_schedulebanktablebody(window.schedulebankdata);
} }
}); });
reloadTimerBank(APIURL); reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "Timerbank", (okdata) => { DoClear(APIURL, "Timerbank", (okdata) => {
reloadTimerBank(APIURL); reloadTimerBank(APIURL,() => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success clear schedulebank : " + okdata.message); alert("Success clear schedulebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear schedulebank : " + errdata.message); alert("Error clear schedulebank : " + errdata.message);
}); });
@@ -236,8 +218,10 @@ $(document).ready(function () {
}; };
fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => { fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => {
reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success add schedule: " + okdata.message); alert("Success add schedule: " + okdata.message);
reloadTimerBank(APIURL); });
}, (errdata) => { }, (errdata) => {
alert("Error add schedule: " + errdata.message); alert("Error add schedule: " + errdata.message);
}); });
@@ -246,8 +230,8 @@ $(document).ready(function () {
}); });
}); });
$btnRemove.click(() => { $btnRemove.click(() => {
if (selectedtimerow) { if (window.selectedschedulerow) {
let cells = selectedtimerow.find('td'); let cells = window.selectedschedulerow.find('td');
/** @type {ScheduleBank} */ /** @type {ScheduleBank} */
let sr = { let sr = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -262,8 +246,10 @@ $(document).ready(function () {
} }
if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) { if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => {
reloadTimerBank(APIURL); reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success delete schedule : " + okdata.message); alert("Success delete schedule : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete schedule : " + errdata.message); alert("Error delete schedule : " + errdata.message);
}); });
@@ -271,8 +257,8 @@ $(document).ready(function () {
} }
}); });
$btnEdit.click(() => { $btnEdit.click(() => {
if (selectedtimerow) { if (window.selectedschedulerow) {
let cells = selectedtimerow.find('td'); let cells = window.selectedschedulerow.find('td');
/** @type {ScheduleBank} */ /** @type {ScheduleBank} */
let sr = { let sr = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -380,8 +366,10 @@ $(document).ready(function () {
}; };
fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => { fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => {
reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success edit schedule: " + okdata.message); alert("Success edit schedule: " + okdata.message);
reloadTimerBank(APIURL); });
}, (errdata) => { }, (errdata) => {
alert("Error edit schedule: " + errdata.message); alert("Error edit schedule: " + errdata.message);
}); });
@@ -396,8 +384,10 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadTimerBank(APIURL); reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success import schedulebank from XLSX : " + okdata.message); alert("Success import schedulebank from XLSX : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing schedulebank from XLSX : " + errdata.message); alert("Error importing schedulebank from XLSX : " + errdata.message);
}); });

View File

@@ -2,23 +2,280 @@
* List of voice types available * List of voice types available
* @type {string[]} * @type {string[]}
*/ */
let voiceTypes = []; window.voiceTypes = [];
/** /**
* List of categories available * List of categories available
* @type {string[]} * @type {string[]}
*/ */
let categories = []; window.categories = [];
/** /**
* List of languages available * List of languages available
* @type {string[]} * @type {string[]}
*/ */
let languages = []; window.languages = [];
/** /**
* List of scheduled days available * List of scheduled days available
* @type {string[]} * @type {string[]}
*/ */
let scheduledays = [] window.scheduledays = []
/**
* @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
* taruh di sini karena dipakai di banyak tempat
* @type {MessageBank[]}
*/
window.messagebankdata ??= [];
/**
* Reload message bank from server
* @param {string} APIURL API URL endpoint, default "MessageBank/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadMessageBank(APIURL = "MessageBank/", callback=null) {
window.messagebankdata ??= [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.messagebankdata.push(...okdata);
console.log("Loaded " + window.messagebankdata.length + " message bank items");
window.selectedmessagerow = null;
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading messagebank : " + errdata.message);
});
}
/**
* @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[]}
*/
window.schedulebankdata = [];
/**
* Reload timer bank from server
* taruh di sini karena dipakai di banyak tempat
* @param {string} APIURL API URL endpoint, default "ScheduleBank/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadTimerBank(APIURL = "ScheduleBank/", callback=null) {
window.schedulebankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.schedulebankdata.push(...okdata);
selectedschedulerow = null;
console.log("Loaded " + window.schedulebankdata.length + " schedule bank items");
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading schedulebank : " + errdata.message);
});
}
/**
* @typedef {Object} BroadcastZone
* @property {number} index
* @property {string} description
* @property {String} SoundChannel
* @property {String} Box
* @property {String} Relay
*/
/**
* List of broadcast zones available
* @type {BroadcastZone[]}
*/
window.BroadcastZoneList ??= [];
/**
* Reload broadcast zones from server
* taruh di sini karena dipakai di banyak tempat
* @param {String} APIURL API URL endpoint (default "BroadcastZones/")
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadBroadcastZones(APIURL = "BroadcastZones/", callback=null) {
window.BroadcastZoneList = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadBroadcastZones : ", okdata)
window.BroadcastZoneList.push(...okdata);
console.log("Loaded " + window.BroadcastZoneList.length + " broadcast zone items");
window.selectedbroadcastzonerow = null;
if (callback && typeof callback === 'function') callback();
} else console.log("reloadBroadcastZones: okdata is not array");
}, (errdata) => {
alert("Error loading broadcast zones : " + errdata.message);
});
}
/**
* @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
* taruh di sini karena dipakai di banyak tempat
* @type {SoundBank[]}
*/
window.soundbankdata = [];
/**
* List of sound files in the soundbank directory, that ends with .wav or .mp3
* taruh di sini karena dipakai di banyak tempat
* @type {string[]}
*/
window.soundbankfiles = [];
/**
* Select2 data source
* See https://select2.org/data-sources/formats
* @type {Select2item[]}
*/
window.select2data = [];
/**
* Reload sound bank from server
* @param {String} APIURL API URL endpoint, default "SoundBank/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadSoundBank(APIURL = "SoundBank/", callback=null) {
window.soundbankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.soundbankdata.push(...okdata);
window.selectedsoundrow = null;
console.log("Loaded " + window.soundbankdata.length + " sound bank items");
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading soundbank : " + errdata.message);
});
}
/**
* Reload soundbank files from server
* @param {String} APIURL API URL endpoint (default "SoundBank/")
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadSoundbankFiles(APIURL = "SoundBank/",callback=null) {
window.soundbankfiles = [];
fetchAPI(APIURL + "ListFiles", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) {
window.soundbankfiles = okdata.filter(item => item.trim().length > 0);
// refill select2data
window.select2data = window.soundbankfiles.map((item, index) => ({ id: index + 1, text: item }));
console.log("Loaded " + window.soundbankfiles.length + " sound bank files");
if (callback && typeof callback === 'function') callback();
} else console.log("reloadSoundbankFiles: okdata is not array");
}, (errdata) => {
alert("Error loading soundbank files : " + errdata.message);
});
}
/**
* @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[]}
*/
window.soundChannels = [];
/**
* Reload sound channels from server
* @param {String} APIURL API URL endpoint (default "SoundChannel/")
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadSoundChannel(APIURL = "SoundChannel/", callback=null) {
window.soundChannels = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadSoundChannel : ", okdata)
window.soundChannels.push(...okdata);
window.selectedsoundchannelrow = null;
console.log("Loaded " + window.soundChannels.length + " sound channel items");
if (callback && typeof callback === 'function') callback();
} else console.log("reloadSoundChannel: okdata is not array");
}, (errdata) => {
alert("Error loading sound channels : " + errdata.message);
});
}
/**
* @typedef {Object} LanguageBank
* @property {number} index
* @property {string} tag
* @property {string} language
*
*/
/** List of Languagebank data loaded from server
* @type {LanguageBank[]}
*/
window.languagebankdata = [];
/**
* Reload language bank from server
* @param {string} APIURL API URL endpoint, default "LanguageLink/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadLanguageBank(APIURL = "LanguageLink/", callback=null) {
window.languagebankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.languagebankdata.push(...okdata);
window.selectedlanguagerow = null;
console.log("Loaded " + window.languagebankdata.length + " language bank items");
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading languagebank : " + errdata.message);
});
}
/** /**
* Create a list item element * Create a list item element
@@ -34,7 +291,7 @@ function ListItem(text, className = "") {
* WebSocket connection * WebSocket connection
* @type {WebSocket} * @type {WebSocket}
*/ */
let ws = null; window.ws = null;
/** /**
* Send a command to the WebSocket server. * Send a command to the WebSocket server.
@@ -42,8 +299,8 @@ let ws = null;
* @param {String} data data to send * @param {String} data data to send
*/ */
function sendCommand(command, data) { function sendCommand(command, data) {
if (ws.readyState === WebSocket.OPEN) { if (window.ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ command, data })); window.ws.send(JSON.stringify({ command, data }));
} }
} }
@@ -118,11 +375,11 @@ function fetchImg(url, cbOK, cbError) {
* Reload voice types from server * Reload voice types from server
*/ */
function getVoiceTypes() { function getVoiceTypes() {
voiceTypes = []; window.voiceTypes = [];
fetchAPI("VoiceType", "GET", {}, null, (okdata) => { fetchAPI("VoiceType", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ; // okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) { if (Array.isArray(okdata)) {
voiceTypes = okdata.filter(item => item.trim().length > 0); window.voiceTypes = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + voiceTypes.length + " voice types : " + voiceTypes.join(", ")); //console.log("Loaded " + voiceTypes.length + " voice types : " + voiceTypes.join(", "));
} else console.log("getVoiceTypes: okdata is not array"); } else console.log("getVoiceTypes: okdata is not array");
}, (errdata) => { }, (errdata) => {
@@ -134,11 +391,11 @@ function getVoiceTypes() {
* Reload categories from server * Reload categories from server
*/ */
function getCategories() { function getCategories() {
categories = []; window.categories = [];
fetchAPI("Category", "GET", {}, null, (okdata) => { fetchAPI("Category", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ; // okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) { if (Array.isArray(okdata)) {
categories = okdata.filter(item => item.trim().length > 0); window.categories = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + categories.length + " categories : " + categories.join(", ")); //console.log("Loaded " + categories.length + " categories : " + categories.join(", "));
} else console.log("getCategories: okdata is not array"); } else console.log("getCategories: okdata is not array");
}, (errdata) => { }, (errdata) => {
@@ -150,11 +407,11 @@ function getCategories() {
* Reload languages from server * Reload languages from server
*/ */
function getLanguages() { function getLanguages() {
languages = []; window.languages = [];
fetchAPI("Language", "GET", {}, null, (okdata) => { fetchAPI("Language", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ; // okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) { if (Array.isArray(okdata)) {
languages = okdata.filter(item => item.trim().length > 0); window.languages = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + languages.length + " languages : " + languages.join(", ") ); //console.log("Loaded " + languages.length + " languages : " + languages.join(", ") );
} else console.log("getLanguages: okdata is not array"); } else console.log("getLanguages: okdata is not array");
}, (errdata) => { }, (errdata) => {
@@ -166,11 +423,11 @@ function getLanguages() {
* Reload scheduled days from server * Reload scheduled days from server
*/ */
function getScheduledDays() { function getScheduledDays() {
scheduledays = []; window.scheduledays = [];
fetchAPI("ScheduleDay", "GET", {}, null, (okdata) => { fetchAPI("ScheduleDay", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ; // okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) { if (Array.isArray(okdata)) {
scheduledays = okdata.filter(item => item.trim().length > 0); window.scheduledays = okdata.filter(item => item.trim().length > 0);
//console.log("Loaded " + scheduledays.length + " scheduled days : " + scheduledays.join(", ") ); //console.log("Loaded " + scheduledays.length + " scheduled days : " + scheduledays.join(", ") );
} else console.log("getScheduledDays: okdata is not array"); } else console.log("getScheduledDays: okdata is not array");
}, (errdata) => { }, (errdata) => {
@@ -273,43 +530,36 @@ function DoImport(APIURL, cbOK, cbError) {
fileInput.remove(); fileInput.remove();
} }
let $onlineindicator = null;
let $cpustatus = null;
let $ramstatus = null;
let $diskstatus = null;
let $networkstatus = null;
let $datetimetext = null;
let greencircle = null; window.greencircle = null;
let redcircle = null; window.redcircle = null;
/** /**
* App entry point * App entry point
*/ */
$(document).ready(function () { $(document).ready(function () {
document.title = "Automatic Announcement System" document.title = "Automatic Announcement System"
fetchImg('green_circle.png', (url) => { greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); }); if (window.greencircle === null){
fetchImg('red_circle.png', (url) => { redcircle = url; }, (err) => { console.error("Error loading red_circle.png : ", err); }); fetchImg('green_circle.png', (url) => { window.greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); });
}
if (window.redcircle === null){
fetchImg('red_circle.png', (url) => { window.redcircle = url; }, (err) => { console.error("Error loading red_circle.png : ", err); });
}
const wsURL = window.location.pathname + '/ws' const wsURL = window.location.pathname + '/ws'
if (chrome && chrome.runtime && chrome.runtime.lastError) { if (chrome && chrome.runtime && chrome.runtime.lastError) {
alert("Runtime error: " + chrome.runtime.lastError.message); alert("Runtime error: " + chrome.runtime.lastError.message);
return; return;
} }
$onlineindicator = $('#onlineindicator');
$cpustatus = $('#cpustatus');
$ramstatus = $('#ramstatus');
$diskstatus = $('#diskstatus');
$networkstatus = $('#networkstatus');
$datetimetext = $('#datetimetext');
// reset status indicators // reset status indicators
function resetStatusIndicators() { function resetStatusIndicators() {
$onlineindicator.attr('src', redcircle); $('#onlineindicator').attr('src', window.redcircle);
$cpustatus.text("CPU : N/A"); $('#cpustatus').text("CPU : N/A");
$ramstatus.text("RAM : N/A"); $('#ramstatus').text("RAM : N/A");
$diskstatus.text("Disk : N/A"); $('#diskstatus').text("Disk : N/A");
$networkstatus.text("Network : N/A"); $('#networkstatus').text("Network : N/A");
$datetimetext.text("Date/Time : N/A"); $('#datetimetext').text("Date/Time : N/A");
} }
@@ -318,47 +568,56 @@ $(document).ready(function () {
getCategories(); getCategories();
getLanguages(); getLanguages();
getScheduledDays(); getScheduledDays();
reloadMessageBank();
reloadTimerBank();
reloadBroadcastZones();
reloadSoundBank();
reloadSoundbankFiles();
reloadSoundChannel();
reloadLanguageBank();
// Initialize WebSocket connection // Initialize WebSocket connection
ws = new WebSocket(wsURL); window.ws = new WebSocket(wsURL);
ws.onopen = () => { window.ws.onopen = () => {
console.log('WebSocket connection established'); console.log('WebSocket connection established');
$onlineindicator.attr('src', greencircle); $('#onlineindicator').attr('src', window.greencircle);
}; };
ws.onmessage = (event) => { window.ws.onmessage = (event) => {
if ($('#onlineindicator').attr('src') !== window.greencircle) {
$('#onlineindicator').attr('src', window.greencircle);
}
let rep = JSON.parse(event.data); let rep = JSON.parse(event.data);
let cmd = rep.reply let cmd = rep.reply
let data = rep.data; let data = rep.data;
if (cmd && cmd.length > 0) { if (cmd && cmd.length > 0) {
switch (cmd) { switch (cmd) {
case "getCPUStatus": case "getCPUStatus":
$cpustatus.text("CPU : " + data) $('#cpustatus').text("CPU : " + data)
break; break;
case "getMemoryStatus": case "getMemoryStatus":
$ramstatus.text("RAM : " + data) $('#ramstatus').text("RAM : " + data)
break; break;
case "getDiskStatus": case "getDiskStatus":
$diskstatus.text("Disk : " + data) $('#diskstatus').text("Disk : " + data)
break; break;
case "getNetworkStatus": case "getNetworkStatus":
$networkstatus.text("Network : " + data) $('#networkstatus').text("Network : " + data)
break; break;
case "getSystemTime": case "getSystemTime":
$datetimetext.text(data) $('#datetimetext').text(data)
break; break;
} }
} }
}; };
ws.onclose = () => { window.ws.onclose = () => {
console.log('WebSocket connection closed'); console.log('WebSocket connection closed');
resetStatusIndicators(); resetStatusIndicators();
}; };
// ws.onerror = (error) => { // window.ws.onerror = (error) => {
// console.error('WebSocket error:', error); // console.error('WebSocket error:', error);
// }; // };
@@ -457,6 +716,18 @@ $(document).ready(function () {
} }
}); });
}) })
$('#usermanagement').click(() => {
sidemenu.hide();
$('#content').load('usermanagement.html', function (response, status, xhr) {
if (status === "success") {
console.log("User Management content loaded successfully");
// pindah ke usermanagement.js
} else {
console.error("Error loading user management content:", xhr.status, xhr.statusText);
}
});
});
$('#settinglink').click(() => { $('#settinglink').click(() => {
sidemenu.hide(); sidemenu.hide();
$('#content').load('setting.html', function (response, status, xhr) { $('#content').load('setting.html', function (response, status, xhr) {

View File

@@ -1,61 +1,11 @@
/**
* @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 * Currently selected soundbank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
*/ */
let selectedsoundrow = null; window.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 * Fill soundbank table body with values
@@ -63,6 +13,7 @@ function reloadSoundBank(APIURL = "SoundBank/") {
*/ */
function fill_soundbanktablebody(vv) { function fill_soundbanktablebody(vv) {
$('#soundbanktablebody').empty(); $('#soundbanktablebody').empty();
console.log("Filling soundbank table with " + vv.length + " items");
if (!Array.isArray(vv) || vv.length === 0) return; if (!Array.isArray(vv) || vv.length === 0) return;
vv.forEach(item => { vv.forEach(item => {
const row = `<tr> const row = `<tr>
@@ -77,17 +28,17 @@ function fill_soundbanktablebody(vv) {
$('#soundbanktablebody').append(row); $('#soundbanktablebody').append(row);
let $addedrow = $('#soundbanktablebody tr:last'); let $addedrow = $('#soundbanktablebody tr:last');
$addedrow.on('click', function () { $addedrow.on('click', function () {
if (selectedsoundrow) { if (window.selectedsoundrow) {
selectedsoundrow.find('td').css('background-color', ''); window.selectedsoundrow.find('td').css('background-color', '');
if (selectedsoundrow.is($(this))) { if (window.selectedsoundrow.is($(this))) {
selectedsoundrow = null; window.selectedsoundrow = null;
$('#btnRemove').prop('disabled', true); $('#btnRemove').prop('disabled', true);
$('#btnEdit').prop('disabled', true); $('#btnEdit').prop('disabled', true);
return; return;
} }
} }
$(this).find('td').css('background-color', '#ffeeba'); $(this).find('td').css('background-color', '#ffeeba');
selectedsoundrow = $(this); window.selectedsoundrow = $(this);
$('#btnRemove').prop('disabled', false); $('#btnRemove').prop('disabled', false);
$('#btnEdit').prop('disabled', false); $('#btnEdit').prop('disabled', false);
}); });
@@ -98,29 +49,13 @@ function fill_soundbanktablebody(vv) {
} }
/**
* 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 () { $(document).ready(function () {
console.log("soundbank.js loaded successfully"); console.log("soundbank.js loaded successfully");
reloadSoundbankFiles(); reloadSoundbankFiles();
$('#soundbanktablebody').empty(); $('#soundbanktablebody').empty();
selectedsoundrow = null; window.selectedsoundrow = null;
let $btnClear = $('#btnClear'); let $btnClear = $('#btnClear');
let $btnAdd = $('#btnAdd'); let $btnAdd = $('#btnAdd');
let $btnRemove = $('#btnRemove'); let $btnRemove = $('#btnRemove');
@@ -166,28 +101,33 @@ $(document).ready(function () {
$modalvoicetype.val(null); $modalvoicetype.val(null);
// fill modalpath options from soundbankfiles[] // fill modalpath options from soundbankfiles[]
// TODO read https://jeesite.com/front/jquery-select2/4.0/index.htm // TODO read https://jeesite.com/front/jquery-select2/4.0/index.htm
console.log("select2data has " + select2data.length + " items"); console.log("window.select2data has " + window.select2data.length + " items");
$('#modalpath').select2({ $('#modalpath').select2({
data: select2data data: window.select2data
}) })
} }
reloadSoundBank(APIURL); reloadSoundBank(APIURL, () => {
fill_soundbanktablebody(window.soundbankdata);
});
$('#findsoundbank').on('input', function () { $('#findsoundbank').on('input', function () {
let searchTerm = $(this).val().trim().toLowerCase(); let searchTerm = $(this).val().trim().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
selectedsoundrow = null; window.selectedsoundrow = null;
let filtered = soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm)); let filtered = window.soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm));
fill_soundbanktablebody(filtered); fill_soundbanktablebody(filtered);
} else { } else {
selectedsoundrow = null; window.selectedsoundrow = null;
fill_soundbanktablebody(soundbankdata); fill_soundbanktablebody(window.soundbankdata);
} }
}); });
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "Soundbank", (okdata) => { DoClear(APIURL, "Soundbank", (okdata) => {
reloadSoundBank(APIURL); reloadSoundBank(APIURL, () => {
fill_soundbanktablebody(window.soundbankdata);
alert("Success clear soundbank : " + okdata.message); alert("Success clear soundbank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear soundbank : " + errdata.message); alert("Error clear soundbank : " + errdata.message);
}); });
@@ -208,8 +148,8 @@ $(document).ready(function () {
}); });
}); });
$btnRemove.click(() => { $btnRemove.click(() => {
if (selectedsoundrow) { if (window.selectedsoundrow) {
let cells = selectedsoundrow.find('td'); let cells = window.selectedsoundrow.find('td');
/** @type {SoundBank} */ /** @type {SoundBank} */
let sb = { let sb = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -222,8 +162,10 @@ $(document).ready(function () {
} }
if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) { if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => {
reloadSoundBank(APIURL); reloadSoundBank(APIURL, () => {
fill_soundbanktablebody(window.soundbankdata);
alert("Success delete soundbank : " + okdata.message); alert("Success delete soundbank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete soundbank : " + errdata.message); alert("Error delete soundbank : " + errdata.message);
}); });
@@ -231,8 +173,8 @@ $(document).ready(function () {
} }
}); });
$btnEdit.click(() => { $btnEdit.click(() => {
if (selectedsoundrow) { if (window.selectedsoundrow) {
let cells = selectedsoundrow.find('td'); let cells = window.selectedsoundrow.find('td');
/** @type {SoundBank} */ /** @type {SoundBank} */
let sb = { let sb = {
index: cells.eq(0).text(), index: cells.eq(0).text(),
@@ -265,8 +207,10 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadSoundBank(APIURL); reloadSoundBank(APIURL, () => {
fill_soundbanktablebody(window.soundbankdata);
alert("Success import soundbank : " + okdata.message); alert("Success import soundbank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing soundbank from XLSX : " + errdata.message); alert("Error importing soundbank from XLSX : " + errdata.message);
}); });

View File

@@ -1,27 +1,18 @@
/**
* @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 // Currently selected sound channel row in the table
let selectedSoundChannel = null; window.selectedSoundChannel = null;
/** /**
* Fills the sound channel table body with the provided data. * Fills the sound channel table body with the provided data.
* @param {SoundChannel[]} vv Sound channel data to populate the table. * @param {SoundChannel[]} vv Sound channel data to populate the table.
*/ */
function fill_soundchanneltablebody(vv) { function fill_soundchanneltablebody(vv) {
const $tbody = $('#soundchanneltablebody'); let $tbody = $('#soundchanneltablebody');
const $btnEditSoundChannel = $('#btnEditSoundChannel'); let $btnEditSoundChannel = $('#btnEditSoundChannel');
const $tablesizeSoundChannel = $('#tablesizeSoundChannel'); let $tablesizeSoundChannel = $('#tablesizeSoundChannel');
$tbody.empty(); $tbody.empty();
$tablesizeSoundChannel.text('Table Length : N/A'); $tablesizeSoundChannel.text('Table Length : N/A');
if (!Array.isArray(vv) || vv.length === 0) return; if (!Array.isArray(vv) || vv.length === 0) return;
@@ -50,22 +41,7 @@ function fill_soundchanneltablebody(vv) {
$tablesizeSoundChannel.text("Table Size: " + vv.length); $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 () { $(document).ready(function () {
console.log("soundchannel.js loaded successfully"); console.log("soundchannel.js loaded successfully");
@@ -85,12 +61,14 @@ $(document).ready(function () {
$findsoundchannel.on('input', function () { $findsoundchannel.on('input', function () {
let searchTerm = $(this).val().toLowerCase(); let searchTerm = $(this).val().toLowerCase();
if (searchTerm.length==0){ if (searchTerm.length==0){
fill_soundchanneltablebody(SoundChannelList); window.selectedSoundChannel = null;
fill_soundchanneltablebody(window.soundChannels);
} else { } else {
let filteredChannels = SoundChannelList.filter(channel => window.selectedSoundChannel = null;
channel.index.toString().includes(searchTerm) || let filteredChannels = window.soundChannels.filter(xx =>
channel.description.toLowerCase().includes(searchTerm) || xx.index.toString().includes(searchTerm) ||
channel.ip.toLowerCase().includes(searchTerm) xx.channel.toLowerCase().includes(searchTerm) ||
xx.ip.toLowerCase().includes(searchTerm)
); );
fill_soundchanneltablebody(filteredChannels); fill_soundchanneltablebody(filteredChannels);
} }
@@ -105,11 +83,15 @@ $(document).ready(function () {
$soundchannelip.val(''); $soundchannelip.val('');
} }
reloadSoundChannel(API_SoundChannel); reloadSoundChannel(API_SoundChannel, () => {
fill_soundchanneltablebody(window.soundChannels);
});
$btnReinitializeSoundChannel.click(() => { $btnReinitializeSoundChannel.click(() => {
DoClear(API_SoundChannel, "SoundChannels", (okdata) => { DoClear(API_SoundChannel, "SoundChannels", (okdata) => {
reloadSoundChannel(API_SoundChannel); reloadSoundChannel(API_SoundChannel, () => {
fill_soundchanneltablebody(window.soundChannels);
alert("Success clear sound channels: " + okdata.message); alert("Success clear sound channels: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear sound channels: " + errdata.message); alert("Error clear sound channels: " + errdata.message);
}); });
@@ -153,8 +135,10 @@ $(document).ready(function () {
fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", {}, newsc, (okdata) => { fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", {}, newsc, (okdata) => {
reloadSoundChannel(API_SoundChannel); reloadSoundChannel(API_SoundChannel, () => {
fill_soundchanneltablebody(window.soundChannels);
alert("Success edit sound channel: " + okdata.message); alert("Success edit sound channel: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit sound channel: " + errdata.message); alert("Error edit sound channel: " + errdata.message);
}); });
@@ -174,8 +158,10 @@ $(document).ready(function () {
$btnImportSoundChannel.click(() => { $btnImportSoundChannel.click(() => {
DoImport(API_SoundChannel, (okdata) => { DoImport(API_SoundChannel, (okdata) => {
reloadSoundChannel(API_SoundChannel); reloadSoundChannel(API_SoundChannel, () => {
fill_soundchanneltablebody(window.soundChannels);
alert("Success import sound channels: " + okdata.message); alert("Success import sound channels: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing sound channels from XLSX: " + errdata.message); alert("Error importing sound channels from XLSX: " + errdata.message);
}); });

View File

@@ -0,0 +1,128 @@
/**
* @typedef {Object} UserDB
* @property {number} index Index number
* @property {string} username Username
* @property {string} password Password (plain)
* @property {string} location Location
* @property {string} soundbank_tags Soundbank variable tags separated by semicolon ;
* @property {string} messagebank_ann_id Messagebank announcement ID separated by semicolon ;
* @property {string} broadcastzones Broadcast zones separated by semicolon ;
*/
/** List of UserDB data loaded from server
* @type {UserDB[]}
*/
window.userdb = [];
/**
* Currently selected user row in table
* @type {JQuery<HTMLElement>|null}
*/
window.selecteduserrow = null;
/**
* Fill user table body with values
* @param {UserDB[]} vv values to fill
*/
function fill_usertablebody(vv) {
$('#usertablebody').empty();
if (!Array.isArray(vv) || vv.length === 0) {
$('#btnExport').prop('disabled', true);
return;
}
vv.forEach(item => {
const row = `<tr>
<td>${item.index}</td>
<td>${item.datenya}</td>
<td>${item.timenya}</td>
<td>${item.machine}</td>
<td>${item.description}</td>
</tr>`;
$('#usertablebody').append(row);
let $addedrow = $('#usertablebody tr:last');
$addedrow.on('click', function () {
if (window.selecteduserrow) {
window.selecteduserrow.find('td').css('background-color', '');
if (window.selecteduserrow.is($(this))) {
window.selecteduserrow = null;
$('#btnRemove').prop('disabled', true);
$('#btnEdit').prop('disabled', true);
return;
}
}
$(this).find('td').css('background-color', '#ffeeba');
window.selecteduserrow = $(this);
$('#btnRemove').prop('disabled', false);
$('#btnEdit').prop('disabled', false);
});
});
$('#tablesize').text("Table Size: " + vv.length);
$('#btnExport').prop('disabled', false);
}
/**
* Reload UserDB from server with date and filter
* @param {String} APIURL API URL endpoint , default "User/"
*/
function reloaduserDB(APIURL = "User/") {
window.userdb = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.userdb.push(...okdata);
fill_usertablebody(window.userdb);
}
}, (errdata) => {
alert("Error loading user database : " + errdata.message);
});
}
$(document).ready(function () {
console.log("usermanagement.js ready");
let $usertablebody = $('#usertablebody');
let $finduser = $('#finduser');
let $btnClear = $('#btnClear');
let $btnAdd = $('#btnAdd');
let $btnRemove = $('#btnRemove');
let $btnEdit = $('#btnEdit');
let $btnExport = $('#btnExport');
let $btnImport = $('#btnImport');
let APIURL = "User/";
// add / edit modal elements
let $addmodal = $('#addmodal');
let $modalindex = $('#modalindex');
let $modalusername = $('#modalusername');
let $modalpassword = $('#modalpassword');
let $modalverifypassword = $('#modalverifypassword');
let $modalsoundbank = $('#modalsoundbank');
let $modalmessagebank = $('#modalmessagebank');
let $modalbroadcastzones = $('#modalbroadcastzones');
let $btnShowSoundbankModal = $('#btnShowSoundbankModal');
let $btnShowMessagebankModal = $('#btnShowMessagebankModal');
let $btnShowBroaadcastZoneModal = $('#btnShowBroaadcastZoneModal');
let $usermanagementsave = $('#usermanagementsave');
let $usermanagementclose = $('#usermanagementclose');
// soundbank selection modal elements
let $soundbankmodal = $('#soundbankmodal');
let $soundbankselection = $('#soundbankselection');
let $soundbankselectionsave = $('#soundbankselectionsave');
let $soundbankselectionclose = $('#soundbankselectionclose');
// broadcast zone selection modal elements
let $broadcastzonemodal = $('#broadcastzonemodal');
let $broadcastzoneselection = $('#broadcastzoneselection');
let $broadcastzoneselectionsave = $('#broadcastzoneselectionsave');
let $broadcastzoneselectionclose = $('#broadcastzoneselectionclose');
// messagebank selection modal elements
let $messagebankmodal = $('#messagebankmodal');
let $messagebankselection = $('#messagebankselection');
let $messagebankselectionsave = $('#messagebankselectionsave');
let $messagebankselectionclose = $('#messagebankselectionclose');
$usertablebody.empty();
$finduser.on('input', function () {
});
});

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -24,34 +24,34 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col"><input type="text" id="broadcastzonedescription" class="form-control input-add" placeholder="description"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="broadcastzonedescription" class="form-control input-add" placeholder="description"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Sound Channel</p> <p class="text-add">Sound Channel</p>
</div> </div>
<div class="col"><select id="broadcastzonesoundchannel" class="form-control input-add" placeholder="sound channel"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="broadcastzonesoundchannel" class="form-control input-add" placeholder="sound channel"></select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Box</p> <p class="text-add">Box</p>
</div> </div>
<div class="col"><input type="text" id="broadcastzonebox" class="form-control input-add" placeholder="Box ID"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="broadcastzonebox" class="form-control input-add" placeholder="Box ID"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Relay</p> <p class="text-add">Relay</p>
</div> </div>
<div class="col"> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 pad-relay">
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<div class="row"> <div class="row">
@@ -167,9 +167,9 @@
</div> </div>
<div class="row"> <div class="row">
<div class="accordion" role="tablist" id="accordion-1"> <div class="accordion" role="tablist" id="accordion-1">
<div class="accordion-item"> <div class="accordion-item pad-accordion">
<h2 class="accordion-header" role="tab"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="true" aria-controls="accordion-1 .item-1">Sound Channel</button></h2> <h2 class="accordion-header" role="tab"><button class="accordion-button collapsed bg-heading1" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="false" aria-controls="accordion-1 .item-1">Sound Channel</button></h2>
<div class="accordion-collapse collapse show item-1" role="tabpanel" data-bs-parent="#accordion-1"> <div class="accordion-collapse collapse item-1" role="tabpanel" data-bs-parent="#accordion-1">
<div class="accordion-body"> <div class="accordion-body">
<div class="row"> <div class="row">
<div class="col-md-7 col-lg-7 col-xl-7"></div> <div class="col-md-7 col-lg-7 col-xl-7"></div>
@@ -178,11 +178,11 @@
</div> </div>
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findsoundchannel" placeholder="Search keyword" name="findsoundbank"></div> <div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findsoundchannel" placeholder="Search keyword" name="findsoundbank"></div>
</div> </div>
<div class="row"> <div class="row pad-search">
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnReinitializeSoundChannel" type="button">Re-Initialize</button></div> <div class="col-6 col-sm-6 col-md-3 col-lg-3 col-xl-3"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnReinitializeSoundChannel" type="button">Re-Initialize</button></div>
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEditSoundChannel" type="button">Edit</button></div> <div class="col-6 col-sm-6 col-md-3 col-lg-3 col-xl-3"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEditSoundChannel" type="button">Edit</button></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExportSoundChannel" type="button">Export</button></div> <div class="col-6 col-md-3 col-lg-3 col-xl-3 col-sm--6"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExportSoundChannel" type="button">Export</button></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImportSoundChannel" type="button">Import</button></div> <div class="col-6 col-sm-6 col-md-3 col-lg-3 col-xl-3"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImportSoundChannel" type="button">Import</button></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@@ -206,9 +206,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="accordion-item"> <div class="accordion-item pad-accordion">
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">Broadcast Zones</button></h2> <h2 class="accordion-header" role="tab"><button class="accordion-button bg-heading2" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="true" aria-controls="accordion-1 .item-2">Broadcast Zones</button></h2>
<div class="accordion-collapse collapse item-2" role="tabpanel" data-bs-parent="#accordion-1"> <div class="accordion-collapse collapse show item-2" role="tabpanel" data-bs-parent="#accordion-1">
<div class="accordion-body"> <div class="accordion-body">
<div class="row"> <div class="row">
<div class="col-md-7 col-lg-7 col-xl-7"></div> <div class="col-md-7 col-lg-7 col-xl-7"></div>
@@ -217,7 +217,7 @@
</div> </div>
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findzone" placeholder="Search keyword" name="findsoundbank"></div> <div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findzone" placeholder="Search keyword" name="findsoundbank"></div>
</div> </div>
<div class="row"> <div class="row pad-search">
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div> <div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div> <div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div> <div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
@@ -238,8 +238,8 @@
<th class="col-sm-1">No</th> <th class="col-sm-1">No</th>
<th class="col-sm-2">Description</th> <th class="col-sm-2">Description</th>
<th class="col-sm-2">SoundChannel</th> <th class="col-sm-2">SoundChannel</th>
<th class="col-sm-2">Box</th> <th class="col-sm-2">ID</th>
<th class="col">Relay</th> <th class="col">BP</th>
</tr> </tr>
</thead> </thead>
<tbody id="broadcastzonetablebody"></tbody> <tbody id="broadcastzonetablebody"></tbody>
@@ -259,25 +259,25 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p>Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col"><input class="w-25" type="text" id="soundchannelindex" readonly="" placeholder="index"></div> <div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-25 form-control input-add" type="text" id="soundchannelindex" readonly="" placeholder="index"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p>Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col"><input class="w-100" type="text" id="soundchanneldescription" placeholder="Description"></div> <div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchanneldescription" placeholder="Description"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p>IP Address</p> <p class="text-add">IP Address</p>
</div> </div>
<div class="col"><input class="w-100" type="text" id="soundchannelip" placeholder="IP Address"></div> <div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchannelip" placeholder="IP Address"></div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-light" id="soundchannelclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="soundchannelsave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundchannelclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundchannelsave" type="button">Save</button></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -60,6 +60,10 @@
<path d="M19,7H9C7.9,7,7,7.9,7,9v10c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V9C21,7.9,20.1,7,19,7z M19,9v2H9V9H19z M13,15v-2h2v2H13z M15,17v2h-2v-2H15z M11,15H9v-2h2V15z M17,13h2v2h-2V13z M9,17h2v2H9V17z M17,19v-2h2v2H17z M6,17H5c-1.1,0-2-0.9-2-2V5 c0-1.1,0.9-2,2-2h10c1.1,0,2,0.9,2,2v1h-2V5H5v10h1V17z"></path> <path d="M19,7H9C7.9,7,7,7.9,7,9v10c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V9C21,7.9,20.1,7,19,7z M19,9v2H9V9H19z M13,15v-2h2v2H13z M15,17v2h-2v-2H15z M11,15H9v-2h2V15z M17,13h2v2h-2V13z M9,17h2v2H9V17z M17,19v-2h2v2H17z M6,17H5c-1.1,0-2-0.9-2-2V5 c0-1.1,0.9-2,2-2h10c1.1,0,2,0.9,2,2v1h-2V5H5v10h1V17z"></path>
</g> </g>
</svg>&nbsp; &nbsp;Log</a></li> </svg>&nbsp; &nbsp;Log</a></li>
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="usermanagement" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
<path d="M0 0h24v24H0z" fill="none"></path>
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path>
</svg>&nbsp;User Management</a></li>
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="settinglink" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;"> <li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="settinglink" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
<g> <g>
<path d="M0,0h24v24H0V0z" fill="none"></path> <path d="M0,0h24v24H0V0z" fill="none"></path>

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -16,7 +16,7 @@
<h2 style="text-align: center;">Language Link</h2> <h2 style="text-align: center;">Language Link</h2>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-row-search">
<div class="col-md-7 col-lg-7 col-xl-7"></div> <div class="col-md-7 col-lg-7 col-xl-7"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search"> <div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
<p class="text-add">Search</p> <p class="text-add">Search</p>
@@ -58,22 +58,22 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col"><input class="w-25 form-control input-add" type="text" id="languagelinkindex" readonly=""></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="languagelinkindex" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Tag</p> <p class="text-add">Tag</p>
</div> </div>
<div class="col"><input type="text" id="languagelinktag" class="form-control input-add"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="languagelinktag" class="form-control input-add"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Language</p> <p class="text-add">Language</p>
</div> </div>
<div class="col text-add"> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 text-add">
<div class="form-check"><input class="form-check-input" type="checkbox" id="langId" name="languages[]" value="id"><label class="form-check-label" for="langId">Indonesia</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="langId" name="languages[]" value="id"><label class="form-check-label" for="langId">Indonesia</label></div>
<div class="form-check"><input class="form-check-input" type="checkbox" id="langLocal" name="languages[]" value="id"><label class="form-check-label" for="langId-1">Local</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="langLocal" name="languages[]" value="id"><label class="form-check-label" for="langId-1">Local</label></div>
<div class="form-check"><input class="form-check-input" type="checkbox" id="langEn" name="languages[]" value="en"><label class="form-check-label" for="langEn">English</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="langEn" name="languages[]" value="en"><label class="form-check-label" for="langEn">English</label></div>

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -17,15 +17,16 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 col-sm col-md-2 col-lg-2 col-xl-2"> <div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2">
<p class="text-add">Select Log Date</p> <p class="text-add">Select Log Date</p>
</div> </div>
<div class="col-6 col-sm col-md-2 col-lg-2 col-xl-2"><input id="logdate" class="form-control" type="date"></div> <div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><input id="logdate" class="form-control" type="date"></div>
<div class="col-md-4 col-lg-4 col-xl-4"><button class="btn btn-primary w-100 h-100" id="btnExport" type="button">Export</button></div> <div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
<div class="col-6 col-sm col-md-2 col-lg-2 col-xl-2"> <div class="col-md-2 col-lg-2 col-xl-2"></div>
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2">
<p class="text-add">Search</p> <p class="text-add">Search</p>
</div> </div>
<div class="col-6 col-sm col-md-2 col-lg-2 col-xl-2"><input type="text" id="searchfilter" class="form-control" placeholder="Search Filter"></div> <div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><input type="text" id="searchfilter" class="form-control" placeholder="Search Filter"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -16,12 +16,12 @@
<h2 style="text-align: center;">Message Bank</h2> <h2 style="text-align: center;">Message Bank</h2>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-row-search">
<div class="col-md-7 col-lg-7 col-xl-7"></div> <div class="col-md-7 col-lg-7 col-xl-7"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search"> <div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<p class="text-add">Search</p> <p class="text-add">Search</p>
</div> </div>
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findmessage" placeholder="Search keyword" name="findsoundbank"></div> <div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control pad-search" type="text" id="findmessage" placeholder="Search keyword" name="findsoundbank"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div> <div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
@@ -62,22 +62,22 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col"><input class="w-25 input-add form-control" type="text" id="messageindex" placeholder="Index" readonly=""></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="messageindex" placeholder="Index" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col"><input type="text" id="messagedescription" class="input-add form-control" placeholder="Description"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="messagedescription" class="input-add form-control" placeholder="Description"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Language</p> <p class="text-add">Language</p>
</div> </div>
<div class="col"><select id="messagelanguage" class="input-add form-control"> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="messagelanguage" class="input-add form-control">
<option value="INDONESIA">Indonesia</option> <option value="INDONESIA">Indonesia</option>
<option value="LOCAL">Local</option> <option value="LOCAL">Local</option>
<option value="ENGLISH">English</option> <option value="ENGLISH">English</option>
@@ -87,16 +87,16 @@
</select></div> </select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">ANN ID</p> <p class="text-add">ANN ID</p>
</div> </div>
<div class="col"><input type="number" id="messageannid" class="input-add form-control" min="1" max="100" value="1" step="1" placeholder="Announcement ID"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="number" id="messageannid" class="input-add form-control" min="1" max="100" value="1" step="1" placeholder="Announcement ID"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Voice Type</p> <p class="text-add">Voice Type</p>
</div> </div>
<div class="col"><select id="messagevoicetype" class="input-add form-control"> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="messagevoicetype" class="input-add form-control">
<option value="VOICE_1">Voice 1</option> <option value="VOICE_1">Voice 1</option>
<option value="VOICE_2">Voice 2</option> <option value="VOICE_2">Voice 2</option>
<option value="VOICE_3">Voice 3</option> <option value="VOICE_3">Voice 3</option>

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -62,46 +62,46 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col"><input class="w-25 input-add form-control" type="text" id="modalindex" readonly=""></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="modalindex" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col"><input type="text" id="modaldescription" class="form-control input-add"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modaldescription" class="form-control input-add"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">TAG</p> <p class="text-add">TAG</p>
</div> </div>
<div class="col"><input type="text" id="modaltag" class="form-control input-add"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modaltag" class="form-control input-add"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Category</p> <p class="text-add">Category</p>
</div> </div>
<div class="col"><select id="modalcategory" class="input-add form-select"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalcategory" class="input-add form-select"></select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Language</p> <p class="text-add">Language</p>
</div> </div>
<div class="col"><select id="modallanguage" class="input-add form-select"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modallanguage" class="input-add form-select"></select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Voice Type</p> <p class="text-add">Voice Type</p>
</div> </div>
<div class="col"><select id="modalvoicetype" class="input-add form-select"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalvoicetype" class="input-add form-select"></select></div>
</div> </div>
<div class="row" style="height: 100px;"> <div class="row" style="height: 100px;">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Path</p> <p class="text-add">Path</p>
</div> </div>
<div class="col"><select class="w-100 js-example-basic-single" id="modalpath" name="modalpath"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select class="w-100 js-example-basic-single input-add form-select" id="modalpath" name="modalpath"></select></div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbanksave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbanksave" type="button">Save</button></div>

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -12,13 +12,13 @@
<body> <body>
<div class="card" id="streamercard"> <div class="card" id="streamercard">
<div class="card-body"> <div class="card-body card-channel">
<h4 class="card-title" id="streamertitle">Channel 01</h4> <h4 class="card-title" id="streamertitle">Channel 01</h4>
<div class="row"> <div class="row">
<div class="col-8"> <div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 col-xxl-6">
<h6 class="text-muted mb-2" id="streamerip">IP :&nbsp;192.168.10.10</h6> <h6 class="text-muted mb-2" id="streamerip">IP :&nbsp;192.168.10.10</h6>
</div> </div>
<div class="col"> <div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 col-xxl-6">
<h6 class="text-muted mb-2" id="streamerbuffer">Free : 64KB</h6> <h6 class="text-muted mb-2" id="streamerbuffer">Free : 64KB</h6>
</div> </div>
</div> </div>

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_03Sept25</title> <title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
@@ -16,7 +16,7 @@
<h2 style="text-align: center;">Schedule Bank</h2> <h2 style="text-align: center;">Schedule Bank</h2>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-row-search">
<div class="col-md-7 col-lg-7 col-xl-7"></div> <div class="col-md-7 col-lg-7 col-xl-7"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search"> <div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
<p class="text-add">Search</p> <p class="text-add">Search</p>
@@ -63,110 +63,110 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col"><input class="w-25 input-add form-control" type="text" id="scheduleid" readonly=""></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="scheduleid" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col"><input type="text" id="scheduledescription" class="input-add form-control"></div> <div class="col"><input type="text" id="scheduledescription" class="input-add form-control"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Day</p> <p class="text-add">Day</p>
</div> </div>
<div class="col"> <div class="col">
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="scheduleeveryday" name="dayselection" value="Everyday"><label class="form-check-label" for="formCheck-1">Everyday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="scheduleeveryday" name="dayselection" value="Everyday"><label class="form-check-label" for="formCheck-1">Everyday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulesunday" name="dayselection" value="Sunday"><label class="form-check-label" for="formCheck-8">Sunday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulesunday" name="dayselection" value="Sunday"><label class="form-check-label" for="formCheck-8">Sunday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulemonday" name="dayselection" value="Monday"><label class="form-check-label" for="formCheck-7">Monday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulemonday" name="dayselection" value="Monday"><label class="form-check-label" for="formCheck-7">Monday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="scheduletuesday" name="dayselection" value="Tuesday"><label class="form-check-label" for="formCheck-6">Tuesday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="scheduletuesday" name="dayselection" value="Tuesday"><label class="form-check-label" for="formCheck-6">Tuesday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulewednesday" name="dayselection" value="Wednesday"><label class="form-check-label" for="formCheck-5">Wednesday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulewednesday" name="dayselection" value="Wednesday"><label class="form-check-label" for="formCheck-5">Wednesday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulethursday" name="dayselection" value="Thursday"><label class="form-check-label" for="formCheck-4">Thursday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulethursday" name="dayselection" value="Thursday"><label class="form-check-label" for="formCheck-4">Thursday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulefriday" name="dayselection" value="Friday"><label class="form-check-label" for="formCheck-3">Friday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulefriday" name="dayselection" value="Friday"><label class="form-check-label" for="formCheck-3">Friday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulesaturday" name="dayselection" value="Saturday"><label class="form-check-label" for="formCheck-2">Saturday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulesaturday" name="dayselection" value="Saturday"><label class="form-check-label" for="formCheck-2">Saturday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col-7 col-sm-7 col-md-7 col-lg-6 col-xl-6 pad-day">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulespecialdate" name="dayselection"><label class="form-check-label" for="formCheck-9">Special Date</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="schedulespecialdate" name="dayselection"><label class="form-check-label" for="formCheck-9">Special Date</label></div>
</div> </div>
<div class="col"><input id="scheduledate" type="date"></div> <div class="col-sm-5 col-md-5 col-lg-6 col-xl-6"><input id="scheduledate" class="form-control" type="date"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Time</p> <p class="text-add">Time</p>
</div> </div>
<div class="col"> <div class="col">
<div class="row w-100 h-100"> <div class="row w-100 h-100">
<div class="col-3"><input class="w-100 h-100" type="number" id="schedulehour" value="0" min="0" max="23" step="1"></div> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3"><input type="number" id="schedulehour" class="input-add form-control class100" value="0" min="0" max="23" step="1"></div>
<div class="col-1"> <div class="col-2 col-sm-2 col-md-2 col-lg-3 col-xl-3">
<p class="w-100 h-100">(H)</p> <p class="pad-time">(H)</p>
</div> </div>
<div class="col-3"><input class="w-100 h-100" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3"><input class="w-100 input-add form-control" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div>
<div class="col-1"> <div class="col-2 col-sm-2 col-md-2 col-lg-3 col-xl-3">
<p class="w-100 h-100">(M)</p> <p class="pad-time">(M)</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Sound Path</p> <p class="text-add">Sound Path</p>
</div> </div>
<div class="col"><input type="text" id="schedulesoundpath" class="input-add form-control"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="schedulesoundpath" class="input-add form-control"></select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Repeat</p> <p class="text-add">Repeat</p>
</div> </div>
<div class="col"><input class="w-25 form-select input-add" type="number" id="schedulerepeat" min="0" max="5" step="1" value="0"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-select input-add" type="number" id="schedulerepeat" min="0" max="5" step="1" value="0"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Enable</p> <p class="text-add">Enable</p>
</div> </div>
<div class="col"><input type="checkbox" id="scheduleenable" class="form-check-input form-check text-add" checked=""></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="checkbox" id="scheduleenable" class="form-check-input form-check text-add" checked=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-3"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Broadcast Zones</p> <p class="text-add">Broadcast Zones</p>
</div> </div>
<div class="col border p-2" id="schedulezones"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 border" id="schedulezones"></div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="scheduleclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic class25 color-add" id="schedulesave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="scheduleclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic class25 color-add" id="schedulesave" type="button">Save</button></div>

View File

@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
<body class="bg-body">
<div class="row">
<div class="col w-100 h-100 pad-header">
<h2 style="text-align: center;">User Management</h2>
</div>
</div>
<div class="row">
<div class="col-md-7 col-lg-7 col-xl-7"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
<p class="text-add">Search</p>
</div>
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="finduser" placeholder="Search keyword" name="findsoundbank"></div>
</div>
<div class="row">
<div class="col">
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
</div>
</div>
<div class="row">
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
</div>
<div class="row">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th class="col-sm-1">Username</th>
<th class="col-sm-1">Location</th>
<th class="col">Soundbank</th>
<th class="col-sm-2">Messagebank</th>
<th class="col-sm-2">Broadcast Zones</th>
</tr>
</thead>
<tbody id="usertablebody"></tbody>
</table>
</div>
</div>
<div class="modal fade border-0" role="dialog" tabindex="-1" id="addmodal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header show">
<h4 class="modal-title align-content-center">Add / Edit User</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="modalindex" readonly=""></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Username</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalusername" class="form-control input-add"></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Password</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="password" id="modalpassword" class="form-control input-add"></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Verify Password</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="password" id="modalverifypassword" class="input-add form-control"></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Sound Bank</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalsoundbank" class="input-add"><button class="btn btn-primary" id="btnShowSoundbankModal" type="button">Select</button></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Message Bank</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalmessagebank" class="input-add"><button class="btn btn-primary" id="btnShowMessagebankModal" type="button">Select</button></div>
</div>
<div class="row" style="height: 100px;">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Broadcast Zones</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalbroadcastzones" class="input-add" name="modalpath"><button class="btn btn-primary" id="btnShowBroaadcastZoneModal" type="button">Select</button></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="usermanagementclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="usermanagementsave" type="button">Save</button></div>
</div>
</div>
</div>
<div class="modal fade" role="dialog" tabindex="-1" id="soundbankmodal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Sound Bank Selection</h4>
</div>
<div class="modal-body">
<p>(mon, ganti list checkbox, dengan id=soundbankselection</p>
</div>
<div class="modal-footer"><button class="btn btn-light" id="soundbankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="soundbankselectionsave" type="button">Save</button></div>
</div>
</div>
</div>
<div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Broadcast Zones Selection</h4>
</div>
<div class="modal-body">
<p>(mon, ganti list checkbox, dengan id=broadcastzoneselection</p>
</div>
<div class="modal-footer"><button class="btn btn-light" id="broadcastzoneselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="broadcastzoneselectionsave" type="button">Save</button></div>
</div>
</div>
</div>
<div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Message Bank Selection</h4>
</div>
<div class="modal-body">
<p>(mon, ganti list checkbox, dengan id=messagebankselection</p>
</div>
<div class="modal-footer"><button class="btn btn-light" id="messagebankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="messagebankselectionsave" type="button">Save</button></div>
</div>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/usermanagement.js"></script>
</body>
</html>

View File

@@ -1,4 +1,5 @@
import audio.AudioPlayer import audio.AudioPlayer
import audio.UDPReceiver
import barix.BarixConnection import barix.BarixConnection
import barix.TCP_Barix_Command_Server import barix.TCP_Barix_Command_Server
import com.sun.jna.Platform import com.sun.jna.Platform
@@ -20,7 +21,10 @@ import kotlin.concurrent.fixedRateTimer
lateinit var db: MariaDB lateinit var db: MariaDB
lateinit var audioPlayer: AudioPlayer lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap() val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver
const val version = "0.0.2 (23/09/2025)" const val version = "0.0.2 (23/09/2025)"
// AAS 64 channels
const val max_channel = 64
// dipakai untuk pilih voice type, bisa diganti via web nanti // dipakai untuk pilih voice type, bisa diganti via web nanti
var selected_voice = VoiceType.VOICE_1.name var selected_voice = VoiceType.VOICE_1.name
@@ -74,16 +78,22 @@ fun main() {
)) ))
web.Start() web.Start()
udpreceiver = UDPReceiver()
if (udpreceiver.Start()) {
Logger.info { "UDP Receiver started on port 5002" }
} else {
Logger.error { "Failed to start UDP Receiver on port 5002" }
}
val androidserver = TCP_Android_Command_Server() val androidserver = TCP_Android_Command_Server()
androidserver.StartTcpServer(5003){ androidserver.StartTcpServer(5003){
Logger.info { it } Logger.info { it }
db.logDB.Add(Log.NewLog("ANDROID", it)) db.logDB.Add(Log.NewLog("ANDROID", it))
} }
val barixserver = TCP_Barix_Command_Server() val barixserver = TCP_Barix_Command_Server()
barixserver.StartTcpServer { cmd -> barixserver.StartTcpServer { cmd ->
Logger.info { cmd } //Logger.info { cmd }
val _streamer = StreamerOutputs[cmd.ipaddress] val _streamer = StreamerOutputs[cmd.ipaddress]
val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress } val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress }
if (_streamer == null) { if (_streamer == null) {
@@ -95,7 +105,8 @@ fun main() {
_bc.bufferRemain = cmd.buffremain _bc.bufferRemain = cmd.buffremain
_bc.statusData = cmd.statusdata _bc.statusData = cmd.statusdata
StreamerOutputs[cmd.ipaddress] = _bc StreamerOutputs[cmd.ipaddress] = _bc
} Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
} else Logger.warn { "soundChannelDB doesn't have soundchannel with IP ${cmd.ipaddress}" }
} else { } else {
// sudah ada, update data // sudah ada, update data
@@ -123,6 +134,7 @@ fun main() {
androidserver.StopTcpCommand() androidserver.StopTcpCommand()
onlinechecker.cancel() onlinechecker.cancel()
web.Stop() web.Stop()
udpreceiver.Stop()
audioPlayer.Close() audioPlayer.Close()
db.close() db.close()
Logger.info { "All services stopped, exiting application." } Logger.info { "All services stopped, exiting application." }

View File

@@ -7,15 +7,18 @@ import codes.Somecodes.Companion.SoundbankResult_directory
import codes.Somecodes.Companion.ValidFile import codes.Somecodes.Companion.ValidFile
import codes.Somecodes.Companion.ValidString import codes.Somecodes.Companion.ValidString
import codes.Somecodes.Companion.dateformat1 import codes.Somecodes.Companion.dateformat1
import codes.Somecodes.Companion.datetimeformat1
import codes.Somecodes.Companion.timeformat2 import codes.Somecodes.Companion.timeformat2
import content.Category import content.Category
import content.Language import content.Language
import content.ScheduleDay import content.ScheduleDay
import database.Messagebank import database.Messagebank
import database.QueueTable
import database.Soundbank import database.Soundbank
import org.tinylog.Logger import org.tinylog.Logger
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
import java.util.function.Consumer import java.util.function.Consumer
@@ -36,16 +39,24 @@ class MainExtension01 {
*/ */
fun AllBroadcastZonesValid(bz: List<String>): Boolean { fun AllBroadcastZonesValid(bz: List<String>): Boolean {
if (bz.isNotEmpty()) { if (bz.isNotEmpty()) {
println("StreamerOutputs: $StreamerOutputs")
val validchannels = bz val validchannels = bz
// check apakah tiap zone ada di database broadcast zones // check apakah tiap zone ada di database broadcast zones
.filter { z1 -> .filter { z1 ->
db.broadcastDB.List.find { z2 -> z2.SoundChannel == z1 } != null println("Checking broadcast zone $z1")
val xx = db.broadcastDB.List.find { z2 -> z2.description == z1 }
println("Found in DB: $xx")
xx != null
} }
// check apakah tiap zone ada di SoundChannelList dan Online // check apakah tiap zone ada di SoundChannelList dan Online
.filter { z3 -> .filter { z3 ->
StreamerOutputs.any { sc -> sc.value.channel == z3 && sc.value.isOnline() } println("Checking if zone $z3 is in StreamerOutputs and online")
val xx = StreamerOutputs.values.find { it.channel == z3 }
println("Is online: $xx")
xx!= null
} }
println("Valid channels: $validchannels")
// kalau jumlah valid channel sama dengan jumlah broadcast zone, berarti semua valid // kalau jumlah valid channel sama dengan jumlah broadcast zone, berarti semua valid
return validchannels.size == bz.size return validchannels.size == bz.size
} }
@@ -481,6 +492,7 @@ class MainExtension01 {
fun Read_Queue_Paging(){ fun Read_Queue_Paging(){
db.queuepagingDB.Get() db.queuepagingDB.Get()
for (qp in db.queuepagingDB.List) { for (qp in db.queuepagingDB.List) {
println("Processing QueuePaging $qp")
if (qp.BroadcastZones.isNotBlank()) { if (qp.BroadcastZones.isNotBlank()) {
val zz = qp.BroadcastZones.split(";") val zz = qp.BroadcastZones.split(";")
if (AllBroadcastZonesValid(zz)) { if (AllBroadcastZonesValid(zz)) {
@@ -612,10 +624,14 @@ class MainExtension01 {
fun Read_Queue_Table(){ fun Read_Queue_Table(){
db.queuetableDB.Get() db.queuetableDB.Get()
db.queuetableDB.List.forEach { qa -> db.queuetableDB.List.forEach { qa ->
println("Processing QueueTable $qa")
if (qa.BroadcastZones.isNotEmpty()) { if (qa.BroadcastZones.isNotEmpty()) {
val zz = qa.BroadcastZones.split(";") val zz = qa.BroadcastZones.split(";")
println("Broadcast zones: $zz")
if (AllBroadcastZonesValid(zz)) { if (AllBroadcastZonesValid(zz)) {
println("All broadcast zones valid")
if (AllBroadcastZoneIdle(zz)) { if (AllBroadcastZoneIdle(zz)) {
println("All broadcast zones idle")
if (qa.Type == "SOUNDBANK") { if (qa.Type == "SOUNDBANK") {
val variables = Get_Soundbank_Data(qa.SB_TAGS) val variables = Get_Soundbank_Data(qa.SB_TAGS)
val languages = qa.Language.split(";") val languages = qa.Language.split(";")
@@ -809,7 +825,19 @@ class MainExtension01 {
it.Day == ddmmyyyy it.Day == ddmmyyyy
} }
if (specialdate != null) { if (specialdate != null) {
// TODO Masukin ke queue table sebagai schedule special date val qt = QueueTable(
0u,
LocalDateTime.now().format(datetimeformat1),
"AAS",
"TIMER",
specialdate.Description,
specialdate.Soundpath,
specialdate.BroadcastZones,
1.toUInt(),
specialdate.Language
)
db.queuetableDB.Add(qt)
return
} }
// cek weekly schedule // cek weekly schedule
@@ -825,7 +853,19 @@ class MainExtension01 {
} }
} }
if (weekly != null) { if (weekly != null) {
// TODO Masukin ke queue table sebagai schedule weekly val qt = QueueTable(
0u,
LocalDateTime.now().format(datetimeformat1),
"AAS",
"TIMER",
weekly.Description,
weekly.Soundpath,
weekly.BroadcastZones,
1.toUInt(),
weekly.Language
)
db.queuetableDB.Add(qt)
return
} }
// check daily schedule // check daily schedule
@@ -833,7 +873,19 @@ class MainExtension01 {
it.Day == ScheduleDay.Everyday.name it.Day == ScheduleDay.Everyday.name
} }
if (daily != null) { if (daily != null) {
// TODO Masukin ke queue table sebagai schedule daily val qt = QueueTable(
0u,
LocalDateTime.now().format(datetimeformat1),
"AAS",
"TIMER",
daily.Description,
daily.Soundpath,
daily.BroadcastZones,
1.toUInt(),
daily.Language
)
db.queuetableDB.Add(qt)
return
} }
} }

View File

@@ -126,6 +126,14 @@ class AudioPlayer (var samplingrate: Int) {
return result return result
} }
fun WavWriter(data: ByteArray, target: String, callback: BiConsumer<Boolean, String>) {
val source = AudioFileInfo()
source.bytes = data
source.fileName = "In-Memory Data"
val sources = listOf(source)
WavWriter(sources, target, callback)
}
/** /**
* Writes the audio data from the sources to a WAV file. * Writes the audio data from the sources to a WAV file.
* @param sources List of AudioFileInfo objects containing the audio data to write. * @param sources List of AudioFileInfo objects containing the audio data to write.

79
src/audio/UDPReceiver.kt Normal file
View File

@@ -0,0 +1,79 @@
package audio
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.DatagramSocket
import java.util.function.Consumer
@Suppress("unused")
/**
* UDPReceiver is a class that listens for UDP packets on a specified port.
* It is designed to run in a separate thread and can be stopped when no longer needed.
* @param portnumber The port to listen for incoming UDP packets (default is 5002
*/
class UDPReceiver(val portnumber: Int = 5002) {
private lateinit var socket : DatagramSocket
private var isRunning = false
private val dataCallback = mutableMapOf<String, Consumer<ByteArray>>()
/**
* Start listening for UDP packets on the specified port.
* @return true if successful, false otherwise
*/
fun Start() : Boolean{
return try {
socket = DatagramSocket(portnumber)
isRunning = true
CoroutineScope(Dispatchers.IO).launch {
while(isRunning){
try {
val buffer = ByteArray(2048)
val packet = java.net.DatagramPacket(buffer, buffer.size)
socket.receive(packet)
val data = ByteArray(packet.length)
System.arraycopy(packet.data, 0, data, 0, packet.length)
dataCallback[packet.address.hostAddress].let {
it?.accept(data)
}
} catch (e: Exception) {
if (isRunning) {
println("Error receiving UDP packet: ${e.message}")
}
}
}
}
true
} catch (e: Exception) {
false
}
}
/**
* Register a callback function to be called when data is received from the specified IP address.
* @param ipaddress The IP address to listen for incoming UDP packets.
* @param callback A callback function that will be called when data is received from the specified IP address.
*/
fun RequestDataFrom(ipaddress: String, callback: Consumer<ByteArray>){
dataCallback[ipaddress] = callback
}
/**
* Unregister the callback function for the specified IP address.
* @param ipaddress The IP address to stop listening for incoming UDP packets.
*/
fun StopRequestDataFrom(ipaddress: String){
dataCallback.remove(ipaddress)
}
/**
* Stop listening for UDP packets and close the socket.
*/
fun Stop(){
if (isRunning){
isRunning = false
socket.close()
}
}
}

View File

@@ -16,6 +16,7 @@ import java.net.InetSocketAddress
import java.util.function.BiConsumer import java.util.function.BiConsumer
import java.util.function.Consumer import java.util.function.Consumer
@Deprecated("Sepertinya gak jadi pake")
@Suppress("unused") @Suppress("unused")
/** /**
* UDPReceiverToFile is a class that listens for UDP packets on a specified address and port * UDPReceiverToFile is a class that listens for UDP packets on a specified address and port

View File

@@ -13,6 +13,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Deprecated("Sepertinya gak jadi pake ini")
@Suppress("unused") @Suppress("unused")
class UDPSenderFromFile(val fileName: String, val bytesPerPackage: Int=1024, targetIP: Array<String>, targetPort: Int ) { class UDPSenderFromFile(val fileName: String, val bytesPerPackage: Int=1024, targetIP: Array<String>, targetPort: Int ) {
val bass: Bass = Bass.Instance val bass: Bass = Bass.Instance

View File

@@ -9,11 +9,11 @@ import java.util.function.Consumer
@Suppress("unused") @Suppress("unused")
class TCP_Barix_Command_Server { class TCP_Barix_Command_Server {
private var tcpserver: ServerSocket? = null lateinit var tcpserver: ServerSocket
private var job: Job? = null lateinit var job: Job
private val socketMap = mutableMapOf<String, Socket>() private val socketMap = mutableMapOf<String, Socket>()
private val regex = """\$\\"STATUSBARIX;(\d+);(\d+);?(\d)?\\"$""" private val regex = """STATUSBARIX;(\d+);(\d+);?(\d)?"""
private val pattern = Regex(regex) private val pattern = Regex(regex)
/** /**
@@ -27,25 +27,23 @@ class TCP_Barix_Command_Server {
val tcp = ServerSocket(port) val tcp = ServerSocket(port)
tcpserver = tcp tcpserver = tcp
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {
Logger.info { "TCP server started" } Logger.info { "TCP StreamerOutput server started on port $port" }
while (isActive) { while (isActive) {
if (tcpserver?.isClosed == true) break if (tcpserver.isClosed) break
try { try {
tcpserver?.accept().use { socket ->
{ val socket = tcpserver.accept()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
if (socket != null) { val key : String = socket.inetAddress.hostAddress
val key : String = socket.inetAddress.hostAddress+":"+socket.port
socketMap[key] = socket socketMap[key] = socket
Logger.info { "Start communicating with $key" } Logger.info { "Start communicating with Streamer Output with IP : $key" }
socket.getInputStream().use { din -> val din = socket.getInputStream()
{
while (isActive) { while (isActive) {
if (din.available()>0){ if (din.available()>0){
val bb = ByteArray(din.available()) val bb = ByteArray(din.available())
din.read(bb) din.read(bb)
// B4A format, 4 bytes di depan adalah size // B4A format, 4 bytes di depan adalah size
val str = String(bb) val str = String(bb, 4, bb.size - 4)
if (ValidString(str)) { if (ValidString(str)) {
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$ // Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
pattern.find(str)?.let { matchResult -> pattern.find(str)?.let { matchResult ->
@@ -56,7 +54,7 @@ class TCP_Barix_Command_Server {
buffremain.toInt(), buffremain.toInt(),
statusdata.toIntOrNull() ?: 0 statusdata.toIntOrNull() ?: 0
) )
Logger.info { "Received valid command from $key : $status" } //Logger.info { "Received valid command from $key : $status" }
cb.accept(status) cb.accept(status)
} ?: run { } ?: run {
Logger.warn { "Invalid command format from $key : $str" } Logger.warn { "Invalid command format from $key : $str" }
@@ -64,15 +62,10 @@ class TCP_Barix_Command_Server {
} }
} }
} }
} Logger.info { "Finished communicating with Streamer Output with IP $key" }
}
Logger.info { "Finished communicating with $key" }
socketMap.remove(key) socketMap.remove(key)
}
} }
}
}
} catch (ex: Exception) { } catch (ex: Exception) {
Logger.error { "Failed accepting TCP Socket, Message : ${ex.message}" } Logger.error { "Failed accepting TCP Socket, Message : ${ex.message}" }
@@ -94,20 +87,18 @@ class TCP_Barix_Command_Server {
*/ */
fun StopTcpCommand(): Boolean { fun StopTcpCommand(): Boolean {
try { try {
tcpserver?.close() tcpserver.close()
runBlocking { runBlocking {
socketMap.values.forEach { socketMap.values.forEach {
it.close() it.close()
} }
socketMap.clear() socketMap.clear()
job?.join() job.join()
} }
Logger.info { "StopTcpCommand success" } Logger.info { "StopTcpCommand success" }
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" } Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
} finally {
tcpserver = null
} }
return false return false
} }

View File

@@ -0,0 +1,45 @@
package commandServer
import codes.Somecodes.Companion.PagingResult_directory
import codes.Somecodes.Companion.filenameformat
import org.tinylog.Logger
import java.io.ByteArrayOutputStream
import java.nio.file.Path
import java.time.LocalDateTime
/**
* Class to handle a paging job, storing incoming audio data and metadata.
* @param fromIP The IP address from which the paging data is received.
* @param broadcastzones The zones to which the paging is broadcasted, is a semicolon-separated string.
*/
class PagingJob(val fromIP: String, val broadcastzones: String) {
val filePath : Path = PagingResult_directory.resolve(LocalDateTime.now().format(filenameformat)+"_RAW.wav")
private val bos : ByteArrayOutputStream = ByteArrayOutputStream()
var totalBytesReceived = 0; private set
var isRunning = true; private set
/**
* Adds incoming audio data to the job.
* @param data The byte array containing audio data.
* @param length The number of bytes to write from the data array.
*/
fun addData(data: ByteArray, length: Int) {
Logger.info{"PagingJob from $fromIP, zones: $broadcastzones, received $length bytes"}
bos.write(data, 0, length)
totalBytesReceived += length
}
/**
* Retrieves the accumulated audio data as a byte array.
* @return A byte array containing all received audio data.
*/
fun GetData(): ByteArray {
return bos.toByteArray()
}
fun Close(){
bos.close()
isRunning = false
}
}

View File

@@ -1,6 +1,13 @@
package commandServer package commandServer
import audioPlayer
import codes.Somecodes.Companion.ValidString import codes.Somecodes.Companion.ValidString
import codes.Somecodes.Companion.datetimeformat1
import content.Language
import database.Messagebank
import database.QueuePaging
import database.QueueTable
import database.Soundbank
import db import db
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -9,19 +16,23 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tinylog.Logger import org.tinylog.Logger
import udpreceiver
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.LocalDateTime
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.io.path.absolutePathString
@Suppress("unused") @Suppress("unused")
class TCP_Android_Command_Server { class TCP_Android_Command_Server {
private var tcpserver: ServerSocket? = null lateinit var tcpserver: ServerSocket
private var job: Job? = null lateinit var job: Job
private val socketMap = mutableMapOf<String, Socket>() private val socketMap = mutableMapOf<String, Socket>()
lateinit var logcb: Consumer<String> lateinit var logcb: Consumer<String>
private val listUserLogin = mutableListOf<userLogin>() private val listUserLogin = mutableListOf<userLogin>()
private val listOnGoingPaging = mutableMapOf<String, PagingJob>()
/** /**
* Start TCP Command Server * Start TCP Command Server
@@ -35,15 +46,16 @@ class TCP_Android_Command_Server {
val tcp = ServerSocket(port) val tcp = ServerSocket(port)
tcpserver = tcp tcpserver = tcp
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {
Logger.info { "TCP server started" } Logger.info { "TCP Android server started on port $port" }
while (isActive) { while (isActive) {
if (tcpserver?.isClosed == true) break if (tcpserver.isClosed) break
try { try {
tcpserver?.accept().use { socket -> tcpserver.accept().use { socket ->
{ {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
if (socket != null) { if (socket != null) {
val key: String = socket.inetAddress.hostAddress + ":" + socket.port // key is IP address only
val key: String = socket.inetAddress.hostAddress
socketMap[key] = socket socketMap[key] = socket
Logger.info { "Start communicating with $key" } Logger.info { "Start communicating with $key" }
socket.getInputStream().let { din -> socket.getInputStream().let { din ->
@@ -108,23 +120,31 @@ class TCP_Android_Command_Server {
return ByteArray(0) return ByteArray(0)
} }
/**
* Process command from Android client
* @param key The client IP address
* @param cmd The command string
* @param cb Callback to send reply string
*/
private fun process_command(key: String, cmd: String, cb: Consumer<String>) { private fun process_command(key: String, cmd: String, cb: Consumer<String>) {
Logger.info { "Command from $key : $cmd" } Logger.info { "Command from $key : $cmd" }
val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() }.map { it.uppercase() } val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() }.map { it.uppercase() }
when (parts[0]) { when (parts[0]) {
"GETLOGIN" -> { "GETLOGIN" -> {
// Android login request
val username = parts.getOrElse(1) { "" } val username = parts.getOrElse(1) { "" }
val password = parts.getOrElse(2) { "" } val password = parts.getOrElse(2) { "" }
if (ValidString(username) && ValidString(password)) { if (ValidString(username) && ValidString(password)) {
if (db.userDB.List.any{it.username==username && it.password==password}) { if (db.userDB.List.any{it.username==username && it.password==password}) {
cb.accept("LOGIN;TRUE@")
val existing = listUserLogin.find { it.ip == key} val existing = listUserLogin.find { it.ip == key}
if (existing!=null){ if (existing!=null){
existing.username = username existing.username = username
} else{ } else{
listUserLogin.add(userLogin(key, username)) listUserLogin.add(userLogin(key, username))
} }
cb.accept("LOGIN;TRUE@")
logcb.accept("Android Login success from $key as $username") logcb.accept("Android Login success from $key as $username")
return
} else { } else {
logcb.accept("Android Login failed from $key as $username") logcb.accept("Android Login failed from $key as $username")
cb.accept("LOGIN;FALSE@") cb.accept("LOGIN;FALSE@")
@@ -135,32 +155,252 @@ class TCP_Android_Command_Server {
} }
} }
"PCMFILE_START" -> { "PCMFILE_START","STARTPAGINGAND" -> {
// TODO read coding here val zones = parts.getOrElse(3) { "" }.replace(",",";")
if (ValidString(zones)){
// create pagingjob
val pj = PagingJob(key, zones)
// masukin ke list
listOnGoingPaging[key] = pj
// start minta data dari udpreceiver
udpreceiver.RequestDataFrom(key){
// push data ke paging job
pj.addData(it, it.size)
}
logcb.accept("Paging started from Android $key")
cb.accept(parts[0]+";OK@")
return
} else logcb.accept("Paging start from Android $key failed, empty zones")
cb.accept(parts[0]+";NG@")
} }
"PCMFILE_STOP" -> { "PCMFILE_STOP","STOPPAGINGAND" -> {
// TODO read coding here val pj = listOnGoingPaging[key]
if (pj!=null){
listOnGoingPaging.remove(key)
udpreceiver.StopRequestDataFrom(key)
logcb.accept("Paging stopped from Android $key")
cb.accept(parts[0]+";OK@")
// get remaining data
val data = pj.GetData()
pj.Close()
audioPlayer.WavWriter(data, pj.filePath.absolutePathString()){
success, message ->
if (success){
// insert to paging queue
val qp = QueuePaging(
0u,
LocalDateTime.now().format(datetimeformat1),
"PAGING",
"NORMAL",
pj.filePath.absolutePathString(),
pj.broadcastzones
)
if (db.queuepagingDB.Add(qp)){
logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
cb.accept(parts[0]+";OK@")
} else {
logcb.accept("Failed to insert paging audio to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
cb.accept(parts[0]+";NG@")
} }
"STARTPAGINGAND" -> { } else {
// TODO read coding here logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : $message")
cb.accept(parts[0]+";NG@")
}
}
} else {
logcb.accept("Paging stop from Android $key failed, no ongoing paging")
cb.accept(parts[0]+";NG@")
} }
"STOPPAGINGAND" -> {
// TODO read coding here
} }
"CANCELPAGINGAND" -> { "CANCELPAGINGAND" -> {
// TODO read coding here val pj = listOnGoingPaging[key]
if (pj!=null){
pj.Close()
listOnGoingPaging.remove(key)
udpreceiver.StopRequestDataFrom(key)
logcb.accept("Paging from Android $key cancelled")
cb.accept("CANCELPAGINGAND;OK@")
return
} else logcb.accept("Paging cancel from Android $key failed, no ongoing paging")
cb.accept("CANCELPAGINGAND;NG@")
} }
"STARTINITIALIZE" -> { "STARTINITIALIZE" -> {
// TODO read coding here // pengiriman variabel ke Android
val username = parts.getOrElse(1) { "" }
if (ValidString(username)){
val userlogin = listUserLogin.find { it.username == username }
if (userlogin != null){
val userdb = db.userDB.List.find { it.username == username }
if (userdb != null){
val result = StringBuilder()
result.append("ZONE")
userdb.broadcastzones.split(";").map { it.trim() }.filter { it.isNotBlank() }.forEach {
result.append(";")
result.append(it)
}
result.append("@")
val VARMESSAGES = mutableListOf<Messagebank>()
userdb.messagebank_ann_id
// messagebank_ann_id adalah rentengan ANN_ID (digit) yang dipisah dengan ;
.split(";")
// trim dulu
.map { it.trim() }
// bukan string kosong antar dua tanda ;
.filter { it.isNotBlank() }
// beneran digit semua
.filter { xx -> xx.all{it.isDigit()} }
// iterasi setiap ANN_ID
.forEach { annid ->
// masukin ke VARMESSAGES yang unik secara ANN_ID dan Language
val xx = db.messageDB.List
.filter{ it.ANN_ID == annid.toUInt() }
.distinctBy { it.Language }
VARMESSAGES.addAll(xx)
}
result.append("MSGTOTAL;").append(VARMESSAGES.size).append("@")
// VAR AP TOTAL
val VARAPTOTAL = mutableListOf<Soundbank>()
val sb_split = userdb.soundbank_tags.split(";").map { it.trim() }.filter { it.isNotBlank() }
sb_split.forEach {
val sb = db.Find_Soundbank_AirplaneName(it).firstOrNull()
if (sb != null) VARAPTOTAL.add(sb)
}
result.append("VARAPTOTAL;").append(VARAPTOTAL.size).append("@")
// VAR CITY TOTAL
val VARCITYTOTAL = mutableListOf<Soundbank>()
sb_split.forEach {
val sb = db.Find_Soundbank_City(it).firstOrNull()
if (sb != null) VARCITYTOTAL.add(sb)
}
result.append("VARCITYTOTAL;").append(VARCITYTOTAL.size).append("@")
// VAR PLACES TOTAL
val VARPLACESTOTAL = mutableListOf<Soundbank>()
sb_split.forEach {
val sb = db.Find_Soundbank_Places(it).firstOrNull()
if (sb != null) VARPLACESTOTAL.add(sb)
}
result.append("VARPLACESTOTAL;").append(VARPLACESTOTAL.size).append("@")
// VAR SHALAT TOTAL
val VARSHALATTOTAL = mutableListOf<Soundbank>()
sb_split.forEach {
val sb = db.Find_Soundbank_Shalat(it).firstOrNull()
if (sb != null) VARSHALATTOTAL.add(sb)
}
result.append("VARSHALATTOTAL;").append(VARSHALATTOTAL.size).append("@")
// VAR SEQUENCE TOTAL
val VARSEQUENCETOTAL = mutableListOf<Soundbank>()
sb_split.forEach {
val sb = db.Find_Soundbank_Sequence(it).firstOrNull()
if (sb != null) VARSEQUENCETOTAL.add(sb)
}
// VAR REASON TOTAL
val VARREASONTOTAL = mutableListOf<Soundbank>()
sb_split.forEach {
val sb = db.Find_Soundbank_Reason(it).firstOrNull()
if (sb != null) VARREASONTOTAL.add(sb)
}
result.append("VARREASONTOTAL;").append(VARREASONTOTAL.size).append("@")
// VAR PROCEDURE TOTAL
val VARPROCEDURETOTAL = mutableListOf<Soundbank>()
sb_split.forEach {
val sb = db.Find_Soundbank_Procedure(it).firstOrNull()
if (sb != null) VARPROCEDURETOTAL.add(sb)
}
result.append("VARPROCEDURETOTAL;").append(VARPROCEDURETOTAL.size).append("@")
// send to sender
cb.accept(result.toString())
result.clear()
//Append MSG, for Android only Indonesia and English
VARMESSAGES.groupBy { it.ANN_ID }.forEach { (ann_id, value) ->
result.append("MSG;").append(ann_id)
result.append(";")
value.find { it.Language== Language.INDONESIA.name }?.let {result.append(it.Message_Detail)} ?: result.append("NA")
result.append(";")
value.find {it.Language== Language.ENGLISH.name }?.let {result.append(it.Message_Detail)} ?: result.append("NA")
result.append("@")
}
// append VARAP
VARAPTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARAP;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// append VARCITY
VARCITYTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARCITY;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// append VARPLACES
VARPLACESTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARPLACES;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// append VARSHALAT
VARSHALATTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARSHALAT;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// append VARSEQUENCE
VARSEQUENCETOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARSEQUENCE;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// append VARREASON
VARREASONTOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARREASON;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// append VARPROCEDURE
VARPROCEDURETOTAL.distinctBy { it.Description }.forEachIndexed { index, soundbank ->
result.append("VARPROCEDURE;").append(index).append(";").append(soundbank.TAG).append(";").append(soundbank.Description).append("@")
}
// send to sender
cb.accept(result.toString())
logcb.accept("All variables sent to $key with username $username")
return
} else logcb.accept("STARTINITIALIZE failed from $key with username $username not found in userDB")
} else logcb.accept("STARTINITIALIZE failed from $key with unregistered username $username")
} else logcb.accept("STARTINITIALIZE failed from $key with empty username")
cb.accept("STARTINITIALIZE;FALSE@")
} }
"BROADCASTAND" -> { "BROADCASTAND" -> {
// TODO read coding here // semi auto dari android, masukin ke queue table
val desc = parts.getOrElse(1) { "" }
val lang = parts.getOrElse(2) { "" }.replace(",",";")
val tags = parts.getOrElse(3) { "" }.replace(",",";")
val zone = parts.getOrElse(4) { "" }.replace(",",";")
if (ValidString(desc)){
if (ValidString(lang)){
if (ValidString(tags)){
if (ValidString(zone)){
val qt = QueueTable(
0u,
LocalDateTime.now().format(datetimeformat1),
"ANDROID",
"SOUNDBANK",
desc,
tags,
zone,
1u,
lang
)
if (db.queuetableDB.Add(qt)){
logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone")
cb.accept("BROADCASTAND;OK@")
return
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, cannot add to queue table")
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty zone")
} else logcb.accept("Broadcsast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty tags")
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty language")
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty description")
cb.accept("NG@")
} }
else -> { else -> {
@@ -176,20 +416,18 @@ class TCP_Android_Command_Server {
*/ */
fun StopTcpCommand(): Boolean { fun StopTcpCommand(): Boolean {
try { try {
tcpserver?.close() tcpserver.close()
runBlocking { runBlocking {
socketMap.values.forEach { socketMap.values.forEach {
it.close() it.close()
} }
socketMap.clear() socketMap.clear()
job?.join() job.join()
} }
Logger.info { "StopTcpCommand success" } Logger.info { "StopTcpCommand success" }
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" } Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
} finally {
tcpserver = null
} }
return false return false
} }

View File

@@ -14,8 +14,8 @@ import java.util.function.Consumer
@Suppress("unused") @Suppress("unused")
class TCP_PC_Command_Server { class TCP_PC_Command_Server {
private var tcpserver: ServerSocket? = null lateinit var tcpserver: ServerSocket
private var job: Job? = null lateinit var job: Job
private val socketMap = mutableMapOf<String, Socket>() private val socketMap = mutableMapOf<String, Socket>()
/** /**
@@ -31,11 +31,11 @@ class TCP_PC_Command_Server {
job = CoroutineScope(Dispatchers.IO).launch { job = CoroutineScope(Dispatchers.IO).launch {
Logger.info { "TCP server started" } Logger.info { "TCP server started" }
while (isActive) { while (isActive) {
if (tcpserver?.isClosed == true) break if (tcpserver.isClosed) break
try { try {
tcpserver?.accept().use { socket -> tcpserver.accept().use { socket ->
{ {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
if (socket != null) { if (socket != null) {
val key : String = socket.inetAddress.hostAddress+":"+socket.port val key : String = socket.inetAddress.hostAddress+":"+socket.port
socketMap[key] = socket socketMap[key] = socket
@@ -81,20 +81,18 @@ class TCP_PC_Command_Server {
*/ */
fun StopTcpCommand(): Boolean { fun StopTcpCommand(): Boolean {
try { try {
tcpserver?.close() tcpserver.close()
runBlocking { runBlocking {
socketMap.values.forEach { socketMap.values.forEach {
it.close() it.close()
} }
socketMap.clear() socketMap.clear()
job?.join() job.join()
} }
Logger.info { "StopTcpCommand success" } Logger.info { "StopTcpCommand success" }
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" } Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
} finally {
tcpserver = null
} }
return false return false
} }

View File

@@ -13,5 +13,6 @@ enum class Category(name: String) {
Year("Year"), Year("Year"),
Birthday("Birthday"), Birthday("Birthday"),
Reason("Reason"), Reason("Reason"),
Sequence("Sequence"),
Procedure("Procedure"); Procedure("Procedure");
} }

View File

@@ -20,4 +20,8 @@ data class Log(
return Log(0u, date, time, machine, description) return Log(0u, date, time, machine, description)
} }
} }
override fun toString() : String {
return "$datenya $timenya [$machine] $description"
}
} }

View File

@@ -2,14 +2,17 @@ package database
import codes.Somecodes.Companion.ValiDateForLogHtml import codes.Somecodes.Companion.ValiDateForLogHtml
import codes.Somecodes.Companion.toJsonString import codes.Somecodes.Companion.toJsonString
import content.Category
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import max_channel
import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.tinylog.Logger import org.tinylog.Logger
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.math.max
/** /**
* A class to manage a connection to a MariaDB database. * A class to manage a connection to a MariaDB database.
@@ -1463,7 +1466,7 @@ class MariaDB(
val countResult = statement?.executeQuery("SELECT COUNT(*) AS count FROM ${super.dbName}") val countResult = statement?.executeQuery("SELECT COUNT(*) AS count FROM ${super.dbName}")
if (countResult?.next() == true) { if (countResult?.next() == true) {
val count = countResult.getInt("count") val count = countResult.getInt("count")
if (count == 0) { if (count < max_channel) {
Logger.info("SoundChannel table is empty, populating with default channels" as Any) Logger.info("SoundChannel table is empty, populating with default channels" as Any)
Clear() Clear()
} }
@@ -1577,9 +1580,9 @@ class MariaDB(
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
Logger.info("${super.dbName} table cleared" as Any) Logger.info("${super.dbName} table cleared" as Any)
List.clear() List.clear()
// create new rows from 1 to 64 with description "Channel 01" to "Channel 64" and empty ip // create new rows from 1 to 64 with description "Channel 1" to "Channel 64" and empty ip
for (i in 1..64) { for (i in 1..max_channel) {
val channel = String.format("Channel %02d", i) val channel = String.format("Channel %d", i)
val insertStatement = val insertStatement =
connection.prepareStatement("INSERT INTO ${super.dbName} (channel, ip) VALUES (?, ?)") connection.prepareStatement("INSERT INTO ${super.dbName} (channel, ip) VALUES (?, ?)")
insertStatement?.setString(1, channel) insertStatement?.setString(1, channel)
@@ -1727,13 +1730,13 @@ class MariaDB(
statement?.setString(4, data.description) statement?.setString(4, data.description)
val rowsAffected = statement?.executeUpdate() val rowsAffected = statement?.executeUpdate()
if (rowsAffected != null && rowsAffected > 0) { if (rowsAffected != null && rowsAffected > 0) {
Logger.info("Log added: [$data.datenya $data.timenya] [$data.machine] $data.description" as Any) Logger.info{"Log added : $data"}
return true return true
} else { } else {
Logger.warn("No log entry added for: [$data.datenya $data.timenya] [$data.machine] $data.description" as Any) Logger.warn{"Failed to add log entry : $data"}
} }
} catch (e: Exception) { } catch (e: Exception) {
Logger.error("Error adding log entry: ${e.message}" as Any) Logger.error{"Error adding log entry: ${e.message}"}
} }
return false return false
} }
@@ -2239,6 +2242,63 @@ class MariaDB(
return null return null
} }
/**
* Find all city soundbank by tag
* @param tag The tags to search for
* @return a list of Soundbank with Category City and matching tag
*/
fun Find_Soundbank_City(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List
.filter{ it.Category== Category.City.name }
.filter { it.TAG.lowercase()==lowerTag}
}
fun Find_Soundbank_AirplaneName(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List
.filter{ it.Category== Category.Airplane_Name.name }
.filter { it.TAG.lowercase()==lowerTag}
}
fun Find_Soundbank_Places(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List
.filter{ it.Category== Category.Places.name }
.filter { it.TAG.lowercase()==lowerTag}
}
fun Find_Soundbank_Shalat(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List
.filter{ it.Category== Category.Shalat.name }
.filter { it.TAG.lowercase()==lowerTag}
}
fun Find_Soundbank_Sequence(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List
.filter{ it.Category== Category.Sequence.name }
.filter { it.TAG.lowercase()==lowerTag}
}
fun Find_Soundbank_Reason(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List
.filter{ it.Category== Category.Reason.name }
.filter { it.TAG.lowercase()==lowerTag}
}
fun Find_Soundbank_Procedure(tag: String) : List<Soundbank> {
val lowerTag = tag.lowercase()
return soundDB.List
.filter { it.Category == Category.Procedure.name }
.filter { it.TAG.lowercase() == lowerTag }
}

View File

@@ -1,4 +1,7 @@
package database package database
@Suppress("unused") data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){
data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String) override fun toString(): String {
return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')"
}
}

View File

@@ -1,4 +1,8 @@
package database package database
@Suppress("unused") data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String){
data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String)
override fun toString(): String {
return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')"
}
}

View File

@@ -16,7 +16,6 @@ import content.Language
import content.ScheduleDay import content.ScheduleDay
import content.VoiceType import content.VoiceType
import database.BroadcastZones import database.BroadcastZones
import database.BroadcastZonesHtml
import database.LanguageLink import database.LanguageLink
import database.MariaDB import database.MariaDB
import database.Messagebank import database.Messagebank
@@ -652,6 +651,9 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate schedulebank table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate schedulebank table")))
} }
} }
post("Add"){
// TODO add new schedule
}
delete("DeleteByIndex/{index}") { delete("DeleteByIndex/{index}") {
// delete by index // delete by index
val index = it.pathParam("index").toUIntOrNull() val index = it.pathParam("index").toUIntOrNull()
@@ -831,17 +833,17 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
path("BroadcastZones"){ path("BroadcastZones"){
get("List") { get("List") {
// TODO : temporary, convert BroadcastZones to BroadcastZonesHtml, karena harus revisi javascript di Bootstrap Studio // TODO : temporary, convert BroadcastZones to BroadcastZonesHtml, karena harus revisi javascript di Bootstrap Studio
val newlist: ArrayList<BroadcastZonesHtml> = db.broadcastDB.List.map { xx -> // val newlist: ArrayList<BroadcastZonesHtml> = db.broadcastDB.List.map { xx ->
BroadcastZonesHtml( // BroadcastZonesHtml(
xx.index, // xx.index,
xx.description, // xx.description,
xx.SoundChannel, // xx.SoundChannel,
xx.id, // xx.id,
xx.bp // xx.bp
) // )
} as ArrayList<BroadcastZonesHtml> // } as ArrayList<BroadcastZonesHtml>
it.result(MariaDB.ArrayListtoString(newlist)) it.result(MariaDB.ArrayListtoString(db.broadcastDB.List))
} }
delete("List"){ delete("List"){
// truncate broadcast zones table // truncate broadcast zones table