Compare commits

...

29 Commits

Author SHA1 Message Date
cdcb02e976 commit 18/10/2025 sync changes from master 2025-10-20 08:53:20 +07:00
17b4485e69 commit 17/10/2025 Add few logs 2025-10-17 17:15:00 +07:00
4d02ab6d07 commit 17/10/2025 Add few logs 2025-10-17 16:55:57 +07:00
8bd57f216a commit 14/10/2025 WebApp fix ScheduleBank ADD & send message 2025-10-17 09:42:50 +07:00
8978a0986d Merge remote-tracking branch 'origin/master' into feature-webapp 2025-10-17 09:12:01 +07:00
bc3f5c5691 commit 16/10/2025
Overview menu beres
2025-10-16 15:54:33 +07:00
e5d8d8059e Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp 2025-10-16 15:42:24 +07:00
1b84ec133b commit 14/10/2025 WebApp Add send language for ScheduleBank 2025-10-16 15:26:59 +07:00
1a6b7de6ec commit 16/10/2025
Overview menu beres
2025-10-16 14:53:26 +07:00
4da5a2fb05 Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp 2025-10-15 16:17:05 +07:00
2ca7004b70 commit 15/10/2025
Overview menu belum beres
2025-10-15 16:13:44 +07:00
1563e233d6 commit 14/10/2025 WebApp Add send language for ScheduleBank 2025-10-15 13:32:08 +07:00
2fe4a46e3e commit 15/10/2025
Network monitoring beres
2025-10-15 12:14:57 +07:00
a53270aaed commit 14/10/2025 WebApp Add send language for ScheduleBank 2025-10-14 16:26:14 +07:00
4d3dc538bd commit 14/10/2025
Messagebank belum beres
2025-10-14 15:14:21 +07:00
d2a2626fd5 Merge remote-tracking branch 'origin/master' into feature-webapp 2025-10-14 14:20:08 +07:00
de54d142ae commit 14/10/2025
Soundbank Menu beres
2025-10-14 13:22:48 +07:00
8c49bb827f Merge remote-tracking branch 'origin/master' into feature-webapp
# Conflicts:
#	src/web/WebApp.kt
2025-10-14 09:11:36 +07:00
b15470845e Merge remote-tracking branch 'origin/master' into feature-webapp
# Conflicts:
#	src/web/WebApp.kt
2025-10-14 09:07:17 +07:00
5f57e1bf2e commit 14/10/2025
combine with coding steph
2025-10-14 08:50:05 +07:00
a133f9a170 commit 14/10/2025
combine with coding steph
2025-10-14 08:21:07 +07:00
1d2c8d1307 commit 08/10/2025 WebApp 2025-10-14 07:21:52 +07:00
e426522380 commit 08/10/2025 WebApp 2025-10-13 16:16:15 +07:00
470f61c79d Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp
# Conflicts:
#	src/web/WebApp.kt
2025-10-13 10:52:21 +07:00
6b00bc7eb0 commit 08/10/2025 WebApp 2025-10-08 17:03:17 +07:00
8409307631 Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp 2025-10-08 17:02:45 +07:00
c0e920d1d5 commit 08/10/2025 WebApp 2025-10-08 14:32:09 +07:00
0d6b4aa49e Merge remote-tracking branch 'origin/master' into feature-webapp 2025-10-08 14:25:10 +07:00
86d50bdb6c commit 08/10/2025 WebApp 2025-10-08 10:28:15 +07:00
47 changed files with 1946 additions and 695 deletions

View File

@@ -37,3 +37,52 @@
--bs-btn-disabled-border-color: #0d6efd;
}
.my-4 {
margin-top: 1.5rem!important;
margin-bottom: 1.5rem!important;
}
.mt-0 {
margin-top: 0!important;
}
.me-2 {
margin-right: .5rem!important;
}
.mb-2 {
margin-bottom: .5rem!important;
}
.mb-3 {
margin-bottom: 1rem!important;
}
.mb-4 {
margin-bottom: 1.5rem!important;
}
.mb-5 {
margin-bottom: 3rem!important;
}
.mb-7 {
margin-bottom: 6rem !important;
}
.mb-auto {
margin-bottom: auto!important;
}
@media (min-width:768px) {
.me-md-auto {
margin-right: auto!important;
}
}
@media (min-width:768px) {
.mb-md-0 {
margin-bottom: 0!important;
}
}

View File

@@ -104,7 +104,7 @@ body {
}
.btn-login {
border-radius: 20px;
border-radius: 8px;
box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
--bs-btn-hover-bg: #5780f2;
background-color: #5278e1;
@@ -277,3 +277,44 @@ table {
border-radius: 0 !important;
}
.p-login {
text-align: left;
font-weight: 600;
margin-bottom: 0;
color: #3E4C66;
}
.h-login {
font-weight: 600;
color: #2E3A59;
}
#file-input {
display: none;
}
.card-setting {
border-radius: 8px;
border: white solid 3px;
}
#drop-area {
border: 2px dashed #ccc;
border-radius: 20px;
width: 400px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
font-family: sans-serif;
color: #666;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
#drop-area.highlight {
background: #f0f8ff;
border-color: #0d6efd;
color: #0d6efd;
}

View File

@@ -228,7 +228,7 @@ $(document).ready(function () {
let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */
let bz = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
SoundChannel: cells.eq(2).text(),
Box: cells.eq(3).text(),
@@ -250,7 +250,7 @@ $(document).ready(function () {
let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */
let bz = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
SoundChannel: cells.eq(2).text(),
Box: cells.eq(3).text(),

31
html/webpage/assets/js/dragdrop.js vendored Normal file
View File

@@ -0,0 +1,31 @@
const dropArea = document.getElementById("drop-area");
const fileInput = document.getElementById("file-input");
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, e => e.preventDefault());
dropArea.addEventListener(eventName, e => e.stopPropagation());
});
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, () => dropArea.classList.add('highlight'));
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, () => dropArea.classList.remove('highlight'));
});
dropArea.addEventListener('click', () => fileInput.click());
dropArea.addEventListener('drop', e => {
const files = e.dataTransfer.files;
handleFiles(files);
});
fileInput.addEventListener('change', e => {
handleFiles(e.target.files);
});
function handleFiles(files) {
console.log("file dropped");
}

View File

