mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
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:
@@ -52,7 +52,7 @@ export type Effects = {
|
||||
options: RequestActionParams,
|
||||
): Promise<null>
|
||||
clearRequests(
|
||||
options: { only: ActionId[] } | { except: ActionId[] },
|
||||
options: { only: string[] } | { except: string[] },
|
||||
): Promise<null>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as T from "../types"
|
||||
import * as IST from "../actions/input/inputSpecTypes"
|
||||
import { Action } from "./setupActions"
|
||||
|
||||
export type RunActionInput<Input> =
|
||||
| Input
|
||||
@@ -43,23 +44,62 @@ export const runAction = async <
|
||||
})
|
||||
}
|
||||
}
|
||||
type GetActionInputType<
|
||||
A extends Action<T.ActionId, any, any, Record<string, unknown>>,
|
||||
> = A extends Action<T.ActionId, any, any, infer I> ? I : never
|
||||
|
||||
// prettier-ignore
|
||||
export type ActionRequest<T extends Omit<T.ActionRequest, "packageId">> =
|
||||
T extends { when: { condition: "input-not-matches" } }
|
||||
? (T extends { input: T.ActionRequestInput } ? T : "input is required for condition 'input-not-matches'")
|
||||
: T
|
||||
type ActionRequestBase = {
|
||||
reason?: string
|
||||
replayId?: string
|
||||
}
|
||||
type ActionRequestInput<
|
||||
T extends Action<T.ActionId, any, any, Record<string, unknown>>,
|
||||
> = {
|
||||
kind: "partial"
|
||||
value: Partial<GetActionInputType<T>>
|
||||
}
|
||||
export type ActionRequestOptions<
|
||||
T extends Action<T.ActionId, any, any, Record<string, unknown>>,
|
||||
> = ActionRequestBase &
|
||||
(
|
||||
| {
|
||||
when?: Exclude<
|
||||
T.ActionRequestTrigger,
|
||||
{ condition: "input-not-matches" }
|
||||
>
|
||||
input?: ActionRequestInput<T>
|
||||
}
|
||||
| {
|
||||
when: T.ActionRequestTrigger & { condition: "input-not-matches" }
|
||||
input: ActionRequestInput<T>
|
||||
}
|
||||
)
|
||||
|
||||
const _validate: T.ActionRequest = {} as ActionRequestOptions<any> & {
|
||||
actionId: string
|
||||
packageId: string
|
||||
severity: T.ActionSeverity
|
||||
}
|
||||
|
||||
export const requestAction = <
|
||||
T extends Omit<T.ActionRequest, "packageId">,
|
||||
T extends Action<T.ActionId, any, any, Record<string, unknown>>,
|
||||
>(options: {
|
||||
effects: T.Effects
|
||||
request: ActionRequest<T> & { replayId?: string; packageId: T.PackageId }
|
||||
packageId: T.PackageId
|
||||
action: T
|
||||
severity: T.ActionSeverity
|
||||
options?: ActionRequestOptions<T>
|
||||
}) => {
|
||||
const request = options.request
|
||||
const request = options.options || {}
|
||||
const actionId = options.action.id
|
||||
const req = {
|
||||
...request,
|
||||
replayId: request.replayId || `${request.packageId}:${request.actionId}`,
|
||||
actionId,
|
||||
packageId: options.packageId,
|
||||
action: undefined,
|
||||
severity: options.severity,
|
||||
replayId: request.replayId || `${options.packageId}:${actionId}`,
|
||||
}
|
||||
delete req.action
|
||||
return options.effects.action.request(req)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export type Run<
|
||||
> = (options: {
|
||||
effects: T.Effects
|
||||
input: ExtractInputSpecType<A> & Record<string, any>
|
||||
}) => Promise<T.ActionResult | null>
|
||||
}) => Promise<T.ActionResult | null | void | undefined>
|
||||
export type GetInput<
|
||||
A extends
|
||||
| Record<string, any>
|
||||
@@ -19,7 +19,9 @@ export type GetInput<
|
||||
| InputSpec<Record<string, any>, never>,
|
||||
> = (options: {
|
||||
effects: T.Effects
|
||||
}) => Promise<null | (ExtractInputSpecType<A> & Record<string, any>)>
|
||||
}) => Promise<
|
||||
null | void | undefined | (ExtractInputSpecType<A> & Record<string, any>)
|
||||
>
|
||||
|
||||
export type MaybeFn<T> = T | ((options: { effects: T.Effects }) => Promise<T>)
|
||||
function callMaybeFn<T>(
|
||||
@@ -91,7 +93,7 @@ export class Action<
|
||||
): Action<Id, Store, {}, {}> {
|
||||
return new Action(
|
||||
id,
|
||||
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })),
|
||||
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })),
|
||||
{},
|
||||
async () => null,
|
||||
run,
|
||||
@@ -114,7 +116,7 @@ export class Action<
|
||||
effects: T.Effects
|
||||
input: Type
|
||||
}): Promise<T.ActionResult | null> {
|
||||
return this.runFn(options)
|
||||
return (await this.runFn(options)) || null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { VersionRange } from "../exver"
|
||||
|
||||
export class Dependency {
|
||||
constructor(
|
||||
readonly data:
|
||||
| {
|
||||
/** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */
|
||||
type: "running"
|
||||
/** The acceptable version range of the dependency. */
|
||||
versionRange: VersionRange
|
||||
/** A list of the dependency's health check IDs that must be passing for the service to be satisfied. */
|
||||
healthChecks: string[]
|
||||
}
|
||||
| {
|
||||
/** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */
|
||||
type: "exists"
|
||||
/** The acceptable version range of the dependency. */
|
||||
versionRange: VersionRange
|
||||
},
|
||||
) {}
|
||||
}
|
||||
@@ -1,22 +1,11 @@
|
||||
import * as T from "../types"
|
||||
import { once } from "../util"
|
||||
import { Dependency } from "./Dependency"
|
||||
|
||||
type DependencyType<Manifest extends T.Manifest> = {
|
||||
[K in keyof {
|
||||
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false
|
||||
? K
|
||||
: never
|
||||
}]: Dependency
|
||||
} & {
|
||||
[K in keyof {
|
||||
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true
|
||||
? K
|
||||
: never
|
||||
}]?: Dependency
|
||||
type DependencyType<Manifest extends T.SDKManifest> = {
|
||||
[K in keyof Manifest["dependencies"]]: Omit<T.DependencyRequirement, "id">
|
||||
}
|
||||
|
||||
export function setupDependencies<Manifest extends T.Manifest>(
|
||||
export function setupDependencies<Manifest extends T.SDKManifest>(
|
||||
fn: (options: { effects: T.Effects }) => Promise<DependencyType<Manifest>>,
|
||||
): (options: { effects: T.Effects }) => Promise<null> {
|
||||
const cell = { updater: async (_: { effects: T.Effects }) => null }
|
||||
@@ -30,24 +19,12 @@ export function setupDependencies<Manifest extends T.Manifest>(
|
||||
const dependencyType = await fn(options)
|
||||
return await options.effects.setDependencies({
|
||||
dependencies: Object.entries(dependencyType).map(
|
||||
([
|
||||
id,
|
||||
{
|
||||
data: { versionRange, ...x },
|
||||
},
|
||||
]) => ({
|
||||
id,
|
||||
...x,
|
||||
...(x.type === "running"
|
||||
? {
|
||||
kind: "running",
|
||||
healthChecks: x.healthChecks,
|
||||
}
|
||||
: {
|
||||
kind: "exists",
|
||||
}),
|
||||
versionRange: versionRange.toString(),
|
||||
}),
|
||||
([id, { versionRange, ...x }, ,]) =>
|
||||
({
|
||||
id,
|
||||
...x,
|
||||
versionRange: versionRange.toString(),
|
||||
}) as T.DependencyRequirement,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import type { ActionId } from "./ActionId"
|
||||
import type { ActionRequestInput } from "./ActionRequestInput"
|
||||
import type { ActionRequestTrigger } from "./ActionRequestTrigger"
|
||||
import type { ActionSeverity } from "./ActionSeverity"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type ActionRequest = {
|
||||
packageId: PackageId
|
||||
actionId: ActionId
|
||||
description?: string
|
||||
severity: ActionSeverity
|
||||
reason?: string
|
||||
when?: ActionRequestTrigger
|
||||
input?: ActionRequestInput
|
||||
}
|
||||
|
||||
7
sdk/base/lib/osBindings/ActionResult.ts
Normal file
7
sdk/base/lib/osBindings/ActionResult.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionResultV0 } from "./ActionResultV0"
|
||||
import type { ActionResultV1 } from "./ActionResultV1"
|
||||
|
||||
export type ActionResult =
|
||||
| ({ version: "0" } & ActionResultV0)
|
||||
| ({ version: "1" } & ActionResultV1)
|
||||
8
sdk/base/lib/osBindings/ActionResultV0.ts
Normal file
8
sdk/base/lib/osBindings/ActionResultV0.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionResultV0 = {
|
||||
message: string
|
||||
value: string | null
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
}
|
||||
18
sdk/base/lib/osBindings/ActionResultV1.ts
Normal file
18
sdk/base/lib/osBindings/ActionResultV1.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionResultV1 =
|
||||
| {
|
||||
type: "string"
|
||||
name: string
|
||||
value: string
|
||||
description: string | null
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
masked: boolean
|
||||
}
|
||||
| {
|
||||
type: "object"
|
||||
name: string
|
||||
value: Array<ActionResultV1>
|
||||
description?: string
|
||||
}
|
||||
3
sdk/base/lib/osBindings/ActionSeverity.ts
Normal file
3
sdk/base/lib/osBindings/ActionSeverity.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionSeverity = "critical" | "important"
|
||||
@@ -1,6 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionVisibility =
|
||||
| "hidden"
|
||||
| { disabled: { reason: string } }
|
||||
| "enabled"
|
||||
export type ActionVisibility = "hidden" | { disabled: string } | "enabled"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { ActionId } from "./ActionId"
|
||||
import type { ActionRequestInput } from "./ActionRequestInput"
|
||||
import type { ActionRequestTrigger } from "./ActionRequestTrigger"
|
||||
import type { ActionSeverity } from "./ActionSeverity"
|
||||
import type { PackageId } from "./PackageId"
|
||||
import type { ReplayId } from "./ReplayId"
|
||||
|
||||
@@ -9,7 +10,8 @@ export type RequestActionParams = {
|
||||
replayId: ReplayId
|
||||
packageId: PackageId
|
||||
actionId: ActionId
|
||||
description?: string
|
||||
severity: ActionSeverity
|
||||
reason?: string
|
||||
when?: ActionRequestTrigger
|
||||
input?: ActionRequestInput
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ export { ActionRequestEntry } from "./ActionRequestEntry"
|
||||
export { ActionRequestInput } from "./ActionRequestInput"
|
||||
export { ActionRequestTrigger } from "./ActionRequestTrigger"
|
||||
export { ActionRequest } from "./ActionRequest"
|
||||
export { ActionResult } from "./ActionResult"
|
||||
export { ActionResultV0 } from "./ActionResultV0"
|
||||
export { ActionResultV1 } from "./ActionResultV1"
|
||||
export { ActionSeverity } from "./ActionSeverity"
|
||||
export { ActionVisibility } from "./ActionVisibility"
|
||||
export { AddAdminParams } from "./AddAdminParams"
|
||||
export { AddAssetParams } from "./AddAssetParams"
|
||||
|
||||
@@ -33,10 +33,6 @@ export const SIGKILL: Signals = "SIGKILL"
|
||||
export const NO_TIMEOUT = -1
|
||||
|
||||
export type PathMaker = (options: { volume: string; path: string }) => string
|
||||
export type ExportedAction = (options: {
|
||||
effects: Effects
|
||||
input?: Record<string, unknown>
|
||||
}) => Promise<ActionResult>
|
||||
export type MaybePromise<A> = Promise<A> | A
|
||||
export namespace ExpectedExports {
|
||||
version: 1
|
||||
@@ -86,10 +82,6 @@ export namespace ExpectedExports {
|
||||
nextVersion: null | string
|
||||
}) => Promise<unknown>
|
||||
|
||||
export type properties = (options: {
|
||||
effects: Effects
|
||||
}) => Promise<PropertiesReturn>
|
||||
|
||||
export type manifest = Manifest
|
||||
|
||||
export type actions = Actions<
|
||||
@@ -105,7 +97,6 @@ export type ABI = {
|
||||
containerInit: ExpectedExports.containerInit
|
||||
packageInit: ExpectedExports.packageInit
|
||||
packageUninit: ExpectedExports.packageUninit
|
||||
properties: ExpectedExports.properties
|
||||
manifest: ExpectedExports.manifest
|
||||
actions: ExpectedExports.actions
|
||||
}
|
||||
@@ -177,58 +168,6 @@ export type ExposeServicePaths<Store = never> = {
|
||||
paths: ExposedStorePaths
|
||||
}
|
||||
|
||||
export type SdkPropertiesValue =
|
||||
| {
|
||||
type: "object"
|
||||
value: { [k: string]: SdkPropertiesValue }
|
||||
description?: string
|
||||
}
|
||||
| {
|
||||
type: "string"
|
||||
/** The value to display to the user */
|
||||
value: string
|
||||
/** A human readable description or explanation of the value */
|
||||
description?: string
|
||||
/** Whether or not to mask the value, for example, when displaying a password */
|
||||
masked?: boolean
|
||||
/** Whether or not to include a button for copying the value to clipboard */
|
||||
copyable?: boolean
|
||||
/** Whether or not to include a button for displaying the value as a QR code */
|
||||
qr?: boolean
|
||||
}
|
||||
|
||||
export type SdkPropertiesReturn = {
|
||||
[key: string]: SdkPropertiesValue
|
||||
}
|
||||
|
||||
export type PropertiesValue =
|
||||
| {
|
||||
/** The type of this value, either "string" or "object" */
|
||||
type: "object"
|
||||
/** A nested mapping of values. The user will experience this as a nested page with back button */
|
||||
value: { [k: string]: PropertiesValue }
|
||||
/** (optional) A human readable description of the new set of values */
|
||||
description: string | null
|
||||
}
|
||||
| {
|
||||
/** The type of this value, either "string" or "object" */
|
||||
type: "string"
|
||||
/** The value to display to the user */
|
||||
value: string
|
||||
/** A human readable description of the value */
|
||||
description: string | null
|
||||
/** Whether or not to mask the value, for example, when displaying a password */
|
||||
masked: boolean | null
|
||||
/** Whether or not to include a button for copying the value to clipboard */
|
||||
copyable: boolean | null
|
||||
/** Whether or not to include a button for displaying the value as a QR code */
|
||||
qr: boolean | null
|
||||
}
|
||||
|
||||
export type PropertiesReturn = {
|
||||
[key: string]: PropertiesValue
|
||||
}
|
||||
|
||||
export type EffectMethod<T extends StringObject = Effects> = {
|
||||
[K in keyof T]-?: K extends string
|
||||
? T[K] extends Function
|
||||
@@ -264,13 +203,6 @@ export type Metadata = {
|
||||
mode: number
|
||||
}
|
||||
|
||||
export type ActionResult = {
|
||||
version: "0"
|
||||
message: string
|
||||
value: string | null
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
}
|
||||
export type SetResult = {
|
||||
dependsOn: DependsOn
|
||||
signal: Signals
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"types": "./index.d.ts",
|
||||
"sideEffects": true,
|
||||
"scripts": {
|
||||
"peggy": "peggy --allowed-start-rules '*' --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs",
|
||||
"peggy": "peggy --allowed-start-rules \"*\" --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs",
|
||||
"test": "jest -c ./jest.config.js --coverage",
|
||||
"buildOutput": "npx prettier --write '**/*.ts'",
|
||||
"buildOutput": "npx prettier --write \"**/*.ts\"",
|
||||
"check": "tsc --noEmit",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user