mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
start sorting addresses
This commit is contained in:
@@ -37,7 +37,7 @@ export class CAWizardComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
launchHttps() {
|
launchHttps() {
|
||||||
this.document.defaultView?.open(`https://${this.config.getHost()}`, '_self')
|
this.document.defaultView?.open(`https://${this.config.host}`, '_self')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async testHttps() {
|
private async testHttps() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { InterfaceGateway } from './interface.utils'
|
|||||||
[showIcons]="false"
|
[showIcons]="false"
|
||||||
[ngModel]="gateway.enabled"
|
[ngModel]="gateway.enabled"
|
||||||
(ngModelChange)="onToggle(gateway)"
|
(ngModelChange)="onToggle(gateway)"
|
||||||
[disabled]="osUi() && !gateway.public"
|
[disabled]="isOs() && !gateway.public"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ import { InterfaceGateway } from './interface.utils'
|
|||||||
})
|
})
|
||||||
export class InterfaceGatewaysComponent {
|
export class InterfaceGatewaysComponent {
|
||||||
readonly gateways = input.required<InterfaceGateway[]>()
|
readonly gateways = input.required<InterfaceGateway[]>()
|
||||||
readonly osUi = input.required<boolean>()
|
readonly isOs = input.required<boolean>()
|
||||||
|
|
||||||
async onToggle(gateway: InterfaceGateway) {}
|
async onToggle(gateway: InterfaceGateway) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { InterfaceAddressesComponent } from './addresses/addresses.component'
|
|||||||
template: `
|
template: `
|
||||||
<!-- @TODO Alex / Matt translation in all nested components -->
|
<!-- @TODO Alex / Matt translation in all nested components -->
|
||||||
<div [style.display]="'grid'">
|
<div [style.display]="'grid'">
|
||||||
<section [gateways]="value().gateways" [osUi]="osUi()"></section>
|
<section [gateways]="value().gateways" [isOs]="value().isOs"></section>
|
||||||
<section [torDomains]="value().torDomains"></section>
|
<section [torDomains]="value().torDomains"></section>
|
||||||
<section [clearnetDomains]="value().clearnetDomains"></section>
|
<section [clearnetDomains]="value().clearnetDomains"></section>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,5 +48,4 @@ export class InterfaceComponent {
|
|||||||
readonly packageId = input('')
|
readonly packageId = input('')
|
||||||
readonly value = input.required<MappedServiceInterface>()
|
readonly value = input.required<MappedServiceInterface>()
|
||||||
readonly isRunning = input.required<boolean>()
|
readonly isRunning = input.required<boolean>()
|
||||||
readonly osUi = input(false)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,113 +1,245 @@
|
|||||||
import { T } from '@start9labs/start-sdk'
|
import { inject, Injectable } from '@angular/core'
|
||||||
|
import { T, utils } from '@start9labs/start-sdk'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
|
||||||
export function getAddresses(
|
type AddressWithInfo = {
|
||||||
serviceInterface: T.ServiceInterface,
|
address: URL
|
||||||
host: T.Host,
|
info: T.HostnameInfo
|
||||||
config: ConfigService,
|
}
|
||||||
): MappedServiceInterface['addresses'] {
|
|
||||||
const addressInfo = serviceInterface.addressInfo
|
|
||||||
const hostnames =
|
|
||||||
host.hostnameInfo[addressInfo.internalPort]?.filter(
|
|
||||||
h =>
|
|
||||||
config.isLocalhost() ||
|
|
||||||
h.kind !== 'ip' ||
|
|
||||||
h.hostname.kind !== 'ipv6' ||
|
|
||||||
!h.hostname.value.startsWith('fe80::'),
|
|
||||||
) || []
|
|
||||||
|
|
||||||
if (config.isLocalhost()) {
|
function filterTor(a: AddressWithInfo): a is (AddressWithInfo & { info: { kind: 'onion' } }) {
|
||||||
const local = hostnames.find(
|
return a.info.kind === 'onion'
|
||||||
h => h.kind === 'ip' && h.hostname.kind === 'local',
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if (local) {
|
function cmpTor(a: AddressWithInfo, b: AddressWithInfo): -1 | 0 | 1 {
|
||||||
hostnames.unshift({
|
if (!filterTor(a) || !filterTor(b)) return 0
|
||||||
kind: 'ip',
|
for (let [x, y, sign] of [[a, b, 1] as const, [b, a, -1] as const]) {
|
||||||
gatewayId: 'lo',
|
if (x.address.protocol === 'http:' && y.address.protocol === 'https:')
|
||||||
public: false,
|
return sign
|
||||||
hostname: {
|
}
|
||||||
kind: 'local',
|
return 0
|
||||||
port: local.hostname.port,
|
}
|
||||||
sslPort: local.hostname.sslPort,
|
|
||||||
value: 'localhost',
|
function filterLan(a: AddressWithInfo): a is (AddressWithInfo & { info: { kind: 'ip', public: false } }) {
|
||||||
},
|
return a.info.kind === 'ip' && !a.info.public
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmpLan(host: T.Host, a: AddressWithInfo, b: AddressWithInfo): -1 | 0 | 1 {
|
||||||
|
if (!filterLan(a) || !filterLan(b)) return 0
|
||||||
|
for (let [x, y, sign] of [[a, b, 1] as const, [b, a, -1] as const]) {
|
||||||
|
if (x.info.kind === 'domain' && host.domains.)
|
||||||
|
return sign
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class InterfaceService {
|
||||||
|
private readonly config = inject(ConfigService)
|
||||||
|
|
||||||
|
getAddresses(
|
||||||
|
serviceInterface: T.ServiceInterface,
|
||||||
|
host: T.Host,
|
||||||
|
): MappedServiceInterface['addresses'] {
|
||||||
|
const hostnamesInfos = this.hostnameInfo(serviceInterface, host)
|
||||||
|
|
||||||
|
const addresses = {
|
||||||
|
common: [],
|
||||||
|
uncommon: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hostnamesInfos.length) return addresses
|
||||||
|
|
||||||
|
hostnamesInfos.forEach(h => {
|
||||||
|
const addresses = utils.addressHostToUrl(serviceInterface.addressInfo, h)
|
||||||
|
|
||||||
|
addresses.forEach(url => {
|
||||||
|
if (h.kind === 'onion') {
|
||||||
|
tor.push({
|
||||||
|
protocol: /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(url)
|
||||||
|
? new URL(url).protocol.replace(':', '').toUpperCase()
|
||||||
|
: null,
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const hostnameKind = h.hostname.kind
|
||||||
|
|
||||||
|
if (
|
||||||
|
h.public ||
|
||||||
|
(hostnameKind === 'domain' &&
|
||||||
|
host.domains[h.hostname.domain]?.public)
|
||||||
|
) {
|
||||||
|
clearnet.push({
|
||||||
|
url,
|
||||||
|
disabled: !h.public,
|
||||||
|
isDomain: hostnameKind == 'domain',
|
||||||
|
authority:
|
||||||
|
hostnameKind == 'domain'
|
||||||
|
? host.domains[h.hostname.domain]?.acme || null
|
||||||
|
: null,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
local.push({
|
||||||
|
nid:
|
||||||
|
hostnameKind === 'local'
|
||||||
|
? 'Local'
|
||||||
|
: `${h.gatewayId} (${hostnameKind})`,
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
common: common.filter(
|
||||||
|
(value, index, self) =>
|
||||||
|
index === self.findIndex(t => t.url === value.url),
|
||||||
|
),
|
||||||
|
uncommon: uncommon.filter(
|
||||||
|
(value, index, self) =>
|
||||||
|
index === self.findIndex(t => t.url === value.url),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const common: Address[] = [
|
/** ${scheme}://${username}@${host}:${externalPort}${suffix} */
|
||||||
{
|
launchableAddress(ui: T.ServiceInterface, host: T.Host): string {
|
||||||
type: 'Local',
|
const hostnameInfos = this.hostnameInfo(ui, host)
|
||||||
description: '',
|
|
||||||
gateway: 'Wired Connection 1',
|
|
||||||
url: 'https://test.local:1234',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'IPv4 (LAN)',
|
|
||||||
description: '',
|
|
||||||
gateway: 'Wired Connection 1',
|
|
||||||
url: 'https://192.168.1.10.local:1234',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const uncommon: Address[] = [
|
|
||||||
{
|
|
||||||
type: 'IPv4 (WAN)',
|
|
||||||
description: '',
|
|
||||||
gateway: 'Wired Connection 1',
|
|
||||||
url: 'https://72.72.72.72',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// hostnames.forEach(h => {
|
if (!hostnameInfos.length) return ''
|
||||||
// const addresses = utils.addressHostToUrl(addressInfo, h)
|
|
||||||
|
|
||||||
// addresses.forEach(url => {
|
const addressInfo = ui.addressInfo
|
||||||
// if (h.kind === 'onion') {
|
const username = addressInfo.username ? addressInfo.username + '@' : ''
|
||||||
// tor.push({
|
const suffix = addressInfo.suffix || ''
|
||||||
// protocol: /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(url)
|
const url = new URL(`https://${username}placeholder${suffix}`)
|
||||||
// ? new URL(url).protocol.replace(':', '').toUpperCase()
|
const use = (hostname: {
|
||||||
// : null,
|
value: string
|
||||||
// url,
|
port: number | null
|
||||||
// })
|
sslPort: number | null
|
||||||
// } else {
|
}) => {
|
||||||
// const hostnameKind = h.hostname.kind
|
url.hostname = hostname.value
|
||||||
|
const useSsl =
|
||||||
|
hostname.port && hostname.sslPort
|
||||||
|
? this.config.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
|
||||||
|
}
|
||||||
|
|
||||||
// if (
|
const ipHostnames = hostnameInfos
|
||||||
// h.public ||
|
.filter(h => h.kind === 'ip')
|
||||||
// (hostnameKind === 'domain' && host.domains[h.hostname.domain]?.public)
|
.map(h => h.hostname) as T.IpHostname[]
|
||||||
// ) {
|
const domainHostname = ipHostnames
|
||||||
// clearnet.push({
|
.filter(h => h.kind === 'domain')
|
||||||
// url,
|
.map(h => h as T.IpHostname & { kind: 'domain' })
|
||||||
// disabled: !h.public,
|
.map(h => ({
|
||||||
// isDomain: hostnameKind == 'domain',
|
value: h.domain,
|
||||||
// authority:
|
sslPort: h.sslPort,
|
||||||
// hostnameKind == 'domain'
|
port: h.port,
|
||||||
// ? host.domains[h.hostname.domain]?.acme || null
|
}))[0]
|
||||||
// : null,
|
const wanIpHostname = hostnameInfos
|
||||||
// })
|
.filter(h => h.kind === 'ip' && h.public && h.hostname.kind !== 'domain')
|
||||||
// } else {
|
.map(h => h.hostname as Exclude<T.IpHostname, { kind: 'domain' }>)
|
||||||
// local.push({
|
.map(h => ({
|
||||||
// nid:
|
value: h.value,
|
||||||
// hostnameKind === 'local'
|
sslPort: h.sslPort,
|
||||||
// ? 'Local'
|
port: h.port,
|
||||||
// : `${h.gatewayId} (${hostnameKind})`,
|
}))[0]
|
||||||
// url,
|
const onionHostname = hostnameInfos
|
||||||
// })
|
.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]
|
||||||
|
|
||||||
return {
|
if (this.config.isClearnet()) {
|
||||||
common: common.filter(
|
if (
|
||||||
(value, index, self) =>
|
!useFirst([domainHostname, wanIpHostname, onionHostname, localHostname])
|
||||||
index === self.findIndex(t => t.url === value.url),
|
) {
|
||||||
),
|
return ''
|
||||||
uncommon: uncommon.filter(
|
}
|
||||||
(value, index, self) =>
|
} else if (this.config.isTor()) {
|
||||||
index === self.findIndex(t => t.url === value.url),
|
if (
|
||||||
),
|
!useFirst([onionHostname, domainHostname, wanIpHostname, localHostname])
|
||||||
|
) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} else if (this.config.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.config.hostname,
|
||||||
|
port: localHostname.port,
|
||||||
|
sslPort: localHostname.sslPort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.href
|
||||||
|
}
|
||||||
|
|
||||||
|
private hostnameInfo(
|
||||||
|
serviceInterface: T.ServiceInterface,
|
||||||
|
host: T.Host,
|
||||||
|
): T.HostnameInfo[] {
|
||||||
|
let hostnameInfo =
|
||||||
|
host.hostnameInfo[serviceInterface.addressInfo.internalPort]
|
||||||
|
return (
|
||||||
|
hostnameInfo?.filter(
|
||||||
|
h =>
|
||||||
|
this.config.isLocalhost() ||
|
||||||
|
!(
|
||||||
|
h.kind === 'ip' &&
|
||||||
|
((h.hostname.kind === 'ipv6' &&
|
||||||
|
h.hostname.value.startsWith('fe80::')) ||
|
||||||
|
h.gatewayId === 'lo')
|
||||||
|
),
|
||||||
|
) || []
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +251,7 @@ export type MappedServiceInterface = T.ServiceInterface & {
|
|||||||
common: Address[]
|
common: Address[]
|
||||||
uncommon: Address[]
|
uncommon: Address[]
|
||||||
}
|
}
|
||||||
|
isOs: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InterfaceGateway = {
|
export type InterfaceGateway = {
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ export class ServiceInterfaceItemComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get href() {
|
get href() {
|
||||||
return this.config.launchableAddress(this.info, this.pkg.hosts)
|
const host = this.pkg.hosts[this.info.addressInfo.hostId]
|
||||||
|
if (!host) return ''
|
||||||
|
return this.config.launchableAddress(this.info, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
openUI() {
|
openUI() {
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ export class UILaunchComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getHref(ui: T.ServiceInterface): string {
|
getHref(ui: T.ServiceInterface): string {
|
||||||
return this.config.launchableAddress(ui, this.pkg.hosts)
|
const host = this.pkg.hosts[ui.addressInfo.hostId]
|
||||||
|
if (!host) return ''
|
||||||
|
return this.config.launchableAddress(ui, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
openUI(ui: T.ServiceInterface) {
|
openUI(ui: T.ServiceInterface) {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { TuiBadge, TuiBreadcrumbs } from '@taiga-ui/kit'
|
|||||||
import { TuiHeader } from '@taiga-ui/layout'
|
import { TuiHeader } from '@taiga-ui/layout'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
||||||
import { getAddresses } from 'src/app/routes/portal/components/interfaces/interface.utils'
|
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { TitleDirective } from 'src/app/services/title.service'
|
import { TitleDirective } from 'src/app/services/title.service'
|
||||||
@@ -120,10 +119,11 @@ export default class ServiceInterfaceRoute {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
addresses: getAddresses(item, host, this.config),
|
addresses: this.config.getAddresses(item, host),
|
||||||
gateways: [],
|
gateways: [],
|
||||||
torDomains: [],
|
torDomains: [],
|
||||||
clearnetDomains: [],
|
clearnetDomains: [],
|
||||||
|
isOs: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { TitleDirective } from 'src/app/services/title.service'
|
|||||||
</hgroup>
|
</hgroup>
|
||||||
</header>
|
</header>
|
||||||
@if (ui(); as ui) {
|
@if (ui(); as ui) {
|
||||||
<service-interface [value]="ui" [isRunning]="true" [osUi]="true" />
|
<service-interface [value]="ui" [isRunning]="true" />
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
host: { class: 'g-subpage' },
|
host: { class: 'g-subpage' },
|
||||||
@@ -90,6 +90,7 @@ export default class StartOsUiComponent {
|
|||||||
],
|
],
|
||||||
torDomains: [],
|
torDomains: [],
|
||||||
clearnetDomains: [],
|
clearnetDomains: [],
|
||||||
|
isOs: true,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Inject, Injectable, DOCUMENT } from '@angular/core'
|
import { Inject, Injectable, DOCUMENT } from '@angular/core'
|
||||||
import { WorkspaceConfig } from '@start9labs/shared'
|
import { WorkspaceConfig } from '@start9labs/shared'
|
||||||
import { T, utils } from '@start9labs/start-sdk'
|
import { T, utils } from '@start9labs/start-sdk'
|
||||||
import { PackageDataEntry } from './patch-db/data-model'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
gitHash,
|
gitHash,
|
||||||
@@ -32,25 +31,23 @@ export class ConfigService {
|
|||||||
return useMocks ? mocks.maskAs === 'tor' : this.hostname.endsWith('.onion')
|
return useMocks ? mocks.maskAs === 'tor' : this.hostname.endsWith('.onion')
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocal(): boolean {
|
|
||||||
return useMocks
|
|
||||||
? mocks.maskAs === 'local'
|
|
||||||
: this.hostname.endsWith('.local')
|
|
||||||
}
|
|
||||||
|
|
||||||
isLocalhost(): boolean {
|
isLocalhost(): boolean {
|
||||||
return useMocks
|
return useMocks
|
||||||
? mocks.maskAs === 'localhost'
|
? mocks.maskAs === 'localhost'
|
||||||
: this.hostname === 'localhost' || this.hostname === '127.0.0.1'
|
: this.hostname === 'localhost' || this.hostname === '127.0.0.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
isIpv4(): boolean {
|
isLanHttp(): boolean {
|
||||||
return useMocks
|
return !this.isTor() && !this.isLocalhost() && !this.isHttps()
|
||||||
? mocks.maskAs === 'ipv4'
|
|
||||||
: new RegExp(utils.Patterns.ipv4.regex).test(this.hostname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isLanIpv4(): boolean {
|
private isLocal(): boolean {
|
||||||
|
return useMocks
|
||||||
|
? mocks.maskAs === 'local'
|
||||||
|
: this.hostname.endsWith('.local')
|
||||||
|
}
|
||||||
|
|
||||||
|
private isLanIpv4(): boolean {
|
||||||
return useMocks
|
return useMocks
|
||||||
? mocks.maskAs === 'ipv4'
|
? mocks.maskAs === 'ipv4'
|
||||||
: new RegExp(utils.Patterns.ipv4.regex).test(this.hostname) &&
|
: new RegExp(utils.Patterns.ipv4.regex).test(this.hostname) &&
|
||||||
@@ -79,177 +76,7 @@ export class ConfigService {
|
|||||||
!this.isIpv6()
|
!this.isIpv6()
|
||||||
}
|
}
|
||||||
|
|
||||||
isLanHttp(): boolean {
|
|
||||||
return !this.isTor() && !this.isLocalhost() && !this.isHttps()
|
|
||||||
}
|
|
||||||
|
|
||||||
isHttps(): boolean {
|
isHttps(): boolean {
|
||||||
return useMocks ? mocks.maskAsHttps : this.protocol === 'https:'
|
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',
|
|
||||||
gatewayId: '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')
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user