Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
589649d9f6
|
||
|
|
ae7724b3c4
|
||
|
|
ef5ccb9274
|
||
|
|
9e010c7873
|
||
|
|
fce1c097d4
|
||
|
|
b5c4e150a5
|
@@ -9,7 +9,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.jaytux.phoebench"
|
||||
version = rootProject.version as PhoebenchVersion
|
||||
version = PhoebenchVersion(1, 2, 1, "patch0")
|
||||
|
||||
if((version as PhoebenchVersion) < (rootProject.version as PhoebenchVersion))
|
||||
throw GradleException("CLI Client version must be at least as high as protocol/common version")
|
||||
|
||||
@@ -57,6 +57,7 @@ object PersistentStorage {
|
||||
|
||||
override fun write(key: String, value: String) {
|
||||
prefs.put(key, value)
|
||||
prefs.flush()
|
||||
}
|
||||
|
||||
override fun erase(key: String) {
|
||||
|
||||
@@ -13,7 +13,7 @@ plugins {
|
||||
alias(libs.plugins.buildconfig)
|
||||
}
|
||||
|
||||
version = rootProject.version as PhoebenchVersion
|
||||
version = PhoebenchVersion(1, 2, 1, "autoscroll")
|
||||
|
||||
if((version as PhoebenchVersion) < (rootProject.version as PhoebenchVersion))
|
||||
throw GradleException("UI Clients version must be at least as high as protocol/common version")
|
||||
|
||||
+5
-1
@@ -138,7 +138,11 @@ class ProjectVM(
|
||||
when(event) {
|
||||
ProjectEvent.Deleted -> back()
|
||||
is ProjectEvent.EntryDeleted -> _benchmarks.value = _benchmarks.value.replace({ it.id == event.benchmarkId }) { it.remove(event.id) }
|
||||
is ProjectEvent.VersionChanged -> _labels.value += (event.label.id to Version.fromResponse(event.label))
|
||||
is ProjectEvent.VersionChanged -> {
|
||||
val updVersion = Version.fromResponse(event.label)
|
||||
_labels.value += (event.label.id to updVersion)
|
||||
_benchmarks.value = _benchmarks.value.map { b -> b.copy(entries = b.entries.map { e -> if(e.label.id == event.label.id) e.copy(label = updVersion) else e }) }
|
||||
}
|
||||
is ProjectEvent.VersionDeleted -> {
|
||||
_labels.value = _labels.value.filter { it.value.id != event.id }
|
||||
_benchmarks.value = _benchmarks.value.map { it.copy(entries = it.entries.filter { e -> e.label.id != event.id }) }
|
||||
|
||||
+43
-1
@@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -161,6 +162,15 @@ fun MonitorMessage(msg: ServerMonitorEvent.ITextEvent) = when(msg) {
|
||||
|
||||
@Composable
|
||||
fun MonitorDialog(name: String, logs: List<ServerMonitorEvent.ITextEvent>, onClose: () -> Unit, onClear: () -> Unit) {
|
||||
var autoFollow by remember { mutableStateOf(true) }
|
||||
val state = rememberLazyListState()
|
||||
|
||||
LaunchedEffect(autoFollow, logs) {
|
||||
if(autoFollow) {
|
||||
state.animateScrollToItem(logs.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false)) {
|
||||
Surface(Modifier.padding(15.dp).fillMaxHeight(0.8f).widthIn(min = 300.dp, max = 2000.dp), shape = MaterialTheme.shapes.medium) {
|
||||
Column(Modifier.padding(8.dp)) {
|
||||
@@ -175,11 +185,22 @@ fun MonitorDialog(name: String, logs: List<ServerMonitorEvent.ITextEvent>, onClo
|
||||
}
|
||||
else {
|
||||
CompositionLocalProvider(LocalTextStyle provides LocalTextStyle.current.makeMonospaced()) {
|
||||
LazyColumn(Modifier.weight(1f).padding(10.dp).background(MaterialTheme.colorScheme.surfaceDim)) {
|
||||
Box(Modifier.weight(1f)) {
|
||||
LazyColumn(Modifier.fillMaxHeight().fillMaxWidth().padding(10.dp).background(MaterialTheme.colorScheme.surfaceDim), state) {
|
||||
items(logs) {
|
||||
MonitorMessage(it)
|
||||
}
|
||||
}
|
||||
|
||||
Box(Modifier.align(Alignment.TopEnd).padding(25.dp)) {
|
||||
val colors = if(autoFollow)
|
||||
IconButtonDefaults.iconButtonColors(containerColor = LocalContentColor.current, contentColor = MaterialTheme.colorScheme.surface)
|
||||
else IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.surface, contentColor = LocalContentColor.current)
|
||||
IconButton({ autoFollow = !autoFollow }, colors = colors) {
|
||||
Icon(Lucide.ArrowDownToLine, "Follow")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
@@ -199,6 +220,7 @@ fun ProjectPanel(vm: ProjectVM, editable: Boolean, versions: Map<Uuid, ProjectVM
|
||||
var addingLabel by remember { mutableStateOf(false) }
|
||||
var addingData by remember { mutableStateOf(false) }
|
||||
var editing by remember { mutableStateOf(false) }
|
||||
var deleting by remember { mutableStateOf(false) }
|
||||
|
||||
Surface(Modifier.fillMaxSize().padding(8.dp), tonalElevation = 10.dp, shape = MaterialTheme.shapes.medium) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
@@ -217,6 +239,9 @@ fun ProjectPanel(vm: ProjectVM, editable: Boolean, versions: Map<Uuid, ProjectVM
|
||||
IconButton({ editing = true }) {
|
||||
Icon(Lucide.Pencil, "Edit benchmark")
|
||||
}
|
||||
IconButton({ deleting = true }) {
|
||||
Icon(Lucide.Trash, "Remove benchmark")
|
||||
}
|
||||
Icon(Lucide.ChevronDown, "Select benchmark...")
|
||||
}
|
||||
}
|
||||
@@ -315,6 +340,8 @@ fun ProjectPanel(vm: ProjectVM, editable: Boolean, versions: Map<Uuid, ProjectVM
|
||||
}
|
||||
|
||||
if(editing) EditBenchmarkDialog(currentBenchmark, { editing = false }) { vm.updateBenchmark(currentBenchmark.id, it) }
|
||||
|
||||
if(deleting) ConfirmDeleteBenchmarkDialog(currentBenchmark, { deleting = false }) { vm.deleteBenchmark(currentBenchmark.id); deleting = false }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -359,6 +386,21 @@ fun ConfirmDeleteProjectDialog(name: String, onCancel: () -> Unit, onDelete: ()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConfirmDeleteBenchmarkDialog(benchmark: ProjectVM.Benchmark, onCancel: () -> Unit, onDelete: () -> Unit) {
|
||||
Dialog(onDismissRequest = onCancel) {
|
||||
Surface(Modifier.padding(15.dp), shape = MaterialTheme.shapes.medium) {
|
||||
Column(Modifier.padding(8.dp).widthIn(min = 250.dp).width(IntrinsicSize.Min)) {
|
||||
Text("Confirm deletion", Modifier.align(Alignment.CenterHorizontally), style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text("Are you sure you want to delete ${benchmark.name}?")
|
||||
Spacer(Modifier.height(10.dp))
|
||||
CancelConfirmXRow(onCancel, onDelete, confirmText = "Delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddBenchmarkDialog(onCancel: () -> Unit, onAdd: (name: String) -> Unit) {
|
||||
Dialog(onDismissRequest = onCancel) {
|
||||
|
||||
@@ -7,7 +7,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.jaytux.phoebench"
|
||||
version = rootProject.version as PhoebenchVersion
|
||||
version = PhoebenchVersion(1, 2, 1, "patch1")
|
||||
|
||||
if((version as PhoebenchVersion) < (rootProject.version as PhoebenchVersion))
|
||||
throw GradleException("Server version must be at least as high as protocol/common version")
|
||||
|
||||
@@ -185,7 +185,7 @@ fun Application.module() {
|
||||
|
||||
cseAuth(Routes.CSE.monitor,
|
||||
{ id, user -> transaction { accessibleProjectCSE(user, id, true) } },
|
||||
ProjectHandler::monitorSetup) { _, _, bus, event ->
|
||||
ProjectHandler::monitorSetup, ProjectHandler::monitorTeardown) { _, _, bus, event ->
|
||||
try {
|
||||
when(event) {
|
||||
is ClientMonitorEvent.ApplicationFinished -> bus.end(ServerMonitorEvent.ApplicationEnd(Clock.System.now(), event.exitCode))
|
||||
|
||||
@@ -282,6 +282,7 @@ inline fun <reified TParams: Any, reified TEvent: Any, TInter, TExtra> Route.wra
|
||||
api: CSERoute<TParams, TEvent>,
|
||||
noinline extra: suspend (ApplicationCall, TParams) -> TInter,
|
||||
noinline setup: suspend (TParams, TInter) -> TExtra,
|
||||
noinline onClose: suspend (TParams, TInter, TExtra) -> Unit,
|
||||
noinline handler: suspend (TParams, TInter, TExtra, TEvent) -> Unit
|
||||
) {
|
||||
webSocket(api.pattern) {
|
||||
@@ -292,11 +293,15 @@ inline fun <reified TParams: Any, reified TEvent: Any, TInter, TExtra> Route.wra
|
||||
|
||||
val converter = KotlinxWebsocketSerializationConverter(Json)
|
||||
val extra = setup(params, inter)
|
||||
try {
|
||||
for (frame in incoming) {
|
||||
handler(params, inter, extra, converter.deserialize<TEvent>(frame))
|
||||
}
|
||||
}
|
||||
catch(e: ClosedReceiveChannelException) {}
|
||||
finally {
|
||||
onClose(params, inter, extra)
|
||||
}
|
||||
}
|
||||
catch(e: RouteError.CSERouteError) {
|
||||
close(CloseReason(e.code, e.message ?: "Unknown error"))
|
||||
}
|
||||
@@ -310,6 +315,7 @@ inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.wrapAuthCSE
|
||||
api: CSERoute<TParams, TEvent>,
|
||||
noinline verifyUser: suspend (TParams, User) -> Unit,
|
||||
noinline setup: suspend (TParams, User) -> TExtra,
|
||||
noinline onClose: suspend (TParams, User, TExtra) -> Unit,
|
||||
noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
|
||||
) = wrapCSE<TParams, TEvent, User, TExtra>(
|
||||
api = api,
|
||||
@@ -327,28 +333,32 @@ inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.wrapAuthCSE
|
||||
user
|
||||
},
|
||||
setup = setup,
|
||||
onClose = onClose,
|
||||
handler = handler
|
||||
)
|
||||
|
||||
inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cse(
|
||||
api: CSERoute<TParams, TEvent>,
|
||||
noinline setup: suspend (TParams, Unit) -> TExtra,
|
||||
noinline setup: suspend (TParams) -> TExtra,
|
||||
noinline onClose: suspend (TParams, TExtra) -> Unit,
|
||||
noinline handler: suspend (TParams, TExtra, TEvent) -> Unit
|
||||
) {
|
||||
if(api.elevation != Elevation.UN_AUTH) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}")
|
||||
wrapCSE(api, { _, _ -> }, setup) { params, _, extra, event -> handler(params, extra, event) }
|
||||
wrapCSE(api, { _, _ -> }, { it, _ -> setup(it) }, { p, _, extra -> onClose(p, extra) }) { params, _, extra, event -> handler(params, extra, event) }
|
||||
}
|
||||
|
||||
inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAuth(
|
||||
api: CSERoute<TParams, TEvent>, noinline verifyUser: suspend (TParams, User) -> Unit,
|
||||
noinline setup: suspend (TParams, User) -> TExtra, noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
|
||||
noinline setup: suspend (TParams, User) -> TExtra, noinline onClose: suspend (TParams, User, TExtra) -> Unit,
|
||||
noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
|
||||
) {
|
||||
if(api.elevation != Elevation.AUTH) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}")
|
||||
wrapAuthCSE(api, verifyUser, setup, handler)
|
||||
wrapAuthCSE(api, verifyUser, setup, onClose, handler)
|
||||
}
|
||||
|
||||
inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAdmin(
|
||||
api: CSERoute<TParams, TEvent>, noinline setup: suspend (TParams, User) -> TExtra,
|
||||
noinline onClose: suspend (TParams, User, TExtra) -> Unit,
|
||||
noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
|
||||
) {
|
||||
if(api.elevation != Elevation.ADMIN) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}")
|
||||
@@ -356,5 +366,5 @@ inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAdmin(
|
||||
if(!user.isAdmin) {
|
||||
throw RouteError.CSERouteError("Admin access required", CloseReasons.NOT_AUTHORIZED)
|
||||
}
|
||||
}, setup, handler)
|
||||
}, setup, onClose, handler)
|
||||
}
|
||||
@@ -402,6 +402,14 @@ object ProjectHandler {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun monitorTeardown(
|
||||
id: Uuid, user: User,
|
||||
bus: SSEBus.MonitorSSEBus<ServerMonitorEvent, ServerMonitorEvent.ApplicationStart, ServerMonitorEvent.Message,
|
||||
ServerMonitorEvent.ApplicationEnd, ServerMonitorEvent.Backlog>
|
||||
) {
|
||||
bus.end(ServerMonitorEvent.ApplicationEnd(Clock.System.now(), -1))
|
||||
}
|
||||
|
||||
fun deleteMonitor(user: User, req: Uuid) = transaction {
|
||||
checkMigration(user)
|
||||
accessibleProject(user, req, true)
|
||||
|
||||
Reference in New Issue
Block a user