backend changes

This commit is contained in:
Aiden McClelland
2025-07-22 16:48:16 -06:00
parent 4d9709eb1c
commit d3e7e37f59
28 changed files with 211 additions and 162 deletions

View File

@@ -35,13 +35,13 @@ const SOCKET_PATH = "/media/startos/rpc/host.sock"
let hostSystemId = 0
export type EffectContext = {
procedureId: string | null
eventId: string | null
callbacks?: CallbackHolder
constRetry?: () => void
}
const rpcRoundFor =
(procedureId: string | null) =>
(eventId: string | null) =>
<K extends T.EffectMethod | "clearCallbacks">(
method: K,
params: Record<string, unknown>,
@@ -52,7 +52,7 @@ const rpcRoundFor =
JSON.stringify({
id,
method,
params: { ...params, procedureId: procedureId || undefined },
params: { ...params, eventId: eventId ?? undefined },
}) + "\n",
)
})
@@ -103,8 +103,9 @@ const rpcRoundFor =
}
export function makeEffects(context: EffectContext): Effects {
const rpcRound = rpcRoundFor(context.procedureId)
const rpcRound = rpcRoundFor(context.eventId)
const self: Effects = {
eventId: context.eventId,
child: (name) =>
makeEffects({ ...context, callbacks: context.callbacks?.child(name) }),
constRetry: context.constRetry,

View File

@@ -242,11 +242,11 @@ export class RpcListener {
.when(runType, async ({ id, params }) => {
const system = this.system
const procedure = jsonPath.unsafeCast(params.procedure)
const { input, timeout, id: procedureId } = params
const { input, timeout, id: eventId } = params
const result = this.getResult(
procedure,
system,
procedureId,
eventId,
timeout,
input,
)
@@ -256,11 +256,11 @@ export class RpcListener {
.when(sandboxRunType, async ({ id, params }) => {
const system = this.system
const procedure = jsonPath.unsafeCast(params.procedure)
const { input, timeout, id: procedureId } = params
const { input, timeout, id: eventId } = params
const result = this.getResult(
procedure,
system,
procedureId,
eventId,
timeout,
input,
)
@@ -275,7 +275,7 @@ export class RpcListener {
const callbacks =
this.callbacks?.getChild("main") || this.callbacks?.child("main")
const effects = makeEffects({
procedureId: null,
eventId: null,
callbacks,
})
return handleRpc(
@@ -304,7 +304,7 @@ export class RpcListener {
}
await this._system.exit(
makeEffects({
procedureId: params.id,
eventId: params.id,
}),
target,
)
@@ -320,14 +320,14 @@ export class RpcListener {
const system = await this.getDependencies.system()
this.callbacks = new CallbackHolder(
makeEffects({
procedureId: params.id,
eventId: params.id,
}),
)
const callbacks = this.callbacks.child("init")
console.error("Initializing...")
await system.init(
makeEffects({
procedureId: params.id,
eventId: params.id,
callbacks,
}),
params.kind,
@@ -399,7 +399,7 @@ export class RpcListener {
private getResult(
procedure: typeof jsonPath._TYPE,
system: System,
procedureId: string,
eventId: string,
timeout: number | null | undefined,
input: any,
) {
@@ -410,7 +410,7 @@ export class RpcListener {
}
const callbacks = this.callbacks?.child(procedure)
const effects = makeEffects({
procedureId,
eventId,
callbacks,
})

View File

@@ -509,13 +509,18 @@ export class SystemForEmbassy implements System {
): Promise<T.ActionInput | null> {
if (actionId === "config") {
const config = await this.getConfig(effects, timeoutMs)
return { spec: config.spec, value: config.config }
return {
eventId: effects.eventId!,
spec: config.spec,
value: config.config,
}
} else if (actionId === "properties") {
return null
} else {
const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"]
if (!oldSpec) return null
return {
eventId: effects.eventId!,
spec: transformConfigSpec(oldSpec as OldConfigSpec),
value: null,
}

View File

@@ -52,6 +52,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ActionInput {
pub event_id: Guid,
#[ts(type = "Record<string, unknown>")]
pub spec: Value,
#[ts(type = "Record<string, unknown> | null")]
@@ -270,6 +271,7 @@ pub fn display_action_result<T: Serialize>(
#[serde(rename_all = "camelCase")]
pub struct RunActionParams {
pub package_id: PackageId,
pub event_id: Option<Guid>,
pub action_id: ActionId,
#[ts(optional, type = "any")]
pub input: Option<Value>,
@@ -278,6 +280,7 @@ pub struct RunActionParams {
#[derive(Parser)]
struct CliRunActionParams {
pub package_id: PackageId,
pub event_id: Option<Guid>,
pub action_id: ActionId,
#[command(flatten)]
pub input: StdinDeserializable<Option<Value>>,
@@ -286,12 +289,14 @@ impl From<CliRunActionParams> for RunActionParams {
fn from(
CliRunActionParams {
package_id,
event_id,
action_id,
input,
}: CliRunActionParams,
) -> Self {
Self {
package_id,
event_id,
action_id,
input: input.0,
}
@@ -331,6 +336,7 @@ pub async fn run_action(
ctx: RpcContext,
RunActionParams {
package_id,
event_id,
action_id,
input,
}: RunActionParams,
@@ -340,7 +346,11 @@ pub async fn run_action(
.await
.as_ref()
.or_not_found(lazy_format!("Manager for {}", package_id))?
.run_action(Guid::new(), action_id, input.unwrap_or_default())
.run_action(
event_id.unwrap_or_default(),
action_id,
input.unwrap_or_default(),
)
.await
.map(|res| res.map(ActionResult::upcast))
}

View File

@@ -198,13 +198,12 @@ pub struct NetworkInfo {
#[model = "Model<Self>"]
#[ts(export)]
pub struct NetworkInterfaceInfo {
pub inbound: Option<bool>,
pub outbound: Option<bool>,
pub public: Option<bool>,
pub ip_info: Option<IpInfo>,
}
impl NetworkInterfaceInfo {
pub fn inbound(&self) -> bool {
self.inbound.unwrap_or_else(|| {
pub fn public(&self) -> bool {
self.public.unwrap_or_else(|| {
!self.ip_info.as_ref().map_or(true, |ip_info| {
let ip4s = ip_info
.subnets

View File

@@ -169,7 +169,7 @@ impl LanPortForwardController {
(
iface.clone(),
(
info.inbound(),
info.public(),
info.ip_info.as_ref().map_or(Vec::new(), |i| {
i.subnets
.iter()
@@ -205,7 +205,7 @@ impl LanPortForwardController {
ip_info
.iter()
.map(|(iface, info)| (iface.clone(), (
info.inbound(),
info.public(),
info.ip_info.as_ref().map_or(Vec::new(), |i| {
i.subnets
.iter()

View File

@@ -335,7 +335,7 @@ impl NetServiceData {
for (interface, public, ip_info) in
net_ifaces.iter().filter_map(|(interface, info)| {
if let Some(ip_info) = &info.ip_info {
Some((interface, info.inbound(), ip_info))
Some((interface, info.public(), ip_info))
} else {
None
}

View File

@@ -58,7 +58,7 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
info.ip_info.as_ref()
.and_then(|ip_info| ip_info.device_type)
.map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")),
info.inbound(),
info.public(),
info.ip_info.as_ref().map_or_else(
|| "<DISCONNECTED>".to_owned(),
|ip_info| ip_info.subnets
@@ -585,21 +585,24 @@ async fn watch_ip(
None
};
write_to.send_if_modified(|m| {
let (inbound, outbound) = m
.get(&iface)
.map_or((None, None), |i| (i.inbound, i.outbound));
m.insert(
iface.clone(),
NetworkInterfaceInfo {
inbound,
outbound,
ip_info: ip_info.clone(),
},
)
.filter(|old| &old.ip_info == &ip_info)
.is_none()
});
write_to.send_if_modified(
|m: &mut BTreeMap<
InternedString,
NetworkInterfaceInfo,
>| {
let public =
m.get(&iface).map_or(None, |i| i.public);
m.insert(
iface.clone(),
NetworkInterfaceInfo {
public,
ip_info: ip_info.clone(),
},
)
.filter(|old| &old.ip_info == &ip_info)
.is_none()
},
);
Ok::<_, Error>(())
})
@@ -856,7 +859,7 @@ impl NetworkInterfaceController {
return false;
}
}
.inbound,
.public,
public,
);
prev != public
@@ -968,8 +971,7 @@ impl ListenerMap {
) -> Result<(), Error> {
let mut keep = BTreeSet::<SocketAddr>::new();
for info in ip_info.values().chain([&NetworkInterfaceInfo {
inbound: Some(false),
outbound: Some(false),
public: Some(false),
ip_info: Some(IpInfo {
name: "lo".into(),
scope_id: 1,
@@ -984,7 +986,7 @@ impl ListenerMap {
ntp_servers: Default::default(),
}),
}]) {
if public || !info.inbound() {
if public || !info.public() {
if let Some(ip_info) = &info.ip_info {
for ipnet in &ip_info.subnets {
let addr = match ipnet.addr() {
@@ -1003,7 +1005,7 @@ impl ListenerMap {
};
keep.insert(addr);
if let Some((_, is_public, wan_ip)) = self.listeners.get_mut(&addr) {
*is_public = info.inbound();
*is_public = info.public();
*wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip);
continue;
}
@@ -1021,7 +1023,7 @@ impl ListenerMap {
.into(),
)
.with_kind(ErrorKind::Network)?,
info.inbound(),
info.public(),
info.ip_info.as_ref().and_then(|i| i.wan_ip),
),
);

View File

@@ -110,10 +110,6 @@ pub async fn list(
})
})
.collect::<Result<Vec<NotificationWithId>, Error>>()?;
db.as_public_mut()
.as_server_info_mut()
.as_unread_notification_count_mut()
.ser(&0)?;
Ok(notifs)
}
Some(before) => {
@@ -195,22 +191,23 @@ pub async fn mark_seen(
) -> Result<(), Error> {
ctx.db
.mutate(|db| {
let mut diff = 0;
let n = db.as_private_mut().as_notifications_mut();
for id in ids {
if !n
.as_idx_mut(&id)
n.as_idx_mut(&id)
.or_not_found(lazy_format!("Notification #{id}"))?
.as_seen_mut()
.replace(&true)?
{
diff += 1;
.ser(&true)?;
}
let mut unread = 0;
for (_, n) in n.as_entries()? {
if !n.as_seen().de()? {
unread += 1;
}
}
db.as_public_mut()
.as_server_info_mut()
.as_unread_notification_count_mut()
.mutate(|n| Ok(*n -= diff))?;
.ser(&unread)?;
Ok(())
})
.await
@@ -223,22 +220,23 @@ pub async fn mark_seen_before(
) -> Result<(), Error> {
ctx.db
.mutate(|db| {
let mut diff = 0;
let n = db.as_private_mut().as_notifications_mut();
for id in n.keys()?.range(..before) {
if !n
.as_idx_mut(&id)
n.as_idx_mut(&id)
.or_not_found(lazy_format!("Notification #{id}"))?
.as_seen_mut()
.replace(&true)?
{
diff += 1;
.ser(&true)?;
}
let mut unread = 0;
for (_, n) in n.as_entries()? {
if !n.as_seen().de()? {
unread += 1;
}
}
db.as_public_mut()
.as_server_info_mut()
.as_unread_notification_count_mut()
.mutate(|n| Ok(*n -= diff))?;
.ser(&unread)?;
Ok(())
})
.await
@@ -251,21 +249,23 @@ pub async fn mark_unseen(
) -> Result<(), Error> {
ctx.db
.mutate(|db| {
let mut diff = 0;
let n = db.as_private_mut().as_notifications_mut();
for id in ids {
if n.as_idx_mut(&id)
n.as_idx_mut(&id)
.or_not_found(lazy_format!("Notification #{id}"))?
.as_seen_mut()
.replace(&false)?
{
diff += 1;
.ser(&false)?;
}
let mut unread = 0;
for (_, n) in n.as_entries()? {
if !n.as_seen().de()? {
unread += 1;
}
}
db.as_public_mut()
.as_server_info_mut()
.as_unread_notification_count_mut()
.mutate(|n| Ok(*n += diff))?;
.ser(&unread)?;
Ok(())
})
.await

View File

@@ -115,7 +115,7 @@ pub fn update_tasks(
}
pub(super) struct RunAction {
id: ActionId,
action_id: ActionId,
input: Value,
}
impl Handler<RunAction> for ServiceActor {
@@ -127,7 +127,7 @@ impl Handler<RunAction> for ServiceActor {
&mut self,
id: Guid,
RunAction {
id: ref action_id,
ref action_id,
input,
}: RunAction,
jobs: &BackgroundJobQueue,
@@ -145,7 +145,7 @@ impl Handler<RunAction> for ServiceActor {
.into_idx(package_id)
.or_not_found(package_id)?
.into_actions()
.into_idx(&action_id)
.into_idx(action_id)
.or_not_found(lazy_format!("{package_id} action {action_id}"))?
.de()?;
if matches!(&action.visibility, ActionVisibility::Disabled(_)) {
@@ -226,14 +226,6 @@ impl Service {
action_id: ActionId,
input: Value,
) -> Result<Option<ActionResult>, Error> {
self.actor
.send(
id,
RunAction {
id: action_id,
input,
},
)
.await?
self.actor.send(id, RunAction { action_id, input }).await?
}
}

View File

@@ -21,21 +21,15 @@ pub async fn rebuild(context: EffectContext) -> Result<(), Error> {
Ok(())
}
pub async fn restart(
context: EffectContext,
ProcedureId { procedure_id }: ProcedureId,
) -> Result<(), Error> {
pub async fn restart(context: EffectContext, EventId { event_id }: EventId) -> Result<(), Error> {
let context = context.deref()?;
context.restart(procedure_id, false).await?;
context.restart(event_id, false).await?;
Ok(())
}
pub async fn shutdown(
context: EffectContext,
ProcedureId { procedure_id }: ProcedureId,
) -> Result<(), Error> {
pub async fn shutdown(context: EffectContext, EventId { event_id }: EventId) -> Result<(), Error> {
let context = context.deref()?;
context.stop(procedure_id, false).await?;
context.stop(event_id, false).await?;
Ok(())
}

View File

@@ -9,8 +9,8 @@ pub(super) use crate::service::effects::context::EffectContext;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct ProcedureId {
pub struct EventId {
#[serde(default)]
#[arg(default_value_t, long)]
pub procedure_id: Guid,
pub event_id: Guid,
}

View File

@@ -105,7 +105,6 @@ pub struct PersistentContainer {
pub(super) lxc_container: OnceCell<LxcContainer>,
pub(super) rpc_client: UnixRpcClient,
pub(super) rpc_server: watch::Sender<Option<(NonDetachingJoinHandle<()>, ShutdownHandle)>>,
// procedures: Mutex<Vec<(ProcedureName, ProcedureId)>>,
js_mount: MountGuard,
volumes: BTreeMap<VolumeId, MountGuard>,
assets: Vec<MountGuard>,

View File

@@ -202,6 +202,7 @@ pub async fn cli_update_system(
prev.overall.set_complete();
progress.update(&prev);
}
println!("Update complete. Restart your server to apply the update.")
} else {
println!("Updating to v{v}...")
}

View File

@@ -27,6 +27,7 @@ import {
/** Used to reach out from the pure js runtime */
export type Effects = {
readonly eventId: string | null
child: (name: string) => Effects
constRetry?: () => void
isInContext: boolean

View File

@@ -1,8 +1,7 @@
import { ExtractInputSpecType, InputSpec, LazyBuild } from "./inputSpec"
import { InputSpec, LazyBuild } from "./inputSpec"
import { List } from "./list"
import { UnionRes, UnionResStaticValidatedAs, Variants } from "./variants"
import {
FilePath,
Pattern,
RandomString,
ValueSpec,
@@ -27,6 +26,12 @@ import {
} from "ts-matches"
import { DeepPartial } from "../../../types"
export const fileInfoParser = object({
path: string,
commitment: object({ hash: string, size: number }),
})
export type FileInfo = typeof fileInfoParser._TYPE
type AsRequired<T, Required extends boolean> = Required extends true
? T
: T | null
@@ -891,47 +896,54 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
}
}, spec.validator)
}
// static file<Store, Required extends boolean>(a: {
// name: string
// description?: string | null
// extensions: string[]
// required: Required
// }) {
// const buildValue = {
// type: "file" as const,
// description: null,
// warning: null,
// ...a,
// }
// return new Value<AsRequired<FilePath, Required>, Store>(
// () => ({
// ...buildValue,
// }),
// asRequiredParser(object({ filePath: string }), a),
// )
// }
// static dynamicFile<Store>(
// a: LazyBuild<
// Store,
// {
// name: string
// description?: string | null
// warning?: string | null
// extensions: string[]
// required: boolean
// }
// >,
// ) {
// return new Value<FilePath | null, Store>(
// async (options) => ({
// type: "file" as const,
// description: null,
// warning: null,
// ...(await a(options)),
// }),
// object({ filePath: string }).nullable(),
// )
// }
static file<Required extends boolean>(a: {
name: string
description?: string | null
warning?: string | null
extensions: string[]
required: Required
}) {
const buildValue = {
type: "file" as const,
description: null,
warning: null,
...a,
}
return new Value<AsRequired<FileInfo, Required>>(
() => ({
spec: {
...buildValue,
},
validator: asRequiredParser(fileInfoParser, a),
}),
asRequiredParser(fileInfoParser, a),
)
}
static dynamicFile<Required extends boolean>(
a: LazyBuild<{
name: string
description?: string | null
warning?: string | null
extensions: string[]
required: Required
}>,
) {
return new Value<AsRequired<FileInfo, Required>, FileInfo | null>(
async (options) => {
const spec = {
type: "file" as const,
description: null,
warning: null,
...(await a(options)),
}
return {
spec,
validator: asRequiredParser(fileInfoParser, spec),
}
},
fileInfoParser.nullable(),
)
}
/**
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
* @example

View File

@@ -66,9 +66,6 @@ export type ValueSpecTextarea = {
immutable: boolean
}
export type FilePath = {
filePath: string
}
export type ValueSpecNumber = {
type: "number"
min: number | null

View File

@@ -5,9 +5,11 @@ import { once } from "../util"
import { InitScript } from "../inits"
import { Parser } from "ts-matches"
type MaybeInputSpec<Type> = {} extends Type ? null : InputSpec<Type>
export type Run<A extends Record<string, any>> = (options: {
effects: T.Effects
input: A
spec: T.inputSpecTypes.InputSpec
}) => Promise<(T.ActionResult & { version: "1" }) | null | void | undefined>
export type GetInput<A extends Record<string, any>> = (options: {
effects: T.Effects
@@ -47,11 +49,14 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
implements ActionInfo<Id, Type>
{
readonly _INPUT: Type = null as any as Type
private cachedParser?: Parser<unknown, Type>
private prevInputSpec: Record<
string,
{ spec: T.inputSpecTypes.InputSpec; validator: Parser<unknown, Type> }
> = {}
private constructor(
readonly id: Id,
private readonly metadataFn: MaybeFn<T.ActionMetadata>,
private readonly inputSpec: InputSpec<Type>,
private readonly inputSpec: MaybeInputSpec<Type>,
private readonly getInputFn: GetInput<Type>,
private readonly runFn: Run<Type>,
) {}
@@ -81,7 +86,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
return new Action(
id,
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })),
InputSpec.of({}),
null,
async () => null,
run,
)
@@ -100,10 +105,15 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
return metadata
}
async getInput(options: { effects: T.Effects }): Promise<T.ActionInput> {
const built = await this.inputSpec.build(options)
this.cachedParser = built.validator
let spec = {}
if (this.inputSpec) {
const built = await this.inputSpec.build(options)
this.prevInputSpec[options.effects.eventId!] = built
spec = built.spec
}
return {
spec: built.spec,
eventId: options.effects.eventId!,
spec,
value:
((await this.getInputFn(options)) as
| Record<string, unknown>
@@ -115,15 +125,23 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
effects: T.Effects
input: Type
}): Promise<T.ActionResult | null> {
const parser =
this.cachedParser ?? (await this.inputSpec.build(options)).validator
let spec = {}
if (this.inputSpec) {
const prevInputSpec = this.prevInputSpec[options.effects.eventId!]
if (!prevInputSpec) {
throw new Error(
`getActionInput has not been called for EventID ${options.effects.eventId}`,
)
}
options.input = prevInputSpec.validator.unsafeCast(options.input)
spec = prevInputSpec.spec
}
return (
(await this.runFn({
effects: options.effects,
input: this.cachedParser
? this.cachedParser.unsafeCast(options.input)
: options.input,
})) || null
input: options.input,
spec,
})) ?? null
)
}
}

View File

@@ -1,6 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
export type ActionInput = {
eventId: Guid
spec: Record<string, unknown>
value: Record<string, unknown> | null
}

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
export type ProcedureId = { procedureId: Guid }
export type EventId = { eventId: Guid }

View File

@@ -2,7 +2,6 @@
import type { IpInfo } from "./IpInfo"
export type NetworkInterfaceInfo = {
inbound: boolean | null
outbound: boolean | null
public: boolean | null
ipInfo: IpInfo | null
}

View File

@@ -70,6 +70,7 @@ export { Duration } from "./Duration"
export { EchoParams } from "./EchoParams"
export { EditSignerParams } from "./EditSignerParams"
export { EncryptedWire } from "./EncryptedWire"
export { EventId } from "./EventId"
export { ExportActionParams } from "./ExportActionParams"
export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams"
export { FileType } from "./FileType"
@@ -155,7 +156,6 @@ export { PackageVersionInfo } from "./PackageVersionInfo"
export { PasswordType } from "./PasswordType"
export { PathOrUrl } from "./PathOrUrl"
export { Percentage } from "./Percentage"
export { ProcedureId } from "./ProcedureId"
export { Progress } from "./Progress"
export { ProgressUnits } from "./ProgressUnits"
export { Public } from "./Public"

View File

@@ -46,6 +46,7 @@ type EffectsTypeChecker<T extends StringObject = Effects> = {
describe("startosTypeValidation ", () => {
test(`checking the params match`, () => {
typeEquality<EffectsTypeChecker>({
eventId: {} as never,
child: "",
isInContext: {} as never,
onLeaveContext: () => {},

View File

@@ -104,7 +104,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
// prettier-ignore
type StartSdkEffectWrapper = {
[K in keyof Omit<Effects, NestedEffects | InterfaceEffects | MainUsedEffects | CallbackEffects | AlreadyExposed>]: (effects: Effects, ...args: Parameters<Effects[K]>) => ReturnType<Effects[K]>
[K in keyof Omit<Effects, "eventId" | NestedEffects | InterfaceEffects | MainUsedEffects | CallbackEffects | AlreadyExposed>]: (effects: Effects, ...args: Parameters<Effects[K]>) => ReturnType<Effects[K]>
}
const startSdkEffectWrapper: StartSdkEffectWrapper = {
restart: (effects, ...args) => effects.restart(...args),

View File

@@ -133,6 +133,7 @@ export class ActionInputModal {
readonly warning = this.context.data.actionInfo.metadata.warning
readonly pkgInfo = this.context.data.pkgInfo
readonly requestInfo = this.context.data.requestInfo
eventId: string | null = null
buttons: ActionButton<any>[] = [
{
@@ -151,6 +152,7 @@ export class ActionInputModal {
).pipe(
map(res => {
const originalValue = res.value || {}
this.eventId = res.eventId
return {
spec: res.spec,
@@ -174,7 +176,12 @@ export class ActionInputModal {
async execute(input: object) {
if (await this.checkConflicts(input)) {
await this.actionService.execute(this.pkgInfo.id, this.actionId, input)
await this.actionService.execute(
this.pkgInfo.id,
this.eventId,
this.actionId,
input,
)
this.context.$implicit.complete()
}
}

View File

@@ -67,9 +67,9 @@ export class ActionService {
},
})
.pipe(filter(Boolean))
.subscribe(() => this.execute(pkgInfo.id, actionInfo.id))
.subscribe(() => this.execute(pkgInfo.id, null, actionInfo.id))
} else {
this.execute(pkgInfo.id, actionInfo.id)
this.execute(pkgInfo.id, null, actionInfo.id)
}
}
} else {
@@ -96,14 +96,20 @@ export class ActionService {
}
}
async execute(packageId: string, actionId: string, input?: object) {
async execute(
packageId: string,
eventId: string | null,
actionId: string,
input?: object,
) {
const loader = this.loader.open('Loading').subscribe()
try {
const res = await this.api.runAction({
packageId,
eventId,
actionId,
input: input || null,
input: input ?? null,
})
if (!res) return

View File

@@ -347,12 +347,14 @@ export namespace RR {
export type GetActionInputReq = { packageId: string; actionId: string } // package.action.get-input
export type GetActionInputRes = {
eventId: string
spec: IST.InputSpec
value: object | null
}
export type ActionReq = {
packageId: string
eventId: string | null
actionId: string
input: object | null
} // package.action.run

View File

@@ -1108,6 +1108,7 @@ export class MockApiService extends ApiService {
): Promise<RR.GetActionInputRes> {
await pauseFor(2000)
return {
eventId: 'ANZXNWIFRTTBZ6T52KQPZILIQQODDHXQ',
value: Mock.MockConfig,
spec: await Mock.getActionInputSpec(),
}