fixing client
This commit is contained in:
@@ -1,49 +1,172 @@
|
|||||||
package at.mocode
|
package at.mocode
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.foundation.layout.safeContentPadding
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import org.jetbrains.compose.resources.painterResource
|
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
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
import at.mocode.composeapp.generated.resources.Res
|
|
||||||
import at.mocode.composeapp.generated.resources.compose_multiplatform
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App() {
|
fun App() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
var showContent by remember { mutableStateOf(false) }
|
val viewModel: PingViewModel = viewModel()
|
||||||
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.safeContentPadding()
|
.fillMaxSize()
|
||||||
.fillMaxSize(),
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Button(onClick = { showContent = !showContent }) {
|
|
||||||
Text("Click me!")
|
// Header
|
||||||
}
|
Card(
|
||||||
AnimatedVisibility(showContent) {
|
modifier = Modifier.fillMaxWidth()
|
||||||
val greeting = remember { Greeting().greet() }
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.padding(16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
Text(
|
||||||
Text("Compose: $greeting")
|
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()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
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}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
|
alias(libs.plugins.kotlinSerialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@@ -22,8 +23,22 @@ kotlin {
|
|||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
// put your Multiplatform dependencies here
|
// 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 {
|
commonTest.dependencies {
|
||||||
implementation(libs.kotlin.test)
|
implementation(libs.kotlin.test)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package at.mocode
|
package at.mocode
|
||||||
|
|
||||||
const val SERVER_PORT = 8080
|
const val SERVER_PORT = 8081
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package at.mocode.service
|
||||||
|
|
||||||
|
// Use direct ping-service for JS Development - based on central.toml
|
||||||
|
actual fun getBaseUrl(): String = "http://localhost:8082"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package at.mocode.service
|
||||||
|
|
||||||
|
// Use direct ping-service for JVM (Desktop) - based on central.toml
|
||||||
|
actual fun getBaseUrl(): String = "http://localhost:8082"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package at.mocode.service
|
||||||
|
|
||||||
|
// Use direct ping-service for WASM Development - based on central.toml
|
||||||
|
actual fun getBaseUrl(): String = "http://localhost:8082"
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
package at.mocode.temp.pingservice
|
package at.mocode.temp.pingservice
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@CrossOrigin(
|
||||||
|
origins = ["http://localhost:8080", "http://localhost:8083", "http://localhost:4000"],
|
||||||
|
methods = [RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS],
|
||||||
|
allowedHeaders = ["*"],
|
||||||
|
allowCredentials = "true"
|
||||||
|
)
|
||||||
class PingController(
|
class PingController(
|
||||||
private val pingService: PingServiceCircuitBreaker
|
private val pingService: PingServiceCircuitBreaker
|
||||||
) {
|
) {
|
||||||
|
|||||||
+20
-1
@@ -2,11 +2,30 @@ package at.mocode.temp.pingservice
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.EnableAspectJAutoProxy
|
import org.springframework.context.annotation.EnableAspectJAutoProxy
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableAspectJAutoProxy
|
@EnableAspectJAutoProxy
|
||||||
class PingServiceApplication
|
class PingServiceApplication {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun corsConfigurer(): WebMvcConfigurer {
|
||||||
|
return object : WebMvcConfigurer {
|
||||||
|
override fun addCorsMappings(registry: CorsRegistry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedOriginPatterns("http://localhost:*")
|
||||||
|
.allowedOrigins("http://localhost:8080", "http://localhost:8083", "http://localhost:4000")
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders("*")
|
||||||
|
.allowCredentials(true)
|
||||||
|
.maxAge(3600)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
runApplication<PingServiceApplication>(*args)
|
runApplication<PingServiceApplication>(*args)
|
||||||
|
|||||||
Reference in New Issue
Block a user