feature clients

new frontend
This commit is contained in:
stefan
2025-09-25 13:03:15 +02:00
parent b8c008ddba
commit 0cc25cb108
32 changed files with 769 additions and 156 deletions
@@ -0,0 +1,41 @@
package at.mocode.clients.pingfeature
import at.mocode.ping.api.PingApi
import at.mocode.ping.api.PingResponse
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
class PingApiClient(
private val baseUrl: String = "http://localhost:8080"
) : PingApi {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
override suspend fun simplePing(): PingResponse {
return client.get("$baseUrl/api/ping/simple").body()
}
override suspend fun enhancedPing(simulate: Boolean): EnhancedPingResponse {
return client.get("$baseUrl/api/ping/enhanced") {
parameter("simulate", simulate)
}.body()
}
override suspend fun healthCheck(): HealthResponse {
return client.get("$baseUrl/api/ping/health").body()
}
}
@@ -0,0 +1,187 @@
package at.mocode.clients.pingfeature
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun PingScreen(viewModel: PingViewModel) {
val uiState = viewModel.uiState
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Ping Service",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
// Action Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { viewModel.performSimplePing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Simple Ping")
}
Button(
onClick = { viewModel.performEnhancedPing() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Enhanced Ping")
}
Button(
onClick = { viewModel.performHealthCheck() },
enabled = !uiState.isLoading,
modifier = Modifier.weight(1f)
) {
Text("Health Check")
}
}
// Loading indicator
if (uiState.isLoading) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
// Error message
uiState.errorMessage?.let { error ->
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "Error",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = error,
color = MaterialTheme.colorScheme.onErrorContainer
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { viewModel.clearError() }
) {
Text("Dismiss")
}
}
}
}
// Simple Ping Response
uiState.simplePingResponse?.let { response ->
ResponseCard(
title = "Simple Ping Response",
status = response.status,
timestamp = response.timestamp,
service = response.service
)
}
// Enhanced Ping Response
uiState.enhancedPingResponse?.let { response ->
ResponseCard(
title = "Enhanced Ping Response",
status = response.status,
timestamp = response.timestamp,
service = response.service,
additionalInfo = mapOf(
"Circuit Breaker State" to response.circuitBreakerState,
"Response Time" to "${response.responseTime}ms"
)
)
}
// Health Response
uiState.healthResponse?.let { response ->
ResponseCard(
title = "Health Check Response",
status = response.status,
timestamp = response.timestamp,
service = response.service,
additionalInfo = mapOf(
"Healthy" to response.healthy.toString()
)
)
}
}
}
@Composable
private fun ResponseCard(
title: String,
status: String,
timestamp: String,
service: String,
additionalInfo: Map<String, String> = emptyMap()
) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
InfoRow("Status", status)
InfoRow("Timestamp", timestamp)
InfoRow("Service", service)
additionalInfo.forEach { (key, value) ->
InfoRow(key, value)
}
}
}
}
@Composable
private fun InfoRow(label: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "$label:",
fontWeight = FontWeight.Medium
)
Text(text = value)
}
}
@@ -0,0 +1,82 @@
package at.mocode.clients.pingfeature
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.mocode.ping.api.PingResponse
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import kotlinx.coroutines.launch
data class PingUiState(
val isLoading: Boolean = false,
val simplePingResponse: PingResponse? = null,
val enhancedPingResponse: EnhancedPingResponse? = null,
val healthResponse: HealthResponse? = null,
val errorMessage: String? = null
)
class PingViewModel : ViewModel() {
private val apiClient = PingApiClient()
var uiState by mutableStateOf(PingUiState())
private set
fun performSimplePing() {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.simplePing()
uiState = uiState.copy(
isLoading = false,
simplePingResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Simple ping failed: ${e.message}"
)
}
}
}
fun performEnhancedPing(simulate: Boolean = false) {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.enhancedPing(simulate)
uiState = uiState.copy(
isLoading = false,
enhancedPingResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Enhanced ping failed: ${e.message}"
)
}
}
}
fun performHealthCheck() {
viewModelScope.launch {
uiState = uiState.copy(isLoading = true, errorMessage = null)
try {
val response = apiClient.healthCheck()
uiState = uiState.copy(
isLoading = false,
healthResponse = response
)
} catch (e: Exception) {
uiState = uiState.copy(
isLoading = false,
errorMessage = "Health check failed: ${e.message}"
)
}
}
}
fun clearError() {
uiState = uiState.copy(errorMessage = null)
}
}