commit 22/01/2026

This commit is contained in:
2026-01-22 13:57:34 +07:00
parent 34cf8c97df
commit ffd41ee225
10 changed files with 163 additions and 72 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
out/ out/
!**/src/main/**/out/ !**/src/main/**/out/
!**/src/test/**/out/ !**/src/test/**/out/
logs/
### Kotlin ### ### Kotlin ###
.kotlin .kotlin

View File

@@ -8,6 +8,7 @@
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" /> <sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/logs" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

View File

@@ -1,5 +1,5 @@
#Configuration saved on 2026-01-21T17:17:53.684082100 #Configuration saved on 2026-01-22T13:49:56.778957100
#Wed Jan 21 17:17:53 WIB 2026 #Thu Jan 22 13:49:56 WIB 2026
activemq_brokerurl=tcp\://localhost\:61616 activemq_brokerurl=tcp\://localhost\:61616
activemq_password=admin activemq_password=admin
activemq_queuename=TEST.QUEUE activemq_queuename=TEST.QUEUE

View File

@@ -1,15 +1,35 @@
$(document).ready(function() { $(document).ready(function() {
// Your code here // Your code here
console.log("log.js is loaded and ready."); console.log("log.js is loaded and ready.");
let $logchooser = $('#logchooser');
// if logchooser value is null, set to today's date in format mm/dd/yyyy
if ($logchooser.val() === null) {
$logchooser.val(new Date().toISOString().split('T')[0]);
}
$('#logoutbtn').on('click', function() { $('#logoutbtn').on('click', function() {
// Clear session storage on logout // Clear session storage on logout
fetch('/logout').then(() => { fetch('/logout').then(() => {
window.location.href = '/login.html'; window.location.href = '/login.html';
}); });
}); });
$logchooser.on('change', function() {
const selectedLog = $(this).val();
GetLog(selectedLog);
});
}); });
$(window).on('beforeunload', function() { $(window).on('beforeunload', function() {
console.log("User is leaving log.html"); console.log("User is leaving log.html");
// Your cleanup code here // Your cleanup code here
}); });
// function GetLog with date parameter in string, with default value today's date in format dd/mm/yyyy
function GetLog(date = new Date().toISOString().split('T')[0]) {
console.log("Fetching logs for date: " + date);
Get('getLogs?'+new URLSearchParams({date: date}).toString(), function(data){
console.log(data);
}, function(error){
alert(error);
})
}

View File

@@ -23,7 +23,7 @@
</nav> </nav>
<div class="row mb-2"> <div class="row mb-2">
<div class="col-2"><label class="col-form-label w-100 h-100 ms-1">Log Chooser</label></div> <div class="col-2"><label class="col-form-label w-100 h-100 ms-1">Log Chooser</label></div>
<div class="col"><input class="form-control-lg" type="date"></div> <div class="col"><input class="form-control-lg" id="logchooser" type="date"></div>
</div> </div>
<div class="row mb-2"> <div class="row mb-2">
<div class="table-responsive"> <div class="table-responsive">

4
src/Log/LogData.kt Normal file
View File

@@ -0,0 +1,4 @@
package Log
@Suppress("UNUSED")
data class LogData(val index: Int, val date: String, val time: String, val function: String, val message: String)

59
src/Log/LogGetter.kt Normal file
View File

