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

@@ -2,18 +2,13 @@ import {
InstalledState,
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { Metric, NotificationLevel, RR, ServerNotifications } from './api.types'
import { NotificationLevel, RR, ServerNotifications } from './api.types'
import { BTC_ICON, LND_ICON, PROXY_ICON, REGISTRY_ICON } from './api-icons'
import { Log } from '@start9labs/shared'
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
import { T, ISB, IST } from '@start9labs/start-sdk'
import { GetPackagesRes } from '@start9labs/marketplace'
const mockBlake3Commitment: T.Blake3Commitment = {
hash: 'fakehash',
size: 0,
}
const mockMerkleArchiveCommitment: T.MerkleArchiveCommitment = {
rootSighash: 'fakehash',
rootMaxsize: 0,
@@ -880,25 +875,6 @@ export module Mock {
}
}
export function getAppMetrics() {
const metr: Metric = {
Metric1: {
value: Math.random(),
unit: 'mi/b',
},
Metric2: {
value: Math.random(),
unit: '%',
},
Metric3: {
value: 10.1,
unit: '%',
},
}
return metr
}
export const ServerLogs: Log[] = [
{
timestamp: '2022-07-28T03:52:54.808769Z',
@@ -946,15 +922,6 @@ export module Mock {
},
}
export const ActionResponse: T.ActionResult = {
version: '0',
message:
'Password changed successfully. If you lose your new password, you will be lost forever.',
value: 'NewPassword1234!',
copyable: true,
qr: true,
}
export const SshKeys: RR.GetSSHKeysRes = [
{
createdAt: new Date().toISOString(),
@@ -1082,11 +1049,26 @@ export module Mock {
},
}
export const PackageProperties: RR.GetPackagePropertiesRes<2> = {
version: 2,
data: {
lndconnect: {
export const ActionRes: RR.ActionRes = {
version: '1',
type: 'string',
name: 'New Password',
description:
'Action was run successfully Action was run successfully Action was run successfully Action was run successfully Action was run successfully',
copyable: true,
qr: true,
masked: true,
value: 'iwejdoiewdhbew',
}
export const ActionProperties: RR.ActionRes = {
version: '1',
type: 'object',
name: 'Properties',
value: [
{
type: 'string',
name: 'LND Connect',
description: 'This is some information about the thing.',
copyable: true,
qr: true,
@@ -1094,45 +1076,50 @@ export module Mock {
value:
'lndconnect://udlyfq2mxa4355pt7cqlrdipnvk2tsl4jtsdw7zaeekenufwcev2wlad.onion:10009?cert=MIICJTCCAcugAwIBAgIRAOyq85fqAiA3U3xOnwhH678wCgYIKoZIzj0EAwIwODEfMB0GAkUEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMB4XDTIwMTAyNjA3MzEyN1oXDTIxMTIyMTA3MzEyN1owODEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKqfhAMMZdY-eFnU5P4bGrQTSx0lo7m8u4V0yYkzUM6jlql_u31_mU2ovLTj56wnZApkEjoPl6fL2yasZA2wiy6OBtTCBsjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH_BAUwAwEB_zAdBgNVHQ4EFgQUYQ9uIO6spltnVCx4rLFL5BvBF9IwWwYDVR0RBFQwUoIMNTc0OTkwMzIyYzZlgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwSAAswCgYIKoZIzj0EAwIDSAAwRQIgVZH2Z2KlyAVY2Q2aIQl0nsvN-OEN49wreFwiBqlxNj4CIQD5_JbpuBFJuf81I5J0FQPtXY-4RppWOPZBb-y6-rkIUQ&macaroon=AgEDbG5kAusBAwoQuA8OUMeQ8Fr2h-f65OdXdRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFAoIbWFjYXJvb24SCGdlbmVyYXRlGhYKB21lc3NhZ2USBHJlYWQSBXdyaXRlGhcKCG9mZmNoYWluEgRyZWFkEgV3cml0ZRoWCgdvbmNoYWluEgRyZWFkEgV3cml0ZRoUCgVwZWVycxIEcmVhZBIFd3JpdGUaGAoGc2lnbmVyEghnZW5lcmF0ZRIEcmVhZAAABiCYsRUoUWuAHAiCSLbBR7b_qULDSl64R8LIU2aqNIyQfA',
},
Nested: {
{
type: 'object',
name: 'Nested Stuff',
description: 'This is a nested thing metric',
value: {
'Last Name': {
value: [
{
type: 'string',
name: 'Last Name',
description: 'The last name of the user',
copyable: true,
qr: true,
masked: false,
value: 'Hill',
},
Age: {
{
type: 'string',
name: 'Age',
description: 'The age of the user',
copyable: false,
qr: false,
masked: false,
value: '35',
},
Password: {
{
type: 'string',
name: 'Password',
description: 'A secret password',
copyable: true,
qr: false,
masked: true,
value: 'password123',
},
},
],
},
'Another Value': {
{
type: 'string',
name: 'Another Value',
description: 'Some more information about the service.',
copyable: false,
qr: true,
masked: false,
value: 'https://guessagain.com',
},
},
],
}
export const getActionInputSpec = async (): Promise<IST.InputSpec> =>
@@ -1692,7 +1679,7 @@ export module Mock {
},
actions: {
config: {
name: 'Bitcoin Config',
name: 'Set Config',
description: 'edit bitcoin.conf',
warning: null,
visibility: 'enabled',
@@ -1700,6 +1687,25 @@ export module Mock {
hasInput: true,
group: null,
},
properties: {
name: 'View Properties',
description: 'view important information about Bitcoin',
warning: null,
visibility: 'enabled',
allowedStatuses: 'any',
hasInput: false,
group: null,
},
test: {
name: 'Do Another Thing',
description:
'An example of an action that shows a warning and takes no input',
warning: 'careful running this action',
visibility: 'enabled',
allowedStatuses: 'only-running',
hasInput: false,
group: null,
},
},
serviceInterfaces: {
ui: {
@@ -1859,7 +1865,27 @@ export module Mock {
storeExposedDependents: [],
registry: 'https://registry.start9.com/',
developerKey: 'developer-key',
requestedActions: {},
requestedActions: {
'bitcoind-config': {
request: {
packageId: 'bitcoind',
actionId: 'config',
severity: 'critical',
reason:
'You must run Config before starting Bitcoin for the first time',
},
active: true,
},
'bitcoind-properties': {
request: {
packageId: 'bitcoind',
actionId: 'properties',
severity: 'important',
reason: 'Check out all the info about your Bitcoin node',
},
active: true,
},
},
}
export const bitcoinProxy: PackageDataEntry<InstalledState> = {
@@ -1992,7 +2018,27 @@ export module Mock {
storeExposedDependents: [],
registry: 'https://registry.start9.com/',
developerKey: 'developer-key',
requestedActions: {},
requestedActions: {
'bitcoind/config': {
active: true,
request: {
packageId: 'bitcoind',
actionId: 'config',
severity: 'critical',
reason: 'LND likes BTC a certain way',
input: {
kind: 'partial',
value: {
color: '#ffffff',
rpcsettings: {
rpcuser: 'lnd',
},
testnet: false,
},
},
},
},
},
}
export const LocalPkgs: { [key: string]: PackageDataEntry<InstalledState> } =

View File

@@ -1,5 +1,4 @@
import { Dump } from 'patch-db-client'
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
import { IST, T } from '@start9labs/start-sdk'
@@ -209,19 +208,12 @@ export module RR {
// package
export type GetPackagePropertiesReq = { id: string } // package.properties
export type GetPackagePropertiesRes<T extends number> =
PackagePropertiesVersioned<T>
export type GetPackageLogsReq = ServerLogsReq & { id: string } // package.logs
export type GetPackageLogsRes = LogsRes
export type FollowPackageLogsReq = FollowServerLogsReq & { id: string } // package.logs.follow
export type FollowPackageLogsRes = FollowServerLogsRes
export type GetPackageMetricsReq = { id: string } // package.metrics
export type GetPackageMetricsRes = Metric
export type InstallPackageReq = T.InstallParams
export type InstallPackageRes = null
@@ -231,13 +223,12 @@ export module RR {
value: object | null
}
export type RunActionReq = {
export type ActionReq = {
packageId: string
actionId: string
prev: GetActionInputRes | null
input: object | null
} // package.action.run
export type RunActionRes = T.ActionResult | null
export type ActionRes = (T.ActionResult & { version: '1' }) | null
export type RestorePackagesReq = {
// package.backup.restore
@@ -494,7 +485,7 @@ export type DependencyError =
| DependencyErrorNotInstalled
| DependencyErrorNotRunning
| DependencyErrorIncorrectVersion
| DependencyErrorConfigUnsatisfied
| DependencyErrorActionRequired
| DependencyErrorHealthChecksFailed
| DependencyErrorTransitive
@@ -512,8 +503,8 @@ export interface DependencyErrorIncorrectVersion {
received: string // version
}
export interface DependencyErrorConfigUnsatisfied {
type: 'configUnsatisfied'
export interface DependencyErrorActionRequired {
type: 'actionRequired'
}
export interface DependencyErrorHealthChecksFailed {

View File

@@ -113,10 +113,6 @@ export abstract class ApiService {
params: RR.GetServerMetricsReq,
): Promise<RR.GetServerMetricsRes>
abstract getPkgMetrics(
params: RR.GetPackageMetricsReq,
): Promise<RR.GetPackageMetricsRes>
abstract updateServer(url?: string): Promise<RR.UpdateServerRes>
abstract restartServer(
@@ -215,10 +211,6 @@ export abstract class ApiService {
// package
abstract getPackageProperties(
params: RR.GetPackagePropertiesReq,
): Promise<RR.GetPackagePropertiesRes<2>['data']>
abstract getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes>
@@ -235,7 +227,7 @@ export abstract class ApiService {
params: RR.GetActionInputReq,
): Promise<RR.GetActionInputRes>
abstract runAction(params: RR.RunActionReq): Promise<RR.RunActionRes>
abstract runAction(params: RR.ActionReq): Promise<RR.ActionRes>
abstract restorePackages(
params: RR.RestorePackagesReq,

View File

@@ -10,7 +10,6 @@ import {
import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source'
import { ApiService } from './embassy-api.service'
import { RR } from './api.types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { ConfigService } from '../config.service'
import { webSocket } from 'rxjs/webSocket'
import { Observable, filter, firstValueFrom } from 'rxjs'
@@ -436,14 +435,6 @@ export class LiveApiService extends ApiService {
// package
async getPackageProperties(
params: RR.GetPackagePropertiesReq,
): Promise<RR.GetPackagePropertiesRes<2>['data']> {
return this.rpcRequest({ method: 'package.properties', params }).then(
parsePropertiesPermissive,
)
}
async getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes> {
@@ -456,12 +447,6 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'package.logs.follow', params })
}
async getPkgMetrics(
params: RR.GetPackageMetricsReq,
): Promise<RR.GetPackageMetricsRes> {
return this.rpcRequest({ method: 'package.metrics', params })
}
async installPackage(
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes> {
@@ -474,7 +459,7 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'package.action.get-input', params })
}
async runAction(params: RR.RunActionReq): Promise<RR.RunActionRes> {
async runAction(params: RR.ActionReq): Promise<RR.ActionRes> {
return this.rpcRequest({ method: 'package.action.run', params })
}

View File

@@ -17,7 +17,6 @@ import {
UpdatingState,
} from 'src/app/services/patch-db/data-model'
import { CifsBackupTarget, RR } from './api.types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './api.fixures'
import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md'
import {
@@ -368,13 +367,6 @@ export class MockApiService extends ApiService {
return Mock.getServerMetrics()
}
async getPkgMetrics(
params: RR.GetServerMetricsReq,
): Promise<RR.GetPackageMetricsRes> {
await pauseFor(2000)
return Mock.getAppMetrics()
}
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
await pauseFor(2000)
const initialProgress = {
@@ -707,13 +699,6 @@ export class MockApiService extends ApiService {
// package
async getPackageProperties(
params: RR.GetPackagePropertiesReq,
): Promise<RR.GetPackagePropertiesRes<2>['data']> {
await pauseFor(2000)
return parsePropertiesPermissive(Mock.PackageProperties)
}
async getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes> {
@@ -795,9 +780,23 @@ export class MockApiService extends ApiService {
}
}
async runAction(params: RR.RunActionReq): Promise<RR.RunActionRes> {
async runAction(params: RR.ActionReq): Promise<RR.ActionRes> {
await pauseFor(2000)
return Mock.ActionResponse
if (params.actionId === 'properties') {
return Mock.ActionProperties
} else if (params.actionId === 'config') {
const patch: RemoveOperation[] = [
{
op: PatchOp.REMOVE,
path: `/packageData/${params.packageId}/requestedActions/${params.packageId}-config`,
},
]
this.mockRevision(patch)
return null
} else {
return Mock.ActionRes
}
}
async restorePackages(

View File

@@ -61,6 +61,7 @@ export const mockPatchData: DataModel = {
passwordHash:
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
versionCompat: '>=0.3.0 <=0.3.6',
postInitMigrationTodos: [],
statusInfo: {
backupProgress: null,
updated: false,
@@ -82,7 +83,6 @@ export const mockPatchData: DataModel = {
selected: null,
lastRegion: null,
},
postInitMigrationTodos: [],
},
packageData: {
bitcoind: {
@@ -107,7 +107,7 @@ export const mockPatchData: DataModel = {
// },
actions: {
config: {
name: 'Bitcoin Config',
name: 'Set Config',
description: 'edit bitcoin.conf',
warning: null,
visibility: 'enabled',
@@ -115,6 +115,25 @@ export const mockPatchData: DataModel = {
hasInput: true,
group: null,
},
properties: {
name: 'View Properties',
description: 'view important information about Bitcoin',
warning: null,
visibility: 'enabled',
allowedStatuses: 'any',
hasInput: false,
group: null,
},
test: {
name: 'Do Another Thing',
description:
'An example of an action that shows a warning and takes no input',
warning: 'careful running this action',
visibility: 'enabled',
allowedStatuses: 'only-running',
hasInput: false,
group: null,
},
},
serviceInterfaces: {
ui: {
@@ -274,7 +293,27 @@ export const mockPatchData: DataModel = {
storeExposedDependents: [],
registry: 'https://registry.start9.com/',
developerKey: 'developer-key',
requestedActions: {},
requestedActions: {
'bitcoind-config': {
request: {
packageId: 'bitcoind',
actionId: 'config',
severity: 'critical',
reason:
'You must run Config before starting Bitcoin for the first time',
},
active: true,
},
'bitcoind-properties': {
request: {
packageId: 'bitcoind',
actionId: 'properties',
severity: 'important',
reason: 'Check out all the info about your Bitcoin node',
},
active: true,
},
},
},
lnd: {
stateInfo: {
@@ -364,7 +403,27 @@ export const mockPatchData: DataModel = {
storeExposedDependents: [],
registry: 'https://registry.start9.com/',
developerKey: 'developer-key',
requestedActions: {},
requestedActions: {
'bitcoind/config': {
active: true,
request: {
packageId: 'bitcoind',
actionId: 'config',
severity: 'critical',
reason: 'LND likes BTC a certain way',
input: {
kind: 'partial',
value: {
color: '#ffffff',
rpcsettings: {
rpcuser: 'lnd',
},
testnet: false,
},
},
},
},
},
},
},
}