mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
0.3.0 refactor
ui: adds overlay layer to patch-db-client ui: getting towards mocks ui: cleans up factory init ui: nice type hack ui: live api for patch ui: api service source + http starts up ui: api source + http ui: rework patchdb config, pass stashTimeout into patchDbModel wires in temp patching into api service ui: example of wiring patchdbmodel into page begin integration remove unnecessary method linting first data rendering rework app initialization http source working for ssh delete call temp patches working entire Embassy tab complete not in kansas anymore ripping, saving progress progress for API request response types and endoint defs Update data-model.ts shambles, but in a good way progress big progress progress installed list working big progress progress progress begin marketplace redesign Update api-types.ts Update api-types.ts marketplace improvements cosmetic dependencies and recommendations begin nym auth approach install wizard restore flow and donations
This commit is contained in:
committed by
Aiden McClelland
parent
46f32cb90b
commit
8d01ebe8b2
@@ -1,107 +0,0 @@
|
||||
|
||||
//////////////// Install/Uninstall ////////////////////////////////////////////////
|
||||
|
||||
type AppDependentBreakage = {
|
||||
// id of the dependent app which will or did break (Stopped) given the action.
|
||||
id: string
|
||||
title: string
|
||||
iconUrl: string
|
||||
}
|
||||
|
||||
POST /apps/:appId/install(?dryrun)
|
||||
|
||||
body: {
|
||||
version: string, //semver
|
||||
}
|
||||
response : ApiAppInstalledFull & { breakages: AppDependentBreakage[] }
|
||||
|
||||
|
||||
|
||||
POST /apps/:appId/uninstall(?dryrun)
|
||||
|
||||
response : { breakages: AppDependentBreakage[] }
|
||||
|
||||
/////////////////////////////// Store/Show /////////////////////////////////////////////////
|
||||
|
||||
|
||||
type ApiAppAvailableFull = ... {
|
||||
// app base data
|
||||
id: string
|
||||
title: string
|
||||
status: AppStatus | null
|
||||
versionInstalled: string | null
|
||||
iconURL: string
|
||||
|
||||
// preview data
|
||||
versionLatest: string
|
||||
descriptionShort: string
|
||||
|
||||
// version specific data
|
||||
releaseNotes: string
|
||||
serviceRequirements: AppDependencyRequirement[]
|
||||
|
||||
// other data
|
||||
descriptionLong: string,
|
||||
version: string[],
|
||||
}
|
||||
|
||||
type AppDependencyRequirement = ... {
|
||||
//app base data (minus status + version installed)
|
||||
id: string
|
||||
title: string
|
||||
iconURL: string
|
||||
|
||||
// dependency data
|
||||
optional: string | null
|
||||
default: boolean
|
||||
versionSpec: string
|
||||
description: string | null
|
||||
violation: AppDependencyRequirementViolation | null
|
||||
}
|
||||
|
||||
type AppDependencyRequirementViolation =
|
||||
{ name: 'missing'; suggestedVersion: string; } |
|
||||
{ name: 'incompatible-version'; suggestedVersion: string; } |
|
||||
{ name: 'incompatible-config'; ruleViolations: string; auto-configurable: boolean } | // (auto-configurable for if/when we do that)
|
||||
{ name: 'incompatible-status'; status: AppStatus; }
|
||||
|
||||
|
||||
// Get App Available Full
|
||||
GET /apps/:appId/store
|
||||
|
||||
response: ApiAppAvailableFull
|
||||
|
||||
|
||||
// Get Version Specific Data for an App Available
|
||||
GET /apps/:appId/store/:version
|
||||
|
||||
response: {
|
||||
// version specific data
|
||||
releaseNotes: string
|
||||
serviceRequirements: AppDependencyRequirement[]
|
||||
}
|
||||
|
||||
///////////////////////////// Installed/Show ///////////////////////////////////////////
|
||||
|
||||
|
||||
type ApiAppInstalledFull {
|
||||
// app base data
|
||||
id: string
|
||||
title: string
|
||||
status: AppStatus | null
|
||||
versionInstalled: string | null
|
||||
iconURL: string
|
||||
|
||||
// preview data
|
||||
|
||||
// other data
|
||||
instructions: string | null
|
||||
lastBackup: string | null
|
||||
configuredRequirements: AppDependencyRequirement[] | null // null if not yet configured
|
||||
}
|
||||
|
||||
|
||||
// Get App Installed Full
|
||||
GET /apps/:appId/installed
|
||||
|
||||
reseponse: AppInstalledFull
|
||||
@@ -1,43 +1,307 @@
|
||||
import { ConfigSpec } from 'src/app/app-config/config-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'
|
||||
import { Dump, Operation, Revision } from 'patch-db-client'
|
||||
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import { DataModel, DependencyError, Manifest, URL } from 'src/app/models/patch-db/data-model'
|
||||
|
||||
/** SERVER **/
|
||||
export module RR {
|
||||
|
||||
export interface ApiServer {
|
||||
name: string
|
||||
status: ServerStatus
|
||||
versionInstalled: string
|
||||
alternativeRegistryUrl: string | null
|
||||
specs: ServerSpecs
|
||||
wifi: {
|
||||
ssids: string[]
|
||||
current: string | null
|
||||
// DB
|
||||
|
||||
export type GetRevisionsRes = Revision[] | Dump<DataModel>
|
||||
|
||||
export type GetDumpRes = Dump<DataModel>
|
||||
|
||||
export type SetDBValueReq = WithExpire<{ pointer: string, value: any }> // db.put.ui
|
||||
export type SetDBValueRes = WithRevision<null>
|
||||
|
||||
// auth
|
||||
|
||||
export type SubmitPinReq = { pin: string } // auth.pin - unauthed
|
||||
export type SubmitPinRes = null
|
||||
|
||||
export type SubmitPasswordReq = { password: string } // auth.password - unauthed
|
||||
export type SubmitPasswordRes = null
|
||||
|
||||
export type LogoutReq = { } // auth.logout
|
||||
export type LogoutRes = null
|
||||
|
||||
// server
|
||||
|
||||
export type GetServerLogsReq = { before?: string } // server.logs
|
||||
export type GetServerLogsRes = Log[]
|
||||
|
||||
export type GetServerMetricsReq = { } // server.metrics
|
||||
export type GetServerMetricsRes = ServerMetrics
|
||||
|
||||
export type UpdateServerReq = WithExpire<{ }> // server.update
|
||||
export type UpdateServerRes = WithRevision<null>
|
||||
|
||||
export type RestartServerReq = { } // server.restart
|
||||
export type RestartServerRes = null
|
||||
|
||||
export type ShutdownServerReq = { } // server.shutdown
|
||||
export type ShutdownServerRes = null
|
||||
|
||||
// network
|
||||
|
||||
export type RefreshLanReq = { } // network.lan.refresh
|
||||
export type RefreshLanRes = null
|
||||
|
||||
// registry
|
||||
|
||||
export type SetRegistryReq = WithExpire<{ url: string }> // registry.set
|
||||
export type SetRegistryRes = WithRevision<null>
|
||||
|
||||
// notification
|
||||
|
||||
export type GetNotificationsReq = WithExpire<{ page: number, 'per-page': number }> // notification.list
|
||||
export type GetNotificationsRes = WithRevision<ServerNotification<number>[]>
|
||||
|
||||
export type DeleteNotificationReq = { id: string } // notification.delete
|
||||
export type DeleteNotificationRes = null
|
||||
|
||||
// wifi
|
||||
|
||||
export type AddWifiReq = { // wifi.add
|
||||
ssid: string
|
||||
password: string
|
||||
country: string
|
||||
priority: number
|
||||
connect: boolean
|
||||
}
|
||||
ssh: SSHFingerprint[]
|
||||
serverId: string
|
||||
welcomeAck: boolean
|
||||
autoCheckUpdates: boolean
|
||||
export type AddWifiRes = null
|
||||
|
||||
export type ConnectWifiReq = WithExpire<{ ssid: string }> // wifi.connect
|
||||
export type ConnectWifiRes = WithRevision<null>
|
||||
|
||||
export type DeleteWifiReq = WithExpire<{ ssid: string }> // wifi.delete
|
||||
export type DeleteWifiRes = WithRevision<null>
|
||||
|
||||
// ssh
|
||||
|
||||
export type GetSSHKeysReq = { } // ssh.get
|
||||
export type GetSSHKeysRes = SSHKeys
|
||||
|
||||
export type AddSSHKeyReq = { pubkey: string } // ssh.add
|
||||
export type AddSSHKeyRes = SSHKeys
|
||||
|
||||
export type DeleteSSHKeyReq = { hash: string } // ssh.delete
|
||||
export type DeleteSSHKeyRes = null
|
||||
|
||||
// backup
|
||||
|
||||
export type CreateBackupReq = WithExpire<{ logicalname: string, password: string }> // backup.create
|
||||
export type CreateBackupRes = WithRevision<null>
|
||||
|
||||
export type RestoreBackupReq = { logicalname: string, password: string } // backup.restore - unauthed
|
||||
export type RestoreBackupRes = null
|
||||
|
||||
// disk
|
||||
|
||||
export type GetDisksReq = { } // disk.list
|
||||
export type GetDisksRes = DiskInfo
|
||||
|
||||
export type EjectDisksReq = { logicalname: string } // disk.eject
|
||||
export type EjectDisksRes = null
|
||||
|
||||
// package
|
||||
|
||||
export type GetPackagePropertiesReq = { id: string } // package.properties
|
||||
export type GetPackagePropertiesRes<T extends number> = PackagePropertiesVersioned<T>
|
||||
|
||||
export type GetPackageLogsReq = { id: string, before?: string } // package.logs
|
||||
export type GetPackageLogsRes = Log[]
|
||||
|
||||
export type InstallPackageReq = WithExpire<{ id: string, version: string }> // package.install
|
||||
export type InstallPackageRes = WithRevision<null>
|
||||
|
||||
export type DryUpdatePackageReq = { id: string, version: string } // package.update.dry
|
||||
export type DryUpdatePackageRes = BreakageRes
|
||||
|
||||
export type GetPackageConfigReq = { id: string } // package.config.get
|
||||
export type GetPackageConfigRes = { spec: ConfigSpec, config: object }
|
||||
|
||||
export type DrySetPackageConfigReq = { id: string, config: object } // package.config.set.dry
|
||||
export type DrySetPackageConfigRes = BreakageRes
|
||||
|
||||
export type SetPackageConfigReq = WithExpire<DrySetPackageConfigReq> // package.config.set
|
||||
export type SetPackageConfigRes = WithRevision<null>
|
||||
|
||||
export type RestorePackageReq = WithExpire<{ id: string, logicalname: string, password: string }> // package.backup.restore
|
||||
export type RestorePackageRes = WithRevision<null>
|
||||
|
||||
export type ExecutePackageActionReq = { id: string, 'action-id': string, input?: object } // package.action
|
||||
export type ExecutePackageActionRes = ActionResponse
|
||||
|
||||
export type StartPackageReq = WithExpire<{ id: string }> // package.start
|
||||
export type StartPackageRes = WithRevision<null>
|
||||
|
||||
export type DryStopPackageReq = StopPackageReq // package.stop.dry
|
||||
export type DryStopPackageRes = BreakageRes
|
||||
|
||||
export type StopPackageReq = WithExpire<{ id: string }> // package.stop
|
||||
export type StopPackageRes = WithRevision<null>
|
||||
|
||||
export type DryRemovePackageReq = RemovePackageReq // package.remove.dry
|
||||
export type DryRemovePackageRes = BreakageRes
|
||||
|
||||
export type RemovePackageReq = WithExpire<{ id: string }> // package.remove
|
||||
export type RemovePackageRes = WithRevision<null>
|
||||
|
||||
export type DryConfigureDependencyReq = { 'dependency-id': string, 'dependent-id': string } // package.dependency.configure.dry
|
||||
export type DryConfigureDependencyRes = object
|
||||
|
||||
|
||||
// marketplace
|
||||
|
||||
export type GetMarketplaceDataReq = { }
|
||||
export type GetMarketplaceDataRes = MarketplaceData
|
||||
|
||||
export type GetMarketplaceEOSReq = { }
|
||||
export type GetMarketplaceEOSRes = MarketplaceEOS
|
||||
|
||||
export type GetAvailableListReq = { category?: string, query?: string, page: number, 'per-page': number }
|
||||
export type GetAvailableListRes = AvailablePreview[]
|
||||
|
||||
export type GetAvailableShowReq = { id: string, version?: string }
|
||||
export type GetAvailableShowRes = AvailableShow
|
||||
}
|
||||
|
||||
/** APPS **/
|
||||
export type ApiAppAvailableFull = Omit<AppAvailableFull, 'versionViewing'>
|
||||
export type WithExpire<T> = { 'expire-id'?: string } & T
|
||||
export type WithRevision<T> = { response: T, revision?: Revision }
|
||||
|
||||
export type ApiAppInstalledPreview = Omit<AppInstalledPreview, 'hasUI' | 'launchable'>
|
||||
export type ApiAppInstalledFull = Omit<AppInstalledFull, 'hasFetchedFull' | 'hasUI' | 'launchable'>
|
||||
|
||||
export interface ApiAppConfig {
|
||||
spec: ConfigSpec
|
||||
config: object | null
|
||||
rules: Rules[]
|
||||
export interface MarketplaceData {
|
||||
categories: string[]
|
||||
}
|
||||
|
||||
/** MISC **/
|
||||
|
||||
export type Unit = { never?: never; } // hack for the unit typ
|
||||
|
||||
export type V1Status = {
|
||||
status: 'nothing' | 'instructions' | 'available'
|
||||
export interface MarketplaceEOS {
|
||||
version: string
|
||||
headline: string
|
||||
notes: string
|
||||
}
|
||||
|
||||
export interface AvailablePreview {
|
||||
id: string
|
||||
title: string
|
||||
version: string
|
||||
icon: URL
|
||||
descriptionShort: string
|
||||
}
|
||||
|
||||
export interface AvailableShow {
|
||||
icon: URL
|
||||
manifest: Manifest
|
||||
categories: string[]
|
||||
versions: string[]
|
||||
'dependency-metadata': {
|
||||
[id: string]: {
|
||||
title: string
|
||||
icon: URL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface BreakageRes {
|
||||
patch: Operation[],
|
||||
breakages: Breakages
|
||||
}
|
||||
|
||||
export interface Breakages {
|
||||
[id: string]: TaggedDependencyError
|
||||
}
|
||||
|
||||
export interface TaggedDependencyError {
|
||||
dependency: string,
|
||||
error: DependencyError,
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
timestamp: string
|
||||
log: string
|
||||
}
|
||||
|
||||
export interface ActionResponse {
|
||||
message: string
|
||||
value: string | number | boolean | null
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
}
|
||||
|
||||
export interface ServerMetrics {
|
||||
[key: string]: {
|
||||
[key: string]: {
|
||||
value: string | number | null
|
||||
unit?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface DiskInfo {
|
||||
[id: string]: DiskInfoEntry
|
||||
}
|
||||
|
||||
export interface DiskInfoEntry {
|
||||
size: string
|
||||
description: string | null
|
||||
partitions: PartitionInfo
|
||||
}
|
||||
|
||||
export interface PartitionInfo {
|
||||
[logicalname: string]: PartitionInfoEntry
|
||||
}
|
||||
|
||||
export interface PartitionInfoEntry {
|
||||
'is-mounted': boolean // We do not allow backups to mounted partitions
|
||||
size: string | null
|
||||
label: string | null
|
||||
}
|
||||
|
||||
export interface ServerSpecs {
|
||||
[key: string]: string | number
|
||||
}
|
||||
|
||||
export interface SSHKeys {
|
||||
[hash: string]: SSHKeyEntry
|
||||
}
|
||||
|
||||
export interface SSHKeyEntry {
|
||||
alg: string
|
||||
hostname: string
|
||||
hash: string
|
||||
}
|
||||
|
||||
export type ServerNotifications = ServerNotification<any>[]
|
||||
|
||||
export interface ServerNotification<T extends number> {
|
||||
id: string
|
||||
'package-id': string | null
|
||||
'created-at': string
|
||||
code: T
|
||||
level: NotificationLevel
|
||||
title: string
|
||||
message: string
|
||||
data: NotificationData<T>
|
||||
}
|
||||
|
||||
export enum NotificationLevel {
|
||||
Success = 'success',
|
||||
Info = 'info',
|
||||
Warning = 'warning',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
export type NotificationData<T> = T extends 0 ? null :
|
||||
T extends 1 ? BackupReport :
|
||||
any
|
||||
|
||||
export interface BackupReport {
|
||||
server: {
|
||||
attempted: boolean
|
||||
error: string | null
|
||||
}
|
||||
packages: {
|
||||
[id: string]: {
|
||||
error: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { HttpService } from '../http.service'
|
||||
import { AppModel } from '../../models/app-model'
|
||||
import { MockApiService } from './mock-api.service'
|
||||
import { LiveApiService } from './live-api.service'
|
||||
import { ServerModel } from 'src/app/models/server-model'
|
||||
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, config)
|
||||
export function ApiServiceFactory (config: ConfigService, http: HttpService) {
|
||||
if (config.api.mocks) {
|
||||
return new MockApiService(config)
|
||||
} else {
|
||||
return new LiveApiService(http, appModel, serverModel, config)
|
||||
return new LiveApiService(http, config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +1,209 @@
|
||||
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, ApiAppInstalledPreview, V1Status } from './api-types'
|
||||
import { AppMetrics, AppMetricsVersioned } from 'src/app/util/metrics.util'
|
||||
import { ConfigSpec } from 'src/app/app-config/config-types'
|
||||
import { Http, Source, Update, Operation, Revision } from 'patch-db-client'
|
||||
import { RR } from './api-types'
|
||||
import { DataModel } from 'src/app/models/patch-db/data-model'
|
||||
import { filter } from 'rxjs/operators'
|
||||
import * as uuid from 'uuid'
|
||||
|
||||
export abstract class ApiService {
|
||||
private $unauthorizedApiResponse$: Subject<{ }> = new Subject()
|
||||
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
|
||||
protected readonly sync = new Subject<Update<DataModel>>()
|
||||
private syncing = true
|
||||
|
||||
watch401$ (): Observable<{ }> {
|
||||
return this.$unauthorizedApiResponse$.asObservable()
|
||||
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
|
||||
// sequenceStream '_' is not used by the live api, but is overridden by the mock
|
||||
watch$ (_?: Observable<number>): Observable<Update<DataModel>> {
|
||||
return this.sync.asObservable().pipe(filter(() => this.syncing))
|
||||
}
|
||||
|
||||
authenticatedRequestsEnabled: boolean = false
|
||||
// used for determining internet connectivity
|
||||
abstract ping (): Promise<void>
|
||||
|
||||
protected received401 () {
|
||||
this.authenticatedRequestsEnabled = false
|
||||
this.$unauthorizedApiResponse$.next()
|
||||
// for getting static files: ex icons, instructions, licenses
|
||||
abstract getStatic (url: string): Promise<string>
|
||||
|
||||
// db
|
||||
|
||||
abstract getRevisions (since: number): Promise<RR.GetRevisionsRes>
|
||||
|
||||
abstract getDump (): Promise<RR.GetDumpRes>
|
||||
|
||||
protected abstract setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes>
|
||||
setDbValue = (params: RR.SetDBValueReq) => this.syncResponse(
|
||||
() => this.setDbValueRaw(params),
|
||||
)()
|
||||
|
||||
// auth
|
||||
|
||||
abstract submitPin (params: RR.SubmitPinReq): Promise<RR.SubmitPinRes>
|
||||
|
||||
abstract submitPassword (params: RR.SubmitPasswordReq): Promise<RR.SubmitPasswordReq>
|
||||
|
||||
abstract logout (params: RR.LogoutReq): Promise<RR.LogoutRes>
|
||||
|
||||
// server
|
||||
|
||||
abstract getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes>
|
||||
|
||||
abstract getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes>
|
||||
|
||||
protected abstract updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
|
||||
updateServer = (params: RR.UpdateServerReq) => this.syncResponse(
|
||||
() => this.updateServerRaw(params),
|
||||
)()
|
||||
|
||||
abstract restartServer (params: RR.UpdateServerReq): Promise<RR.RestartServerRes>
|
||||
|
||||
abstract shutdownServer (params: RR.ShutdownServerReq): Promise<RR.ShutdownServerRes>
|
||||
|
||||
// network
|
||||
|
||||
abstract refreshLan (params: RR.RefreshLanReq): Promise<RR.RefreshLanRes>
|
||||
|
||||
// registry
|
||||
|
||||
protected abstract setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes>
|
||||
setRegistry = (params: RR.SetRegistryReq) => this.syncResponse(
|
||||
() => this.setRegistryRaw(params),
|
||||
)()
|
||||
|
||||
// notification
|
||||
|
||||
abstract getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes>
|
||||
getNotifications = (params: RR.GetNotificationsReq) => this.syncResponse<RR.GetNotificationsRes['response'], any>(
|
||||
() => this.getNotificationsRaw(params),
|
||||
)()
|
||||
|
||||
abstract deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes>
|
||||
|
||||
// wifi
|
||||
|
||||
abstract addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes>
|
||||
|
||||
protected abstract connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
|
||||
connectWifi = (params: RR.ConnectWifiReq) => this.syncResponse(
|
||||
() => this.connectWifiRaw(params),
|
||||
)()
|
||||
|
||||
protected abstract deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.ConnectWifiRes>
|
||||
deleteWifi = (params: RR.DeleteWifiReq) => this.syncResponse(
|
||||
() => this.deleteWifiRaw(params),
|
||||
)()
|
||||
|
||||
// ssh
|
||||
|
||||
abstract getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes>
|
||||
|
||||
abstract addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes>
|
||||
|
||||
abstract deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes>
|
||||
|
||||
// backup
|
||||
|
||||
protected abstract createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes>
|
||||
createBackup = (params: RR.CreateBackupReq) => this.syncResponse(
|
||||
() => this.createBackupRaw(params),
|
||||
)()
|
||||
|
||||
protected abstract restoreBackupRaw (params: RR.RestoreBackupReq): Promise<RR.RestoreBackupRes>
|
||||
restoreBackup = (params: RR.RestoreBackupReq) => this.syncResponse(
|
||||
() => this.restoreBackupRaw(params),
|
||||
)()
|
||||
|
||||
// disk
|
||||
|
||||
abstract getDisks (params: RR.GetDisksReq): Promise<RR.GetDisksRes>
|
||||
|
||||
abstract ejectDisk (params: RR.EjectDisksReq): Promise<RR.EjectDisksRes>
|
||||
|
||||
// package
|
||||
|
||||
abstract getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<any>['data']>
|
||||
|
||||
abstract getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes>
|
||||
|
||||
protected abstract installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes>
|
||||
installPackage = (params: RR.InstallPackageReq) => this.syncResponse(
|
||||
() => this.installPackageRaw(params),
|
||||
)()
|
||||
|
||||
abstract dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes>
|
||||
|
||||
abstract getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes>
|
||||
|
||||
abstract drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes>
|
||||
|
||||
protected abstract setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes>
|
||||
setPackageConfig = (params: RR.SetPackageConfigReq) => this.syncResponse(
|
||||
() => this.setPackageConfigRaw(params),
|
||||
)()
|
||||
|
||||
protected abstract restorePackageRaw (params: RR.RestorePackageReq): Promise<RR.RestorePackageRes>
|
||||
restorePackage = (params: RR.RestorePackageReq) => this.syncResponse(
|
||||
() => this.restorePackageRaw(params),
|
||||
)()
|
||||
|
||||
abstract executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes>
|
||||
|
||||
protected abstract startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes>
|
||||
startPackage = (params: RR.StartPackageReq) => this.syncResponse(
|
||||
() => this.startPackageRaw(params),
|
||||
)()
|
||||
|
||||
abstract dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes>
|
||||
|
||||
protected abstract stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes>
|
||||
stopPackage = (params: RR.StopPackageReq) => this.syncResponse(
|
||||
() => this.stopPackageRaw(params),
|
||||
)()
|
||||
|
||||
abstract dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes>
|
||||
|
||||
protected abstract removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes>
|
||||
removePackage = (params: RR.RemovePackageReq) => this.syncResponse(
|
||||
() => this.removePackageRaw(params),
|
||||
)()
|
||||
|
||||
abstract dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes>
|
||||
|
||||
// marketplace
|
||||
|
||||
abstract getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes>
|
||||
|
||||
abstract getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes>
|
||||
|
||||
abstract getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes>
|
||||
|
||||
abstract getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes>
|
||||
|
||||
// Helper allowing quick decoration to sync the response patch and return the response contents.
|
||||
// Pass in a tempUpdate function which returns a UpdateTemp corresponding to a temporary
|
||||
// state change you'd like to enact prior to request and expired when request terminates.
|
||||
private syncResponse<T, F extends (...args: any[]) => Promise<{ response: T, revision?: Revision }>> (f: F, temp?: Operation): (...args: Parameters<F>) => Promise<T> {
|
||||
return (...a) => {
|
||||
let expireId = undefined
|
||||
if (temp) {
|
||||
expireId = uuid.v4()
|
||||
this.sync.next({ patch: [temp], expiredBy: expireId })
|
||||
}
|
||||
|
||||
return f(a).then(({ response, revision }) => {
|
||||
if (revision) this.sync.next(revision)
|
||||
return response
|
||||
}) as any
|
||||
}
|
||||
}
|
||||
|
||||
abstract testConnection (url: string): Promise<true>
|
||||
abstract getCheckAuth (): Promise<Unit> // Throws an error on failed auth.
|
||||
abstract postLogin (password: string): Promise<Unit> // Throws an error on failed auth.
|
||||
abstract postLogout (): Promise<Unit> // Throws an error on failed auth.
|
||||
abstract getServer (timeout?: number): Promise<ApiServer>
|
||||
abstract getVersionLatest (): Promise<ReqRes.GetVersionLatestRes>
|
||||
abstract getServerMetrics (): Promise<ReqRes.GetServerMetricsRes>
|
||||
abstract getNotifications (page: number, perPage: number): Promise<S9Notification[]>
|
||||
abstract deleteNotification (id: string): Promise<Unit>
|
||||
abstract toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit>
|
||||
abstract updateAgent (version: any): Promise<Unit>
|
||||
abstract acknowledgeOSWelcome (version: string): Promise<Unit>
|
||||
abstract getAvailableApps (): Promise<AppAvailablePreview[]>
|
||||
abstract getAvailableApp (appId: string): Promise<AppAvailableFull>
|
||||
abstract getAvailableAppVersionSpecificInfo (appId: string, versionSpec: string): Promise<AppAvailableVersionSpecificInfo>
|
||||
abstract getInstalledApp (appId: string): Promise<AppInstalledFull>
|
||||
abstract getAppMetrics (appId: string): Promise<AppMetrics>
|
||||
abstract getInstalledApps (): Promise<AppInstalledPreview[]>
|
||||
abstract getExternalDisks (): Promise<DiskInfo[]>
|
||||
abstract getAppConfig (appId: string): Promise<{ spec: ConfigSpec, config: object, rules: Rules[] }>
|
||||
abstract getAppLogs (appId: string, params?: ReqRes.GetAppLogsReq): Promise<string[]>
|
||||
abstract getServerLogs (): Promise<string[]>
|
||||
abstract installApp (appId: string, version: string, dryRun?: boolean): Promise<AppInstalledFull & { breakages: DependentBreakage[] }>
|
||||
abstract uninstallApp (appId: string, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
|
||||
abstract startApp (appId: string): Promise<Unit>
|
||||
abstract stopApp (appId: string, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
|
||||
abstract restartApp (appId: string): Promise<Unit>
|
||||
abstract createAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit>
|
||||
abstract restoreAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit>
|
||||
abstract stopAppBackup (appId: string): Promise<Unit>
|
||||
abstract patchAppConfig (app: AppInstalledPreview, config: object, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
|
||||
abstract postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }>
|
||||
abstract patchServerConfig (attr: string, value: any): Promise<Unit>
|
||||
abstract wipeAppData (app: AppInstalledPreview): Promise<Unit>
|
||||
abstract addSSHKey (sshKey: string): Promise<Unit>
|
||||
abstract deleteSSHKey (sshKey: SSHFingerprint): Promise<Unit>
|
||||
abstract addWifi (ssid: string, password: string, country: string, connect: boolean): Promise<Unit>
|
||||
abstract connectWifi (ssid: string): Promise<Unit>
|
||||
abstract deleteWifi (ssid: string): Promise<Unit>
|
||||
abstract restartServer (): Promise<Unit>
|
||||
abstract shutdownServer (): Promise<Unit>
|
||||
abstract ejectExternalDisk (logicalName: string): Promise<Unit>
|
||||
abstract serviceAction (appId: string, serviceAction: ServiceAction): Promise<ReqRes.ServiceActionResponse>
|
||||
abstract refreshLAN (): Promise<Unit>
|
||||
abstract checkV1Status (): Promise<V1Status>
|
||||
// @TODO better types?
|
||||
// private async process<T, F extends (args: object) => Promise<{ response: T, revision?: Revision }>> (f: F, temps: Operation[] = []): Promise<T> {
|
||||
// let expireId = undefined
|
||||
// if (temps.length) {
|
||||
// expireId = uuid.v4()
|
||||
// this.sync.next({ patch: temps, expiredBy: expireId })
|
||||
// }
|
||||
// const { response, revision } = await f({ ...f.arguments, expireId })
|
||||
// if (revision) this.sync.next(revision)
|
||||
// return response
|
||||
// }
|
||||
}
|
||||
|
||||
export function isRpcFailure<Error, Result> (arg: { error: Error } | { result: Result }): arg is { error: Error } {
|
||||
return !!(arg as any).error
|
||||
}
|
||||
|
||||
export function isRpcSuccess<Error, Result> (arg: { error: Error } | { result: Result }): arg is { result: Result } {
|
||||
return !!(arg as any).result
|
||||
}
|
||||
|
||||
export module ReqRes {
|
||||
export type GetVersionRes = { version: string }
|
||||
export type PostLoginReq = { password: string }
|
||||
export type PostLoginRes = Unit
|
||||
export type ServiceActionRequest = {
|
||||
jsonrpc: '2.0',
|
||||
id: string,
|
||||
method: string
|
||||
}
|
||||
export type ServiceActionResponse = {
|
||||
jsonrpc: '2.0',
|
||||
id: string
|
||||
} & ({ error: { code: number, message: string } } | { result: string })
|
||||
export type GetCheckAuthRes = { }
|
||||
export type GetServerRes = ApiServer
|
||||
export type GetVersionLatestRes = { versionLatest: string, releaseNotes: string }
|
||||
export type GetServerMetricsRes = ServerMetrics
|
||||
export type GetAppAvailableRes = ApiAppAvailableFull
|
||||
export type GetAppAvailableVersionInfoRes = AppAvailableVersionSpecificInfo
|
||||
export type GetAppsAvailableRes = AppAvailablePreview[]
|
||||
export type GetExternalDisksRes = DiskInfo[]
|
||||
export type GetAppInstalledRes = ApiAppInstalledFull
|
||||
export type GetAppConfigRes = ApiAppConfig
|
||||
export type GetAppLogsReq = { after?: string, before?: string, page?: string, perPage?: string }
|
||||
export type GetServerLogsReq = { }
|
||||
export type GetAppLogsRes = string[]
|
||||
export type GetServerLogsRes = string[]
|
||||
export type GetAppMetricsRes = AppMetricsVersioned<number>
|
||||
export type GetAppsInstalledRes = ApiAppInstalledPreview[]
|
||||
export type PostInstallAppReq = { version: string }
|
||||
export type PostInstallAppRes = ApiAppInstalledFull & { breakages: DependentBreakage[] }
|
||||
export type PostUpdateAgentReq = { version: string }
|
||||
export type PostAppBackupCreateReq = { logicalname: string, password: string }
|
||||
export type PostAppBackupCreateRes = Unit
|
||||
export type PostAppBackupRestoreReq = { logicalname: string, password: string }
|
||||
export type PostAppBackupRestoreRes = Unit
|
||||
export type PostAppBackupStopRes = Unit
|
||||
export type PatchAppConfigReq = { config: object }
|
||||
export type PatchServerConfigReq = { value: string }
|
||||
export type GetNotificationsReq = { page: string, perPage: string }
|
||||
export type GetNotificationsRes = S9Notification[]
|
||||
export type PostAddWifiReq = { ssid: string, password: string, country: string, skipConnect: boolean }
|
||||
export type PostConnectWifiReq = { country: string }
|
||||
export type PostAddSSHKeyReq = { sshKey: string }
|
||||
export type PostAddSSHKeyRes = SSHFingerprint
|
||||
}
|
||||
|
||||
// used for type inference in syncResponse
|
||||
type ExtractResultPromise<T extends Promise<any>> = T extends Promise<infer R> ? Promise<R> : any
|
||||
|
||||
@@ -1,338 +1,223 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HttpService, Method, HttpOptions } from '../http.service'
|
||||
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 { ApiAppInstalledPreview, ApiServer, Unit, V1Status } 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'
|
||||
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 { HttpService, Method } from '../http.service'
|
||||
import { ApiService } from './api.service'
|
||||
import { RR } from './api-types'
|
||||
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
||||
import { ConfigService } from '../config.service'
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
constructor (
|
||||
private readonly http: HttpService,
|
||||
// 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> {
|
||||
return this.http.raw.get(url).pipe(mapTo(true as true), catchError(e => catchHttpStatusError(e))).toPromise()
|
||||
async ping (): Promise<void> {
|
||||
return this.http.rpcRequest({ method: 'ping', params: { } })
|
||||
}
|
||||
|
||||
// Used to check whether password or key is valid. If so, it will be used implicitly by all other calls.
|
||||
async getCheckAuth (): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.GET, url: '/authenticate' }, { version: '' })
|
||||
async getStatic (url: string): Promise<string> {
|
||||
return this.http.httpRequest({ method: Method.GET, url })
|
||||
}
|
||||
|
||||
async postLogin (password: string): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/login', data: { password } }, { version: '' })
|
||||
// db
|
||||
|
||||
async getRevisions (since: number): Promise<RR.GetRevisionsRes> {
|
||||
return this.http.rpcRequest({ method: 'db.revisions', params: { since } })
|
||||
}
|
||||
|
||||
async postLogout (): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/logout' }, { version: '' }).then(() => { this.authenticatedRequestsEnabled = false; return { } })
|
||||
async getDump (): Promise<RR.GetDumpRes> {
|
||||
return this.http.rpcRequest({ method: 'db.dump' })
|
||||
}
|
||||
|
||||
async getServer (timeout?: number): Promise<ApiServer> {
|
||||
return this.authRequest<ReqRes.GetServerRes>({ method: Method.GET, url: '/', readTimeout: timeout })
|
||||
async setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
|
||||
return this.http.rpcRequest({ method: 'db.put.ui', params })
|
||||
}
|
||||
|
||||
async acknowledgeOSWelcome (version: string): Promise<Unit> {
|
||||
return this.authRequest<Unit>({ method: Method.POST, url: `/welcome/${version}` })
|
||||
// auth
|
||||
|
||||
async submitPin (params: RR.SubmitPinReq): Promise<RR.SubmitPinRes> {
|
||||
return this.http.rpcRequest({ method: 'auth.pin', params })
|
||||
}
|
||||
|
||||
async getVersionLatest (): Promise<ReqRes.GetVersionLatestRes> {
|
||||
return this.authRequest<ReqRes.GetVersionLatestRes>({ method: Method.GET, url: '/versionLatest' }, { version: '' })
|
||||
async submitPassword (params: RR.SubmitPasswordReq): Promise<RR.SubmitPasswordRes> {
|
||||
return this.http.rpcRequest({ method: 'auth.password', params })
|
||||
}
|
||||
|
||||
async getServerMetrics (): Promise<ReqRes.GetServerMetricsRes> {
|
||||
return this.authRequest<ReqRes.GetServerMetricsRes>({ method: Method.GET, url: `/metrics` })
|
||||
async logout (params: RR.LogoutReq): Promise<RR.LogoutRes> {
|
||||
return this.http.rpcRequest({ method: 'auth.logout', params })
|
||||
}
|
||||
|
||||
async getNotifications (page: number, perPage: number): Promise<S9Notification[]> {
|
||||
const params: ReqRes.GetNotificationsReq = {
|
||||
page: String(page),
|
||||
perPage: String(perPage),
|
||||
}
|
||||
return this.authRequest<ReqRes.GetNotificationsRes>({ method: Method.GET, url: `/notifications`, params })
|
||||
// server
|
||||
|
||||
async getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
|
||||
return this.http.rpcRequest( { method: 'server.logs', params })
|
||||
}
|
||||
|
||||
async deleteNotification (id: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.DELETE, url: `/notifications/${id}` })
|
||||
async getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes> {
|
||||
return this.http.rpcRequest({ method: 'server.metrics', params })
|
||||
}
|
||||
|
||||
async getExternalDisks (): Promise<DiskInfo[]> {
|
||||
return this.authRequest<ReqRes.GetExternalDisksRes>({ method: Method.GET, url: `/disks` })
|
||||
async updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
return this.http.rpcRequest({ method: 'server.update', params })
|
||||
}
|
||||
|
||||
// TODO: EJECT-DISKS
|
||||
async ejectExternalDisk (logicalName: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/disks/eject`, data: { logicalName } })
|
||||
async restartServer (params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
|
||||
return this.http.rpcRequest({ method: 'server.restart', params })
|
||||
}
|
||||
|
||||
async updateAgent (version: string): Promise<Unit> {
|
||||
const data: ReqRes.PostUpdateAgentReq = {
|
||||
version: `=${version}`,
|
||||
}
|
||||
return this.authRequest({ method: Method.POST, url: '/update', data })
|
||||
async shutdownServer (params: RR.ShutdownServerReq): Promise<RR.ShutdownServerRes> {
|
||||
return this.http.rpcRequest({ method: 'server.shutdown', params })
|
||||
}
|
||||
|
||||
async getAvailableAppVersionSpecificInfo (appId: string, versionSpec: string): Promise<AppAvailableVersionSpecificInfo> {
|
||||
return this
|
||||
.authRequest<Replace<ReqRes.GetAppAvailableVersionInfoRes, 'versionViewing', 'version'>>({ method: Method.GET, url: `/apps/${appId}/store/${versionSpec}` })
|
||||
.then(res => ({ ...res, versionViewing: res.version }))
|
||||
.then(res => {
|
||||
delete res['version']
|
||||
return res
|
||||
})
|
||||
// network
|
||||
|
||||
async refreshLan (params: RR.RefreshLanReq): Promise<RR.RefreshLanRes> {
|
||||
return this.http.rpcRequest({ method: 'network.lan.refresh', params })
|
||||
}
|
||||
|
||||
async getAvailableApps (): Promise<AppAvailablePreview[]> {
|
||||
const res = await this.authRequest<ReqRes.GetAppsAvailableRes>({ method: Method.GET, url: '/apps/store' })
|
||||
return res.map(a => {
|
||||
const latestVersionTimestamp = new Date(a.latestVersionTimestamp)
|
||||
if (isNaN(latestVersionTimestamp as any)) throw new Error(`Invalid latestVersionTimestamp ${a.latestVersionTimestamp}`)
|
||||
return { ...a, latestVersionTimestamp }
|
||||
})
|
||||
// registry
|
||||
|
||||
async setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes> {
|
||||
return this.http.rpcRequest({ method: 'registry.set', params })
|
||||
}
|
||||
|
||||
async getAvailableApp (appId: string): Promise<AppAvailableFull> {
|
||||
return this.authRequest<ReqRes.GetAppAvailableRes>({ method: Method.GET, url: `/apps/${appId}/store` })
|
||||
.then(res => {
|
||||
return {
|
||||
...res,
|
||||
versionViewing: res.versionLatest,
|
||||
}
|
||||
})
|
||||
// notification
|
||||
|
||||
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> {
|
||||
return this.http.rpcRequest({ method: 'notifications.list', params })
|
||||
}
|
||||
|
||||
async getInstalledApp (appId: string): Promise<AppInstalledFull> {
|
||||
return this.authRequest<ReqRes.GetAppInstalledRes>({ method: Method.GET, url: `/apps/${appId}/installed` })
|
||||
.then(app => {
|
||||
return {
|
||||
...app,
|
||||
hasFetchedFull: true,
|
||||
hasUI: this.config.hasUI(app),
|
||||
launchable: this.config.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
async deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes> {
|
||||
return this.http.rpcRequest({ method: 'notifications.delete', params })
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
})
|
||||
})
|
||||
// wifi
|
||||
|
||||
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
|
||||
return this.http.rpcRequest({ method: 'wifi.add', params })
|
||||
}
|
||||
|
||||
async getAppConfig (appId: string): Promise<ReqRes.GetAppConfigRes> {
|
||||
return this.authRequest<ReqRes.GetAppConfigRes>({ method: Method.GET, url: `/apps/${appId}/config` })
|
||||
async connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
|
||||
return this.http.rpcRequest({ method: 'wifi.connect', params })
|
||||
}
|
||||
|
||||
async getAppLogs (appId: string, params: ReqRes.GetAppLogsReq = { }): Promise<string[]> {
|
||||
return this.authRequest<ReqRes.GetAppLogsRes>({ method: Method.GET, url: `/apps/${appId}/logs`, params: params as any })
|
||||
async deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
|
||||
return this.http.rpcRequest({ method: 'wifi.delete', params })
|
||||
}
|
||||
|
||||
async getServerLogs (): Promise<string[]> {
|
||||
return this.authRequest<ReqRes.GetServerLogsRes>({ method: Method.GET, url: `/logs` })
|
||||
// ssh
|
||||
|
||||
async getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
|
||||
return this.http.rpcRequest({ method: 'ssh.get', params })
|
||||
}
|
||||
|
||||
async getAppMetrics (appId: string): Promise<AppMetrics> {
|
||||
return this.authRequest<ReqRes.GetAppMetricsRes | string>({ method: Method.GET, url: `/apps/${appId}/metrics` })
|
||||
.then(parseMetricsPermissive)
|
||||
async addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
|
||||
return this.http.rpcRequest({ method: 'ssh.add', params })
|
||||
}
|
||||
|
||||
async installApp (appId: string, version: string, dryRun: boolean = false): Promise<AppInstalledFull & { breakages: DependentBreakage[] }> {
|
||||
const data: ReqRes.PostInstallAppReq = {
|
||||
version,
|
||||
}
|
||||
return this.authRequest<ReqRes.PostInstallAppRes>({ method: Method.POST, url: `/apps/${appId}/install${dryRunParam(dryRun, true)}`, data })
|
||||
.then(app => {
|
||||
return {
|
||||
...app,
|
||||
hasFetchedFull: false,
|
||||
hasUI: this.config.hasUI(app),
|
||||
launchable: this.config.isLaunchable(app),
|
||||
}
|
||||
})
|
||||
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
|
||||
return this.http.rpcRequest({ method: 'ssh.delete', params })
|
||||
}
|
||||
|
||||
async uninstallApp (appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/uninstall${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
|
||||
// backup
|
||||
|
||||
async createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
|
||||
return this.http.rpcRequest({ method: 'backup.create', params })
|
||||
}
|
||||
|
||||
async startApp (appId: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/start`, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.RUNNING }))
|
||||
.then(() => ({ }))
|
||||
async restoreBackupRaw (params: RR.RestoreBackupReq): Promise<RR.RestoreBackupRes> {
|
||||
return this.http.rpcRequest({ method: 'backup.restore', params })
|
||||
}
|
||||
|
||||
async stopApp (appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
const res = await this.authRequest<{ breakages: DependentBreakage[] }>({ method: Method.POST, url: `/apps/${appId}/stop${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
|
||||
if (!dryRun) this.appModel.update({ id: appId, status: AppStatus.STOPPING }, modulateTime(new Date(), 5, 'seconds'))
|
||||
return res
|
||||
// disk
|
||||
|
||||
getDisks (params: RR.GetDisksReq): Promise<RR.GetDisksRes> {
|
||||
return this.http.rpcRequest({ method: 'disk.list', params })
|
||||
}
|
||||
|
||||
async restartApp (appId: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/restart`, readTimeout: 60000 })
|
||||
.then(() => ({ } as any))
|
||||
ejectDisk (params: RR.EjectDisksReq): Promise<RR.EjectDisksRes> {
|
||||
return this.http.rpcRequest({ method: 'disk.eject', params })
|
||||
}
|
||||
|
||||
async createAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit> {
|
||||
const data: ReqRes.PostAppBackupCreateReq = {
|
||||
password: password || undefined,
|
||||
logicalname,
|
||||
}
|
||||
return this.authRequest<ReqRes.PostAppBackupCreateRes>({ method: Method.POST, url: `/apps/${appId}/backup`, data, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.CREATING_BACKUP }))
|
||||
.then(() => ({ }))
|
||||
// package
|
||||
|
||||
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<any>['data']> {
|
||||
return this.http.rpcRequest({ method: 'package.properties', params })
|
||||
.then(parsePropertiesPermissive)
|
||||
}
|
||||
|
||||
async stopAppBackup (appId: string): Promise<Unit> {
|
||||
return this.authRequest<ReqRes.PostAppBackupStopRes>({ method: Method.POST, url: `/apps/${appId}/backup/stop`, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.STOPPED }))
|
||||
.then(() => ({ }))
|
||||
async getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes> {
|
||||
return this.http.rpcRequest( { method: 'package.logs', params })
|
||||
}
|
||||
|
||||
async restoreAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit> {
|
||||
const data: ReqRes.PostAppBackupRestoreReq = {
|
||||
password: password || undefined,
|
||||
logicalname,
|
||||
}
|
||||
return this.authRequest<ReqRes.PostAppBackupRestoreRes>({ method: Method.POST, url: `/apps/${appId}/backup/restore`, data, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.RESTORING_BACKUP }))
|
||||
.then(() => ({ }))
|
||||
async installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.install', params })
|
||||
}
|
||||
|
||||
async patchAppConfig (app: AppInstalledPreview, config: object, dryRun = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
const data: ReqRes.PatchAppConfigReq = {
|
||||
config,
|
||||
}
|
||||
return this.authRequest({ method: Method.PATCH, url: `/apps/${app.id}/config${dryRunParam(dryRun, true)}`, data, readTimeout: 60000 })
|
||||
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.update.dry', params })
|
||||
}
|
||||
|
||||
async postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${dependencyId}/autoconfig/${dependentId}${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
|
||||
async getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes> {
|
||||
return this.http.rpcRequest({ method: 'package.config.get', params })
|
||||
}
|
||||
|
||||
async patchServerConfig (attr: string, value: any): Promise<Unit> {
|
||||
const data: ReqRes.PatchServerConfigReq = {
|
||||
value,
|
||||
}
|
||||
return this.authRequest({ method: Method.PATCH, url: `/${attr}`, data, readTimeout: 60000 })
|
||||
.then(() => this.serverModel.update({ [attr]: value }))
|
||||
.then(() => ({ }))
|
||||
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes> {
|
||||
return this.http.rpcRequest({ method: 'package.config.set.dry', params })
|
||||
}
|
||||
|
||||
async wipeAppData (app: AppInstalledPreview): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${app.id}/wipe`, readTimeout: 60000 }).then((res) => {
|
||||
this.appModel.update({ id: app.id, status: AppStatus.NEEDS_CONFIG })
|
||||
return res
|
||||
})
|
||||
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> {
|
||||
return this.http.rpcRequest({ method: 'package.config.set', params })
|
||||
}
|
||||
|
||||
async toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/lan/${toggle}` })
|
||||
async restorePackageRaw (params: RR.RestorePackageReq): Promise<RR.RestorePackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.restore', params })
|
||||
}
|
||||
|
||||
async addSSHKey (sshKey: string): Promise<Unit> {
|
||||
const data: ReqRes.PostAddSSHKeyReq = {
|
||||
sshKey,
|
||||
}
|
||||
const fingerprint = await this.authRequest<ReqRes.PostAddSSHKeyRes>({ method: Method.POST, url: `/sshKeys`, data })
|
||||
this.serverModel.update({ ssh: [...this.serverModel.peek().ssh, fingerprint] })
|
||||
return { }
|
||||
async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> {
|
||||
return this.http.rpcRequest({ method: 'package.action', params })
|
||||
}
|
||||
|
||||
async addWifi (ssid: string, password: string, country: string, connect: boolean): Promise<Unit> {
|
||||
const data: ReqRes.PostAddWifiReq = {
|
||||
ssid,
|
||||
password,
|
||||
country,
|
||||
skipConnect: !connect,
|
||||
}
|
||||
return this.authRequest({ method: Method.POST, url: `/wifi`, data })
|
||||
async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.start', params })
|
||||
}
|
||||
|
||||
async connectWifi (ssid: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: encodeURI(`/wifi/${ssid}`) })
|
||||
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.stop.dry', params })
|
||||
}
|
||||
|
||||
async deleteWifi (ssid: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.DELETE, url: encodeURI(`/wifi/${ssid}`) })
|
||||
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.stop', params })
|
||||
}
|
||||
|
||||
async deleteSSHKey (fingerprint: SSHFingerprint): Promise<Unit> {
|
||||
await this.authRequest({ method: Method.DELETE, url: `/sshKeys/${fingerprint.hash}` })
|
||||
const ssh = this.serverModel.peek().ssh
|
||||
this.serverModel.update({ ssh: ssh.filter(s => s !== fingerprint) })
|
||||
return { }
|
||||
async dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.remove.dry', params })
|
||||
}
|
||||
|
||||
async restartServer (): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: '/restart', readTimeout: 60000 })
|
||||
async removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes> {
|
||||
return this.http.rpcRequest({ method: 'package.remove', params })
|
||||
}
|
||||
|
||||
async shutdownServer (): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: '/shutdown', readTimeout: 60000 })
|
||||
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> {
|
||||
return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params })
|
||||
}
|
||||
|
||||
async serviceAction (appId: string, s: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
|
||||
const data: ReqRes.ServiceActionRequest = {
|
||||
jsonrpc: '2.0',
|
||||
id: uuid.v4(),
|
||||
method: s.id,
|
||||
}
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/actions`, data, readTimeout: 300000 })
|
||||
// marketplace
|
||||
|
||||
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> {
|
||||
return this.http.rpcRequest({ method: 'marketplace.data', params })
|
||||
}
|
||||
|
||||
async refreshLAN (): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: '/network/lan/reset' })
|
||||
async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> {
|
||||
return this.http.rpcRequest({ method: 'marketplace.eos', params })
|
||||
}
|
||||
|
||||
async checkV1Status (): Promise<V1Status> {
|
||||
return this.http.request({ method: Method.GET, url: 'https://registry.start9labs.com/sys/status' })
|
||||
async getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes> {
|
||||
return this.http.rpcRequest({ method: 'marketplace.available.list', params })
|
||||
}
|
||||
|
||||
private async authRequest<T> (opts: HttpOptions, overrides: Partial<{ version: string }> = { }): Promise<T> {
|
||||
if (!this.authenticatedRequestsEnabled) throw new Error(`Authenticated requests are not enabled. Do you need to login?`)
|
||||
|
||||
opts.withCredentials = true
|
||||
return this.http.serverRequest<T>(opts, overrides).catch((e: HttpError) => {
|
||||
console.log(`Got a server error!`, e)
|
||||
if (isUnauthorized(e)) this.received401()
|
||||
throw e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type HttpError = HttpErrorResponse & { error: { code: string, message: string } }
|
||||
|
||||
const dryRunParam = (dryRun: boolean, first: boolean) => {
|
||||
if (!dryRun) return ''
|
||||
return first ? `?dryrun` : `&dryrun`
|
||||
}
|
||||
|
||||
function catchHttpStatusError (error: HttpErrorResponse): Observable<true> {
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
// A client-side or network error occurred. Handle it accordingly.
|
||||
return throwError('Not Connected')
|
||||
} else {
|
||||
return of(true)
|
||||
async getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes> {
|
||||
return this.http.rpcRequest({ method: 'marketplace.available', params })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# Size Limit [![Cult Of Martians][cult-img]][cult]
|
||||
|
||||
<img src="https://ai.github.io/size-limit/logo.svg" align="right"
|
||||
alt="Size Limit logo by Anton Lovchikov" width="120" height="178">
|
||||
|
||||
Size Limit is a performance budget tool for JavaScript. It checks every commit
|
||||
on CI, calculates the real cost of your JS for end-users and throws an error
|
||||
if the cost exceeds the limit.
|
||||
@@ -159,192 +156,6 @@ interactive elements, using React/Vue/Svelte lib or vanilla JS.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### Big Libraries
|
||||
|
||||
JS libraries > 10 KB in size.
|
||||
|
||||
This preset includes headless Chrome, and will measure your lib’s execution
|
||||
time. You likely don’t need this overhead for a small 2 KB lib, but for larger
|
||||
ones the execution time is a more accurate and understandable metric that
|
||||
the size in bytes. Library like [React] is a good example for this preset.
|
||||
|
||||
<details><summary><b>Show instructions</b></summary>
|
||||
|
||||
1. Install preset:
|
||||
|
||||
```sh
|
||||
$ npm install --save-dev size-limit @size-limit/preset-big-lib
|
||||
```
|
||||
|
||||
2. Add the `size-limit` section and the `size` script to your `package.json`:
|
||||
|
||||
```diff
|
||||
+ "size-limit": [
|
||||
+ {
|
||||
+ "path": "dist/react.production-*.js"
|
||||
+ }
|
||||
+ ],
|
||||
"scripts": {
|
||||
"build": "webpack ./scripts/rollup/build.js",
|
||||
+ "size": "npm run build && size-limit",
|
||||
"test": "jest && eslint ."
|
||||
}
|
||||
```
|
||||
|
||||
3. If you use ES modules you can test the size after tree-shaking with `import`
|
||||
option:
|
||||
|
||||
```diff
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "dist/react.production-*.js",
|
||||
+ "import": "{ createComponent }"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
4. Here’s how you can get the size for your current project:
|
||||
|
||||
```sh
|
||||
$ npm run size
|
||||
|
||||
Package size: 30.08 KB with all dependencies, minified and gzipped
|
||||
Loading time: 602 ms on slow 3G
|
||||
Running time: 214 ms on Snapdragon 410
|
||||
Total time: 815 ms
|
||||
```
|
||||
|
||||
5. Now, let’s set the limit. Add 25% to the current total time and use that
|
||||
as the limit in your `package.json`:
|
||||
|
||||
```diff
|
||||
"size-limit": [
|
||||
{
|
||||
+ "limit": "1 s",
|
||||
"path": "dist/react.production-*.js"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
6. Add a `size` script to your test suite:
|
||||
|
||||
```diff
|
||||
"scripts": {
|
||||
"build": "rollup ./scripts/rollup/build.js",
|
||||
"size": "npm run build && size-limit",
|
||||
- "test": "jest && eslint ."
|
||||
+ "test": "jest && eslint . && npm run size"
|
||||
}
|
||||
```
|
||||
|
||||
7. If you don’t have a continuous integration service running, don’t forget
|
||||
to add one — start with [Travis CI].
|
||||
8. Add the library size to docs, it will help users to choose your project:
|
||||
|
||||
```diff
|
||||
# Project Name
|
||||
|
||||
Short project description
|
||||
|
||||
* **Fast.** 10% faster than competitor.
|
||||
+ * **Small.** 15 KB (minified and gzipped).
|
||||
+ [Size Limit](https://github.com/ai/size-limit) controls the size.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### Small Libraries
|
||||
|
||||
JS libraries < 10 KB in size.
|
||||
|
||||
This preset will only measure the size, without the execution time, so it’s
|
||||
suitable for small libraries. If your library is larger, you likely want
|
||||
the Big Libraries preset above. [Nano ID] or [Storeon] are good examples
|
||||
for this preset.
|
||||
|
||||
<details><summary><b>Show instructions</b></summary>
|
||||
|
||||
1. First, install `size-limit`:
|
||||
|
||||
```sh
|
||||
$ npm install --save-dev size-limit @size-limit/preset-small-lib
|
||||
```
|
||||
|
||||
2. Add the `size-limit` section and the `size` script to your `package.json`:
|
||||
|
||||
```diff
|
||||
+ "size-limit": [
|
||||
+ {
|
||||
+ "path": "index.js"
|
||||
+ }
|
||||
+ ],
|
||||
"scripts": {
|
||||
+ "size": "size-limit",
|
||||
"test": "jest && eslint ."
|
||||
}
|
||||
```
|
||||
|
||||
3. Here’s how you can get the size for your current project:
|
||||
|
||||
```sh
|
||||
$ npm run size
|
||||
|
||||
Package size: 177 B with all dependencies, minified and gzipped
|
||||
```
|
||||
|
||||
4. If your project size starts to look bloated, run `--why` for analysis:
|
||||
|
||||
```sh
|
||||
$ npm run size -- --why
|
||||
```
|
||||
|
||||
5. Now, let’s set the limit. Determine the current size of your library,
|
||||
add just a little bit (a kilobyte, maybe) and use that as the limit
|
||||
in your `package.json`:
|
||||
|
||||
```diff
|
||||
"size-limit": [
|
||||
{
|
||||
+ "limit": "9 KB",
|
||||
"path": "index.js"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
6. Add the `size` script to your test suite:
|
||||
|
||||
```diff
|
||||
"scripts": {
|
||||
"size": "size-limit",
|
||||
- "test": "jest && eslint ."
|
||||
+ "test": "jest && eslint . && npm run size"
|
||||
}
|
||||
```
|
||||
|
||||
7. If you don’t have a continuous integration service running, don’t forget
|
||||
to add one — start with [Travis CI].
|
||||
8. Add the library size to docs, it will help users to choose your project:
|
||||
|
||||
```diff
|
||||
# Project Name
|
||||
|
||||
Short project description
|
||||
|
||||
* **Fast.** 10% faster than competitor.
|
||||
+ * **Small.** 500 bytes (minified and gzipped). No dependencies.
|
||||
+ [Size Limit](https://github.com/ai/size-limit) controls the size.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
[Travis CI]: https://github.com/dwyl/learn-travis
|
||||
[Storeon]: https://github.com/ai/storeon/
|
||||
[Nano ID]: https://github.com/ai/nanoid/
|
||||
[React]: https://github.com/facebook/react/
|
||||
|
||||
|
||||
## Reports
|
||||
|
||||
Size Limit has a [GitHub action] that comments and rejects pull requests based
|
||||
@@ -371,99 +182,6 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
|
||||
## Config
|
||||
|
||||
Size Limits supports three ways to define config.
|
||||
|
||||
1. `size-limit` section in `package.json`:
|
||||
|
||||
```json
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "index.js",
|
||||
"import": "{ createStore }",
|
||||
"limit": "500 ms"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
2. or a separate `.size-limit.json` config file:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"path": "index.js",
|
||||
"import": "{ createStore }",
|
||||
"limit": "500 ms"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
3. or a more flexible `.size-limit.js` config file:
|
||||
|
||||
```js
|
||||
module.exports = [
|
||||
{
|
||||
path: "index.js",
|
||||
import: "{ createStore }",
|
||||
limit: "500 ms"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Each section in the config can have these options:
|
||||
|
||||
* **path**: relative paths to files. The only mandatory option.
|
||||
It could be a path `"index.js"`, a [pattern] `"dist/app-*.js"`
|
||||
or an array `["index.js", "dist/app-*.js", "!dist/app-exclude.js"]`.
|
||||
* **import**: partial import to test tree-shaking. It could be `"{ lib }"`
|
||||
to test `import { lib } from 'lib'` or `{ "a.js": "{ a }", "b.js": "{ b }" }`
|
||||
to test multiple files.
|
||||
* **limit**: size or time limit for files from the `path` option. It should be
|
||||
a string with a number and unit, separated by a space.
|
||||
Format: `100 B`, `10 KB`, `500 ms`, `1 s`.
|
||||
* **name**: the name of the current section. It will only be useful
|
||||
if you have multiple sections.
|
||||
* **entry**: when using a custom webpack config, a webpack entry could be given.
|
||||
It could be a string or an array of strings.
|
||||
By default, the total size of all entry points will be checked.
|
||||
* **webpack**: with `false` it will disable webpack.
|
||||
* **running**: with `false` it will disable calculating running time.
|
||||
* **gzip**: with `false` it will disable gzip compression.
|
||||
* **brotli**: with `true` it will use brotli compression and disable gzip compression.
|
||||
* **config**: a path to a custom webpack config.
|
||||
* **ignore**: an array of files and dependencies to exclude from
|
||||
the project size calculation.
|
||||
|
||||
If you use Size Limit to track the size of CSS files, make sure to set
|
||||
`webpack: false`. Otherwise, you will get wrong numbers, because webpack
|
||||
inserts `style-loader` runtime (≈2 KB) into the bundle.
|
||||
|
||||
[pattern]: https://github.com/sindresorhus/globby#globbing-patterns
|
||||
|
||||
|
||||
## Plugins and Presets
|
||||
|
||||
Plugins:
|
||||
|
||||
* `@size-limit/file` checks the size of files with Gzip, Brotli
|
||||
or without compression.
|
||||
* `@size-limit/webpack` adds your library to empty webpack project
|
||||
and prepares bundle file for `file` plugin.
|
||||
* `@size-limit/time` uses headless Chrome to track time to execute JS.
|
||||
* `@size-limit/dual-publish` compiles files to ES modules with [`dual-publish`]
|
||||
to check size after tree-shaking.
|
||||
|
||||
Plugin presets:
|
||||
|
||||
* `@size-limit/preset-app` contains `file` and `time` plugins.
|
||||
* `@size-limit/preset-big-lib` contains `webpack`, `file`, and `time` plugins.
|
||||
* `@size-limit/preset-small-lib` contains `webpack` and `file` plugins.
|
||||
|
||||
[`dual-publish`]: https://github.com/ai/dual-publish
|
||||
|
||||
|
||||
## JS API
|
||||
|
||||
```js
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user