@@ -0,0 +1,59 @@
package Log
import org.tinylog.Logger
import java.nio.file.Files
import java.nio.file.Paths
/**
* Class to get log data from log files
*/
@Suppress("UNUSED")
class LogGetter {
// valid date format either dd-MM-yyyy or dd/MM/yyyy
private val dateRegex = """(\d{2})[-/](\d{2})[-/](\d{4})""".toRegex()
// each logline is in format dd/MM/yyyy HH:mm:ss Function: Message
private val logLineRegex = """(\d{2}/\d{2}/\d{4}) (\d{2}:\d{2}:\d{2}) (.+?): (.+)""".toRegex()
// log folder is current working directory + /logs
private val logFolder: String = System.getProperty("user.dir") + "/logs"
/**
* Get log data for a specific date
* @param date Date in format dd-MM-yyyy or dd/MM/yyyy
* @return List of LogData for the specified date or empty list if date is invalid or no logs found
*/
fun getLog(date: String) : ArrayList<LogData> {
// check date with dateRegex and find dd MM yyyy
val x = dateRegex.find(date)
if (x==null) {
Logger.error { "Invalid date format: $date" }
return arrayListOf()
}
val (day, month, year) = x.destructured
// logFilePath is logFolder/year/month/day.log
val p = Paths.get(logFolder, year, month, "${day}.log")
// check if file exists
if (!Files.exists(p)) {
Logger.error { "Log file does not exist: $p" }
return arrayListOf()
}
val logDataList = ArrayList<LogData>()
Files.newBufferedReader(p).useLines { lines ->
lines.forEachIndexed { index, string ->
val ll = logLineRegex.find(string)
if (ll != null && ll.groupValues.size == 5) {
val logData = LogData(
index ,
ll.groupValues[1],
ll.groupValues[2],
ll.groupValues[3],
ll.groupValues[4]
)
logDataList.add(logData)
}
}
}
return logDataList
}
}

View File

@@ -2,6 +2,7 @@ import ActiveMQ.ActiveMQClient
import Other.Config import Other.Config
import Web.WebUI import Web.WebUI
import database.MySQLInjector import database.MySQLInjector
import org.tinylog.provider.ProviderRegistry
import java.util.function.Consumer import java.util.function.Consumer
lateinit var config : Config lateinit var config : Config
@@ -23,5 +24,7 @@ fun main() {
webUI.Stop() webUI.Stop()
activeclient.Stop() activeclient.Stop()
mysql.Stop() mysql.Stop()
// shutdown tinylog properly
ProviderRegistry.getLoggingProvider().shutdown();
}) })
} }

View File

