mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
add error status (#2746)
* add error status * update types * ṗ̶̰̙̓͒̈́ͅü̵̢̙̫̣ŗ̷̪̺̺͛g̴̲͉͎̬̒̇e̵̪̎̅͌ ̶̡̜̘͐͛t̶͎͍̣̿̍̐h̴͕̩͗̈́̎̑e̵͚͒̂͝ ̸̛͙̦͈͝v̶̱͙̬̽̔ọ̶̧̡̒̓i̸̬̲͍̋̈́d̴͉̀ * fix some extra voids * add `package.rebuild` * introduce error status and pkg rebuild and fix mocks * minor fixes * fix build --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -257,6 +257,9 @@ export module RR {
|
||||
export type StopPackageReq = { id: string } // package.stop
|
||||
export type StopPackageRes = null
|
||||
|
||||
export type RebuildPackageReq = { id: string } // package.rebuild
|
||||
export type RebuildPackageRes = null
|
||||
|
||||
export type UninstallPackageReq = { id: string } // package.uninstall
|
||||
export type UninstallPackageRes = null
|
||||
|
||||
|
||||
@@ -249,6 +249,10 @@ export abstract class ApiService {
|
||||
|
||||
abstract stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes>
|
||||
|
||||
abstract rebuildPackage(
|
||||
params: RR.RebuildPackageReq,
|
||||
): Promise<RR.RebuildPackageRes>
|
||||
|
||||
abstract uninstallPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes>
|
||||
|
||||
@@ -498,6 +498,12 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'package.stop', params })
|
||||
}
|
||||
|
||||
async rebuildPackage(
|
||||
params: RR.RebuildPackageReq,
|
||||
): Promise<RR.RebuildPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.rebuild', params })
|
||||
}
|
||||
|
||||
async uninstallPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes> {
|
||||
|
||||
@@ -2,10 +2,12 @@ import { Injectable } from '@angular/core'
|
||||
import { Log, RPCErrorDetails, RPCOptions, pauseFor } from '@start9labs/shared'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
import {
|
||||
AddOperation,
|
||||
Operation,
|
||||
PatchOp,
|
||||
pathFromArray,
|
||||
RemoveOperation,
|
||||
ReplaceOperation,
|
||||
Revision,
|
||||
} from 'patch-db-client'
|
||||
import {
|
||||
@@ -636,14 +638,14 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
|
||||
await pauseFor(2000)
|
||||
const path = '/serverInfo/statusInfo/backupProgress'
|
||||
const serverPath = '/serverInfo/statusInfo/backupProgress'
|
||||
const ids = params.packageIds
|
||||
|
||||
setTimeout(async () => {
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i]
|
||||
const appPath = `/packageData/${id}/status/main/status`
|
||||
const appPatch = [
|
||||
const appPath = `/packageData/${id}/status/main/`
|
||||
const appPatch: ReplaceOperation<T.MainStatus['main']>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: appPath,
|
||||
@@ -660,40 +662,43 @@ export class MockApiService extends ApiService {
|
||||
value: 'stopped',
|
||||
},
|
||||
])
|
||||
this.mockRevision([
|
||||
|
||||
const serverPatch: ReplaceOperation<T.BackupProgress['complete']>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `${path}/${id}/complete`,
|
||||
path: `${serverPath}/${id}/complete`,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
]
|
||||
this.mockRevision(serverPatch)
|
||||
}
|
||||
|
||||
await pauseFor(1000)
|
||||
|
||||
// set server back to running
|
||||
const lastPatch = [
|
||||
// remove backupProgress
|
||||
const lastPatch: ReplaceOperation<T.ServerStatus['backupProgress']>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path,
|
||||
path: serverPath,
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.mockRevision(lastPatch)
|
||||
}, 500)
|
||||
|
||||
const originalPatch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path,
|
||||
value: ids.reduce((acc, val) => {
|
||||
return {
|
||||
...acc,
|
||||
[val]: { complete: false },
|
||||
}
|
||||
}, {}),
|
||||
},
|
||||
]
|
||||
const originalPatch: ReplaceOperation<T.ServerStatus['backupProgress']>[] =
|
||||
[
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: serverPath,
|
||||
value: ids.reduce((acc, val) => {
|
||||
return {
|
||||
...acc,
|
||||
[val]: { complete: false },
|
||||
}
|
||||
}, {}),
|
||||
},
|
||||
]
|
||||
|
||||
this.mockRevision(originalPatch)
|
||||
|
||||
@@ -750,7 +755,7 @@ export class MockApiService extends ApiService {
|
||||
this.installProgress(params.id)
|
||||
}, 1000)
|
||||
|
||||
const patch: Operation<
|
||||
const patch: AddOperation<
|
||||
PackageDataEntry<InstallingState | UpdatingState>
|
||||
>[] = [
|
||||
{
|
||||
@@ -799,7 +804,7 @@ export class MockApiService extends ApiService {
|
||||
params: RR.RestorePackagesReq,
|
||||
): Promise<RR.RestorePackagesRes> {
|
||||
await pauseFor(2000)
|
||||
const patch: Operation<PackageDataEntry>[] = params.ids.map(id => {
|
||||
const patch: AddOperation<PackageDataEntry>[] = params.ids.map(id => {
|
||||
setTimeout(async () => {
|
||||
this.installProgress(id)
|
||||
}, 2000)
|
||||
@@ -826,76 +831,61 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
|
||||
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
|
||||
const path = `/packageData/${params.id}/status/main`
|
||||
const path = `/packageData/${params.id}/status`
|
||||
|
||||
await pauseFor(2000)
|
||||
|
||||
setTimeout(async () => {
|
||||
const patch2 = [
|
||||
const patch2: ReplaceOperation<T.MainStatus & { main: 'running' }>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: 'running',
|
||||
},
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/started',
|
||||
value: new Date().toISOString(),
|
||||
path,
|
||||
value: {
|
||||
main: 'running',
|
||||
started: new Date().toISOString(),
|
||||
health: {
|
||||
'ephemeral-health-check': {
|
||||
name: 'Ephemeral Health Check',
|
||||
result: 'starting',
|
||||
message: null,
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
name: 'Unnecessary Health Check',
|
||||
result: 'disabled',
|
||||
message: 'Custom disabled message',
|
||||
},
|
||||
'chain-state': {
|
||||
name: 'Chain State',
|
||||
result: 'loading',
|
||||
message: 'Bitcoin is syncing from genesis',
|
||||
},
|
||||
'p2p-interface': {
|
||||
name: 'P2P Interface',
|
||||
result: 'success',
|
||||
message: null,
|
||||
},
|
||||
'rpc-interface': {
|
||||
name: 'RPC Interface',
|
||||
result: 'failure',
|
||||
message: 'Custom failure message',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
|
||||
const patch3 = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/health',
|
||||
value: {
|
||||
'ephemeral-health-check': {
|
||||
result: 'starting',
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
result: 'disabled',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch3)
|
||||
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch4 = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/health',
|
||||
value: {
|
||||
'ephemeral-health-check': {
|
||||
result: 'starting',
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
result: 'disabled',
|
||||
},
|
||||
'chain-state': {
|
||||
result: 'loading',
|
||||
message: 'Bitcoin is syncing from genesis',
|
||||
},
|
||||
'p2p-interface': {
|
||||
result: 'success',
|
||||
},
|
||||
'rpc-interface': {
|
||||
result: 'failure',
|
||||
error: 'RPC interface unreachable.',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch4)
|
||||
}, 2000)
|
||||
|
||||
const originalPatch = [
|
||||
const originalPatch: ReplaceOperation<
|
||||
T.MainStatus & { main: 'starting' }
|
||||
>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: 'starting',
|
||||
path,
|
||||
value: {
|
||||
main: 'starting',
|
||||
health: {},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -907,74 +897,57 @@ export class MockApiService extends ApiService {
|
||||
async restartPackage(
|
||||
params: RR.RestartPackageReq,
|
||||
): Promise<RR.RestartPackageRes> {
|
||||
// first enact stop
|
||||
await pauseFor(2000)
|
||||
const path = `/packageData/${params.id}/status/main`
|
||||
const path = `/packageData/${params.id}/status`
|
||||
|
||||
setTimeout(async () => {
|
||||
const patch2: Operation<any>[] = [
|
||||
const patch2: ReplaceOperation<T.MainStatus & { main: 'running' }>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: 'starting',
|
||||
},
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: path + '/restarting',
|
||||
value: true,
|
||||
path,
|
||||
value: {
|
||||
main: 'running',
|
||||
started: new Date().toISOString(),
|
||||
health: {
|
||||
'ephemeral-health-check': {
|
||||
name: 'Ephemeral Health Check',
|
||||
result: 'starting',
|
||||
message: null,
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
name: 'Unnecessary Health Check',
|
||||
result: 'disabled',
|
||||
message: 'Custom disabled message',
|
||||
},
|
||||
'chain-state': {
|
||||
name: 'Chain State',
|
||||
result: 'loading',
|
||||
message: 'Bitcoin is syncing from genesis',
|
||||
},
|
||||
'p2p-interface': {
|
||||
name: 'P2P Interface',
|
||||
result: 'success',
|
||||
message: null,
|
||||
},
|
||||
'rpc-interface': {
|
||||
name: 'RPC Interface',
|
||||
result: 'failure',
|
||||
message: 'Custom failure message',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch3: Operation<any>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: 'running',
|
||||
},
|
||||
{
|
||||
op: PatchOp.REMOVE,
|
||||
path: path + '/restarting',
|
||||
},
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/health',
|
||||
value: {
|
||||
'ephemeral-health-check': {
|
||||
result: 'starting',
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
result: 'disabled',
|
||||
},
|
||||
'chain-state': {
|
||||
result: 'loading',
|
||||
message: 'Bitcoin is syncing from genesis',
|
||||
},
|
||||
'p2p-interface': {
|
||||
result: 'success',
|
||||
},
|
||||
'rpc-interface': {
|
||||
result: 'failure',
|
||||
error: 'RPC interface unreachable.',
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
]
|
||||
this.mockRevision(patch3)
|
||||
}, this.revertTime)
|
||||
|
||||
const patch = [
|
||||
const patch: ReplaceOperation<T.MainStatus & { main: 'restarting' }>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/status',
|
||||
value: 'restarting',
|
||||
},
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path + '/health',
|
||||
value: {},
|
||||
path,
|
||||
value: {
|
||||
main: 'restarting',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -985,29 +958,24 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
await pauseFor(2000)
|
||||
const path = `/packageData/${params.id}/status/main`
|
||||
const path = `/packageData/${params.id}/status`
|
||||
|
||||
setTimeout(() => {
|
||||
const patch2 = [
|
||||
const patch2: ReplaceOperation<T.MainStatus & { main: 'stopped' }>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path,
|
||||
value: {
|
||||
status: 'stopped',
|
||||
},
|
||||
value: { main: 'stopped' },
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch2)
|
||||
}, this.revertTime)
|
||||
|
||||
const patch = [
|
||||
const patch: ReplaceOperation<T.MainStatus & { main: 'stopping' }>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: path,
|
||||
value: {
|
||||
status: 'stopping',
|
||||
timeout: '35s',
|
||||
},
|
||||
value: { main: 'stopping' },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1016,6 +984,12 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async rebuildPackage(
|
||||
params: RR.RebuildPackageReq,
|
||||
): Promise<RR.RebuildPackageRes> {
|
||||
return this.restartPackage(params)
|
||||
}
|
||||
|
||||
async uninstallPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes> {
|
||||
@@ -1031,7 +1005,7 @@ export class MockApiService extends ApiService {
|
||||
this.mockRevision(patch2)
|
||||
}, this.revertTime)
|
||||
|
||||
const patch = [
|
||||
const patch: ReplaceOperation<T.PackageState['state']>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/packageData/${params.id}/stateInfo/state`,
|
||||
|
||||
@@ -96,36 +96,14 @@ export const mockPatchData: DataModel = {
|
||||
icon: '/assets/img/service-icons/bitcoind.svg',
|
||||
lastBackup: null,
|
||||
status: {
|
||||
main: 'running',
|
||||
started: '2021-06-14T20:49:17.774Z',
|
||||
health: {
|
||||
'ephemeral-health-check': {
|
||||
name: 'Ephemeral Health Check',
|
||||
result: 'starting',
|
||||
message: null,
|
||||
},
|
||||
'chain-state': {
|
||||
name: 'Chain State',
|
||||
result: 'loading',
|
||||
message: 'Bitcoin is syncing from genesis',
|
||||
},
|
||||
'p2p-interface': {
|
||||
name: 'P2P',
|
||||
result: 'success',
|
||||
message: 'Health check successful',
|
||||
},
|
||||
'rpc-interface': {
|
||||
name: 'RPC',
|
||||
result: 'failure',
|
||||
message: 'RPC interface unreachable.',
|
||||
},
|
||||
'unnecessary-health-check': {
|
||||
name: 'Unnecessary Health Check',
|
||||
result: 'disabled',
|
||||
message: null,
|
||||
},
|
||||
},
|
||||
main: 'stopped',
|
||||
},
|
||||
// status: {
|
||||
// main: 'error',
|
||||
// message: 'Bitcoin is erroring out',
|
||||
// debug: 'This is a complete stack trace for bitcoin',
|
||||
// onRebuild: 'start',
|
||||
// },
|
||||
actions: {
|
||||
config: {
|
||||
name: 'Bitcoin Config',
|
||||
|
||||
@@ -80,6 +80,7 @@ export type PrimaryStatus =
|
||||
| 'stopped'
|
||||
| 'backingUp'
|
||||
| 'needsConfig'
|
||||
| 'error'
|
||||
|
||||
export type DependencyStatus = 'warning' | 'satisfied'
|
||||
|
||||
@@ -139,6 +140,11 @@ export const PrimaryRendering: Record<PrimaryStatus, StatusRendering> = {
|
||||
color: 'warning',
|
||||
showDots: false,
|
||||
},
|
||||
error: {
|
||||
display: 'Service Launch Error',
|
||||
color: 'danger',
|
||||
showDots: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const DependencyRendering: Record<DependencyStatus, StatusRendering> = {
|
||||
|
||||
85
web/projects/ui/src/app/services/standard-actions.service.ts
Normal file
85
web/projects/ui/src/app/services/standard-actions.service.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { hasCurrentDeps } from '../util/has-deps'
|
||||
import { getAllPackages } from '../util/get-package-data'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
import { AlertController, NavController } from '@ionic/angular'
|
||||
import { ApiService } from './api/embassy-api.service'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StandardActionsService {
|
||||
constructor(
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly api: ApiService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly errorService: ErrorService,
|
||||
private readonly loader: LoadingService,
|
||||
private readonly navCtrl: NavController,
|
||||
) {}
|
||||
|
||||
async rebuild(id: string) {
|
||||
const loader = this.loader.open(`Rebuilding Container...`).subscribe()
|
||||
|
||||
try {
|
||||
await this.api.rebuildPackage({ id })
|
||||
this.navCtrl.navigateBack('/services/' + id)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
async tryUninstall(manifest: T.Manifest): Promise<void> {
|
||||
const { id, title, alerts } = manifest
|
||||
|
||||
let message =
|
||||
alerts.uninstall ||
|
||||
`Uninstalling ${title} will permanently delete its data`
|
||||
|
||||
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
|
||||
message = `${message}. Services that depend on ${title} will no longer work properly and may crash`
|
||||
}
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Warning',
|
||||
message,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Uninstall',
|
||||
handler: () => {
|
||||
this.uninstall(id)
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
cssClass: 'alert-warning-message',
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async uninstall(id: string) {
|
||||
const loader = this.loader.open(`Beginning uninstall...`).subscribe()
|
||||
|
||||
try {
|
||||
await this.api.uninstallPackage({ id })
|
||||
this.api
|
||||
.setDbValue<boolean>(['ackInstructions', id], false)
|
||||
.catch(e => console.error('Failed to mark instructions as unseen', e))
|
||||
this.navCtrl.navigateRoot('/services')
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user