mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +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:
@@ -150,15 +150,15 @@ export function makeEffects(context: EffectContext): Effects {
|
||||
stack: new Error().stack,
|
||||
}) as ReturnType<T.Effects["bind"]>
|
||||
},
|
||||
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
|
||||
return rpcRound("clear-bindings", {}) as ReturnType<
|
||||
clearBindings(...[options]: Parameters<T.Effects["clearBindings"]>) {
|
||||
return rpcRound("clear-bindings", { ...options }) as ReturnType<
|
||||
T.Effects["clearBindings"]
|
||||
>
|
||||
},
|
||||
clearServiceInterfaces(
|
||||
...[]: Parameters<T.Effects["clearServiceInterfaces"]>
|
||||
...[options]: Parameters<T.Effects["clearServiceInterfaces"]>
|
||||
) {
|
||||
return rpcRound("clear-service-interfaces", {}) as ReturnType<
|
||||
return rpcRound("clear-service-interfaces", { ...options }) as ReturnType<
|
||||
T.Effects["clearServiceInterfaces"]
|
||||
>
|
||||
},
|
||||
|
||||
@@ -42,6 +42,7 @@ export const matchRpcResult = anyOf(
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
export type RpcResult = typeof matchRpcResult._TYPE
|
||||
type SocketResponse = ({ jsonrpc: "2.0"; id: IdType } & RpcResult) | null
|
||||
|
||||
@@ -88,7 +89,7 @@ const sandboxRunType = object(
|
||||
const callbackType = object({
|
||||
method: literal("callback"),
|
||||
params: object({
|
||||
callback: number,
|
||||
id: number,
|
||||
args: array,
|
||||
}),
|
||||
})
|
||||
@@ -288,8 +289,8 @@ export class RpcListener {
|
||||
|
||||
return handleRpc(id, result)
|
||||
})
|
||||
.when(callbackType, async ({ params: { callback, args } }) => {
|
||||
this.callCallback(callback, args)
|
||||
.when(callbackType, async ({ params: { id, args } }) => {
|
||||
this.callCallback(id, args)
|
||||
return null
|
||||
})
|
||||
.when(startType, async ({ id }) => {
|
||||
@@ -410,7 +411,7 @@ export class RpcListener {
|
||||
input: any,
|
||||
) {
|
||||
const ensureResultTypeShape = (
|
||||
result: void | T.ActionInput | T.PropertiesReturn | T.ActionResult | null,
|
||||
result: void | T.ActionInput | T.ActionResult | null,
|
||||
): { result: any } => {
|
||||
if (isResult(result)) return result
|
||||
return { result }
|
||||
@@ -428,8 +429,6 @@ export class RpcListener {
|
||||
return system.createBackup(effects, timeout || null)
|
||||
case "/backup/restore":
|
||||
return system.restoreBackup(effects, timeout || null)
|
||||
case "/properties":
|
||||
return system.properties(effects, timeout || null)
|
||||
case "/packageInit":
|
||||
return system.packageInit(effects, timeout || null)
|
||||
case "/packageUninit":
|
||||
|
||||
@@ -135,6 +135,34 @@ type OldGetConfigRes = {
|
||||
spec: OldConfigSpec
|
||||
}
|
||||
|
||||
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 PackagePropertiesV2 = {
|
||||
[name: string]: PackagePropertyObject | PackagePropertyString
|
||||
}
|
||||
@@ -157,7 +185,7 @@ export type PackagePropertyObject = {
|
||||
|
||||
const asProperty_ = (
|
||||
x: PackagePropertyString | PackagePropertyObject,
|
||||
): T.PropertiesValue => {
|
||||
): PropertiesValue => {
|
||||
if (x.type === "object") {
|
||||
return {
|
||||
...x,
|
||||
@@ -177,7 +205,7 @@ const asProperty_ = (
|
||||
...x,
|
||||
}
|
||||
}
|
||||
const asProperty = (x: PackagePropertiesV2): T.PropertiesReturn =>
|
||||
const asProperty = (x: PackagePropertiesV2): PropertiesReturn =>
|
||||
Object.fromEntries(
|
||||
Object.entries(x).map(([key, value]) => [key, asProperty_(value)]),
|
||||
)
|
||||
@@ -214,6 +242,31 @@ const matchProperties = object({
|
||||
data: matchPackageProperties,
|
||||
})
|
||||
|
||||
function convertProperties(
|
||||
name: string,
|
||||
value: PropertiesValue,
|
||||
): T.ActionResultV1 {
|
||||
if (value.type === "string") {
|
||||
return {
|
||||
type: "string",
|
||||
name,
|
||||
description: value.description,
|
||||
copyable: value.copyable || false,
|
||||
masked: value.masked || false,
|
||||
qr: value.qr || false,
|
||||
value: value.value,
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "object",
|
||||
name,
|
||||
description: value.description || undefined,
|
||||
value: Object.entries(value.value).map(([name, value]) =>
|
||||
convertProperties(name, value),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_REGISTRY = "https://registry.start9.com"
|
||||
export class SystemForEmbassy implements System {
|
||||
currentRunning: MainLoop | undefined
|
||||
@@ -245,6 +298,9 @@ export class SystemForEmbassy implements System {
|
||||
await this.dependenciesAutoconfig(effects, depId, null)
|
||||
}
|
||||
}
|
||||
await effects.setMainStatus({ status: "stopped" })
|
||||
await this.exportActions(effects)
|
||||
await this.exportNetwork(effects)
|
||||
}
|
||||
|
||||
async exit(): Promise<void> {
|
||||
@@ -281,10 +337,15 @@ export class SystemForEmbassy implements System {
|
||||
await effects.setDataVersion({
|
||||
version: ExtendedVersion.parseEmver(this.manifest.version).toString(),
|
||||
})
|
||||
} else {
|
||||
await effects.action.request({
|
||||
packageId: this.manifest.id,
|
||||
actionId: "config",
|
||||
severity: "critical",
|
||||
replayId: "needs-config",
|
||||
reason: "This service must be configured before it can be run",
|
||||
})
|
||||
}
|
||||
await effects.setMainStatus({ status: "stopped" })
|
||||
await this.exportActions(effects)
|
||||
await this.exportNetwork(effects)
|
||||
}
|
||||
async exportNetwork(effects: Effects) {
|
||||
for (const [id, interfaceValue] of Object.entries(
|
||||
@@ -375,6 +436,8 @@ export class SystemForEmbassy implements System {
|
||||
if (actionId === "config") {
|
||||
const config = await this.getConfig(effects, timeoutMs)
|
||||
return { 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
|
||||
@@ -393,6 +456,17 @@ export class SystemForEmbassy implements System {
|
||||
if (actionId === "config") {
|
||||
await this.setConfig(effects, input, timeoutMs)
|
||||
return null
|
||||
} else if (actionId === "properties") {
|
||||
return {
|
||||
version: "1",
|
||||
type: "object",
|
||||
name: "Properties",
|
||||
description:
|
||||
"Runtime information, credentials, and other values of interest",
|
||||
value: Object.entries(await this.properties(effects, timeoutMs)).map(
|
||||
([name, value]) => convertProperties(name, value),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
return this.action(effects, actionId, input, timeoutMs)
|
||||
}
|
||||
@@ -405,17 +479,21 @@ export class SystemForEmbassy implements System {
|
||||
if (manifest.config) {
|
||||
actions.config = {
|
||||
name: "Configure",
|
||||
description: "Edit the configuration of this service",
|
||||
description: `Customize ${manifest.title}`,
|
||||
"allowed-statuses": ["running", "stopped"],
|
||||
"input-spec": {},
|
||||
implementation: { type: "script", args: [] },
|
||||
}
|
||||
await effects.action.request({
|
||||
packageId: this.manifest.id,
|
||||
actionId: "config",
|
||||
replayId: "needs-config",
|
||||
description: "This service must be configured before it can be run",
|
||||
})
|
||||
}
|
||||
if (manifest.properties) {
|
||||
actions.properties = {
|
||||
name: "Properties",
|
||||
description:
|
||||
"Runtime information, credentials, and other values of interest",
|
||||
"allowed-statuses": ["running", "stopped"],
|
||||
"input-spec": null,
|
||||
implementation: { type: "script", args: [] },
|
||||
}
|
||||
}
|
||||
for (const [actionId, action] of Object.entries(actions)) {
|
||||
const hasRunning = !!action["allowed-statuses"].find(
|
||||
@@ -694,7 +772,7 @@ export class SystemForEmbassy implements System {
|
||||
async properties(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.PropertiesReturn> {
|
||||
): Promise<PropertiesReturn> {
|
||||
// TODO BLU-J set the properties ever so often
|
||||
const setConfigValue = this.manifest.properties
|
||||
if (!setConfigValue) throw new Error("There is no properties")
|
||||
@@ -867,7 +945,8 @@ export class SystemForEmbassy implements System {
|
||||
actionId: "config",
|
||||
packageId: id,
|
||||
replayId: `${id}/config`,
|
||||
description: `Configure this dependency for the needs of ${this.manifest.title}`,
|
||||
severity: "important",
|
||||
reason: `Configure this dependency for the needs of ${this.manifest.title}`,
|
||||
input: {
|
||||
kind: "partial",
|
||||
value: diff.diff,
|
||||
|
||||
@@ -57,12 +57,6 @@ export class SystemForStartOs implements System {
|
||||
effects,
|
||||
}))
|
||||
}
|
||||
properties(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.PropertiesReturn> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
getActionInput(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
|
||||
Reference in New Issue
Block a user