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 com.parseable.android.data.formatBytes
22import kotlinx.serialization.json.Json
23import kotlinx.serialization.json.JsonObject
25private val prettyJson = Json { prettyPrint = true }
27@OptIn(ExperimentalMaterial3Api::class)
32 onStreamDeleted: () -> Unit,
33 viewModel: StreamInfoViewModel = hiltViewModel(),
35 val state by viewModel.state.collectAsStateWithLifecycle()
36 var showDeleteConfirmation by remember { mutableStateOf(false) }
38 val lifecycleOwner = LocalLifecycleOwner.current
39 LaunchedEffect(streamName, lifecycleOwner) {
40 lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
41 viewModel.load(streamName)
45 LaunchedEffect(state.deleteSuccess) {
46 if (state.deleteSuccess) {
47 viewModel.consumeDeleteSuccess()
55 title = { Text("Stream: $streamName") },
57 IconButton(onClick = onBack) {
58 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
63 onClick = { showDeleteConfirmation = true },
64 enabled = !state.isDeleting && !state.isLoading,
68 contentDescription = "Delete stream",
69 tint = MaterialTheme.colorScheme.error,
76 if (state.isLoading) {
81 contentAlignment = Alignment.Center,
83 CircularProgressIndicator()
90 .verticalScroll(rememberScrollState())
92 verticalArrangement = Arrangement.spacedBy(16.dp),
95 state.stats?.let { stats ->
96 InfoSection(title = "Statistics") {
97 InfoRow("Event Count", stats.ingestion?.count?.toString() ?: stats.ingestion?.lifetimeCount?.toString() ?: "N/A")
98 InfoRow("Ingestion Size", formatBytes(stats.ingestion?.size) ?: formatBytes(stats.ingestion?.lifetimeSize) ?: "N/A")
99 InfoRow("Storage Size", formatBytes(stats.storage?.size) ?: formatBytes(stats.storage?.lifetimeSize) ?: "N/A")
100 if (stats.ingestion?.lifetimeCount != null) {
101 InfoRow("Lifetime Events", stats.ingestion.lifetimeCount.toString())
103 if (stats.ingestion?.deletedCount != null) {
104 InfoRow("Deleted Events", stats.ingestion.deletedCount.toString())
110 if (state.schema.isNotEmpty()) {
111 InfoSection(title = "Schema (${state.schema.size} fields)") {
112 state.schema.forEach { field ->
115 field.dataType?.toString()?.trim('"') ?: "Unknown",
119 } else if (state.schemaFailed) {
120 InfoSection(title = "Schema") {
122 text = "Schema unavailable",
123 style = MaterialTheme.typography.bodyMedium,
124 color = MaterialTheme.colorScheme.onSurfaceVariant,
130 if (state.retention.isNotEmpty()) {
131 InfoSection(title = "Retention") {
132 state.retention.forEach { r ->
133 InfoRow("Duration", r.duration ?: "N/A")
134 InfoRow("Action", r.action ?: "N/A")
135 if (r.description != null) {
136 InfoRow("Description", r.description)
143 state.rawInfo?.let { rawInfo ->
144 val prettyInfo = remember(rawInfo) {
145 prettyJson.encodeToString(JsonObject.serializer(), rawInfo)
147 InfoSection(title = "Stream Info") {
150 style = MaterialTheme.typography.bodySmall,
151 fontFamily = FontFamily.Monospace,
156 state.error?.let { errorText ->
158 colors = CardDefaults.cardColors(
159 containerColor = MaterialTheme.colorScheme.errorContainer,
164 modifier = Modifier.padding(16.dp),
165 color = MaterialTheme.colorScheme.onErrorContainer,
173 if (showDeleteConfirmation) {
175 onDismissRequest = { showDeleteConfirmation = false },
178 Icons.Filled.Warning,
179 contentDescription = "Warning",
180 tint = MaterialTheme.colorScheme.error,
183 title = { Text("Delete Stream") },
186 "Are you sure you want to delete \"$streamName\"? " +
187 "This will permanently remove all log data and cannot be undone."
193 showDeleteConfirmation = false
194 viewModel.deleteStream()
196 colors = ButtonDefaults.buttonColors(
197 containerColor = MaterialTheme.colorScheme.error,
204 TextButton(onClick = { showDeleteConfirmation = false }) {
215 content: @Composable ColumnScope.() -> Unit,
218 modifier = Modifier.fillMaxWidth(),
219 elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
221 Column(modifier = Modifier.padding(16.dp)) {
224 style = MaterialTheme.typography.titleMedium,
225 fontWeight = FontWeight.Bold,
227 Spacer(modifier = Modifier.height(12.dp))
234fun InfoRow(label: String, value: String) {
238 .padding(vertical = 4.dp),
239 horizontalArrangement = Arrangement.SpaceBetween,
243 style = MaterialTheme.typography.bodyMedium,
244 color = MaterialTheme.colorScheme.onSurfaceVariant,
245 modifier = Modifier.weight(1f),
249 style = MaterialTheme.typography.bodyMedium,
250 fontWeight = FontWeight.Medium,
251 modifier = Modifier.weight(1f),
252 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