@@ -175,7 +175,7 @@ $(document).ready(function () {
let cells = window.selectedlanguagerow.find('td');
/** @type {Language} */
let ll = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
tag: cells.eq(1).text(),
language: cells.eq(2).text()
}
@@ -194,7 +194,7 @@ $(document).ready(function () {
let cells = window.selectedlanguagerow.find('td');
/** @type {Language} */
let ll = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
tag: cells.eq(1).text(),
language: cells.eq(2).text()
}

View File

@@ -121,18 +121,33 @@ $(document).ready(function () {
function refill_messageavailablevariables() {
$messageavailablevariables.empty();
categories.forEach(cat => {
$messageavailablevariables.append(ListItem(`{${cat}}`));
$messageavailablevariables.append(ListItem(`[${cat}]`));
});
if ($messagelanguage.val() && $messagevoicetype.val()) {
soundbankdata
.filter(sb => sb.language.toLowerCase() === $messagelanguage.val().toLowerCase())
.filter(sb => sb.voiceType.toLowerCase() === $messagevoicetype.val().toLowerCase())
.filter(sb => sb.category.toLowerCase() === "phrase")
.forEach(sb => {
$messageavailablevariables.append(ListItem(`[${sb.Description}]`));
let lang = $messagelanguage.val();
let vt = $messagevoicetype.val();
if (lang && lang.length > 0){
console.log("Selected Language:", lang);
if (vt && vt.length > 0){
console.log("Selected Voice Type:", vt);
fetchAPI(`SoundBank/GetPhrases/${lang}/${vt}`, "GET", {}, null, (okdata) => {
if (Array.isArray(okdata) && okdata.length > 0) {
console.log(`Loaded ${okdata.length} phrases from soundbank for language=${lang} and voiceType=${vt}`);
console.log(JSON.stringify(okdata));
okdata.forEach(sb => {
if (sb.description && sb.description.length > 0) {
$messageavailablevariables.append(ListItem(`${sb.description} [${sb.TAG}]`));
}
});
}
}, (errdata) => {
//alert("Error loading phrases from soundbank : " + errdata.message);
});
}
}
}
/**
@@ -143,7 +158,7 @@ $(document).ready(function () {
$messagedescription.val('');
// fill messagelanguage options from languages[]
$messagelanguage.empty();
languages.forEach(lang => {
window.languages.forEach(lang => {
$messagelanguage.append(new Option(lang, lang));
});
$messagelanguage.val(null);
@@ -154,7 +169,7 @@ $(document).ready(function () {
$messageannid.val(1);
// fill messagevoicetype options from voiceTypes[]
$messagevoicetype.empty();
voiceTypes.forEach(vt => {
window.voiceTypes.forEach(vt => {
$messagevoicetype.append(new Option(vt, vt));
});
$messagevoicetype.val(null);
@@ -305,7 +320,7 @@ $(document).ready(function () {
let cells = window.selectedmessagerow.find('td');
/** @type {MessageBank} */
let mb = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
language: cells.eq(2).text(),
aNN_ID: parseInt(cells.eq(3).text()),
@@ -329,7 +344,7 @@ $(document).ready(function () {
let cells = window.selectedmessagerow.find('td');
/** @type {MessageBank} */
let mb = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
language: cells.eq(2).text(),
aNN_ID: parseInt(cells.eq(3).text()),
@@ -344,19 +359,8 @@ $(document).ready(function () {
// Fill modal fields with selected messagebank data
$messageindex.val(mb.index).prop('disabled', true);
$messagedescription.val(mb.description);
// Fill messagelanguage options and select current
$messagelanguage.empty();
languages.forEach(lang => {
$messagelanguage.append(new Option(lang, lang));
});
$messagelanguage.val(mb.language);
// Fill messagevoicetype options and select current
$messagevoicetype.empty();
voiceTypes.forEach(vt => {
$messagevoicetype.append(new Option(vt, vt));
});
$messagevoicetype.val(mb.voice_Type);
// Set annid
$messageannid.val(mb.aNN_ID);
// Refill message available variables
refill_messageavailablevariables();

View File

@@ -0,0 +1,359 @@
/**
* @typedef {Object} StreamerOutputData
* @property {number} index - The index of the Barix connection.
* @property {string} channel - The channel name of the Barix connection.
* @property {string} ipaddress - The IP address of the Barix connection.
* @property {number} bufferRemain - The remaining buffer size of the Barix connection.
* @property {boolean} isPlaying - true = playback started, false = playback stopped
* @property {number} vu - The VU level of the Barix connection, 0 to 100.
*/
/**
* @typedef {Object} PagingQueue
* @property {number} index - The index of the paging queue item.
* @property {string} Date_Time - The date and time of the paging queue item.
* @property {string} Source - The source of the paging queue item.
* @property {string} Type - The type of the paging queue item.
* @property {string} Message - The message of the paging queue item.
* @property {string} BroadcastZones - The broadcast zones of the paging queue item.
*/
/**
* @typedef {Object} StreamerCard
* @property {JQuery<HTMLElement> | null} title - The jQuery result should be <h4> element.
* @property {JQuery<HTMLElement> | null} ip - The jQuery result should be <h6> element.
* @property {JQuery<HTMLElement> | null} buffer - The jQuery result should be <h6> element.
* @property {JQuery<HTMLElement> | null} status - The jQuery result should be <p> element.
* @property {JQuery<HTMLElement> | null} vu - The jQuery result should be <progress-bar> element.
*/
function getCardByIndex(index) {
let obj = {
// title is <h4> element wiht id `streamertitle${index}`, with index as two digit number, e.g. 01, 02, 03
title: $(`#streamertitle${index.toString().padStart(2, '0')}`),
// ip is <h6> element with id `streamerip${index}`, with index as two digit number, e.g. 01, 02, 03
ip: $(`#streamerip${index.toString().padStart(2, '0')}`),
// buffer is <h6> element with id `streamerbuffer${index}`, with index as two digit number, e.g. 01, 02, 03
buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`),
// status is <p> element with id `streamerstatus${index}`, with index as two digit number, e.g. 01, 02, 03
status: $(`#streamerstatus${index.toString().padStart(2, '0')}`),
// vu is <progress-bar> element with id `streamervu${index}`, with index as two digit number, e.g. 01, 02, 03
vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`),
}
return obj;
}
/**
* Updates the streamer card with the provided values.
* @param {StreamerOutputData[]} values
*/
function UpdateStreamerCard(values) {
if (!Array.isArray(values) || values.length === 0) return;
function setProgress(index, $bar, value, max = 100) {
const v = Number(value ?? 0);
const pct = Math.max(0, Math.min(100, Math.round((v / max) * 100)));
//if (index!==1) return; // only update index 1 for testing
//console.log(`setProgress: index=${index}, value=${v}, pct=${pct}`);
$bar
.attr('aria-valuenow', v) // semantic value
.css('width', pct + '%') // visual width
.text(pct); // optional label
}
for (let i = 1; i <= 64; i++) {
let vv = values.find(v => v.index === i);
let card = getCardByIndex(i);
if (vv) {
// there is value for this index
if (card.title) card.title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`);
if (card.ip) card.ip.text(`IP Address: ${vv.ipaddress ? vv.ipaddress : 'N/A'}`);
if (card.buffer) card.buffer.text(`Buffer: ${vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A'}`);
if (card.status) card.status.text(`Status: ${vv.isPlaying ? 'Playing' : 'Stopped'}`);
if (card.vu) {
setProgress(i, card.vu, vv.vu, 100);
}
} else {
// no value for this index, disable the card
if (card.title) card.title.text(`Channel ${i.toString().padStart(2, '0')}`);
if (card.ip) card.ip.text(`IP Address: N/A`);
if (card.buffer) card.buffer.text(`Buffer: N/A`);
if (card.status) card.status.text(`Status: Disconnected`);
if (card.vu) {
setProgress(i, card.vu, 0, 100);
}
}
}
}
/**
* @type {PagingQueue[]}
*/
window.PagingQueue = [];
/**
* @type {JQuery<HTMLElement> | null}
*/
window.selectedpagingrow = null;
/**
* @typedef {Object} QueueTable
* @property {number} index - The index of the automatic queue item.
* @property {string} Date_Time - The date and time of the automatic queue item.
* @property {string} Source - The source of the automatic queue item.
* @property {string} Type - The type of the automatic queue item.
* @property {string} Message - The message of the automatic queue item.
* @property {string} SB_TAGS - The SB_TAGS of the automatic queue item.
* @property {string} BroadcastZones - The broadcast zones of the automatic queue item.
* @property {number} Repeat - The repeat count of the automatic queue item.
* @property {string} Language - The language of the automatic queue item.
*/
/**
* @type {QueueTable[]}
*/
window.QueueTable = [];
/**
* @type {JQuery<HTMLElement> | null}
*/
window.selectedautomaticrow = null;
/**
* Fills the paging queue table body with the provided data.
* @param {PagingQueue[]} vv array of PagingQueue objects
* @returns
*/
function fill_pagingqueuetablebody(vv) {
$('#pagingqueuetable').empty();
if (!Array.isArray(vv) || vv.length === 0) return;
vv.forEach(item => {
// fill index and description columns using item properties
$('#pagingqueuetable').append(`<tr>
<td>${item.index}</td>
<td>${item.date_Time}</td>
<td>${item.source}</td>
<td>${item.type}</td>
<td>${item.message}</td>
<td>${item.broadcastZones}</td>
</tr>`);
let $addedrow = $('#pagingqueuetable tr:last');
$addedrow.off('click').on('click', function () {
if (window.selectedpagingrow) {
window.selectedpagingrow.find('td').css('background-color', '');
if (window.selectedpagingrow.is($(this))) {
window.selectedpagingrow = null;
$('#removepagingqueue').prop('disabled', true);
return;
}
}
window.selectedpagingrow = $(this);
window.selectedpagingrow.find('td').css('background-color', 'lightblue');
$('#removepagingqueue').prop('disabled', false);
});
});
}
/**
* Fills the automatic queue table body with the provided data.
* @param {QueueTable[]} vv array of QueueTable objects
* @returns
*/
function fill_automaticqueuetablebody(vv) {
$('#automaticqueuetable').empty();
if (!Array.isArray(vv) || vv.length === 0) return;
vv.forEach(item => {
// fill index and description columns using item properties
//console.log("fill_automaticqueuetablebody: item", item);
$('#automaticqueuetable').append(`<tr>
<td>${item.index}</td>
<td>${item.date_Time}</td>
<td>${item.source}</td>
<td>${item.type}</td>
<td>${item.message}</td>
<td>${item.broadcastZones}</td>
</tr>`);
let $addedrow = $('#automaticqueuetable tr:last');
$addedrow.off('click').on('click', function () {
if (window.selectedautomaticrow) {
window.selectedautomaticrow.find('td').css('background-color', '');
if (window.selectedautomaticrow.is($(this))) {
window.selectedautomaticrow = null;
$('#removeautomatictable').prop('disabled', true);
return;
}
}
window.selectedautomaticrow = $(this);
window.selectedautomaticrow.find('td').css('background-color', 'lightblue');
$('#removeautomatictable').prop('disabled', false);
});
});
}
function reloadPagingQueue(APIURL = "QueuePaging/") {
window.PagingQueue = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata) && okdata.length > 0) {
window.PagingQueue.push(...okdata);
fill_pagingqueuetablebody(window.PagingQueue);
} else {
console.log("reloadPagingQueue: okdata is not array");
}
}, (errdata) => {
console.log("reloadPagingQueue: errdata", errdata);
});
}
function reloadAutomaticQueue(APIURL = "QueueTable/") {
window.QueueTable = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata) && okdata.length > 0) {
window.QueueTable.push(...okdata);
fill_automaticqueuetablebody(window.QueueTable);
} else {
console.log("reloadAutomaticQueue: okdata is not array");
}
}, (errdata) => {
console.log("reloadAutomaticQueue: errdata", errdata);
});
}
function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
console.log("RemovePagingQueueByIndex: okdata", okdata);
reloadPagingQueue(APIURL);
}, (errdata) => {
console.log("RemovePagingQueueByIndex: errdata", errdata);
});
}
function RemoveAutomaticQueueByIndex(index, APIURL = "QueueTable/") {
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
console.log("RemoveAutomaticQueueByIndex: okdata", okdata);
reloadAutomaticQueue(APIURL);
}, (errdata) => {
console.log("RemoveAutomaticQueueByIndex: errdata", errdata);
});
}
$(document).ready(function () {
console.log("overview.js loaded");
$('#clearpagingqueue').off('click').on('click', function () {
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
reloadPagingQueue();
alert("Success clear Paging Queue: " + okdata.message);
}, (errdata) => {
alert("Error clear Paging Queue: " + errdata.message);
});
});
$('#removepagingqueue').off('click').on('click', function () {
if (window.selectedpagingrow) {
let cells = window.selectedpagingrow.find('td');
let index = Number(cells.eq(0).text());
let description = cells.eq(1).text();
if (!isNaN(index) && description && description.length > 0) {
if (confirm(`Are you sure to remove Paging Queue Index: ${index} Description: ${description} ?`)) {
RemovePagingQueueByIndex(index);
window.selectedpagingrow = null;
$('#removepagingqueue').prop('disabled', true);
}
}
}
});
$('#clearautomatictable').off('click').on('click', function () {
DoClear("QueueTable/", "Automatic Queue", (okdata) => {
reloadAutomaticQueue();
alert("Success clear Automatic Queue: " + okdata.message);
}, (errdata) => {
alert("Error clear Automatic Queue: " + errdata.message);
});
});
$('#removeautomatictable').off('click').on('click', function () {
if (window.selectedautomaticrow) {
let cells = window.selectedautomaticrow.find('td');
let index = Number(cells.eq(0).text());
let description = cells.eq(1).text();
if (!isNaN(index) && description && description.length > 0) {
if (confirm(`Are you sure to remove Automatic Queue Index: ${index} Description: ${description} ?`)) {
RemoveAutomaticQueueByIndex(index);
window.selectedautomaticrow = null;
$('#removeautomatictable').prop('disabled', true);
}
}
}
});
let intervaljob1 = null;
let intervaljob2 = null;
function runIntervalJob() {
if (intervaljob1) clearInterval(intervaljob1);
intervaljob1 = setInterval(() => {
sendCommand("getStreamerOutputs", "");
}, 100);
if (intervaljob2) clearInterval(intervaljob2);
intervaljob2 = setInterval(() => {
sendCommand("getPagingQueue", "");
sendCommand("getAASQueue", "");
}, 2000);
console.log("overview.js interval job started");
}
runIntervalJob();
window.addEventListener('ws_connected', () => {
console.log("overview.js ws_connected event triggered");
runIntervalJob();
});
window.addEventListener('ws_disconnected', () => {
console.log("overview.js ws_disconnected event triggered");
if (intervaljob) clearInterval(intervaljob);
intervaljob = null;
});
window.addEventListener('ws_message', (event) => {
let rep = event.detail;
let cmd = rep.reply;
let data = rep.data;
if (cmd && cmd.length > 0) {
switch (cmd) {
case "getPagingQueue":
let pq = JSON.parse(data);
//console.log("getPagingQueue:", pq);
window.PagingQueue = [];
if (Array.isArray(pq) && pq.length > 0) {
window.PagingQueue.push(...pq);
}
fill_pagingqueuetablebody(window.PagingQueue);
break;
case "getAASQueue":
let aq = JSON.parse(data);
//console.log("getAASQueue:", aq);
window.QueueTable = [];
if (Array.isArray(aq) && aq.length > 0) {
window.QueueTable.push(...aq);
}
fill_automaticqueuetablebody(window.QueueTable);
break;
case "getStreamerOutputs":
/**
* @type {StreamerOutputData[]}
*/
let so = JSON.parse(data);
UpdateStreamerCard(so);
break;
}
}
});
$(window).on('beforeunload', function () {
console.log("overview.js beforeunload event triggered");
clearInterval(intervaljob1);
clearInterval(intervaljob2);
intervaljob1 = null;
intervaljob2 = null;
});
});

View File

