mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
rework LAN display and service launchability
This commit is contained in:
committed by
Keagan McClelland
parent
daf701a76c
commit
deb0b1e561
@@ -1,5 +1,5 @@
|
||||
import { ConfigSpec } from 'src/app/app-config/config-types'
|
||||
import { AppAvailableFull, AppInstalledFull } from 'src/app/models/app-types'
|
||||
import { AppAvailableFull, AppInstalledFull, AppInstalledPreview } from 'src/app/models/app-types'
|
||||
import { Rules } from '../../models/app-model'
|
||||
import { SSHFingerprint, ServerStatus, ServerSpecs } from '../../models/server-model'
|
||||
|
||||
@@ -23,7 +23,9 @@ export interface ApiServer {
|
||||
|
||||
/** APPS **/
|
||||
export type ApiAppAvailableFull = Omit<AppAvailableFull, 'versionViewing'>
|
||||
export type ApiAppInstalledFull = Omit<AppInstalledFull, 'hasFetchedFull'>
|
||||
|
||||
export type ApiAppInstalledPreview = Omit<AppInstalledPreview, 'hasUI' | 'launchable'>
|
||||
export type ApiAppInstalledFull = Omit<AppInstalledFull, 'hasFetchedFull' | 'hasUI' | 'launchable'>
|
||||
|
||||
export interface ApiAppConfig {
|
||||
spec: ConfigSpec
|
||||
|
||||
@@ -7,8 +7,8 @@ import { ConfigService } from '../config.service'
|
||||
|
||||
export function ApiServiceFactory (config: ConfigService, http: HttpService, appModel: AppModel, serverModel: ServerModel) {
|
||||
if (config.api.useMocks) {
|
||||
return new MockApiService(appModel, serverModel)
|
||||
return new MockApiService(appModel, serverModel, config)
|
||||
} else {
|
||||
return new LiveApiService(http, appModel, serverModel)
|
||||
return new LiveApiService(http, appModel, serverModel, config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Rules } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerMetrics, DiskInfo } from '../../models/server-model'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Unit, ApiServer, ApiAppInstalledFull, ApiAppConfig, ApiAppAvailableFull } from './api-types'
|
||||
import { Unit, ApiServer, ApiAppInstalledFull, ApiAppConfig, ApiAppAvailableFull, ApiAppInstalledPreview } from './api-types'
|
||||
import { AppMetrics, AppMetricsVersioned } from 'src/app/util/metrics.util'
|
||||
import { ConfigSpec } from 'src/app/app-config/config-types'
|
||||
|
||||
@@ -102,7 +102,7 @@ export module ReqRes {
|
||||
export type GetAppLogsRes = string[]
|
||||
export type GetServerLogsRes = string[]
|
||||
export type GetAppMetricsRes = AppMetricsVersioned<number>
|
||||
export type GetAppsInstalledRes = AppInstalledPreview[]
|
||||
export type GetAppsInstalledRes = ApiAppInstalledPreview[]
|
||||
export type PostInstallAppReq = { version: string }
|
||||
export type PostInstallAppRes = ApiAppInstalledFull & { breakages: DependentBreakage[] }
|
||||
export type PostUpdateAgentReq = { version: string }
|
||||
|
||||
@@ -4,8 +4,8 @@ import { AppModel, AppStatus } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledFull, AppInstalledPreview, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerModel, DiskInfo } from '../../models/server-model'
|
||||
import { ApiService, ReqRes } from './api.service'
|
||||
import { ApiServer, Unit } from './api-types'
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http'
|
||||
import { ApiAppInstalledPreview, ApiServer, Unit } from './api-types'
|
||||
import { HttpErrorResponse } from '@angular/common/http'
|
||||
import { isUnauthorized } from 'src/app/util/web.util'
|
||||
import { Replace } from 'src/app/util/types.util'
|
||||
import { AppMetrics, parseMetricsPermissive } from 'src/app/util/metrics.util'
|
||||
@@ -13,7 +13,7 @@ import { modulateTime } from 'src/app/util/misc.util'
|
||||
import { Observable, of, throwError } from 'rxjs'
|
||||
import { catchError, mapTo } from 'rxjs/operators'
|
||||
import * as uuid from 'uuid'
|
||||
import { METHODS } from 'http'
|
||||
import { ConfigService } from '../config.service'
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
@@ -22,6 +22,7 @@ export class LiveApiService extends ApiService {
|
||||
// TODO remove app + server model from here. updates to state should be done in a separate class wrapping ApiService + App/ServerModel
|
||||
private readonly appModel: AppModel,
|
||||
private readonly serverModel: ServerModel,
|
||||
private readonly config: ConfigService,
|
||||
) { super() }
|
||||
|
||||
testConnection(url: string): Promise<true> {
|
||||
@@ -116,11 +117,27 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
async getInstalledApp(appId: string): Promise<AppInstalledFull> {
|
||||
return this.authRequest<ReqRes.GetAppInstalledRes>({ method: Method.GET, url: `/apps/${appId}/installed` })
|
||||
.then(app => ({ ...app, hasFetchedFull: true }))
|
||||
.then(app => {
|
||||
return {
|
||||
...app,
|
||||
hasFetchedFull: true,
|
||||
hasUI: this.config.hasUI(app),
|
||||
launchable: this.config.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getInstalledApps(): Promise<AppInstalledPreview[]> {
|
||||
return this.authRequest<ReqRes.GetAppsInstalledRes>({ method: Method.GET, url: `/apps/installed` })
|
||||
.then(apps => {
|
||||
return apps.map(app => {
|
||||
return {
|
||||
...app,
|
||||
hasUI: this.config.hasUI(app),
|
||||
launchable: this.config.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getAppConfig(appId: string): Promise<ReqRes.GetAppConfigRes> {
|
||||
@@ -145,7 +162,14 @@ export class LiveApiService extends ApiService {
|
||||
version,
|
||||
}
|
||||
return this.authRequest<ReqRes.PostInstallAppRes>({ method: Method.POST, url: `/apps/${appId}/install${dryRunParam(dryRun, true)}`, data })
|
||||
.then(res => ({ ...res, hasFetchedFull: false }))
|
||||
.then(app => {
|
||||
return {
|
||||
...app,
|
||||
hasFetchedFull: false,
|
||||
hasUI: this.config.hasUI(app),
|
||||
launchable: this.config.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async uninstallApp(appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
|
||||
@@ -4,9 +4,10 @@ import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalle
|
||||
import { S9Notification, SSHFingerprint, ServerStatus, ServerModel, DiskInfo } from '../../models/server-model'
|
||||
import { pauseFor } from '../../util/misc.util'
|
||||
import { ApiService, ReqRes } from './api.service'
|
||||
import { ApiServer, Unit as EmptyResponse, Unit } from './api-types'
|
||||
import { ApiAppInstalledFull, ApiAppInstalledPreview, ApiServer, Unit as EmptyResponse, Unit } from './api-types'
|
||||
import { AppMetrics, AppMetricsVersioned, parseMetricsPermissive } from 'src/app/util/metrics.util'
|
||||
import { mockApiAppAvailableFull, mockApiAppAvailableVersionInfo, mockApiAppInstalledFull, mockAppDependentBreakages, toInstalledPreview } from './mock-app-fixures'
|
||||
import { ConfigService } from '../config.service'
|
||||
|
||||
//@TODO consider moving to test folders.
|
||||
@Injectable()
|
||||
@@ -16,6 +17,7 @@ export class MockApiService extends ApiService {
|
||||
constructor (
|
||||
private readonly appModel: AppModel,
|
||||
private readonly serverModel: ServerModel,
|
||||
private readonly config: ConfigService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -107,6 +109,14 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async getInstalledApp (appId: string): Promise<AppInstalledFull> {
|
||||
return mockGetInstalledApp(appId)
|
||||
.then(app => {
|
||||
return {
|
||||
...app,
|
||||
hasFetchedFull: false,
|
||||
hasUI: this.hasUI(app),
|
||||
launchable: this.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getAppMetrics (appId: string): Promise<AppMetrics> {
|
||||
@@ -115,6 +125,15 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async getInstalledApps (): Promise<AppInstalledPreview[]> {
|
||||
return mockGetInstalledApps()
|
||||
.then(apps => {
|
||||
return apps.map(app => {
|
||||
return {
|
||||
...app,
|
||||
hasUI: this.hasUI(app),
|
||||
launchable: this.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getAppConfig (appId: string): Promise<ReqRes.GetAppConfigRes> {
|
||||
@@ -131,6 +150,14 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async installApp (appId: string, version: string, dryRun: boolean): Promise<AppInstalledFull & { breakages: DependentBreakage[] }> {
|
||||
return mockInstallApp(appId)
|
||||
.then(app => {
|
||||
return {
|
||||
...app,
|
||||
hasFetchedFull: true,
|
||||
hasUI: this.hasUI(app),
|
||||
launchable: this.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async uninstallApp (appId: string, dryRun: boolean): Promise<{ breakages: DependentBreakage[] }> {
|
||||
@@ -230,7 +257,6 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
|
||||
async serviceAction (appId: string, action: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
|
||||
console.log('service action', appId, action)
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
@@ -243,9 +269,22 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
refreshLAN (): Promise<Unit> {
|
||||
async refreshLAN (): Promise<Unit> {
|
||||
return mockRefreshLAN()
|
||||
}
|
||||
|
||||
private hasUI (app: ApiAppInstalledPreview): boolean {
|
||||
return app.lanUi || app.torUi
|
||||
}
|
||||
|
||||
private isLaunchable (app: ApiAppInstalledPreview): boolean {
|
||||
return !this.config.isConsulate &&
|
||||
app.status === AppStatus.RUNNING &&
|
||||
(
|
||||
(app.torAddress && app.torUi && this.config.isTor()) ||
|
||||
(app.lanAddress && app.lanUi && !this.config.isTor())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function mockGetServer (): Promise<ReqRes.GetServerRes> {
|
||||
@@ -294,12 +333,12 @@ async function mockGetAvailableApps (): Promise<ReqRes.GetAppsAvailableRes> {
|
||||
return Object.values(mockApiAppAvailableFull)
|
||||
}
|
||||
|
||||
async function mockGetInstalledApp (appId: string): Promise<AppInstalledFull> {
|
||||
async function mockGetInstalledApp (appId: string): Promise<ReqRes.GetAppInstalledRes> {
|
||||
await pauseFor(1000)
|
||||
return { ...mockApiAppInstalledFull[appId], hasFetchedFull: true }
|
||||
return { ...mockApiAppInstalledFull[appId] }
|
||||
}
|
||||
|
||||
async function mockGetInstalledApps (): Promise<AppInstalledPreview[]> {
|
||||
async function mockGetInstalledApps (): Promise<ApiAppInstalledPreview[]> {
|
||||
await pauseFor(1000)
|
||||
return Object.values(mockApiAppInstalledFull).map(toInstalledPreview).filter(({ versionInstalled}) => !!versionInstalled)
|
||||
}
|
||||
@@ -329,9 +368,9 @@ async function mockGetAppConfig (): Promise<ReqRes.GetAppConfigRes> {
|
||||
return mockApiAppConfig
|
||||
}
|
||||
|
||||
async function mockInstallApp (appId: string): Promise<AppInstalledFull & { breakages: DependentBreakage[] }> {
|
||||
async function mockInstallApp (appId: string): Promise<ApiAppInstalledFull & { breakages: DependentBreakage[] }> {
|
||||
await pauseFor(1000)
|
||||
return { ...mockApiAppInstalledFull[appId], hasFetchedFull: true, ...mockAppDependentBreakages }
|
||||
return { ...mockApiAppInstalledFull[appId], ...mockAppDependentBreakages }
|
||||
}
|
||||
|
||||
async function mockUninstallApp (): Promise< { breakages: DependentBreakage[] } > {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AppStatus } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppDependency, BaseApp, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo } from '../../models/app-types'
|
||||
import { modulateTime } from 'src/app/util/misc.util'
|
||||
import { ApiAppInstalledFull } from './api-types'
|
||||
|
||||
export function toAvailablePreview (f: AppAvailableFull): AppAvailablePreview {
|
||||
return {
|
||||
@@ -23,8 +24,11 @@ export function toInstalledPreview (f: AppInstalledFull): AppInstalledPreview {
|
||||
title: f.title,
|
||||
iconURL: f.iconURL,
|
||||
torAddress: f.torAddress,
|
||||
ui: f.ui,
|
||||
lanAddress: f.lanAddress,
|
||||
lanUi: f.lanUi,
|
||||
torUi: f.torUi,
|
||||
hasUI: f.hasUI,
|
||||
launchable: f.launchable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +49,7 @@ export function toServiceBreakage (f: BaseApp): DependentBreakage {
|
||||
}
|
||||
}
|
||||
|
||||
export const bitcoinI: AppInstalledFull = {
|
||||
export const bitcoinI: ApiAppInstalledFull = {
|
||||
id: 'bitcoind',
|
||||
versionInstalled: '0.18.1',
|
||||
lanAddress: 'bitcoinLan.local',
|
||||
@@ -57,8 +61,8 @@ export const bitcoinI: AppInstalledFull = {
|
||||
instructions: 'some instructions',
|
||||
lastBackup: new Date().toISOString(),
|
||||
configuredRequirements: [],
|
||||
hasFetchedFull: true,
|
||||
ui: false,
|
||||
lanUi: false,
|
||||
torUi: false,
|
||||
restoreAlert: 'if you restore this app horrible things will happen to the people you love.',
|
||||
actions: [
|
||||
{ id: 'sync-chain', name: 'Sync Chain', description: 'this will sync with the chain like from Avatar', allowedStatuses: [ AppStatus.RUNNING, AppStatus.RUNNING, AppStatus.RUNNING, AppStatus.RUNNING ]},
|
||||
@@ -66,14 +70,14 @@ export const bitcoinI: AppInstalledFull = {
|
||||
],
|
||||
}
|
||||
|
||||
export const lightningI: AppInstalledFull = {
|
||||
id: 'c-lightning',
|
||||
export const lightningI: ApiAppInstalledFull = {
|
||||
id: 'lightning',
|
||||
lanAddress: 'lightningLan.local',
|
||||
status: AppStatus.RUNNING,
|
||||
title: 'C Lightning',
|
||||
versionInstalled: '1.0.0',
|
||||
torAddress: '4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion',
|
||||
iconURL: 'assets/img/service-icons/bitwarden.png',
|
||||
iconURL: 'assets/img/service-icons/c-lightning.png',
|
||||
instructions: 'some instructions',
|
||||
lastBackup: new Date().toISOString(),
|
||||
configuredRequirements: [
|
||||
@@ -86,12 +90,12 @@ export const lightningI: AppInstalledFull = {
|
||||
violation: null,
|
||||
}),
|
||||
],
|
||||
hasFetchedFull: true,
|
||||
ui: true,
|
||||
lanUi: false,
|
||||
torUi: true,
|
||||
actions: [],
|
||||
}
|
||||
|
||||
export const cupsI: AppInstalledFull = {
|
||||
export const cupsI: ApiAppInstalledFull = {
|
||||
id: 'cups',
|
||||
lanAddress: 'cupsLan.local',
|
||||
versionInstalled: '2.1.0',
|
||||
@@ -102,7 +106,6 @@ export const cupsI: AppInstalledFull = {
|
||||
|
||||
instructions: 'some instructions',
|
||||
lastBackup: new Date().toISOString(),
|
||||
ui: true,
|
||||
uninstallAlert: 'This is A GREAT APP man, I just don\'t know',
|
||||
configuredRequirements: [
|
||||
toServiceRequirement(lightningI,
|
||||
@@ -133,7 +136,8 @@ export const cupsI: AppInstalledFull = {
|
||||
violation: { name: 'incompatible-config', ruleViolations: ['bro', 'seriously', 'fix this'] },
|
||||
}),
|
||||
],
|
||||
hasFetchedFull: true,
|
||||
lanUi: true,
|
||||
torUi: true,
|
||||
actions: [],
|
||||
}
|
||||
|
||||
@@ -296,7 +300,7 @@ export const mockApiAppAvailableFull: { [appId: string]: AppAvailableFull; } = {
|
||||
bitwarden: bitwardenA,
|
||||
}
|
||||
|
||||
export const mockApiAppInstalledFull: { [appId: string]: AppInstalledFull; } = {
|
||||
export const mockApiAppInstalledFull: { [appId: string]: ApiAppInstalledFull; } = {
|
||||
bitcoind: bitcoinI,
|
||||
cups: cupsI,
|
||||
lightning: lightningI,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { AppStatus } from '../models/app-model'
|
||||
import { ApiAppInstalledPreview } from './api/api-types'
|
||||
|
||||
const { useMocks, mockOver, skipStartupAlerts } = require('../../../use-mocks.json') as UseMocks
|
||||
|
||||
@@ -22,12 +24,25 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
skipStartupAlerts = skipStartupAlerts
|
||||
isConsulateIos = window['platform'] === 'ios'
|
||||
isConsulateAndroid = window['platform'] === 'android'
|
||||
isConsulate = window['platform'] === 'ios'
|
||||
|
||||
isTor () : boolean {
|
||||
return (this.api.useMocks && mockOver === 'tor') || this.origin.endsWith('.onion')
|
||||
}
|
||||
|
||||
hasUI (app: ApiAppInstalledPreview): boolean {
|
||||
return app.lanUi || app.torUi
|
||||
}
|
||||
|
||||
isLaunchable (app: ApiAppInstalledPreview): boolean {
|
||||
return !this.isConsulate &&
|
||||
app.status === AppStatus.RUNNING &&
|
||||
(
|
||||
(app.torAddress && app.torUi && this.isTor()) ||
|
||||
(app.lanAddress && app.lanUi && !this.isTor())
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function removeProtocol (str: string): string {
|
||||
|
||||
Reference in New Issue
Block a user