3 Commits
Author SHA1 Message Date
jaytux 126a3bfff2 Clients: use http->ws, https->wss 2026-09-01 15:17:39 +02:00
jaytux 563ee98d03 ANSI update 2026-09-01 14:58:17 +02:00
jaytux 7f92df1ca5 CLI: fix keyring backend bug 2026-09-01 14:09:37 +02:00
10 changed files with 37 additions and 27 deletions
+1 -1
View File
@@ -14,4 +14,4 @@ repositories {
mavenCentral() mavenCentral()
} }
version = PhoebenchVersion(1, 2, 0, "") version = PhoebenchVersion(1, 2, 1, "")
+3 -1
View File
@@ -80,7 +80,9 @@ kotlin {
} }
tasks.withType<ShadowJar> { tasks.withType<ShadowJar> {
archiveFileName = if(isDebug) "phoebench-cli-debug.jar" else "phoebench-cli.jar" val archive = if(isDebug) "phoebench-cli-debug.jar" else "phoebench-cli.jar"
println("Using archive '$archive'")
archiveFileName = archive
} }
buildConfig { buildConfig {
@@ -6,16 +6,21 @@ import kotlin.system.exitProcess
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
object AuthHandlers { object AuthHandlers {
suspend fun checkServer() =
Client.callRoute(Routes.handshake, EmptyRequest()).bind { handshake ->
if(handshake.version != ProtocolVersion.VERSION) {
val server = Client.getServer() ?: "<unknown server ULR>"
Client.onClearServer()
ErrorResponse("Cannot use $server: protocol version mismatch (server: ${handshake.version}, expected: ${ProtocolVersion.VERSION})").error()
}
Unit.value()
}
fun serverPrompt(server: String?): Either<ErrorResponse, Unit> { fun serverPrompt(server: String?): Either<ErrorResponse, Unit> {
val useServer = server.maybePrompt("server") { it } val useServer = server.maybePrompt("server") { it }
Client.onSelectServer(useServer) Client.onSelectServer(useServer)
return runBlocking { return runBlocking {
Client.callRoute(Routes.handshake, EmptyRequest()).bind { handshake -> checkServer().bind { Client.onSelectServer(useServer).value() }
if (handshake.version != ProtocolVersion.VERSION) {
Client.onClearServer()
ErrorResponse("Mismatched protocol version (server: ${handshake.version}, client: ${ProtocolVersion.VERSION})").error()
} else Unit.value()
}
} }
} }
@@ -2,6 +2,7 @@ package com.jaytux.phoebench.clients.cli
import com.github.javakeyring.BackendNotSupportedException import com.github.javakeyring.BackendNotSupportedException
import com.github.javakeyring.Keyring import com.github.javakeyring.Keyring
import com.github.javakeyring.PasswordAccessException
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
@@ -22,7 +23,11 @@ object PersistentStorage {
private object KeyringBackend : IBackend { private object KeyringBackend : IBackend {
override fun read(key: String): String? = try { override fun read(key: String): String? = try {
Keyring.create().use { it.getPassword(SERVICE, key) } Keyring.create().use { it.getPassword(SERVICE, key) }
} catch(e: Exception) { }
catch(e: PasswordAccessException) {
return null
}
catch(e: BackendNotSupportedException) {
_backend = PreferencesBackend _backend = PreferencesBackend
_backend.read(key) _backend.read(key)
} }
@@ -25,6 +25,7 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -237,10 +238,12 @@ fun SignupView(server: String?, error: String?, onSwitchLogin: () -> Unit, onSig
@Composable @Composable
fun AuthenticatedRoot() { fun AuthenticatedRoot() {
val snacks = SnackProvider.get() val snacks = SnackProvider.get()
val auth = AuthProvider.get()
val state = remember { SnackbarHostState() } val state = remember { SnackbarHostState() }
val vm = viewModel { HomeVM() } val vm = viewModel { HomeVM() }
var loggingOut by remember { mutableStateOf(false) } var loggingOut by remember { mutableStateOf(false) }
var currentProject by remember { mutableStateOf<Uuid?>(null) } var currentProject by remember { mutableStateOf<Uuid?>(null) }
val server by auth.server
LaunchedEffect(snacks) { LaunchedEffect(snacks) {
snacks.snacks.collect { state.showSnackbar(it) } snacks.snacks.collect { state.showSnackbar(it) }
@@ -254,7 +257,12 @@ fun AuthenticatedRoot() {
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { Text("PhoeBench v${ClientVersion.VERSION}", style = MaterialTheme.typography.headlineLarge) }, title = {
Column {
Text("PhoeBench v${ClientVersion.VERSION}", style = MaterialTheme.typography.headlineLarge)
server?.let { Text("Connected to $it", style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(0.6f)) }
}
},
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer, containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
@@ -133,24 +133,20 @@ fun MonitorMessage(msg: ServerMonitorEvent.ITextEvent) = when(msg) {
val weight = if(ANSI.BOLD in ansi) FontWeight.Bold else null val weight = if(ANSI.BOLD in ansi) FontWeight.Bold else null
val decoration = if(ANSI.UNDERLINE in ansi) TextDecoration.Underline else null val decoration = if(ANSI.UNDERLINE in ansi) TextDecoration.Underline else null
val color = when { val color = when {
ANSI.FG_BLACK in ansi -> Color.Black
ANSI.FG_RED in ansi -> Color.Red ANSI.FG_RED in ansi -> Color.Red
ANSI.FG_GREEN in ansi -> Color.Green ANSI.FG_GREEN in ansi -> Color.Green
ANSI.FG_YELLOW in ansi -> Color.Yellow ANSI.FG_YELLOW in ansi -> Color.Yellow
ANSI.FG_BLUE in ansi -> Color.Blue ANSI.FG_BLUE in ansi -> Color.Blue
ANSI.FG_MAGENTA in ansi -> Color.Magenta ANSI.FG_GRAY in ansi -> Color.Gray
ANSI.FG_CYAN in ansi -> Color.Cyan
else -> LocalContentColor.current else -> LocalContentColor.current
} }
val background = when { val background = when {
ANSI.BG_BLACK in ansi -> Color.Black
ANSI.BG_RED in ansi -> Color.Red ANSI.BG_RED in ansi -> Color.Red
ANSI.BG_GREEN in ansi -> Color.Green ANSI.BG_GREEN in ansi -> Color.Green
ANSI.BG_YELLOW in ansi -> Color.Yellow ANSI.BG_YELLOW in ansi -> Color.Yellow
ANSI.BG_BLUE in ansi -> Color.Blue ANSI.BG_BLUE in ansi -> Color.Blue
ANSI.BG_MAGENTA in ansi -> Color.Magenta ANSI.BG_GRAY in ansi -> Color.Gray
ANSI.BG_CYAN in ansi -> Color.Cyan
else -> null else -> null
}?.let { Modifier.background(it) } ?: Modifier }?.let { Modifier.background(it) } ?: Modifier
@@ -15,7 +15,7 @@ sealed class CSERoute<TParams, TEvent: Any>(val path: String, val elevation: Ele
abstract fun extractParams(reqParams: Parameters): TParams? abstract fun extractParams(reqParams: Parameters): TParams?
suspend fun call(client: IClient, params: TParams, body: suspend (sender: suspend (TEvent) -> Unit) -> Unit): Either<ErrorResponse, Unit> { suspend fun call(client: IClient, params: TParams, body: suspend (sender: suspend (TEvent) -> Unit) -> Unit): Either<ErrorResponse, Unit> {
val server = client.serverUrl.replace("https", "ws").replace("http", "ws") val server = if(client.serverUrl.startsWith("http")) client.serverUrl.replace("http", "ws") else "ws://${client.serverUrl}"
var error: ErrorResponse? = null var error: ErrorResponse? = null
try { try {
client.client.webSocket("$server${buildUrl(params)}", {}) { client.client.webSocket("$server${buildUrl(params)}", {}) {
@@ -78,13 +78,11 @@ sealed class ProjectEvent {
enum class ANSI(val bitIdx: Int, val ansiCode: Int) { enum class ANSI(val bitIdx: Int, val ansiCode: Int) {
BOLD(0, 1), UNDERLINE(1, 4), BOLD(0, 1), UNDERLINE(1, 4),
FG_BLACK(2, 30), FG_RED(3, 31), FG_GREEN(4, 32), FG_RED(2, 31), FG_GREEN(3, 32), FG_YELLOW(4, 33),
FG_YELLOW(5, 33), FG_BLUE(6, 34), FG_MAGENTA(7, 35), FG_BLUE(5, 34), FG_GRAY(6, 90),
FG_CYAN(8, 36),
BG_BLACK(9, 40), BG_RED(10, 41), BG_GREEN(11, 42), BG_RED(7, 41), BG_GREEN(8, 42), BG_YELLOW(9, 43),
BG_YELLOW(12, 43), BG_BLUE(13, 44), BG_MAGENTA(14, 45), BG_BLUE(10, 44), BG_GRAY(11, 100);
BG_CYAN(15, 46);
companion object { companion object {
private val _mapping: Map<Int, ANSI> private val _mapping: Map<Int, ANSI>
@@ -179,7 +179,6 @@ fun Application.module() {
) { _, id, sender -> ) { _, id, sender ->
val bus = Buses.monitorBus(id) val bus = Buses.monitorBus(id)
val backlog = bus.onConnect() val backlog = bus.onConnect()
println("Bus [$id] connected; sending backlog (${backlog.start != null} // ${backlog.messages.size} // ${backlog.end != null})")
sender(bus.bus.serializer, backlog) sender(bus.bus.serializer, backlog)
bus.bus bus.bus
} }
@@ -11,21 +11,18 @@ class MutableBackLog<TStart, TEvent, TEnd> {
fun registerStart(event: TStart) { fun registerStart(event: TStart) {
if(start != null) throw IllegalStateException("Start is already set.") if(start != null) throw IllegalStateException("Start is already set.")
start = event start = event
println("[BACKLOG]: start '$event'")
} }
fun onEvent(event: TEvent) { fun onEvent(event: TEvent) {
if(start == null) throw IllegalStateException("Start is not set yet.") if(start == null) throw IllegalStateException("Start is not set yet.")
if(end != null) throw IllegalStateException("End is already set.") if(end != null) throw IllegalStateException("End is already set.")
_events += event _events += event
println("[BACKLOG]: event '$event'")
} }
fun registerEnd(event: TEnd) { fun registerEnd(event: TEnd) {
if(start == null) throw IllegalStateException("Start is not set yet.") if(start == null) throw IllegalStateException("Start is not set yet.")
if(end != null) throw IllegalStateException("End is already set.") if(end != null) throw IllegalStateException("End is already set.")
end = event end = event
println("[BACKLOG]: end '$event'")
} }
fun reset() { fun reset() {