mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
rename frontend to web
This commit is contained in:
382
web/projects/ui/src/app/services/patch-db/data-model.ts
Normal file
382
web/projects/ui/src/app/services/patch-db/data-model.ts
Normal file
@@ -0,0 +1,382 @@
|
||||
import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||
import { Url } from '@start9labs/shared'
|
||||
import { Manifest } from '@start9labs/marketplace'
|
||||
import { BackupJob } 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'
|
||||
|
||||
export interface DataModel {
|
||||
'server-info': ServerInfo
|
||||
'package-data': { [id: string]: PackageDataEntry }
|
||||
ui: UIData
|
||||
}
|
||||
|
||||
export interface UIData {
|
||||
name: string | null
|
||||
'ack-welcome': string // emver
|
||||
marketplace: UIMarketplaceData
|
||||
gaming: {
|
||||
snake: {
|
||||
'high-score': number
|
||||
}
|
||||
}
|
||||
'ack-instructions': Record<string, boolean>
|
||||
theme: string
|
||||
widgets: readonly Widget[]
|
||||
desktop: readonly string[]
|
||||
}
|
||||
|
||||
export interface Widget {
|
||||
id: string
|
||||
meta: {
|
||||
name: string
|
||||
width: number
|
||||
height: number
|
||||
mobileWidth: number
|
||||
mobileHeight: number
|
||||
}
|
||||
url?: string
|
||||
settings?: string
|
||||
}
|
||||
|
||||
export interface UIMarketplaceData {
|
||||
'selected-url': string
|
||||
'known-hosts': {
|
||||
'https://registry.start9.com/': UIStore
|
||||
'https://community-registry.start9.com/': UIStore
|
||||
[url: string]: UIStore
|
||||
}
|
||||
}
|
||||
|
||||
export interface UIStore {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface ServerInfo {
|
||||
id: string
|
||||
version: string
|
||||
country: string
|
||||
ui: AddressInfo
|
||||
network: NetworkInfo
|
||||
'last-backup': string | null
|
||||
'unread-notification-count': number
|
||||
'status-info': ServerStatusInfo
|
||||
'eos-version-compat': string
|
||||
pubkey: string
|
||||
'ca-fingerprint': string
|
||||
'ntp-synced': boolean
|
||||
zram: boolean
|
||||
smtp: typeof customSmtp.validator._TYPE
|
||||
'password-hash': string
|
||||
platform: string
|
||||
}
|
||||
|
||||
export type NetworkInfo = {
|
||||
wifi: WiFiInfo
|
||||
start9ToSubdomain: Omit<Domain, 'provider'> | null
|
||||
domains: Domain[]
|
||||
wanConfig: {
|
||||
upnp: boolean
|
||||
forwards: PortForward[]
|
||||
}
|
||||
proxies: Proxy[]
|
||||
outboundProxy: OsOutboundProxy
|
||||
primaryProxies: {
|
||||
inbound: string | null
|
||||
outbound: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export type DomainInfo = {
|
||||
domain: string
|
||||
subdomain: string | null
|
||||
}
|
||||
|
||||
export type InboundProxy = { proxyId: string } | 'primary' | null
|
||||
export type OsOutboundProxy = InboundProxy
|
||||
export type ServiceOutboundProxy = OsOutboundProxy | 'mirror'
|
||||
|
||||
export type PortForward = {
|
||||
assigned: number
|
||||
override: number | null
|
||||
target: number
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export type WiFiInfo = {
|
||||
enabled: boolean
|
||||
lastRegion: string | null
|
||||
}
|
||||
|
||||
export type Domain = {
|
||||
value: string
|
||||
createdAt: string
|
||||
provider: string
|
||||
networkStrategy: NetworkStrategy
|
||||
usedBy: {
|
||||
service: { id: string | null; title: string } // null means startos
|
||||
interfaces: { id: string | null; title: string }[] // null means startos
|
||||
}[]
|
||||
}
|
||||
|
||||
export type NetworkStrategy =
|
||||
| { proxyId: string | null } // null means system primary
|
||||
| { ipStrategy: 'ipv4' | 'ipv6' | 'dualstack' }
|
||||
|
||||
export type Proxy = {
|
||||
id: string
|
||||
name: string
|
||||
createdAt: string
|
||||
type: 'outbound' | 'inbound-outbound' | 'vlan' | { error: string }
|
||||
endpoint: string
|
||||
// below is overlay only
|
||||
usedBy: {
|
||||
services: { id: string | null; title: string }[] // implies outbound - null means startos
|
||||
domains: string[] // implies inbound
|
||||
}
|
||||
primaryInbound: boolean
|
||||
primaryOutbound: boolean
|
||||
}
|
||||
|
||||
export interface IpInfo {
|
||||
[iface: string]: {
|
||||
wireless: boolean
|
||||
ipv4: string | null
|
||||
ipv6: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServerStatusInfo {
|
||||
'current-backup': null | {
|
||||
job: BackupJob
|
||||
'backup-progress': {
|
||||
[packageId: string]: {
|
||||
complete: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
updated: boolean
|
||||
'update-progress': { size: number | null; downloaded: number } | null
|
||||
restarting: boolean
|
||||
'shutting-down': boolean
|
||||
}
|
||||
|
||||
export enum ServerStatus {
|
||||
Running = 'running',
|
||||
Updated = 'updated',
|
||||
BackingUp = 'backing-up',
|
||||
}
|
||||
|
||||
export interface PackageDataEntry {
|
||||
state: PackageState
|
||||
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 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 enum PackageState {
|
||||
Installing = 'installing',
|
||||
Installed = 'installed',
|
||||
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
|
||||
warning: string | null
|
||||
disabled: string | null
|
||||
'input-spec': InputSpec | null
|
||||
group: string | null
|
||||
}
|
||||
|
||||
export interface Status {
|
||||
configured: boolean
|
||||
main: MainStatus
|
||||
'dependency-config-errors': { [id: string]: string | null }
|
||||
}
|
||||
|
||||
export type MainStatus =
|
||||
| MainStatusStopped
|
||||
| MainStatusStopping
|
||||
| MainStatusStarting
|
||||
| MainStatusRunning
|
||||
| MainStatusBackingUp
|
||||
| MainStatusRestarting
|
||||
| MainStatusConfiguring
|
||||
|
||||
export interface MainStatusStopped {
|
||||
status: PackageMainStatus.Stopped
|
||||
}
|
||||
|
||||
export interface MainStatusStopping {
|
||||
status: PackageMainStatus.Stopping
|
||||
}
|
||||
|
||||
export interface MainStatusStarting {
|
||||
status: PackageMainStatus.Starting
|
||||
}
|
||||
|
||||
export interface MainStatusRunning {
|
||||
status: PackageMainStatus.Running
|
||||
started: string // UTC date string
|
||||
health: { [id: string]: HealthCheckResult }
|
||||
}
|
||||
|
||||
export interface MainStatusBackingUp {
|
||||
status: PackageMainStatus.BackingUp
|
||||
}
|
||||
|
||||
export interface MainStatusRestarting {
|
||||
status: PackageMainStatus.Restarting
|
||||
}
|
||||
|
||||
export interface MainStatusConfiguring {
|
||||
status: PackageMainStatus.Configuring
|
||||
}
|
||||
|
||||
export enum PackageMainStatus {
|
||||
Starting = 'starting',
|
||||
Running = 'running',
|
||||
Stopping = 'stopping',
|
||||
Stopped = 'stopped',
|
||||
BackingUp = 'backing-up',
|
||||
Restarting = 'restarting',
|
||||
Configuring = 'configuring',
|
||||
}
|
||||
|
||||
export type HealthCheckResult = { name: string } & (
|
||||
| HealthCheckResultStarting
|
||||
| HealthCheckResultLoading
|
||||
| HealthCheckResultDisabled
|
||||
| HealthCheckResultSuccess
|
||||
| HealthCheckResultFailure
|
||||
)
|
||||
|
||||
export enum HealthResult {
|
||||
Starting = 'starting',
|
||||
Loading = 'loading',
|
||||
Disabled = 'disabled',
|
||||
Success = 'success',
|
||||
Failure = 'failure',
|
||||
}
|
||||
|
||||
export interface HealthCheckResultStarting {
|
||||
result: HealthResult.Starting
|
||||
}
|
||||
|
||||
export interface HealthCheckResultDisabled {
|
||||
result: HealthResult.Disabled
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface HealthCheckResultSuccess {
|
||||
result: HealthResult.Success
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface HealthCheckResultLoading {
|
||||
result: HealthResult.Loading
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface HealthCheckResultFailure {
|
||||
result: HealthResult.Failure
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Bootstrapper, DBCache } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { StorageService } from '../storage.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LocalStorageBootstrap implements Bootstrapper<DataModel> {
|
||||
static CONTENT_KEY = 'patch-db-cache'
|
||||
|
||||
constructor(private readonly storage: StorageService) {}
|
||||
|
||||
init(): DBCache<DataModel> {
|
||||
const cache = this.storage.get<DBCache<DataModel>>(
|
||||
LocalStorageBootstrap.CONTENT_KEY,
|
||||
)
|
||||
|
||||
return cache || { sequence: 0, data: {} as DataModel }
|
||||
}
|
||||
|
||||
update(cache: DBCache<DataModel>): void {
|
||||
this.storage.set(LocalStorageBootstrap.CONTENT_KEY, cache)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { InjectionToken, Injector } from '@angular/core'
|
||||
import { Update } from 'patch-db-client'
|
||||
import {
|
||||
bufferTime,
|
||||
catchError,
|
||||
filter,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
defer,
|
||||
EMPTY,
|
||||
from,
|
||||
interval,
|
||||
Observable,
|
||||
} from 'rxjs'
|
||||
import { DataModel } from './data-model'
|
||||
import { AuthService } from '../auth.service'
|
||||
import { ConnectionService } from '../connection.service'
|
||||
import { ApiService } from '../api/embassy-api.service'
|
||||
import { ConfigService } from '../config.service'
|
||||
|
||||
export const PATCH_SOURCE = new InjectionToken<Observable<Update<DataModel>[]>>(
|
||||
'',
|
||||
)
|
||||
|
||||
export function sourceFactory(
|
||||
injector: Injector,
|
||||
): Observable<Update<DataModel>[]> {
|
||||
// defer() needed to avoid circular dependency with ApiService, since PatchDB is needed there
|
||||
return defer(() => {
|
||||
const api = injector.get(ApiService)
|
||||
const authService = injector.get(AuthService)
|
||||
const connectionService = injector.get(ConnectionService)
|
||||
const configService = injector.get(ConfigService)
|
||||
const isTor = configService.isTor()
|
||||
const timeout = isTor ? 16000 : 4000
|
||||
|
||||
const websocket$ = api.openPatchWebsocket$().pipe(
|
||||
bufferTime(250),
|
||||
filter(updates => !!updates.length),
|
||||
catchError((_, watch$) => {
|
||||
connectionService.websocketConnected$.next(false)
|
||||
|
||||
return interval(timeout).pipe(
|
||||
switchMap(() =>
|
||||
from(api.echo({ message: 'ping', timeout })).pipe(
|
||||
catchError(() => EMPTY),
|
||||
),
|
||||
),
|
||||
take(1),
|
||||
switchMap(() => watch$),
|
||||
)
|
||||
}),
|
||||
tap(() => connectionService.websocketConnected$.next(true)),
|
||||
)
|
||||
|
||||
return authService.isVerified$.pipe(
|
||||
switchMap(verified => (verified ? websocket$ : EMPTY)),
|
||||
)
|
||||
})
|
||||
}
|
||||
20
web/projects/ui/src/app/services/patch-db/patch-db.module.ts
Normal file
20
web/projects/ui/src/app/services/patch-db/patch-db.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { Injector, NgModule } from '@angular/core'
|
||||
import { PATCH_SOURCE, sourceFactory } from './patch-db.factory'
|
||||
|
||||
// This module is purely for providers organization purposes
|
||||
@NgModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PATCH_SOURCE,
|
||||
deps: [Injector],
|
||||
useFactory: sourceFactory,
|
||||
},
|
||||
{
|
||||
provide: PatchDB,
|
||||
deps: [PATCH_SOURCE],
|
||||
useClass: PatchDB,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class PatchDbModule {}
|
||||
Reference in New Issue
Block a user