diff --git a/.idea/misc.xml b/.idea/misc.xml index 3661012..91f6be8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,9 @@ - + + diff --git a/build.gradle.kts b/build.gradle.kts index 6a35f6d..a2372e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,8 +26,8 @@ dependencies { compileOnly("eu.cloudnetservice.cloudnet:node:4.0.0-RC10") compileOnly("eu.cloudnetservice.cloudnet:bridge:4.0.0-RC10") - compileOnly("com.github.cloudnetservice.cloud-command-framework:cloud-core:1.9.0-cn1") - compileOnly("com.github.cloudnetservice.cloud-command-framework:cloud-annotations:1.9.0-cn1") + compileOnly("com.github.cloudnetservice.cloud-command-framework:cloud-core:2.0.0-cn1") + compileOnly("com.github.cloudnetservice.cloud-command-framework:cloud-annotations:2.0.0-cn1") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") implementation("io.ktor:ktor-server-core:$ktor_version") @@ -36,6 +36,8 @@ dependencies { implementation("io.ktor:ktor-server-content-negotiation:$ktor_version") implementation("io.ktor:ktor-server-auth:$ktor_version") implementation("io.ktor:ktor-server-auth-jwt:$ktor_version") + implementation("io.ktor:ktor-server-websockets:$ktor_version") + implementation("io.ktor:ktor-server-cors:$ktor_version") implementation("org.mariadb.jdbc:mariadb-java-client:3.4.0") diff --git a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/CloudNet_Rest_Module.kt b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/CloudNet_Rest_Module.kt index d443601..0b1bf95 100644 --- a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/CloudNet_Rest_Module.kt +++ b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/CloudNet_Rest_Module.kt @@ -6,50 +6,67 @@ package io.github.thecguy.cloudnet_rest_module import eu.cloudnetservice.common.language.I18n import eu.cloudnetservice.driver.document.Document import eu.cloudnetservice.driver.document.DocumentFactory +import eu.cloudnetservice.driver.event.EventListener +import eu.cloudnetservice.driver.event.events.service.CloudServiceLogEntryEvent import eu.cloudnetservice.driver.inject.InjectionLayer import eu.cloudnetservice.driver.module.ModuleLifeCycle import eu.cloudnetservice.driver.module.ModuleTask import eu.cloudnetservice.driver.module.driver.DriverModule +import eu.cloudnetservice.driver.network.http.websocket.WebSocketFrameType +import eu.cloudnetservice.driver.provider.ServiceTaskProvider import eu.cloudnetservice.node.ShutdownHandler +import eu.cloudnetservice.node.cluster.NodeServerProvider import eu.cloudnetservice.node.command.CommandProvider import eu.cloudnetservice.node.service.CloudServiceManager -import eu.cloudnetservice.driver.provider.ServiceTaskProvider - - +import eu.cloudnetservice.node.service.ServiceConsoleLineHandler +import eu.cloudnetservice.node.service.ServiceConsoleLogCache import io.github.thecguy.cloudnet_rest_module.commands.rest import io.github.thecguy.cloudnet_rest_module.config.Configuration import io.github.thecguy.cloudnet_rest_module.coroutines.AuthChecker import io.github.thecguy.cloudnet_rest_module.utli.AuthUtil import io.github.thecguy.cloudnet_rest_module.utli.DBManager import io.github.thecguy.cloudnet_rest_module.utli.JsonUtils - +import io.github.thecguy.cloudnet_rest_module.utli.WebsocketManager import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.engine.* import io.ktor.server.netty.* +import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.swagger.* import io.ktor.server.response.* import io.ktor.server.routing.* +import io.ktor.server.websocket.* +import io.ktor.websocket.* import jakarta.inject.Named import jakarta.inject.Singleton +import kong.unirest.core.json.JSONArray import kong.unirest.core.json.JSONObject +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.launch import org.jetbrains.annotations.NotNull +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.time.Duration import java.util.* @Singleton class CloudNet_Rest_Module : DriverModule() { + private val Logger: Logger = LoggerFactory.getLogger(CloudNet_Rest_Module::class.java) private val dbm = DBManager() private val jsonUtils = JsonUtils() private val authChecker = AuthChecker() private val authUtil = AuthUtil() + private val websocketManager = WebsocketManager() @Volatile private var configuration: Configuration? = null + @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.LOADED) fun convertConfig() { val config = this.readConfig(DocumentFactory.json()) @@ -69,6 +86,7 @@ class CloudNet_Rest_Module : DriverModule() { @ModuleTask(order = 125, lifecycle = ModuleLifeCycle.LOADED) fun load() { + println("dsym74") configuration = this.readConfig( Configuration::class.java, { @@ -78,31 +96,34 @@ class CloudNet_Rest_Module : DriverModule() { "cloudnet_rest", "127.0.0.1", 3306, - 8080 + 2412 ) }, DocumentFactory.json() ) + dbm.dbexecute("CREATE TABLE IF NOT EXISTS cloudnet_rest_users (id SERIAL PRIMARY KEY, user TEXT, password TEXT)") dbm.dbexecute("CREATE TABLE IF NOT EXISTS cloudnet_rest_permission (id SERIAL PRIMARY KEY, user TEXT, permission TEXT)") dbm.dbexecute("CREATE TABLE IF NOT EXISTS cloudnet_rest_auths (id SERIAL PRIMARY KEY, type TEXT, value TEXT, timestamp TEXT, user TEXT)") dbm.dbexecute("DELETE FROM cloudnet_rest_auths") authChecker.schedule() } + @OptIn(DelicateCoroutinesApi::class) @ModuleTask(order = 127, lifecycle = ModuleLifeCycle.STARTED) fun started( @NotNull cloudServiceManager: CloudServiceManager, @NotNull shutdownHandler: ShutdownHandler, @NotNull serviceTaskProvider: ServiceTaskProvider, + @NotNull nodeServerProvider: NodeServerProvider, @NotNull @Named("module") injectionLayer: InjectionLayer<*> ) { I18n.loadFromLangPath(CloudNet_Rest_Module::class.java) GlobalScope.launch { - main(cloudServiceManager, shutdownHandler, serviceTaskProvider) + main(cloudServiceManager, shutdownHandler, serviceTaskProvider, nodeServerProvider) } - println("Rest API listening on port ${configuration!!.restapi_port}!") + Logger.info("RestAPI listening on port ${configuration!!.restapi_port}") } @ModuleTask(lifecycle = ModuleLifeCycle.STARTED) fun start(commandProvider: CommandProvider) { @@ -115,13 +136,31 @@ class CloudNet_Rest_Module : DriverModule() { println("Closed DB connection!") } + @OptIn(DelicateCoroutinesApi::class) + @EventListener + fun cloudServiceLogEntryEvent(event: CloudServiceLogEntryEvent) { + // Extract the service name from the event + val serviceName = event.serviceInfo().name() + + // Get the log message or any other relevant data from the event + val message = event.line() + + // Broadcast the message to WebSocket sessions listening to this service + GlobalScope.launch { + println("Sending message: $message to service: $serviceName") + WebsocketManager.WebSocketManager.broadcast(serviceName, message) + } + } + + @OptIn(DelicateCoroutinesApi::class) private fun main( @NotNull cloudServiceManager: CloudServiceManager, @NotNull shutdownHandler: ShutdownHandler, - @NotNull serviceTaskProvider: ServiceTaskProvider + @NotNull serviceTaskProvider: ServiceTaskProvider, + @NotNull nodeServerProvider: NodeServerProvider ) { val port = configuration!!.restapi_port - embeddedServer(Netty, port = port) { + embeddedServer(Netty, port = port, host = "0.0.0.0") { install(ShutDownUrl.ApplicationCallPlugin) { shutDownUrl = "/debug/shutdown" @@ -150,14 +189,131 @@ class CloudNet_Rest_Module : DriverModule() { } } } + install(WebSockets) { + pingPeriod = Duration.ofSeconds(15) + timeout = Duration.ofSeconds(15) + maxFrameSize = Long.MAX_VALUE + masking = false + } + //install(CORS) { + + // anyHost() + + // allowMethod(HttpMethod.Get) + // allowMethod(HttpMethod.Put) + // allowMethod(HttpMethod.Post) + // allowMethod(HttpMethod.Options) + // allowMethod(HttpMethod.Patch) + // allowMethod(HttpMethod.Delete) + // allowMethod(HttpMethod.Head) + + // allowHeader(HttpHeaders.Upgrade) + + // allowHeader(HttpHeaders.SecWebSocketProtocol) + // allowHeader(HttpHeaders.Authorization) + // allowHeader(HttpHeaders.Accept) + // allowHeader(HttpHeaders.ContentType) + + // allowCredentials = true + // allowSameOrigin = true + + //} routing { swaggerUI(path = "swagger", swaggerFile = "openapi/swagger.yaml") + + + + // _ _ _ _____ _ _ + // / \ | | | ||_ _|| | | | + // / _ \ | | | | | | | |_| | + // / ___ \ | |_| | | | | _ | + // /_/ \_\ \___/ |_| |_| |_| + + authenticate("auth-basic") { - get("/auth") { + post("api/v1/auth") { call.respond(jsonUtils.token(call.principal()?.name.toString()).toString(4)) } } + get("api/v1/auth/verify") { + val token = call.request.headers["Authorization"] + if (authUtil.validToken(token.toString())) { + call.response.status(HttpStatusCode.OK) + } else { + call.response.status(HttpStatusCode.Unauthorized) + } + } + get("api/v1/auth/revoke") { + val token = call.request.headers["Authorization"] + if (authUtil.validToken(token.toString())) { + dbm.dbexecute("DELETE FROM cloudnet_rest_auths WHERE value = '$token'") + call.response.status(HttpStatusCode.OK) + } else { + call.response.status(HttpStatusCode.Unauthorized) + } + } + + // ____ _____ ____ _____ + // | _ \ | ____|/ ___| |_ _| + // | |_) || _| \___ \ | | + // | _ < | |___ ___) | | | + // |_| \_\|_____||____/ |_| + get("api/v1/users") { + val token = call.request.headers["Authorization"] + val usrs = dbm.cmd_rest_users() + if (authUtil.authToken(token.toString(), "cloudnet.rest.users")) { + val userS = JSONObject() + val users = JSONArray() + usrs.forEach { user -> + users.put(user) + } + userS.put("users", users) + call.respond(userS.toString(4)) + } else { + call.response.status(HttpStatusCode.Unauthorized) + } + } + + // _ _ ___ ____ _____ + // | \ | | / _ \ | _ \ | ____| + // | \| || | | || | | || _| + // | |\ || |_| || |_| || |___ + // |_| \_| \___/ |____/ |_____| + + get("api/v1/node") { + val token = call.request.headers["Authorization"] + if (authUtil.authToken(token.toString(), "cloudnet.rest.node")) { + call.respond(jsonUtils.nodeInfo(nodeServerProvider.localNode().nodeInfoSnapshot()).toString(4)) + } else { + call.response.status(HttpStatusCode.Unauthorized) + } + } + get("api/v1/node/ping") { + val token = call.request.headers["Authorization"] + if (authUtil.authToken(token.toString(), "cloudnet.rest.node.ping")) { + call.response.status(HttpStatusCode.NoContent) + } else { + call.response.status(HttpStatusCode.Unauthorized) + } + } + + // ____ _ _ _ ____ _____ _____ ____ + // / ___|| | | | | |/ ___| |_ _|| ____|| _ \ + // | | | | | | | |\___ \ | | | _| | |_) | + // | |___ | |___ | |_| | ___) | | | | |___ | _ < + // \____||_____| \___/ |____/ |_| |_____||_| \_\ + + + + + + + + + + + //val token = call.request.headers["Authorization"] //if (authUtil.authToken(token.toString(), "cloudnet.rest.services")) { //} else { @@ -165,7 +321,7 @@ class CloudNet_Rest_Module : DriverModule() { //} //services - get("/services") { + get("api/v1/services") { val services = jsonUtils.services(cloudServiceManager) val token = call.request.headers["Authorization"] if (authUtil.authToken(token.toString(), "cloudnet.rest.services")) { @@ -180,7 +336,7 @@ class CloudNet_Rest_Module : DriverModule() { } - get("/services/{service}") { + get("api/v1/services/{service}") { val services = cloudServiceManager.services().map { it.name() }.toList() val serv = services.contains(call.parameters["service"]) @@ -196,8 +352,82 @@ class CloudNet_Rest_Module : DriverModule() { } } + webSocket("api/v1/services/{service}/liveLog") { + val services = cloudServiceManager.services().map { it.name() }.toList() + val serv = services.contains(call.parameters["service"]) + val token = call.parameters["token"] + + Logger.info(token.toString()) + + if (authUtil.authToken(token.toString(), "cloudnet.rest.service.liveLog")) { + Logger.warn("WebSocket - Token valid!") + } else { + Logger.error("WebSocket - Token invalid!") + } + + if (serv) { + Logger.warn("WebSocket - Service valid!") + } else { + Logger.error("WebSocket - Service invalid!") + } + + if (authUtil.authToken(token.toString(), "cloudnet.rest.service.liveLog")) { + + if (serv) { + + + val service = call.parameters["service"] + + val handler: ServiceConsoleLineHandler = + ServiceConsoleLineHandler { _: ServiceConsoleLogCache?, line: String?, _: Boolean -> + GlobalScope.launch { + WebsocketManager.WebSocketManager.broadcast( + service!!, + line.toString() + ) + } + } + + cloudServiceManager.localCloudService( + cloudServiceManager.serviceByName(service!!)!!.serviceId().uniqueId() + )!!.serviceConsoleLogCache().addHandler(handler) + + + // Add the session to the WebSocketManager for this specific service + WebsocketManager.WebSocketManager.addSession(service, this) + + try { + // Listen for incoming WebSocket frames (if needed) + incoming.consumeEach { frame -> + if (frame is Frame.Text) { + val receivedText = frame.readText() + println("Received message from client: $receivedText") + } + } + } finally { + println("Disconnected!") + + cloudServiceManager.localCloudService( + cloudServiceManager.serviceByName(service)!!.serviceId().uniqueId() + )!!.serviceConsoleLogCache().removeHandler(handler) + WebsocketManager.WebSocketManager.removeSession(service, this) + } + } else { + println("ForceClose") + close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Missing service parameter")) + } + } + } + + + + + + + + //tasks - get("/tasks") { + get("api/v1/tasks") { val token = call.request.headers["Authorization"] if (authUtil.authToken(token.toString(), "cloudnet.rest.tasks")) { call.respond(jsonUtils.tasks(serviceTaskProvider).toString(4)) @@ -206,7 +436,7 @@ class CloudNet_Rest_Module : DriverModule() { } } - get("/tasks/{task}") { + get("api/v1/tasks/{task}") { val tasks = serviceTaskProvider.serviceTasks().map { it.name() }.toList() val tas = tasks.contains(call.parameters["task"]) @@ -221,7 +451,7 @@ class CloudNet_Rest_Module : DriverModule() { call.response.status(HttpStatusCode.Unauthorized) } } - delete("/tasks/{task}") { + delete("api/v1/tasks/{task}") { val tasks = serviceTaskProvider.serviceTasks().map { it.name() }.toList() val tas = tasks.contains(call.parameters["task"]) @@ -240,13 +470,13 @@ class CloudNet_Rest_Module : DriverModule() { - get("/node/shutdown") { + get("api/v1/node/shutdown") { call.respondText("NOTE: The Cloud is shutting down!") shutdownHandler.shutdown() } - get("/") { + get("api/v1/") { call.respondText("Welcome to my Rest API!") } diff --git a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/commands/rest.kt b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/commands/rest.kt index 661b373..a48b4c7 100644 --- a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/commands/rest.kt +++ b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/commands/rest.kt @@ -1,11 +1,10 @@ package io.github.thecguy.cloudnet_rest_module.commands -import cloud.commandframework.annotations.Argument -import cloud.commandframework.annotations.CommandMethod -import cloud.commandframework.annotations.CommandPermission -import cloud.commandframework.annotations.parsers.Parser -import cloud.commandframework.annotations.suggestions.Suggestions -import cloud.commandframework.context.CommandContext +import org.incendo.cloud.annotations.Argument +import org.incendo.cloud.annotations.parser.Parser +import org.incendo.cloud.annotations.suggestion.Suggestions +import org.incendo.cloud.context.CommandContext +import org.incendo.cloud.annotations.Permission import eu.cloudnetservice.common.language.I18n import eu.cloudnetservice.driver.inject.InjectionLayer @@ -17,6 +16,7 @@ import eu.cloudnetservice.node.command.source.CommandSource import io.github.thecguy.cloudnet_rest_module.utli.DBManager import jakarta.inject.Singleton +import org.incendo.cloud.annotations.Command import org.jetbrains.annotations.NotNull @@ -24,7 +24,7 @@ import java.util.* @Singleton -@CommandPermission("thecguy.test") +@Permission("thecguy.rest") @Description("test") class rest { private val dbManager = DBManager() @@ -41,14 +41,14 @@ class rest { return taskProvider.serviceTasks().toList() } - @CommandMethod("rest users") + @Command("rest users") fun users( source: CommandSource, ) { source.sendMessage(I18n.trans("module-rest-command-users")) source.sendMessage(dbManager.cmd_rest_users()) } - @CommandMethod("rest user create ") + @Command("rest user create ") fun createUser( source: CommandSource, @NotNull @Argument("username") username: String, @@ -63,7 +63,7 @@ class rest { source.sendMessage(I18n.trans("module-rest-command-createduser")) } } - @CommandMethod("rest user delete ") + @Command("rest user delete ") fun deleteUser( source: CommandSource, @NotNull @Argument("username") username: String @@ -76,7 +76,7 @@ class rest { source.sendMessage(I18n.trans("module-rest-command-usernotexist")) } } - @CommandMethod("rest user user add permission ") + @Command("rest user user add permission ") fun addPermsToUser( source: CommandSource, @NotNull @Argument("username") username: String, @@ -95,7 +95,7 @@ class rest { source.sendMessage(I18n.trans("module-rest-command-usernotexist")) } } - @CommandMethod("rest user user remove permission ") + @Command("rest user user remove permission ") fun remPerms( source: CommandSource, @NotNull @Argument("username") username: String, @@ -114,4 +114,5 @@ class rest { source.sendMessage(I18n.trans("module-rest-command-usernotexist")) } } + } \ No newline at end of file diff --git a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/AuthUtil.kt b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/AuthUtil.kt index c6e5c9f..d3cd034 100644 --- a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/AuthUtil.kt +++ b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/AuthUtil.kt @@ -1,18 +1,23 @@ package io.github.thecguy.cloudnet_rest_module.utli + +import org.slf4j.Logger +import org.slf4j.LoggerFactory import java.security.SecureRandom class AuthUtil internal constructor() { - val dbManager = DBManager() + private val dbManager = DBManager() + private val Logger: Logger = LoggerFactory.getLogger(AuthUtil::class.java) - init { + init { + println("init success!") } fun generateToken(length: Int): String { - val characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/*-+-_!" + val characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/*-_!" val random = SecureRandom() val token = StringBuilder(length) @@ -29,6 +34,7 @@ class AuthUtil internal constructor() { } fun authToken(token: String, permission: String): Boolean { + Logger.info(token) return if (validToken(token)) { val user = dbManager.tokenToUser(token) val perms = dbManager.cmd_rest_perms(user) @@ -42,8 +48,9 @@ class AuthUtil internal constructor() { } } - private fun validToken(token: String): Boolean { + fun validToken(token: String): Boolean { val tokens = dbManager.tokens() + Logger.info(tokens.toString()) return tokens.contains(token) } diff --git a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/DBManager.kt b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/DBManager.kt index cbf049b..31bacb2 100644 --- a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/DBManager.kt +++ b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/DBManager.kt @@ -13,12 +13,10 @@ class DBManager internal constructor() { init { - val hikariLogger = LoggerFactory.getLogger("com.zaxxer.hikari") as ch.qos.logback.classic.Logger - hikariLogger.level = Level.DEBUG - val config = HikariConfig() - val host = "127.0.0.1" + + val host = "192.168.0.204" val port = 3306 val database = "cloudnet_rest" val username = "cloudnet" diff --git a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/JsonUtils.kt b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/JsonUtils.kt index 3ea7fc5..3799953 100644 --- a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/JsonUtils.kt +++ b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/JsonUtils.kt @@ -1,8 +1,8 @@ package io.github.thecguy.cloudnet_rest_module.utli +import eu.cloudnetservice.driver.cluster.NodeInfoSnapshot import eu.cloudnetservice.driver.provider.ServiceTaskProvider import eu.cloudnetservice.node.service.CloudServiceManager -import io.github.thecguy.cloudnet_rest_module.config.Configuration import kong.unirest.core.json.JSONArray import kong.unirest.core.json.JSONObject import org.jetbrains.annotations.NotNull @@ -150,8 +150,38 @@ class JsonUtils internal constructor() { return jTask } - - + fun nodeInfo(nodeInfoSnapshot: NodeInfoSnapshot): JSONObject { + val nodeInfo = JSONObject() + + val nNode = JSONObject() + nNode.put("listeners", nodeInfoSnapshot.node().listeners()) + nNode.put("uniqueId", nodeInfoSnapshot.node().uniqueId()) + nodeInfo.put("node", nNode) + + nodeInfo.put("modules", nodeInfoSnapshot.modules()) + nodeInfo.put("creationTime", nodeInfoSnapshot.creationTime()) + nodeInfo.put("version", nodeInfoSnapshot.version()) + nodeInfo.put("currentServicesCount", nodeInfoSnapshot.currentServicesCount()) + nodeInfo.put("draining", nodeInfoSnapshot.draining()) + nodeInfo.put("maxMemory", nodeInfoSnapshot.maxMemory()) + nodeInfo.put("maxProcessorUsageToStartServices", nodeInfoSnapshot.maxProcessorUsageToStartServices()) + nodeInfo.put("reservedMemory", nodeInfoSnapshot.reservedMemory()) + nodeInfo.put("startupMillis", nodeInfoSnapshot.startupMillis()) + nodeInfo.put("usedMemory", nodeInfoSnapshot.usedMemory()) + val processSnapshot = JSONObject() + processSnapshot.put("pid", nodeInfoSnapshot.processSnapshot().pid) + processSnapshot.put("threads", nodeInfoSnapshot.processSnapshot().threads) + processSnapshot.put("cpuUsage", nodeInfoSnapshot.processSnapshot().cpuUsage) + processSnapshot.put("maxHeapMemory", nodeInfoSnapshot.processSnapshot().maxHeapMemory) + processSnapshot.put("systemCpuUsage", nodeInfoSnapshot.processSnapshot().systemCpuUsage) + processSnapshot.put("heapUsageMemory", nodeInfoSnapshot.processSnapshot().heapUsageMemory) + processSnapshot.put("currentLoadedClassCount", nodeInfoSnapshot.processSnapshot().currentLoadedClassCount) + processSnapshot.put("totalLoadedClassCount", nodeInfoSnapshot.processSnapshot().totalLoadedClassCount) + processSnapshot.put("unloadedClassCount", nodeInfoSnapshot.processSnapshot().unloadedClassCount) + nodeInfo.put("processSnapshot", processSnapshot) + + return nodeInfo + } fun token(user: String):JSONObject { diff --git a/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/WebsocketManager.kt b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/WebsocketManager.kt new file mode 100644 index 0000000..5582503 --- /dev/null +++ b/src/main/kotlin/io/github/thecguy/cloudnet_rest_module/utli/WebsocketManager.kt @@ -0,0 +1,36 @@ +package io.github.thecguy.cloudnet_rest_module.utli + +import io.ktor.websocket.* +import java.util.concurrent.ConcurrentHashMap + +class WebsocketManager { + + object WebSocketManager { + // Store sessions by service name + private val serviceSessions: MutableMap> = ConcurrentHashMap() + + // Add a WebSocket session for a specific service + fun addSession(service: String, session: DefaultWebSocketSession) { + serviceSessions.computeIfAbsent(service) { ConcurrentHashMap.newKeySet() }.add(session) + } + + // Remove a WebSocket session for a specific service + fun removeSession(service: String, session: DefaultWebSocketSession) { + serviceSessions[service]?.remove(session) + if (serviceSessions[service]?.isEmpty() == true) { + serviceSessions.remove(service) // Clean up if no sessions are left + } + } + + // Send message to all open WebSocket sessions for a specific service + suspend fun broadcast(service: String, message: String) { + serviceSessions[service]?.forEach { session -> + try { + session.send(message) + } catch (e: Exception) { + e.printStackTrace() // Handle errors during message sending + } + } + } + } +} \ No newline at end of file