@@ -1,12 +1,11 @@
package Web package Web
import Log.LogGetter
import config import config
import io.javalin.Javalin import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.before import io.javalin.apibuilder.ApiBuilder.before
import io.javalin.apibuilder.ApiBuilder.get import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.apibuilder.ApiBuilder.path
import io.javalin.apibuilder.ApiBuilder.post import io.javalin.apibuilder.ApiBuilder.post
import io.javalin.apibuilder.ApiBuilder.ws
import org.tinylog.Logger import org.tinylog.Logger
@Suppress("unused") @Suppress("unused")
@@ -14,11 +13,13 @@ import org.tinylog.Logger
* Start WebUI Server * Start WebUI Server
*/ */
class WebUI{ class WebUI{
// regex untuk date dengan format YYYY-MM-DD
private val dateRegex1 = """\d{4}-\d{2}-\d{2}""".toRegex()
private var app : Javalin = Javalin.create { cfg -> private var app : Javalin = Javalin.create { cfg ->
cfg.staticFiles.add("/") cfg.staticFiles.add("/")
cfg.router.apiBuilder { cfg.router.apiBuilder {
path("/"){ get("/"){
get {
if (config.WebUsername==it.cookie("username")){ if (config.WebUsername==it.cookie("username")){
Logger.info{"${it.ip()} logged in as ${it.cookie("username")}, forward to home.html"} Logger.info{"${it.ip()} logged in as ${it.cookie("username")}, forward to home.html"}
it.redirect("home.html") it.redirect("home.html")
@@ -27,9 +28,7 @@ class WebUI{
it.redirect("login.html") it.redirect("login.html")
} }
} }
} post("login.html"){
path("login.html"){
post{
val username = it.formParam("username") val username = it.formParam("username")
val password = it.formParam("password") val password = it.formParam("password")
if (config.WebUsername==username && config.WebPassword==password) { if (config.WebUsername==username && config.WebPassword==password) {
@@ -41,14 +40,30 @@ class WebUI{
it.redirect("/login.html?error=1") it.redirect("/login.html?error=1")
} }
} }
get("getLogs"){
var date = it.queryParam("date")
Logger.info{"${it.ip()} User ${it.cookie("username")} requested logs for date: $date"}
if (date!=null){
if (date.isNotEmpty()){
if (dateRegex1.matches(date)){
// ketemu format YYYY-MM-DD, convert format ke dd/MM/yyyy
val parts = date.split("-")
date = "${parts[2]}/${parts[1]}/${parts[0]}"
} }
path("logout"){ val logs = LogGetter().getLog(date)
get { if (logs.isNotEmpty()){
it.json(logs)
} else it.status(404).json(webReply("No logs found for the specified date"))
} else it.status(400).json(webReply("Date parameter is empty"))
} else it.status(400).json(webReply("Please provide a date"))
}
get("logout"){
Logger.info{"${it.ip()} User ${it.cookie("username")} logged out"} Logger.info{"${it.ip()} User ${it.cookie("username")} logged out"}
it.removeCookie("username") it.removeCookie("username")
it.redirect("login.html") it.redirect("login.html")
} }
}
get("getSetting"){ get("getSetting"){
val fd = farmData(config.ActiveMQ_BrokerURL, config.ActiveMQ_Username, config.ActiveMQ_Password, config.ActiveMQ_QueueName) val fd = farmData(config.ActiveMQ_BrokerURL, config.ActiveMQ_Username, config.ActiveMQ_Password, config.ActiveMQ_QueueName)
@@ -97,49 +112,27 @@ class WebUI{
} }
} }
path("home.html"){ before("home.html"){
before{
if (config.WebUsername!=it.cookie("username")){ if (config.WebUsername!=it.cookie("username")){
Logger.info {"${it.ip()} Have not logged in, forward to login.html"} Logger.info {"${it.ip()} Have not logged in, forward to login.html"}
it.redirect("login.html") it.redirect("login.html")
return@before return@before
} }
} }
before("log.html"){
ws("/ws"){ ws ->
ws.onConnect { wsconnectcontext -> Logger.info { "WebSocket connected: ${wsconnectcontext.sessionId()}"; wsconnectcontext.enableAutomaticPings() } }
ws.onClose { wsclosecontext -> Logger.info { "WebSocket closed: ${wsclosecontext.sessionId()}" } }
ws.onError { wserrorcontext -> Logger.error { "WebSocket error in session ${wserrorcontext.sessionId()}: ${wserrorcontext.error()?.message}" } }
ws.onMessage { wsMessageContext ->
// TODO handle incoming messages
}
}
}
path("log.html"){
before{
if (config.WebUsername!=it.cookie("username")){ if (config.WebUsername!=it.cookie("username")){
Logger.info{"${it.ip()} Have not logged in, forward to login.html"} Logger.info{"${it.ip()} Have not logged in, forward to login.html"}
it.redirect("login.html") it.redirect("login.html")
return@before return@before
} }
} }
before("setting.html"){
}
path("setting.html"){
before{
if (config.WebUsername!=it.cookie("username")){ if (config.WebUsername!=it.cookie("username")){
Logger.info{"${it.ip()} Have not logged in, forward to login.html"} Logger.info{"${it.ip()} Have not logged in, forward to login.html"}
it.redirect("login.html") it.redirect("login.html")
return@before return@before
} }
} }
}
} }
} }

10
src/tinylog.properties Normal file
View File

@@ -0,0 +1,10 @@
writer1 = console
writer1.level = info
writer1.format = {date:dd/MM/yyyy HH:mm:ss} {class}.{method}(): {message}
writer2 = rolling file
writer2.format = {date:dd/MM/yyyy HH:mm:ss} {class}.{method}(): {message}
writer2.file = logs/{date:yyyy}/{date:MM}/{date:dd}.log
writer2.level = info
autoshutdown = false # optional, default: true