Gateways, domains, and new service interface (#3001)

* add support for inbound proxies

* backend changes

* fix file type

* proxy -> tunnel, implement backend apis

* wip start-tunneld

* add domains and gateways, remove routers, fix docs links

* dont show hidden actions

* show and test dns

* edit instead of chnage acme and change gateway

* refactor: domains page

* refactor: gateways page

* domains and acme refactor

* certificate authorities

* refactor public/private gateways

* fix fe types

* domains mostly finished

* refactor: add file control to form service

* add ip util to sdk

* domains api + migration

* start service interface page, WIP

* different options for clearnet domains

* refactor: styles for interfaces page

* minor

* better placeholder for no addresses

* start sorting addresses

* best address logic

* comments

* fix unnecessary export

* MVP of service interface page

* domains preferred

* fix: address comments

* only translations left

* wip: start-tunnel & fix build

* forms for adding domain, rework things based on new ideas

* fix: dns testing

* public domain, max width, descriptions for dns

* nix StartOS domains, implement public and private domains at interface scope

* restart tor instead of reset

* better icon for restart tor

* dns

* fix sort functions for public and private domains

* with todos

* update types

* clean up tech debt, bump dependencies

* revert to ts-rs v9

* fix all types

* fix dns form

* add missing translations

* it builds

* fix: comments (#3009)

* fix: comments

* undo default

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* fix: refactor legacy components (#3010)

* fix: comments

* fix: refactor legacy components

* remove default again

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* more translations

* wip

* fix deadlock

* coukd work

* simple renaming

* placeholder for empty service interfaces table

* honor hidden form values

* remove logs

* reason instead of description

* fix dns

* misc fixes

* implement toggling gateways for service interface

* fix showing dns records

* move status column in service list

* remove unnecessary truthy check

* refactor: refactor forms components and remove legacy Taiga UI package (#3012)

* handle wh file uploads

* wip: debugging tor

* socks5 proxy working

* refactor: fix multiple comments (#3013)

* refactor: fix multiple comments

* styling changes, add documentation to sidebar

* translations for dns page

* refactor: subtle colors

* rearrange service page

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* fix file_stream and remove non-terminating test

* clean  up logs

* support for sccache

* fix gha sccache

* more marketplace translations

* install wizard clarity

* stub hostnameInfo in migration

* fix address info after setup, fix styling on SI page, new 040 release notes

* remove tor logs from os

* misc fixes

* reset tor still not functioning...

* update ts

* minor styling and wording

* chore: some fixes (#3015)

* fix gateway renames

* different handling for public domains

* styling fixes

* whole navbar should not be clickable on service show page

* timeout getState request

* remove links from changelog

* misc fixes from pairing

* use custom name for gateway in more places

* fix dns parsing

* closes #3003

* closes #2999

* chore: some fixes (#3017)

* small copy change

* revert hardcoded error for testing

* dont require port forward if gateway is public

* use old wan ip when not available

* fix .const hanging on undefined

* fix test

* fix doc test

* fix renames

* update deps

* allow specifying dependency metadata directly

* temporarily make dependencies not cliackable in marketplace listings

* fix socks bind

* fix test

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2025-09-09 21:43:51 -06:00
committed by GitHub
parent 1cc9a1a30b
commit add01ebc68
537 changed files with 19940 additions and 20551 deletions

View File

@@ -67,9 +67,9 @@ export class ActionService {
},
})
.pipe(filter(Boolean))
.subscribe(() => this.execute(pkgInfo.id, actionInfo.id))
.subscribe(() => this.execute(pkgInfo.id, null, actionInfo.id))
} else {
this.execute(pkgInfo.id, actionInfo.id)
this.execute(pkgInfo.id, null, actionInfo.id)
}
}
} else {
@@ -96,14 +96,20 @@ export class ActionService {
}
}
async execute(packageId: string, actionId: string, input?: object) {
async execute(
packageId: string,
eventId: string | null,
actionId: string,
input?: object,
) {
const loader = this.loader.open('Loading').subscribe()
try {
const res = await this.api.runAction({
packageId,
eventId,
actionId,
input: input || null,
input: input ?? null,
})
if (!res) return

View File

@@ -110,7 +110,7 @@ export namespace Mock {
squashfs: {
aarch64: {
publishedAt: '2025-04-21T20:58:48.140749883Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_aarch64.squashfs',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_aarch64.squashfs',
commitment: {
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
size: 1343500288,
@@ -122,7 +122,7 @@ export namespace Mock {
},
'aarch64-nonfree': {
publishedAt: '2025-04-21T21:07:00.249285116Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_aarch64-nonfree.squashfs',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_aarch64-nonfree.squashfs',
commitment: {
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
size: 1653075968,
@@ -134,7 +134,7 @@ export namespace Mock {
},
raspberrypi: {
publishedAt: '2025-04-21T21:16:12.933319237Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_raspberrypi.squashfs',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_raspberrypi.squashfs',
commitment: {
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
size: 1490731008,
@@ -146,7 +146,7 @@ export namespace Mock {
},
x86_64: {
publishedAt: '2025-04-21T21:14:20.246908903Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_x86_64.squashfs',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_x86_64.squashfs',
commitment: {
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
size: 1411657728,
@@ -158,7 +158,7 @@ export namespace Mock {
},
'x86_64-nonfree': {
publishedAt: '2025-04-21T21:15:17.955265284Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.9/startos-0.4.0-alpha.9-33ae46f~dev_x86_64-nonfree.squashfs',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.10/startos-0.4.0-alpha.10-33ae46f~dev_x86_64-nonfree.squashfs',
commitment: {
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
size: 1731035136,
@@ -354,7 +354,7 @@ export namespace Mock {
}
export const BitcoinDep: T.DependencyMetadata = {
title: 'Bitcoin Core',
title: 'Bitcoin',
icon: BTC_ICON,
optional: false,
description: 'Needed to run',
@@ -385,7 +385,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -420,7 +420,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -465,7 +465,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -500,7 +500,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -547,23 +547,13 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.5',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: PROXY_ICON,
description: 'Used for authorized proxying of RPC requests',
optional: true,
},
bitcoind: BitcoinDep,
'btc-rpc-proxy': ProxyDep,
},
donationUrl: null,
alerts: {
@@ -605,23 +595,13 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.4',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: PROXY_ICON,
description: 'Used for authorized proxying of RPC requests',
optional: true,
},
bitcoind: BitcoinDep,
'btc-rpc-proxy': ProxyDep,
},
donationUrl: null,
alerts: {
@@ -667,7 +647,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -702,7 +682,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -747,23 +727,13 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: null,
description: 'Used for authorized RPC requests',
optional: true,
},
bitcoind: BitcoinDep,
'btc-rpc-proxy': ProxyDep,
},
donationUrl: null,
alerts: {
@@ -805,17 +775,12 @@ export namespace Mock {
marketingSite: '',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.36',
sdkVersion: '0.4.0-beta.37',
gitHash: 'fakehash',
icon: PROXY_ICON,
sourceVersion: null,
dependencyMetadata: {
bitcoind: {
title: 'Bitcoin Core',
icon: BTC_ICON,
description: 'Used for RPC requests',
optional: false,
},
bitcoind: BitcoinDep,
},
donationUrl: null,
alerts: {
@@ -1954,7 +1919,7 @@ export namespace Mock {
manifest: MockManifestBitcoind,
},
s9pk: '/media/startos/data/package-data/archive/installed/asdfasdf.s9pk',
icon: '/assets/img/service-icons/bitcoind.svg',
icon: '/assets/img/service-icons/bitcoin-core.svg',
lastBackup: null,
status: {
main: 'running',
@@ -2059,7 +2024,8 @@ export namespace Mock {
net: {
assignedPort: 80,
assignedSslPort: 443,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
addSsl: null,
@@ -2068,13 +2034,14 @@ export namespace Mock {
},
},
},
publicDomains: {},
privateDomains: [],
onions: [],
domains: {},
hostnameInfo: {
80: [
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'local',
@@ -2085,7 +2052,7 @@ export namespace Mock {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'local',
@@ -2096,7 +2063,7 @@ export namespace Mock {
},
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'ipv4',
@@ -2107,7 +2074,7 @@ export namespace Mock {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'ipv4',
@@ -2118,7 +2085,7 @@ export namespace Mock {
},
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'ipv6',
@@ -2130,7 +2097,7 @@ export namespace Mock {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'ipv6',
@@ -2158,7 +2125,8 @@ export namespace Mock {
net: {
assignedPort: 8332,
assignedSslPort: null,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
addSsl: null,
@@ -2167,8 +2135,9 @@ export namespace Mock {
},
},
},
publicDomains: {},
privateDomains: [],
onions: [],
domains: {},
hostnameInfo: {
8332: [],
},
@@ -2180,7 +2149,8 @@ export namespace Mock {
net: {
assignedPort: 8333,
assignedSslPort: null,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
addSsl: null,
@@ -2189,8 +2159,9 @@ export namespace Mock {
},
},
},
publicDomains: {},
privateDomains: [],
onions: [],
domains: {},
hostnameInfo: {
8333: [],
},
@@ -2253,8 +2224,8 @@ export namespace Mock {
},
currentDependencies: {
bitcoind: {
title: Mock.MockManifestBitcoind.title,
icon: 'assets/img/service-icons/bitcoind.svg',
title: BitcoinDep.title,
icon: BitcoinDep.icon,
kind: 'running',
versionRange: '>=26.0.0',
healthChecks: [],
@@ -2350,8 +2321,8 @@ export namespace Mock {
},
currentDependencies: {
bitcoind: {
title: Mock.MockManifestBitcoind.title,
icon: 'assets/img/service-icons/bitcoind.svg',
title: BitcoinDep.title,
icon: BitcoinDep.icon,
kind: 'running',
versionRange: '>=26.0.0',
healthChecks: [],

View File

@@ -104,6 +104,16 @@ export namespace RR {
export type DiskRepairReq = {} // server.disk.repair
export type DiskRepairRes = null
export type SetDnsReq = {
servers: string[] | null
} // net.dns.set-static
export type SetDnsRes = null
export type QueryDnsReq = {
fqdn: string
} // net.dns.query
export type QueryDnsRes = string | null
export type ResetTorReq = {
wipeState: boolean
reason: string
@@ -234,10 +244,28 @@ export namespace RR {
}
export type CreateBackupRes = null
// package
// network
export type AddTunnelReq = {
name: string
config: string // file contents
public: boolean
} // net.tunnel.add
export type AddTunnelRes = {
id: string
}
export type UpdateTunnelReq = {
id: string
name: string
} // net.gateway.set-name
export type UpdateTunnelRes = null
export type RemoveTunnelReq = { id: string } // net.tunnel.remove
export type RemoveTunnelRes = null
export type InitAcmeReq = {
provider: 'letsencrypt' | 'letsencrypt-staging' | string
provider: string
contact: string[]
}
export type InitAcmeRes = null
@@ -252,14 +280,15 @@ export namespace RR {
key: string
}
export type GenerateTorKeyReq = {} // net.tor.key.generate
export type AddTorKeyRes = string // onion address without .onion suffix
export type AddTorKeyRes = string // onion address *with* .onion suffix
export type ServerBindingSetPublicReq = {
// server.host.binding.set-public
internalPort: number
public: boolean | null // default true
export type ServerBindingToggleGatewayReq = {
// server.host.binding.set-gateway-enabled
gateway: T.GatewayId
internalPort: 80
enabled: boolean
}
export type BindingSetPublicRes = null
export type ServerBindingToggleGatewayRes = null
export type ServerAddOnionReq = {
// server.host.address.onion.add
@@ -270,45 +299,73 @@ export namespace RR {
export type ServerRemoveOnionReq = ServerAddOnionReq // server.host.address.onion.remove
export type RemoveOnionRes = null
export type ServerAddDomainReq = {
// server.host.address.domain.add
domain: string // FQDN
private: boolean
acme: string | null // "letsencrypt" | "letsencrypt-staging" | Url | null
export type OsUiAddPublicDomainReq = {
// server.host.address.domain.public.add
fqdn: string // FQDN
gateway: T.GatewayId
acme: string | null // URL. null means local Root CA
}
export type AddDomainRes = null
export type OsUiAddPublicDomainRes = QueryDnsRes
export type ServerRemoveDomainReq = {
// server.host.address.domain.remove
domain: string // FQDN
export type OsUiRemovePublicDomainReq = {
// server.host.address.domain.public.remove
fqdn: string // FQDN
}
export type RemoveDomainRes = null
export type OsUiRemovePublicDomainRes = null
export type PkgBindingSetPublicReq = ServerBindingSetPublicReq & {
// package.host.binding.set-public
export type OsUiAddPrivateDomainReq = {
// server.host.address.domain.private.add
fqdn: string // FQDN
}
export type OsUiAddPrivateDomainRes = null
export type OsUiRemovePrivateDomainReq = {
// server.host.address.domain.private.remove
fqdn: string // FQDN
}
export type OsUiRemovePrivateDomainRes = null
export type PkgBindingToggleGatewayReq = Omit<
ServerBindingToggleGatewayReq,
'internalPort'
> & {
// package.host.binding.set-gateway-enabled
internalPort: number
package: T.PackageId // string
host: T.HostId // string
}
export type PkgBindingToggleGatewayRes = null
export type PkgAddOnionReq = ServerAddOnionReq & {
// package.host.address.onion.add
package: T.PackageId // string
host: T.HostId // string
}
export type PkgRemoveOnionReq = PkgAddOnionReq // package.host.address.onion.remove
export type PkgAddDomainReq = ServerAddDomainReq & {
// package.host.address.domain.add
export type PkgAddPublicDomainReq = OsUiAddPublicDomainReq & {
// package.host.address.domain.public.add
package: T.PackageId // string
host: T.HostId // string
}
export type PkgAddPublicDomainRes = OsUiAddPublicDomainRes
export type PkgRemoveDomainReq = ServerRemoveDomainReq & {
// package.host.address.domain.remove
export type PkgRemovePublicDomainReq = OsUiRemovePublicDomainReq & {
// package.host.address.domain.public.remove
package: T.PackageId // string
host: T.HostId // string
}
export type PkgRemovePublicDomainRes = OsUiRemovePublicDomainRes
export type PkgAddPrivateDomainReq = OsUiAddPrivateDomainReq & {
// package.host.address.domain.private.add
package: T.PackageId // string
host: T.HostId // string
}
export type PkgAddPrivateDomainRes = OsUiAddPrivateDomainRes
export type PkgRemovePrivateDomainReq = PkgAddPrivateDomainReq
export type PkgRemovePrivateDomainRes = OsUiRemovePrivateDomainRes
export type GetPackageLogsReq = FetchLogsReq & { id: string } // package.logs
export type GetPackageLogsRes = FetchLogsRes
@@ -324,12 +381,14 @@ export namespace RR {
export type GetActionInputReq = { packageId: string; actionId: string } // package.action.get-input
export type GetActionInputRes = {
eventId: string
spec: IST.InputSpec
value: object | null
}
export type ActionReq = {
packageId: string
eventId: string | null
actionId: string
input: object | null
} // package.action.run
@@ -374,7 +433,7 @@ export namespace RR {
icon: string // base64
}
export type SideloadPackageRes = {
upload: string // guid
upload: string
progress: string // guid
}
@@ -394,15 +453,6 @@ export namespace RR {
export type GetRegistryPackagesRes = GetPackagesRes
}
export type Breakages = {
[id: string]: TaggedDependencyError
}
export type TaggedDependencyError = {
dependency: string
error: DependencyError
}
interface MetricData {
value: string
unit: string
@@ -566,99 +616,9 @@ export type Encrypted = {
encrypted: string
}
export type DependencyError =
| DependencyErrorNotInstalled
| DependencyErrorNotRunning
| DependencyErrorIncorrectVersion
| DependencyErrorActionRequired
| DependencyErrorHealthChecksFailed
| DependencyErrorTransitive
export type DependencyErrorNotInstalled = {
type: 'notInstalled'
}
export type DependencyErrorNotRunning = {
type: 'notRunning'
}
export type DependencyErrorIncorrectVersion = {
type: 'incorrectVersion'
expected: string // version range
received: string // version
}
export interface DependencyErrorActionRequired {
type: 'actionRequired'
}
export type DependencyErrorHealthChecksFailed = {
type: 'healthChecksFailed'
check: T.NamedHealthCheckResult
}
export type DependencyErrorTransitive = {
type: 'transitive'
}
// @TODO 041
// export namespace RR041 {
// // ** domains **
// export type ClaimStart9ToReq = { networkInterfaceId: string } // net.domain.me.claim
// export type ClaimStart9ToRes = null
// export type DeleteStart9ToReq = {} // net.domain.me.delete
// export type DeleteStart9ToRes = null
// export type AddDomainReq = {
// hostname: string
// provider: {
// name: string
// username: string | null
// password: string | null
// }
// networkInterfaceId: string
// } // net.domain.add
// export type AddDomainRes = null
// export type DeleteDomainReq = { hostname: string } // net.domain.delete
// export type DeleteDomainRes = null
// // port forwards
// export type OverridePortReq = { target: number; port: number } // net.port-forwards.override
// export type OverridePortRes = null
// // ** proxies **
// export type AddProxyReq = {
// name: string
// config: string
// } // net.proxy.add
// export type AddProxyRes = null
// export type UpdateProxyReq = {
// name: string
// } // net.proxy.update
// export type UpdateProxyRes = null
// export type DeleteProxyReq = { id: string } // net.proxy.delete
// export type DeleteProxyRes = null
// // ** set outbound proxies **
// export type SetOsOutboundProxyReq = {
// proxy: string | null
// } // server.proxy.set-outbound
// export type SetOsOutboundProxyRes = null
// export type SetServiceOutboundProxyReq = {
// packageId: string
// proxy: string | null
// } // package.proxy.set-outbound
// export type SetServiceOutboundProxyRes = null
// // ** automated backups **
@@ -740,20 +700,6 @@ export type DependencyErrorTransitive = {
// @TODO 041 types
// export type AppMetrics = {
// memory: {
// percentageUsed: MetricData
// used: MetricData
// }
// cpu: {
// percentageUsed: MetricData
// }
// disk: {
// percentageUsed: MetricData
// used: MetricData
// }
// }
// export type RemoteBackupTarget = CifsBackupTarget | CloudBackupTarget
// export type BackupTarget = RemoteBackupTarget | DiskBackupTarget

View File

@@ -6,8 +6,8 @@ import { WebSocketSubject } from 'rxjs/webSocket'
export abstract class ApiService {
// http
// for sideloading packages
abstract uploadPackage(guid: string, body: Blob): Promise<void>
// for uploading files
abstract uploadFile(guid: string, body: Blob): Promise<void>
// for getting static files: ex license
abstract getStaticProxy(
@@ -90,8 +90,6 @@ export abstract class ApiService {
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes>
abstract getTorLogs(params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes>
abstract followServerLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes>
@@ -100,10 +98,6 @@ export abstract class ApiService {
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes>
abstract followTorLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes>
abstract followServerMetrics(
params: RR.FollowServerMetricsReq,
): Promise<RR.FollowServerMetricsRes>
@@ -122,16 +116,12 @@ export abstract class ApiService {
abstract toggleKiosk(enable: boolean): Promise<null>
abstract setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes>
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
// @TODO 041
// ** server outbound proxy **
// abstract setOsOutboundProxy(
// params: RR.SetOsOutboundProxyReq,
// ): Promise<RR.SetOsOutboundProxyRes>
// smtp
abstract setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes>
@@ -182,38 +172,14 @@ export abstract class ApiService {
// ** proxies **
// @TODO 041
abstract addTunnel(params: RR.AddTunnelReq): Promise<RR.AddTunnelRes>
// abstract addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes>
abstract updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes>
// abstract updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes>
// abstract deleteProxy(params: RR.DeleteProxyReq): Promise<RR.DeleteProxyRes>
abstract removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes>
// ** domains **
// @TODO 041
// abstract claimStart9ToDomain(
// params: RR.ClaimStart9ToReq,
// ): Promise<RR.ClaimStart9ToRes>
// abstract deleteStart9ToDomain(
// params: RR.DeleteStart9ToReq,
// ): Promise<RR.DeleteStart9ToRes>
// abstract addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes>
// abstract deleteDomain(params: RR.DeleteDomainReq): Promise<RR.DeleteDomainRes>
// ** port forwards **
// @TODO 041
// abstract overridePortForward(
// params: RR.OverridePortReq,
// ): Promise<RR.OverridePortRes>
// wifi
abstract enableWifi(params: RR.EnabledWifiReq): Promise<RR.EnabledWifiRes>
@@ -366,8 +332,8 @@ export abstract class ApiService {
// ** service outbound proxy **
// abstract setServiceOutboundProxy(
// params: RR.SetServiceOutboundProxyReq,
// ): Promise<RR.SetServiceOutboundProxyRes>
// params: RR.SetServiceOutboundTunnelReq,
// ): Promise<RR.SetServiceOutboundTunnelRes>
abstract initAcme(params: RR.InitAcmeReq): Promise<RR.InitAcmeRes>
@@ -379,9 +345,9 @@ export abstract class ApiService {
params: RR.GenerateTorKeyReq,
): Promise<RR.AddTorKeyRes>
abstract serverBindingSetPubic(
params: RR.ServerBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes>
abstract serverBindingToggleGateway(
params: RR.ServerBindingToggleGatewayReq,
): Promise<RR.ServerBindingToggleGatewayRes>
abstract serverAddOnion(params: RR.ServerAddOnionReq): Promise<RR.AddOnionRes>
@@ -389,17 +355,25 @@ export abstract class ApiService {
params: RR.ServerRemoveOnionReq,
): Promise<RR.RemoveOnionRes>
abstract serverAddDomain(
params: RR.ServerAddDomainReq,
): Promise<RR.AddDomainRes>
abstract osUiAddPublicDomain(
params: RR.OsUiAddPublicDomainReq,
): Promise<RR.OsUiAddPublicDomainRes>
abstract serverRemoveDomain(
params: RR.ServerRemoveDomainReq,
): Promise<RR.RemoveDomainRes>
abstract osUiRemovePublicDomain(
params: RR.OsUiRemovePublicDomainReq,
): Promise<RR.OsUiRemovePublicDomainRes>
abstract pkgBindingSetPubic(
params: RR.PkgBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes>
abstract osUiAddPrivateDomain(
params: RR.OsUiAddPrivateDomainReq,
): Promise<RR.OsUiAddPrivateDomainRes>
abstract osUiRemovePrivateDomain(
params: RR.OsUiRemovePrivateDomainReq,
): Promise<RR.OsUiRemovePrivateDomainRes>
abstract pkgBindingToggleGateway(
params: RR.PkgBindingToggleGatewayReq,
): Promise<RR.PkgBindingToggleGatewayRes>
abstract pkgAddOnion(params: RR.PkgAddOnionReq): Promise<RR.AddOnionRes>
@@ -407,9 +381,19 @@ export abstract class ApiService {
params: RR.PkgRemoveOnionReq,
): Promise<RR.RemoveOnionRes>
abstract pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes>
abstract pkgAddPublicDomain(
params: RR.PkgAddPublicDomainReq,
): Promise<RR.PkgAddPublicDomainRes>
abstract pkgRemoveDomain(
params: RR.PkgRemoveDomainReq,
): Promise<RR.RemoveDomainRes>
abstract pkgRemovePublicDomain(
params: RR.PkgRemovePublicDomainReq,
): Promise<RR.PkgRemovePublicDomainRes>
abstract pkgAddPrivateDomain(
params: RR.PkgAddPrivateDomainReq,
): Promise<RR.PkgAddPrivateDomainRes>
abstract pkgRemovePrivateDomain(
params: RR.PkgRemovePrivateDomainReq,
): Promise<RR.PkgRemovePrivateDomainRes>
}

View File

@@ -33,9 +33,9 @@ export class LiveApiService extends ApiService {
this.document.defaultView.rpcClient = this
}
// for sideloading packages
// for uploading files
async uploadPackage(guid: string, body: Blob): Promise<void> {
async uploadFile(guid: string, body: Blob): Promise<void> {
await this.httpRequest({
method: Method.POST,
body,
@@ -97,7 +97,7 @@ export class LiveApiService extends ApiService {
}
async getState(): Promise<RR.ServerState> {
return this.rpcRequest({ method: 'state', params: {} })
return this.rpcRequest({ method: 'state', params: {}, timeout: 10000 })
}
// db
@@ -212,10 +212,6 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'server.kernel-logs', params })
}
async getTorLogs(params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
return this.rpcRequest({ method: 'net.tor.logs', params })
}
async followServerLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
@@ -228,12 +224,6 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'server.kernel-logs.follow', params })
}
async followTorLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
return this.rpcRequest({ method: 'net.tor.logs.follow', params })
}
async followServerMetrics(
params: RR.FollowServerMetricsReq,
): Promise<RR.FollowServerMetricsRes> {
@@ -267,16 +257,24 @@ export class LiveApiService extends ApiService {
})
}
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
return this.rpcRequest({
method: 'net.dns.set-static',
params,
})
}
async queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes> {
return this.rpcRequest({
method: 'net.dns.query',
params,
})
}
async resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes> {
return this.rpcRequest({ method: 'net.tor.reset', params })
}
// async setOsOutboundProxy(
// params: RR.SetOsOutboundProxyReq,
// ): Promise<RR.SetOsOutboundProxyRes> {
// return this.rpcRequest({ method: 'server.proxy.set-outbound', params })
// }
// marketplace URLs
async checkOSUpdate(
@@ -352,47 +350,17 @@ export class LiveApiService extends ApiService {
// proxies
// async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {
// return this.rpcRequest({ method: 'net.proxy.add', params })
// }
async addTunnel(params: RR.AddTunnelReq): Promise<RR.AddTunnelRes> {
return this.rpcRequest({ method: 'net.tunnel.add', params })
}
// async updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes> {
// return this.rpcRequest({ method: 'net.proxy.update', params })
// }
async updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes> {
return this.rpcRequest({ method: 'net.gateway.set-name', params })
}
// async deleteProxy(params: RR.DeleteProxyReq): Promise<RR.DeleteProxyRes> {
// return this.rpcRequest({ method: 'net.proxy.delete', params })
// }
// domains
// async claimStart9ToDomain(
// params: RR.ClaimStart9ToReq,
// ): Promise<RR.ClaimStart9ToRes> {
// return this.rpcRequest({ method: 'net.domain.me.claim', params })
// }
// async deleteStart9ToDomain(
// params: RR.DeleteStart9ToReq,
// ): Promise<RR.DeleteStart9ToRes> {
// return this.rpcRequest({ method: 'net.domain.me.delete', params })
// }
// async addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes> {
// return this.rpcRequest({ method: 'net.domain.add', params })
// }
// async deleteDomain(params: RR.DeleteDomainReq): Promise<RR.DeleteDomainRes> {
// return this.rpcRequest({ method: 'net.domain.delete', params })
// }
// port forwards
// async overridePortForward(
// params: RR.OverridePortReq,
// ): Promise<RR.OverridePortRes> {
// return this.rpcRequest({ method: 'net.port-forwards.override', params })
// }
async removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes> {
return this.rpcRequest({ method: 'net.tunnel.remove', params })
}
// wifi
@@ -627,8 +595,8 @@ export class LiveApiService extends ApiService {
}
// async setServiceOutboundProxy(
// params: RR.SetServiceOutboundProxyReq,
// ): Promise<RR.SetServiceOutboundProxyRes> {
// params: RR.SetServiceOutboundTunnelReq,
// ): Promise<RR.SetServiceOutboundTunnelRes> {
// return this.rpcRequest({ method: 'package.proxy.set-outbound', params })
// }
@@ -660,11 +628,11 @@ export class LiveApiService extends ApiService {
})
}
async serverBindingSetPubic(
params: RR.ServerBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes> {
async serverBindingToggleGateway(
params: RR.ServerBindingToggleGatewayReq,
): Promise<RR.ServerBindingToggleGatewayRes> {
return this.rpcRequest({
method: 'server.host.binding.set-public',
method: 'server.host.binding.set-gateway-enabled',
params,
})
}
@@ -685,29 +653,47 @@ export class LiveApiService extends ApiService {
})
}
async serverAddDomain(
params: RR.ServerAddDomainReq,
): Promise<RR.AddDomainRes> {
async osUiAddPublicDomain(
params: RR.OsUiAddPublicDomainReq,
): Promise<RR.OsUiAddPublicDomainRes> {
return this.rpcRequest({
method: 'server.host.address.domain.add',
method: 'server.host.address.domain.public.add',
params,
})
}
async serverRemoveDomain(
params: RR.ServerRemoveDomainReq,
): Promise<RR.RemoveDomainRes> {
async osUiRemovePublicDomain(
params: RR.OsUiRemovePublicDomainReq,
): Promise<RR.OsUiRemovePublicDomainRes> {
return this.rpcRequest({
method: 'server.host.address.domain.remove',
method: 'server.host.address.domain.public.remove',
params,
})
}
async pkgBindingSetPubic(
params: RR.PkgBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes> {
async osUiAddPrivateDomain(
params: RR.OsUiAddPrivateDomainReq,
): Promise<RR.OsUiAddPrivateDomainRes> {
return this.rpcRequest({
method: 'package.host.binding.set-public',
method: 'server.host.address.domain.private.add',
params,
})
}
async osUiRemovePrivateDomain(
params: RR.OsUiRemovePrivateDomainReq,
): Promise<RR.OsUiRemovePrivateDomainRes> {
return this.rpcRequest({
method: 'server.host.address.domain.private.remove',
params,
})
}
async pkgBindingToggleGateway(
params: RR.PkgBindingToggleGatewayReq,
): Promise<RR.PkgBindingToggleGatewayRes> {
return this.rpcRequest({
method: 'package.host.binding.set-gateway-enabled',
params,
})
}
@@ -728,18 +714,38 @@ export class LiveApiService extends ApiService {
})
}
async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
async pkgAddPublicDomain(
params: RR.PkgAddPublicDomainReq,
): Promise<RR.PkgAddPublicDomainRes> {
return this.rpcRequest({
method: 'package.host.address.domain.add',
method: 'package.host.address.domain.public.add',
params,
})
}
async pkgRemoveDomain(
params: RR.PkgRemoveDomainReq,
): Promise<RR.RemoveDomainRes> {
async pkgRemovePublicDomain(
params: RR.PkgRemovePublicDomainReq,
): Promise<RR.PkgRemovePublicDomainRes> {
return this.rpcRequest({
method: 'package.host.address.domain.remove',
method: 'package.host.address.domain.public.remove',
params,
})
}
async pkgAddPrivateDomain(
params: RR.PkgAddPrivateDomainReq,
): Promise<RR.PkgAddPrivateDomainRes> {
return this.rpcRequest({
method: 'package.host.address.domain.private.add',
params,
})
}
async pkgRemovePrivateDomain(
params: RR.PkgRemovePrivateDomainReq,
): Promise<RR.PkgRemovePrivateDomainRes> {
return this.rpcRequest({
method: 'package.host.address.domain.private.remove',
params,
})
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { pauseFor, Log, RPCErrorDetails, RPCOptions } from '@start9labs/shared'
import { pauseFor, Log, RPCErrorDetails } from '@start9labs/shared'
import { ApiService } from './embassy-api.service'
import {
AddOperation,
@@ -24,7 +24,7 @@ import { AuthService } from '../auth.service'
import { T } from '@start9labs/start-sdk'
import { MarketplacePkg } from '@start9labs/marketplace'
import { WebSocketSubject } from 'rxjs/webSocket'
import { toAcmeUrl } from 'src/app/utils/acme'
import { toAuthorityUrl } from 'src/app/utils/acme'
import markdown from './md-sample.md'
@@ -71,7 +71,7 @@ export class MockApiService extends ApiService {
.subscribe()
}
async uploadPackage(guid: string, body: Blob): Promise<void> {
async uploadFile(guid: string, body: Blob): Promise<void> {
await pauseFor(2000)
}
@@ -303,17 +303,6 @@ export class MockApiService extends ApiService {
}
}
async getTorLogs(params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
await pauseFor(2000)
const entries = this.randomLogs(params.limit)
return {
entries,
startCursor: 'startCursor',
endCursor: 'end-cursor',
}
}
async followServerLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
@@ -334,16 +323,6 @@ export class MockApiService extends ApiService {
}
}
async followTorLogs(
params: RR.FollowServerLogsReq,
): Promise<RR.FollowServerLogsRes> {
await pauseFor(2000)
return {
startCursor: 'start-cursor',
guid: 'logs-guid',
}
}
private randomLogs(limit = 1): Log[] {
const arrLength = Math.ceil(limit / Mock.ServerLogs.length)
const logs = new Array(arrLength)
@@ -462,28 +441,32 @@ export class MockApiService extends ApiService {
return null
}
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
await pauseFor(2000)
const patch: ReplaceOperation<T.DnsSettings['staticServers']>[] = [
{
op: PatchOp.REPLACE,
path: '/serverInfo/network/dns/staticServers',
value: params.servers,
},
]
this.mockRevision(patch)
return null
}
async queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes> {
await pauseFor(2000)
return null
}
async resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes> {
await pauseFor(2000)
return null
}
// async setOsOutboundProxy(
// params: RR.SetOsOutboundProxyReq,
// ): Promise<RR.SetOsOutboundProxyRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: '/serverInfo/network/outboundProxy',
// value: params.proxy,
// },
// ]
// this.mockRevision(patch)
// return null
// }
// marketplace URLs
async checkOSUpdate(
@@ -559,153 +542,67 @@ export class MockApiService extends ApiService {
return null
}
// network
// proxies
// async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {
// await pauseFor(2000)
private proxyId = 0
async addTunnel(params: RR.AddTunnelReq): Promise<RR.AddTunnelRes> {
await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.ADD,
// path: `/serverInfo/network/networkInterfaces/wga1`,
// value: {
// inbound: true,
// outbound: true,
// ipInfo: {
// name: params.name,
// scopeId: 3,
// deviceType: 'wireguard',
// subnets: [],
// wanIp: '1.1.1.1',
// ntpServers: [],
// },
// },
// },
// ]
// this.mockRevision(patch)
const id = `wg${this.proxyId++}`
// return null
// }
const patch: AddOperation<T.NetworkInterfaceInfo>[] = [
{
op: PatchOp.ADD,
path: `/serverInfo/network/gateways/${id}`,
value: {
name: params.name,
public: params.public,
secure: false,
ipInfo: {
name: id,
scopeId: 3,
deviceType: 'wireguard',
subnets: ['192.168.1.10/24'],
wanIp: '203.0.113.45',
ntpServers: [],
lanIp: ['192.168.1.10'],
dnsServers: [],
},
},
},
]
this.mockRevision(patch)
// async updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes> {
// await pauseFor(2000)
return { id }
}
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: `/serverInfo/network/proxies/0/name`,
// value: params.name,
// },
// ]
// this.mockRevision(patch)
async updateTunnel(params: RR.UpdateTunnelReq): Promise<RR.UpdateTunnelRes> {
await pauseFor(2000)
// return null
// }
const patch: ReplaceOperation<string>[] = [
{
op: PatchOp.REPLACE,
path: `/serverInfo/network/gateways/${params.id}/label`,
value: params.name,
},
]
this.mockRevision(patch)
// async deleteProxy(params: RR.DeleteProxyReq): Promise<RR.DeleteProxyRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: '/serverInfo/network/proxies',
// value: [],
// },
// ]
// this.mockRevision(patch)
return null
}
// return null
// }
async removeTunnel(params: RR.RemoveTunnelReq): Promise<RR.RemoveTunnelRes> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
{
op: PatchOp.REMOVE,
path: `/serverInfo/network/gateways/${params.id}`,
},
]
this.mockRevision(patch)
// domains
// async claimStart9ToDomain(
// params: RR.ClaimStart9ToReq,
// ): Promise<RR.ClaimStart9ToRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: '/serverInfo/network/start9To',
// value: {
// subdomain: 'xyz',
// networkInterfaceId: params.networkInterfaceId,
// },
// },
// ]
// this.mockRevision(patch)
// return null
// }
// async deleteStart9ToDomain(
// params: RR.DeleteStart9ToReq,
// ): Promise<RR.DeleteStart9ToRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: '/serverInfo/network/start9To',
// value: null,
// },
// ]
// this.mockRevision(patch)
// return null
// }
// async addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: `/serverInfo/network/domains`,
// value: {
// [params.hostname]: {
// networkInterfaceId: params.networkInterfaceId,
// provider: params.provider.name,
// },
// },
// },
// ]
// this.mockRevision(patch)
// return null
// }
// async deleteDomain(params: RR.DeleteDomainReq): Promise<RR.DeleteDomainRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: '/serverInfo/network/domains',
// value: {},
// },
// ]
// this.mockRevision(patch)
// return null
// }
// port forwards
// async overridePortForward(
// params: RR.OverridePortReq,
// ): Promise<RR.OverridePortRes> {
// await pauseFor(2000)
// const patch = [
// {
// op: PatchOp.REPLACE,
// path: '/serverInfo/network/wanConfig/forwards/0/override',
// value: params.port,
// },
// ]
// this.mockRevision(patch)
// return null
// }
return null
}
// wifi
@@ -1108,6 +1005,7 @@ export class MockApiService extends ApiService {
): Promise<RR.GetActionInputRes> {
await pauseFor(2000)
return {
eventId: 'ANZXNWIFRTTBZ6T52KQPZILIQQODDHXQ',
value: Mock.MockConfig,
spec: await Mock.getActionInputSpec(),
}
@@ -1371,8 +1269,8 @@ export class MockApiService extends ApiService {
}
// async setServiceOutboundProxy(
// params: RR.SetServiceOutboundProxyReq,
// ): Promise<RR.SetServiceOutboundProxyRes> {
// params: RR.SetServiceOutboundTunnelReq,
// ): Promise<RR.SetServiceOutboundTunnelRes> {
// await pauseFor(2000)
// const patch = [
// {
@@ -1394,7 +1292,7 @@ export class MockApiService extends ApiService {
op: PatchOp.ADD,
path: `/serverInfo/acme`,
value: {
[toAcmeUrl(params.provider)]: { contact: params.contact },
[toAuthorityUrl(params.provider)]: { contact: params.contact },
},
},
]
@@ -1421,24 +1319,24 @@ export class MockApiService extends ApiService {
async addTorKey(params: RR.AddTorKeyReq): Promise<RR.AddTorKeyRes> {
await pauseFor(2000)
return 'vanityabcdefghijklmnop'
return 'vanityabcdefghijklmnop.onion'
}
async generateTorKey(params: RR.GenerateTorKeyReq): Promise<RR.AddTorKeyRes> {
await pauseFor(2000)
return 'abcdefghijklmnopqrstuv'
return 'abcdefghijklmnopqrstuv.onion'
}
async serverBindingSetPubic(
params: RR.PkgBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes> {
async serverBindingToggleGateway(
params: RR.ServerBindingToggleGatewayReq,
): Promise<RR.ServerBindingToggleGatewayRes> {
await pauseFor(2000)
const patch = [
{
op: PatchOp.REPLACE,
path: `/serverInfo/host/bindings/${params.internalPort}/net/public`,
value: params.public,
path: `/serverInfo/network/host/bindings/${params.internalPort}/net/publicEnabled`,
value: params.enabled ? [params.gateway] : [],
},
]
this.mockRevision(patch)
@@ -1493,15 +1391,17 @@ export class MockApiService extends ApiService {
return null
}
async serverAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
async osUiAddPublicDomain(
params: RR.OsUiAddPublicDomainReq,
): Promise<RR.OsUiAddPublicDomainRes> {
await pauseFor(2000)
const patch: Operation<any>[] = [
{
op: PatchOp.ADD,
path: `/serverInfo/host/domains`,
path: `/serverInfo/host/publicDomains`,
value: {
[params.domain]: { public: !params.private, acme: params.acme },
[params.fqdn]: { gateway: params.gateway, acme: params.acme },
},
},
{
@@ -1509,11 +1409,11 @@ export class MockApiService extends ApiService {
path: `/serverInfo/host/hostnameInfo/80/0`,
value: {
kind: 'ip',
networkInterfaceId: 'eth0',
public: false,
gatewayId: 'eth0',
public: true,
hostname: {
kind: 'domain',
domain: params.domain,
domain: params.fqdn,
subdomain: null,
port: null,
sslPort: 443,
@@ -1526,15 +1426,15 @@ export class MockApiService extends ApiService {
return null
}
async serverRemoveDomain(
params: RR.PkgRemoveDomainReq,
): Promise<RR.RemoveDomainRes> {
async osUiRemovePublicDomain(
params: RR.OsUiRemovePublicDomainReq,
): Promise<RR.OsUiRemovePublicDomainRes> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
{
op: PatchOp.REMOVE,
path: `/serverInfo/host/domains/${params.domain}`,
path: `/serverInfo/host/publicDomains/${params.fqdn}`,
},
{
op: PatchOp.REMOVE,
@@ -1546,16 +1446,70 @@ export class MockApiService extends ApiService {
return null
}
async pkgBindingSetPubic(
params: RR.PkgBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes> {
async osUiAddPrivateDomain(
params: RR.OsUiAddPrivateDomainReq,
): Promise<RR.OsUiAddPrivateDomainRes> {
await pauseFor(2000)
const patch: Operation<any>[] = [
{
op: PatchOp.REPLACE,
path: `/serverInfo/host/privateDomains`,
value: [params.fqdn],
},
{
op: PatchOp.ADD,
path: `/serverInfo/host/hostnameInfo/80/0`,
value: {
kind: 'ip',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'domain',
domain: params.fqdn,
subdomain: null,
port: null,
sslPort: 443,
},
},
},
]
this.mockRevision(patch)
return null
}
async osUiRemovePrivateDomain(
params: RR.OsUiRemovePrivateDomainReq,
): Promise<RR.OsUiRemovePrivateDomainRes> {
await pauseFor(2000)
const patch: Operation<any>[] = [
{
op: PatchOp.REPLACE,
path: `/serverInfo/host/privateDomains`,
value: [],
},
{
op: PatchOp.REMOVE,
path: `/serverInfo/host/hostnameInfo/80/0`,
},
]
this.mockRevision(patch)
return null
}
async pkgBindingToggleGateway(
params: RR.PkgBindingToggleGatewayReq,
): Promise<RR.PkgBindingToggleGatewayRes> {
await pauseFor(2000)
const patch = [
{
op: PatchOp.REPLACE,
path: `/packageData/${params.package}/hosts/${params.host}/bindings/${params.internalPort}/net/public`,
value: params.public,
path: `/packageData/${params.package}/hosts/${params.host}/bindings/${params.internalPort}/net/privateDisabled`,
value: params.enabled ? [] : [params.gateway],
},
]
this.mockRevision(patch)
@@ -1610,15 +1564,17 @@ export class MockApiService extends ApiService {
return null
}
async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
async pkgAddPublicDomain(
params: RR.PkgAddPublicDomainReq,
): Promise<RR.PkgAddPublicDomainRes> {
await pauseFor(2000)
const patch: Operation<any>[] = [
{
op: PatchOp.ADD,
path: `/packageData/${params.package}/hosts/${params.host}/domains`,
path: `/packageData/${params.package}/hosts/${params.host}/publicDomains`,
value: {
[params.domain]: { public: !params.private, acme: params.acme },
[params.fqdn]: { gateway: params.gateway, acme: params.acme },
},
},
{
@@ -1626,11 +1582,11 @@ export class MockApiService extends ApiService {
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
value: {
kind: 'ip',
networkInterfaceId: 'eth0',
public: false,
gatewayId: 'eth0',
public: true,
hostname: {
kind: 'domain',
domain: params.domain,
domain: params.fqdn,
subdomain: null,
port: null,
sslPort: 443,
@@ -1643,15 +1599,69 @@ export class MockApiService extends ApiService {
return null
}
async pkgRemoveDomain(
params: RR.PkgRemoveDomainReq,
): Promise<RR.RemoveDomainRes> {
async pkgRemovePublicDomain(
params: RR.PkgRemovePublicDomainReq,
): Promise<RR.PkgRemovePublicDomainRes> {
await pauseFor(2000)
const patch: RemoveOperation[] = [
{
op: PatchOp.REMOVE,
path: `/packageData/${params.package}/hosts/${params.host}/domains/${params.domain}`,
path: `/packageData/${params.package}/hosts/${params.host}/publicDomains/${params.fqdn}`,
},
{
op: PatchOp.REMOVE,
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
},
]
this.mockRevision(patch)
return null
}
async pkgAddPrivateDomain(
params: RR.PkgAddPrivateDomainReq,
): Promise<RR.PkgAddPrivateDomainRes> {
await pauseFor(2000)
const patch: Operation<any>[] = [
{
op: PatchOp.REPLACE,
path: `/packageData/${params.package}/hosts/${params.host}/privateDomains`,
value: [params.fqdn],
},
{
op: PatchOp.ADD,
path: `/packageData/${params.package}/hosts/${params.host}/hostnameInfo/80/0`,
value: {
kind: 'ip',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'domain',
domain: params.fqdn,
subdomain: null,
port: null,
sslPort: 443,
},
},
},
]
this.mockRevision(patch)
return null
}
async pkgRemovePrivateDomain(
params: RR.PkgRemovePrivateDomainReq,
): Promise<RR.PkgRemovePrivateDomainRes> {
await pauseFor(2000)
const patch: Operation<any>[] = [
{
op: PatchOp.REPLACE,
path: `/packageData/${params.package}/hosts/${params.host}/privateDomains`,
value: [],
},
{
op: PatchOp.REMOVE,

View File

@@ -1,6 +1,6 @@
import { DataModel } from 'src/app/services/patch-db/data-model'
import { Mock } from './api.fixures'
import { knownACME } from 'src/app/utils/acme'
import { knownAuthorities } from 'src/app/utils/acme'
const version = require('../../../../../../package.json').version
export const mockPatchData: DataModel = {
@@ -28,7 +28,7 @@ export const mockPatchData: DataModel = {
lastRegion: null,
},
acme: {
[knownACME[0].url]: {
[knownAuthorities[0].url]: {
contact: ['mailto:support@start9.com'],
},
},
@@ -39,7 +39,8 @@ export const mockPatchData: DataModel = {
net: {
assignedPort: null,
assignedSslPort: 443,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
preferredExternalPort: 80,
@@ -51,13 +52,14 @@ export const mockPatchData: DataModel = {
},
},
},
domains: {},
publicDomains: {},
privateDomains: [],
onions: ['myveryownspecialtoraddress'],
hostnameInfo: {
80: [
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'local',
@@ -68,7 +70,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'local',
@@ -79,7 +81,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'ipv4',
@@ -90,7 +92,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'ipv4',
@@ -101,7 +103,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'ipv6',
@@ -113,7 +115,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'ipv6',
@@ -134,22 +136,26 @@ export const mockPatchData: DataModel = {
],
},
},
networkInterfaces: {
gateways: {
eth0: {
inbound: false,
outbound: true,
name: null,
public: null,
secure: null,
ipInfo: {
name: 'Wired Connection 1',
scopeId: 1,
deviceType: 'ethernet',
subnets: ['10.0.0.2/24'],
wanIp: null,
wanIp: '203.0.113.45',
ntpServers: [],
lanIp: ['10.0.2.12'],
dnsServers: [],
},
},
wlan0: {
inbound: false,
outbound: true,
name: null,
public: null,
secure: null,
ipInfo: {
name: 'Wireless Connection 1',
scopeId: 2,
@@ -158,10 +164,34 @@ export const mockPatchData: DataModel = {
'10.0.90.12/24',
'fe80::cd00:0000:0cde:1257:0000:211e:72cd/64',
],
wanIp: null,
wanIp: '203.0.113.45',
ntpServers: [],
lanIp: ['10.0.90.12'],
dnsServers: ['8.8.8.8'],
},
},
wireguard1: {
name: 'StartTunnel',
public: null,
secure: null,
ipInfo: {
name: 'wireguard1',
scopeId: 2,
deviceType: 'wireguard',
subnets: [
'10.0.90.12/24',
'fe80::cd00:0000:0cde:1257:0000:211e:72cd/64',
],
wanIp: '203.0.113.45',
ntpServers: [],
lanIp: ['10.0.90.12'],
dnsServers: ['1.1.1.1'],
},
},
},
dns: {
dhcpServers: ['1.1.1.1', '8.8.8.8'],
staticServers: null,
},
},
unreadNotificationCount: 4,
@@ -200,7 +230,7 @@ export const mockPatchData: DataModel = {
},
},
s9pk: '/media/startos/data/package-data/archive/installed/asdfasdf.s9pk',
icon: '/assets/img/service-icons/bitcoind.svg',
icon: '/assets/img/service-icons/bitcoin-core.svg',
lastBackup: new Date(new Date().valueOf() - 604800001).toISOString(),
status: {
main: 'stopped',
@@ -309,7 +339,8 @@ export const mockPatchData: DataModel = {
net: {
assignedPort: 80,
assignedSslPort: 443,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
addSsl: null,
@@ -318,13 +349,14 @@ export const mockPatchData: DataModel = {
},
},
},
publicDomains: {},
privateDomains: [],
onions: [],
domains: {},
hostnameInfo: {
80: [
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'local',
@@ -335,7 +367,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'local',
@@ -346,7 +378,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'ipv4',
@@ -357,7 +389,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'ipv4',
@@ -368,7 +400,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'eth0',
gatewayId: 'eth0',
public: false,
hostname: {
kind: 'ipv6',
@@ -380,7 +412,7 @@ export const mockPatchData: DataModel = {
},
{
kind: 'ip',
networkInterfaceId: 'wlan0',
gatewayId: 'wlan0',
public: false,
hostname: {
kind: 'ipv6',
@@ -408,7 +440,8 @@ export const mockPatchData: DataModel = {
net: {
assignedPort: 8332,
assignedSslPort: null,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
addSsl: null,
@@ -417,8 +450,9 @@ export const mockPatchData: DataModel = {
},
},
},
publicDomains: {},
privateDomains: [],
onions: [],
domains: {},
hostnameInfo: {
8332: [],
},
@@ -430,7 +464,8 @@ export const mockPatchData: DataModel = {
net: {
assignedPort: 8333,
assignedSslPort: null,
public: false,
publicEnabled: [],
privateDisabled: [],
},
options: {
addSsl: null,
@@ -439,8 +474,9 @@ export const mockPatchData: DataModel = {
},
},
},
publicDomains: {},
privateDomains: [],
onions: [],
domains: {},
hostnameInfo: {
8333: [],
},
@@ -557,15 +593,15 @@ export const mockPatchData: DataModel = {
},
currentDependencies: {
bitcoind: {
title: 'Bitcoin Core',
icon: 'assets/img/service-icons/bitcoind.svg',
title: Mock.BitcoinDep.title,
icon: Mock.BitcoinDep.icon,
kind: 'running',
versionRange: '>=26.0.0',
healthChecks: [],
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
title: Mock.ProxyDep.title,
icon: Mock.ProxyDep.icon,
kind: 'running',
versionRange: '>2.0.0',
healthChecks: [],

View File

@@ -1,7 +1,6 @@
import { Inject, Injectable, DOCUMENT } from '@angular/core'
import { WorkspaceConfig } from '@start9labs/shared'
import { T, utils } from '@start9labs/start-sdk'
import { PackageDataEntry } from './patch-db/data-model'
const {
gitHash,
@@ -32,25 +31,23 @@ export class ConfigService {
return useMocks ? mocks.maskAs === 'tor' : this.hostname.endsWith('.onion')
}
isLocal(): boolean {
return useMocks
? mocks.maskAs === 'local'
: this.hostname.endsWith('.local')
}
isLocalhost(): boolean {
return useMocks
? mocks.maskAs === 'localhost'
: this.hostname === 'localhost' || this.hostname === '127.0.0.1'
}
isIpv4(): boolean {
return useMocks
? mocks.maskAs === 'ipv4'
: new RegExp(utils.Patterns.ipv4.regex).test(this.hostname)
isLanHttp(): boolean {
return !this.isTor() && !this.isLocalhost() && !this.isHttps()
}
isLanIpv4(): boolean {
private isLocal(): boolean {
return useMocks
? mocks.maskAs === 'local'
: this.hostname.endsWith('.local')
}
private isLanIpv4(): boolean {
return useMocks
? mocks.maskAs === 'ipv4'
: new RegExp(utils.Patterns.ipv4.regex).test(this.hostname) &&
@@ -79,177 +76,7 @@ export class ConfigService {
!this.isIpv6()
}
isLanHttp(): boolean {
return !this.isTor() && !this.isLocalhost() && !this.isHttps()
}
isHttps(): boolean {
return useMocks ? mocks.maskAsHttps : this.protocol === 'https:'
}
isSecure(): boolean {
return window.isSecureContext || this.isTor()
}
isLaunchable(
state: T.PackageState['state'],
status: T.MainStatus['main'],
): boolean {
return state === 'installed' && status === 'running'
}
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
launchableAddress(ui: T.ServiceInterface, hosts: T.Hosts): string {
const host = hosts[ui.addressInfo.hostId]
if (!host) return ''
let hostnameInfo = host.hostnameInfo[ui.addressInfo.internalPort]
hostnameInfo =
hostnameInfo?.filter(
h =>
this.isLocalhost() ||
h.kind !== 'ip' ||
h.hostname.kind !== 'ipv6' ||
!h.hostname.value.startsWith('fe80::'),
) || []
if (this.isLocalhost()) {
const local = hostnameInfo.find(
h => h.kind === 'ip' && h.hostname.kind === 'local',
)
if (local) {
hostnameInfo.unshift({
kind: 'ip',
networkInterfaceId: 'lo',
public: false,
hostname: {
kind: 'local',
port: local.hostname.port,
sslPort: local.hostname.sslPort,
value: 'localhost',
},
})
}
}
if (!hostnameInfo) return ''
const addressInfo = ui.addressInfo
const username = addressInfo.username ? addressInfo.username + '@' : ''
const suffix = addressInfo.suffix || ''
const url = new URL(`https://${username}placeholder${suffix}`)
const use = (hostname: {
value: string
port: number | null
sslPort: number | null
}) => {
url.hostname = hostname.value
const useSsl =
hostname.port && hostname.sslPort ? this.isHttps() : !!hostname.sslPort
url.protocol = useSsl
? `${addressInfo.sslScheme || 'https'}:`
: `${addressInfo.scheme || 'http'}:`
const port = useSsl ? hostname.sslPort : hostname.port
const omitPort = useSsl
? ui.addressInfo.sslScheme === 'https' && port === 443
: ui.addressInfo.scheme === 'http' && port === 80
if (!omitPort && port) url.port = String(port)
}
const useFirst = (
hostnames: (
| {
value: string
port: number | null
sslPort: number | null
}
| undefined
)[],
) => {
const first = hostnames.find(h => h)
if (first) {
use(first)
}
return !!first
}
const ipHostnames = hostnameInfo
.filter(h => h.kind === 'ip')
.map(h => h.hostname) as T.IpHostname[]
const domainHostname = ipHostnames
.filter(h => h.kind === 'domain')
.map(h => h as T.IpHostname & { kind: 'domain' })
.map(h => ({
value: h.domain,
sslPort: h.sslPort,
port: h.port,
}))[0]
const wanIpHostname = hostnameInfo
.filter(h => h.kind === 'ip' && h.public && h.hostname.kind !== 'domain')
.map(h => h.hostname as Exclude<T.IpHostname, { kind: 'domain' }>)
.map(h => ({
value: h.value,
sslPort: h.sslPort,
port: h.port,
}))[0]
const onionHostname = hostnameInfo
.filter(h => h.kind === 'onion')
.map(h => h as T.HostnameInfo & { kind: 'onion' })
.map(h => ({
value: h.hostname.value,
sslPort: h.hostname.sslPort,
port: h.hostname.port,
}))[0]
const localHostname = ipHostnames
.filter(h => h.kind === 'local')
.map(h => h as T.IpHostname & { kind: 'local' })
.map(h => ({ value: h.value, sslPort: h.sslPort, port: h.port }))[0]
if (this.isClearnet()) {
if (
!useFirst([domainHostname, wanIpHostname, onionHostname, localHostname])
) {
return ''
}
} else if (this.isTor()) {
if (
!useFirst([onionHostname, domainHostname, wanIpHostname, localHostname])
) {
return ''
}
} else if (this.isIpv6()) {
const ipv6Hostname = ipHostnames.find(h => h.kind === 'ipv6') as {
kind: 'ipv6'
value: string
scopeId: number
port: number | null
sslPort: number | null
}
if (!useFirst([ipv6Hostname, localHostname])) {
return ''
}
} else {
// ipv4 or .local or localhost
if (!localHostname) return ''
use({
value: this.hostname,
port: localHostname.port,
sslPort: localHostname.sslPort,
})
}
return url.href
}
getHost(): string {
return this.host
}
}
export function hasUi(
interfaces: PackageDataEntry['serviceInterfaces'],
): boolean {
return Object.values(interfaces).some(iface => iface.type === 'ui')
}

View File

@@ -116,7 +116,7 @@ export class ControlsService {
private alert(content: i18nKey): Promise<boolean> {
return firstValueFrom(
this.dialog
.openConfirm<boolean>({
.openConfirm({
label: 'Warning',
size: 's',
data: {

View File

@@ -10,11 +10,46 @@ import {
import deepEqual from 'fast-deep-equal'
import { Observable } from 'rxjs'
import { isInstalled } from 'src/app/utils/get-package-data'
import { DependencyError } from './api/api.types'
import { T } from '@start9labs/start-sdk'
export type AllDependencyErrors = Record<string, PkgDependencyErrors>
export type PkgDependencyErrors = Record<string, DependencyError | null>
export type DependencyError =
| DependencyErrorNotInstalled
| DependencyErrorNotRunning
| DependencyErrorIncorrectVersion
| DependencyErrorTaskRequired
| DependencyErrorHealthChecksFailed
| DependencyErrorTransitive
export type DependencyErrorNotInstalled = {
type: 'notInstalled'
}
export type DependencyErrorNotRunning = {
type: 'notRunning'
}
export type DependencyErrorIncorrectVersion = {
type: 'incorrectVersion'
expected: string // version range
received: string // version
}
export interface DependencyErrorTaskRequired {
type: 'taskRequired'
}
export type DependencyErrorHealthChecksFailed = {
type: 'healthChecksFailed'
check: T.NamedHealthCheckResult
}
export type DependencyErrorTransitive = {
type: 'transitive'
}
@Injectable({
providedIn: 'root',
})
@@ -103,15 +138,17 @@ export class DepErrorService {
// action required
if (
Object.values(pkg.tasks).some(
t =>
t.active &&
t.task.packageId === depId &&
t.task.severity === 'critical',
)
Object.values(pkg.tasks)
.filter(t => !!t)
.some(
t =>
t.active &&
t.task.packageId === depId &&
t.task.severity === 'critical',
)
) {
return {
type: 'actionRequired',
type: 'taskRequired',
}
}

View File

@@ -8,6 +8,7 @@ import {
Validators,
} from '@angular/forms'
import { IST, utils } from '@start9labs/start-sdk'
import { tuiCreateFileFormatValidator } from '@taiga-ui/kit'
import Mustache from 'mustache'
@Injectable({
@@ -138,6 +139,10 @@ export class FormService {
case 'multiselect':
value = currentValue === undefined ? spec.default : currentValue
return this.formBuilder.control(value, multiselectValidators(spec))
case 'hidden':
return this.formBuilder.control(currentValue || null)
case 'file':
return this.formBuilder.control(null, fileValidators(spec))
default:
return this.formBuilder.control(null)
}
@@ -236,6 +241,18 @@ function multiselectValidators(spec: IST.ValueSpecMultiselect): ValidatorFn[] {
return validators
}
function fileValidators(spec: IST.ValueSpecFile): ValidatorFn[] {
const validators: ValidatorFn[] = [
tuiCreateFileFormatValidator(spec.extensions.join(',')),
]
if (spec.required) {
validators.push(Validators.required)
}
return validators
}
function listValidators(spec: IST.ValueSpecList): ValidatorFn[] {
const validators: ValidatorFn[] = []
validators.push(listInRange(spec.minLength, spec.maxLength))

View File

@@ -0,0 +1,45 @@
import { inject, Injectable } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import { T, utils } from '@start9labs/start-sdk'
import { map } from 'rxjs/operators'
import { DataModel } from './patch-db/data-model'
import { toSignal } from '@angular/core/rxjs-interop'
export type GatewayPlus = T.NetworkInterfaceInfo & {
id: string
name: string
ipInfo: T.IpInfo
subnets: utils.IpNet[]
lanIpv4: string[]
wanIp?: utils.IpAddress
public: boolean
}
@Injectable()
export class GatewayService {
readonly gateways = toSignal(
inject<PatchDB<DataModel>>(PatchDB)
.watch$('serverInfo', 'network', 'gateways')
.pipe(
map(gateways =>
Object.entries(gateways)
.filter(([_, val]) => !!val?.ipInfo)
.map(([id, val]) => {
const subnets =
val.ipInfo?.subnets.map(s => utils.IpNet.parse(s)) ?? []
const name = val.name ?? val.ipInfo!.name
return {
...val,
id,
name,
subnets,
lanIpv4: subnets.filter(s => s.isIpv4()).map(s => s.address),
public: val.public ?? subnets.some(s => s.isPublic()),
wanIp:
val.ipInfo?.wanIp && utils.IpAddress.parse(val.ipInfo?.wanIp),
} as GatewayPlus
}),
),
),
)
}

View File

@@ -115,7 +115,6 @@ export class MarketplaceService {
flavor: string | null,
registryUrl?: string,
): Observable<MarketplacePkg> {
console.log('HERE')
return this.currentRegistry$.pipe(
switchMap(registry => {
const url = registryUrl || registry.url
@@ -147,7 +146,7 @@ export class MarketplaceService {
}
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {
console.warn('FETCHING REGISTRY: ', url)
console.log('FETCHING REGISTRY: ', url)
return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe(
map(([info, packages]) => ({ info, packages, url })),
catchError(e => {

View File

@@ -1,7 +1,10 @@
import { Languages } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
export type DataModel = T.Public & { ui: UIData; packageData: AllPackageData }
export type DataModel = T.Public & {
ui: UIData
packageData: AllPackageData
}
export type UIData = {
name: string | null
@@ -11,22 +14,6 @@ export type UIData = {
language: Languages
}
export type NetworkInfo = T.NetworkInfo & {
// @TODO 041
// start9To: {
// subdomain: string
// networkInterfaceId: string
// } | null
// domains: {
// [key: string]: Domain
// }
// wanConfig: {
// upnp: boolean
// forwards: PortForward[]
// }
// outboundProxy: string | null
}
export type PackageDataEntry<T extends StateInfo = StateInfo> =
T.PackageDataEntry & {
stateInfo: T

View File

@@ -28,7 +28,7 @@ export function getInstalledPrimaryStatus({
return Object.values(tasks).some(
t => t.active && t.task.severity === 'critical',
)
? 'actionRequired'
? 'taskRequired'
: status.main
}
@@ -37,7 +37,7 @@ function getHealthStatus(status: T.MainStatus): T.HealthStatus | null {
return null
}
const values = Object.values(status.health)
const values = Object.values(status.health).filter(h => !!h)
if (values.some(h => h.result === 'failure')) {
return 'failure'
@@ -71,7 +71,7 @@ export type PrimaryStatus =
| 'restarting'
| 'stopped'
| 'backingUp'
| 'actionRequired'
| 'taskRequired'
| 'error'
export type DependencyStatus = 'warning' | 'satisfied'
@@ -127,7 +127,7 @@ export const PrimaryRendering: Record<PrimaryStatus, StatusRendering> = {
color: 'success',
showDots: false,
},
actionRequired: {
taskRequired: {
display: 'Task Required',
color: 'warning',
showDots: false,

View File

@@ -1,5 +1,3 @@
// @TODO 041
// import { Injectable } from '@angular/core'
// import { ErrorService, LoadingService } from '@start9labs/shared'
// import { TuiDialogOptions } from '@taiga-ui/core'
@@ -28,14 +26,14 @@
// ) {}
// async presentModalSetOutboundProxy(current: string | null, pkgId?: string) {
// const networkInterfaces = await firstValueFrom(
// this.patch.watch$('serverInfo', 'network', 'networkInterfaces'),
// const gateways = await firstValueFrom(
// this.patch.watch$('serverInfo', 'network', 'gateways'),
// )
// const config = ISB.InputSpec.of({
// proxyId: ISB.Value.select({
// name: 'Select Proxy',
// default: current || '',
// values: Object.entries(networkInterfaces)
// values: Object.entries(gateways)
// .filter(
// ([_, n]) => n.outbound && n.ipInfo?.deviceType === 'wireguard',
// )
@@ -52,7 +50,7 @@
// const options: Partial<
// TuiDialogOptions<FormContext<typeof config.validator._TYPE>>
// > = {
// label: 'Outbound Proxy',
// label: 'Outbound proxy',
// data: {
// spec: await configBuilderToSpec(config),
// buttons: [