mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Fix/fe bugs 3 (#2943)
* fix typeo in patch db seed * show all registries in updates tab, fix required dependnecy display in marketplace, update browser tab title desc * always show pointer for version select * chore: fix comments * support html in action desc and marketplace long desc, only show qr in action res if qr is true * disable save if smtp creds not edited, show better smtp success message * dont dismiss login spinner until patchDB returns * feat: redesign of service dashboard and interface (#2946) * feat: redesign of service dashboard and interface * chore: comments * re-add setup complete * dibale launch UI when not running, re-style things, rename things * back to 1000 * fix clearnet docs link and require password retype in setup wiz * faster hint display * display dependency ID if title not available * fix migration * better init progress view * fix setup success page by providing VERSION and notifications page fixes * force uninstall from service error page, soft or hard * handle error state better * chore: fixed for install and setup wizards * chore: fix issues (#2949) * enable and disable kiosk mode * minor fixes * fix dependency mounts * dismissable tasks * provide replayId * default if health check success message is null * look for wifi interface too * dash for null user agent in sessions * add disk repair to diagnostic api --------- Co-authored-by: waterplea <alexander@inkin.ru> Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -18,7 +18,7 @@ const mockMerkleArchiveCommitment: T.MerkleArchiveCommitment = {
|
||||
|
||||
const mockDescription = {
|
||||
short: 'Lorem ipsum dolor sit amet',
|
||||
long: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
long: 'Lorem ipsum dolor sit amet, <p>consectetur adipiscing elit</p>, sed do eiusmod <i>tempor</i> incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
}
|
||||
|
||||
export namespace Mock {
|
||||
@@ -632,55 +632,6 @@ export namespace Mock {
|
||||
},
|
||||
},
|
||||
},
|
||||
'btc-rpc-proxy': {
|
||||
'=0.3.2.6:0': {
|
||||
best: {
|
||||
'0.3.2.6:0': {
|
||||
title: 'Bitcoin Proxy',
|
||||
description: mockDescription,
|
||||
hardwareRequirements: { arch: null, device: [], ram: null },
|
||||
license: 'mit',
|
||||
wrapperRepo: 'https://github.com/Start9Labs/btc-rpc-proxy-wrappers',
|
||||
upstreamRepo: 'https://github.com/Kixunil/btc-rpc-proxy',
|
||||
supportSite: 'https://github.com/Kixunil/btc-rpc-proxy/issues',
|
||||
marketingSite: '',
|
||||
releaseNotes: 'Upstream release and minor fixes.',
|
||||
osVersion: '0.3.6',
|
||||
gitHash: 'fakehash',
|
||||
icon: PROXY_ICON,
|
||||
sourceVersion: null,
|
||||
dependencyMetadata: {
|
||||
bitcoind: {
|
||||
title: 'Bitcoin Core',
|
||||
icon: BTC_ICON,
|
||||
description: 'Used for RPC requests',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
donationUrl: null,
|
||||
alerts: {
|
||||
install: 'test',
|
||||
uninstall: 'test',
|
||||
start: 'test',
|
||||
stop: 'test',
|
||||
restore: 'test',
|
||||
},
|
||||
s9pk: {
|
||||
url: 'https://github.com/Start9Labs/btc-rpc-proxy-startos/releases/download/v0.3.2.7.1/btc-rpc-proxy.s9pk',
|
||||
commitment: mockMerkleArchiveCommitment,
|
||||
signatures: {},
|
||||
publishedAt: Date.now().toString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
categories: ['bitcoin'],
|
||||
otherVersions: {
|
||||
'0.3.2.7:0': {
|
||||
releaseNotes: 'Upstream release and minor fixes.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const RegistryPackages: GetPackagesRes = {
|
||||
@@ -857,11 +808,7 @@ export namespace Mock {
|
||||
},
|
||||
},
|
||||
categories: ['bitcoin'],
|
||||
otherVersions: {
|
||||
'0.3.2.6:0': {
|
||||
releaseNotes: 'Upstream release and minor fixes.',
|
||||
},
|
||||
},
|
||||
otherVersions: {},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -891,7 +838,7 @@ export namespace Mock {
|
||||
id: 2,
|
||||
packageId: null,
|
||||
createdAt: '2019-12-26T14:20:30.872Z',
|
||||
code: 2,
|
||||
code: 0,
|
||||
level: 'warning',
|
||||
title: 'SSH Key Added',
|
||||
message: 'A new SSH key was added. If you did not do this, shit is bad.',
|
||||
@@ -902,7 +849,7 @@ export namespace Mock {
|
||||
id: 3,
|
||||
packageId: null,
|
||||
createdAt: '2019-12-26T14:20:30.872Z',
|
||||
code: 3,
|
||||
code: 0,
|
||||
level: 'info',
|
||||
title: 'SSH Key Removed',
|
||||
message: 'A SSH key was removed.',
|
||||
@@ -913,7 +860,7 @@ export namespace Mock {
|
||||
id: 4,
|
||||
packageId: 'bitcoind',
|
||||
createdAt: '2019-12-26T14:20:30.872Z',
|
||||
code: 4,
|
||||
code: 0,
|
||||
level: 'error',
|
||||
title: 'Service Crashed',
|
||||
message: new Array(3)
|
||||
@@ -1339,7 +1286,7 @@ export namespace Mock {
|
||||
result: {
|
||||
type: 'single',
|
||||
copyable: true,
|
||||
qr: true,
|
||||
qr: false,
|
||||
masked: true,
|
||||
value: 'iwejdoiewdhbew',
|
||||
},
|
||||
|
||||
@@ -335,6 +335,12 @@ export namespace RR {
|
||||
} // package.action.run
|
||||
export type ActionRes = (T.ActionResult & { version: '1' }) | null
|
||||
|
||||
export type ClearTaskReq = {
|
||||
packageId: string
|
||||
replayId: string
|
||||
} // package.action.clear-task
|
||||
export type ClearTaskRes = null
|
||||
|
||||
export type RestorePackagesReq = {
|
||||
// package.backup.restore
|
||||
ids: string[]
|
||||
@@ -356,7 +362,11 @@ export namespace RR {
|
||||
export type RebuildPackageReq = { id: string } // package.rebuild
|
||||
export type RebuildPackageRes = null
|
||||
|
||||
export type UninstallPackageReq = { id: string } // package.uninstall
|
||||
export type UninstallPackageReq = {
|
||||
id: string
|
||||
force: boolean
|
||||
soft: boolean
|
||||
} // package.uninstall
|
||||
export type UninstallPackageRes = null
|
||||
|
||||
export type SideloadPackageReq = {
|
||||
|
||||
@@ -120,6 +120,8 @@ export abstract class ApiService {
|
||||
|
||||
abstract repairDisk(params: RR.DiskRepairReq): Promise<RR.DiskRepairRes>
|
||||
|
||||
abstract toggleKiosk(enable: boolean): Promise<null>
|
||||
|
||||
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
|
||||
|
||||
// @TODO 041
|
||||
@@ -335,6 +337,8 @@ export abstract class ApiService {
|
||||
|
||||
abstract runAction(params: RR.ActionReq): Promise<RR.ActionRes>
|
||||
|
||||
abstract clearTask(params: RR.ClearTaskReq): Promise<RR.ClearTaskRes>
|
||||
|
||||
abstract restorePackages(
|
||||
params: RR.RestorePackagesReq,
|
||||
): Promise<RR.RestorePackagesRes>
|
||||
|
||||
@@ -261,6 +261,13 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'disk.repair', params })
|
||||
}
|
||||
|
||||
async toggleKiosk(enable: boolean): Promise<null> {
|
||||
return this.rpcRequest({
|
||||
method: enable ? 'kiosk.enable' : 'kiosk.disable',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
async resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes> {
|
||||
return this.rpcRequest({ method: 'net.tor.reset', params })
|
||||
}
|
||||
@@ -577,6 +584,10 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'package.action.run', params })
|
||||
}
|
||||
|
||||
async clearTask(params: RR.ClearTaskReq): Promise<RR.ClearTaskRes> {
|
||||
return this.rpcRequest({ method: 'package.action.clear-task', params })
|
||||
}
|
||||
|
||||
async restorePackages(
|
||||
params: RR.RestorePackagesReq,
|
||||
): Promise<RR.RestorePackagesRes> {
|
||||
|
||||
@@ -22,11 +22,7 @@ import { from, interval, map, shareReplay, startWith, Subject, tap } from 'rxjs'
|
||||
import { mockPatchData } from './mock-patch'
|
||||
import { AuthService } from '../auth.service'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import {
|
||||
GetPackageRes,
|
||||
GetPackagesRes,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import markdown from 'raw-loader!../../../../../shared/assets/markdown/md-sample.md'
|
||||
import { WebSocketSubject } from 'rxjs/webSocket'
|
||||
import { toAcmeUrl } from 'src/app/utils/acme'
|
||||
@@ -166,7 +162,6 @@ export class MockApiService extends ApiService {
|
||||
pathArr: Array<string | number>,
|
||||
value: T,
|
||||
): Promise<RR.SetDBValueRes> {
|
||||
console.warn(pathArr, value)
|
||||
const pointer = pathFromArray(pathArr)
|
||||
const params: RR.SetDBValueReq<T> = { pointer, value }
|
||||
await pauseFor(2000)
|
||||
@@ -449,6 +444,21 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async toggleKiosk(enable: boolean): Promise<null> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/serverInfo/kiosk',
|
||||
value: enable,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes> {
|
||||
await pauseFor(2000)
|
||||
return null
|
||||
@@ -1103,23 +1113,32 @@ export class MockApiService extends ApiService {
|
||||
async runAction(params: RR.ActionReq): Promise<RR.ActionRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
if (params.actionId === 'properties') {
|
||||
// return Mock.ActionResGroup
|
||||
return Mock.ActionResMessage
|
||||
// return Mock.ActionResSingle
|
||||
} else if (params.actionId === 'config') {
|
||||
const patch: RemoveOperation[] = [
|
||||
{
|
||||
op: PatchOp.REMOVE,
|
||||
path: `/packageData/${params.packageId}/requestedActions/${params.packageId}-config`,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
return null
|
||||
} else {
|
||||
return Mock.ActionResMessage
|
||||
// return Mock.ActionResSingle
|
||||
}
|
||||
const patch: ReplaceOperation<{ [key: string]: T.TaskEntry }>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/packageData/${params.packageId}/tasks`,
|
||||
value: {},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
// return Mock.ActionResGroup
|
||||
return Mock.ActionResMessage
|
||||
// return Mock.ActionResSingle
|
||||
}
|
||||
|
||||
async clearTask(params: RR.ClearTaskReq): Promise<RR.ClearTaskRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: RemoveOperation[] = [
|
||||
{
|
||||
op: PatchOp.REMOVE,
|
||||
path: `/packageData/${params.packageId}/tasks/${params.replayId}`,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async restorePackages(
|
||||
|
||||
@@ -183,13 +183,7 @@ export const mockPatchData: DataModel = {
|
||||
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
|
||||
caFingerprint: '63:2B:11:99:44:40:17:DF:37:FC:C3:DF:0F:3D:15',
|
||||
ntpSynced: false,
|
||||
smtp: {
|
||||
server: '',
|
||||
port: 587,
|
||||
from: '',
|
||||
login: '',
|
||||
password: '',
|
||||
},
|
||||
smtp: null,
|
||||
platform: 'x86_64-nonfree',
|
||||
zram: true,
|
||||
governor: 'performance',
|
||||
@@ -221,7 +215,7 @@ export const mockPatchData: DataModel = {
|
||||
actions: {
|
||||
config: {
|
||||
name: 'Set Config',
|
||||
description: 'edit bitcoin.conf',
|
||||
description: 'edit bitcoin.conf, <b>soo cool!</b>',
|
||||
warning: null,
|
||||
visibility: 'enabled',
|
||||
allowedStatuses: 'any',
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { ReplaySubject } from 'rxjs'
|
||||
import { StorageService } from './storage.service'
|
||||
|
||||
const SHOW_DEV_TOOLS = 'SHOW_DEV_TOOLS'
|
||||
const SHOW_DISK_REPAIR = 'SHOW_DISK_REPAIR'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ClientStorageService {
|
||||
private readonly storage = inject(StorageService)
|
||||
readonly showDevTools$ = new ReplaySubject<boolean>(1)
|
||||
readonly showDiskRepair$ = new ReplaySubject<boolean>(1)
|
||||
|
||||
constructor(private readonly storage: StorageService) {}
|
||||
|
||||
init() {
|
||||
this.showDevTools$.next(!!this.storage.get(SHOW_DEV_TOOLS))
|
||||
this.showDiskRepair$.next(!!this.storage.get(SHOW_DISK_REPAIR))
|
||||
}
|
||||
|
||||
toggleShowDevTools(): boolean {
|
||||
@@ -25,11 +21,4 @@ export class ClientStorageService {
|
||||
this.showDevTools$.next(newVal)
|
||||
return newVal
|
||||
}
|
||||
|
||||
toggleShowDiskRepair(): boolean {
|
||||
const newVal = !this.storage.get(SHOW_DISK_REPAIR)
|
||||
this.storage.set(SHOW_DISK_REPAIR, newVal)
|
||||
this.showDiskRepair$.next(newVal)
|
||||
return newVal
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { ClientStorageService } from './client-storage.service'
|
||||
|
||||
const { start9, community } = defaultRegistries
|
||||
|
||||
@@ -55,20 +54,6 @@ export class MarketplaceService {
|
||||
]),
|
||||
)
|
||||
|
||||
// option to filter out hosts containing 'alpha' or 'beta' substrings in registryURL
|
||||
readonly filteredRegistries$: Observable<StoreIdentity[]> = combineLatest([
|
||||
inject(ClientStorageService).showDevTools$,
|
||||
this.registries$,
|
||||
]).pipe(
|
||||
map(([devMode, registries]) =>
|
||||
devMode
|
||||
? registries
|
||||
: registries.filter(
|
||||
({ url }) => !url.includes('alpha') && !url.includes('beta'),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
readonly currentRegistryUrl$ = new ReplaySubject<string>(1)
|
||||
|
||||
readonly requestErrors$ = new BehaviorSubject<string[]>([])
|
||||
@@ -252,7 +237,6 @@ export class MarketplaceService {
|
||||
oldName: string | null,
|
||||
newName: string,
|
||||
): Promise<void> {
|
||||
console.warn(oldName, newName)
|
||||
if (oldName !== newName) {
|
||||
this.api.setDbValue<string>(['registries', url], newName)
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export class NotificationService {
|
||||
{ data, createdAt, code, title, message }: ServerNotification<number>,
|
||||
full = false,
|
||||
) {
|
||||
const label = full || code === 2 ? title : 'Backup Report'
|
||||
const label = code === 1 ? 'Backup Report' : title
|
||||
const component = code === 1 ? REPORT : MARKDOWN
|
||||
const content = code === 1 ? data : of(data)
|
||||
|
||||
@@ -104,6 +104,7 @@ export class NotificationService {
|
||||
content,
|
||||
timestamp: createdAt,
|
||||
},
|
||||
size: code === 1 ? 'm' : 'l',
|
||||
})
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PkgDependencyErrors } from './dep-error.service'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { i18nKey } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
export interface PackageStatus {
|
||||
primary: PrimaryStatus
|
||||
dependency: DependencyStatus | null
|
||||
health: T.HealthStatus | null
|
||||
}
|
||||
|
||||
export function renderPkgStatus(
|
||||
pkg: PackageDataEntry,
|
||||
depErrors: PkgDependencyErrors = {},
|
||||
): PackageStatus {
|
||||
export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus {
|
||||
let primary: PrimaryStatus
|
||||
let dependency: DependencyStatus | null = null
|
||||
let health: T.HealthStatus | null = null
|
||||
|
||||
if (pkg.stateInfo.state === 'installed') {
|
||||
primary = getInstalledPrimaryStatus(pkg)
|
||||
dependency = getDependencyStatus(depErrors)
|
||||
health = getHealthStatus(pkg.status)
|
||||
} else {
|
||||
primary = pkg.stateInfo.state
|
||||
}
|
||||
|
||||
return { primary, dependency, health }
|
||||
return { primary, health }
|
||||
}
|
||||
|
||||
export function getInstalledPrimaryStatus({
|
||||
@@ -39,10 +32,6 @@ export function getInstalledPrimaryStatus({
|
||||
: status.main
|
||||
}
|
||||
|
||||
function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus {
|
||||
return Object.values(depErrors).some(err => !!err) ? 'warning' : 'satisfied'
|
||||
}
|
||||
|
||||
function getHealthStatus(status: T.MainStatus): T.HealthStatus | null {
|
||||
if (status.main !== 'running' || !status.main) {
|
||||
return null
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getAllPackages } from '../utils/get-package-data'
|
||||
import { hasCurrentDeps } from '../utils/has-deps'
|
||||
import { ApiService } from './api/embassy-api.service'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
import { RR } from './api/api.types'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -40,13 +41,20 @@ export class StandardActionsService {
|
||||
}
|
||||
}
|
||||
|
||||
async uninstall({ id, title, alerts }: T.Manifest): Promise<void> {
|
||||
let content =
|
||||
alerts.uninstall ||
|
||||
`${this.i18n.transform('Uninstalling')} ${title} ${this.i18n.transform('will permanently delete its data.')}`
|
||||
async uninstall(
|
||||
{ id, title, alerts }: T.Manifest,
|
||||
{ force, soft }: { force: boolean; soft: boolean } = {
|
||||
force: false,
|
||||
soft: false,
|
||||
},
|
||||
): Promise<void> {
|
||||
let content = soft
|
||||
? ''
|
||||
: alerts.uninstall ||
|
||||
`${this.i18n.transform('Uninstalling')} ${title} ${this.i18n.transform('will permanently delete its data.')}`
|
||||
|
||||
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
|
||||
content = `${content}. ${this.i18n.transform('Services that depend on')} ${title} ${this.i18n.transform('will no longer work properly and may crash.')}`
|
||||
content = `${content}${content ? ' ' : ''}${this.i18n.transform('Services that depend on')} ${title} ${this.i18n.transform('will no longer work properly and may crash.')}`
|
||||
}
|
||||
|
||||
this.dialog
|
||||
@@ -60,17 +68,15 @@ export class StandardActionsService {
|
||||
},
|
||||
})
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(() => this.doUninstall(id))
|
||||
.subscribe(() => this.doUninstall({ id, force, soft }))
|
||||
}
|
||||
|
||||
private async doUninstall(id: string) {
|
||||
private async doUninstall(options: RR.UninstallPackageReq) {
|
||||
const loader = this.loader.open('Beginning uninstall').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.uninstallPackage({ id })
|
||||
await this.api
|
||||
.setDbValue<boolean>(['ackInstructions', id], false)
|
||||
.catch(e => console.error('Failed to mark instructions as unseen', e))
|
||||
await this.api.uninstallPackage(options)
|
||||
await this.api.setDbValue<boolean>(['ackInstructions', options.id], false)
|
||||
await this.router.navigate(['portal'])
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
Reference in New Issue
Block a user