From de419aeae8b055d56732cdad1d15775141a6beb9 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 3 Sep 2022 11:15:43 +0200 Subject: [PATCH] web-map tidy up, and add diagram to README --- README.md | 38 ++++++++++++++++++- .../dev/adamko/kafkatorio/webmap/App.kt | 24 ++---------- .../webmap/config/ApplicationProperties.kt | 2 + .../kafkatorio/webmap/layout/headerNav.kt | 13 ++++--- .../kafkatorio/webmap/layout/homePage.kt | 2 +- .../webmap/layout/servers/serverPage.kt | 2 +- .../dev/adamko/kafkatorio/webmap/map.kt | 7 ++-- .../webmap/services/WebsocketService.kt | 10 ++--- .../src/main/resources/css/kafkatorio.css | 11 +----- 9 files changed, 60 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 94fc22c..54c20e7 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,42 @@ and WebSocket APIs. Development is ongoing. At present the only application is a [live-view web map](https://factorio.adamko.dev), that shows the current status of a Factorio -server, and any connected players. Other possibilities are exposing metrics, and inter-server -communication. +server, and any connected players. Other usage possibilities include exposing live metrics, and +inter-server communication. + +### Overview + +```mermaid +sequenceDiagram + +Factorio multiplayer server->>events-mod: in-game events +Note over events-mod: converts events to packets +events-mod->>logs processor: packets logged to console +logs processor->>Kafka: publishes packets
(Requires API key!) + +Kafka->>events-server: + +Note over events-server: Packets are processed + +events-server->>web-map: Web map tiles (REST API) +events-server-)web-map: WebSocket updates
(e.g. player movement) +``` + +1. The Kafkatorio game mod, [`events-mod`](./modules/events-mod), collects event data, de-bouncing + and grouping events, and converts them to JSON packets +2. `events-mod` prints the packets to the Factorio game server logs +3. The Factorio logs are forwarded to a Kafka cluster, either using + * [syslog](https://docs.docker.com/config/containers/logging/syslog/) (using Docker logging + plugin) + * or [Kafka Pipe](./modules/infra-kafka-pipe) (a script that reads the server logs and forwards + them to a Kafka topic) +4. The packets are consumed by [`events-server`](./modules/events-server), which processes the + packets using Kafka Streams, creating and hosting map tiles. + + `events-server` serves the map tiles via a REST API, and produces WebSocket messages regarding + updates (for example, when a player's location has changed). +5. The `web-map` uses Leaflet/JS to display the map, using the tiles and WebSocket messages from + `events-server` ## License diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt index 35b0729..f823576 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt @@ -50,12 +50,10 @@ object App : Application() { var siteStateStore: ReduxStore by appState private set -// var wsService: WebsocketService by appState -// private set init { this.siteStateStore = createReduxStore(SiteState::plus, SiteState()) -// this.wsService = wsService + require("./css/kafkatorio.css") WebsocketService.packetsFlow @@ -78,9 +76,6 @@ object App : Application() { this.appState = state.toMutableMap() -// val siteRouting = SiteRouting(reduxStore) -// val websocketService = WebsocketService(siteStateStore) - SiteRouting.init() root("kvapp") { @@ -99,9 +94,6 @@ object App : Application() { } } } - -// div("Kafkatorio Web Map") -// add(kvMaps) } // kvMaps.leafletMap { @@ -124,20 +116,10 @@ object App : Application() { } + fun main() { startApplication( - { -// val reduxStore = createFactorioReduxStore() -// val wsService = WebsocketService( -// reduxStore = reduxStore, -// ) -// App( -// reduxStore = reduxStore, -// wsService = wsService, -// ) - - App - }, + { App }, module.hot, BootstrapModule, // BootstrapCssModule, diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt index 02485cb..652481c 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt @@ -4,6 +4,8 @@ import kotlinx.browser.window object ApplicationProperties { + + /** Determine the WebSocket URL based on the window location URL. */ @Suppress("HttpUrlsUsage") val websocketServerUrl: String get() = window.location.origin diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt index 5cf782f..10d6702 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt @@ -3,14 +3,15 @@ package dev.adamko.kafkatorio.webmap.layout import dev.adamko.kafkatorio.webmap.SiteState import dev.adamko.kafkatorio.webmap.SiteView import io.kvision.core.Container -import io.kvision.html.div -import io.kvision.html.link -import io.kvision.html.nav +import io.kvision.navbar.nav +import io.kvision.navbar.navLink +import io.kvision.navbar.navbar + fun Container.headerNav(state: SiteState) { - nav(className = "navbar") { - div(className = "container") { - link("home", SiteView.HOME.path) + navbar { + nav { + navLink("Home", SiteView.HOME.path) } } } diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt index 2356aa2..91187c7 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt @@ -40,7 +40,7 @@ private fun Container.servers(state: SiteState) { // } // } - button(text = "$serverId") { + button(text = "$serverId", className = "btn btn-link") { style = ButtonStyle.PRIMARY size = ButtonSize.SMALL diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt index 7d8d2b3..2319b6a 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt @@ -9,7 +9,7 @@ import io.kvision.maps.externals.leaflet.layer.tile.GridLayer fun Container.serverPage(state: SiteState) { val factorioGameState = state.factorioGameState - div(className = "server-page") { + div(className = "server-page container-fluid") { if (factorioGameState != null) { add(factorioGameState.map.kvMap) diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt index cc661b4..e47c3fe 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt @@ -122,6 +122,7 @@ class FactorioMap( } } + private fun createServerTileLayer( layer: ServerMapTileLayer ): TileLayer { @@ -160,11 +161,11 @@ class FactorioMap( return baseTileLayer } - private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String { - return "/kafkatorio/data/servers/${serverId.id}/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png" - } + private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String = + "/kafkatorio/data/servers/${serverId.id}/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png" companion object { + /** Add this attribute to `` tiles that need to be dynamically refreshed. */ const val DYNAMIC_RELOAD_ATT = "dynamic-reload" } } diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt index aacb43f..8553f08 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt @@ -20,6 +20,7 @@ import dev.adamko.kafkatorio.webmap.rootJob import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -33,14 +34,13 @@ import org.w3c.dom.events.Event object WebsocketService : CoroutineScope { override val coroutineContext: CoroutineContext = - CoroutineName("WebsocketService") + Job(rootJob) + Dispatchers.Default + CoroutineName("WebsocketService") + Job(rootJob) private val ws = WebSocket(ApplicationProperties.websocketServerUrl) -// private val ws = WebSocket("ws://localhost:12080/ws/foo") - private val _packetsFlow = MutableSharedFlow() + private val packetsMutableFlow = MutableSharedFlow() val packetsFlow: SharedFlow - get() = _packetsFlow.asSharedFlow() + get() = packetsMutableFlow.asSharedFlow() init { ws.onmessage = ::handleMessageEvent @@ -69,7 +69,7 @@ object WebsocketService : CoroutineScope { if (packet != null) { - launch { _packetsFlow.emit(packet) } + launch { packetsMutableFlow.emit(packet) } when (packet) { is EventServerPacket.ChunkTileSaved -> {} diff --git a/modules/web-map/src/main/resources/css/kafkatorio.css b/modules/web-map/src/main/resources/css/kafkatorio.css index 2533785..00e0e43 100644 --- a/modules/web-map/src/main/resources/css/kafkatorio.css +++ b/modules/web-map/src/main/resources/css/kafkatorio.css @@ -7,16 +7,7 @@ image-rendering: pixelated; } -/*html {*/ -/* background: #2B2B2BFF;*/ -/*}*/ - -/*body {*/ -/* background: #2B2B2BFF;*/ -/*}*/ - .leaflet-container { + /* make the Leaflet map background transparent, so it matches the site theme */ background-color: rgba(255, 0, 0, 0.0) !important; - /*background: rgb(34, 34, 34) !important;*/ - /*background: var(--bs-body-bg-rgb) !important;*/ }