feat(Tracer Bullet)
This commit is contained in:
@@ -57,7 +57,6 @@ kotlin {
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,22 @@ import at.mocode.client.common.components.horses.PferdeListe
|
||||
import at.mocode.client.common.components.masterdata.StammdatenListe
|
||||
import at.mocode.client.web.screens.CreatePersonScreen
|
||||
import at.mocode.client.web.screens.PersonListScreen
|
||||
import at.mocode.client.web.viewmodel.CreatePersonViewModel
|
||||
import at.mocode.client.web.viewmodel.PersonListViewModel
|
||||
import at.mocode.core.domain.model.DatenQuelleE
|
||||
import at.mocode.core.domain.model.PferdeGeschlechtE
|
||||
import at.mocode.events.domain.model.Veranstaltung
|
||||
import at.mocode.horses.domain.model.DomPferd
|
||||
import at.mocode.masterdata.domain.model.LandDefinition
|
||||
import kotlinx.datetime.Clock
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.auth.*
|
||||
import io.ktor.client.plugins.auth.providers.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Main application composable for the desktop application.
|
||||
@@ -103,6 +110,10 @@ data class TabItem(
|
||||
*/
|
||||
@Composable
|
||||
fun DashboardScreen() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var pingResult by remember { mutableStateOf<String?>(null) }
|
||||
var pingLoading by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@@ -120,6 +131,15 @@ fun DashboardScreen() {
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
// Display ping result if available
|
||||
pingResult?.let { result ->
|
||||
Text(
|
||||
text = "Ping Result: $result",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Quick access buttons
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -144,6 +164,47 @@ fun DashboardScreen() {
|
||||
Text("Suche")
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pingLoading = true
|
||||
try {
|
||||
val pingClient = HttpClient(CIO) {
|
||||
install(ContentNegotiation) {
|
||||
json(Json { ignoreUnknownKeys = true })
|
||||
}
|
||||
install(Auth) {
|
||||
basic {
|
||||
credentials {
|
||||
BasicAuthCredentials(username = "admin", password = "admin")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val response: Map<String, String> = pingClient.get("http://localhost:8080/api/ping").body()
|
||||
pingResult = response["status"] ?: "No status in response"
|
||||
|
||||
pingClient.close()
|
||||
} catch (e: Exception) {
|
||||
pingResult = "Error: ${e.message}"
|
||||
} finally {
|
||||
pingLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !pingLoading
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
if (pingLoading) Icons.Default.Refresh else Icons.Default.NetworkCheck,
|
||||
contentDescription = "Ping Test"
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(if (pingLoading) "Pinging..." else "Ping Test")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ kotlin {
|
||||
|
||||
// Stellt die Web-spezifischen (HTML) Teile von Jetpack Compose bereit.
|
||||
implementation(compose.html.core)
|
||||
|
||||
// HTTP client for making requests to the backend
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.ktor.client.js)
|
||||
implementation(libs.ktor.client.contentNegotiation)
|
||||
implementation(libs.ktor.client.serialization.kotlinx.json)
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
|
||||
@@ -1,36 +1,173 @@
|
||||
package at.mocode.client.web
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.client.common.BaseApp
|
||||
import androidx.compose.runtime.*
|
||||
import org.jetbrains.compose.web.dom.*
|
||||
import org.jetbrains.compose.web.css.*
|
||||
import kotlinx.coroutines.launch
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(val status: String)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun App() {
|
||||
BaseApp {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Meldestelle - Reitersport Management") }
|
||||
)
|
||||
var responseStatus by remember { mutableStateOf<String?>(null) }
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val httpClient = remember {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json { ignoreUnknownKeys = true })
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues).fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
// Placeholder content
|
||||
Text("Welcome to Meldestelle - Reitersport Management")
|
||||
Text("This is a desktop application for managing equestrian events")
|
||||
}
|
||||
}
|
||||
|
||||
Div({
|
||||
style {
|
||||
fontFamily("Arial, sans-serif")
|
||||
padding(20.px)
|
||||
maxWidth(800.px)
|
||||
margin("0 auto")
|
||||
}
|
||||
}) {
|
||||
H1({
|
||||
style {
|
||||
color(Color.darkblue)
|
||||
textAlign("center")
|
||||
marginBottom(30.px)
|
||||
}
|
||||
}) {
|
||||
Text("Meldestelle - Reitersport Management")
|
||||
}
|
||||
|
||||
Div({
|
||||
style {
|
||||
textAlign("center")
|
||||
marginBottom(20.px)
|
||||
}
|
||||
}) {
|
||||
P { Text("Welcome to the Meldestelle Web Application") }
|
||||
P { Text("Click the button below to test the backend connection") }
|
||||
}
|
||||
|
||||
Div({
|
||||
style {
|
||||
textAlign("center")
|
||||
marginBottom(20.px)
|
||||
}
|
||||
}) {
|
||||
Button({
|
||||
style {
|
||||
backgroundColor(Color.lightblue)
|
||||
color(Color.white)
|
||||
border(0.px)
|
||||
padding(10.px, 20.px)
|
||||
fontSize(16.px)
|
||||
cursor("pointer")
|
||||
borderRadius(5.px)
|
||||
}
|
||||
onClick {
|
||||
scope.launch {
|
||||
try {
|
||||
isLoading = true
|
||||
errorMessage = null
|
||||
responseStatus = null
|
||||
|
||||
// Try different potential gateway URLs with correct routing
|
||||
val gatewayUrls = listOf(
|
||||
"http://localhost:8080/api/ping/ping", // Correct gateway path
|
||||
"http://localhost:8080/ping", // Direct service call (fallback)
|
||||
"http://localhost:8081/api/ping/ping" // Alternative gateway port
|
||||
)
|
||||
|
||||
var success = false
|
||||
for (url in gatewayUrls) {
|
||||
try {
|
||||
val response: HttpResponse = httpClient.get(url)
|
||||
val responseText = response.bodyAsText()
|
||||
|
||||
// Try to parse as JSON first
|
||||
try {
|
||||
val pingResponse = Json.decodeFromString<PingResponse>(responseText)
|
||||
responseStatus = pingResponse.status
|
||||
success = true
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
// If JSON parsing fails, use the raw response
|
||||
responseStatus = responseText
|
||||
success = true
|
||||
break
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Continue to next URL
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
errorMessage = "Could not reach any backend service. Please ensure the backend is running."
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
errorMessage = "Error: ${e.message}"
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
disabled(isLoading)
|
||||
}) {
|
||||
Text(if (isLoading) "Loading..." else "Ping Backend")
|
||||
}
|
||||
}
|
||||
|
||||
// Response display area
|
||||
Div({
|
||||
style {
|
||||
textAlign("center")
|
||||
marginTop(20.px)
|
||||
minHeight(100.px)
|
||||
border(1.px, LineStyle.Solid, Color.lightgray)
|
||||
borderRadius(5.px)
|
||||
padding(20.px)
|
||||
backgroundColor(Color.lightyellow)
|
||||
}
|
||||
}) {
|
||||
when {
|
||||
isLoading -> {
|
||||
P { Text("Sending request to backend...") }
|
||||
}
|
||||
errorMessage != null -> {
|
||||
P({
|
||||
style {
|
||||
color(Color.red)
|
||||
fontWeight("bold")
|
||||
}
|
||||
}) {
|
||||
Text(errorMessage!!)
|
||||
}
|
||||
}
|
||||
responseStatus != null -> {
|
||||
P({
|
||||
style {
|
||||
color(Color.green)
|
||||
fontWeight("bold")
|
||||
fontSize(18.px)
|
||||
}
|
||||
}) {
|
||||
Text("Backend Response: $responseStatus")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
P { Text("Click the button above to test backend connection") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package at.mocode.client.web
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import org.jetbrains.compose.web.renderComposable
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
title = "Meldestelle - Reitersport Management",
|
||||
onCloseRequest = ::exitApplication
|
||||
) {
|
||||
fun main() {
|
||||
renderComposable(rootElementId = "root") {
|
||||
App()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Meldestelle - Reitersport Management</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="MeldestelleWebApp.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user