mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Merge branch 'integration/new-container-runtime' of github.com:Start9Labs/start-os into integration/new-container-runtime
This commit is contained in:
4
core/startos/bindings/ActionMetadata.ts
Normal file
4
core/startos/bindings/ActionMetadata.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AllowedStatuses } from "./AllowedStatuses";
|
||||||
|
|
||||||
|
export interface ActionMetadata { name: string, description: string, warning: string | null, disabled: boolean, input: {[key: string]: any}, allowedStatuses: AllowedStatuses, group: string | null, }
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type AllowedStatuses = "only-running" | "only-stopped" | "any" | "disabled";
|
export type AllowedStatuses = "only-running" | "only-stopped" | "any";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { AllowedStatuses } from "./AllowedStatuses";
|
import type { ActionMetadata } from "./ActionMetadata";
|
||||||
|
|
||||||
export interface ExportActionParams { name: string, description: string, id: string, input: {[key: string]: any}, allowedStatuses: AllowedStatuses, group: string | null, }
|
export interface ExportActionParams { id: string, metadata: ActionMetadata, }
|
||||||
@@ -385,6 +385,10 @@ impl NetService {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ip(&self) -> Ipv4Addr {
|
||||||
|
self.ip.to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for NetService {
|
impl Drop for NetService {
|
||||||
|
|||||||
@@ -260,16 +260,24 @@ enum AllowedStatuses {
|
|||||||
OnlyRunning, // onlyRunning
|
OnlyRunning, // onlyRunning
|
||||||
OnlyStopped,
|
OnlyStopped,
|
||||||
Any,
|
Any,
|
||||||
Disabled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ExportActionParams {
|
struct ExportActionParams {
|
||||||
|
#[ts(type = "string")]
|
||||||
|
id: ActionId,
|
||||||
|
metadata: ActionMetadata
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ActionMetadata {
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
id: String,
|
warning: Option<String>,
|
||||||
|
disabled: bool,
|
||||||
#[ts(type = "{[key: string]: any}")]
|
#[ts(type = "{[key: string]: any}")]
|
||||||
input: Value,
|
input: Value,
|
||||||
allowed_statuses: AllowedStatuses,
|
allowed_statuses: AllowedStatuses,
|
||||||
@@ -337,7 +345,18 @@ async fn get_system_smtp(
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr, Error> {
|
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Ipv4Addr, Error> {
|
||||||
todo!()
|
match context.0.upgrade() {
|
||||||
|
Some(c) => {
|
||||||
|
let net_service = c.persistent_container.net_service.lock().await;
|
||||||
|
Ok(net_service.get_ip())
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Upgrade on Weak<ServiceActorSeed> resulted in a None variant"),
|
||||||
|
crate::ErrorKind::NotFound
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async fn get_service_port_forward(
|
async fn get_service_port_forward(
|
||||||
context: EffectContext,
|
context: EffectContext,
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
| Config<any, never>,
|
| Config<any, never>,
|
||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
>(
|
>(
|
||||||
|
id: string,
|
||||||
metaData: Omit<ActionMetadata, "input"> & {
|
metaData: Omit<ActionMetadata, "input"> & {
|
||||||
input: Config<Type, Store> | Config<Type, never>
|
input: Config<Type, Store> | Config<Type, never>
|
||||||
},
|
},
|
||||||
@@ -209,7 +210,12 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
) => {
|
) => {
|
||||||
const { input, ...rest } = metaData
|
const { input, ...rest } = metaData
|
||||||
return createAction<Manifest, Store, ConfigType, Type>(rest, fn, input)
|
return createAction<Manifest, Store, ConfigType, Type>(
|
||||||
|
id,
|
||||||
|
rest,
|
||||||
|
fn,
|
||||||
|
input,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
configConstants: { smtpConfig },
|
configConstants: { smtpConfig },
|
||||||
createInterface: (
|
createInterface: (
|
||||||
@@ -238,6 +244,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
| Config<any, never>,
|
| Config<any, never>,
|
||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
>(
|
>(
|
||||||
|
id: string,
|
||||||
metaData: (options: {
|
metaData: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
}) => MaybePromise<Omit<ActionMetadata, "input">>,
|
}) => MaybePromise<Omit<ActionMetadata, "input">>,
|
||||||
@@ -248,6 +255,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
input: Config<Type, Store> | Config<Type, never>,
|
input: Config<Type, Store> | Config<Type, never>,
|
||||||
) => {
|
) => {
|
||||||
return createAction<Manifest, Store, ConfigType, Type>(
|
return createAction<Manifest, Store, ConfigType, Type>(
|
||||||
|
id,
|
||||||
metaData,
|
metaData,
|
||||||
fn,
|
fn,
|
||||||
input,
|
input,
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ export class CreatedAction<
|
|||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
> {
|
> {
|
||||||
private constructor(
|
private constructor(
|
||||||
public readonly myMetaData: MaybeFn<
|
public readonly id: string,
|
||||||
|
public readonly myMetadata: MaybeFn<
|
||||||
Manifest,
|
Manifest,
|
||||||
Store,
|
Store,
|
||||||
Omit<ActionMetadata, "input">
|
Omit<ActionMetadata, "input">
|
||||||
@@ -37,12 +38,14 @@ export class CreatedAction<
|
|||||||
| Config<any, never>,
|
| Config<any, never>,
|
||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
>(
|
>(
|
||||||
metaData: MaybeFn<Manifest, Store, Omit<ActionMetadata, "input">>,
|
id: string,
|
||||||
|
metadata: MaybeFn<Manifest, Store, Omit<ActionMetadata, "input">>,
|
||||||
fn: (options: { effects: Effects; input: Type }) => Promise<ActionResult>,
|
fn: (options: { effects: Effects; input: Type }) => Promise<ActionResult>,
|
||||||
inputConfig: Config<Type, Store> | Config<Type, never>,
|
inputConfig: Config<Type, Store> | Config<Type, never>,
|
||||||
) {
|
) {
|
||||||
return new CreatedAction<Manifest, Store, ConfigType, Type>(
|
return new CreatedAction<Manifest, Store, ConfigType, Type>(
|
||||||
metaData,
|
id,
|
||||||
|
metadata,
|
||||||
fn,
|
fn,
|
||||||
inputConfig as Config<Type, Store>,
|
inputConfig as Config<Type, Store>,
|
||||||
)
|
)
|
||||||
@@ -62,15 +65,15 @@ export class CreatedAction<
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async metaData(options: { effects: Effects }) {
|
async metadata(options: { effects: Effects }) {
|
||||||
if (this.myMetaData instanceof Function)
|
if (this.myMetadata instanceof Function)
|
||||||
return await this.myMetaData(options)
|
return await this.myMetadata(options)
|
||||||
return this.myMetaData
|
return this.myMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
async ActionMetadata(options: { effects: Effects }): Promise<ActionMetadata> {
|
async ActionMetadata(options: { effects: Effects }): Promise<ActionMetadata> {
|
||||||
return {
|
return {
|
||||||
...(await this.metaData(options)),
|
...(await this.metadata(options)),
|
||||||
input: await this.input.build(options),
|
input: await this.input.build(options),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { once } from "../util/once"
|
|
||||||
import { CreatedAction } from "./createAction"
|
import { CreatedAction } from "./createAction"
|
||||||
|
|
||||||
export function setupActions<Manifest extends SDKManifest, Store>(
|
export function setupActions<Manifest extends SDKManifest, Store>(
|
||||||
@@ -9,8 +8,7 @@ export function setupActions<Manifest extends SDKManifest, Store>(
|
|||||||
const myActions = async (options: { effects: Effects }) => {
|
const myActions = async (options: { effects: Effects }) => {
|
||||||
const actions: Record<string, CreatedAction<Manifest, Store, any>> = {}
|
const actions: Record<string, CreatedAction<Manifest, Store, any>> = {}
|
||||||
for (const action of createdActions) {
|
for (const action of createdActions) {
|
||||||
const actionMetadata = await action.metaData(options)
|
actions[action.id] = action
|
||||||
actions[actionMetadata.id] = action
|
|
||||||
}
|
}
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,9 +169,10 @@ export type DaemonReturned = {
|
|||||||
export type ActionMetadata = {
|
export type ActionMetadata = {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
id: string
|
warning: string | null
|
||||||
input: InputSpec
|
input: InputSpec
|
||||||
allowedStatuses: "only-running" | "only-stopped" | "any" | "disabled"
|
disabled: boolean
|
||||||
|
allowedStatuses: "only-running" | "only-stopped" | "any"
|
||||||
/**
|
/**
|
||||||
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
||||||
*/
|
*/
|
||||||
@@ -447,7 +448,7 @@ export type Effects = {
|
|||||||
*
|
*
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
exportAction(options: ActionMetadata): Promise<void>
|
exportAction(options: { id: string; metadata: ActionMetadata }): Promise<void>
|
||||||
/**
|
/**
|
||||||
* Remove an action that was exported. Used problably during main or during setConfig.
|
* Remove an action that was exported. Used problably during main or during setConfig.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<ion-item class="service-card" [routerLink]="['/marketplace', pkg.manifest.id]">
|
<ion-item class="service-card" [routerLink]="['/marketplace', pkg.manifest.id]">
|
||||||
<ion-thumbnail slot="start">
|
<ion-thumbnail slot="start">
|
||||||
<img alt="" [src]="pkg | mimeType | trustUrl" />
|
<img alt="" [src]="pkg.icon | trustUrl" />
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2 class="montserrat">
|
<h2 class="montserrat">
|
||||||
|
|||||||
@@ -4,17 +4,10 @@ import { IonicModule } from '@ionic/angular'
|
|||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import { SharedPipesModule } from '@start9labs/shared'
|
import { SharedPipesModule } from '@start9labs/shared'
|
||||||
import { ItemComponent } from './item.component'
|
import { ItemComponent } from './item.component'
|
||||||
import { MimeTypePipeModule } from '../../../pipes/mime-type.pipe'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ItemComponent],
|
declarations: [ItemComponent],
|
||||||
exports: [ItemComponent],
|
exports: [ItemComponent],
|
||||||
imports: [
|
imports: [CommonModule, IonicModule, RouterModule, SharedPipesModule],
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule,
|
|
||||||
SharedPipesModule,
|
|
||||||
MimeTypePipeModule,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class ItemModule {}
|
export class ItemModule {}
|
||||||
|
|||||||
@@ -18,15 +18,11 @@
|
|||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||||
<ng-container [ngSwitch]="dep.value.requirement.type">
|
<span *ngIf="dep.value.optional; else required">(optional)</span>
|
||||||
<span *ngSwitchCase="'required'">(required)</span>
|
<ng-template #required>
|
||||||
<span *ngSwitchCase="'opt-out'">(required by default)</span>
|
|
||||||
<span *ngSwitchCase="'opt-in'">(optional)</span>
|
<span *ngSwitchCase="'opt-in'">(optional)</span>
|
||||||
</ng-container>
|
</ng-template>
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
|
||||||
<small>{{ dep.value.version | displayEmver }}</small>
|
|
||||||
</p>
|
|
||||||
<p>{{ dep.value.description }}</p>
|
<p>{{ dep.value.description }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export class DependenciesComponent {
|
|||||||
pkg!: MarketplacePkg
|
pkg!: MarketplacePkg
|
||||||
|
|
||||||
getImg(key: string): string {
|
getImg(key: string): string {
|
||||||
// @TODO fix when registry api is updated to include mimetype in icon url
|
return this.pkg['dependency-metadata'][key].icon
|
||||||
return 'data:image/png;base64,' + this.pkg['dependency-metadata'][key].icon
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="header montserrat">
|
<div class="header montserrat">
|
||||||
<img class="logo" alt="" [src]="pkg | mimeType | trustUrl" />
|
<img class="logo" alt="" [src]="pkg.icon | trustUrl" />
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<h1 ticker class="title">{{ pkg.manifest.title }}</h1>
|
<h1 ticker class="title">{{ pkg.manifest.title }}</h1>
|
||||||
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
|
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
|
|
||||||
import { PackageComponent } from './package.component'
|
import { PackageComponent } from './package.component'
|
||||||
import { MimeTypePipeModule } from '../../../pipes/mime-type.pipe'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [PackageComponent],
|
declarations: [PackageComponent],
|
||||||
@@ -19,7 +18,6 @@ import { MimeTypePipeModule } from '../../../pipes/mime-type.pipe'
|
|||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
TickerModule,
|
TickerModule,
|
||||||
MimeTypePipeModule,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PackageModule {}
|
export class PackageModule {}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { NgModule, Pipe, PipeTransform } from '@angular/core'
|
|
||||||
import { MarketplacePkg } from '../types'
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'mimeType',
|
|
||||||
})
|
|
||||||
export class MimeTypePipe implements PipeTransform {
|
|
||||||
transform(pkg: MarketplacePkg): string {
|
|
||||||
if (pkg.manifest.assets.icon) {
|
|
||||||
switch (pkg.manifest.assets.icon.split('.').pop()) {
|
|
||||||
case 'png':
|
|
||||||
return `data:image/png;base64,${pkg.icon}`
|
|
||||||
case 'jpeg':
|
|
||||||
case 'jpg':
|
|
||||||
return `data:image/jpeg;base64,${pkg.icon}`
|
|
||||||
case 'gif':
|
|
||||||
return `data:image/gif;base64,${pkg.icon}`
|
|
||||||
case 'svg':
|
|
||||||
return `data:image/svg+xml;base64,${pkg.icon}`
|
|
||||||
default:
|
|
||||||
return `data:image/png;base64,${pkg.icon}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `data:image/png;base64,${pkg.icon}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [MimeTypePipe],
|
|
||||||
exports: [MimeTypePipe],
|
|
||||||
})
|
|
||||||
export class MimeTypePipeModule {}
|
|
||||||
@@ -22,7 +22,6 @@ export * from './pages/show/package/package.component'
|
|||||||
export * from './pages/show/package/package.module'
|
export * from './pages/show/package/package.module'
|
||||||
|
|
||||||
export * from './pipes/filter-packages.pipe'
|
export * from './pipes/filter-packages.pipe'
|
||||||
export * from './pipes/mime-type.pipe'
|
|
||||||
|
|
||||||
export * from './services/marketplace.service'
|
export * from './services/marketplace.service'
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ export interface StoreInfo {
|
|||||||
categories: string[]
|
categories: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StoreIdentityWithData = StoreData & StoreIdentity
|
||||||
|
|
||||||
export interface MarketplacePkg {
|
export interface MarketplacePkg {
|
||||||
icon: Url
|
icon: Url
|
||||||
license: Url
|
license: Url
|
||||||
instructions: Url
|
instructions: Url
|
||||||
manifest: MarketplaceManifest
|
manifest: Manifest
|
||||||
categories: string[]
|
categories: string[]
|
||||||
versions: string[]
|
versions: string[]
|
||||||
'dependency-metadata': {
|
'dependency-metadata': {
|
||||||
@@ -35,10 +37,11 @@ export interface MarketplacePkg {
|
|||||||
export interface DependencyMetadata {
|
export interface DependencyMetadata {
|
||||||
title: string
|
title: string
|
||||||
icon: Url
|
icon: Url
|
||||||
|
optional: boolean
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarketplaceManifest<T = unknown> {
|
export interface Manifest {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
version: string
|
version: string
|
||||||
@@ -47,12 +50,9 @@ export interface MarketplaceManifest<T = unknown> {
|
|||||||
short: string
|
short: string
|
||||||
long: string
|
long: string
|
||||||
}
|
}
|
||||||
assets: {
|
|
||||||
icon: string // ie. icon.png
|
|
||||||
}
|
|
||||||
replaces?: string[]
|
replaces?: string[]
|
||||||
'release-notes': string
|
'release-notes': string
|
||||||
license: string // type of license
|
license: string // name of license
|
||||||
'wrapper-repo': Url
|
'wrapper-repo': Url
|
||||||
'upstream-repo': Url
|
'upstream-repo': Url
|
||||||
'support-site': Url
|
'support-site': Url
|
||||||
@@ -65,23 +65,12 @@ export interface MarketplaceManifest<T = unknown> {
|
|||||||
start: string | null
|
start: string | null
|
||||||
stop: string | null
|
stop: string | null
|
||||||
}
|
}
|
||||||
dependencies: Record<string, Dependency<T>>
|
dependencies: Record<string, Dependency>
|
||||||
|
'os-version': string
|
||||||
|
'has-config': boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Dependency<T> {
|
export interface Dependency {
|
||||||
version: string
|
|
||||||
requirement:
|
|
||||||
| {
|
|
||||||
type: 'opt-in'
|
|
||||||
how: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'opt-out'
|
|
||||||
how: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'required'
|
|
||||||
}
|
|
||||||
description: string | null
|
description: string | null
|
||||||
config: T
|
optional: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class AppConfigPage {
|
|||||||
|
|
||||||
this.pkg = pkg
|
this.pkg = pkg
|
||||||
|
|
||||||
if (!this.pkg['state-info'].manifest.config) return
|
if (!this.pkg['state-info'].manifest['has-config']) return
|
||||||
|
|
||||||
let newConfig: object | undefined
|
let newConfig: object | undefined
|
||||||
let patch: Operation[] | undefined
|
let patch: Operation[] | undefined
|
||||||
@@ -152,7 +152,7 @@ export class AppConfigPage {
|
|||||||
|
|
||||||
this.saving = true
|
this.saving = true
|
||||||
|
|
||||||
if (hasCurrentDeps(this.pkg)) {
|
if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patch))) {
|
||||||
this.dryConfigure()
|
this.dryConfigure()
|
||||||
} else {
|
} else {
|
||||||
this.configure()
|
this.configure()
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding-top with-widgets">
|
<ion-content class="ion-padding-top with-widgets">
|
||||||
<ng-container *ngIf="pkg$ | async as pkg">
|
<ng-container *ngIf="pkg$ | async as pkg">
|
||||||
<ion-item-group
|
<ion-item-group *ngIf="pkg['state-info'].state === 'installed'">
|
||||||
*ngIf="pkg['state-info'].state === 'installed' && pkg['state-info'].manifest as manifest"
|
|
||||||
>
|
|
||||||
<!-- ** standard actions ** -->
|
<!-- ** standard actions ** -->
|
||||||
<ion-item-divider>Standard Actions</ion-item-divider>
|
<ion-item-divider>Standard Actions</ion-item-divider>
|
||||||
<app-actions-item
|
<app-actions-item
|
||||||
@@ -24,11 +22,11 @@
|
|||||||
></app-actions-item>
|
></app-actions-item>
|
||||||
|
|
||||||
<!-- ** specific actions ** -->
|
<!-- ** specific actions ** -->
|
||||||
<ion-item-divider *ngIf="!(manifest.actions | empty)">
|
<ion-item-divider *ngIf="!(pkg.actions | empty)">
|
||||||
Actions for {{ manifest.title }}
|
Actions for {{ pkg['state-info'].manifest.title }}
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<app-actions-item
|
<app-actions-item
|
||||||
*ngFor="let action of manifest.actions | keyvalue: asIsOrder"
|
*ngFor="let action of pkg.actions | keyvalue: asIsOrder"
|
||||||
[action]="{
|
[action]="{
|
||||||
name: action.value.name,
|
name: action.value.name,
|
||||||
description: action.value.description,
|
description: action.value.description,
|
||||||
|
|||||||
@@ -9,19 +9,17 @@ import {
|
|||||||
} from '@ionic/angular'
|
} from '@ionic/angular'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import {
|
||||||
Action,
|
|
||||||
DataModel,
|
DataModel,
|
||||||
InstalledState,
|
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
StateInfo,
|
|
||||||
Status,
|
Status,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||||
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
|
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||||
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
||||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||||
import { getManifest } from 'src/app/util/get-package-data'
|
import { getAllPackages, getManifest } from 'src/app/util/get-package-data'
|
||||||
|
import { ActionMetadata } from '@start9labs/start-sdk/cjs/sdk/lib/types'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-actions',
|
selector: 'app-actions',
|
||||||
@@ -44,19 +42,22 @@ export class AppActionsPage {
|
|||||||
private readonly patch: PatchDB<DataModel>,
|
private readonly patch: PatchDB<DataModel>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handleAction(status: Status, action: { key: string; value: Action }) {
|
async handleAction(
|
||||||
|
status: Status,
|
||||||
|
action: { key: string; value: ActionMetadata },
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
status &&
|
status &&
|
||||||
(action.value['allowed-statuses'] as PackageMainStatus[]).includes(
|
action.value.allowedStatuses.includes(
|
||||||
status.main.status,
|
status.main.status, // @TODO
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!isEmptyObject(action.value['input-spec'] || {})) {
|
if (!isEmptyObject(action.value.input || {})) {
|
||||||
const modal = await this.modalCtrl.create({
|
const modal = await this.modalCtrl.create({
|
||||||
component: GenericFormPage,
|
component: GenericFormPage,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
title: action.value.name,
|
title: action.value.name,
|
||||||
spec: action.value['input-spec'],
|
spec: action.value.input,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Execute',
|
text: 'Execute',
|
||||||
@@ -92,7 +93,7 @@ export class AppActionsPage {
|
|||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const statuses = [...action.value['allowed-statuses']]
|
const statuses = [...action.value.allowedStatuses] // @TODO
|
||||||
const last = statuses.pop()
|
const last = statuses.pop()
|
||||||
let statusesStr = statuses.join(', ')
|
let statusesStr = statuses.join(', ')
|
||||||
let error = ''
|
let error = ''
|
||||||
@@ -126,7 +127,7 @@ export class AppActionsPage {
|
|||||||
alerts.uninstall ||
|
alerts.uninstall ||
|
||||||
`Uninstalling ${title} will permanently delete its data`
|
`Uninstalling ${title} will permanently delete its data`
|
||||||
|
|
||||||
if (hasCurrentDeps(pkg)) {
|
if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patch))) {
|
||||||
message = `${message}. Services that depend on ${title} will no longer work properly and may crash`
|
message = `${message}. Services that depend on ${title} will no longer work properly and may crash`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { Routes, RouterModule } from '@angular/router'
|
import { Routes, RouterModule } from '@angular/router'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { AppShowPage } from './app-show.page'
|
import { AppShowPage } from './app-show.page'
|
||||||
import { EmverPipesModule, ResponsiveColModule } from '@start9labs/shared'
|
import {
|
||||||
|
EmptyPipe,
|
||||||
|
EmverPipesModule,
|
||||||
|
ResponsiveColModule,
|
||||||
|
SharedPipesModule,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||||
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
||||||
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
|
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
|
||||||
@@ -52,6 +57,7 @@ const routes: Routes = [
|
|||||||
UiPipeModule,
|
UiPipeModule,
|
||||||
ResponsiveColModule,
|
ResponsiveColModule,
|
||||||
StatusComponentModule,
|
StatusComponentModule,
|
||||||
|
SharedPipesModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppShowPageModule {}
|
export class AppShowPageModule {}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
>
|
>
|
||||||
<!-- ** health checks ** -->
|
<!-- ** health checks ** -->
|
||||||
<app-show-health-checks
|
<app-show-health-checks
|
||||||
*ngIf="status.primary === 'running'"
|
*ngIf="pkg.status.main.status === 'running'"
|
||||||
[manifest]="pkg['state-info'].manifest"
|
[healthChecks]="pkg.status.main.health"
|
||||||
></app-show-health-checks>
|
></app-show-health-checks>
|
||||||
<!-- ** dependencies ** -->
|
<!-- ** dependencies ** -->
|
||||||
<app-show-dependencies
|
<app-show-dependencies
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { PatchDB } from 'patch-db-client'
|
|||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
InstallingState,
|
InstallingState,
|
||||||
Manifest,
|
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
UpdatingState,
|
UpdatingState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
@@ -27,6 +26,7 @@ import {
|
|||||||
isRestoring,
|
isRestoring,
|
||||||
isUpdating,
|
isUpdating,
|
||||||
} from 'src/app/util/get-package-data'
|
} from 'src/app/util/get-package-data'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
export interface DependencyInfo {
|
export interface DependencyInfo {
|
||||||
id: string
|
id: string
|
||||||
@@ -97,6 +97,7 @@ export class AppShowPage {
|
|||||||
depErrors: PkgDependencyErrors,
|
depErrors: PkgDependencyErrors,
|
||||||
): DependencyInfo {
|
): DependencyInfo {
|
||||||
const { errorText, fixText, fixAction } = this.getDepErrors(
|
const { errorText, fixText, fixAction } = this.getDepErrors(
|
||||||
|
pkg,
|
||||||
manifest,
|
manifest,
|
||||||
depId,
|
depId,
|
||||||
depErrors,
|
depErrors,
|
||||||
@@ -106,7 +107,7 @@ export class AppShowPage {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: depId,
|
id: depId,
|
||||||
version: manifest.dependencies[depId].version, // do we want this version range?
|
version: pkg['current-dependencies'][depId].versionRange, // @TODO do we want this version range?
|
||||||
title: depInfo?.title || depId,
|
title: depInfo?.title || depId,
|
||||||
icon: depInfo?.icon || '',
|
icon: depInfo?.icon || '',
|
||||||
errorText: errorText
|
errorText: errorText
|
||||||
@@ -119,6 +120,7 @@ export class AppShowPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getDepErrors(
|
private getDepErrors(
|
||||||
|
pkg: PackageDataEntry,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
depId: string,
|
depId: string,
|
||||||
depErrors: PkgDependencyErrors,
|
depErrors: PkgDependencyErrors,
|
||||||
@@ -133,15 +135,15 @@ export class AppShowPage {
|
|||||||
if (depError.type === DependencyErrorType.NotInstalled) {
|
if (depError.type === DependencyErrorType.NotInstalled) {
|
||||||
errorText = 'Not installed'
|
errorText = 'Not installed'
|
||||||
fixText = 'Install'
|
fixText = 'Install'
|
||||||
fixAction = () => this.fixDep(manifest, 'install', depId)
|
fixAction = () => this.fixDep(pkg, manifest, 'install', depId)
|
||||||
} else if (depError.type === DependencyErrorType.IncorrectVersion) {
|
} else if (depError.type === DependencyErrorType.IncorrectVersion) {
|
||||||
errorText = 'Incorrect version'
|
errorText = 'Incorrect version'
|
||||||
fixText = 'Update'
|
fixText = 'Update'
|
||||||
fixAction = () => this.fixDep(manifest, 'update', depId)
|
fixAction = () => this.fixDep(pkg, manifest, 'update', depId)
|
||||||
} else if (depError.type === DependencyErrorType.ConfigUnsatisfied) {
|
} else if (depError.type === DependencyErrorType.ConfigUnsatisfied) {
|
||||||
errorText = 'Config not satisfied'
|
errorText = 'Config not satisfied'
|
||||||
fixText = 'Auto config'
|
fixText = 'Auto config'
|
||||||
fixAction = () => this.fixDep(manifest, 'configure', depId)
|
fixAction = () => this.fixDep(pkg, manifest, 'configure', depId)
|
||||||
} else if (depError.type === DependencyErrorType.NotRunning) {
|
} else if (depError.type === DependencyErrorType.NotRunning) {
|
||||||
errorText = 'Not running'
|
errorText = 'Not running'
|
||||||
fixText = 'Start'
|
fixText = 'Start'
|
||||||
@@ -160,6 +162,7 @@ export class AppShowPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async fixDep(
|
private async fixDep(
|
||||||
|
pkg: PackageDataEntry,
|
||||||
pkgManifest: Manifest,
|
pkgManifest: Manifest,
|
||||||
action: 'install' | 'update' | 'configure',
|
action: 'install' | 'update' | 'configure',
|
||||||
id: string,
|
id: string,
|
||||||
@@ -167,22 +170,21 @@ export class AppShowPage {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'install':
|
case 'install':
|
||||||
case 'update':
|
case 'update':
|
||||||
return this.installDep(pkgManifest, id)
|
return this.installDep(pkg, pkgManifest, id)
|
||||||
case 'configure':
|
case 'configure':
|
||||||
return this.configureDep(pkgManifest, id)
|
return this.configureDep(pkgManifest, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async installDep(
|
private async installDep(
|
||||||
|
pkg: PackageDataEntry,
|
||||||
pkgManifest: Manifest,
|
pkgManifest: Manifest,
|
||||||
depId: string,
|
depId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const version = pkgManifest.dependencies[depId].version
|
|
||||||
|
|
||||||
const dependentInfo: DependentInfo = {
|
const dependentInfo: DependentInfo = {
|
||||||
id: pkgManifest.id,
|
id: pkgManifest.id,
|
||||||
title: pkgManifest.title,
|
title: pkgManifest.title,
|
||||||
version,
|
version: pkg['current-dependencies'][depId].versionRange,
|
||||||
}
|
}
|
||||||
const navigationExtras: NavigationExtras = {
|
const navigationExtras: NavigationExtras = {
|
||||||
state: { dependentInfo },
|
state: { dependentInfo },
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ModalController, ToastController } from '@ionic/angular'
|
|||||||
import { copyToClipboard, MarkdownComponent } from '@start9labs/shared'
|
import { copyToClipboard, MarkdownComponent } from '@start9labs/shared'
|
||||||
import { from } from 'rxjs'
|
import { from } from 'rxjs'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { Manifest } from 'src/app/services/patch-db/data-model'
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show-additional',
|
selector: 'app-show-additional',
|
||||||
|
|||||||
@@ -1,91 +1,81 @@
|
|||||||
<ng-container
|
<ng-container *ngIf="!(healthChecks | empty)">
|
||||||
*ngIf="manifest | toHealthChecks | async | keyvalue: asIsOrder as checks"
|
<ion-item-divider>Health Checks</ion-item-divider>
|
||||||
>
|
<!-- connected -->
|
||||||
<ng-container *ngIf="checks.length">
|
<ng-container *ngIf="connected$ | async; else disconnected">
|
||||||
<ion-item-divider>Health Checks</ion-item-divider>
|
<ion-item *ngFor="let check of healthChecks | keyvalue">
|
||||||
<!-- connected -->
|
<!-- result -->
|
||||||
<ng-container *ngIf="connected$ | async; else disconnected">
|
<ng-container *ngIf="check.value.result as result; else noResult">
|
||||||
<ion-item *ngFor="let health of checks">
|
<ion-spinner
|
||||||
<!-- result -->
|
*ngIf="isLoading(result)"
|
||||||
<ng-container *ngIf="health.value?.result as result; else noResult">
|
class="icon-spinner"
|
||||||
<ion-spinner
|
color="primary"
|
||||||
*ngIf="isLoading(result)"
|
slot="start"
|
||||||
class="icon-spinner"
|
></ion-spinner>
|
||||||
color="primary"
|
<ion-icon
|
||||||
slot="start"
|
*ngIf="result === 'success'"
|
||||||
></ion-spinner>
|
slot="start"
|
||||||
<ion-icon
|
name="checkmark"
|
||||||
*ngIf="result === HealthResult.Success"
|
color="success"
|
||||||
slot="start"
|
></ion-icon>
|
||||||
name="checkmark"
|
<ion-icon
|
||||||
color="success"
|
*ngIf="result === 'failure'"
|
||||||
></ion-icon>
|
slot="start"
|
||||||
<ion-icon
|
name="warning-outline"
|
||||||
*ngIf="result === HealthResult.Failure"
|
color="warning"
|
||||||
slot="start"
|
></ion-icon>
|
||||||
name="warning-outline"
|
<ion-icon
|
||||||
color="warning"
|
*ngIf="result === 'disabled'"
|
||||||
></ion-icon>
|
slot="start"
|
||||||
<ion-icon
|
name="remove"
|
||||||
*ngIf="result === HealthResult.Disabled"
|
color="dark"
|
||||||
slot="start"
|
></ion-icon>
|
||||||
name="remove"
|
|
||||||
color="dark"
|
|
||||||
></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2 class="bold">
|
|
||||||
{{ manifest['health-checks'][health.key].name }}
|
|
||||||
</h2>
|
|
||||||
<ion-text [color]="result | healthColor">
|
|
||||||
<p>
|
|
||||||
<span *ngIf="isReady(result)">{{ result | titlecase }}</span>
|
|
||||||
<span *ngIf="result === HealthResult.Starting">...</span>
|
|
||||||
<span *ngIf="result === HealthResult.Failure">
|
|
||||||
{{ $any(health.value).error }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="result === HealthResult.Loading">
|
|
||||||
{{ $any(health.value).message }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
*ngIf="
|
|
||||||
result === HealthResult.Success &&
|
|
||||||
manifest['health-checks'][health.key]['success-message']
|
|
||||||
"
|
|
||||||
>
|
|
||||||
:
|
|
||||||
{{ manifest['health-checks'][health.key]['success-message'] }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</ion-text>
|
|
||||||
</ion-label>
|
|
||||||
</ng-container>
|
|
||||||
<!-- no result -->
|
|
||||||
<ng-template #noResult>
|
|
||||||
<ion-spinner
|
|
||||||
class="icon-spinner"
|
|
||||||
color="dark"
|
|
||||||
slot="start"
|
|
||||||
></ion-spinner>
|
|
||||||
<ion-label>
|
|
||||||
<h2 class="bold">
|
|
||||||
{{ manifest['health-checks'][health.key].name }}
|
|
||||||
</h2>
|
|
||||||
<p class="primary">Awaiting result...</p>
|
|
||||||
</ion-label>
|
|
||||||
</ng-template>
|
|
||||||
</ion-item>
|
|
||||||
</ng-container>
|
|
||||||
<!-- disconnected -->
|
|
||||||
<ng-template #disconnected>
|
|
||||||
<ion-item *ngFor="let health of checks">
|
|
||||||
<ion-avatar slot="start">
|
|
||||||
<ion-skeleton-text class="avatar"></ion-skeleton-text>
|
|
||||||
</ion-avatar>
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<ion-skeleton-text class="label"></ion-skeleton-text>
|
<h2 class="bold">
|
||||||
<ion-skeleton-text class="description"></ion-skeleton-text>
|
{{ check.value.name }}
|
||||||
|
</h2>
|
||||||
|
<ion-text [color]="result | healthColor">
|
||||||
|
<p>
|
||||||
|
<span *ngIf="isReady(result)">{{ result | titlecase }}</span>
|
||||||
|
<span *ngIf="result === 'starting'">...</span>
|
||||||
|
<span
|
||||||
|
*ngIf="
|
||||||
|
check.value.result === 'failure' ||
|
||||||
|
check.value.result === 'loading' ||
|
||||||
|
check.value.result === 'success'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ check.value.message }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</ion-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ng-container>
|
||||||
</ng-template>
|
<!-- no result -->
|
||||||
|
<ng-template #noResult>
|
||||||
|
<ion-spinner
|
||||||
|
class="icon-spinner"
|
||||||
|
color="dark"
|
||||||
|
slot="start"
|
||||||
|
></ion-spinner>
|
||||||
|
<ion-label>
|
||||||
|
<h2 class="bold">
|
||||||
|
{{ check.value.name }}
|
||||||
|
</h2>
|
||||||
|
<p class="primary">Awaiting result...</p>
|
||||||
|
</ion-label>
|
||||||
|
</ng-template>
|
||||||
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<!-- disconnected -->
|
||||||
|
<ng-template #disconnected>
|
||||||
|
<ion-item *ngFor="let health; in: checks">
|
||||||
|
<ion-avatar slot="start">
|
||||||
|
<ion-skeleton-text class="avatar"></ion-skeleton-text>
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<ion-skeleton-text class="label"></ion-skeleton-text>
|
||||||
|
<ion-skeleton-text class="description"></ion-skeleton-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { HealthResult, Manifest } from 'src/app/services/patch-db/data-model'
|
import {
|
||||||
|
HealthCheckResult,
|
||||||
|
HealthResult,
|
||||||
|
MainStatus,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show-health-checks',
|
selector: 'app-show-health-checks',
|
||||||
@@ -10,9 +15,7 @@ import { HealthResult, Manifest } from 'src/app/services/patch-db/data-model'
|
|||||||
})
|
})
|
||||||
export class AppShowHealthChecksComponent {
|
export class AppShowHealthChecksComponent {
|
||||||
@Input()
|
@Input()
|
||||||
manifest!: Manifest
|
healthChecks!: Record<string, HealthCheckResult>
|
||||||
|
|
||||||
HealthResult = HealthResult
|
|
||||||
|
|
||||||
readonly connected$ = this.connectionService.connected$
|
readonly connected$ = this.connectionService.connected$
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import {
|
|||||||
PrimaryStatus,
|
PrimaryStatus,
|
||||||
} from 'src/app/services/pkg-status-rendering.service'
|
} from 'src/app/services/pkg-status-rendering.service'
|
||||||
import {
|
import {
|
||||||
Manifest,
|
DataModel,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
PackageState,
|
|
||||||
Status,
|
Status,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
@@ -18,7 +17,13 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
import { ModalService } from 'src/app/services/modal.service'
|
import { ModalService } from 'src/app/services/modal.service'
|
||||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { isInstalled, getManifest } from 'src/app/util/get-package-data'
|
import {
|
||||||
|
isInstalled,
|
||||||
|
getManifest,
|
||||||
|
getAllPackages,
|
||||||
|
} from 'src/app/util/get-package-data'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show-status',
|
selector: 'app-show-status',
|
||||||
@@ -47,6 +52,7 @@ export class AppShowStatusComponent {
|
|||||||
private readonly launcherService: UiLauncherService,
|
private readonly launcherService: UiLauncherService,
|
||||||
private readonly modalService: ModalService,
|
private readonly modalService: ModalService,
|
||||||
private readonly connectionService: ConnectionService,
|
private readonly connectionService: ConnectionService,
|
||||||
|
private readonly patch: PatchDB<DataModel>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get interfaces(): PackageDataEntry['service-interfaces'] {
|
get interfaces(): PackageDataEntry['service-interfaces'] {
|
||||||
@@ -116,7 +122,7 @@ export class AppShowStatusComponent {
|
|||||||
const { title, alerts } = this.manifest
|
const { title, alerts } = this.manifest
|
||||||
|
|
||||||
let message = alerts.stop || ''
|
let message = alerts.stop || ''
|
||||||
if (hasCurrentDeps(this.pkg)) {
|
if (hasCurrentDeps(this.manifest.id, await getAllPackages(this.patch))) {
|
||||||
const depMessage = `Services that depend on ${title} will no longer work properly and may crash`
|
const depMessage = `Services that depend on ${title} will no longer work properly and may crash`
|
||||||
message = message ? `${message}.\n\n${depMessage}` : depMessage
|
message = message ? `${message}.\n\n${depMessage}` : depMessage
|
||||||
}
|
}
|
||||||
@@ -148,7 +154,7 @@ export class AppShowStatusComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async tryRestart(): Promise<void> {
|
async tryRestart(): Promise<void> {
|
||||||
if (hasCurrentDeps(this.pkg)) {
|
if (hasCurrentDeps(this.manifest.id, await getAllPackages(this.patch))) {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Warning',
|
header: 'Warning',
|
||||||
message: `Services that depend on ${this.manifest.title} may temporarily experiences issues`,
|
message: `Services that depend on ${this.manifest.title} may temporarily experiences issues`,
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import { MarkdownComponent } from '@start9labs/shared'
|
|||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
InstalledState,
|
InstalledState,
|
||||||
Manifest,
|
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { ModalService } from 'src/app/services/modal.service'
|
import { ModalService } from 'src/app/services/modal.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { from, map, Observable } from 'rxjs'
|
import { from, map, Observable } from 'rxjs'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { getManifest } from 'src/app/util/get-package-data'
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
export interface Button {
|
export interface Button {
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { Pipe, PipeTransform } from '@angular/core'
|
|||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
HealthCheckResult,
|
HealthCheckResult,
|
||||||
Manifest,
|
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { isEmptyObject } from '@start9labs/shared'
|
import { isEmptyObject } from '@start9labs/shared'
|
||||||
import { map, startWith } from 'rxjs/operators'
|
import { map, startWith } from 'rxjs/operators'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'toHealthChecks',
|
name: 'toHealthChecks',
|
||||||
@@ -18,26 +18,17 @@ export class ToHealthChecksPipe implements PipeTransform {
|
|||||||
|
|
||||||
transform(
|
transform(
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
): Observable<Record<string, HealthCheckResult | null>> | null {
|
): Observable<Record<string, HealthCheckResult | null> | null> {
|
||||||
const healthChecks = Object.keys(manifest['health-checks']).reduce(
|
return this.patch
|
||||||
(obj, key) => ({ ...obj, [key]: null }),
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
|
|
||||||
const healthChecks$ = this.patch
|
|
||||||
.watch$('package-data', manifest.id, 'status', 'main')
|
.watch$('package-data', manifest.id, 'status', 'main')
|
||||||
.pipe(
|
.pipe(
|
||||||
map(main => {
|
map(main => {
|
||||||
// Question: is this ok or do we have to use Object.keys
|
|
||||||
// to maintain order and the keys initially present in pkg?
|
|
||||||
return main.status === PackageMainStatus.Running &&
|
return main.status === PackageMainStatus.Running &&
|
||||||
!isEmptyObject(main.health)
|
!isEmptyObject(main.health)
|
||||||
? main.health
|
? main.health
|
||||||
: healthChecks
|
: null
|
||||||
}),
|
}),
|
||||||
startWith(healthChecks),
|
startWith(null),
|
||||||
)
|
)
|
||||||
|
|
||||||
return isEmptyObject(healthChecks) ? null : healthChecks$
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,14 @@
|
|||||||
View Installed
|
View Installed
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ng-container *ngIf="localPkg; else install">
|
<ng-container *ngIf="localPkg; else install">
|
||||||
<ng-container *ngIf="localPkg['state-info'].state === 'installed'">
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
localPkg['state-info'].state === 'installed' &&
|
||||||
|
(localPkg | toManifest) as manifest
|
||||||
|
"
|
||||||
|
>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === -1"
|
*ngIf="(manifest.version | compareEmver: pkg.manifest.version) === -1"
|
||||||
expand="block"
|
expand="block"
|
||||||
color="success"
|
color="success"
|
||||||
(click)="tryInstall()"
|
(click)="tryInstall()"
|
||||||
@@ -18,7 +23,7 @@
|
|||||||
Update
|
Update
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 1"
|
*ngIf="(manifest.version | compareEmver: pkg.manifest.version) === 1"
|
||||||
expand="block"
|
expand="block"
|
||||||
color="warning"
|
color="warning"
|
||||||
(click)="tryInstall()"
|
(click)="tryInstall()"
|
||||||
@@ -27,7 +32,7 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
<ng-container *ngIf="showDevTools$ | async">
|
<ng-container *ngIf="showDevTools$ | async">
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 0"
|
*ngIf="(manifest.version | compareEmver: pkg.manifest.version) === 0"
|
||||||
expand="block"
|
expand="block"
|
||||||
color="success"
|
color="success"
|
||||||
(click)="tryInstall()"
|
(click)="tryInstall()"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import { AlertController, LoadingController } from '@ionic/angular'
|
import { AlertController, LoadingController } from '@ionic/angular'
|
||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
|
Manifest,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageState,
|
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { ClientStorageService } from 'src/app/services/client-storage.service'
|
import { ClientStorageService } from 'src/app/services/client-storage.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
@@ -57,10 +57,6 @@ export class MarketplaceShowControlsComponent {
|
|||||||
private readonly patch: PatchDB<DataModel>,
|
private readonly patch: PatchDB<DataModel>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get localVersion(): string {
|
|
||||||
return this.localPkg ? getManifest(this.localPkg).version : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
async tryInstall() {
|
async tryInstall() {
|
||||||
const currentMarketplace = await firstValueFrom(
|
const currentMarketplace = await firstValueFrom(
|
||||||
this.marketplaceService.getSelectedHost$(),
|
this.marketplaceService.getSelectedHost$(),
|
||||||
@@ -80,10 +76,12 @@ export class MarketplaceShowControlsComponent {
|
|||||||
if (!proceed) return
|
if (!proceed) return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localManifest = getManifest(this.localPkg)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.emver.compare(this.localVersion, this.pkg.manifest.version) !==
|
this.emver.compare(localManifest.version, this.pkg.manifest.version) !==
|
||||||
0 &&
|
0 &&
|
||||||
hasCurrentDeps(this.localPkg)
|
hasCurrentDeps(localManifest.id, await getAllPackages(this.patch))
|
||||||
) {
|
) {
|
||||||
this.dryInstall(url)
|
this.dryInstall(url)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { MarketplaceShowPage } from './marketplace-show.page'
|
|||||||
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component'
|
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component'
|
||||||
import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component'
|
import { MarketplaceShowDependentComponent } from './marketplace-show-dependent/marketplace-show-dependent.component'
|
||||||
import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component'
|
import { MarketplaceShowControlsComponent } from './marketplace-show-controls/marketplace-show-controls.component'
|
||||||
|
import { UiPipeModule } from 'src/app/pipes/ui/ui.module'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -41,6 +42,7 @@ const routes: Routes = [
|
|||||||
AboutModule,
|
AboutModule,
|
||||||
DependenciesModule,
|
DependenciesModule,
|
||||||
AdditionalModule,
|
AdditionalModule,
|
||||||
|
UiPipeModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MarketplaceShowPage,
|
MarketplaceShowPage,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { isPlatform, LoadingController, NavController } from '@ionic/angular'
|
import { isPlatform, LoadingController, NavController } from '@ionic/angular'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { Manifest } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import cbor from 'cbor'
|
import cbor from 'cbor'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
interface Positions {
|
interface Positions {
|
||||||
[key: string]: [bigint, bigint] // [position, length]
|
[key: string]: [bigint, bigint] // [position, length]
|
||||||
@@ -133,9 +133,7 @@ export class SideloadPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getIcon(positions: Positions, file: Blob) {
|
async getIcon(positions: Positions, file: Blob) {
|
||||||
const contentType = `image/${this.toUpload.manifest?.assets.icon
|
const contentType = '' // @TODO
|
||||||
.split('.')
|
|
||||||
.pop()}`
|
|
||||||
const data = file.slice(
|
const data = file.slice(
|
||||||
Number(positions['icon'][0]),
|
Number(positions['icon'][0]),
|
||||||
Number(positions['icon'][0]) + Number(positions['icon'][1]),
|
Number(positions['icon'][0]) + Number(positions['icon'][1]),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { RouterModule, Routes } from '@angular/router'
|
|||||||
import { FilterUpdatesPipe, UpdatesPage } from './updates.page'
|
import { FilterUpdatesPipe, UpdatesPage } from './updates.page'
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||||
import {
|
import {
|
||||||
EmverDisplayPipe,
|
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
@@ -14,7 +13,6 @@ import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/sk
|
|||||||
import { RoundProgressModule } from 'angular-svg-round-progressbar'
|
import { RoundProgressModule } from 'angular-svg-round-progressbar'
|
||||||
import { InstallingProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
import { InstallingProgressPipeModule } from 'src/app/pipes/install-progress/install-progress.module'
|
||||||
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
import { StoreIconComponentModule } from 'src/app/components/store-icon/store-icon.component.module'
|
||||||
import { MimeTypePipeModule } from '@start9labs/marketplace'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -37,7 +35,6 @@ const routes: Routes = [
|
|||||||
InstallingProgressPipeModule,
|
InstallingProgressPipeModule,
|
||||||
StoreIconComponentModule,
|
StoreIconComponentModule,
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
MimeTypePipeModule,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class UpdatesPageModule {}
|
export class UpdatesPageModule {}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<ion-accordion *ngIf="data.localPkgs[pkg.manifest.id] as local">
|
<ion-accordion *ngIf="data.localPkgs[pkg.manifest.id] as local">
|
||||||
<ion-item lines="none" slot="header">
|
<ion-item lines="none" slot="header">
|
||||||
<ion-avatar slot="start" style="width: 50px; height: 50px">
|
<ion-avatar slot="start" style="width: 50px; height: 50px">
|
||||||
<img [src]="pkg | mimeType | trustUrl" />
|
<img [src]="pkg.icon | trustUrl" />
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1 style="line-height: 1.3">{{ pkg.manifest.title }}</h1>
|
<h1 style="line-height: 1.3">{{ pkg.manifest.title }}</h1>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
></ion-spinner>
|
></ion-spinner>
|
||||||
<ng-template #updateBtn>
|
<ng-template #updateBtn>
|
||||||
<ion-button
|
<ion-button
|
||||||
(click)="tryUpdate(pkg.manifest, host.url, local, $event)"
|
(click)="tryUpdate(pkg.manifest, host.url, $event)"
|
||||||
[color]="marketplaceService.updateErrors[pkg.manifest.id] ? 'danger' : 'tertiary'"
|
[color]="marketplaceService.updateErrors[pkg.manifest.id] ? 'danger' : 'tertiary'"
|
||||||
strong
|
strong
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
|||||||
import {
|
import {
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
Marketplace,
|
Marketplace,
|
||||||
MarketplaceManifest,
|
Manifest,
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
StoreIdentity,
|
StoreIdentity,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
@@ -70,12 +70,7 @@ export class UpdatesPage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryUpdate(
|
async tryUpdate(manifest: Manifest, url: string, e: Event): Promise<void> {
|
||||||
manifest: MarketplaceManifest,
|
|
||||||
url: string,
|
|
||||||
local: PackageDataEntry,
|
|
||||||
e: Event,
|
|
||||||
): Promise<void> {
|
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
const { id, version } = manifest
|
const { id, version } = manifest
|
||||||
@@ -83,14 +78,15 @@ export class UpdatesPage {
|
|||||||
delete this.marketplaceService.updateErrors[id]
|
delete this.marketplaceService.updateErrors[id]
|
||||||
this.marketplaceService.updateQueue[id] = true
|
this.marketplaceService.updateQueue[id] = true
|
||||||
|
|
||||||
if (hasCurrentDeps(local)) {
|
// manifest.id OK because same as local id for update
|
||||||
|
if (hasCurrentDeps(manifest.id, await getAllPackages(this.patch))) {
|
||||||
this.dryInstall(manifest, url)
|
this.dryInstall(manifest, url)
|
||||||
} else {
|
} else {
|
||||||
this.install(id, version, url)
|
this.install(id, version, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async dryInstall(manifest: MarketplaceManifest, url: string) {
|
private async dryInstall(manifest: Manifest, url: string) {
|
||||||
const { id, version, title } = manifest
|
const { id, version, title } = manifest
|
||||||
|
|
||||||
const breakages = dryUpdate(
|
const breakages = dryUpdate(
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { Manifest, PackageDataEntry } from '../../services/patch-db/data-model'
|
import { PackageDataEntry } from '../../services/patch-db/data-model'
|
||||||
import { hasUi } from '../../services/config.service'
|
import { hasUi } from '../../services/config.service'
|
||||||
import { getManifest } from 'src/app/util/get-package-data'
|
import { getManifest } from 'src/app/util/get-package-data'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'hasUi',
|
name: 'hasUi',
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
DockerIoFormat,
|
|
||||||
InstalledState,
|
InstalledState,
|
||||||
Manifest,
|
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
PackageState,
|
PackageState,
|
||||||
ServerStatusInfo,
|
ServerStatusInfo,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { Metric, NotificationLevel, RR, ServerNotifications } from './api.types'
|
import { Metric, NotificationLevel, RR, ServerNotifications } from './api.types'
|
||||||
|
|
||||||
import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons'
|
import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons'
|
||||||
import { DependencyMetadata, MarketplacePkg } from '@start9labs/marketplace'
|
import {
|
||||||
|
DependencyMetadata,
|
||||||
|
Manifest,
|
||||||
|
MarketplacePkg,
|
||||||
|
} from '@start9labs/marketplace'
|
||||||
import { Log } from '@start9labs/shared'
|
import { Log } from '@start9labs/shared'
|
||||||
|
|
||||||
export module Mock {
|
export module Mock {
|
||||||
@@ -57,14 +58,6 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
replaces: ['banks', 'governments'],
|
replaces: ['banks', 'governments'],
|
||||||
'release-notes': 'Taproot, Schnorr, and more.',
|
'release-notes': 'Taproot, Schnorr, and more.',
|
||||||
assets: {
|
|
||||||
icon: 'icon.png',
|
|
||||||
license: 'LICENSE.md',
|
|
||||||
instructions: 'INSTRUCTIONS.md',
|
|
||||||
docker_images: 'image.tar',
|
|
||||||
assets: './assets',
|
|
||||||
scripts: './scripts',
|
|
||||||
},
|
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper',
|
'wrapper-repo': 'https://github.com/start9labs/bitcoind-wrapper',
|
||||||
'upstream-repo': 'https://github.com/bitcoin/bitcoin',
|
'upstream-repo': 'https://github.com/bitcoin/bitcoin',
|
||||||
@@ -79,231 +72,9 @@ export module Mock {
|
|||||||
start: 'Starting Bitcoin is good for your health.',
|
start: 'Starting Bitcoin is good for your health.',
|
||||||
stop: null,
|
stop: null,
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
'os-version': '0.2.12',
|
||||||
config: {
|
|
||||||
get: null,
|
|
||||||
set: null,
|
|
||||||
},
|
|
||||||
volumes: {},
|
|
||||||
'min-os-version': '0.2.12',
|
|
||||||
backup: {
|
|
||||||
create: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
restore: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migrations: null,
|
|
||||||
actions: {
|
|
||||||
resync: {
|
|
||||||
name: 'Resync Blockchain',
|
|
||||||
description: 'Use this to resync the Bitcoin blockchain from genesis',
|
|
||||||
warning: 'This will take a couple of days.',
|
|
||||||
'allowed-statuses': [
|
|
||||||
PackageMainStatus.Running,
|
|
||||||
PackageMainStatus.Stopped,
|
|
||||||
],
|
|
||||||
implementation: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
'input-spec': {
|
|
||||||
reason: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Re-sync Reason',
|
|
||||||
description: 'Your reason for re-syncing. Why are you doing this?',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
pattern: '^[a-zA-Z]+$',
|
|
||||||
'pattern-description': 'Must contain only letters.',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Your Name',
|
|
||||||
description: 'Tell the class your name.',
|
|
||||||
nullable: true,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
warning: 'You may loose all your money by providing your name.',
|
|
||||||
},
|
|
||||||
notifications: {
|
|
||||||
name: 'Notification Preferences',
|
|
||||||
type: 'list',
|
|
||||||
subtype: 'enum',
|
|
||||||
description: 'how you want to be notified',
|
|
||||||
range: '[1,3]',
|
|
||||||
default: ['email'],
|
|
||||||
spec: {
|
|
||||||
'value-names': {
|
|
||||||
email: 'Email',
|
|
||||||
text: 'Text',
|
|
||||||
call: 'Call',
|
|
||||||
push: 'Push',
|
|
||||||
webhook: 'Webhook',
|
|
||||||
},
|
|
||||||
values: ['email', 'text', 'call', 'push', 'webhook'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'days-ago': {
|
|
||||||
type: 'number',
|
|
||||||
name: 'Days Ago',
|
|
||||||
description: 'Number of days to re-sync.',
|
|
||||||
nullable: false,
|
|
||||||
default: 100,
|
|
||||||
range: '[0, 9999]',
|
|
||||||
integral: true,
|
|
||||||
},
|
|
||||||
'top-speed': {
|
|
||||||
type: 'number',
|
|
||||||
name: 'Top Speed',
|
|
||||||
description: 'The fastest you can possibly run.',
|
|
||||||
nullable: false,
|
|
||||||
range: '[-1000, 1000]',
|
|
||||||
integral: false,
|
|
||||||
units: 'm/s',
|
|
||||||
},
|
|
||||||
testnet: {
|
|
||||||
name: 'Testnet',
|
|
||||||
type: 'boolean',
|
|
||||||
description:
|
|
||||||
'<ul><li>determines whether your node is running on testnet or mainnet</li></ul><script src="fake"></script>',
|
|
||||||
warning: 'Chain will have to resync!',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
randomEnum: {
|
|
||||||
name: 'Random Enum',
|
|
||||||
type: 'enum',
|
|
||||||
'value-names': {
|
|
||||||
null: 'Null',
|
|
||||||
good: 'Good',
|
|
||||||
bad: 'Bad',
|
|
||||||
ugly: 'Ugly',
|
|
||||||
},
|
|
||||||
default: 'null',
|
|
||||||
description: 'This is not even real.',
|
|
||||||
warning: 'Be careful changing this!',
|
|
||||||
values: ['null', 'good', 'bad', 'ugly'],
|
|
||||||
},
|
|
||||||
'emergency-contact': {
|
|
||||||
name: 'Emergency Contact',
|
|
||||||
type: 'object',
|
|
||||||
description: 'The person to contact in case of emergency.',
|
|
||||||
spec: {
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Name',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
pattern: '^[a-zA-Z]+$',
|
|
||||||
'pattern-description': 'Must contain only letters.',
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Email',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ips: {
|
|
||||||
name: 'Whitelist IPs',
|
|
||||||
type: 'list',
|
|
||||||
subtype: 'string',
|
|
||||||
description:
|
|
||||||
'external ip addresses that are authorized to access your Bitcoin node',
|
|
||||||
warning:
|
|
||||||
'Any IP you allow here will have RPC access to your Bitcoin node.',
|
|
||||||
range: '[1,10]',
|
|
||||||
default: ['192.168.1.1'],
|
|
||||||
spec: {
|
|
||||||
pattern: '^[0-9]{1,3}([,.][0-9]{1,3})?$',
|
|
||||||
'pattern-description': 'Must be a valid IP address',
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bitcoinNode: {
|
|
||||||
type: 'union',
|
|
||||||
default: 'internal',
|
|
||||||
tag: {
|
|
||||||
id: 'type',
|
|
||||||
'variant-names': {
|
|
||||||
internal: 'Internal',
|
|
||||||
external: 'External',
|
|
||||||
},
|
|
||||||
name: 'Bitcoin Node Settings',
|
|
||||||
description: 'The node settings',
|
|
||||||
warning: 'Careful changing this',
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
internal: {
|
|
||||||
'lan-address': {
|
|
||||||
name: 'LAN Address',
|
|
||||||
type: 'pointer',
|
|
||||||
subtype: 'package',
|
|
||||||
target: 'lan-address',
|
|
||||||
description: 'the lan address',
|
|
||||||
interface: 'tor-address',
|
|
||||||
'package-id': '12341234',
|
|
||||||
},
|
|
||||||
'friendly-name': {
|
|
||||||
name: 'Friendly Name',
|
|
||||||
type: 'string',
|
|
||||||
description: 'the lan address',
|
|
||||||
nullable: true,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
external: {
|
|
||||||
'public-domain': {
|
|
||||||
name: 'Public Domain',
|
|
||||||
type: 'string',
|
|
||||||
description: 'the public address of the node',
|
|
||||||
nullable: false,
|
|
||||||
default: 'bitcoinnode.com',
|
|
||||||
pattern: '.*',
|
|
||||||
'pattern-description': 'anything',
|
|
||||||
masked: false,
|
|
||||||
copyable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies: {},
|
dependencies: {},
|
||||||
|
'has-config': true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MockManifestLnd: Manifest = {
|
export const MockManifestLnd: Manifest = {
|
||||||
@@ -315,14 +86,6 @@ export module Mock {
|
|||||||
long: 'More info about LND. More info about LND. More info about LND.',
|
long: 'More info about LND. More info about LND. More info about LND.',
|
||||||
},
|
},
|
||||||
'release-notes': 'Dual funded channels!',
|
'release-notes': 'Dual funded channels!',
|
||||||
assets: {
|
|
||||||
icon: 'icon.png',
|
|
||||||
license: 'LICENSE.md',
|
|
||||||
instructions: 'INSTRUCTIONS.md',
|
|
||||||
docker_images: 'image.tar',
|
|
||||||
assets: './assets',
|
|
||||||
scripts: './scripts',
|
|
||||||
},
|
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper',
|
'wrapper-repo': 'https://github.com/start9labs/lnd-wrapper',
|
||||||
'upstream-repo': 'https://github.com/lightningnetwork/lnd',
|
'upstream-repo': 'https://github.com/lightningnetwork/lnd',
|
||||||
@@ -337,82 +100,19 @@ export module Mock {
|
|||||||
start: 'Starting LND is good for your health.',
|
start: 'Starting LND is good for your health.',
|
||||||
stop: null,
|
stop: null,
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
'os-version': '0.2.12',
|
||||||
config: {
|
|
||||||
get: null,
|
|
||||||
set: null,
|
|
||||||
},
|
|
||||||
volumes: {},
|
|
||||||
'min-os-version': '0.2.12',
|
|
||||||
backup: {
|
|
||||||
create: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
restore: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migrations: null,
|
|
||||||
actions: {
|
|
||||||
resync: {
|
|
||||||
name: 'Resync Network Graph',
|
|
||||||
description: 'Your node will resync its network graph.',
|
|
||||||
warning: 'This will take a couple hours.',
|
|
||||||
'allowed-statuses': [PackageMainStatus.Running],
|
|
||||||
implementation: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
'input-spec': null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
version: '=0.21.0',
|
|
||||||
description: 'LND needs bitcoin to live.',
|
description: 'LND needs bitcoin to live.',
|
||||||
requirement: {
|
optional: true,
|
||||||
type: 'opt-out',
|
|
||||||
how: 'You can use an external node from your server if you prefer.',
|
|
||||||
},
|
|
||||||
config: null,
|
|
||||||
},
|
},
|
||||||
'btc-rpc-proxy': {
|
'btc-rpc-proxy': {
|
||||||
version: '>=0.2.2',
|
|
||||||
description:
|
description:
|
||||||
'As long as Bitcoin is pruned, LND needs Bitcoin Proxy to fetch block over the P2P network.',
|
'As long as Bitcoin is pruned, LND needs Bitcoin Proxy to fetch block over the P2P network.',
|
||||||
requirement: {
|
optional: true,
|
||||||
type: 'opt-in',
|
|
||||||
how: `To use Proxy's user management system, go to LND config and select Bitcoin Proxy under Bitcoin config.`,
|
|
||||||
},
|
|
||||||
config: null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'has-config': true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MockManifestBitcoinProxy: Manifest = {
|
export const MockManifestBitcoinProxy: Manifest = {
|
||||||
@@ -425,14 +125,6 @@ export module Mock {
|
|||||||
long: 'More info about Bitcoin Proxy. More info about Bitcoin Proxy. More info about Bitcoin Proxy.',
|
long: 'More info about Bitcoin Proxy. More info about Bitcoin Proxy. More info about Bitcoin Proxy.',
|
||||||
},
|
},
|
||||||
'release-notes': 'Even better support for Bitcoin and wallets!',
|
'release-notes': 'Even better support for Bitcoin and wallets!',
|
||||||
assets: {
|
|
||||||
icon: 'icon.png',
|
|
||||||
license: 'LICENSE.md',
|
|
||||||
instructions: 'INSTRUCTIONS.md',
|
|
||||||
docker_images: 'image.tar',
|
|
||||||
assets: './assets',
|
|
||||||
scripts: './scripts',
|
|
||||||
},
|
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
'wrapper-repo': 'https://github.com/start9labs/btc-rpc-proxy-wrapper',
|
'wrapper-repo': 'https://github.com/start9labs/btc-rpc-proxy-wrapper',
|
||||||
'upstream-repo': 'https://github.com/Kixunil/btc-rpc-proxy',
|
'upstream-repo': 'https://github.com/Kixunil/btc-rpc-proxy',
|
||||||
@@ -446,84 +138,27 @@ export module Mock {
|
|||||||
start: null,
|
start: null,
|
||||||
stop: null,
|
stop: null,
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
'os-version': '0.2.12',
|
||||||
config: { get: {} as any, set: {} as any },
|
|
||||||
volumes: {},
|
|
||||||
'min-os-version': '0.2.12',
|
|
||||||
backup: {
|
|
||||||
create: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [''],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
restore: {
|
|
||||||
type: 'docker',
|
|
||||||
image: '',
|
|
||||||
system: true,
|
|
||||||
entrypoint: '',
|
|
||||||
args: [''],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Yaml,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migrations: null,
|
|
||||||
actions: {},
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
version: '>=0.20.0',
|
|
||||||
description: 'Bitcoin Proxy requires a Bitcoin node.',
|
description: 'Bitcoin Proxy requires a Bitcoin node.',
|
||||||
requirement: {
|
optional: false,
|
||||||
type: 'required',
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
check: {
|
|
||||||
type: 'docker',
|
|
||||||
image: 'alpine',
|
|
||||||
system: true,
|
|
||||||
entrypoint: 'true',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Cbor,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '10m',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
'auto-configure': {
|
|
||||||
type: 'docker',
|
|
||||||
image: 'alpine',
|
|
||||||
system: true,
|
|
||||||
entrypoint: 'cat',
|
|
||||||
args: [],
|
|
||||||
mounts: {},
|
|
||||||
'io-format': DockerIoFormat.Cbor,
|
|
||||||
inject: false,
|
|
||||||
'shm-size': '10m',
|
|
||||||
'sigterm-timeout': null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'has-config': false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BitcoinDep: DependencyMetadata = {
|
export const BitcoinDep: DependencyMetadata = {
|
||||||
title: 'Bitcoin Core',
|
title: 'Bitcoin Core',
|
||||||
icon: BTC_ICON,
|
icon: BTC_ICON,
|
||||||
|
optional: false,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProxyDep: DependencyMetadata = {
|
export const ProxyDep: DependencyMetadata = {
|
||||||
title: 'Bitcoin Proxy',
|
title: 'Bitcoin Proxy',
|
||||||
icon: PROXY_ICON,
|
icon: PROXY_ICON,
|
||||||
|
optional: true,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1765,6 +1400,7 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
'dependency-config-errors': {},
|
'dependency-config-errors': {},
|
||||||
},
|
},
|
||||||
|
actions: {}, // @TODO need mocks
|
||||||
'service-interfaces': {
|
'service-interfaces': {
|
||||||
ui: {
|
ui: {
|
||||||
id: 'ui',
|
id: 'ui',
|
||||||
@@ -1981,12 +1617,6 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'current-dependents': {
|
|
||||||
lnd: {
|
|
||||||
pointers: [],
|
|
||||||
'health-checks': [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'current-dependencies': {},
|
'current-dependencies': {},
|
||||||
'dependency-info': {},
|
'dependency-info': {},
|
||||||
'marketplace-url': 'https://registry.start9.com/',
|
'marketplace-url': 'https://registry.start9.com/',
|
||||||
@@ -2007,6 +1637,7 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
'dependency-config-errors': {},
|
'dependency-config-errors': {},
|
||||||
},
|
},
|
||||||
|
actions: {},
|
||||||
'service-interfaces': {
|
'service-interfaces': {
|
||||||
ui: {
|
ui: {
|
||||||
id: 'ui',
|
id: 'ui',
|
||||||
@@ -2115,15 +1746,9 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'current-dependents': {
|
|
||||||
lnd: {
|
|
||||||
pointers: [],
|
|
||||||
'health-checks': [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'current-dependencies': {
|
'current-dependencies': {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
pointers: [],
|
versionRange: '>=26.0.0',
|
||||||
'health-checks': [],
|
'health-checks': [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2153,6 +1778,7 @@ export module Mock {
|
|||||||
'btc-rpc-proxy': 'Username not found',
|
'btc-rpc-proxy': 'Username not found',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
actions: {},
|
||||||
'service-interfaces': {
|
'service-interfaces': {
|
||||||
grpc: {
|
grpc: {
|
||||||
id: 'grpc',
|
id: 'grpc',
|
||||||
@@ -2365,14 +1991,13 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'current-dependents': {},
|
|
||||||
'current-dependencies': {
|
'current-dependencies': {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
pointers: [],
|
versionRange: '>=26.0.0',
|
||||||
'health-checks': [],
|
'health-checks': [],
|
||||||
},
|
},
|
||||||
'btc-rpc-proxy': {
|
'btc-rpc-proxy': {
|
||||||
pointers: [],
|
versionRange: '>2.0.0', // @TODO
|
||||||
'health-checks': [],
|
'health-checks': [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Dump, Revision } from 'patch-db-client'
|
import { Dump, Revision } from 'patch-db-client'
|
||||||
import { MarketplacePkg, StoreInfo } from '@start9labs/marketplace'
|
import { Manifest, MarketplacePkg, StoreInfo } from '@start9labs/marketplace'
|
||||||
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
|
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
|
||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
HealthCheckResult,
|
HealthCheckResult,
|
||||||
Manifest,
|
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
|
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
|
||||||
|
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ export class MockApiService extends ApiService {
|
|||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
const id = ids[i]
|
const id = ids[i]
|
||||||
const appPath = `/package-data/${id}/installed/status/main/status`
|
const appPath = `/package-data/${id}/status/main/status`
|
||||||
const appPatch = [
|
const appPatch = [
|
||||||
{
|
{
|
||||||
op: PatchOp.REPLACE,
|
op: PatchOp.REPLACE,
|
||||||
@@ -735,7 +735,7 @@ export class MockApiService extends ApiService {
|
|||||||
const patch = [
|
const patch = [
|
||||||
{
|
{
|
||||||
op: PatchOp.REPLACE,
|
op: PatchOp.REPLACE,
|
||||||
path: `/package-data/${params.id}/installed/status/configured`,
|
path: `/package-data/${params.id}/status/configured`,
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -782,7 +782,7 @@ export class MockApiService extends ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
|
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
|
||||||
const path = `/package-data/${params.id}/installed/status/main`
|
const path = `/package-data/${params.id}/status/main`
|
||||||
|
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
@@ -865,7 +865,7 @@ export class MockApiService extends ApiService {
|
|||||||
): Promise<RR.RestartPackageRes> {
|
): Promise<RR.RestartPackageRes> {
|
||||||
// first enact stop
|
// first enact stop
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
const path = `/package-data/${params.id}/installed/status/main`
|
const path = `/package-data/${params.id}/status/main`
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const patch2: Operation<any>[] = [
|
const patch2: Operation<any>[] = [
|
||||||
@@ -941,7 +941,7 @@ export class MockApiService extends ApiService {
|
|||||||
|
|
||||||
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
const path = `/package-data/${params.id}/installed/status/main`
|
const path = `/package-data/${params.id}/status/main`
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const patch2 = [
|
const patch2 = [
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
DockerIoFormat,
|
|
||||||
HealthResult,
|
HealthResult,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
PackageState,
|
PackageState,
|
||||||
@@ -93,26 +92,33 @@ export const mockPatchData: DataModel = {
|
|||||||
started: '2021-06-14T20:49:17.774Z',
|
started: '2021-06-14T20:49:17.774Z',
|
||||||
health: {
|
health: {
|
||||||
'ephemeral-health-check': {
|
'ephemeral-health-check': {
|
||||||
|
name: 'Ephemeral Health Check',
|
||||||
result: HealthResult.Starting,
|
result: HealthResult.Starting,
|
||||||
},
|
},
|
||||||
'chain-state': {
|
'chain-state': {
|
||||||
|
name: 'Chain State',
|
||||||
result: HealthResult.Loading,
|
result: HealthResult.Loading,
|
||||||
message: 'Bitcoin is syncing from genesis',
|
message: 'Bitcoin is syncing from genesis',
|
||||||
},
|
},
|
||||||
'p2p-interface': {
|
'p2p-interface': {
|
||||||
|
name: 'P2P',
|
||||||
result: HealthResult.Success,
|
result: HealthResult.Success,
|
||||||
|
message: 'Health check successful',
|
||||||
},
|
},
|
||||||
'rpc-interface': {
|
'rpc-interface': {
|
||||||
|
name: 'RPC',
|
||||||
result: HealthResult.Failure,
|
result: HealthResult.Failure,
|
||||||
error: 'RPC interface unreachable.',
|
message: 'RPC interface unreachable.',
|
||||||
},
|
},
|
||||||
'unnecessary-health-check': {
|
'unnecessary-health-check': {
|
||||||
|
name: 'Unnecessary Health Check',
|
||||||
result: HealthResult.Disabled,
|
result: HealthResult.Disabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'dependency-config-errors': {},
|
'dependency-config-errors': {},
|
||||||
},
|
},
|
||||||
|
actions: {}, // @TODO
|
||||||
'service-interfaces': {
|
'service-interfaces': {
|
||||||
ui: {
|
ui: {
|
||||||
id: 'ui',
|
id: 'ui',
|
||||||
@@ -329,12 +335,6 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'current-dependents': {
|
|
||||||
lnd: {
|
|
||||||
pointers: [],
|
|
||||||
'health-checks': [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'current-dependencies': {},
|
'current-dependencies': {},
|
||||||
'dependency-info': {},
|
'dependency-info': {},
|
||||||
'marketplace-url': 'https://registry.start9.com/',
|
'marketplace-url': 'https://registry.start9.com/',
|
||||||
@@ -359,6 +359,7 @@ export const mockPatchData: DataModel = {
|
|||||||
'btc-rpc-proxy': 'This is a config unsatisfied error',
|
'btc-rpc-proxy': 'This is a config unsatisfied error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
actions: {},
|
||||||
'service-interfaces': {
|
'service-interfaces': {
|
||||||
grpc: {
|
grpc: {
|
||||||
id: 'grpc',
|
id: 'grpc',
|
||||||
@@ -569,14 +570,13 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'current-dependents': {},
|
|
||||||
'current-dependencies': {
|
'current-dependencies': {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
pointers: [],
|
versionRange: '>=26.0.0',
|
||||||
'health-checks': [],
|
'health-checks': [],
|
||||||
},
|
},
|
||||||
'btc-rpc-proxy': {
|
'btc-rpc-proxy': {
|
||||||
pointers: [],
|
versionRange: '>2.0.0',
|
||||||
'health-checks': [],
|
'health-checks': [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,19 +85,14 @@ export class DepErrorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkgManifest = pkg['state-info'].manifest
|
const versionRange = pkg['current-dependencies'][depId].versionRange
|
||||||
const depManifest = dep['state-info'].manifest
|
const depManifest = dep['state-info'].manifest
|
||||||
|
|
||||||
// incorrect version
|
// incorrect version
|
||||||
if (
|
if (!this.emver.satisfies(depManifest.version, versionRange)) {
|
||||||
!this.emver.satisfies(
|
|
||||||
depManifest.version,
|
|
||||||
pkgManifest.dependencies[depId].version,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
type: DependencyErrorType.IncorrectVersion,
|
type: DependencyErrorType.IncorrectVersion,
|
||||||
expected: pkgManifest.dependencies[depId].version,
|
expected: versionRange,
|
||||||
received: depManifest.version,
|
received: depManifest.version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
|
||||||
import { Url } from '@start9labs/shared'
|
import { Url } from '@start9labs/shared'
|
||||||
import { MarketplaceManifest } from '@start9labs/marketplace'
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info'
|
import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info'
|
||||||
import { types } from '@start9labs/start-sdk'
|
import { types } from '@start9labs/start-sdk'
|
||||||
|
import { InputSpec } from '@start9labs/start-sdk/cjs/sdk/lib/config/configTypes'
|
||||||
|
import { ActionMetadata } from '@start9labs/start-sdk/cjs/sdk/lib/types'
|
||||||
type ServiceInterfaceWithHostInfo = types.ServiceInterfaceWithHostInfo
|
type ServiceInterfaceWithHostInfo = types.ServiceInterfaceWithHostInfo
|
||||||
|
|
||||||
export interface DataModel {
|
export interface DataModel {
|
||||||
@@ -111,8 +112,8 @@ export type PackageDataEntry<T extends StateInfo = StateInfo> = {
|
|||||||
'state-info': T
|
'state-info': T
|
||||||
icon: Url
|
icon: Url
|
||||||
status: Status
|
status: Status
|
||||||
|
actions: Record<string, ActionMetadata>
|
||||||
'last-backup': string | null
|
'last-backup': string | null
|
||||||
'current-dependents': { [id: string]: CurrentDependencyInfo }
|
|
||||||
'current-dependencies': { [id: string]: CurrentDependencyInfo }
|
'current-dependencies': { [id: string]: CurrentDependencyInfo }
|
||||||
'dependency-info': {
|
'dependency-info': {
|
||||||
[id: string]: {
|
[id: string]: {
|
||||||
@@ -152,125 +153,10 @@ export enum PackageState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrentDependencyInfo {
|
export interface CurrentDependencyInfo {
|
||||||
pointers: any[]
|
versionRange: string
|
||||||
'health-checks': string[] // array of health check IDs
|
'health-checks': string[] // array of health check IDs
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Manifest extends MarketplaceManifest<DependencyConfig | null> {
|
|
||||||
assets: {
|
|
||||||
license: string // filename
|
|
||||||
instructions: string // filename
|
|
||||||
icon: string // filename
|
|
||||||
docker_images: string // filename
|
|
||||||
assets: string // path to assets folder
|
|
||||||
scripts: string // path to scripts folder
|
|
||||||
}
|
|
||||||
'health-checks': Record<
|
|
||||||
string,
|
|
||||||
ActionImpl & { name: string; 'success-message': string | null }
|
|
||||||
>
|
|
||||||
config: ConfigActions | null
|
|
||||||
volumes: Record<string, Volume>
|
|
||||||
'min-os-version': string
|
|
||||||
backup: BackupActions
|
|
||||||
migrations: Migrations | null
|
|
||||||
actions: Record<string, Action>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DependencyConfig {
|
|
||||||
check: ActionImpl
|
|
||||||
'auto-configure': ActionImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ActionImpl {
|
|
||||||
type: 'docker'
|
|
||||||
image: string
|
|
||||||
system: boolean
|
|
||||||
entrypoint: string
|
|
||||||
args: string[]
|
|
||||||
mounts: { [id: string]: string }
|
|
||||||
'io-format': DockerIoFormat | null
|
|
||||||
inject: boolean
|
|
||||||
'shm-size': string
|
|
||||||
'sigterm-timeout': string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum DockerIoFormat {
|
|
||||||
Json = 'json',
|
|
||||||
Yaml = 'yaml',
|
|
||||||
Cbor = 'cbor',
|
|
||||||
Toml = 'toml',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfigActions {
|
|
||||||
get: ActionImpl | null
|
|
||||||
set: ActionImpl | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Volume = VolumeData
|
|
||||||
|
|
||||||
export interface VolumeData {
|
|
||||||
type: VolumeType.Data
|
|
||||||
readonly: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VolumeAssets {
|
|
||||||
type: VolumeType.Assets
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VolumePointer {
|
|
||||||
type: VolumeType.Pointer
|
|
||||||
'package-id': string
|
|
||||||
'volume-id': string
|
|
||||||
path: string
|
|
||||||
readonly: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VolumeCertificate {
|
|
||||||
type: VolumeType.Certificate
|
|
||||||
'interface-id': string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VolumeBackup {
|
|
||||||
type: VolumeType.Backup
|
|
||||||
readonly: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum VolumeType {
|
|
||||||
Data = 'data',
|
|
||||||
Assets = 'assets',
|
|
||||||
Pointer = 'pointer',
|
|
||||||
Certificate = 'certificate',
|
|
||||||
Backup = 'backup',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TorConfig {
|
|
||||||
'port-mapping': { [port: number]: number }
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LanConfig = {
|
|
||||||
[port: number]: { ssl: boolean; mapping: number }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BackupActions {
|
|
||||||
create: ActionImpl
|
|
||||||
restore: ActionImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Migrations {
|
|
||||||
from: { [versionRange: string]: ActionImpl }
|
|
||||||
to: { [versionRange: string]: ActionImpl }
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Action {
|
|
||||||
name: string
|
|
||||||
description: string
|
|
||||||
warning: string | null
|
|
||||||
implementation: ActionImpl
|
|
||||||
'allowed-statuses': (PackageMainStatus.Stopped | PackageMainStatus.Running)[]
|
|
||||||
'input-spec': ConfigSpec | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Status {
|
export interface Status {
|
||||||
configured: boolean
|
configured: boolean
|
||||||
main: MainStatus
|
main: MainStatus
|
||||||
@@ -302,7 +188,7 @@ export interface MainStatusStarting {
|
|||||||
export interface MainStatusRunning {
|
export interface MainStatusRunning {
|
||||||
status: PackageMainStatus.Running
|
status: PackageMainStatus.Running
|
||||||
started: string // UTC date string
|
started: string // UTC date string
|
||||||
health: { [id: string]: HealthCheckResult }
|
health: Record<string, HealthCheckResult>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainStatusBackingUp {
|
export interface MainStatusBackingUp {
|
||||||
@@ -323,12 +209,13 @@ export enum PackageMainStatus {
|
|||||||
Restarting = 'restarting',
|
Restarting = 'restarting',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HealthCheckResult =
|
export type HealthCheckResult = { name: string } & (
|
||||||
| HealthCheckResultStarting
|
| HealthCheckResultStarting
|
||||||
| HealthCheckResultLoading
|
| HealthCheckResultLoading
|
||||||
| HealthCheckResultDisabled
|
| HealthCheckResultDisabled
|
||||||
| HealthCheckResultSuccess
|
| HealthCheckResultSuccess
|
||||||
| HealthCheckResultFailure
|
| HealthCheckResultFailure
|
||||||
|
)
|
||||||
|
|
||||||
export enum HealthResult {
|
export enum HealthResult {
|
||||||
Starting = 'starting',
|
Starting = 'starting',
|
||||||
@@ -348,6 +235,7 @@ export interface HealthCheckResultDisabled {
|
|||||||
|
|
||||||
export interface HealthCheckResultSuccess {
|
export interface HealthCheckResultSuccess {
|
||||||
result: HealthResult.Success
|
result: HealthResult.Success
|
||||||
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckResultLoading {
|
export interface HealthCheckResultLoading {
|
||||||
@@ -357,7 +245,7 @@ export interface HealthCheckResultLoading {
|
|||||||
|
|
||||||
export interface HealthCheckResultFailure {
|
export interface HealthCheckResultFailure {
|
||||||
result: HealthResult.Failure
|
result: HealthResult.Failure
|
||||||
error: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstallingInfo = {
|
export type InstallingInfo = {
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ export function renderPkgStatus(
|
|||||||
if (pkg['state-info'].state === PackageState.Installed) {
|
if (pkg['state-info'].state === PackageState.Installed) {
|
||||||
primary = getPrimaryStatus(pkg.status)
|
primary = getPrimaryStatus(pkg.status)
|
||||||
dependency = getDependencyStatus(depErrors)
|
dependency = getDependencyStatus(depErrors)
|
||||||
health = getHealthStatus(
|
health = getHealthStatus(pkg.status)
|
||||||
pkg.status,
|
|
||||||
!isEmptyObject(pkg['state-info'].manifest['health-checks']),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
primary = pkg['state-info'].state as string as PrimaryStatus
|
primary = pkg['state-info'].state as string as PrimaryStatus
|
||||||
}
|
}
|
||||||
@@ -52,29 +49,26 @@ function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus {
|
|||||||
: DependencyStatus.Satisfied
|
: DependencyStatus.Satisfied
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHealthStatus(
|
function getHealthStatus(status: Status): HealthStatus | null {
|
||||||
status: Status,
|
|
||||||
hasHealthChecks: boolean,
|
|
||||||
): HealthStatus | null {
|
|
||||||
if (status.main.status !== PackageMainStatus.Running || !status.main.health) {
|
if (status.main.status !== PackageMainStatus.Running || !status.main.health) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = Object.values(status.main.health)
|
const values = Object.values(status.main.health)
|
||||||
|
|
||||||
if (values.some(h => h.result === 'failure')) {
|
if (values.some(h => !h.result)) {
|
||||||
return HealthStatus.Failure
|
return HealthStatus.Waiting
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.length && hasHealthChecks) {
|
if (values.some(h => h.result === 'failure')) {
|
||||||
return HealthStatus.Waiting
|
return HealthStatus.Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.some(h => h.result === 'loading')) {
|
if (values.some(h => h.result === 'loading')) {
|
||||||
return HealthStatus.Loading
|
return HealthStatus.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.some(h => !h.result || h.result === 'starting')) {
|
if (values.some(h => h.result === 'starting')) {
|
||||||
return HealthStatus.Starting
|
return HealthStatus.Starting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function dryUpdate(
|
|||||||
Object.keys(pkg['current-dependencies'] || {}).some(
|
Object.keys(pkg['current-dependencies'] || {}).some(
|
||||||
pkgId => pkgId === id,
|
pkgId => pkgId === id,
|
||||||
) &&
|
) &&
|
||||||
!emver.satisfies(version, getManifest(pkg).dependencies[id].version),
|
!emver.satisfies(version, pkg['current-dependencies'][id].versionRange),
|
||||||
)
|
)
|
||||||
.map(pkg => getManifest(pkg).title)
|
.map(pkg => getManifest(pkg).title)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import {
|
|||||||
DataModel,
|
DataModel,
|
||||||
InstalledState,
|
InstalledState,
|
||||||
InstallingState,
|
InstallingState,
|
||||||
Manifest,
|
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageState,
|
PackageState,
|
||||||
UpdatingState,
|
UpdatingState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { firstValueFrom } from 'rxjs'
|
import { firstValueFrom } from 'rxjs'
|
||||||
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
|
|
||||||
export async function getPackage(
|
export async function getPackage(
|
||||||
patch: PatchDB<DataModel>,
|
patch: PatchDB<DataModel>,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { PackageDataEntry } from '../services/patch-db/data-model'
|
import { PackageDataEntry } from '../services/patch-db/data-model'
|
||||||
import { getManifest } from './get-package-data'
|
|
||||||
|
|
||||||
export function hasCurrentDeps(pkg: PackageDataEntry): boolean {
|
export function hasCurrentDeps(
|
||||||
return !!Object.keys(pkg['current-dependents']).filter(
|
id: string,
|
||||||
depId => depId !== getManifest(pkg).id,
|
pkgs: Record<string, PackageDataEntry>,
|
||||||
).length
|
): boolean {
|
||||||
|
return !!Object.values(pkgs).some(pkg => !!pkg['current-dependencies'][id])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user