1package com.parseable.android.ui.screens.logviewer
3import androidx.compose.foundation.layout.*
4import androidx.compose.foundation.rememberScrollState
5import androidx.compose.foundation.verticalScroll
6import androidx.compose.material.icons.Icons
7import androidx.compose.material.icons.automirrored.filled.ArrowBack
8import androidx.compose.material.icons.filled.*
9import androidx.compose.material3.*
10import androidx.compose.runtime.*
11import androidx.compose.ui.Alignment
12import androidx.compose.ui.Modifier
13import androidx.compose.ui.text.font.FontFamily
14import androidx.compose.ui.text.font.FontWeight
15import androidx.compose.ui.unit.dp
16import androidx.hilt.navigation.compose.hiltViewModel
17import androidx.lifecycle.Lifecycle
18import androidx.lifecycle.compose.LocalLifecycleOwner
19import androidx.lifecycle.compose.collectAsStateWithLifecycle
20import androidx.lifecycle.repeatOnLifecycle
21import kotlinx.serialization.json.Json
22import kotlinx.serialization.json.JsonObject
24private val prettyJson = Json { prettyPrint = true }
26@OptIn(ExperimentalMaterial3Api::class)
31 onStreamDeleted: () -> Unit,
32 viewModel: StreamInfoViewModel = hiltViewModel(),
34 val state by viewModel.state.collectAsStateWithLifecycle()
35 var showDeleteConfirmation by remember { mutableStateOf(false) }
37 val lifecycleOwner = LocalLifecycleOwner.current
38 LaunchedEffect(streamName, lifecycleOwner) {
39 lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
40 viewModel.load(streamName)
44 LaunchedEffect(state.deleteSuccess) {
45 if (state.deleteSuccess) {
46 viewModel.consumeDeleteSuccess()
54 title = { Text("Stream: $streamName") },
56 IconButton(onClick = onBack) {
57 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
62 onClick = { showDeleteConfirmation = true },
63 enabled = !state.isDeleting && !state.isLoading,
67 contentDescription = "Delete stream",
68 tint = MaterialTheme.colorScheme.error,
75 if (state.isLoading) {
80 contentAlignment = Alignment.Center,
82 CircularProgressIndicator()
89 .verticalScroll(rememberScrollState())
91 verticalArrangement = Arrangement.spacedBy(16.dp),
94 state.stats?.let { stats ->
95 InfoSection(title = "Statistics") {
96 InfoRow("Event Count", stats.ingestion?.count?.toString() ?: stats.ingestion?.lifetimeCount?.toString() ?: "N/A")
97 InfoRow("Ingestion Size", stats.ingestion?.size ?: stats.ingestion?.lifetimeSize ?: "N/A")
98 InfoRow("Storage Size", stats.storage?.size ?: stats.storage?.lifetimeSize ?: "N/A")
99 if (stats.ingestion?.lifetimeCount != null) {
100 InfoRow("Lifetime Events", stats.ingestion.lifetimeCount.toString())
102 if (stats.ingestion?.deletedCount != null) {
103 InfoRow("Deleted Events", stats.ingestion.deletedCount.toString())
109 if (state.schema.isNotEmpty()) {
110 InfoSection(title = "Schema (${state.schema.size} fields)") {
111 state.schema.forEach { field ->
114 field.dataType?.toString()?.trim('"') ?: "Unknown",
118 } else if (state.schemaFailed) {
119 InfoSection(title = "Schema") {
121 text = "Schema unavailable",
122 style = MaterialTheme.typography.bodyMedium,
123 color = MaterialTheme.colorScheme.onSurfaceVariant,
129 if (state.retention.isNotEmpty()) {
130 InfoSection(title = "Retention") {
131 state.retention.forEach { r ->
132 InfoRow("Duration", r.duration ?: "N/A")
133 InfoRow("Action", r.action ?: "N/A")
134 if (r.description != null) {
135 InfoRow("Description", r.description)
142 state.rawInfo?.let { rawInfo ->
143 val prettyInfo = remember(rawInfo) {
144 prettyJson.encodeToString(JsonObject.serializer(), rawInfo)
146 InfoSection(title = "Stream Info") {
149 style = MaterialTheme.typography.bodySmall,
150 fontFamily = FontFamily.Monospace,
155 state.error?.let { errorText ->
157 colors = CardDefaults.cardColors(
158 containerColor = MaterialTheme.colorScheme.errorContainer,
163 modifier = Modifier.padding(16.dp),
164 color = MaterialTheme.colorScheme.onErrorContainer,
172 if (showDeleteConfirmation) {
174 onDismissRequest = { showDeleteConfirmation = false },
177 Icons.Filled.Warning,
178 contentDescription = "Warning",
179 tint = MaterialTheme.colorScheme.error,
182 title = { Text("Delete Stream") },
185 "Are you sure you want to delete \"$streamName\"? " +
186 "This will permanently remove all log data and cannot be undone."
192 showDeleteConfirmation = false
193 viewModel.deleteStream()
195 colors = ButtonDefaults.buttonColors(
196 containerColor = MaterialTheme.colorScheme.error,
203 TextButton(onClick = { showDeleteConfirmation = false }) {
214 content: @Composable ColumnScope.() -> Unit,
217 modifier = Modifier.fillMaxWidth(),
218 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
220 Column(modifier = Modifier.padding(16.dp)) {
223 style = MaterialTheme.typography.titleMedium,
224 fontWeight = FontWeight.Bold,
226 Spacer(modifier = Modifier.height(12.dp))
233fun InfoRow(label: String, value: String) {
237 .padding(vertical = 4.dp),
238 horizontalArrangement = Arrangement.SpaceBetween,
242 style = MaterialTheme.typography.bodyMedium,
243 color = MaterialTheme.colorScheme.onSurfaceVariant,
244 modifier = Modifier.weight(1f),
248 style = MaterialTheme.typography.bodyMedium,
249 fontWeight = FontWeight.Medium,
250 modifier = Modifier.weight(1f),
251 fontFamily = FontFamily.Monospace,
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list