sdk tweaks (#2760)

* sdk tweaks

* update action result types

* accommodate new action response types

* fix: show action value labels

* Feature/get status effect (#2765)

* wip: get status

* feat: Add the get_status for effects

* feat: Do a callback

---------

Co-authored-by: J H <dragondef@gmail.com>

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: waterplea <alexander@inkin.ru>
Co-authored-by: J H <dragondef@gmail.com>
This commit is contained in:
Aiden McClelland
2024-10-28 12:12:36 -06:00
committed by GitHub
parent 42cfd69463
commit 26ae0bf207
28 changed files with 871 additions and 456 deletions

View File

@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::fmt;
use clap::{CommandFactory, FromArgMatches, Parser};
@@ -6,7 +7,6 @@ use models::PackageId;
use qrcode::QrCode;
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tracing::instrument;
use ts_rs::TS;
@@ -24,7 +24,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
from_fn_async(get_action_input)
.with_display_serializable()
.with_about("Get action input spec")
.with_call_remote::<CliContext>()
.with_call_remote::<CliContext>(),
)
.subcommand(
"run",
@@ -37,7 +37,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
Ok(())
})
.with_about("Run service action")
.with_call_remote::<CliContext>()
.with_call_remote::<CliContext>(),
)
}
@@ -121,83 +121,78 @@ impl fmt::Display for ActionResultV0 {
}
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct ActionResultV1 {
pub title: String,
pub message: Option<String>,
pub result: Option<ActionResultValue>,
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct ActionResultMember {
pub name: String,
pub description: Option<String>,
#[serde(flatten)]
#[ts(flatten)]
pub value: ActionResultValue,
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[serde(tag = "type")]
pub enum ActionResultV1 {
String {
name: String,
pub enum ActionResultValue {
Single {
value: String,
description: Option<String>,
copyable: bool,
qr: bool,
masked: bool,
},
Object {
name: String,
value: Vec<ActionResultV1>,
#[ts(optional)]
description: Option<String>,
Group {
value: Vec<ActionResultMember>,
},
}
impl ActionResultV1 {
impl ActionResultValue {
fn fmt_rec(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
match self {
Self::String {
name,
value,
description,
qr,
..
} => {
for i in 0..indent {
Self::Single { value, qr, .. } => {
for _ in 0..indent {
write!(f, " ")?;
}
write!(f, "{name}")?;
if let Some(description) = description {
write!(f, ": {description}")?;
}
if !value.is_empty() {
write!(f, ":\n")?;
for i in 0..indent {
write!(f, "{value}")?;
if *qr {
use qrcode::render::unicode;
writeln!(f)?;
for _ in 0..indent {
write!(f, " ")?;
}
write!(f, "{value}")?;
if *qr {
use qrcode::render::unicode;
write!(f, "\n")?;
for i in 0..indent {
write!(f, " ")?;
}
write!(
f,
"{}",
QrCode::new(value.as_bytes())
.unwrap()
.render::<unicode::Dense1x2>()
.build()
)?;
}
write!(
f,
"{}",
QrCode::new(value.as_bytes())
.unwrap()
.render::<unicode::Dense1x2>()
.build()
)?;
}
}
Self::Object {
name,
value,
description,
} => {
for i in 0..indent {
write!(f, " ")?;
}
write!(f, "{name}")?;
if let Some(description) = description {
write!(f, ": {description}")?;
}
for value in value {
write!(f, ":\n")?;
for i in 0..indent {
Self::Group { value } => {
for ActionResultMember {
name,
description,
value,
} in value
{
for _ in 0..indent {
write!(f, " ")?;
}
write!(f, "{name}")?;
if let Some(description) = description {
write!(f, ": {description}")?;
}
writeln!(f, ":")?;
value.fmt_rec(f, indent + 1)?;
}
}
@@ -207,7 +202,14 @@ impl ActionResultV1 {
}
impl fmt::Display for ActionResultV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_rec(f, 0)
writeln!(f, "{}:", self.title)?;
if let Some(message) = &self.message {
writeln!(f, "{message}")?;
}
if let Some(result) = &self.result {
result.fmt_rec(f, 1)?;
}
Ok(())
}
}

View File

@@ -36,6 +36,7 @@ struct ServiceCallbackMap {
(NonDetachingJoinHandle<()>, Vec<CallbackHandler>),
>,
get_store: BTreeMap<PackageId, BTreeMap<JsonPointer, Vec<CallbackHandler>>>,
get_status: BTreeMap<PackageId, Vec<CallbackHandler>>,
}
impl ServiceCallbacks {
@@ -71,6 +72,10 @@ impl ServiceCallbacks {
});
!v.is_empty()
});
this.get_status.retain(|_, v| {
v.retain(|h| h.handle.is_active() && h.seed.strong_count() > 0);
!v.is_empty()
});
})
}
@@ -220,6 +225,20 @@ impl ServiceCallbacks {
.push(handler);
})
}
pub(super) fn add_get_status(&self, package_id: PackageId, handler: CallbackHandler) {
self.mutate(|this| this.get_status.entry(package_id).or_default().push(handler))
}
#[must_use]
pub fn get_status(&self, package_id: &PackageId) -> Option<CallbackHandlers> {
self.mutate(|this| {
if let Some(watched) = this.get_status.remove(package_id) {
Some(CallbackHandlers(watched))
} else {
None
}
.filter(|cb| !cb.0.is_empty())
})
}
pub(super) fn add_get_store(
&self,

View File

@@ -1,9 +1,11 @@
use std::str::FromStr;
use clap::builder::ValueParserFactory;
use models::FromStrParser;
use models::{FromStrParser, PackageId};
use crate::service::effects::prelude::*;
use crate::service::rpc::CallbackId;
use crate::status::MainStatus;
pub async fn restart(
context: EffectContext,
@@ -23,6 +25,46 @@ pub async fn shutdown(
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct GetStatusParams {
#[ts(optional)]
pub package_id: Option<PackageId>,
#[ts(optional)]
#[arg(skip)]
pub callback: Option<CallbackId>,
}
pub async fn get_status(
context: EffectContext,
GetStatusParams {
package_id,
callback,
}: GetStatusParams,
) -> Result<MainStatus, Error> {
let context = context.deref()?;
let id = package_id.unwrap_or_else(|| context.seed.id.clone());
let db = context.seed.ctx.db.peek().await;
let status = db
.as_public()
.as_package_data()
.as_idx(&id)
.or_not_found(&id)?
.as_status()
.de()?;
if let Some(callback) = callback {
let callback = callback.register(&context.seed.persistent_container);
context.seed.ctx.callbacks.add_get_status(
id,
super::callbacks::CallbackHandler::new(&context, callback),
);
}
Ok(status)
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]

View File

@@ -50,6 +50,12 @@ pub fn handler<C: Context>() -> ParentHandler<C> {
.no_display()
.with_call_remote::<ContainerCliContext>(),
)
.subcommand(
"get-status",
from_fn_async(control::get_status)
.no_display()
.with_call_remote::<ContainerCliContext>(),
)
// dependency
.subcommand(
"set-dependencies",

View File

@@ -1,6 +1,8 @@
use std::sync::Arc;
use std::time::Duration;
use imbl::vector;
use super::start_stop::StartStop;
use super::ServiceActorSeed;
use crate::prelude::*;
@@ -45,7 +47,8 @@ async fn service_actor_loop(
let id = &seed.id;
let kinds = current.borrow().kinds();
if let Err(e) = async {
seed.ctx
let major_changes_state = seed
.ctx
.db
.mutate(|d| {
if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) {
@@ -89,11 +92,22 @@ async fn service_actor_loop(
..
} => MainStatus::Stopped,
};
let previous = i.as_status().de()?;
i.as_status_mut().ser(&main_status)?;
return Ok(previous
.major_changes(&main_status)
.then_some((previous, main_status)));
}
Ok(())
Ok(None)
})
.await?;
if let Some((previous, new_state)) = major_changes_state {
if let Some(callbacks) = seed.ctx.callbacks.get_status(id) {
callbacks
.call(vector![to_value(&previous)?, to_value(&new_state)?])
.await?;
}
}
seed.synchronized.notify_waiters();
match kinds {

View File

@@ -66,6 +66,20 @@ impl MainStatus {
}
}
pub fn major_changes(&self, other: &Self) -> bool {
match (self, other) {
(MainStatus::Running { .. }, MainStatus::Running { .. }) => false,
(MainStatus::Starting { .. }, MainStatus::Starting { .. }) => false,
(MainStatus::Stopping, MainStatus::Stopping) => false,
(MainStatus::Stopped, MainStatus::Stopped) => false,
(MainStatus::Restarting, MainStatus::Restarting) => false,
(MainStatus::Restoring, MainStatus::Restoring) => false,
(MainStatus::BackingUp { .. }, MainStatus::BackingUp { .. }) => false,
(MainStatus::Error { .. }, MainStatus::Error { .. }) => false,
_ => true,
}
}
pub fn backing_up(self) -> Self {
MainStatus::BackingUp {
on_complete: if self.running() {