fixing web-app

This commit is contained in:
stefan
2025-09-24 14:21:57 +02:00
parent cd2b0796a6
commit 1c4184809a
156 changed files with 440 additions and 1708 deletions
-110
View File
@@ -1,110 +0,0 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
// alias(libs.plugins.composeHotReload)
}
// Project version configuration
version = "1.0.0"
group = "at.mocode"
kotlin {
// Configure JVM toolchain for all JVM targets
jvmToolchain(21)
jvm()
js {
browser()
binaries.executable()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}
sourceSets {
commonMain.dependencies {
implementation(projects.client.shared)
// Core Compose Dependencies - minimiert für kleinere Bundle-Größe
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
// UiToolingPreview nur für Development, nicht für Production WASM
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
// HTTP client dependencies for ping-service - optimiert
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.cio)
}
}
}
compose.desktop {
application {
mainClass = "at.mocode.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Meldestelle"
packageVersion = "1.0.0"
// Application metadata
description = "Pferdesport Meldestelle System - Client Application"
copyright = "© 2025 Meldestelle Project"
vendor = "at.mocode"
// Platform-specific configurations
linux {
iconFile.set(project.file("src/commonMain/resources/icon.png"))
packageName = "meldestelle"
debMaintainer = "stefan@mocode.at"
menuGroup = "Office"
}
windows {
iconFile.set(project.file("src/commonMain/resources/icon.ico"))
menuGroup = "Meldestelle"
upgradeUuid = "61DAB35E-17CB-43B8-8A72-39876CF0E021"
}
macOS {
iconFile.set(project.file("src/commonMain/resources/icon.icns"))
bundleID = "at.mocode.meldestelle"
packageBuildVersion = "1.0.0"
packageVersion = "1.0.0"
}
}
buildTypes.release.proguard {
configurationFiles.from(project.file("compose-desktop.pro"))
}
}
}
@@ -1,52 +0,0 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="450dp"
android:height="450dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
android:fillColor="#6075f2"/>
<path
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
android:fillColor="#6b57ff"/>
<path
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
<aapt:attr name="android:fillColor">
<gradient
android:centerX="23.131"
android:centerY="18.441"
android:gradientRadius="42.132"
android:type="radial">
<item
android:offset="0"
android:color="#FF5383EC"/>
<item
android:offset="0.867"
android:color="#FF7F52FF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="44.172"
android:startY="4.377"
android:endX="17.973"
android:endY="34.035"
android:type="linear">
<item
android:offset="0"
android:color="#FF33C3FF"/>
<item
android:offset="0.878"
android:color="#FF5383EC"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
android:fillColor="#000000"/>
</vector>
@@ -1,172 +0,0 @@
package at.mocode
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import at.mocode.ui.PingViewModel
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
@Preview
fun App() {
MaterialTheme {
val viewModel: PingViewModel = viewModel()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Header
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Meldestelle - Ping Service Client",
style = MaterialTheme.typography.headlineMedium
)
Text(
text = "Trace-Bullet Implementation",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.secondary
)
}
}
// Action Buttons
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "API Tests",
style = MaterialTheme.typography.titleMedium
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.simplePing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Simple Ping")
}
Button(
onClick = { viewModel.enhancedPing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Enhanced Ping")
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.healthCheck() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Health Check")
}
Button(
onClick = { viewModel.enhancedPing(simulate = true) },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("Test Failure")
}
}
}
}
// Loading Indicator
if (uiState.isLoading) {
CircularProgressIndicator()
}
// Error Display
uiState.error?.let { error ->
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Text(
text = error,
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onErrorContainer
)
}
}
// Results Display
uiState.lastPingResponse?.let { response ->
ResultCard("Simple Ping Result", response)
}
uiState.lastEnhancedResponse?.let { response ->
ResultCard("Enhanced Ping Result", response)
}
uiState.lastHealthResponse?.let { response ->
ResultCard("Health Check Result", response)
}
}
}
}
@Composable
private fun ResultCard(title: String, data: Any) {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = data.toString(),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.fillMaxWidth()
)
}
}
}
@@ -1,88 +0,0 @@
package at.mocode.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.mocode.model.EnhancedPingResponse
import at.mocode.model.HealthResponse
import at.mocode.model.PingResponse
import at.mocode.service.PingService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
data class PingUiState(
val isLoading: Boolean = false,
val lastPingResponse: PingResponse? = null,
val lastEnhancedResponse: EnhancedPingResponse? = null,
val lastHealthResponse: HealthResponse? = null,
val error: String? = null
)
class PingViewModel : ViewModel() {
private val pingService = PingService()
private val _uiState = MutableStateFlow(PingUiState())
val uiState: StateFlow<PingUiState> = _uiState.asStateFlow()
fun simplePing() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
pingService.ping()
.onSuccess { response ->
_uiState.value = _uiState.value.copy(
isLoading = false,
lastPingResponse = response
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
error = "Ping failed: ${exception.message}"
)
}
}
}
fun enhancedPing(simulate: Boolean = false) {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
pingService.enhancedPing(simulate)
.onSuccess { response ->
_uiState.value = _uiState.value.copy(
isLoading = false,
lastEnhancedResponse = response
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
error = "Enhanced ping failed: ${exception.message}"
)
}
}
}
fun healthCheck() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
pingService.health()
.onSuccess { response ->
_uiState.value = _uiState.value.copy(
isLoading = false,
lastHealthResponse = response
)
}
.onFailure { exception ->
_uiState.value = _uiState.value.copy(
isLoading = false,
error = "Health check failed: ${exception.message}"
)
}
}
}
}
@@ -1,12 +0,0 @@
package at.mocode
import kotlin.test.Test
import kotlin.test.assertEquals
class ComposeAppCommonTest {
@Test
fun example() {
assertEquals(3, 1 + 2)
}
}
@@ -1,21 +0,0 @@
package at.mocode
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Meldestelle",
) {
App()
}
}
@Preview
@Composable
fun AppDesktopPreview() {
App()
}
@@ -1,11 +0,0 @@
package at.mocode
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.ComposeViewport
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
ComposeViewport {
App()
}
}
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meldestelle</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<script type="application/javascript" src="composeApp.js"></script>
</head>
<body>
</body>
</html>
@@ -1,7 +0,0 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
@@ -1,21 +0,0 @@
/*
* Temporary workaround for [KT-80582](https://youtrack.jetbrains.com/issue/KT-80582)
*
* This file should be safe to be removed once the ticket is closed and the project is updated to Kotlin version which solves that issue.
*/
config.watchOptions = config.watchOptions || {
ignored: ["**/*.kt", "**/node_modules"]
}
if (config.devServer) {
config.devServer.static = config.devServer.static.map(file => {
if (typeof file === "string") {
return {
directory: file,
watch: false,
}
} else {
return file
}
})
}
-47
View File
@@ -1,47 +0,0 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
// Configure JVM toolchain for all JVM targets
jvmToolchain(21)
jvm()
js {
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
}
sourceSets {
commonMain.dependencies {
// HTTP Client dependencies for ping-service
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.core)
}
jvmMain.dependencies {
implementation(libs.ktor.client.cio)
}
jsMain.dependencies {
implementation(libs.ktor.client.js)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
@@ -1,3 +0,0 @@
package at.mocode
const val SERVER_PORT = 8081
@@ -1,9 +0,0 @@
package at.mocode
class Greeting {
private val platform = getPlatform()
fun greet(): String {
return "Hello, ${platform.name}!"
}
}
@@ -1,7 +0,0 @@
package at.mocode
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
@@ -1,27 +0,0 @@
package at.mocode.model
import kotlinx.serialization.Serializable
@Serializable
data class PingResponse(
val status: String,
val timestamp: String,
val service: String
)
@Serializable
data class EnhancedPingResponse(
val status: String,
val timestamp: String,
val service: String,
val circuitBreakerState: String? = null,
val responseTime: Long? = null
)
@Serializable
data class HealthResponse(
val status: String,
val timestamp: String,
val service: String,
val healthy: Boolean
)
@@ -1,65 +0,0 @@
package at.mocode.service
import at.mocode.model.EnhancedPingResponse
import at.mocode.model.HealthResponse
import at.mocode.model.PingResponse
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class PingService {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = 10000
connectTimeoutMillis = 5000
}
}
private val baseUrl = getBaseUrl()
suspend fun ping(): Result<PingResponse> = runCatching {
client.get("$baseUrl/ping").body<PingResponse>()
}
suspend fun enhancedPing(simulate: Boolean = false): Result<EnhancedPingResponse> = runCatching {
// Fallback: Use simple ping and enhance response locally
val response = client.get("$baseUrl/ping").body<PingResponse>()
EnhancedPingResponse(
status = response.status,
timestamp = response.timestamp,
service = response.service,
circuitBreakerState = if (simulate) "OPEN" else "CLOSED",
responseTime = 100L
)
}
suspend fun health(): Result<HealthResponse> = runCatching {
// Fallback: Use simple ping to determine health
val response = client.get("$baseUrl/ping").body<PingResponse>()
HealthResponse(
status = response.status,
timestamp = response.timestamp,
service = response.service,
healthy = response.status == "pong"
)
}
suspend fun testFailure(): Result<EnhancedPingResponse> = runCatching {
// Simulate failure for testing
throw RuntimeException("Simulated failure for testing")
}
}
// Platform-specific base URL
expect fun getBaseUrl(): String
@@ -1,12 +0,0 @@
package at.mocode
import kotlin.test.Test
import kotlin.test.assertEquals
class SharedCommonTest {
@Test
fun example() {
assertEquals(3, 1 + 2)
}
}
@@ -1,7 +0,0 @@
package at.mocode
class JsPlatform: Platform {
override val name: String = "Web with Kotlin/JS"
}
actual fun getPlatform(): Platform = JsPlatform()
@@ -1,4 +0,0 @@
package at.mocode.service
// Use direct ping-service for JS Development - based on central.toml
actual fun getBaseUrl(): String = "http://localhost:8082"
@@ -1,7 +0,0 @@
package at.mocode
class JVMPlatform: Platform {
override val name: String = "Java ${System.getProperty("java.version")}"
}
actual fun getPlatform(): Platform = JVMPlatform()
@@ -1,4 +0,0 @@
package at.mocode.service
// Use direct ping-service for JVM (Desktop) - based on central.toml
actual fun getBaseUrl(): String = "http://localhost:8082"
@@ -1,7 +0,0 @@
package at.mocode
class WasmPlatform: Platform {
override val name: String = "Web with Kotlin/Wasm"
}
actual fun getPlatform(): Platform = WasmPlatform()
@@ -1,4 +0,0 @@
package at.mocode.service
// Use direct ping-service for WASM Development - based on central.toml
actual fun getBaseUrl(): String = "http://localhost:8082"