commit 23/09/2025

This commit is contained in:
2025-09-23 13:56:16 +07:00
parent 7c67fe90ee
commit 85ccf05634
8 changed files with 237 additions and 56 deletions

View File

@@ -123,7 +123,17 @@ $(document).ready(function () {
function clearBroadcastZoneModal() {
$broadcastzoneindex.prop('disabled', true).val('');
$broadcastzonedescription.val('');
$broadcastzonesoundchannel.val('');
// fill broadcastzonesoundchannel from SoundChannelList
$broadcastzonesoundchannel.empty();
if (Array.isArray(SoundChannelList) && 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));
}
});
}
$broadcastzonebox.val('');
for (let i = 1; i <= 32; i++) {
cbRelay(i).prop('checked', false);
@@ -179,7 +189,7 @@ $(document).ready(function () {
Box: box,
Relay: relay
};
fetchAPI(APIURL_BroadcastZone + "Add", "POST", bz, null, (okdata) => {
fetchAPI(APIURL_BroadcastZone + "Add", "POST", {}, bz, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone);
alert("Success add new broadcast zone: " + okdata.message);
}, (errdata) => {
@@ -272,7 +282,7 @@ $(document).ready(function () {
Box: box,
Relay: relay
};
fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", bzUpdate, null, (okdata) => {
fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", {}, bzUpdate, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone);
alert("Success edit broadcast zone: " + okdata.message);
}, (errdata) => {

View File

@@ -69,9 +69,16 @@ function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) {
}
}
fetch(url, options)
.then(response => {
.then(async(response) => {
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
let msg ;
try{
let _xxx = await response.json();
msg = _xxx.message || response.statusText;
} catch {
msg = await response.statusText;
}
throw new Error(msg);
}
return response.json();
})

View File

@@ -150,7 +150,9 @@ $(document).ready(function () {
return;
}
fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", newsc, null, (okdata) => {
fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", {}, newsc, (okdata) => {
reloadSoundChannel(API_SoundChannel);
alert("Success edit sound channel: " + okdata.message);
}, (errdata) => {

View File

@@ -282,8 +282,8 @@
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/broadcastzones.js"></script>
<script src="assets/js/soundchannel.js"></script>
<script src="assets/js/broadcastzones.js"></script>
</body>
</html>

View File

@@ -19,11 +19,12 @@ import java.time.LocalTime
fun main() {
val version = "0.0.1 (23/09/2025)"
if (Platform.isWindows()) {
// supaya OSHI bisa mendapatkan CPU usage di Windows seperti di Task Manager
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, true)
}
Logger.info("Application started" as Any)
Logger.info{"Starting AAS New Generation version $version"}
val audioPlayer = AudioPlayer(44100) // 44100 Hz sampling rate
audioPlayer.InitAudio(1)
val content = ContentCache()

View File

@@ -181,6 +181,28 @@ class Somecodes {
}
}
/**
* Check if a string is a valid IPv4 address.
* @param value The string to check.
* @return True if the string is a valid IPv4 address, false otherwise.
*/
fun ValidIPV4(value: String): Boolean{
return try{
if (ValidString(value)){
val parts = value.split(".")
if (parts.size != 4) return false
for (part in parts){
val num = part.toInt()
if (num !in 0..255) return false
}
true
} else throw Exception()
} catch (_: Exception){
false
}
}
/**
* Check if a string is a valid date in the format "dd-MM-yyyy".
* This format is used for log HTML files.

View File

@@ -1737,7 +1737,7 @@ class MariaDB(
fun Add_BroadcastZones(broadcastZones: BroadcastZones): Boolean {
try {
val statement =
connection?.prepareStatement("INSERT INTO broadcast_zones (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)")
connection?.prepareStatement("INSERT INTO broadcastzones (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)")
statement?.setString(1, broadcastZones.description)
statement?.setString(2, broadcastZones.SoundChannel)
statement?.setString(3, broadcastZones.Box)
@@ -1766,7 +1766,7 @@ class MariaDB(
if (connection != null) {
connection!!.autoCommit = false
val sql =
"INSERT INTO broadcast_zones (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)"
"INSERT INTO broadcastzones (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)"
val statement = connection!!.prepareStatement(sql)
for (bz in broadcastZonesList) {
statement.setString(1, bz.description)
@@ -1797,7 +1797,7 @@ class MariaDB(
fun Update_BroadcastZones_by_index(index: UInt, broadcastZones: BroadcastZones): Boolean {
try {
val statement =
connection?.prepareStatement("UPDATE broadcast_zones SET description = ?, SoundChannel = ?, Box = ?, Relay = ? WHERE `index` = ?")
connection?.prepareStatement("UPDATE broadcastzones SET description = ?, SoundChannel = ?, Box = ?, Relay = ? WHERE `index` = ?")
statement?.setString(1, broadcastZones.description)
statement?.setString(2, broadcastZones.SoundChannel)
statement?.setString(3, broadcastZones.Box)
@@ -1823,7 +1823,7 @@ class MariaDB(
*/
fun Delete_BroadcastZones_by_index(index: UInt): Boolean {
try {
val statement = connection?.prepareStatement("DELETE FROM broadcast_zones WHERE `index` = ?")
val statement = connection?.prepareStatement("DELETE FROM broadcastzones WHERE `index` = ?")
statement?.setLong(1, index.toLong())
val rowsAffected = statement?.executeUpdate()
if (rowsAffected != null && rowsAffected > 0) {
@@ -1846,11 +1846,11 @@ class MariaDB(
try {
val statement = connection?.createStatement()
// use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_broadcast_zones LIKE broadcast_zones")
statement?.executeUpdate("INSERT INTO temp_broadcast_zones (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM broadcast_zones ORDER BY description ASC")
statement?.executeUpdate("TRUNCATE TABLE broadcast_zones")
statement?.executeUpdate("INSERT INTO broadcast_zones (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM temp_broadcast_zones")
statement?.executeUpdate("DROP TABLE temp_broadcast_zones")
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_broadcastzones LIKE broadcastzones")
statement?.executeUpdate("INSERT INTO temp_broadcastzones (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM broadcastzones ORDER BY description ASC")
statement?.executeUpdate("TRUNCATE TABLE broadcastzones")
statement?.executeUpdate("INSERT INTO broadcastzones (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM temp_broadcastzones")
statement?.executeUpdate("DROP TABLE temp_broadcastzones")
Logger.info("broadcast_zones table resorted by description" as Any)
// reload the local list
GetBroadcastZones()
@@ -1868,7 +1868,7 @@ class MariaDB(
try {
val statement = connection?.createStatement()
// use TRUNCATE to reset auto increment index
statement?.executeUpdate("TRUNCATE TABLE broadcast_zones")
statement?.executeUpdate("TRUNCATE TABLE broadcastzones")
Logger.info("Broadcast zones table cleared" as Any)
return true
} catch (e: Exception) {
@@ -1884,7 +1884,7 @@ class MariaDB(
fun Export_BroadcastZones_XLSX(): XSSFWorkbook? {
try {
val statement = connection?.createStatement()
val resultSet = statement?.executeQuery("SELECT * FROM broadcast_zones")
val resultSet = statement?.executeQuery("SELECT * FROM broadcastzones")
val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("BroadcastZones")
val headerRow = sheet.createRow(0)

View File

@@ -4,6 +4,7 @@ import codes.Somecodes
import codes.Somecodes.Companion.ListAudioFiles
import codes.Somecodes.Companion.ValiDateForLogHtml
import codes.Somecodes.Companion.ValidFile
import codes.Somecodes.Companion.ValidIPV4
import codes.Somecodes.Companion.ValidScheduleDay
import codes.Somecodes.Companion.ValidScheduleTime
import codes.Somecodes.Companion.ValidString
@@ -13,9 +14,11 @@ import content.Category
import content.Language
import content.ScheduleDay
import content.VoiceType
import database.BroadcastZones
import database.LanguageLink
import database.MariaDB
import database.Messagebank
import database.SoundChannel
import database.Soundbank
import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.before
@@ -265,11 +268,11 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
if (json.isEmpty) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _description = json.get("Description").asText()
val _tag = json.get("TAG").asText()
val _category = json.get("Category").asText()
val _language = json.get("Language").asText()
val _path = json.get("Path").asText()
val _description = json.get("Description").asText("")
val _tag = json.get("TAG").asText("")
val _category = json.get("Category").asText("")
val _language = json.get("Language").asText("")
val _path = json.get("Path").asText("")
var changed = false
if (ValidString(_description) && _description != sb.Description) {
sb.Description = _description
@@ -355,12 +358,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
}
post("Add"){
val json : JsonNode = objectmapper.readTree(it.body())
val description = json.get("Description")?.asText() ?: ""
val language = json.get("Language")?.asText() ?: ""
val description = json.get("Description")?.asText("") ?: ""
val language = json.get("Language")?.asText("") ?: ""
val ann_id = json.get("ANN_ID")?.asInt()?.toUInt() ?: 0u
val voice_type = json.get("Voice_Type")?.asText() ?: ""
val message_detail = json.get("Message_Detail")?.asText() ?: ""
val message_tags = json.get("Message_TAGS")?.asText() ?: ""
val voice_type = json.get("Voice_Type")?.asText("") ?: ""
val message_detail = json.get("Message_Detail")?.asText("") ?: ""
val message_tags = json.get("Message_TAGS")?.asText("") ?: ""
if (description.isNotEmpty()){
if (language.isNotEmpty() && Language.entries.any{ lang -> lang.name == language }){
if (ann_id>0u){
@@ -416,12 +419,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
if (json.isEmpty) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _description = json.get("Description").asText()
val _language = json.get("Language").asText()
val _description = json.get("Description").asText("")
val _language = json.get("Language").asText("")
val _ann_id = json.get("ANN_ID").asInt().toUInt()
val _voice_type = json.get("Voice_Type").asText()
val _message_detail = json.get("Message_Detail").asText()
val _message_tags = json.get("Message_TAGS").asText()
val _voice_type = json.get("Voice_Type").asText("")
val _message_detail = json.get("Message_Detail").asText("")
val _message_tags = json.get("Message_TAGS").asText("")
var changed = false
if (ValidString(_description) && _description != mb.Description) {
@@ -507,8 +510,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
post("Add"){
// Parse JSON from request body
val json: JsonNode = objectmapper.readTree(it.body())
val tag = json.get("tag").asText()
val languages = json.get("language").asText().split(";")
val tag = json.get("tag").asText("")
val languages = json.get("language").asText("").split(";")
println("Add Language Link, tag=$tag, languages=$languages")
if (ValidString(tag)){
if (languages.all { xx -> Language.entries.any { yy -> yy.name.equals(xx,true)} }){
@@ -571,8 +574,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
if (json.isEmpty) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _tag = json.get("tag").asText()
val _language = json.get("language").asText()
val _tag = json.get("tag").asText("")
val _language = json.get("language").asText("")
var changed = false
if (ValidString(_language) && _language != ll.Language) {
ll.Language = _language
@@ -670,14 +673,14 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
if (json.isEmpty) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _description = json.get("Description").asText()
val _time = json.get("Time").asText()
val _day = json.get("Day").asText()
val _soundpath = json.get("Soundpath").asText()
val _description = json.get("Description").asText("")
val _time = json.get("Time").asText("")
val _day = json.get("Day").asText("")
val _soundpath = json.get("Soundpath").asText("")
val _repeat = json.get("Repeat").asInt().toUByte()
val _enable = json.get("Enable").asBoolean()
val _broadcast_zones = json.get("BroadcastZones").asText()
val _language = json.get("Language").asText()
val _broadcast_zones = json.get("BroadcastZones").asText("")
val _language = json.get("Language").asText("")
var changed = false
if (ValidString(_description) && _description != sb.Description) {
sb.Description = _description
@@ -832,12 +835,24 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
}
}
post("Add") {
// Parse JSON from request body
val json: JsonNode = objectmapper.readTree(it.body())
val zone = json.get("zone").asText()
val description = json.get("description").asText()
println("Add Broadcast Zone, zone=$zone, description=$description")
// TODO continue tomorrow
val json : JsonNode = objectmapper.readTree(it.body())
val _description = json.get("description").asText("")
val _soundchannel = json.get("SoundChannel").asText("")
val _box = json.get("Box").asText("")
val _relay = json.get("Relay").asText("")
if (ValidString(_description)){
if (ValidString(_soundchannel)){
if (ValidString(_box)){
if (ValidString(_relay)){
val newbp = BroadcastZones(0u,_description,_soundchannel,_box,_relay)
if (db.Add_BroadcastZones(newbp)){
db.Resort_BroadcastZones_by_description()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add broadcast zone to database")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Relay")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Box")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid SoundChannel")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid description")))
}
delete("DeleteByIndex/{index}") {
// delete by index
@@ -853,8 +868,52 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
}
}
}
post("UpdateByIndex/{index}") {
// TODO continue tomorrow
patch("UpdateByIndex/{index}") {
// update by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val bz = db.BroadcastZoneList.find { xx -> xx.index == index }
if (bz == null) {
it.status(404).result(objectmapper.writeValueAsString(resultMessage("Broadcast zone with index $index not found")))
} else {
val json: JsonNode = objectmapper.readTree(it.body())
if (json.isEmpty) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _description = json.get("description").asText("")
val _soundchannel = json.get("SoundChannel").asText("")
val _box = json.get("Box").asText("")
val _relay = json.get("Relay").asText("")
var changed = false
if (ValidString(_description) && _description != bz.description) {
bz.description = _description
changed = true
}
if (ValidString(_soundchannel) && _soundchannel != bz.SoundChannel) {
bz.SoundChannel = _soundchannel
changed = true
}
if (ValidString(_box) && _box != bz.Box) {
bz.Box = _box
changed = true
}
if (ValidString(_relay) && _relay != bz.Relay) {
bz.Relay = _relay
changed = true
}
if (changed) {
if (db.Update_BroadcastZones_by_index(index, bz)) {
db.Resort_BroadcastZones_by_description()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update broadcast zone with index $index")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for broadcast zone with index $index")))
}
}
}
}
get("ExportXLSX") {
@@ -910,17 +969,97 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>, val
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val sc = db.SoundChannelList.find { xx -> xx.index == index }
if (sc == null) {
println("Sound channel with index $index not found")
it.status(404).result(objectmapper.writeValueAsString(resultMessage("Sound channel with index $index not found")))
} else {
val json: JsonNode = objectmapper.readTree(it.body())
println("Received JSON: $json")
if (json.isEmpty) {
println("UpdateByIndex with index=$index has empty body")
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else {
val _channel = json.get("description").asText("")
val _ip = json.get("ip").asText("")
println("Update sound channel with index $index, channel=$_channel, ip=$_ip")
if (ValidString(_channel)){
if (ValidIPV4(_ip)){
if (_channel.equals(sc.channel) && _ip.equals(sc.ip)) {
println("Nothing has changed for sound channel with index $index")
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for sound channel with index $index")))
return@patch
} else {
// cek apakah ada soundchannel lain yang pakai ip dan channel yang sama
if (db.SoundChannelList.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 json: JsonNode = objectmapper.readTree(it.body())
// ada sesuatu yang ganti
val newsc = SoundChannel(0u, _channel, _ip)
if (db.Update_SoundChannel_by_index(index,newsc)){
println("Updated sound channel with index $index")
db.Resort_SoundChannel_by_index()
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 {
println("Invalid IP address")
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid IP address")))
}
} else {
println("Invalid channel")
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid channel")))
}
}
}
}
}
get("ExportXLSX"){
val xlsxdata = db.Export_SoundChannel_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"soundchannel.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to export sound channel to XLSX")))
}
}
post("ImportXLSX"){
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.Import_SoundChannel_XLSX(xlsx)) {
db.Resort_SoundChannel_by_index()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import sound channel from XLSX")))
}
} catch (e: Exception) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
}