Convert properties to an action (#2751)

* update actions response types and partially implement in UI

* further remove diagnostic ui

* convert action response nested to array

* prepare action res modal for Alex

* ad dproperties action for Bitcoin

* feat: add action success dialog (#2753)

* feat: add action success dialog

* mocks for string action res and hide properties from actions page

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* return null

* remove properties from backend

* misc fixes

* make severity separate argument

* rename ActionRequest to ActionRequestOptions

* add clearRequests

* fix s9pk build

* remove config and properties, introduce action requests

* better ux, better moocks, include icons

* fix dependency types

* add variant for versionCompat

* fix dep icon display and patch operation display

* misc fixes

* misc fixes

* alpha 12

* honor provided input to set values in action

* fix: show full descriptions of action success items (#2758)

* fix type

* fix: fix build:deps command on Windows (#2752)

* fix: fix build:deps command on Windows

* fix: add escaped quotes

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>

* misc db compatibility fixes

---------

Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
Matt Hill
2024-10-17 13:31:56 -06:00
committed by GitHub
parent fb074c8c32
commit 2ba56b8c59
105 changed files with 1385 additions and 1578 deletions

View File

@@ -7,7 +7,6 @@ pub enum ProcedureName {
GetConfig,
SetConfig,
CreateBackup,
Properties,
RestoreBackup,
GetActionInput(ActionId),
RunAction(ActionId),
@@ -23,7 +22,6 @@ impl ProcedureName {
ProcedureName::SetConfig => "/config/set".to_string(),
ProcedureName::GetConfig => "/config/get".to_string(),
ProcedureName::CreateBackup => "/backup/create".to_string(),
ProcedureName::Properties => "/properties".to_string(),
ProcedureName::RestoreBackup => "/backup/restore".to_string(),
ProcedureName::RunAction(id) => format!("/actions/{}/run", id),
ProcedureName::GetActionInput(id) => format!("/actions/{}/getInput", id),

View File

@@ -6,6 +6,7 @@ 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;
@@ -74,21 +75,25 @@ pub async fn get_action_input(
.await
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(tag = "version")]
#[ts(export)]
pub enum ActionResult {
#[serde(rename = "0")]
V0(ActionResultV0),
#[serde(rename = "1")]
V1(ActionResultV1),
}
impl fmt::Display for ActionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::V0(res) => res.fmt(f),
Self::V1(res) => res.fmt(f),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, TS)]
pub struct ActionResultV0 {
pub message: String,
pub value: Option<String>,
@@ -116,6 +121,96 @@ impl fmt::Display for ActionResultV0 {
}
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[serde(tag = "type")]
pub enum ActionResultV1 {
String {
name: String,
value: String,
description: Option<String>,
copyable: bool,
qr: bool,
masked: bool,
},
Object {
name: String,
value: Vec<ActionResultV1>,
#[ts(optional)]
description: Option<String>,
},
}
impl ActionResultV1 {
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 {
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, " ")?;
}
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()
)?;
}
}
}
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 {
write!(f, " ")?;
}
value.fmt_rec(f, indent + 1)?;
}
}
}
Ok(())
}
}
impl fmt::Display for ActionResultV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_rec(f, 0)
}
}
pub fn display_action_result<T: Serialize>(params: WithIoFormat<T>, result: Option<ActionResult>) {
let Some(result) = result else {
return;

View File

@@ -228,6 +228,8 @@ pub async fn subscribe(
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct CliApplyParams {
#[arg(long)]
allow_model_mismatch: bool,
expr: String,
path: Option<PathBuf>,
}
@@ -238,7 +240,12 @@ async fn cli_apply(
context,
parent_method,
method,
params: CliApplyParams { expr, path },
params:
CliApplyParams {
allow_model_mismatch,
expr,
path,
},
..
}: HandlerArgs<CliContext, CliApplyParams>,
) -> Result<(), RpcError> {
@@ -253,7 +260,14 @@ async fn cli_apply(
&expr,
)?;
Ok::<_, Error>((
let value = if allow_model_mismatch {
serde_json::from_value::<Value>(res.clone().into()).with_ctx(|_| {
(
crate::ErrorKind::Deserialization,
"result does not match database model",
)
})?
} else {
to_value(
&serde_json::from_value::<model::Database>(res.clone().into()).with_ctx(
|_| {
@@ -263,9 +277,9 @@ async fn cli_apply(
)
},
)?,
)?,
(),
))
)?
};
Ok::<_, Error>((value, ()))
})
.await?;
} else {

View File

@@ -338,7 +338,7 @@ pub struct ActionMetadata {
#[serde(rename_all_fields = "camelCase")]
pub enum ActionVisibility {
Hidden,
Disabled { reason: String },
Disabled(String),
Enabled,
}
impl Default for ActionVisibility {
@@ -444,14 +444,29 @@ pub struct ActionRequestEntry {
pub struct ActionRequest {
pub package_id: PackageId,
pub action_id: ActionId,
#[serde(default)]
pub severity: ActionSeverity,
#[ts(optional)]
pub description: Option<String>,
pub reason: Option<String>,
#[ts(optional)]
pub when: Option<ActionRequestTrigger>,
#[ts(optional)]
pub input: Option<ActionRequestInput>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "kebab-case")]
#[ts(export)]
pub enum ActionSeverity {
Critical,
Important,
}
impl Default for ActionSeverity {
fn default() -> Self {
ActionSeverity::Important
}
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]

View File

@@ -49,7 +49,6 @@ pub mod notifications;
pub mod os_install;
pub mod prelude;
pub mod progress;
pub mod properties;
pub mod registry;
pub mod rpc_continuations;
pub mod s9pk;
@@ -395,15 +394,6 @@ pub fn package<C: Context>() -> ParentHandler<C> {
.no_display()
.with_about("Display package logs"),
)
.subcommand(
"properties",
from_fn_async(properties::properties)
.with_custom_display_fn(|_handle, result| {
Ok(properties::display_properties(result))
})
.with_about("Display package Properties")
.with_call_remote::<CliContext>(),
)
.subcommand(
"backup",
backup::package_backup::<C>()

View File

@@ -113,7 +113,7 @@ async fn ws_handler(
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LogResponse {
entries: Reversible<LogEntry>,
pub entries: Reversible<LogEntry>,
start_cursor: Option<String>,
end_cursor: Option<String>,
}

View File

@@ -1,35 +0,0 @@
use clap::Parser;
use imbl_value::{json, Value};
use models::PackageId;
use serde::{Deserialize, Serialize};
use crate::context::RpcContext;
use crate::prelude::*;
use crate::Error;
pub fn display_properties(response: Value) {
println!("{}", response);
}
#[derive(Deserialize, Serialize, Parser)]
#[serde(rename_all = "camelCase")]
#[command(rename_all = "kebab-case")]
pub struct PropertiesParam {
id: PackageId,
}
// #[command(display(display_properties))]
pub async fn properties(
ctx: RpcContext,
PropertiesParam { id }: PropertiesParam,
) -> Result<Value, Error> {
match &*ctx.services.get(&id).await {
Some(service) => Ok(json!({
"version": 2,
"data": service.properties().await?
})),
None => Err(Error::new(
eyre!("Could not find a service with id {id}"),
ErrorKind::NotFound,
)),
}
}

View File

@@ -32,6 +32,7 @@ pub async fn bind(
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ClearBindingsParams {
#[serde(default)]
pub except: Vec<BindId>,
}

View File

@@ -55,7 +55,6 @@ pub mod cli;
mod control;
pub mod effects;
pub mod persistent_container;
mod properties;
mod rpc;
mod service_actor;
pub mod service_map;
@@ -132,6 +131,7 @@ impl ServiceRef {
);
Ok(())
})?;
d.as_private_mut().as_package_stores_mut().remove(&id)?;
Ok(Some(pde))
} else {
Ok(None)

View File

@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};
use std::ops::Deref;
use std::path::Path;
use std::sync::{Arc, Weak};
use std::time::Duration;
@@ -379,11 +380,7 @@ impl PersistentContainer {
));
}
self.rpc_client
.request(rpc::Init, Empty {})
.await
.map_err(Error::from)
.log_err();
self.rpc_client.request(rpc::Init, Empty {}).await?;
self.state.send_modify(|s| s.rt_initialized = true);
@@ -391,7 +388,10 @@ impl PersistentContainer {
}
#[instrument(skip_all)]
fn destroy(&mut self) -> Option<impl Future<Output = Result<(), Error>> + 'static> {
fn destroy(
&mut self,
error: bool,
) -> Option<impl Future<Output = Result<(), Error>> + 'static> {
if self.destroyed {
return None;
}
@@ -406,6 +406,24 @@ impl PersistentContainer {
self.destroyed = true;
Some(async move {
let mut errs = ErrorCollection::new();
if error {
if let Some(lxc_container) = &lxc_container {
if let Some(logs) = errs.handle(
crate::logs::fetch_logs(
crate::logs::LogSource::Container(lxc_container.guid.deref().clone()),
Some(50),
None,
None,
false,
)
.await,
) {
for log in logs.entries.iter() {
eprintln!("{log}");
}
}
}
}
if let Some((hdl, shutdown)) = rpc_server {
errs.handle(rpc_client.request(rpc::Exit, Empty {}).await);
shutdown.shutdown();
@@ -433,7 +451,7 @@ impl PersistentContainer {
#[instrument(skip_all)]
pub async fn exit(mut self) -> Result<(), Error> {
if let Some(destroy) = self.destroy() {
if let Some(destroy) = self.destroy(false) {
dbg!(destroy.await)?;
}
tracing::info!("Service for {} exited", self.s9pk.as_manifest().id);
@@ -551,7 +569,7 @@ impl PersistentContainer {
impl Drop for PersistentContainer {
fn drop(&mut self) {
if let Some(destroy) = self.destroy() {
if let Some(destroy) = self.destroy(true) {
tokio::spawn(async move { destroy.await.log_err() });
}
}

View File

@@ -1,23 +0,0 @@
use std::time::Duration;
use models::ProcedureName;
use crate::prelude::*;
use crate::rpc_continuations::Guid;
use crate::service::Service;
impl Service {
// TODO: leave here or switch to Actor Message?
pub async fn properties(&self) -> Result<Value, Error> {
let container = &self.seed.persistent_container;
container
.execute::<Value>(
Guid::new(),
ProcedureName::Properties,
Value::Null,
Some(Duration::from_secs(30)),
)
.await
.with_kind(ErrorKind::Unknown)
}
}

View File

@@ -1,3 +1,5 @@
use std::io;
use tracing::Subscriber;
use tracing_subscriber::util::SubscriberInitExt;
@@ -21,7 +23,11 @@ impl EmbassyLogger {
let filter_layer = filter_layer
.add_directive("tokio=trace".parse().unwrap())
.add_directive("runtime=trace".parse().unwrap());
let fmt_layer = fmt::layer().with_target(true);
let fmt_layer = fmt::layer()
.with_writer(io::stderr)
.with_line_number(true)
.with_file(true)
.with_target(true);
let sub = tracing_subscriber::registry()
.with(filter_layer)

View File

@@ -171,9 +171,17 @@ fn version_accessor(db: &mut Value) -> Option<&mut Value> {
fn version_compat_accessor(db: &mut Value) -> Option<&mut Value> {
if db.get("public").is_some() {
db.get_mut("public")?
.get_mut("serverInfo")?
.get_mut("versionCompat")
let server_info = db.get_mut("public")?.get_mut("serverInfo")?;
if server_info.get("versionCompat").is_some() {
server_info.get_mut("versionCompat")
} else {
if let Some(prev) = server_info.get("eosVersionCompat").cloned() {
server_info
.as_object_mut()?
.insert("versionCompat".into(), prev);
}
server_info.get_mut("versionCompat")
}
} else {
db.get_mut("server-info")?.get_mut("eos-version-compat")
}