mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
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:
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user