commit 10/10/2025

Broadcast Zones and Sound Channels
This commit is contained in:
2025-10-10 15:01:09 +07:00
parent fdc7556dd7
commit 41d6dd7f47
6 changed files with 96 additions and 79 deletions

View File

@@ -19,6 +19,12 @@ window.BroadcastZoneList ??= [];
*/
window.selectedBroadcastZoneRow = null;
/**
* List of sound channels available
* @type {String[]}
*/
window.SoundChannelList = []
/**
* Fill broadcast zone table body with values
* @param {BroadcastZone[]} vv values to fill
@@ -36,7 +42,7 @@ function fill_broadcastzonetablebody(vv) {
</tr>`;
$('#broadcastzonetablebody').append(row);
let $addedrow = $('#broadcastzonetablebody tr:last');
$addedrow.click(function () {
$addedrow.off('click').on('click', function () {
if (window.selectedBroadcastZoneRow) {
window.selectedBroadcastZoneRow.find('td').css('background-color', '');
if (window.selectedBroadcastZoneRow.is($(this))) {
@@ -72,6 +78,18 @@ function reloadBroadcastZones(APIURL = "BroadcastZones/") {
});
}
function fetchSoundChannels(APIURL = "SoundChannel/") {
window.SoundChannelList = [];
fetchAPI(APIURL + "SoundChannelDescriptions", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("fetchSoundChannels : ", okdata)
window.SoundChannelList.push(...okdata);
} else console.log("fetchSoundChannels: okdata is not array");
}, (errdata) => {
alert("Error loading sound channels : " + errdata.message);
});
}
$(document).ready(function () {
console.log("broadcastzones.js loaded successfully");
window.selectedBroadcastZoneRow = null;
@@ -95,7 +113,7 @@ $(document).ready(function () {
let $findzone = $('#findzone');
$findzone.on('input', function () {
$findzone.off('input').on('input', function () {
let searchTerm = $findzone.val().trim().toLowerCase();
if (searchTerm.length > 0) {
window.selectedBroadcastZoneRow = null;
@@ -125,24 +143,26 @@ $(document).ready(function () {
$broadcastzonedescription.val('');
// fill broadcastzonesoundchannel from SoundChannelList
$broadcastzonesoundchannel.empty();
if (Array.isArray(SoundChannelList) && SoundChannelList.length > 0) {
console.log("SoundChannelList:", window.SoundChannelList);
if (Array.isArray(window.SoundChannelList) && window.SoundChannelList.length > 0) {
// SoundChannelList ada isinya
SoundChannelList.forEach(ch => {
if (ch.channel && ch.channel.length > 0){
// hanya yang punya channel saja
$broadcastzonesoundchannel.append($('<option>').val(ch.channel).text(ch.channel));
window.SoundChannelList.forEach(ch => {
if (ch && ch.length>0){
$broadcastzonesoundchannel.append($('<option>').val(ch).text(ch));
}
});
}
$broadcastzonebox.val('');
$broadcastzonebox.val('1').prop('disabled', true);
for (let i = 1; i <= 32; i++) {
cbRelay(i).prop('checked', false);
}
}
fetchSoundChannels();
reloadBroadcastZones(APIURL_BroadcastZone);
$btnClear.click(() => {
$btnClear.off('click').on('click', () => {
DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone);
alert("Success clear broadcast zones: " + okdata.message);
@@ -151,7 +171,7 @@ $(document).ready(function () {
});
});
$btnAdd.click(() => {
$btnAdd.off('click').on('click', () => {
$broadcastzonemodal.modal('show');
clearBroadcastZoneModal();
@@ -203,7 +223,7 @@ $(document).ready(function () {
});
});
$btnRemove.click(() => {
$btnRemove.off('click').on('click', () => {
if (window.selectedBroadcastZoneRow) {
let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */
@@ -225,7 +245,7 @@ $(document).ready(function () {
}
});
$btnEdit.click(() => {
$btnEdit.off('click').on('click', () => {
if (window.selectedBroadcastZoneRow) {
let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */
@@ -242,12 +262,12 @@ $(document).ready(function () {
$broadcastzoneindex.val(bz.index);
$broadcastzonedescription.val(bz.description);
$broadcastzonesoundchannel.val(bz.SoundChannel);
$broadcastzonebox.val(bz.id);
if (bz.bp) {
bz.bp.split(';').forEach(relayId => {
let id = parseInt(relayId, 10);
cbRelay(id).prop('checked', true);
$broadcastzonebox.val(bz.Box);
if (bz.Relay && bz.Relay.length > 0) {
bz.Relay.split(';').map(Number).filter(n => !isNaN(n) && n>=1 && n<=8).forEach(relayId => {
cbRelay(relayId).prop('checked', true);
});
}
$broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () {
let description = $broadcastzonedescription.val().trim();
@@ -297,11 +317,11 @@ $(document).ready(function () {
}
});
$btnExport.click(() => {
$btnExport.off('click').on('click', () => {
DoExport(APIURL_BroadcastZone, "broadcastzones.xlsx", {});
});
$btnImport.click(() => {
$btnImport.off('click').on('click', () => {
DoImport(APIURL_BroadcastZone, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone);
alert("Success import broadcast zones: " + okdata.message);

View File

@@ -18,10 +18,9 @@ window.selectedSoundChannel = null;
* @param {SoundChannel[]} vv Sound channel data to populate the table.
*/
function fill_soundchanneltablebody(vv) {
let $tbody = $('#soundchanneltablebody');
let $btnEditSoundChannel = $('#btnEditSoundChannel');
let $tablesizeSoundChannel = $('#tablesizeSoundChannel');
$tbody.empty();
$('#soundchanneltablebody').empty();
$tablesizeSoundChannel.text('Table Length : N/A');
if (!Array.isArray(vv) || vv.length === 0) return;
@@ -32,9 +31,9 @@ function fill_soundchanneltablebody(vv) {
<td>${item.channel}</td>
<td>${item.ip}</td>
</tr>`;
$tbody.append(row);
$('#soundchanneltablebody').append(row);
let $addedrow = $('#soundchanneltablebody tr:last');
$addedrow.click(function () {
$addedrow.off('click').on('click', function () {
if (selectedSoundChannel) {
selectedSoundChannel.find('td').css('background-color', '');
if (selectedSoundChannel.is($(this))) {
@@ -83,7 +82,7 @@ $(document).ready(function () {
$btnEditSoundChannel.prop('disabled', true);
let API_SoundChannel = "SoundChannel/";
$findsoundchannel.on('input', function () {
$findsoundchannel.off('input').on('input', function () {
let searchTerm = $(this).val().toLowerCase();
if (searchTerm.length==0){
window.selectedSoundChannel = null;
@@ -109,7 +108,7 @@ $(document).ready(function () {
}
reloadSoundChannel(API_SoundChannel);
$btnReinitializeSoundChannel.click(() => {
$btnReinitializeSoundChannel.off('click').on('click', () => {
DoClear(API_SoundChannel, "SoundChannels", (okdata) => {
reloadSoundChannel(API_SoundChannel);
alert("Success clear sound channels: " + okdata.message);
@@ -117,7 +116,7 @@ $(document).ready(function () {
alert("Error clear sound channels: " + errdata.message);
});
});
$btnEditSoundChannel.click(() => {
$btnEditSoundChannel.off('click').on('click', () => {
if (selectedSoundChannel) {
let cells = selectedSoundChannel.find('td');
/** @type {SoundChannel} */
@@ -171,11 +170,11 @@ $(document).ready(function () {
}
}
});
$btnExportSoundChannel.click(() => {
$btnExportSoundChannel.off('click').on('click', () => {
DoExport(API_SoundChannel, "soundchannels.xlsx", {});
});
$btnImportSoundChannel.click(() => {
$btnImportSoundChannel.off('click').on('click', () => {
DoImport(API_SoundChannel, (okdata) => {
reloadSoundChannel(API_SoundChannel);
alert("Success import sound channels: " + okdata.message);

View File

@@ -28,7 +28,7 @@
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index"></div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index" readonly=""></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
@@ -80,7 +80,7 @@
<div class="form-check"><input class="form-check-input" type="checkbox" id="R08"><label class="form-check-label" for="formCheck-8">08</label></div>
</div>
</div>
<div class="col-3">
<div class="col-3 invisible">
<div class="row">
<div class="form-check"><input class="form-check-input" type="checkbox" id="R09"><label class="form-check-label" for="formCheck-25">09</label></div>
</div>
@@ -106,7 +106,7 @@
<div class="form-check"><input class="form-check-input" type="checkbox" id="R16"><label class="form-check-label" for="formCheck-32">16</label></div>
</div>
</div>
<div class="col-3">
<div class="col-3 invisible">
<div class="row">
<div class="form-check"><input class="form-check-input" type="checkbox" id="R17"><label class="form-check-label" for="formCheck-17">17</label></div>
</div>
@@ -132,7 +132,7 @@
<div class="form-check"><input class="form-check-input" type="checkbox" id="R24"><label class="form-check-label" for="formCheck-24">24</label></div>
</div>
</div>
<div class="col-3">
<div class="col-3 invisible">
<div class="row">
<div class="form-check"><input class="form-check-input" type="checkbox" id="R25"><label class="form-check-label" for="formCheck-9">25</label></div>
</div>
@@ -269,13 +269,13 @@
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Description</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchanneldescription" placeholder="Description"></div>
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchanneldescription" placeholder="Description" readonly=""></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">IP Address</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchannelip" placeholder="IP Address"></div>
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchannelip" placeholder="IP Address" required="" inputmode="numeric" pattern="^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$"></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundchannelclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundchannelsave" type="button">Save</button></div>

View File

@@ -176,7 +176,7 @@ fun main() {
StreamerOutputs[cmd.ipaddress] = _bc
Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
} else Logger.warn { "soundChannelDB doesn't have soundchannel with IP ${cmd.ipaddress}" }
}
} else {
// sudah ada, update data

View File

@@ -995,7 +995,7 @@ class MariaDB(
override fun Add(data: BroadcastZones): Boolean {
try {
val statement =
connection.prepareStatement("INSERT INTO ${super.dbName} (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)")
connection.prepareStatement("INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) VALUES (?, ?, ?, ?)")
statement?.setString(1, data.description)
statement?.setString(2, data.SoundChannel)
statement?.setString(3, data.id)
@@ -1017,7 +1017,7 @@ class MariaDB(
try {
connection.autoCommit = false
val sql =
"INSERT INTO ${super.dbName} (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)"
"INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) VALUES (?, ?, ?, ?)"
val statement = connection.prepareStatement(sql)
for (bz in data) {
statement.setString(1, bz.description)
@@ -1040,7 +1040,7 @@ class MariaDB(
override fun UpdateByIndex(index: Int, data: BroadcastZones): Boolean {
try {
val statement =
connection.prepareStatement("UPDATE ${super.dbName} SET description = ?, SoundChannel = ?, Box = ?, Relay = ? WHERE `index` = ?")
connection.prepareStatement("UPDATE ${super.dbName} SET description = ?, SoundChannel = ?, id = ?, bp = ? WHERE `index` = ?")
statement?.setString(1, data.description)
statement?.setString(2, data.SoundChannel)
statement?.setString(3, data.id)
@@ -1066,9 +1066,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 (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM ${super.dbName} ORDER BY description ")
statement?.executeUpdate("INSERT INTO $tempdb_name (description, SoundChannel, id, bp) SELECT description, SoundChannel, id, bp FROM ${super.dbName} ORDER BY description ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM $tempdb_name")
statement?.executeUpdate("INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) SELECT description, SoundChannel, id, bp FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by description" as Any)
// reload the local list
@@ -1085,7 +1085,7 @@ class MariaDB(
val sheet = workbook.getSheet("BroadcastZones")
?: throw Exception("No sheet named 'BroadcastZones' found")
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
val headers = arrayOf("Index", "description", "SoundChannel", "Box", "Relay")
val headers = arrayOf("Index", "description", "SoundChannel", "id", "bp")
for ((colIndex, header) in headers.withIndex()) {
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
@@ -1098,9 +1098,9 @@ class MariaDB(
val row = sheet.getRow(rowIndex) ?: continue
val description = row.getCell(1)?.stringCellValue ?: continue
val soundChannel = row.getCell(2)?.stringCellValue ?: continue
val box = row.getCell(3)?.stringCellValue ?: continue
val relay = row.getCell(4)?.stringCellValue ?: continue
val broadcastZone = BroadcastZones(0u, description, soundChannel, box, relay)
val id = row.getCell(3)?.stringCellValue ?: continue
val bp = row.getCell(4)?.stringCellValue ?: continue
val broadcastZone = BroadcastZones(0u, description, soundChannel, id, bp)
_broadcastZonesList.add(broadcastZone)
}
return AddAll(_broadcastZonesList)
@@ -1117,7 +1117,7 @@ class MariaDB(
val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("BroadcastZones")
val headerRow = sheet.createRow(0)
val headers = arrayOf("Index", "description", "SoundChannel", "Box", "Relay")
val headers = arrayOf("Index", "description", "SoundChannel", "id", "bp")
for ((colIndex, header) in headers.withIndex()) {
val cell = headerRow.createCell(colIndex)
cell.setCellValue(header)
@@ -1128,8 +1128,8 @@ class MariaDB(
row.createCell(0).setCellValue(resultSet.getString("index"))
row.createCell(1).setCellValue(resultSet.getString("description"))
row.createCell(2).setCellValue(resultSet.getString("SoundChannel"))
row.createCell(3).setCellValue(resultSet.getString("Box"))
row.createCell(4).setCellValue(resultSet.getString("Relay"))
row.createCell(3).setCellValue(resultSet.getString("id"))
row.createCell(4).setCellValue(resultSet.getString("bp"))
}
for (i in headers.indices) {
sheet.autoSizeColumn(i)
@@ -2361,6 +2361,17 @@ class MariaDB(
.sorted()
}
/**
* Get all distinct sound channel from soundchannelDB
* @return a list of distinct sound channel sorted alphabetically
*/
fun Get_SoundChannel_List(): List<String> {
return soundchannelDB.List
.distinctBy { it.channel }
.map { it.channel }
.sorted()
}
/**
* Check if a username already exists in the userDB (case-insensitive)
*/

View File

@@ -1114,17 +1114,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
}
path("BroadcastZones") {
get("List") {
// TODO : temporary, convert BroadcastZones to BroadcastZonesHtml, karena harus revisi javascript di Bootstrap Studio
// val newlist: ArrayList<BroadcastZonesHtml> = db.broadcastDB.List.map { xx ->
// BroadcastZonesHtml(
// xx.index,
// xx.description,
// xx.SoundChannel,
// xx.id,
// xx.bp
// )
// } as ArrayList<BroadcastZonesHtml>
it.result(MariaDB.ArrayListtoString(db.broadcastDB.List))
}
get("BroadcastZoneDescriptions") { ctx ->
@@ -1276,6 +1265,9 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
get("List") {
it.result(MariaDB.ArrayListtoString(db.soundchannelDB.List))
}
get("SoundChannelDescriptions") {
it.result(objectmapper.writeValueAsString(db.Get_SoundChannel_List()))
}
delete("List") {
// truncate sound channel table
if (db.soundchannelDB.Clear()) {
@@ -1318,29 +1310,24 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} else {
// cek apakah ada soundchannel lain yang pakai ip dan channel yang sama
if (db.soundchannelDB.List.any { xx ->
xx.index != index && _ip.equals(
xx.ip
) && _channel.equals(xx.channel, true)
}) {
println("Another sound channel already uses IP $_ip and channel $_channel")
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Another sound channel already uses IP $_ip and channel $_channel")))
return@patch
val othersc = db.soundchannelDB.List.filter { sc -> sc.ip == _ip }.filter { sc -> sc.index != index }
if (othersc.isNotEmpty()){
it.status(400).result(objectmapper.writeValueAsString(resultMessage("This IP address is already used by another sound channel")))
} else {
// ada sesuatu yang ganti
val newsc = SoundChannel(0u, _channel, _ip)
if (db.soundchannelDB.UpdateByIndex(index.toInt(), newsc)) {
println("Updated sound channel with index $index")
db.soundchannelDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
println("Failed to update sound channel with index $index")
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update sound channel with index $index")))
}
}
// ada sesuatu yang ganti
val newsc = SoundChannel(0u, _channel, _ip)
if (db.soundchannelDB.UpdateByIndex(index.toInt(), newsc)) {
println("Updated sound channel with index $index")
db.soundchannelDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
println("Failed to update sound channel with index $index")
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update sound channel with index $index")))
return@patch
}
}
} else {