mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
merge 036, everything broken
This commit is contained in:
@@ -1,16 +1,28 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { InstallProgress } from 'src/app/services/patch-db/data-model'
|
||||
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||
import { Progress } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'installProgress',
|
||||
name: 'installingProgressString',
|
||||
})
|
||||
export class InstallProgressPipe implements PipeTransform {
|
||||
transform(installProgress?: InstallProgress): string {
|
||||
const totalProgress =
|
||||
packageLoadingProgress(installProgress)?.totalProgress || 0
|
||||
export class InstallingProgressDisplayPipe implements PipeTransform {
|
||||
transform(progress: Progress): string {
|
||||
if (progress === true) return 'finalizing'
|
||||
if (progress === false || !progress.total) return 'unknown %'
|
||||
const percentage = Math.round((100 * progress.done) / progress.total)
|
||||
|
||||
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
|
||||
return percentage < 99 ? String(percentage) + '%' : 'finalizing'
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'installingProgress',
|
||||
})
|
||||
export class InstallingProgressPipe implements PipeTransform {
|
||||
transform(progress: Progress): number | null {
|
||||
if (progress === true) return 1
|
||||
if (progress === false || !progress.total) return null
|
||||
return Number((progress.done / progress.total).toFixed(2))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
||||
|
||||
@Pipe({
|
||||
name: 'progressData',
|
||||
standalone: true,
|
||||
})
|
||||
export class ProgressDataPipe implements PipeTransform {
|
||||
transform(pkg: PackageDataEntry): ProgressData | null {
|
||||
return packageLoadingProgress(pkg['install-progress'])
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
HealthResult,
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
RR,
|
||||
ServerNotifications,
|
||||
} from './api.types'
|
||||
|
||||
import { BTC_ICON, LND_ICON, PROXY_ICON } from './api-icons'
|
||||
import {
|
||||
DependencyMetadata,
|
||||
@@ -903,7 +902,7 @@ export module Mock {
|
||||
integer: false,
|
||||
}),
|
||||
}),
|
||||
displayAs: 'I\'m {{last-name}}, {{first-name}} {{last-name}}',
|
||||
displayAs: "I'm {{last-name}}, {{first-name}} {{last-name}}",
|
||||
uniqueBy: 'last-name',
|
||||
},
|
||||
),
|
||||
@@ -1277,261 +1276,652 @@ export module Mock {
|
||||
|
||||
export const MockDependencyConfig = MockConfig
|
||||
|
||||
export const bitcoind: PackageDataEntry = {
|
||||
state: PackageState.Installed,
|
||||
export const bitcoind: PackageDataEntry<InstalledState> = {
|
||||
'state-info': {
|
||||
state: PackageState.Installed,
|
||||
manifest: MockManifestBitcoind,
|
||||
},
|
||||
icon: '/assets/img/service-icons/bitcoind.svg',
|
||||
manifest: MockManifestBitcoind,
|
||||
installed: {
|
||||
'last-backup': null,
|
||||
'installed-at': new Date().toISOString(),
|
||||
status: {
|
||||
configured: true,
|
||||
main: {
|
||||
status: PackageMainStatus.Running,
|
||||
started: '2021-06-14T20:49:17.774Z',
|
||||
health: {
|
||||
'ephemeral-health-check': {
|
||||
name: 'Ephemeral Health Check',
|
||||
result: HealthResult.Starting,
|
||||
},
|
||||
'chain-state': {
|
||||
name: 'Chain State',
|
||||
result: HealthResult.Loading,
|
||||
message: 'Bitcoin is syncing from genesis',
|
||||
},
|
||||
'p2p-interface': {
|
||||
name: 'P2P Interface',
|
||||
result: HealthResult.Success,
|
||||
message: 'the health check ran successfully',
|
||||
},
|
||||
'rpc-interface': {
|
||||
name: 'RPC Interface',
|
||||
result: HealthResult.Failure,
|
||||
error: 'RPC interface unreachable.',
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
name: 'Totally Unnecessary',
|
||||
result: HealthResult.Disabled,
|
||||
reason: 'You disabled this on purpose',
|
||||
},
|
||||
},
|
||||
},
|
||||
'dependency-config-errors': {},
|
||||
'last-backup': null,
|
||||
status: {
|
||||
configured: true,
|
||||
main: {
|
||||
status: PackageMainStatus.Running,
|
||||
started: new Date().toISOString(),
|
||||
health: {},
|
||||
},
|
||||
interfaceInfo: {
|
||||
rpc: {
|
||||
name: 'Bitcoin RPC',
|
||||
description: `Bitcoin's RPC interface`,
|
||||
addressInfo: {
|
||||
ipInfo: {
|
||||
eth0: {
|
||||
wireless: false,
|
||||
ipv4: '192.168.1.1:8333',
|
||||
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD:8333',
|
||||
},
|
||||
},
|
||||
lanHostname: 'adjective-noun:8333',
|
||||
torHostname: 'bitcoind-rpc-address.onion',
|
||||
domainInfo: null,
|
||||
},
|
||||
type: 'ui',
|
||||
},
|
||||
p2p: {
|
||||
name: 'Bitcoin P2P',
|
||||
description: `Bitcoin's P2P interface`,
|
||||
addressInfo: {
|
||||
ipInfo: {
|
||||
eth0: {
|
||||
wireless: false,
|
||||
ipv4: '192.168.1.1:8332',
|
||||
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD:8332',
|
||||
},
|
||||
},
|
||||
lanHostname: 'adjective-noun:8332',
|
||||
torHostname: 'bitcoind-p2p-address.onion',
|
||||
domainInfo: null,
|
||||
},
|
||||
type: 'ui',
|
||||
},
|
||||
},
|
||||
'current-dependencies': {},
|
||||
'current-dependents': {},
|
||||
'dependency-info': {},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
'has-config': true,
|
||||
outboundProxy: null,
|
||||
'dependency-config-errors': {},
|
||||
},
|
||||
actions: {
|
||||
resync: {
|
||||
name: 'Resync Blockchain',
|
||||
description: 'Use this to resync the Bitcoin blockchain from genesis',
|
||||
warning: 'This will take a couple of days.',
|
||||
disabled: null,
|
||||
group: null,
|
||||
'input-spec': {
|
||||
reason: {
|
||||
type: 'text',
|
||||
inputmode: 'text',
|
||||
name: 'Re-sync Reason',
|
||||
description: 'Your reason for re-syncing. Why are you doing this?',
|
||||
placeholder: null,
|
||||
required: true,
|
||||
masked: false,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [
|
||||
{
|
||||
regex: '^[a-zA-Z]+$',
|
||||
description: 'must contain only letters.',
|
||||
},
|
||||
],
|
||||
warning: null,
|
||||
default: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
generate: null,
|
||||
'service-interfaces': {
|
||||
ui: {
|
||||
id: 'ui',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'Web UI',
|
||||
description:
|
||||
'A launchable web app for you to interact with your Bitcoin node',
|
||||
type: 'ui',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'abcdefg',
|
||||
bindOptions: {
|
||||
scheme: 'http',
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
addXForwardedHeaders: false,
|
||||
preferredExternalPort: 443,
|
||||
scheme: 'https',
|
||||
},
|
||||
secure: null,
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'abcdefg',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 1234,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'bitcoin-ui-address.onion',
|
||||
port: 80,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: null,
|
||||
sslPort: 1234,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: null,
|
||||
sslPort: 1234,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
rpc: {
|
||||
id: 'rpc',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'RPC',
|
||||
description:
|
||||
'Used by dependent services and client wallets for connecting to your node',
|
||||
type: 'api',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'bcdefgh',
|
||||
bindOptions: {
|
||||
scheme: 'http',
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
addXForwardedHeaders: false,
|
||||
preferredExternalPort: 443,
|
||||
scheme: 'https',
|
||||
},
|
||||
secure: null,
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'bcdefgh',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 2345,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'bitcoin-rpc-address.onion',
|
||||
port: 80,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: null,
|
||||
sslPort: 2345,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: null,
|
||||
sslPort: 2345,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
p2p: {
|
||||
id: 'p2p',
|
||||
hasPrimary: true,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'P2P',
|
||||
description:
|
||||
'Used for connecting to other nodes on the Bitcoin network',
|
||||
type: 'p2p',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'cdefghi',
|
||||
bindOptions: {
|
||||
scheme: 'bitcoin',
|
||||
preferredExternalPort: 8333,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: false,
|
||||
},
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'cdefghi',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 3456,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'bitcoin-p2p-address.onion',
|
||||
port: 8333,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 3456,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 3456,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'current-dependents': {
|
||||
lnd: {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'current-dependencies': {},
|
||||
'dependency-info': {},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
'has-config': true,
|
||||
outboundProxy: null,
|
||||
}
|
||||
|
||||
export const bitcoinProxy: PackageDataEntry = {
|
||||
state: PackageState.Installed,
|
||||
export const bitcoinProxy: PackageDataEntry<InstalledState> = {
|
||||
'state-info': {
|
||||
state: PackageState.Installed,
|
||||
manifest: MockManifestBitcoinProxy,
|
||||
},
|
||||
icon: '/assets/img/service-icons/btc-rpc-proxy.png',
|
||||
manifest: MockManifestBitcoinProxy,
|
||||
installed: {
|
||||
'last-backup': null,
|
||||
'installed-at': new Date().toISOString(),
|
||||
status: {
|
||||
configured: false,
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
'dependency-config-errors': {},
|
||||
'last-backup': null,
|
||||
status: {
|
||||
configured: false,
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
interfaceInfo: {
|
||||
rpc: {
|
||||
name: 'Proxy RPC addresses',
|
||||
description: `Use these addresses to access Proxy's RPC interface`,
|
||||
addressInfo: {
|
||||
ipInfo: {
|
||||
eth0: {
|
||||
wireless: false,
|
||||
ipv4: '192.168.1.1:8459',
|
||||
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD:8459',
|
||||
'dependency-config-errors': {},
|
||||
},
|
||||
'service-interfaces': {
|
||||
ui: {
|
||||
id: 'ui',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'Web UI',
|
||||
description: 'A launchable web app for Bitcoin Proxy',
|
||||
type: 'ui',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'hijklmnop',
|
||||
bindOptions: {
|
||||
scheme: 'http',
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
addXForwardedHeaders: false,
|
||||
preferredExternalPort: 443,
|
||||
scheme: 'https',
|
||||
},
|
||||
secure: {
|
||||
ssl: true,
|
||||
},
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'hijklmnop',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 4567,
|
||||
},
|
||||
},
|
||||
lanHostname: 'adjective-noun.local:8459',
|
||||
torHostname: 'btcrpc-proxy-address.onion',
|
||||
domainInfo: null,
|
||||
},
|
||||
type: 'api',
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'proxy-ui-address.onion',
|
||||
port: 80,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: null,
|
||||
sslPort: 4567,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: null,
|
||||
sslPort: 4567,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'wlan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 4567,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'wlan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.7',
|
||||
port: null,
|
||||
sslPort: 4567,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'wlan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: null,
|
||||
sslPort: 4567,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {
|
||||
bitcoind: {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'dependency-info': {
|
||||
bitcoind: {
|
||||
title: Mock.MockManifestBitcoind.title,
|
||||
icon: 'assets/img/service-icons/bitcoind.svg',
|
||||
},
|
||||
},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
'has-config': true,
|
||||
outboundProxy: null,
|
||||
},
|
||||
actions: {},
|
||||
'current-dependents': {
|
||||
lnd: {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'current-dependencies': {
|
||||
bitcoind: {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'dependency-info': {
|
||||
bitcoind: {
|
||||
title: Mock.MockManifestBitcoind.title,
|
||||
icon: 'assets/img/service-icons/bitcoind.svg',
|
||||
},
|
||||
},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
'has-config': true,
|
||||
outboundProxy: null,
|
||||
}
|
||||
|
||||
export const lnd: PackageDataEntry = {
|
||||
state: PackageState.Installed,
|
||||
export const lnd: PackageDataEntry<InstalledState> = {
|
||||
'state-info': {
|
||||
state: PackageState.Installed,
|
||||
manifest: MockManifestLnd,
|
||||
},
|
||||
icon: '/assets/img/service-icons/lnd.png',
|
||||
manifest: MockManifestLnd,
|
||||
installed: {
|
||||
'last-backup': null,
|
||||
'installed-at': new Date().toISOString(),
|
||||
status: {
|
||||
configured: true,
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
'dependency-config-errors': {
|
||||
'btc-rpc-proxy': 'Username not found',
|
||||
},
|
||||
'last-backup': null,
|
||||
status: {
|
||||
configured: true,
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
interfaceInfo: {
|
||||
ui: {
|
||||
name: 'Web UI',
|
||||
description: 'The browser web interface for LND',
|
||||
addressInfo: {
|
||||
ipInfo: {
|
||||
eth0: {
|
||||
wireless: false,
|
||||
ipv4: '192.168.1.1:7171',
|
||||
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD:7171',
|
||||
},
|
||||
},
|
||||
lanHostname: 'adjective-noun.local:7171',
|
||||
torHostname: 'lnd-ui-address.onion',
|
||||
domainInfo: null,
|
||||
},
|
||||
type: 'ui',
|
||||
},
|
||||
grpc: {
|
||||
name: 'gRPC',
|
||||
description: 'For connecting to LND gRPC interface',
|
||||
addressInfo: {
|
||||
ipInfo: {
|
||||
eth0: {
|
||||
wireless: false,
|
||||
ipv4: '192.168.1.1:9191',
|
||||
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD:9191',
|
||||
},
|
||||
},
|
||||
lanHostname: 'adjective-noun.local:9191',
|
||||
torHostname: 'lnd-grpc-address.onion',
|
||||
domainInfo: null,
|
||||
},
|
||||
type: 'p2p',
|
||||
},
|
||||
'dependency-config-errors': {
|
||||
'btc-rpc-proxy': 'Username not found',
|
||||
},
|
||||
'current-dependencies': {
|
||||
bitcoind: {
|
||||
'health-checks': [],
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'current-dependents': {},
|
||||
'dependency-info': {
|
||||
bitcoind: {
|
||||
title: Mock.MockManifestBitcoind.title,
|
||||
icon: 'assets/img/service-icons/bitcoind.svg',
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
title: Mock.MockManifestBitcoinProxy.title,
|
||||
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
|
||||
},
|
||||
},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
'has-config': true,
|
||||
outboundProxy: null,
|
||||
},
|
||||
actions: {},
|
||||
'service-interfaces': {
|
||||
grpc: {
|
||||
id: 'grpc',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'GRPC',
|
||||
description:
|
||||
'Used by dependent services and client wallets for connecting to your node',
|
||||
type: 'api',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'qrstuv',
|
||||
bindOptions: {
|
||||
scheme: 'grpc',
|
||||
preferredExternalPort: 10009,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: true,
|
||||
},
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'qrstuv',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'lnd-grpc-address.onion',
|
||||
port: 10009,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
lndconnect: {
|
||||
id: 'lndconnect',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: true,
|
||||
name: 'LND Connect',
|
||||
description:
|
||||
'Used by client wallets adhering to LND Connect protocol to connect to your node',
|
||||
type: 'api',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'qrstuv',
|
||||
bindOptions: {
|
||||
scheme: 'lndconnect',
|
||||
preferredExternalPort: 10009,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: true,
|
||||
},
|
||||
},
|
||||
suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'qrstuv',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'lnd-grpc-address.onion',
|
||||
port: 10009,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
p2p: {
|
||||
id: 'p2p',
|
||||
hasPrimary: true,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'P2P',
|
||||
description:
|
||||
'Used for connecting to other nodes on the Bitcoin network',
|
||||
type: 'p2p',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'rstuvw',
|
||||
bindOptions: {
|
||||
scheme: null,
|
||||
preferredExternalPort: 9735,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: true,
|
||||
},
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'rstuvw',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 6789,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'lnd-p2p-address.onion',
|
||||
port: 9735,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 6789,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 6789,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {
|
||||
bitcoind: {
|
||||
'health-checks': [],
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'dependency-info': {
|
||||
bitcoind: {
|
||||
title: Mock.MockManifestBitcoind.title,
|
||||
icon: 'assets/img/service-icons/bitcoind.svg',
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
title: Mock.MockManifestBitcoinProxy.title,
|
||||
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
|
||||
},
|
||||
},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
'has-config': true,
|
||||
outboundProxy: null,
|
||||
}
|
||||
|
||||
export const LocalPkgs: { [key: string]: PackageDataEntry } = {
|
||||
bitcoind,
|
||||
'btc-rpc-proxy': bitcoinProxy,
|
||||
lnd,
|
||||
}
|
||||
export const LocalPkgs: { [key: string]: PackageDataEntry<InstalledState> } =
|
||||
{
|
||||
bitcoind: bitcoind,
|
||||
'btc-rpc-proxy': bitcoinProxy,
|
||||
lnd: lnd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +89,6 @@ export module RR {
|
||||
} // net.tor.reset
|
||||
export type ResetTorRes = null
|
||||
|
||||
export type ToggleZramReq = {
|
||||
enable: boolean
|
||||
} // server.experimental.zram
|
||||
export type ToggleZramRes = null
|
||||
|
||||
export type SetOsOutboundProxyReq = {
|
||||
proxy: OsOutboundProxy
|
||||
} // server.proxy.set-outbound
|
||||
|
||||
@@ -103,8 +103,6 @@ export abstract class ApiService {
|
||||
|
||||
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
|
||||
|
||||
abstract toggleZram(params: RR.ToggleZramReq): Promise<RR.ToggleZramRes>
|
||||
|
||||
abstract setOsOutboundProxy(
|
||||
params: RR.SetOsOutboundProxyReq,
|
||||
): Promise<RR.SetOsOutboundProxyRes>
|
||||
|
||||
@@ -212,10 +212,6 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'net.tor.reset', params })
|
||||
}
|
||||
|
||||
async toggleZram(params: RR.ToggleZramReq): Promise<RR.ToggleZramRes> {
|
||||
return this.rpcRequest({ method: 'server.experimental.zram', params })
|
||||
}
|
||||
|
||||
async setOsOutboundProxy(
|
||||
params: RR.SetOsOutboundProxyReq,
|
||||
): Promise<RR.SetOsOutboundProxyRes> {
|
||||
|
||||
@@ -10,15 +10,17 @@ import {
|
||||
} from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstallProgress,
|
||||
FullProgress,
|
||||
InstallingState,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
Proxy,
|
||||
StateInfo,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { BackupTargetType, Metrics, RR } from './api.types'
|
||||
import { Mock } from './api.fixures'
|
||||
// import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md'
|
||||
import {
|
||||
EMPTY,
|
||||
iif,
|
||||
@@ -38,14 +40,34 @@ import { AuthService } from '../auth.service'
|
||||
import { ConnectionService } from '../connection.service'
|
||||
import { StoreInfo } from '@start9labs/marketplace'
|
||||
|
||||
const PROGRESS: InstallProgress = {
|
||||
size: 120,
|
||||
downloaded: 0,
|
||||
'download-complete': false,
|
||||
validated: 0,
|
||||
'validation-complete': false,
|
||||
unpacked: 0,
|
||||
'unpack-complete': false,
|
||||
const PROGRESS: FullProgress = {
|
||||
overall: {
|
||||
done: 0,
|
||||
total: 120,
|
||||
},
|
||||
phases: [
|
||||
{
|
||||
name: 'Downloading',
|
||||
progress: {
|
||||
done: 0,
|
||||
total: 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Validating',
|
||||
progress: {
|
||||
done: 0,
|
||||
total: 40,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Installing',
|
||||
progress: {
|
||||
done: 0,
|
||||
total: 40,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -399,20 +421,6 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async toggleZram(params: RR.ToggleZramReq): Promise<RR.ToggleZramRes> {
|
||||
await pauseFor(2000)
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/server-info/zram',
|
||||
value: params.enable,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async setOsOutboundProxy(
|
||||
params: RR.SetOsOutboundProxyReq,
|
||||
): Promise<RR.SetOsOutboundProxyRes> {
|
||||
@@ -965,15 +973,30 @@ export class MockApiService extends ApiService {
|
||||
this.updateProgress(params.id)
|
||||
}, 1000)
|
||||
|
||||
const patch: Operation<PackageDataEntry>[] = [
|
||||
const manifest = Mock.LocalPkgs[params.id]['state-info'].manifest
|
||||
|
||||
const patch: Operation<
|
||||
PackageDataEntry<InstallingState | UpdatingState>
|
||||
>[] = [
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: `/package-data/${params.id}`,
|
||||
value: {
|
||||
...Mock.LocalPkgs[params.id],
|
||||
// state: PackageState.Installing,
|
||||
state: PackageState.Updating,
|
||||
'install-progress': { ...PROGRESS },
|
||||
'state-info': {
|
||||
// if installing
|
||||
state: PackageState.Installing,
|
||||
|
||||
// if updating
|
||||
// state: PackageState.Updating,
|
||||
// manifest,
|
||||
|
||||
// both
|
||||
'installing-info': {
|
||||
'new-manifest': manifest,
|
||||
progress: PROGRESS,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -1029,9 +1052,13 @@ export class MockApiService extends ApiService {
|
||||
path: `/package-data/${id}`,
|
||||
value: {
|
||||
...Mock.LocalPkgs[id],
|
||||
state: PackageState.Restoring,
|
||||
'install-progress': { ...PROGRESS },
|
||||
installed: undefined,
|
||||
'state-info': {
|
||||
state: PackageState.Restoring,
|
||||
'installing-info': {
|
||||
'new-manifest': Mock.LocalPkgs[id]['state-info'].manifest!,
|
||||
progress: PROGRESS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -1174,14 +1201,16 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
await pauseFor(2000)
|
||||
const path = `/package-data/${params.id}/installed/status/main`
|
||||
const path = `/package-data/${params.id}/status/main`
|
||||
|
||||
setTimeout(() => {
|
||||
const patch2 = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: PackageMainStatus.Stopped,
|
||||
path: path,
|
||||
value: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
@@ -1190,8 +1219,11 @@ export class MockApiService extends ApiService {
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: PackageMainStatus.Stopping,
|
||||
path: path,
|
||||
value: {
|
||||
status: PackageMainStatus.Stopping,
|
||||
timeout: '35s',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1218,7 +1250,7 @@ export class MockApiService extends ApiService {
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${params.id}/state`,
|
||||
path: `/package-data/${params.id}/state-info/state`,
|
||||
value: PackageState.Removing,
|
||||
},
|
||||
]
|
||||
@@ -1283,55 +1315,100 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
|
||||
private async updateProgress(id: string): Promise<void> {
|
||||
const progress = { ...PROGRESS }
|
||||
const phases = [
|
||||
{ progress: 'downloaded', completion: 'download-complete' },
|
||||
{ progress: 'validated', completion: 'validation-complete' },
|
||||
{ progress: 'unpacked', completion: 'unpack-complete' },
|
||||
] as const
|
||||
const progress = JSON.parse(JSON.stringify(PROGRESS))
|
||||
|
||||
for (let phase of phases) {
|
||||
let i = progress[phase.progress]
|
||||
const size = progress?.size || 0
|
||||
while (i < size) {
|
||||
await pauseFor(250)
|
||||
i = Math.min(i + 5, size)
|
||||
progress[phase.progress] = i
|
||||
for (let [i, phase] of progress.phases.entries()) {
|
||||
if (typeof phase.progress !== 'object' || !phase.progress.total) {
|
||||
await pauseFor(2000)
|
||||
|
||||
if (i === progress.size) {
|
||||
progress[phase.completion] = true
|
||||
}
|
||||
|
||||
const patch = [
|
||||
const patches: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/install-progress`,
|
||||
value: { ...progress },
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/phases/${i}/progress`,
|
||||
value: true,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
// overall
|
||||
if (typeof progress.overall === 'object' && progress.overall.total) {
|
||||
const step = progress.overall.total / progress.phases.length
|
||||
|
||||
progress.overall.done += step
|
||||
|
||||
patches.push({
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/overall/done`,
|
||||
value: progress.overall.done,
|
||||
})
|
||||
}
|
||||
|
||||
this.mockRevision(patches)
|
||||
} else {
|
||||
const step = phase.progress.total / 4
|
||||
|
||||
while (phase.progress.done < phase.progress.total) {
|
||||
await pauseFor(500)
|
||||
|
||||
phase.progress.done += step
|
||||
|
||||
const patches: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/phases/${i}/progress/done`,
|
||||
value: phase.progress.done,
|
||||
},
|
||||
]
|
||||
|
||||
// overall
|
||||
if (typeof progress.overall === 'object' && progress.overall.total) {
|
||||
const step = progress.overall.total / progress.phases.length / 4
|
||||
|
||||
progress.overall.done += step
|
||||
|
||||
patches.push({
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/overall/done`,
|
||||
value: progress.overall.done,
|
||||
})
|
||||
}
|
||||
|
||||
this.mockRevision(patches)
|
||||
|
||||
if (phase.progress.done === phase.progress.total) {
|
||||
await pauseFor(250)
|
||||
this.mockRevision([
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/phases/${i}/progress`,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const patch2: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state`,
|
||||
value: PackageState.Installed,
|
||||
await pauseFor(1000)
|
||||
this.mockRevision([
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info/installing-info/progress/overall`,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
|
||||
await pauseFor(1000)
|
||||
const patch2: Operation<StateInfo>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/package-data/${id}/state-info`,
|
||||
value: {
|
||||
state: PackageState.Installed,
|
||||
manifest: Mock.LocalPkgs[id]['state-info'].manifest,
|
||||
},
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: `/package-data/${id}/installed`,
|
||||
value: { ...Mock.LocalPkgs[id].installed },
|
||||
},
|
||||
{
|
||||
op: PatchOp.REMOVE,
|
||||
path: `/package-data/${id}/install-progress`,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
}, 1000)
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
}
|
||||
|
||||
private async updateOSProgress() {
|
||||
|
||||
@@ -96,7 +96,6 @@ export const mockPatchData: DataModel = {
|
||||
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
|
||||
'ca-fingerprint': 'SHA-256: 63 2B 11 99 44 40 17 DF 37 FC C3 DF 0F 3D 15',
|
||||
'ntp-synced': false,
|
||||
zram: false,
|
||||
smtp: {
|
||||
server: '',
|
||||
port: 587,
|
||||
@@ -115,6 +114,232 @@ export const mockPatchData: DataModel = {
|
||||
...Mock.bitcoind.manifest,
|
||||
version: '0.19.0',
|
||||
},
|
||||
'service-interfaces': {
|
||||
ui: {
|
||||
id: 'ui',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'Web UI',
|
||||
description:
|
||||
'A launchable web app for you to interact with your Bitcoin node',
|
||||
type: 'ui',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'abcdefg',
|
||||
bindOptions: {
|
||||
scheme: 'http',
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
addXForwardedHeaders: false,
|
||||
preferredExternalPort: 443,
|
||||
scheme: 'https',
|
||||
},
|
||||
secure: null,
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'abcdefg',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 1234,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'bitcoin-ui-address.onion',
|
||||
port: 80,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: null,
|
||||
sslPort: 1234,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: null,
|
||||
sslPort: 1234,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
rpc: {
|
||||
id: 'rpc',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'RPC',
|
||||
description:
|
||||
'Used by dependent services and client wallets for connecting to your node',
|
||||
type: 'api',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'bcdefgh',
|
||||
bindOptions: {
|
||||
scheme: 'http',
|
||||
preferredExternalPort: 80,
|
||||
addSsl: {
|
||||
addXForwardedHeaders: false,
|
||||
preferredExternalPort: 443,
|
||||
scheme: 'https',
|
||||
},
|
||||
secure: null,
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'bcdefgh',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: null,
|
||||
sslPort: 2345,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'bitcoin-rpc-address.onion',
|
||||
port: 80,
|
||||
sslPort: 443,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: null,
|
||||
sslPort: 2345,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: null,
|
||||
sslPort: 2345,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
p2p: {
|
||||
id: 'p2p',
|
||||
hasPrimary: true,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'P2P',
|
||||
description:
|
||||
'Used for connecting to other nodes on the Bitcoin network',
|
||||
type: 'p2p',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'cdefghi',
|
||||
bindOptions: {
|
||||
scheme: 'bitcoin',
|
||||
preferredExternalPort: 8333,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: false,
|
||||
},
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'cdefghi',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 3456,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'bitcoin-p2p-address.onion',
|
||||
port: 8333,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 3456,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 3456,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'current-dependents': {
|
||||
lnd: {
|
||||
pointers: [],
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'current-dependencies': {},
|
||||
'dependency-info': {},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
},
|
||||
lnd: {
|
||||
...Mock.lnd,
|
||||
@@ -122,6 +347,248 @@ export const mockPatchData: DataModel = {
|
||||
...Mock.lnd.manifest,
|
||||
version: '0.11.0',
|
||||
},
|
||||
icon: '/assets/img/service-icons/lnd.png',
|
||||
'last-backup': null,
|
||||
status: {
|
||||
configured: true,
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
'dependency-config-errors': {
|
||||
'btc-rpc-proxy': 'This is a config unsatisfied error',
|
||||
},
|
||||
},
|
||||
'service-interfaces': {
|
||||
grpc: {
|
||||
id: 'grpc',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'GRPC',
|
||||
description:
|
||||
'Used by dependent services and client wallets for connecting to your node',
|
||||
type: 'api',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'qrstuv',
|
||||
bindOptions: {
|
||||
scheme: 'grpc',
|
||||
preferredExternalPort: 10009,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: true,
|
||||
},
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'qrstuv',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'lnd-grpc-address.onion',
|
||||
port: 10009,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
lndconnect: {
|
||||
id: 'lndconnect',
|
||||
hasPrimary: false,
|
||||
disabled: false,
|
||||
masked: true,
|
||||
name: 'LND Connect',
|
||||
description:
|
||||
'Used by client wallets adhering to LND Connect protocol to connect to your node',
|
||||
type: 'api',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'qrstuv',
|
||||
bindOptions: {
|
||||
scheme: 'lndconnect',
|
||||
preferredExternalPort: 10009,
|
||||
addSsl: null,
|
||||
secure: {
|
||||
ssl: true,
|
||||
},
|
||||
},
|
||||
suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'qrstuv',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'lnd-grpc-address.onion',
|
||||
port: 10009,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 5678,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
p2p: {
|
||||
id: 'p2p',
|
||||
hasPrimary: true,
|
||||
disabled: false,
|
||||
masked: false,
|
||||
name: 'P2P',
|
||||
description:
|
||||
'Used for connecting to other nodes on the Bitcoin network',
|
||||
type: 'p2p',
|
||||
addressInfo: {
|
||||
username: null,
|
||||
hostId: 'rstuvw',
|
||||
bindOptions: {
|
||||
scheme: null,
|
||||
preferredExternalPort: 9735,
|
||||
addSsl: null,
|
||||
secure: { ssl: true },
|
||||
},
|
||||
suffix: '',
|
||||
},
|
||||
hostInfo: {
|
||||
id: 'rstuvw',
|
||||
kind: 'multi',
|
||||
hostnames: [
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'local',
|
||||
value: 'adjective-noun.local',
|
||||
port: 6789,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'onion',
|
||||
hostname: {
|
||||
value: 'lnd-p2p-address.onion',
|
||||
port: 9735,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv4',
|
||||
value: '192.168.1.5',
|
||||
port: 6789,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'ip',
|
||||
networkInterfaceId: 'elan0',
|
||||
public: false,
|
||||
hostname: {
|
||||
kind: 'ipv6',
|
||||
value: '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
|
||||
port: 6789,
|
||||
sslPort: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {
|
||||
bitcoind: {
|
||||
'health-checks': [],
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
'health-checks': [],
|
||||
},
|
||||
},
|
||||
'dependency-info': {
|
||||
bitcoind: {
|
||||
title: 'Bitcoin Core',
|
||||
icon: 'assets/img/service-icons/bitcoind.svg',
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
title: 'Bitcoin Proxy',
|
||||
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
|
||||
},
|
||||
},
|
||||
'marketplace-url': 'https://registry.start9.com/',
|
||||
'developer-key': 'developer-key',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { WorkspaceConfig } from '@start9labs/shared'
|
||||
import { InterfaceInfo } from 'src/app/services/patch-db/data-model'
|
||||
import { types } from '@start9labs/start-sdk'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
|
||||
type HostnameInfoIp = types.HostnameInfoIp
|
||||
type HostnameInfoOnion = types.HostnameInfoOnion
|
||||
|
||||
const {
|
||||
gitHash,
|
||||
@@ -72,16 +80,60 @@ export class ConfigService {
|
||||
return window.isSecureContext || this.isTor()
|
||||
}
|
||||
|
||||
launchableAddress({ addressInfo }: InterfaceInfo): string {
|
||||
return this.isTor()
|
||||
? addressInfo.torHostname
|
||||
: this.isLocalhost()
|
||||
? `https://${addressInfo.lanHostname}`
|
||||
: this.isLocal() || this.isIpv4() || this.isIpv6()
|
||||
? `https://${this.hostname}`
|
||||
: addressInfo.domainInfo?.subdomain
|
||||
? `https://${addressInfo.domainInfo.subdomain}${addressInfo.domainInfo.domain}`
|
||||
: `https://${addressInfo.domainInfo?.domain}`
|
||||
isLaunchable(state: PackageState, status: PackageMainStatus): boolean {
|
||||
return (
|
||||
state === PackageState.Installed && status === PackageMainStatus.Running
|
||||
)
|
||||
}
|
||||
|
||||
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
|
||||
launchableAddress(
|
||||
interfaces: PackageDataEntry['service-interfaces'],
|
||||
): string {
|
||||
const ui = Object.values(interfaces).find(i => i.type === 'ui')
|
||||
|
||||
if (!ui) return ''
|
||||
|
||||
const host = ui.hostInfo
|
||||
const addressInfo = ui.addressInfo
|
||||
const scheme = this.isHttps() ? 'https' : 'http'
|
||||
const username = addressInfo.username ? addressInfo.username + '@' : ''
|
||||
const suffix = addressInfo.suffix || ''
|
||||
const url = new URL(`${scheme}://${username}placeholder${suffix}`)
|
||||
|
||||
if (host.kind === 'multi') {
|
||||
const onionHostname = host.hostnames.find(
|
||||
(h: any) => h.kind === 'onion',
|
||||
) as HostnameInfoOnion
|
||||
|
||||
if (this.isTor() && onionHostname) {
|
||||
url.hostname = onionHostname.hostname.value
|
||||
} else {
|
||||
const ipHostname = host.hostnames.find(
|
||||
(h: any) => h.kind === 'ip',
|
||||
) as HostnameInfoIp
|
||||
|
||||
if (!ipHostname) return ''
|
||||
|
||||
url.hostname = this.hostname
|
||||
url.port = String(
|
||||
ipHostname.hostname.sslPort || ipHostname.hostname.port,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const hostname = host.hostname
|
||||
|
||||
if (!hostname) return ''
|
||||
|
||||
if (this.isTor() && hostname.kind === 'onion') {
|
||||
url.hostname = hostname.hostname.value
|
||||
} else {
|
||||
url.hostname = this.hostname
|
||||
url.port = String(hostname.hostname.sslPort || hostname.hostname.port)
|
||||
}
|
||||
}
|
||||
|
||||
return url.href
|
||||
}
|
||||
|
||||
getHost(): string {
|
||||
@@ -114,3 +166,9 @@ export function removeProtocol(str: string): string {
|
||||
export function removePort(str: string): string {
|
||||
return str.split(':')[0]
|
||||
}
|
||||
|
||||
export function hasUi(
|
||||
interfaces: PackageDataEntry['service-interfaces'],
|
||||
): boolean {
|
||||
return Object.values(interfaces).some(iface => iface.type === 'ui')
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
HealthResult,
|
||||
InstalledPackageInfo,
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
} from './patch-db/data-model'
|
||||
import * as deepEqual from 'fast-deep-equal'
|
||||
import { Manifest } from '@start9labs/marketplace'
|
||||
import { Observable } from 'rxjs'
|
||||
import { isInstalled } from '../util/get-package-data'
|
||||
|
||||
export type AllDependencyErrors = Record<string, PkgDependencyErrors>
|
||||
export type PkgDependencyErrors = Record<string, DependencyError | null>
|
||||
@@ -58,20 +60,14 @@ export class DepErrorService {
|
||||
pkgId: string,
|
||||
outerErrors: AllDependencyErrors,
|
||||
): PkgDependencyErrors {
|
||||
const pkgInstalled = pkgs[pkgId].installed
|
||||
const pkg = pkgs[pkgId]
|
||||
|
||||
if (!pkgInstalled) return {}
|
||||
if (!isInstalled(pkg)) return {}
|
||||
|
||||
return currentDeps(pkgs, pkgId).reduce(
|
||||
(innerErrors, depId): PkgDependencyErrors => ({
|
||||
...innerErrors,
|
||||
[depId]: this.getDepError(
|
||||
pkgs,
|
||||
pkgInstalled,
|
||||
pkgs[pkgId].manifest,
|
||||
depId,
|
||||
outerErrors,
|
||||
),
|
||||
[depId]: this.getDepError(pkgs, pkg, depId, outerErrors),
|
||||
}),
|
||||
{} as PkgDependencyErrors,
|
||||
)
|
||||
@@ -79,21 +75,22 @@ export class DepErrorService {
|
||||
|
||||
private getDepError(
|
||||
pkgs: DataModel['package-data'],
|
||||
pkgInstalled: InstalledPackageInfo,
|
||||
pkgManifest: Manifest,
|
||||
pkg: PackageDataEntry<InstalledState>,
|
||||
depId: string,
|
||||
outerErrors: AllDependencyErrors,
|
||||
): DependencyError | null {
|
||||
const depInstalled = pkgs[depId]?.installed
|
||||
const depManifest = pkgs[depId]?.manifest
|
||||
const dep = pkgs[depId]
|
||||
|
||||
// not installed
|
||||
if (!depInstalled) {
|
||||
if (!dep || dep['state-info'].state !== PackageState.Installed) {
|
||||
return {
|
||||
type: DependencyErrorType.NotInstalled,
|
||||
}
|
||||
}
|
||||
|
||||
const pkgManifest = pkg['state-info'].manifest
|
||||
const depManifest = dep['state-info'].manifest
|
||||
|
||||
// incorrect version
|
||||
if (
|
||||
!this.emver.satisfies(
|
||||
@@ -110,16 +107,14 @@ export class DepErrorService {
|
||||
|
||||
// invalid config
|
||||
if (
|
||||
Object.values(pkgInstalled.status['dependency-config-errors']).some(
|
||||
err => !!err,
|
||||
)
|
||||
Object.values(pkg.status['dependency-config-errors']).some(err => !!err)
|
||||
) {
|
||||
return {
|
||||
type: DependencyErrorType.ConfigUnsatisfied,
|
||||
}
|
||||
}
|
||||
|
||||
const depStatus = depInstalled.status.main.status
|
||||
const depStatus = dep.status.main.status
|
||||
|
||||
// not running
|
||||
if (
|
||||
@@ -133,12 +128,8 @@ export class DepErrorService {
|
||||
|
||||
// health check failure
|
||||
if (depStatus === PackageMainStatus.Running) {
|
||||
for (let id of pkgInstalled['current-dependencies'][depId][
|
||||
'health-checks'
|
||||
]) {
|
||||
if (
|
||||
depInstalled.status.main.health[id]?.result !== HealthResult.Success
|
||||
) {
|
||||
for (let id of pkg['current-dependencies'][depId]['health-checks']) {
|
||||
if (dep.status.main.health[id]?.result !== HealthResult.Success) {
|
||||
return {
|
||||
type: DependencyErrorType.HealthChecksFailed,
|
||||
}
|
||||
@@ -162,9 +153,9 @@ export class DepErrorService {
|
||||
}
|
||||
|
||||
function currentDeps(pkgs: DataModel['package-data'], id: string): string[] {
|
||||
return Object.keys(
|
||||
pkgs[id]?.installed?.['current-dependencies'] || {},
|
||||
).filter(depId => depId !== id)
|
||||
return Object.keys(pkgs[id]?.['current-dependencies'] || {}).filter(
|
||||
depId => depId !== id,
|
||||
)
|
||||
}
|
||||
|
||||
function dependencyDepth(
|
||||
|
||||
@@ -3,9 +3,8 @@ import { Url } from '@start9labs/shared'
|
||||
import { Manifest } from '@start9labs/marketplace'
|
||||
import { BackupJob, ServerNotifications } from '../api/api.types'
|
||||
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
||||
import { NetworkInterfaceType } from '@start9labs/start-sdk/lib/util/utils'
|
||||
import { DependencyInfo } from 'src/app/apps/portal/routes/service/types/dependency-info'
|
||||
import { PackageStatus } from '../pkg-status-rendering.service'
|
||||
import { types } from '@start9labs/start-sdk'
|
||||
type ServiceInterfaceWithHostInfo = types.ServiceInterfaceWithHostInfo
|
||||
|
||||
export interface DataModel {
|
||||
'server-info': ServerInfo
|
||||
@@ -58,7 +57,7 @@ export interface ServerInfo {
|
||||
id: string
|
||||
version: string
|
||||
country: string
|
||||
ui: AddressInfo
|
||||
ui: HostnameInfo[]
|
||||
network: NetworkInfo
|
||||
'last-backup': string | null
|
||||
unreadNotifications: {
|
||||
@@ -70,7 +69,6 @@ export interface ServerInfo {
|
||||
pubkey: string
|
||||
'ca-fingerprint': string
|
||||
'ntp-synced': boolean
|
||||
zram: boolean
|
||||
smtp: typeof customSmtp.validator._TYPE
|
||||
'password-hash': string
|
||||
platform: string
|
||||
@@ -172,62 +170,43 @@ export enum ServerStatus {
|
||||
BackingUp = 'backing-up',
|
||||
}
|
||||
|
||||
export interface PackageDataEntry {
|
||||
state: PackageState
|
||||
export type PackageDataEntry<T extends StateInfo = StateInfo> = {
|
||||
'state-info': T
|
||||
icon: Url
|
||||
status: Status
|
||||
'last-backup': string | null
|
||||
'current-dependents': { [id: string]: CurrentDependencyInfo }
|
||||
'current-dependencies': { [id: string]: CurrentDependencyInfo }
|
||||
'dependency-info': {
|
||||
[id: string]: {
|
||||
title: string
|
||||
icon: Url
|
||||
}
|
||||
}
|
||||
'service-interfaces': Record<string, ServiceInterfaceWithHostInfo>
|
||||
'marketplace-url': string | null
|
||||
'developer-key': string
|
||||
'has-config': boolean
|
||||
outboundProxy: ServiceOutboundProxy
|
||||
}
|
||||
|
||||
export type StateInfo = InstalledState | InstallingState | UpdatingState
|
||||
|
||||
export type InstalledState = {
|
||||
state: PackageState.Installed | PackageState.Removing
|
||||
manifest: Manifest
|
||||
icon: string
|
||||
installed?: InstalledPackageInfo // when: installed
|
||||
actions?: Record<string, Action> // when: installed
|
||||
'install-progress'?: InstallProgress // when: installing, updating, restoring
|
||||
}
|
||||
|
||||
export type PackagePlus = {
|
||||
pkg: PackageDataEntry
|
||||
status: PackageStatus
|
||||
dependencies: DependencyInfo[]
|
||||
export type InstallingState = {
|
||||
state: PackageState.Installing | PackageState.Restoring
|
||||
'installing-info': InstallingInfo
|
||||
}
|
||||
|
||||
// export type PackageDataEntry =
|
||||
// | PackageDataEntryInstalled
|
||||
// | PackageDataEntryNeedsUpdate
|
||||
// | PackageDataEntryRemoving
|
||||
// | PackageDataEntryRestoring
|
||||
// | PackageDataEntryUpdating
|
||||
// | PackageDataEntryInstalling
|
||||
|
||||
// export type PackageDataEntryBase = {
|
||||
// manifest: Manifest
|
||||
// icon: Url
|
||||
// }
|
||||
|
||||
// export interface PackageDataEntryInstalled extends PackageDataEntryBase {
|
||||
// state: PackageState.Installed
|
||||
// installed: InstalledPackageInfo
|
||||
// actions: Record<string, Action>
|
||||
// }
|
||||
|
||||
// export interface PackageDataEntryNeedsUpdate extends PackageDataEntryBase {
|
||||
// state: PackageState.NeedsUpdate
|
||||
// }
|
||||
|
||||
// export interface PackageDataEntryRemoving extends PackageDataEntryBase {
|
||||
// state: PackageState.Removing
|
||||
// }
|
||||
|
||||
// export interface PackageDataEntryRestoring extends PackageDataEntryBase {
|
||||
// state: PackageState.Restoring
|
||||
// 'install-progress': InstallProgress
|
||||
// }
|
||||
|
||||
// export interface PackageDataEntryUpdating extends PackageDataEntryBase {
|
||||
// state: PackageState.Updating
|
||||
// 'install-progress': InstallProgress
|
||||
// }
|
||||
|
||||
// export interface PackageDataEntryInstalling extends PackageDataEntryBase {
|
||||
// state: PackageState.Installing
|
||||
// 'install-progress': InstallProgress
|
||||
// }
|
||||
export type UpdatingState = {
|
||||
state: PackageState.Updating
|
||||
'installing-info': InstallingInfo
|
||||
manifest: Manifest
|
||||
}
|
||||
|
||||
export enum PackageState {
|
||||
Installing = 'installing',
|
||||
@@ -235,41 +214,12 @@ export enum PackageState {
|
||||
Updating = 'updating',
|
||||
Removing = 'removing',
|
||||
Restoring = 'restoring',
|
||||
NeedsUpdate = 'needs-update',
|
||||
}
|
||||
|
||||
export interface InstalledPackageInfo {
|
||||
status: Status
|
||||
'last-backup': string | null
|
||||
'installed-at': string
|
||||
'current-dependencies': Record<string, CurrentDependencyInfo>
|
||||
'current-dependents': Record<string, CurrentDependencyInfo>
|
||||
'dependency-info': Record<string, { title: string; icon: Url }>
|
||||
interfaceInfo: Record<string, InterfaceInfo>
|
||||
'marketplace-url': string | null
|
||||
'developer-key': string
|
||||
'has-config': boolean
|
||||
outboundProxy: ServiceOutboundProxy
|
||||
}
|
||||
|
||||
export interface CurrentDependencyInfo {
|
||||
'health-checks': string[] // array of health check IDs
|
||||
}
|
||||
|
||||
export interface InterfaceInfo {
|
||||
name: string
|
||||
description: string
|
||||
type: NetworkInterfaceType
|
||||
addressInfo: AddressInfo
|
||||
}
|
||||
|
||||
export interface AddressInfo {
|
||||
ipInfo: IpInfo
|
||||
lanHostname: string
|
||||
torHostname: string
|
||||
domainInfo: DomainInfo | null
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
name: string
|
||||
description: string
|
||||
@@ -300,6 +250,7 @@ export interface MainStatusStopped {
|
||||
|
||||
export interface MainStatusStopping {
|
||||
status: PackageMainStatus.Stopping
|
||||
timeout: string
|
||||
}
|
||||
|
||||
export interface MainStatusStarting {
|
||||
@@ -374,12 +325,13 @@ export interface HealthCheckResultFailure {
|
||||
error: string
|
||||
}
|
||||
|
||||
export interface InstallProgress {
|
||||
readonly size: number | null
|
||||
readonly downloaded: number
|
||||
readonly 'download-complete': boolean
|
||||
readonly validated: number
|
||||
readonly 'validation-complete': boolean
|
||||
readonly unpacked: number
|
||||
readonly 'unpack-complete': boolean
|
||||
export type InstallingInfo = {
|
||||
progress: FullProgress
|
||||
'new-manifest': Manifest
|
||||
}
|
||||
|
||||
export type FullProgress = {
|
||||
overall: Progress
|
||||
phases: { name: string; progress: Progress }[]
|
||||
}
|
||||
export type Progress = boolean | { done: number; total: number | null } // false means indeterminate. true means complete
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackagePlus,
|
||||
PackageState,
|
||||
Status,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
@@ -21,12 +20,15 @@ export function renderPkgStatus(
|
||||
let dependency: DependencyStatus | null = null
|
||||
let health: HealthStatus | null = null
|
||||
|
||||
if (pkg.state === PackageState.Installed && pkg.installed) {
|
||||
primary = getPrimaryStatus(pkg.installed.status)
|
||||
if (pkg['state-info'].state === PackageState.Installed) {
|
||||
primary = getPrimaryStatus(pkg.status)
|
||||
dependency = getDependencyStatus(depErrors)
|
||||
health = getHealthStatus(pkg.installed.status)
|
||||
health = getHealthStatus(
|
||||
pkg.status,
|
||||
!isEmptyObject(pkg['state-info'].manifest['health-checks']),
|
||||
)
|
||||
} else {
|
||||
primary = pkg.state
|
||||
primary = pkg['state-info'].state as string as PrimaryStatus
|
||||
}
|
||||
|
||||
return { primary, dependency, health }
|
||||
|
||||
@@ -8,13 +8,11 @@ import {
|
||||
PrimaryStatus,
|
||||
StatusRendering,
|
||||
} from '../services/pkg-status-rendering.service'
|
||||
import { ProgressData } from './progress-data'
|
||||
|
||||
export interface PkgInfo {
|
||||
entry: PackageDataEntry
|
||||
primaryRendering: StatusRendering
|
||||
primaryStatus: PrimaryStatus | PackageState | PackageMainStatus
|
||||
installProgress: ProgressData | null
|
||||
error: boolean
|
||||
warning: boolean
|
||||
transitioning: boolean
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface ProgressData {
|
||||
totalProgress: number
|
||||
downloadProgress: number
|
||||
validateProgress: number
|
||||
unpackProgress: number
|
||||
isComplete: boolean
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Emver } from '@start9labs/shared'
|
||||
import { DataModel } from '../services/patch-db/data-model'
|
||||
import { getManifest } from './get-package-data'
|
||||
|
||||
export function dryUpdate(
|
||||
{ id, version }: { id: string; version: string },
|
||||
@@ -9,9 +10,10 @@ export function dryUpdate(
|
||||
return Object.values(pkgs)
|
||||
.filter(
|
||||
pkg =>
|
||||
Object.keys(pkg.installed?.['current-dependencies'] || {}).some(
|
||||
Object.keys(pkg['current-dependencies'] || {}).some(
|
||||
pkgId => pkgId === id,
|
||||
) && !emver.satisfies(version, pkg.manifest.dependencies[id].version),
|
||||
) &&
|
||||
!emver.satisfies(version, getManifest(pkg).dependencies[id].version),
|
||||
)
|
||||
.map(pkg => pkg.manifest.title)
|
||||
.map(pkg => getManifest(pkg).title)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledState,
|
||||
InstallingState,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@@ -17,3 +22,41 @@ export async function getAllPackages(
|
||||
): Promise<DataModel['package-data']> {
|
||||
return firstValueFrom(patch.watch$('package-data'))
|
||||
}
|
||||
|
||||
export function getManifest(pkg: PackageDataEntry): Manifest {
|
||||
if (isInstalled(pkg) || isRemoving(pkg)) return pkg['state-info'].manifest
|
||||
|
||||
return (pkg['state-info'] as InstallingState)['installing-info'][
|
||||
'new-manifest'
|
||||
]
|
||||
}
|
||||
|
||||
export function isInstalled(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstalledState> {
|
||||
return pkg['state-info'].state === PackageState.Installed
|
||||
}
|
||||
|
||||
export function isRemoving(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstalledState> {
|
||||
return pkg['state-info'].state === PackageState.Removing
|
||||
}
|
||||
|
||||
export function isInstalling(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstallingState> {
|
||||
return pkg['state-info'].state === PackageState.Installing
|
||||
}
|
||||
|
||||
export function isRestoring(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<InstallingState> {
|
||||
return pkg['state-info'].state === PackageState.Restoring
|
||||
}
|
||||
|
||||
export function isUpdating(
|
||||
pkg: PackageDataEntry,
|
||||
): pkg is PackageDataEntry<UpdatingState> {
|
||||
return pkg['state-info'].state === PackageState.Updating
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
renderPkgStatus,
|
||||
} from '../services/pkg-status-rendering.service'
|
||||
import { PkgInfo } from '../types/pkg-info'
|
||||
import { packageLoadingProgress } from './package-loading-progress'
|
||||
import { PkgDependencyErrors } from '../services/dep-error.service'
|
||||
|
||||
export function getPackageInfo(
|
||||
@@ -21,7 +20,6 @@ export function getPackageInfo(
|
||||
entry,
|
||||
primaryRendering,
|
||||
primaryStatus: statuses.primary,
|
||||
installProgress: packageLoadingProgress(entry['install-progress']),
|
||||
error:
|
||||
statuses.health === HealthStatus.Failure ||
|
||||
statuses.dependency === DependencyStatus.Warning,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { PackageDataEntry } from '../services/patch-db/data-model'
|
||||
import { getManifest } from './get-package-data'
|
||||
|
||||
export function hasCurrentDeps(pkg: PackageDataEntry): boolean {
|
||||
return !!Object.keys(pkg.installed?.['current-dependents'] || {}).filter(
|
||||
depId => depId !== pkg.manifest.id,
|
||||
return !!Object.keys(pkg['current-dependents']).filter(
|
||||
depId => depId !== getManifest(pkg).id,
|
||||
).length
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { isEmptyObject } from '@start9labs/shared'
|
||||
import { ProgressData } from 'src/app/types/progress-data'
|
||||
import { InstallProgress } from '../services/patch-db/data-model'
|
||||
|
||||
export function packageLoadingProgress(
|
||||
loadData?: InstallProgress,
|
||||
): ProgressData | null {
|
||||
if (!loadData || isEmptyObject(loadData)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let {
|
||||
downloaded,
|
||||
validated,
|
||||
unpacked,
|
||||
size,
|
||||
'download-complete': downloadComplete,
|
||||
'validation-complete': validationComplete,
|
||||
'unpack-complete': unpackComplete,
|
||||
} = loadData
|
||||
|
||||
// only permit 100% when "complete" == true
|
||||
size = size || 0
|
||||
downloaded = downloadComplete ? size : Math.max(downloaded - 1, 0)
|
||||
validated = validationComplete ? size : Math.max(validated - 1, 0)
|
||||
unpacked = unpackComplete ? size : Math.max(unpacked - 1, 0)
|
||||
|
||||
const downloadWeight = 1
|
||||
const validateWeight = 0.2
|
||||
const unpackWeight = 0.7
|
||||
|
||||
const numerator = Math.floor(
|
||||
downloadWeight * downloaded +
|
||||
validateWeight * validated +
|
||||
unpackWeight * unpacked,
|
||||
)
|
||||
|
||||
const denominator = Math.floor(
|
||||
size * (downloadWeight + validateWeight + unpackWeight),
|
||||
)
|
||||
const totalProgress = Math.floor((100 * numerator) / denominator)
|
||||
|
||||
return {
|
||||
totalProgress,
|
||||
downloadProgress: Math.floor((100 * downloaded) / size),
|
||||
validateProgress: Math.floor((100 * validated) / size),
|
||||
unpackProgress: Math.floor((100 * unpacked) / size),
|
||||
isComplete: downloadComplete && validationComplete && unpackComplete,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user