@@ -101,28 +101,30 @@ $(document).ready(function () {
let $schedulehour = $schedulemodal.find('#schedulehour');
// number input 0-59
let $scheduleminute = $schedulemodal.find('#scheduleminute');
// text input
let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath');
// select2 for message
let $schedulemessage = $schedulemodal.find('#schedulemessage');
$schedulemessage.select2({});
// number input 0-5
let $schedulerepeat = $schedulemodal.find('#schedulerepeat');
// checkbox
let $scheduleenable = $schedulemodal.find('#scheduleenable');
// div for list of checkboxes
// select2 for broadcastzones
let $schedulezones = $schedulemodal.find('#schedulezones');
$schedulezones.select2({});
// radio button for everyday
let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday');
// radio button for weekdays
let $schedulesunday = $schedulemodal.find('#schedulesunday');
let $schedulemonday = $schedulemodal.find('#schedulemonday');
let $scheduletuesday = $schedulemodal.find('#scheduletuesday');
let $schedulewednesday = $schedulemodal.find('#schedulewednesday');
let $schedulethursday = $schedulemodal.find('#schedulethursday');
let $schedulefriday = $schedulemodal.find('#schedulefriday');
let $schedulesaturday = $schedulemodal.find('#schedulesaturday');
// radio button for weekly
let $scheduleweekly = $schedulemodal.find('#scheduleweekly');
// select2 for weekly selection
let $weeklyselect = $schedulemodal.find('#weeklyselect');
$weeklyselect.select2({});
// radio button for specific date
let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
// date input
let $scheduledate = $schedulemodal.find('#scheduledate');
// select2 for language
let $languageselect = $schedulemodal.find('#languageselect');
$languageselect.select2({});
$schedulespecialdate.on('change', function () {
if ($(this).is(':checked')) {
@@ -137,17 +139,10 @@ $(document).ready(function () {
$scheduledescription.val('');
$schedulehour.val('0');
$scheduleminute.val('0');
$schedulesoundpath.val('');
$schedulerepeat.val('0');
$scheduleenable.prop('checked', true);
$scheduleeveryday.prop('checked', false);
$schedulesunday.prop('checked', false);
$schedulemonday.prop('checked', false);
$scheduletuesday.prop('checked', false);
$schedulewednesday.prop('checked', false);
$schedulethursday.prop('checked', false);
$schedulefriday.prop('checked', false);
$schedulesaturday.prop('checked', false);
$schedulespecialdate.prop('checked', false);
$scheduledate.prop('disabled', true).val('');
@@ -250,7 +245,7 @@ $(document).ready(function () {
let cells = window.selectedschedulerow.find('td');
/** @type {ScheduleBank} */
let sr = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
day: cells.eq(2).text(),
time: cells.eq(3).text(),
@@ -275,7 +270,7 @@ $(document).ready(function () {
let cells = window.selectedschedulerow.find('td');
/** @type {ScheduleBank} */
let sr = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
day: cells.eq(2).text(),
time: cells.eq(3).text(),

View File

@@ -69,10 +69,10 @@ function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) {
}
}
fetch(url, options)
.then(async(response) => {
.then(async (response) => {
if (!response.ok) {
let msg ;
try{
let msg;
try {
let _xxx = await response.json();
msg = _xxx.message || response.statusText;
} catch {
@@ -281,10 +281,10 @@ window.redcircle = null;
*/
$(document).ready(function () {
document.title = "Automatic Announcement System"
if (window.greencircle === null){
if (window.greencircle === null) {
fetchImg('green_circle.png', (url) => { window.greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); });
}
if (window.redcircle === null){
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'
@@ -313,52 +313,81 @@ $(document).ready(function () {
getScheduledDays();
// Initialize WebSocket connection
window.ws = new WebSocket(wsURL);
// reconnect handle
let ws_reconnect;
window.ws.onopen = () => {
console.log('WebSocket connection established');
$('#onlineindicator').attr('src', window.greencircle);
};
window.ws.onmessage = (event) => {
if ($('#onlineindicator').attr('src') !== window.greencircle) {
function reconnect() {
if (window.ws && window.ws.readyState === WebSocket.OPEN) return;
const s = new WebSocket(wsURL);
s.addEventListener('open', () => {
console.log('WebSocket connection established');
$('#onlineindicator').attr('src', window.greencircle);
}
let rep = JSON.parse(event.data);
let cmd = rep.reply
let data = rep.data;
if (cmd && cmd.length > 0) {
switch (cmd) {
case "getCPUStatus":
$('#cpustatus').text("CPU : " + data)
break;
case "getMemoryStatus":
$('#ramstatus').text("RAM : " + data)
break;
case "getDiskStatus":
$('#diskstatus').text("Disk : " + data)
break;
case "getNetworkStatus":
$('#networkstatus').text("Network : " + data)
break;
case "getSystemTime":
$('#datetimetext').text(data)
break;
if (ws_reconnect) {
// stop reconnect attempts
clearTimeout(ws_reconnect);
ws_reconnect = null;
}
window.dispatchEvent(new Event('ws_connected'));
});
s.addEventListener('close', () => {
console.log('WebSocket connection closed');
window.dispatchEvent(new Event('ws_disconnected'));
resetStatusIndicators();
if (!ws_reconnect) {
clearTimeout(ws_reconnect);
ws_reconnect = null;
}
ws_reconnect = setTimeout(reconnect, 5000); // try to reconnect every 5 seconds
});
s.addEventListener('message', (event) => {
if ($('#onlineindicator').attr('src') !== window.greencircle) {
$('#onlineindicator').attr('src', window.greencircle);
}
let rep = JSON.parse(event.data);
window.dispatchEvent(new CustomEvent('ws_message', { detail: rep }));
let cmd = rep.reply
let data = rep.data;
if (cmd && cmd.length > 0) {
switch (cmd) {
case "getCPUStatus":
$('#cpustatus').text("CPU : " + data)
break;
case "getMemoryStatus":
$('#ramstatus').text("RAM : " + data)
break;
case "getDiskStatus":
$('#diskstatus').text("Disk : " + data)
break;
case "getNetworkStatus":
let result = "";
let json = JSON.parse(data);
if (Array.isArray(json) && json.length > 0) {
json.forEach((net) => {
if (result.length > 0) result += "\n"
result += `${net.displayName} (${net.ipV4addr.join(";")}) TX:${(net.txSpeed / 1024).toFixed(1)} KB/s RX:${(net.rxSpeed / 1024).toFixed(1)} KB/s`
})
} else result = "N/A";
$('#networkstatus').text(result)
break;
case "getSystemTime":
$('#datetimetext').text(data)
break;
}
}
});
window.ws = s;
}
reconnect();
window.addEventListener('beforeunload', () => {
try{
window.ws?.close(1000, "Client closed connection");
} catch (error) {
console.error("Error closing WebSocket connection:", error);
}
};
window.ws.onclose = () => {
console.log('WebSocket connection closed');
resetStatusIndicators();
};
// window.ws.onerror = (error) => {
// console.error('WebSocket error:', error);
// };
});
setInterval(() => {
sendCommand("getCPUStatus", "")

View File

@@ -1,3 +1,11 @@
/**
* @typedef {Object} Select2item
* @property {number} id
* @property {string} text
*/
/**
* @typedef {Object} SoundBank
* @property {number} index
@@ -9,12 +17,6 @@
* @property {string} path
*/
/**
* @typedef {Object} Select2item
* @property {number} id
* @property {string} text
*/
/**
* List of Soundbank data loaded from server
* @type {SoundBank[]}
@@ -98,8 +100,9 @@ function fill_soundbanktablebody(vv) {
* @param {String} language
* @param {String} category
* @param {String} voiceType
* @param {Function} cb callback function when done
*/
function reloadSoundbankFiles(language, category, voiceType) {
function reloadSoundbankFiles(language, category, voiceType, cb=null) {
window.select2data = [];
$('#modalpath').empty().trigger('change');
if (language && language.length > 0) {
@@ -117,6 +120,7 @@ function reloadSoundbankFiles(language, category, voiceType) {
width: '100%',
dropdownParent: $('#soundbankmodal')
});
if (cb) cb();
}
}, (errdata) => {
alert("Error loading soundbank files : " + errdata.message);
@@ -278,6 +282,7 @@ $(document).ready(function () {
* @type {SoundBank}
*/
let nsb = {
index: 0,
Description: description,
TAG: tag,
Category: category,
@@ -302,7 +307,7 @@ $(document).ready(function () {
let cells = window.selectedsoundrow.find('td');
/** @type {SoundBank} */
let sb = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
description: cells.eq(1).text(),
tag: cells.eq(2).text(),
category: cells.eq(3).text(),
@@ -325,7 +330,7 @@ $(document).ready(function () {
let cells = window.selectedsoundrow.find('td');
/** @type {SoundBank} */
let sb = {
index: cells.eq(0).text(),
index: Number(cells.eq(0).text()),
Description: cells.eq(1).text(),
TAG: cells.eq(2).text(),
Category: cells.eq(3).text(),
@@ -336,16 +341,24 @@ $(document).ready(function () {
if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.Description} Tag=${sb.TAG}?`)) {
$modal.modal('show');
clearSoundbankModal();
SetupEventForCategoryLanguageVoiceType();
$modalindex.val(sb.index).prop('disabled', true);
$modaldescription.val(sb.Description);
$modaltag.val(sb.TAG);
$modalcategory.val(sb.Category);
selected_category = sb.Category;
$modallanguage.val(sb.Language);
selected_language = sb.Language;
$modalvoicetype.val(sb.VoiceType);
$('#modalpath').val(sb.Path).trigger('change');
selected_voicetype = sb.VoiceType;
// load soundbank files for selected language, category, voiceType
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype, () => {
// set selected path
$('#modalpath').val(getFilenameFromPath(sb.Path)).trigger('change');
});
SetupEventForCategoryLanguageVoiceType();
// event on Click save button
$modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
let description = $modaldescription.val().trim();
@@ -388,7 +401,7 @@ $(document).ready(function () {
sb.Language = language;
sb.VoiceType = voiceType;
sb.Path = path;
fetchAPI(APIURL + "UpdateByIndex/" + sb.index, "PUT", {}, sb, (okdata) => {
fetchAPI(APIURL + "UpdateByIndex/" + sb.index, "PATCH", {}, sb, (okdata) => {
reloadSoundBank(APIURL);
alert("Success update soundbank : " + okdata.message);
}, (errdata) => {

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -196,9 +195,9 @@
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th class="col-sm-3">Description</th>
<th class="col">IP Address</th>
<th class="class05">No</th>
<th class="class75">Description</th>
<th class="class20">IP Address</th>
</tr>
</thead>
<tbody id="soundchanneltablebody"></tbody>
@@ -237,11 +236,11 @@
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th class="col-sm-2">Description</th>
<th class="col-sm-2">SoundChannel</th>
<th class="col-sm-2">ID</th>
<th class="col">BP</th>
<th class="class05">No</th>
<th class="class40">Description</th>
<th class="class20">SoundChannel</th>
<th class="class05">ID</th>
<th class="class30">BP</th>
</tr>
</thead>
<tbody id="broadcastzonetablebody"></tbody>
@@ -287,7 +286,6 @@
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/soundchannel.js"></script>
<script src="assets/js/broadcastzones.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -43,9 +42,9 @@
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th class="col-sm-2">TAG</th>
<th class="col">Languages</th>
<th class="class05">No</th>
<th class="class20">TAG</th>
<th class="class75">Languages</th>
</tr>
</thead>
<tbody id="languagebanktablebody"></tbody>
@@ -92,7 +91,6 @@
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/languagelink.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -40,11 +39,11 @@
<table class="table">
<thead>
<tr>
<th class="col-1 col-md-1">No</th>
<th class="col-2 col-md-2 col-lg-2">Date</th>
<th class="col-2 col-md-2 col-lg-2">Time</th>
<th class="col-2 col-md-2 col-lg-2">Machine</th>
<th>Description</th>
<th class="class10">No</th>
<th class="class15">Date</th>
<th class="class15">Time</th>
<th class="class15">Machine</th>
<th class="class45">Description</th>
</tr>
</thead>
<tbody id="logtablebody"></tbody>
@@ -54,7 +53,6 @@
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/log.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -7,7 +7,6 @@
<title>AAS NewGeneration 17092025</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -15,22 +14,21 @@
<body>
<section class="position-relative py-4 py-xl-5">
<div class="container">
<div class="row mb-5">
<div class="col-md-8 col-xl-6 text-center mx-auto">
<h2>Sign In</h2>
</div>
</div>
<div class="row d-flex justify-content-center">
<div class="row mb-4"></div>
<div class="row d-flex justify-content-center mb-7">
<div class="col-md-6 col-xl-4">
<div class="card mb-5 card-login">
<div class="card-body d-flex flex-column align-items-center">
<div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person bg-icon-login">
<div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon bg-icon-login"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person bg-icon-login">
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664z"></path>
</svg></div>
<form class="text-center" method="post">
<div class="mb-3"><input class="form-control input-login" type="text" name="username" placeholder="Username"></div>
<div class="mb-3"><input class="form-control input-login" type="password" name="password" placeholder="Password"></div>
<div class="mb-3"><button class="btn btn-primary d-block w-100 btn-login" type="submit">Sign In</button></div>
<h2 class="mb-3 h-login">Login</h2>
<form class="text-center py-2 bottom-signin" method="post">
<p class="p-login">Username</p>
<div class="mb-3"><input class="form-control input-login" type="text" name="username" placeholder="Enter your username"></div>
<p class="p-login">Password</p>
<div class="mb-3"><input class="form-control input-login" type="password" name="password" placeholder="Enter your password"></div>
<div class="mb-3 py-2"><button class="btn btn-primary d-block w-100 btn-login" type="submit">Login</button></div>
</form>
</div>
</div>
@@ -40,7 +38,6 @@
</section>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -43,13 +42,13 @@
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th class="col-sm-2">Description</th>
<th class="col-sm-1">Language</th>
<th class="col-sm-1">ANN ID</th>
<th class="col-sm-1">Type</th>
<th class="col-sm-3">Message Details</th>
<th class="col-sm-3">Message Tags</th>
<th class="class05">No</th>
<th class="class15">Description</th>
<th class="class10">Language</th>
<th class="class10">ANN ID</th>
<th class="class10">Type</th>
<th class="class35">Message Details</th>
<th class="class15">Message Tags</th>
</tr>
</thead>
<tbody id="messagebanktablebody"></tbody>
@@ -134,7 +133,6 @@
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/messagebank.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -103,7 +102,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-4">
<div class="card card-channel" id="streamercard-4">
<div class="card-body">
<h4 class="card-title" id="streamertitle05">Channel 05</h4>
<div class="row">
@@ -122,7 +121,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-5">
<div class="card card-channel" id="streamercard-5">
<div class="card-body">
<h4 class="card-title" id="streamertitle06">Channel 06</h4>
<div class="row">
@@ -141,7 +140,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-6">
<div class="card card-channel" id="streamercard-6">
<div class="card-body">
<h4 class="card-title" id="streamertitle07">Channel 07</h4>
<div class="row">
@@ -160,7 +159,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-7">
<div class="card card-channel" id="streamercard-7">
<div class="card-body">
<h4 class="card-title" id="streamertitle08">Channel 08</h4>
<div class="row">
@@ -181,7 +180,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-8">
<div class="card card-channel" id="streamercard-8">
<div class="card-body">
<h4 class="card-title" id="streamertitle09">Channel 09</h4>
<div class="row">
@@ -200,7 +199,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-9">
<div class="card card-channel" id="streamercard-9">
<div class="card-body">
<h4 class="card-title" id="streamertitle10">Channel 10</h4>
<div class="row">
@@ -219,7 +218,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-10">
<div class="card card-channel" id="streamercard-10">
<div class="card-body">
<h4 class="card-title" id="streamertitle11">Channel 11</h4>
<div class="row">
@@ -238,7 +237,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-11">
<div class="card card-channel" id="streamercard-11">
<div class="card-body">
<h4 class="card-title" id="streamertitle12">Channel 12</h4>
<div class="row">
@@ -259,7 +258,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-12">
<div class="card card-channel" id="streamercard-12">
<div class="card-body">
<h4 class="card-title" id="streamertitle13">Channel 13</h4>
<div class="row">
@@ -278,7 +277,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-13">
<div class="card card-channel" id="streamercard-13">
<div class="card-body">
<h4 class="card-title" id="streamertitle14">Channel 14</h4>
<div class="row">
@@ -297,7 +296,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-14">
<div class="card card-channel" id="streamercard-14">
<div class="card-body">
<h4 class="card-title" id="streamertitle15">Channel 15</h4>
<div class="row">
@@ -316,7 +315,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-15">
<div class="card card-channel" id="streamercard-15">
<div class="card-body">
<h4 class="card-title" id="streamertitle16">Channel 16</h4>
<div class="row">
@@ -337,7 +336,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-16">
<div class="card card-channel" id="streamercard-16">
<div class="card-body">
<h4 class="card-title" id="streamertitle17">Channel 17</h4>
<div class="row">
@@ -356,7 +355,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-17">
<div class="card card-channel" id="streamercard-17">
<div class="card-body">
<h4 class="card-title" id="streamertitle18">Channel 18</h4>
<div class="row">
@@ -375,7 +374,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-18">
<div class="card card-channel" id="streamercard-18">
<div class="card-body">
<h4 class="card-title" id="streamertitle19">Channel 19</h4>
<div class="row">
@@ -394,7 +393,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-19">
<div class="card card-channel" id="streamercard-19">
<div class="card-body">
<h4 class="card-title" id="streamertitle20">Channel 20</h4>
<div class="row">
@@ -415,7 +414,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-20">
<div class="card card-channel" id="streamercard-20">
<div class="card-body">
<h4 class="card-title" id="streamertitle21">Channel 21</h4>
<div class="row">
@@ -434,7 +433,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-21">
<div class="card card-channel" id="streamercard-21">
<div class="card-body">
<h4 class="card-title" id="streamertitle22">Channel 22</h4>
<div class="row">
@@ -453,7 +452,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-22">
<div class="card card-channel" id="streamercard-22">
<div class="card-body">
<h4 class="card-title" id="streamertitle23">Channel 23</h4>
<div class="row">
@@ -472,7 +471,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-23">
<div class="card card-channel" id="streamercard-23">
<div class="card-body">
<h4 class="card-title" id="streamertitle24">Channel 24</h4>
<div class="row">
@@ -493,7 +492,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-24">
<div class="card card-channel" id="streamercard-24">
<div class="card-body">
<h4 class="card-title" id="streamertitle25">Channel 25</h4>
<div class="row">
@@ -512,7 +511,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-25">
<div class="card card-channel" id="streamercard-25">
<div class="card-body">
<h4 class="card-title" id="streamertitle26">Channel 26</h4>
<div class="row">
@@ -531,7 +530,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-26">
<div class="card card-channel" id="streamercard-26">
<div class="card-body">
<h4 class="card-title" id="streamertitle27">Channel 27</h4>
<div class="row">
@@ -550,7 +549,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-27">
<div class="card card-channel" id="streamercard-27">
<div class="card-body">
<h4 class="card-title" id="streamertitle28">Channel 28</h4>
<div class="row">
@@ -571,7 +570,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-28">
<div class="card card-channel" id="streamercard-28">
<div class="card-body">
<h4 class="card-title" id="streamertitle29">Channel 29</h4>
<div class="row">
@@ -590,7 +589,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-29">
<div class="card card-channel" id="streamercard-29">
<div class="card-body">
<h4 class="card-title" id="streamertitle30">Channel 30</h4>
<div class="row">
@@ -609,7 +608,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-30">
<div class="card card-channel" id="streamercard-30">
<div class="card-body">
<h4 class="card-title" id="streamertitle31">Channel 31</h4>
<div class="row">
@@ -628,7 +627,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-31">
<div class="card card-channel" id="streamercard-31">
<div class="card-body">
<h4 class="card-title" id="streamertitle32">Channel 32</h4>
<div class="row">
@@ -649,7 +648,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-32">
<div class="card card-channel" id="streamercard-32">
<div class="card-body">
<h4 class="card-title" id="streamertitle33">Channel 33</h4>
<div class="row">
@@ -668,7 +667,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-33">
<div class="card card-channel" id="streamercard-33">
<div class="card-body">
<h4 class="card-title" id="streamertitle34">Channel 34</h4>
<div class="row">
@@ -687,7 +686,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-34">
<div class="card card-channel" id="streamercard-34">
<div class="card-body">
<h4 class="card-title" id="streamertitle35">Channel 35</h4>
<div class="row">
@@ -706,7 +705,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-35">
<div class="card card-channel" id="streamercard-35">
<div class="card-body">
<h4 class="card-title" id="streamertitle36">Channel 36</h4>
<div class="row">
@@ -727,7 +726,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-36">
<div class="card card-channel" id="streamercard-36">
<div class="card-body">
<h4 class="card-title" id="streamertitle37">Channel 37</h4>
<div class="row">
@@ -746,7 +745,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-37">
<div class="card card-channel" id="streamercard-37">
<div class="card-body">
<h4 class="card-title" id="streamertitle38">Channel 38</h4>
<div class="row">
@@ -765,7 +764,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-38">
<div class="card card-channel" id="streamercard-38">
<div class="card-body">
<h4 class="card-title" id="streamertitle39">Channel 39</h4>
<div class="row">
@@ -784,7 +783,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-39">
<div class="card card-channel" id="streamercard-39">
<div class="card-body">
<h4 class="card-title" id="streamertitle40">Channel 40</h4>
<div class="row">
@@ -805,7 +804,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-40">
<div class="card card-channel" id="streamercard-40">
<div class="card-body">
<h4 class="card-title" id="streamertitle41">Channel 41</h4>
<div class="row">
@@ -824,7 +823,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-41">
<div class="card card-channel" id="streamercard-41">
<div class="card-body">
<h4 class="card-title" id="streamertitle42">Channel 42</h4>
<div class="row">
@@ -843,7 +842,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-42">
<div class="card card-channel" id="streamercard-42">
<div class="card-body">
<h4 class="card-title" id="streamertitle43">Channel 43</h4>
<div class="row">
@@ -862,7 +861,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-43">
<div class="card card-channel" id="streamercard-43">
<div class="card-body">
<h4 class="card-title" id="streamertitle44">Channel 44</h4>
<div class="row">
@@ -883,7 +882,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-44">
<div class="card card-channel" id="streamercard-44">
<div class="card-body">
<h4 class="card-title" id="streamertitle-44">Channel 45</h4>
<div class="row">
@@ -902,7 +901,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-45">
<div class="card card-channel" id="streamercard-45">
<div class="card-body">
<h4 class="card-title" id="streamertitle46">Channel 46</h4>
<div class="row">
@@ -921,7 +920,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-46">
<div class="card card-channel" id="streamercard-46">
<div class="card-body">
<h4 class="card-title" id="streamertitle47">Channel 47</h4>
<div class="row">
@@ -940,7 +939,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-47">
<div class="card card-channel" id="streamercard-47">
<div class="card-body">
<h4 class="card-title" id="streamertitle48">Channel 48</h4>
<div class="row">
@@ -961,7 +960,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-48">
<div class="card card-channel" id="streamercard-48">
<div class="card-body">
<h4 class="card-title" id="streamertitle49">Channel 49</h4>
<div class="row">
@@ -980,7 +979,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-49">
<div class="card card-channel" id="streamercard-49">
<div class="card-body">
<h4 class="card-title" id="streamertitle50">Channel 50</h4>
<div class="row">
@@ -999,7 +998,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-50">
<div class="card card-channel" id="streamercard-50">
<div class="card-body">
<h4 class="card-title" id="streamertitle51">Channel 51</h4>
<div class="row">
@@ -1018,7 +1017,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-51">
<div class="card card-channel" id="streamercard-51">
<div class="card-body">
<h4 class="card-title" id="streamertitle52">Channel 52</h4>
<div class="row">
@@ -1039,7 +1038,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-52">
<div class="card card-channel" id="streamercard-52">
<div class="card-body">
<h4 class="card-title" id="streamertitle53">Channel 53</h4>
<div class="row">
@@ -1058,7 +1057,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-53">
<div class="card card-channel" id="streamercard-53">
<div class="card-body">
<h4 class="card-title" id="streamertitle54">Channel 54</h4>
<div class="row">
@@ -1077,7 +1076,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-54">
<div class="card card-channel" id="streamercard-54">
<div class="card-body">
<h4 class="card-title" id="streamertitle55">Channel 55</h4>
<div class="row">
@@ -1096,7 +1095,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-55">
<div class="card card-channel" id="streamercard-55">
<div class="card-body">
<h4 class="card-title" id="streamertitle56">Channel 56</h4>
<div class="row">
@@ -1117,7 +1116,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-56">
<div class="card card-channel" id="streamercard-56">
<div class="card-body">
<h4 class="card-title" id="streamertitle57">Channel 57</h4>
<div class="row">
@@ -1136,7 +1135,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-57">
<div class="card card-channel" id="streamercard-57">
<div class="card-body">
<h4 class="card-title" id="streamertitle58">Channel 58</h4>
<div class="row">
@@ -1155,7 +1154,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-58">
<div class="card card-channel" id="streamercard-58">
<div class="card-body">
<h4 class="card-title" id="streamertitle59">Channel 59</h4>
<div class="row">
@@ -1174,7 +1173,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-59">
<div class="card card-channel" id="streamercard-59">
<div class="card-body">
<h4 class="card-title" id="streamertitle60">Channel 60</h4>
<div class="row">
@@ -1195,7 +1194,7 @@
</div>
<div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-60">
<div class="card card-channel" id="streamercard-60">
<div class="card-body">
<h4 class="card-title" id="streamertitle61">Channel 61</h4>
<div class="row">
@@ -1214,7 +1213,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-61">
<div class="card card-channel" id="streamercard-61">
<div class="card-body">
<h4 class="card-title" id="streamertitle62">Channel 62</h4>
<div class="row">
@@ -1233,7 +1232,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-62">
<div class="card card-channel" id="streamercard-62">
<div class="card-body">
<h4 class="card-title" id="streamertitle63">Channel 63</h4>
<div class="row">
@@ -1252,7 +1251,7 @@
</div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-63">
<div class="card card-channel" id="streamercard-63">
<div class="card-body">
<h4 class="card-title" id="streamertitle64">Channel 64</h4>
<div class="row">
@@ -1264,7 +1263,7 @@
</div>
</div>
<p class="card-text" id="streamerstatus64">Status : Idle</p>
<div class="progress" id="streamervu-63">
<div class="progress" id="streamervu64">
<div class="progress-bar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">50%</div>
</div>
</div>
@@ -1283,8 +1282,12 @@
<table class="table">
<thead>
<tr>
<th>Index</th>
<th>Description</th>
<th class="class05">Index</th>
<th class="class10">Date Time</th>
<th class="class15">Source</th>
<th class="class10">Type</th>
<th class="class30">Message</th>
<th class="class30">Broadcast Zones</th>
</tr>
</thead>
<tbody id="pagingqueuetable"></tbody>
@@ -1307,8 +1310,12 @@
<table class="table">
<thead>
<tr>
<th>Index</th>
<th>Description</th>
<th class="class05">Index</th>
<th class="class10">Date Time</th>
<th class="class15">Source</th>
<th class="class10">Type</th>
<th class="class30">Message</th>
<th class="class30">Broadcast Zones</th>
</tr>
</thead>
<tbody id="automaticqueuetable"></tbody>
@@ -1325,7 +1332,7 @@
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/select2.min.js"></script>
<script src="assets/js/overview.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -18,9 +17,70 @@
<h2 style="text-align: center;">Setting</h2>
</div>
</div>
<div class="row">
<div class="col">
<div class="card card-setting">
<div class="card-body">
<h4 class="card-title"><strong>Upload Soundbank</strong></h4>
<hr>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Path</label></div>
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 form-control" type="text" id="setting_path"></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="save_directory" type="button">Save Directory</button></div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Category</label></div>
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_category" class="input-add form-select"></select></div>
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Language</label></div>
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_language" class="input-add form-select"></select></div>
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Voice</label></div>
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_voice" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<div class="bg-white w-100" id="drop-area" multiple=""><input type="file" id="file-input"><label class="form-label d">Drop files here or click to select</label></div>
</div>
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<div class="row"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row py-5">
<div class="col">
<div class="card card-setting">
<div class="card-body pad-accordion">
<h4 class="card-title"><strong>FIS CODE</strong></h4>
<hr>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GOP</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GOP" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GBP</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GBP" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GFC</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GFC" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">FLD</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_FLD" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label"></label></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" type="button">Save</button></div>
</div>
</div>
</div>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/select2.min.js"></script>
<script src="assets/js/dragdrop.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -43,13 +42,13 @@
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th>Description</th>
<th class="col-sm-1">TAG</th>
<th class="col-sm-1">Category</th>
<th class="col-sm-1">Language</th>
<th class="col-sm-1">Type</th>
<th class="col-sm-3">Filename</th>
<th class="class05">No</th>
<th class="class20">Description</th>
<th class="class10">TAG</th>
<th class="class15">Category</th>
<th class="class15">Language</th>
<th class="class10">Type</th>
<th class="class25">Filename</th>
</tr>
</thead>
<tbody id="soundbanktablebody"></tbody>
@@ -103,7 +102,7 @@
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Path</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalpath" class="input-add form-select" name="modalpath" data-control="select2"></select></div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalpath" class="input-add form-select cw-100" name="modalpath" data-control="select2"></select></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>
@@ -113,7 +112,6 @@
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/soundbank.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -32,7 +31,6 @@
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -43,14 +42,15 @@
<table class="table">
<thead>
<tr>
<th class="col-sm-1">No</th>
<th class="col-sm-2">Description</th>
<th class="col-sm-1">Day</th>
<th class="col-sm-1">Time</th>
<th class="col-sm-2">Sound Path</th>
<th class="col-sm-1">Repeat</th>
<th class="col-sm-1">Enable</th>
<th>Broadcast Zones</th>
<th class="class05">No</th>
<th class="class15">Description</th>
<th class="class15">Day</th>
<th class="class10">Time</th>
<th class="class15">Sound Path</th>
<th class="class10">Repeat</th>
<th class="class05">Enable</th>
<th class="class15">Broadcast Zones</th>
<th class="class10">Language</th>
</tr>
</thead>
<tbody id="schedulebanktablebody"></tbody>
@@ -88,37 +88,13 @@
</div>
<div class="row pad-day">
<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>
</div>
<div class="row pad-day">
<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>
</div>
<div class="row pad-day">
<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>
</div>
<div class="row pad-day">
<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>
</div>
<div class="row pad-day">
<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>
</div>
<div class="row pad-day">
<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>
</div>
<div class="row pad-day">
<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="scheduleweekly"><label class="form-check-label" for="formCheck-1">Weekly</label></div><select class="w-100 input-add form-select" id="weeklyselect">
<optgroup label="This is a group">
<option value="12" selected="">This is item 1</option>
<option value="13">This is item 2</option>
<option value="14">This is item 3</option>
</optgroup>
</select>
</div>
</div>
<div class="row">
@@ -135,12 +111,12 @@
</div>
<div class="col">
<div class="row w-100 h-100">
<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-2 col-sm-2 col-md-2 col-lg-3 col-xl-3">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input type="number" id="schedulehour" class="input-add form-control class100" value="0" min="0" max="23" step="1"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<p class="pad-time">(H)</p>
</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-2 col-sm-2 col-md-2 col-lg-3 col-xl-3">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 input-add form-control" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<p class="pad-time">(M)</p>
</div>
</div>
@@ -148,9 +124,25 @@
</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 Path</p>
<p class="text-add">Message</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="schedulemessage" class="input-add form-select"></select></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">Language</p>
</div>
<div class="col">
<div class="row pad-day">
<div class="col"><select class="w-100 input-add form-select" id="languageselect">
<optgroup label="This is a group">
<option value="12" selected="">This is item 1</option>
<option value="13">This is item 2</option>
<option value="14">This is item 3</option>
</optgroup>
</select></div>
</div>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="schedulesoundpath" 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">
@@ -168,7 +160,13 @@
<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 border" id="schedulezones"></div>
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-8 border"><select class="w-100 input-add form-select" id="schedulezones">
<optgroup label="This is a group">
<option value="12" selected="">This is item 1</option>
<option value="13">This is item 2</option>
<option value="14">This is item 3</option>
</optgroup>
</select></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>
@@ -178,7 +176,6 @@
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/schedulebank.js"></script>
<script src="assets/js/select2.min.js"></script>
</body>
</html>

View File

@@ -4,10 +4,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_08OKT25</title>
<title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css">
</head>
@@ -43,13 +42,13 @@
<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-sm-2">Airline</th>
<th class="col-sm-2">City</th>
<th class="col-sm-2">Messagebank</th>
<th class="col">Broadcast Zones</th>
<th class="class05">No</th>
<th class="class10">Username</th>
<th class="class15">Location</th>
<th class="class15">Airline</th>
<th class="class15">City</th>
<th class="class20">Messagebank</th>
<th class="class20">Broadcast Zones</th>
</tr>
</thead>
<tbody id="usertablebody"></tbody>
@@ -200,7 +199,6 @@
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/select2.min.js"></script>
<script src="assets/js/usermanagement.js"></script>
</body>

View File

@@ -31,7 +31,7 @@ lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver
lateinit var tcpreceiver: TCPReceiver
const val version = "0.0.5 (09/10/2025)"
const val version = "0.0.8 (16/10/2025)"
// AAS 64 channels
const val max_channel = 64
@@ -167,7 +167,6 @@ fun main() {
val barixserver = TCP_Barix_Command_Server()
barixserver.StartTcpServer { cmd ->
//Logger.info { cmd }
val _tcp = barixserver.getSocket(cmd.ipaddress)
val _streamer = StreamerOutputs[cmd.ipaddress]
val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress }
@@ -177,7 +176,8 @@ fun main() {
if (_sc != null) {
val _bc = BarixConnection(_sc.index, _sc.channel, cmd.ipaddress)
_bc.vu = cmd.vu
// cmd.vu 0 - 32767, kita convert ke 0 - 100
_bc.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
_bc.bufferRemain = cmd.buffremain
_bc.statusData = cmd.statusdata
_bc.commandsocket = _tcp
@@ -191,7 +191,8 @@ fun main() {
if (_sc != null && _sc.channel != _streamer.channel) {
_streamer.channel = _sc.channel
}
_streamer.vu = cmd.vu
// cmd.vu 0 - 32767, kita convert ke 0 - 100
_streamer.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
_streamer.bufferRemain = cmd.buffremain
_streamer.statusData = cmd.statusdata

View File

@@ -260,7 +260,7 @@ class MainExtension01 {
val value = variables["AL"].orEmpty()
if (ValidString(value)) {
val airplane =
sb.firstOrNull { it.Category == Category.Airline_Name.name && it.TAG == value }
sb.firstOrNull { it.Category == Category.Airplane_Name.name && it.TAG == value }
if (airplane != null) {
if (ValidFile(airplane.Path)) {
files.add(airplane.Path)
@@ -561,7 +561,7 @@ class MainExtension01 {
.filter { it.Type=="PAGING" }
list.forEach { qp ->
println("Processing $qp")
//println("Processing $qp")
if (qp.BroadcastZones.isNotBlank()){
if (ValidFile(qp.Message)){
val zz = qp.BroadcastZones.split(";")
@@ -588,11 +588,12 @@ class MainExtension01 {
Logger.info { logmessage }
db.Add_Log("AAS", logmessage)
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
return true
} else {
// file tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log(
"AAS",
"Cancelled paging message with index ${qp.index} due to invalid audio file"
@@ -602,11 +603,13 @@ class MainExtension01 {
} else {
// ada broadcast zone yang tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled paging message with index ${qp.index} due to invalid broadcast zone")
}
} else {
// file tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log(
"AAS",
"Cancelled paging message with index ${qp.index} due to invalid audio file"
@@ -615,6 +618,7 @@ class MainExtension01 {
} else {
// invalid broadcast zone, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled paging message with index ${qp.index} due to empty broadcast zone")
}
}
@@ -688,6 +692,7 @@ class MainExtension01 {
Logger.info { logmsg }
db.Add_Log("AAS", logmsg)
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
return true
} else {
db.Add_Log(
@@ -698,6 +703,7 @@ class MainExtension01 {
}
} else{
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", result.message)
}
}
@@ -706,6 +712,7 @@ class MainExtension01 {
} else {
// tidak ada messagebank dengan ann_id ini, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log(
"AAS",
"Cancelled Shalat message with index ${qp.index} due to ANN_ID $ann_id not found in Messagebank"
@@ -716,16 +723,19 @@ class MainExtension01 {
} else {
// invalid ann_id, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to invalid ANN_ID")
}
} else {
// ada broadcast zone yang tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to invalid broadcast zone")
}
} else {
// invalid broadcast zone, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to empty broadcast zone")
}
}
@@ -789,14 +799,17 @@ class MainExtension01 {
Logger.info { logmsg }
db.Add_Log("AAS", logmsg)
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
return true
}
} else{
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", result.message)
}
} else {
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", result.message)
}
@@ -805,6 +818,7 @@ class MainExtension01 {
} else {
// tidak ada messagebank dengan ann_id ini, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log(
"AAS",
"Cancelled TIMER with index ${qa.index} due to ANN_ID $ann_id not found in Messagebank"
@@ -815,16 +829,19 @@ class MainExtension01 {
} else {
// invalid ann_id, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to invalid ANN_ID")
}
} else {
// ada broadcast zone yang tidak valid, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to invalid broadcast zone")
}
} else {
// invalid broadcast zone, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to empty broadcast zone")
}
}
@@ -866,7 +883,7 @@ class MainExtension01 {
db.queuetableDB.Get()
val list = db.queuetableDB.List.filter { it.Type=="SOUNDBANK" }
list.forEach { qa ->
println("Processing $qa")
//println("Processing $qa")
if (qa.BroadcastZones.isNotEmpty()){
val zz = qa.BroadcastZones.split(";")
if (AllBroadcastZonesValid(zz)){
@@ -903,6 +920,7 @@ class MainExtension01 {
"Cancelled SOUNDBANK message with index ${qa.index} due to missing or invalid ANN_ID in SB_TAGS"
)
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
return@forEach
}
// sampe sini punya ann_id valid
@@ -930,12 +948,14 @@ class MainExtension01 {
} else {
db.Add_Log("AAS", result.message)
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
}
}
if (listafi.isNotEmpty()){
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
// bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore
// dan titik dua diganti dash
val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-")
@@ -972,6 +992,7 @@ class MainExtension01 {
} else {
println("Failed to write WAV file: ${result.message}")
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", result.message)
}
@@ -979,6 +1000,7 @@ class MainExtension01 {
} else {
// tidak ada messagebank dengan ann_id ini, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log(
"AAS",
"Cancelled SOUNDBANK message with index ${qa.index} due to ANN_ID $ann_id not found in Messagebank"
@@ -990,11 +1012,13 @@ class MainExtension01 {
} else {
// ada broadcast zone yang tidak valid, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled SOUNDBANK message with index ${qa.index} due to invalid broadcast zone")
}
} else {
// invalid broadcast zone, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled SOUNDBANK message with index ${qa.index} due to empty broadcast zone")
}
}

View File

@@ -20,7 +20,6 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
private var _sd: Int = 0
private var _vu: Int = 0
private var _onlinecounter = 0
private val udp = DatagramSocket(0)
private val inet = InetSocketAddress(ipaddress, port)
private val maxUDPsize = 1000
private var _tcp: Socket? = null
@@ -105,24 +104,26 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) {
if (data.isNotEmpty()) {
CoroutineScope(Dispatchers.IO).launch {
val bb = ByteBuffer.wrap(data)
while(bb.hasRemaining()){
try {
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
bb.get(chunk)
while(bufferRemain<chunk.size){
delay(10)
DatagramSocket().use{ udp ->
val bb = ByteBuffer.wrap(data)
while(bb.hasRemaining()){
try {
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
bb.get(chunk)
//println("Buffer remain: $bufferRemain, sending chunk size: ${chunk.size}")
while(bufferRemain<chunk.size){
delay(10)
//println("Waiting until buffer enough..")
}
udp.send(DatagramPacket(chunk, chunk.size, inet))
delay(2)
} catch (e: Exception) {
cbFail.accept("SendData to $ipaddress:$port failed, message: ${e.message}")
return@launch
}
udp.send(DatagramPacket(chunk, chunk.size, inet))
delay(1)
} catch (e: Exception) {
cbFail.accept("SendData to $ipaddress:$port failed, message: ${e.message}")
return@launch
}
cbOK.accept("SendData to $channel ($ipaddress:$port) succeeded, ${data.size} bytes sent")
}
cbOK.accept("SendData to $channel ($ipaddress:$port) succeeded, ${data.size} bytes sent")
}
} else cbFail.accept("SendData to $ipaddress:$port failed, data is empty")

View File

@@ -3,8 +3,10 @@ package barix
import codes.Somecodes.Companion.ValidString
import kotlinx.coroutines.*
import org.tinylog.Logger
import java.io.DataInputStream
import java.net.ServerSocket
import java.net.Socket
import java.nio.ByteBuffer
import java.util.function.Consumer
@Suppress("unused")
@@ -37,13 +39,19 @@ class TCP_Barix_Command_Server {
val key : String = socket.inetAddress.hostAddress
socketMap[key] = socket
Logger.info { "Start communicating with Streamer Output with IP : $key" }
val din = socket.getInputStream()
while (isActive) {
if (din.available()>0){
val bb = ByteArray(din.available())
din.read(bb)
try{
val din = DataInputStream(socket.getInputStream())
while (isActive) {
val length = ByteArray(4)
din.readFully(length)
val readlength = ByteBuffer.wrap(length).getInt()
//println("Read Length : $readlength")
val bb = ByteArray(readlength)
din.readFully(bb)
// B4A format, 4 bytes di depan adalah size
val str = String(bb, 4, bb.size - 4)
val str = String(bb)
//println("Received from $key : $str")
if (ValidString(str)) {
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
pattern.find(str)?.let { matchResult ->
@@ -60,8 +68,12 @@ class TCP_Barix_Command_Server {
Logger.warn { "Invalid command format from $key : $str" }
}
}
}
} catch (ex:Exception){
Logger.error { "Error in communication with Streamer Output with IP $key, Message : ${ex.message}" }
}
Logger.info { "Finished communicating with Streamer Output with IP $key" }
socketMap.remove(key)

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import content.Category
import content.Language
import content.NetworkInformation
import content.ScheduleDay
import content.VoiceType
import kotlinx.coroutines.CoroutineScope
@@ -15,6 +16,8 @@ import oshi.SystemInfo
import oshi.hardware.CentralProcessor
import oshi.hardware.GlobalMemory
import oshi.hardware.NetworkIF
import oshi.hardware.Sensors
import oshi.software.os.OperatingSystem
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDateTime
@@ -35,6 +38,9 @@ class Somecodes {
val si = SystemInfo()
val processor: CentralProcessor = si.hardware.processor
val memory : GlobalMemory = si.hardware.memory
val NetworkInfoMap = mutableMapOf<String, NetworkInformation>()
val sensor : Sensors = si.hardware.sensors
val os : OperatingSystem = si.operatingSystem
val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")
val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
@@ -62,6 +68,14 @@ class Somecodes {
return Soundbank_directory.resolve(language.name).resolve(voice.name).resolve(category.name)
}
fun SoundbankDirectory(language: String, voice: String, category: String) : Path{
return SoundbankDirectory(
Language.valueOf(language),
VoiceType.valueOf(voice),
Category.valueOf(category)
)
}
fun ExtractFilesFromClassPath(resourcePath: String, outputDir: Path) {
try {
val resource = Somecodes::class.java.getResource(resourcePath)
@@ -270,16 +284,43 @@ class Somecodes {
, (usedMemory.toDouble() / totalMemory * 100))
}
fun GetNetworkStatus(networkname: String) : String{
fun GetNetworkStatus(cb : Consumer<List<NetworkInformation>>) {
val networks: List<NetworkIF> = si.hardware.networkIFs.toList()
//TODO cari network yang sesuai dengan networkname
// networks.forEach { net ->
//
// println(net.name+" "+net.displayName+" ")
// net.updateAttributes()
// println("upload: ${SizetoHuman(net.bytesSent)} sent, download: ${SizetoHuman(net.bytesRecv)} received")
// }
return ""
networks.forEach { net ->
if (net.ifOperStatus==NetworkIF.IfOperStatus.UP){
if (net.iPv4addr.size>0 || net.iPv6addr.size>0){
var ni = NetworkInfoMap[net.name]
if (ni == null){
ni = NetworkInformation(net.name, net.displayName, net.macaddr)
NetworkInfoMap[net.name] = ni
}
ni.ipV4addr = net.iPv4addr.toMutableList()
ni.ipV6addr = net.iPv6addr.toMutableList()
ni.speed = net.speed
ni.packetsSent = net.packetsSent
ni.packetsRecv = net.packetsRecv
if (ni.updateStamp==0L){
ni.bytesSent = net.bytesSent
ni.bytesRecv = net.bytesRecv
ni.txSpeed = 0
ni.rxSpeed = 0
ni.updateStamp = System.currentTimeMillis()
} else {
// tx speed = (current bytesSent - previous bytesSent) / (current time - previous time) * 1000
val currentTime = System.currentTimeMillis()
ni.txSpeed = ((net.bytesSent - ni.bytesSent) * 1000 / (currentTime - ni.updateStamp))
ni.rxSpeed = ((net.bytesRecv - ni.bytesRecv) * 1000 / (currentTime - ni.updateStamp))
ni.bytesSent = net.bytesSent
ni.bytesRecv = net.bytesRecv
ni.updateStamp = currentTime
}
} else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name)
} else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name)
}
cb.accept(NetworkInfoMap.values.toList())
}
/**
@@ -455,6 +496,35 @@ class Somecodes {
sb.append(".wav")
return sb.toString()
}
/**
* Get sensors information using OSHI library.
* @return A string representing the CPU temperature, fan speeds, and CPU voltage, or an empty string if not available.
*/
fun GetSensorsInfo() : String {
val cputemp = sensor.cpuTemperature
val cpuvolt = sensor.cpuVoltage
val fanspeed = sensor.fanSpeeds
return if (cpuvolt>0 && cputemp > 0 && fanspeed.isNotEmpty()){
String.format("CPU Temp: %.1f °C\nFan Speeds: %s RPM\nCPU Voltage: %.2f V",
sensor.cpuTemperature,
sensor.fanSpeeds.joinToString("/"),
sensor.cpuVoltage
)
} else ""
}
fun GetUptime() : String {
val value = os.systemUptime
return if (value>0){
// number of seconds since system boot
val hours = value / 3600
val minutes = (value % 3600) / 60
val seconds = value % 60
String.format("%02d:%02d:%02d", hours, minutes, seconds)
} else ""
}
}

View File

@@ -69,7 +69,7 @@ class TCP_Android_Command_Server {
din.read(bb)
// B4A format, 4 bytes di depan adalah size
val str = String(bb, 4, bb.size - 4)
println("Received command from $key : $str")
//println("Received command from $key : $str")
str.split("@").map { it.trim() }.filter { ValidString(it) }
.forEach {
process_command(key,it) { reply ->
@@ -220,6 +220,7 @@ class TCP_Android_Command_Server {
pj.broadcastzones
)
if (db.queuepagingDB.Add(qp)) {
db.queuepagingDB.Resort()
logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
cb.accept("PCMFILE_STOP;OK@")
return
@@ -274,6 +275,7 @@ class TCP_Android_Command_Server {
pj.broadcastzones
)
if (db.queuepagingDB.Add(qp)){
db.queuepagingDB.Resort()
logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
cb.accept("STOPPAGINGAND;OK@")
return
@@ -306,7 +308,7 @@ class TCP_Android_Command_Server {
if (userlogin != null){
val userdb = db.userDB.List.find { it.username == username }
if (userdb != null){
println("Sending initialization data to $key with username $username")
//println("Sending initialization data to $key with username $username")
val result = StringBuilder()
// kirim Zone
result.append("ZONE")
@@ -349,7 +351,7 @@ class TCP_Android_Command_Server {
.filter { it.isNotBlank() }
.forEach { al ->
val sb = db.soundDB.List
.filter { it.Category.equals(Category.Airline_Name.name, true) }
.filter { it.Category.equals(Category.Airplane_Name.name, true) }
.filter { it.TAG.equals(al, true)}
.distinctBy { it.TAG }
VARAPTOTAL.addAll(sb)
@@ -627,6 +629,7 @@ class TCP_Android_Command_Server {
lang
)
if (db.queuetableDB.Add(qt)){
db.queuetableDB.Resort()
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

View File

@@ -3,7 +3,7 @@ package content
@Suppress("unused")
enum class Category(name: String) {
Airline_Code("Airline_Code"),
Airline_Name("Airline_Name"),
Airplane_Name("Airplane_Name"),
AlphabetNumeric("AlphabetNumeric"),
City("City"),
Phrase("Phrase"),

View File

@@ -0,0 +1,15 @@
package content
class NetworkInformation(val name: String, val displayName: String, val macAddress: String) {
var ipV4addr: MutableList<String> = mutableListOf()
var ipV6addr: MutableList<String> = mutableListOf()
var bytesSent: Long = 0
var bytesRecv: Long = 0
var packetsSent: Long = 0
var packetsRecv: Long = 0
var speed: Long = 0 // in bits per second
var updateStamp : Long = 0 // epoch time in milliseconds, buat hitung speed
var txSpeed: Long = 0
var rxSpeed: Long = 0
}

View File

@@ -1,4 +1,28 @@
package database
@Suppress("unused")
data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String)
data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String){
/**
* Check if all fields are not empty
*/
fun isNotEmpty(): Boolean{
if (description.isNotEmpty()){
if (SoundChannel.isNotEmpty()){
if (id.isNotEmpty()){
if (bp.isNotEmpty()){
return true
}
}
}
}
return false
}
/**
* Return a string representation of the BroadcastZones object.
*/
override fun toString(): String {
return "BroadcastZones(index=$index, description='$description', SoundChannel='$SoundChannel', id='$id', bp='$bp')"
}
}

View File

@@ -1,3 +0,0 @@
package database
data class BroadcastZonesHtml(var index: UInt, var description: String, var SoundChannel: String, var Box: String, var Relay: String)

View File

@@ -1,4 +0,0 @@
package database
@Suppress("unused")
data class IpZones(var index:UInt, var description: String, var ip: String)

View File

@@ -1,4 +1,22 @@
package database
@Suppress("unused")
data class LanguageLink(var index: UInt, var TAG: String, var Language: String)
data class LanguageLink(var index: UInt, var TAG: String, var Language: String){
/**
* Check if the LanguageLink object has non-empty TAG and Language fields.
* @return true if both TAG and Language are not empty, false otherwise.
*/
fun isNotEmpty() : Boolean {
return TAG.isNotEmpty() && Language.isNotEmpty()
}
/**
* Returns a string representation of the LanguageLink object.
* @return A string in the format "LanguageLink(index=..., TAG='...', Language='...')".
*/
override fun toString(): String {
return "LanguageLink(index=$index, TAG='$TAG', Language='$Language')"
}
}

View File

@@ -1,4 +1,9 @@
package database
@Suppress("unused")
data class LogSemiauto(val index: UInt, val date: String, val time: String, val source: String, val description: String)
data class LogSemiauto(val index: UInt, val date: String, val time: String, val source: String, val description: String){
override fun toString(): String {
return "$date $time [$source] $description"
}
}

View File

@@ -104,7 +104,7 @@ class MariaDB(
List.clear()
try {
val statement = connection.createStatement()
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}")
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName} ORDER BY Category, Language, VoiceType, TAG")
while (resultSet?.next() == true) {
val soundbank = Soundbank(
resultSet.getLong("index").toUInt(),
@@ -225,7 +225,7 @@ class MariaDB(
// use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM ${super.dbName} ORDER BY Description ")
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM ${super.dbName} ORDER BY Category, Language, VoiceType, TAG ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE $tempdb_name")
@@ -1250,9 +1250,9 @@ class MariaDB(
// use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language FROM ${super.dbName} ORDER BY `index` ")
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language FROM ${super.dbName} ORDER BY `index` ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language FROM $tempdb_name")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by index" as Any)
// reload the local list
@@ -1411,9 +1411,9 @@ class MariaDB(
// use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, SB_TAGS) SELECT Date_Time, Source, Type, Message, SB_TAGS FROM ${super.dbName} ORDER BY `index` ")
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, BroadcastZones) SELECT Date_Time, Source, Type, Message, BroadcastZones FROM ${super.dbName} ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS) SELECT Date_Time, Source, Type, Message, SB_TAGS FROM $tempdb_name")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, BroadcastZones) SELECT Date_Time, Source, Type, Message, BroadcastZones FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by index" as Any)
// reload the local list
@@ -1436,7 +1436,7 @@ class MariaDB(
val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("QueuePaging")
val headerRow = sheet.createRow(0)
val headers = arrayOf("Index", "Date_Time", "Source", "Type", "Message", "SB_TAGS")
val headers = arrayOf("Index", "Date_Time", "Source", "Type", "Message", "BroadcastZones")
for ((colIndex, header) in headers.withIndex()) {
val cell = headerRow.createCell(colIndex)
cell.setCellValue(header)
@@ -1449,7 +1449,7 @@ class MariaDB(
row.createCell(2).setCellValue(resultSet.getString("Source"))
row.createCell(3).setCellValue(resultSet.getString("Type"))
row.createCell(4).setCellValue(resultSet.getString("Message"))
row.createCell(5).setCellValue(resultSet.getString("SB_TAGS"))
row.createCell(5).setCellValue(resultSet.getString("BroadcastZones"))
}
for (i in headers.indices) {
sheet.autoSizeColumn(i)
@@ -2280,7 +2280,7 @@ class MariaDB(
fun Find_Soundbank_AirplaneName(tag: String) : List<Soundbank>{
return soundDB.List
.filter{ it.Category== Category.Airline_Name.name }
.filter{ it.Category== Category.Airplane_Name.name }
.filter { it.TAG.equals(tag,true)}
}

View File

@@ -8,4 +8,27 @@ data class Messagebank(
var Voice_Type: String,
var Message_Detail: String,
var Message_TAGS: String
)
) {
/**
* Check if all fields are not empty and ANN_ID > 0
*/
fun isNotEmpty(): Boolean{
if (Description.isNotEmpty()){
if (Language.isNotEmpty()){
if (Voice_Type.isNotEmpty()){
if (Message_Detail.isNotEmpty()){
if (Message_TAGS.isNotEmpty()){
return true
}
}
}
}
}
return false
}
override fun toString(): String {
return "Messagebank(index=$index, Description='$Description', Language='$Language', ANN_ID=$ANN_ID, Voice_Type='$Voice_Type', Message_Detail='$Message_Detail', Message_TAGS='$Message_TAGS')"
}
}

View File

@@ -1,4 +1,13 @@
package database
@Suppress("unused")
data class QueueFids(var index: UInt, var ALCODE: String, var FLNUM: String, var ORIGIN: String, var ETAD: String, var FREMARK: String)
data class QueueFids(var index: UInt, var ALCODE: String, var FLNUM: String, var ORIGIN: String, var ETAD: String, var FREMARK: String){
fun isNotEmpty(): Boolean {
return ALCODE.isNotEmpty() && FLNUM.isNotEmpty() && ORIGIN.isNotEmpty() && ETAD.isNotEmpty() && FREMARK.isNotEmpty()
}
override fun toString(): String {
return "QueueFids(index=$index, ALCODE='$ALCODE', FLNUM='$FLNUM', ORIGIN='$ORIGIN', ETAD='$ETAD', FREMARK='$FREMARK')"
}
}

View File

@@ -1,6 +1,10 @@
package database
data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){
fun isNotEmpty(): Boolean {
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && BroadcastZones.isNotEmpty()
}
override fun toString(): String {
return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')"
}

View File

@@ -2,6 +2,12 @@ package database
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){
/**
* Check if all fields are not empty
*/
fun isNotEmpty(): Boolean {
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && SB_TAGS.isNotEmpty() && BroadcastZones.isNotEmpty() && Language.isNotEmpty()
}
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

@@ -10,4 +10,13 @@ data class ScheduleBank(
var Repeat: UByte,
var Enable: Boolean,
var BroadcastZones: String,
var Language: String)
var Language: String) {
override fun toString(): String {
return "ScheduleBank(index=$index, Description='$Description', Day='$Day', Time='$Time', Soundpath='$Soundpath', Repeat=$Repeat, Enable=$Enable, BroadcastZones='$BroadcastZones', Language='$Language')"
}
fun isNotEmpty() : Boolean{
return Description.isNotEmpty() && Day.isNotEmpty() && Time.isNotEmpty() && Soundpath.isNotEmpty() && BroadcastZones.isNotEmpty() && Language.isNotEmpty()
}
}

View File

@@ -1,4 +1,17 @@
package database
@Suppress("unused")
data class SoundChannel(val index: UInt, val channel: String, val ip: String)
data class SoundChannel(val index: UInt, val channel: String, val ip: String) {
override fun toString(): String {
return "SoundChannel(index=$index, channel='$channel', ip='$ip')"
}
/**
* Check if both channel and ip are not empty
* @return true if both channel and ip are not empty, false otherwise
*/
fun isNotEmpty() : Boolean {
return channel.isNotEmpty() && ip.isNotEmpty()
}
}

View File

@@ -1,5 +1,6 @@
package database
data class Soundbank(
var index: UInt,
var Description: String,
@@ -8,4 +9,32 @@ data class Soundbank(
var Language: String,
var VoiceType: String,
var Path: String,
)
){
/**
* Check if all fields are not empty
*/
fun isNotEmpty(): Boolean{
return Description.isNotEmpty() && TAG.isNotEmpty() && Category.isNotEmpty() && Language.isNotEmpty() && VoiceType.isNotEmpty() && Path.isNotEmpty()
}
/**
* Check if Category is valid
*/
fun ValidCategory() : Boolean{
return content.Category.entries.any{ it.name == this.Category}
}
/**
* Check if VoiceType is valid
*/
fun ValidVoiceType() : Boolean{
return content.VoiceType.entries.any{ it.name == this.VoiceType}
}
/**
* Check if Language is valid
*/
fun ValidLanguage() : Boolean{
return content.Language.entries.any{ it.name == this.Language}
}
}

View File

@@ -6,6 +6,10 @@ data class UserDB(var index: UInt, var username: String, var password: String, v
return "UserDB(index=$index, username='$username', location='$location', airline_tags='$airline_tags', city_tags='$city_tags', messagebank_ann_id='$messagebank_ann_id', broadcastzones='$broadcastzones')"
}
fun isNotEmpty() : Boolean {
return username.isNotEmpty() && password.isNotEmpty() && location.isNotEmpty() && airline_tags.isNotEmpty() && city_tags.isNotEmpty() && messagebank_ann_id.isNotEmpty() && broadcastzones.isNotEmpty()
}
/**
* Compares this UserDB object with another UserDB object for equality.
* Two UserDB objects are considered equal if all their properties are equal, except for the index property.

266
src/toa/Vx3K.kt Normal file
View File

@@ -0,0 +1,266 @@
package toa
import codes.Somecodes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.tinylog.Logger
import java.net.Inet4Address
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.function.BiConsumer
/**
* VX3K Protocol
* @param ipaddress IP address of the VX3K device, default to 192.168.14.1
* @param port Port number of the VX3K device, from 50050-50053 default to 50053
*/
class Vx3K(val ipaddress : String = "192.168.14.1", val port : Int = 50053) {
private val remotesocket : InetSocketAddress
init{
if (port !in 50050..50053){
throw IllegalArgumentException("Port number must be between 50050 and 50053")
}
try{
val inet = Inet4Address.getByName(ipaddress)
remotesocket = InetSocketAddress(inet, port)
} catch (_ : Exception){
throw IllegalArgumentException("Invalid IP address: $ipaddress")
}
}
/**
* Connect to the VX3K device
* @param timeout Connection timeout in milliseconds, default to 30000 ms
*/
fun Connect(timeout: Int = 30000){
try{
val socket = Socket()
// read timeout 5 seconds
socket.soTimeout = 5000
socket.connect(remotesocket, timeout)
} catch (e : Exception){
Logger.error { "Failed to connect with ${remotesocket.hostName}:${remotesocket.port}, Message: ${e.message}" }
}
}
/**
* Virtual Contact Input (Commmand 0x1001)
* @param ID : Device ID for VX3K, range 0 - 31
* @param CIN : Contact Input Number, range 0 - 15 for normal terminal, 16 for emergency contact input1, 17 for emergency contact input2
* @param isON : true for ON, false for OFF
* @param cb : Callback function with parameters (success: Boolean, message: String)
*/
fun VirtualCIN(ID: Short, CIN: Short, isON: Boolean, cb : BiConsumer<Boolean, String>){
val commandID = 0x1001.toShort()
if (ID !in 0..31){
cb.accept(false, "ID must be between 0 and 31")
return
}
if (CIN !in 0..17){
cb.accept(false, "CIN must be between 0 and 17")
return
}
val payload = ByteBuffer.allocate(6).order(ByteOrder.BIG_ENDIAN)
payload.putShort(ID)
payload.putShort(CIN)
payload.putShort(if (isON) 1 else 0)
val command = Make_Request_Command(commandID, payload.array())
Send_Receive(command,8){
success, reply ->
if (success){
val bb = ByteBuffer.wrap(reply).order(ByteOrder.BIG_ENDIAN)
val resp_commandID = bb.short
val resp_code = bb.short
if (resp_commandID==commandID){
if (resp_code.toInt() == 0){
cb.accept(true, "Virtual CIN command sent successfully to ${remotesocket.hostName}:${remotesocket.port}")
} else {
cb.accept(false, "Virtual CIN command failed with response code $resp_code from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Invalid response command ID $resp_commandID from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Failed to send Virtual CIN command to ${remotesocket.hostName}:${remotesocket.port}")
}
}
}
/**
* Open / Close Audio Input in Broadcast Pattern (Command 0x1003 for old firmware, 0x1101 for new firmware)
* @param NewFirmware Set to true if using new firmware version
* @param ID Device ID for VX3K, range 0 - 39 for new firmware, 0 - 31 for old firmware
* @param Channel Audio Input Channel, range 0 - 3
* @param BroadcastZones Vx3K_BroadcastZone object with selected broadcast zones
* @param cb Callback function with parameters (success: Boolean, message: String)
*/
fun AudioInput_BroadcastPattern(NewFirmware : Boolean, ID: Short, Channel: Short, BroadcastZones: Vx3K_BroadcastZone, cb: BiConsumer<Boolean, String>){
val CommandID = if (NewFirmware) 0x1101.toShort() else 0x1003.toShort()
if (NewFirmware){
if (ID !in 0..39){
cb.accept(false, "ID must be between 0 and 39")
return
}
} else {
if (ID !in 0..31){
cb.accept(false, "ID must be between 0 and 31")
return
}
}
if (Channel !in 0..3){
cb.accept(false, "Channel must be between 0 and 3")
return
}
val payload = ByteBuffer.allocate(if (NewFirmware) 86 else 70).order(ByteOrder.BIG_ENDIAN)
// Audio Input = 1
payload.putShort(1)
// VX3K ID
payload.putShort(ID)
// Audio Input Channel
payload.putShort(Channel)
// Broadcast Pattern Zones
payload.put(BroadcastZones.payload)
val command = Make_Request_Command(CommandID, payload.array())
Send_Receive(command,8){
success, reply ->
if (success){
val bb = ByteBuffer.wrap(reply).order(ByteOrder.BIG_ENDIAN)
val resp_commandID = bb.short
val resp_code = bb.short
if (resp_commandID==CommandID){
if (resp_code.toInt() == 0){
cb.accept(true, "Open Audio Input Broadcast command sent successfully to ${remotesocket.hostName}:${remotesocket.port}")
} else {
cb.accept(false, "Open Audio Input Broadcast command failed with response code $resp_code from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Invalid response command ID $resp_commandID from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Failed to send Open Audio Input Broadcast command to ${remotesocket.hostName}:${remotesocket.port}")
}
}
}
/**
* Open / Close Network Broadcast Pattern (Command 0x1102 for old firmware, 0x1104 for new firmware)
* @param NewFirmware Set to true if using new firmware version
* @param multicastIP Multicast IP address in string format, from 224.0.0.0 ~ 239.255.255.255, default to 224.0.0.1
* @param port UDP Port number, port 5000-5255 is invalid, default to 5300
* @param priority Broadcast Priority value, range 1 - 1024, default to 512 (to be checked later)
* @param isBGM true for BGM, false for Paging
* @param SSRC SSRC value, range 1 - 65535, default to 1 (to be checked later)
* @param payloadType RTP Payload Type, range 0 - 127, default to 0 (to be checked later)
* @param payloadSize RTP Payload Size, range 0 - 1500, default to 1000 (to be checked later)
* @param BroadcastZones Vx3K_BroadcastZone object with selected broadcast zones
* @param cb Callback function with parameters (success: Boolean, message: String)
*/
fun Network_BroadcastPattern(NewFirmware: Boolean,multicastIP: String = "224.0.0.1", port: Int = 5300, priority: Int = 512, isBGM: Boolean, SSRC: UShort = 1u, payloadType: Short = 0, payloadSize: Short = 1000, BroadcastZones: Vx3K_BroadcastZone, cb:BiConsumer<Boolean, String>){
val CommandID = if (NewFirmware) 0x1104.toShort() else 0x1102.toShort()
if (Somecodes.ValidIPV4(multicastIP)){
val inet = Inet4Address.getByName(multicastIP)
if (inet.isMulticastAddress){
if (port in 5256..65535){
if (priority in 1..1024){
if (SSRC in 1u..65535u){
if (payloadType in 0..127){
if (payloadSize in 0..1500){
val jitterbuffer = 1000 //TODO 32 signed integer, to be checked later
val payload = ByteBuffer.allocate(if (NewFirmware) 100 else 84)
payload.put(inet.address)
payload.putShort(port.toShort())
payload.putShort(priority.toShort())
payload.putShort(if (isBGM) 1 else 0)
payload.putShort(SSRC.toShort())
payload.putShort(payloadType)
payload.putShort(payloadSize)
payload.putInt(jitterbuffer)
payload.put(BroadcastZones.payload)
val command = Make_Request_Command(CommandID, payload.array())
Send_Receive(command,10){
success, reply ->
if (success){
val bb = ByteBuffer.wrap(reply).order(ByteOrder.BIG_ENDIAN)
val resp_commandID = bb.short
val resp_code = bb.short
// buang 2 short
bb.short
bb.short
val sound_source_number = bb.short
if (resp_commandID==CommandID){
if (resp_code.toInt() == 0){
cb.accept(true, "Open Network Broadcast command sent successfully to ${remotesocket.hostName}:${remotesocket.port}, Sound Source Number: $sound_source_number")
} else {
cb.accept(false, "Open Network Broadcast command failed with response code $resp_code from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Invalid response command ID $resp_commandID from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Failed to send Open Network Broadcast command to ${remotesocket.hostName}:${remotesocket.port}")
}
}
} else cb.accept(false, "Payload Size must be between 0 and 1500")
} else cb.accept(false, "Payload Type must be between 0 and 127")
} else cb.accept(false, "SSRC must be between 1 and 65535")
} else cb.accept(false, "Priority must be between 1 and 1024")
} else cb.accept(false, "Port must be greater than 5255 and less than 65536")
} else cb.accept(false, "multicastIP must between 224.0.0.0 ~ 239.255.255.255")
} else cb.accept(false, "Multicast IP address invalid")
}
/**
* Send command and receive reply from VX3K device
* @param command Command byte array to send
* @param expectedlength Expected length of the reply byte array
* @param cb Callback function with parameters (success: Boolean, reply: ByteArray)
*/
private fun Send_Receive(command: ByteArray, expectedlength: Int, cb : BiConsumer<Boolean, ByteArray>){
CoroutineScope(Dispatchers.IO).launch {
try{
val tcp = Socket()
tcp.soTimeout = 5000
tcp.connect(remotesocket, 30000)
val outstream = tcp.getOutputStream()
val instream = tcp.getInputStream()
outstream.write(command)
outstream.flush()
val reply = ByteArray(expectedlength)
instream.read(reply)
outstream.close()
instream.close()
tcp.close()
cb.accept(true, reply)
} catch (_: Exception){
cb.accept(false, ByteArray(0))
}
}
}
/**
* Wrap payload into VX3K Request Command format
* @param commandID Command ID
* @param Payload Payload data
* @return ByteArray of the complete command
*/
private fun Make_Request_Command(commandID: Short, Payload: ByteArray) : ByteArray {
val result = ByteBuffer.allocate(8+Payload.size).order(ByteOrder.BIG_ENDIAN)
// command ID (2 bytes)
result.putShort(commandID)
// Response code (2 bytes), set 0 for Request
result.putShort(0)
// command length = command header (8 bytes) + payload size
result.putShort((Payload.size+8).toShort())
// flag and reserver = 0 (2 bytes)
result.putShort(0)
result.put(Payload)
// read to byte array
return result.array()
}
}

View File

@@ -0,0 +1,67 @@
package toa
import kotlin.experimental.and
import kotlin.experimental.or
/**
* VX3K Broadcast Zone Configuration
* Old Firmware: Broadcast Zone 1 - 512
* New Firmware: Broadcast Zone 1 - 640
* @param NewFirmware Set to true if using new firmware version
*/
@Suppress("unused")
class Vx3K_BroadcastZone(val NewFirmware: Boolean = false) {
val payload: ByteArray = if (NewFirmware) ByteArray(80) else ByteArray(64)
/**
* Set a zone as active
* @param zonenumber Zone number to set (1 to 512 for old firmware, 1 to 640 for new firmware)
*/
fun SetZone(zonenumber: Int){
if (zonenumber<1) throw Exception("Minimum zone number is 1")
if (NewFirmware){
if (zonenumber>640) throw Exception("Maximum zone number is 640 for new firmware")
} else {
if (zonenumber>512) throw Exception("Maximum zone number is 512 for old firmware")
}
val byteIndex = (zonenumber - 1) / 8
val bitIndex = (zonenumber - 1) % 8
payload[byteIndex] = payload[byteIndex] or (1 shl bitIndex).toByte()
}
/**
* Clear a zone (set as inactive)
* @param zonenumber Zone number to clear (1 to 512 for old firmware, 1 to 640 for new firmware)
*/
fun ClearZone(zonenumber: Int){
if (zonenumber<1) throw Exception("Minimum zone number is 1")
if (NewFirmware){
if (zonenumber>640) throw Exception("Maximum zone number is 640 for new firmware")
} else {
if (zonenumber>512) throw Exception("Maximum zone number is 512 for old firmware")
}
val byteIndex = (zonenumber - 1) / 8
val bitIndex = (zonenumber - 1) % 8
payload[byteIndex] = payload[byteIndex] and ((1 shl bitIndex).inv().toByte())
}
/**
* Set all zones as active
*/
fun SetAllZones(){
for (i in payload.indices){
payload[i] = 0xFF.toByte()
}
}
/**
* Clear all zones (set all as inactive)
*/
fun ClearAllZones(){
for (i in payload.indices){
payload[i] = 0
}
}
}

View File

@@ -0,0 +1,9 @@
package web
class StreamerOutputData(val index: UInt, val channel: String, val ipaddress: String, val vu: Int, val bufferRemain: Int, var isPlaying: Boolean) {
companion object{
fun fromBarixConnection(bc: barix.BarixConnection): StreamerOutputData {
return StreamerOutputData(bc.index, bc.channel, bc.ipaddress, bc.vu, bc.bufferRemain, bc.isPlaying())
}
}
}

File diff suppressed because it is too large Load Diff