This commit is contained in:
Hy4ri committed 2026-06-16 00:52:36 +03:00
1 parent 0562906634
commit 8ca4599c6a
14 files changed
+362 -166

No files matched your search

@@ -15,7 +15,11 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
@@ -52,7 +56,7 @@ fun KeysScreen(
val context = LocalContext.current
LaunchedEffect(Unit) {
viewModel.loadKeys(reveal = false)
viewModel.loadKeys()
}
LaunchedEffect(state.toastMessage) {
@@ -77,8 +81,11 @@ fun KeysScreen(
},
title = { Text("Keys & Credentials") },
actions = {
Button(onClick = { viewModel.loadKeys(reveal = !state.isRevealed) }) {
Text(if (state.isRevealed) "Hide Values" else "Reveal Values")
IconButton(onClick = { viewModel.loadKeys() }) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = "Refresh",
)
}
},
)
@@ -108,9 +115,20 @@ fun KeysScreen(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(state.envVars.toList()) { (key, value) ->
items(state.envVars.toList()) { (key, config) ->
var isEditing by remember { mutableStateOf(false) }
var editedValue by remember { mutableStateOf(value) }
val revealedVal = state.revealedValues[key]
val isRevealed = revealedVal != null
var editedValue by remember { mutableStateOf(revealedVal ?: "") }
val displayValue =
if (isRevealed) {
revealedVal.orEmpty()
} else if (config.isSet) {
config.redactedValue ?: "********"
} else {
"Not configured"
}
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
@@ -119,6 +137,14 @@ fun KeysScreen(
style = MaterialTheme.typography.titleMedium,
fontFamily = FontFamily.Monospace,
)
if (!config.description.isNullOrBlank()) {
Text(
text = config.description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
modifier = Modifier.padding(vertical = 4.dp),
)
}
Spacer(modifier = Modifier.height(4.dp))
if (isEditing) {
OutlinedTextField(
@@ -141,7 +167,6 @@ fun KeysScreen(
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
isEditing = false
editedValue = value
}) {
Text("Cancel")
}
@@ -153,13 +178,36 @@ fun KeysScreen(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = value,
text = displayValue,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontFamily = FontFamily.Monospace,
modifier = Modifier.weight(1f),
)
IconButton(onClick = { isEditing = true }) {
Text("Edit", style = MaterialTheme.typography.bodySmall)
Row {
if (config.isSet) {
IconButton(onClick = {
if (isRevealed) {
viewModel.hideKey(key)
} else {
viewModel.revealKey(key)
}
}) {
Icon(
imageVector = if (isRevealed) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
contentDescription = "Toggle Visibility",
)
}
}
IconButton(onClick = {
editedValue = if (isRevealed) revealedVal.orEmpty() else ""
isEditing = true
}) {
Icon(
imageVector = Icons.Filled.Edit,
contentDescription = "Edit",
)
}
}
}
}
@@ -2,6 +2,9 @@ package com.m57.hermescontrol.ui.keys
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.m57.hermescontrol.data.model.EnvVarConfig
import com.m57.hermescontrol.data.model.EnvVarRevealRequest
import com.m57.hermescontrol.data.model.EnvVarUpdate
import com.m57.hermescontrol.data.remote.ApiClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -13,8 +16,8 @@ import kotlinx.coroutines.withContext
data class KeysUiState(
val isLoading: Boolean = false,
val envVars: Map<String, String> = emptyMap(),
val isRevealed: Boolean = false,
val envVars: Map<String, EnvVarConfig> = emptyMap(),
val revealedValues: Map<String, String> = emptyMap(),
val errorMessage: String? = null,
val toastMessage: String? = null,
)
@@ -29,18 +32,13 @@ class KeysViewModel : ViewModel() {
try {
val response =
withContext(Dispatchers.IO) {
if (reveal) {
ApiClient.hermesApi.revealEnvVars()
} else {
ApiClient.hermesApi.getEnvVars()
}
ApiClient.hermesApi.getEnvVars()
}
if (response.isSuccessful) {
_uiState.update {
it.copy(
isLoading = false,
envVars = response.body().orEmpty(),
isRevealed = reveal,
)
}
} else {
@@ -57,23 +55,50 @@ class KeysViewModel : ViewModel() {
}
}
fun revealKey(key: String) {
viewModelScope.launch {
try {
val response =
withContext(Dispatchers.IO) {
ApiClient.hermesApi.revealEnvVar(EnvVarRevealRequest(key))
}
if (response.isSuccessful && response.body() != null) {
val body = response.body()!!
_uiState.update { state ->
state.copy(
revealedValues = state.revealedValues + (body.key to body.value),
)
}
} else {
_uiState.update { it.copy(toastMessage = "Failed to reveal key: HTTP ${response.code()}") }
}
} catch (e: Exception) {
_uiState.update { it.copy(toastMessage = "Failed to reveal key: ${e.message}") }
}
}
}
fun hideKey(key: String) {
_uiState.update { state ->
state.copy(
revealedValues = state.revealedValues.toMutableMap().apply { remove(key) },
)
}
}
fun updateKey(
key: String,
value: String,
) {
viewModelScope.launch {
try {
val updatedVars =
_uiState.value.envVars.toMutableMap().apply {
put(key, value)
}
val response =
withContext(Dispatchers.IO) {
ApiClient.hermesApi.updateEnvVar(updatedVars)
ApiClient.hermesApi.updateEnvVar(EnvVarUpdate(key, value))
}
if (response.isSuccessful) {
_uiState.update { it.copy(toastMessage = "Key updated successfully") }
loadKeys(_uiState.value.isRevealed)
loadKeys()
} else {
_uiState.update { it.copy(toastMessage = "Failed to update key: HTTP ${response.code()}") }
}