mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
multiple bugs and better port forward ux
This commit is contained in:
@@ -701,4 +701,7 @@ export default {
|
|||||||
771: 'Spiel vorbei',
|
771: 'Spiel vorbei',
|
||||||
772: 'Beliebige Taste drücken oder tippen zum Starten',
|
772: 'Beliebige Taste drücken oder tippen zum Starten',
|
||||||
773: 'Beliebige Taste drücken oder tippen zum Neustarten',
|
773: 'Beliebige Taste drücken oder tippen zum Neustarten',
|
||||||
|
774: 'Der Portstatus kann nicht ermittelt werden, solange der Dienst nicht läuft',
|
||||||
|
775: 'Diese Adresse funktioniert nicht aus Ihrem lokalen Netzwerk aufgrund einer Router-Hairpinning-Einschränkung',
|
||||||
|
776: 'Aktion nicht gefunden',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -701,4 +701,7 @@ export const ENGLISH: Record<string, number> = {
|
|||||||
'Game Over': 771,
|
'Game Over': 771,
|
||||||
'Press any key or tap to start': 772,
|
'Press any key or tap to start': 772,
|
||||||
'Press any key or tap to play again': 773,
|
'Press any key or tap to play again': 773,
|
||||||
|
'Port status cannot be determined while service is not running': 774,
|
||||||
|
'This address will not work from your local network due to a router hairpinning limitation': 775,
|
||||||
|
'Action not found': 776,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -701,4 +701,7 @@ export default {
|
|||||||
771: 'Fin del juego',
|
771: 'Fin del juego',
|
||||||
772: 'Pulsa cualquier tecla o toca para empezar',
|
772: 'Pulsa cualquier tecla o toca para empezar',
|
||||||
773: 'Pulsa cualquier tecla o toca para jugar de nuevo',
|
773: 'Pulsa cualquier tecla o toca para jugar de nuevo',
|
||||||
|
774: 'El estado del puerto no se puede determinar mientras el servicio no está en ejecución',
|
||||||
|
775: 'Esta dirección no funcionará desde tu red local debido a una limitación de hairpinning del router',
|
||||||
|
776: 'Acción no encontrada',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -701,4 +701,7 @@ export default {
|
|||||||
771: 'Partie terminée',
|
771: 'Partie terminée',
|
||||||
772: "Appuyez sur une touche ou touchez l'écran pour commencer",
|
772: "Appuyez sur une touche ou touchez l'écran pour commencer",
|
||||||
773: "Appuyez sur une touche ou touchez l'écran pour rejouer",
|
773: "Appuyez sur une touche ou touchez l'écran pour rejouer",
|
||||||
|
774: "L'état du port ne peut pas être déterminé tant que le service n'est pas en cours d'exécution",
|
||||||
|
775: "Cette adresse ne fonctionnera pas depuis votre réseau local en raison d'une limitation de hairpinning du routeur",
|
||||||
|
776: 'Action introuvable',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -701,4 +701,7 @@ export default {
|
|||||||
771: 'Koniec gry',
|
771: 'Koniec gry',
|
||||||
772: 'Naciśnij dowolny klawisz lub dotknij, aby rozpocząć',
|
772: 'Naciśnij dowolny klawisz lub dotknij, aby rozpocząć',
|
||||||
773: 'Naciśnij dowolny klawisz lub dotknij, aby zagrać ponownie',
|
773: 'Naciśnij dowolny klawisz lub dotknij, aby zagrać ponownie',
|
||||||
|
774: 'Status portu nie może być określony, gdy usługa nie jest uruchomiona',
|
||||||
|
775: 'Ten adres nie będzie działać z Twojej sieci lokalnej z powodu ograniczenia hairpinning routera',
|
||||||
|
776: 'Nie znaleziono akcji',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -287,9 +287,12 @@ export class AddressActionsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showDnsValidation() {
|
showDnsValidation() {
|
||||||
|
const port = this.address().hostnameInfo.port
|
||||||
|
if (port === null) return
|
||||||
this.domainHealth.showPublicDomainSetup(
|
this.domainHealth.showPublicDomainSetup(
|
||||||
this.address().hostnameInfo.hostname,
|
this.address().hostnameInfo.hostname,
|
||||||
this.gatewayId(),
|
this.gatewayId(),
|
||||||
|
port,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,14 +171,18 @@ export class InterfaceAddressesComponent {
|
|||||||
default: null,
|
default: null,
|
||||||
patterns: [utils.Patterns.domain],
|
patterns: [utils.Patterns.domain],
|
||||||
}).map(f => f.toLocaleLowerCase()),
|
}).map(f => f.toLocaleLowerCase()),
|
||||||
authority: ISB.Value.select({
|
...(iface.addSsl
|
||||||
name: this.i18n.transform('Certificate Authority'),
|
? {
|
||||||
description: this.i18n.transform(
|
authority: ISB.Value.select({
|
||||||
'Select a Certificate Authority to issue SSL/TLS certificates for this domain',
|
name: this.i18n.transform('Certificate Authority'),
|
||||||
),
|
description: this.i18n.transform(
|
||||||
values: authorities,
|
'Select a Certificate Authority to issue SSL/TLS certificates for this domain',
|
||||||
default: Object.keys(network.acme)[0] || 'local',
|
),
|
||||||
}),
|
values: authorities,
|
||||||
|
default: Object.keys(network.acme)[0] || 'local',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
this.formDialog.open(FormComponent, {
|
this.formDialog.open(FormComponent, {
|
||||||
@@ -250,7 +254,13 @@ export class InterfaceAddressesComponent {
|
|||||||
await this.api.osUiAddPublicDomain(params)
|
await this.api.osUiAddPublicDomain(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.domainHealth.checkPublicDomain(fqdn, gatewayId)
|
const port = this.gatewayGroup().addresses.find(
|
||||||
|
a => a.access === 'public' && a.hostnameInfo.port !== null,
|
||||||
|
)?.hostnameInfo.port
|
||||||
|
|
||||||
|
if (port !== undefined && port !== null) {
|
||||||
|
await this.domainHealth.checkPublicDomain(fqdn, gatewayId, port)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
tuiSwitchOptionsProvider,
|
tuiSwitchOptionsProvider,
|
||||||
} from '@taiga-ui/kit'
|
} from '@taiga-ui/kit'
|
||||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import { PortCheckIconComponent } from 'src/app/routes/portal/components/port-check-icon.component'
|
||||||
|
import { PortCheckWarningsComponent } from 'src/app/routes/portal/components/port-check-warnings.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
@@ -28,7 +30,7 @@ export type DomainValidationData = {
|
|||||||
fqdn: string
|
fqdn: string
|
||||||
gateway: DnsGateway
|
gateway: DnsGateway
|
||||||
port: number
|
port: number
|
||||||
initialResults?: { dnsPass: boolean; portPass: boolean }
|
initialResults?: { dnsPass: boolean; portResult: T.CheckPortRes | null }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -93,18 +95,12 @@ export type DomainValidationData = {
|
|||||||
{{ 'create this port forwarding rule' | i18n }}
|
{{ 'create this port forwarding rule' | i18n }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@let portRes = portResult();
|
||||||
|
|
||||||
<table [appTable]="[null, 'External Port', 'Internal Port', null]">
|
<table [appTable]="[null, 'External Port', 'Internal Port', null]">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="status">
|
<td class="status">
|
||||||
@if (portLoading()) {
|
<port-check-icon [result]="portRes" [loading]="portLoading()" />
|
||||||
<tui-loader size="s" />
|
|
||||||
} @else if (portPass() === true) {
|
|
||||||
<tui-icon class="g-positive" icon="@tui.check" />
|
|
||||||
} @else if (portPass() === false) {
|
|
||||||
<tui-icon class="g-negative" icon="@tui.x" />
|
|
||||||
} @else {
|
|
||||||
<tui-icon class="g-secondary" icon="@tui.minus" />
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ context.data.port }}</td>
|
<td>{{ context.data.port }}</td>
|
||||||
<td>{{ context.data.port }}</td>
|
<td>{{ context.data.port }}</td>
|
||||||
@@ -121,6 +117,8 @@ export type DomainValidationData = {
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<port-check-warnings [result]="portRes" />
|
||||||
|
|
||||||
@if (!isManualMode) {
|
@if (!isManualMode) {
|
||||||
<footer class="g-buttons padding-top">
|
<footer class="g-buttons padding-top">
|
||||||
<button
|
<button
|
||||||
@@ -217,6 +215,8 @@ export type DomainValidationData = {
|
|||||||
TuiButtonLoading,
|
TuiButtonLoading,
|
||||||
TuiIcon,
|
TuiIcon,
|
||||||
TuiLoader,
|
TuiLoader,
|
||||||
|
PortCheckIconComponent,
|
||||||
|
PortCheckWarningsComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DomainValidationComponent {
|
export class DomainValidationComponent {
|
||||||
@@ -234,11 +234,16 @@ export class DomainValidationComponent {
|
|||||||
readonly dnsLoading = signal(false)
|
readonly dnsLoading = signal(false)
|
||||||
readonly portLoading = signal(false)
|
readonly portLoading = signal(false)
|
||||||
readonly dnsPass = signal<boolean | undefined>(undefined)
|
readonly dnsPass = signal<boolean | undefined>(undefined)
|
||||||
readonly portPass = signal<boolean | undefined>(undefined)
|
readonly portResult = signal<T.CheckPortRes | undefined>(undefined)
|
||||||
|
|
||||||
readonly allPass = computed(
|
readonly allPass = computed(() => {
|
||||||
() => this.dnsPass() === true && this.portPass() === true,
|
const result = this.portResult()
|
||||||
)
|
return (
|
||||||
|
this.dnsPass() === true &&
|
||||||
|
!!result?.openInternally &&
|
||||||
|
!!result?.openExternally
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
readonly isManualMode = !this.context.data.initialResults
|
readonly isManualMode = !this.context.data.initialResults
|
||||||
|
|
||||||
@@ -246,7 +251,7 @@ export class DomainValidationComponent {
|
|||||||
const initial = this.context.data.initialResults
|
const initial = this.context.data.initialResults
|
||||||
if (initial) {
|
if (initial) {
|
||||||
this.dnsPass.set(initial.dnsPass)
|
this.dnsPass.set(initial.dnsPass)
|
||||||
this.portPass.set(initial.portPass)
|
if (initial.portResult) this.portResult.set(initial.portResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +280,7 @@ export class DomainValidationComponent {
|
|||||||
port: this.context.data.port,
|
port: this.context.data.port,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.portPass.set(result.reachable)
|
this.portResult.set(result)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { DialogService, ErrorService } from '@start9labs/shared'
|
import { DialogService, ErrorService } from '@start9labs/shared'
|
||||||
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { firstValueFrom } from 'rxjs'
|
import { firstValueFrom } from 'rxjs'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
@@ -15,28 +16,36 @@ export class DomainHealthService {
|
|||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
async checkPublicDomain(fqdn: string, gatewayId: string): Promise<void> {
|
async checkPublicDomain(
|
||||||
|
fqdn: string,
|
||||||
|
gatewayId: string,
|
||||||
|
port: number,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const gateway = await this.getGatewayData(gatewayId)
|
const gateway = await this.getGatewayData(gatewayId)
|
||||||
if (!gateway) return
|
if (!gateway) return
|
||||||
|
|
||||||
const [dnsPass, portPass] = await Promise.all([
|
const [dnsPass, portResult] = await Promise.all([
|
||||||
this.api
|
this.api
|
||||||
.queryDns({ fqdn })
|
.queryDns({ fqdn })
|
||||||
.then(ip => ip === gateway.ipInfo.wanIp)
|
.then(ip => ip === gateway.ipInfo.wanIp)
|
||||||
.catch(() => false),
|
.catch(() => false),
|
||||||
this.api
|
this.api
|
||||||
.checkPort({ gateway: gatewayId, port: 443 })
|
.checkPort({ gateway: gatewayId, port })
|
||||||
.then(r => r.reachable)
|
.catch((): null => null),
|
||||||
.catch(() => false),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!dnsPass || !portPass) {
|
const portOk =
|
||||||
|
!!portResult?.openInternally &&
|
||||||
|
!!portResult?.openExternally &&
|
||||||
|
!!portResult?.hairpinning
|
||||||
|
|
||||||
|
if (!dnsPass || !portOk) {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
this.openPublicDomainModal(fqdn, gateway, 443, {
|
this.openPublicDomainModal(fqdn, gateway, port, {
|
||||||
dnsPass,
|
dnsPass,
|
||||||
portPass,
|
portResult,
|
||||||
}),
|
}),
|
||||||
250,
|
250,
|
||||||
)
|
)
|
||||||
@@ -66,12 +75,16 @@ export class DomainHealthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showPublicDomainSetup(fqdn: string, gatewayId: string): Promise<void> {
|
async showPublicDomainSetup(
|
||||||
|
fqdn: string,
|
||||||
|
gatewayId: string,
|
||||||
|
port: number,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const gateway = await this.getGatewayData(gatewayId)
|
const gateway = await this.getGatewayData(gatewayId)
|
||||||
if (!gateway) return
|
if (!gateway) return
|
||||||
|
|
||||||
this.openPublicDomainModal(fqdn, gateway, 443)
|
this.openPublicDomainModal(fqdn, gateway, port)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
}
|
}
|
||||||
@@ -82,14 +95,18 @@ export class DomainHealthService {
|
|||||||
const gateway = await this.getGatewayData(gatewayId)
|
const gateway = await this.getGatewayData(gatewayId)
|
||||||
if (!gateway) return
|
if (!gateway) return
|
||||||
|
|
||||||
const portPass = await this.api
|
const portResult = await this.api
|
||||||
.checkPort({ gateway: gatewayId, port })
|
.checkPort({ gateway: gatewayId, port })
|
||||||
.then(r => r.reachable)
|
.catch((): null => null)
|
||||||
.catch(() => false)
|
|
||||||
|
|
||||||
if (!portPass) {
|
const portOk =
|
||||||
|
!!portResult?.openInternally &&
|
||||||
|
!!portResult?.openExternally &&
|
||||||
|
!!portResult?.hairpinning
|
||||||
|
|
||||||
|
if (!portOk) {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => this.openPortForwardModal(gateway, port, { portPass }),
|
() => this.openPortForwardModal(gateway, port, { portResult }),
|
||||||
250,
|
250,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -133,7 +150,7 @@ export class DomainHealthService {
|
|||||||
fqdn: string,
|
fqdn: string,
|
||||||
gateway: DnsGateway,
|
gateway: DnsGateway,
|
||||||
port: number,
|
port: number,
|
||||||
initialResults?: { dnsPass: boolean; portPass: boolean },
|
initialResults?: { dnsPass: boolean; portResult: T.CheckPortRes | null },
|
||||||
) {
|
) {
|
||||||
this.dialog
|
this.dialog
|
||||||
.openComponent(DOMAIN_VALIDATION, {
|
.openComponent(DOMAIN_VALIDATION, {
|
||||||
@@ -147,7 +164,7 @@ export class DomainHealthService {
|
|||||||
private openPortForwardModal(
|
private openPortForwardModal(
|
||||||
gateway: DnsGateway,
|
gateway: DnsGateway,
|
||||||
port: number,
|
port: number,
|
||||||
initialResults?: { portPass: boolean },
|
initialResults?: { portResult: T.CheckPortRes | null },
|
||||||
) {
|
) {
|
||||||
this.dialog
|
this.dialog
|
||||||
.openComponent(PORT_FORWARD_VALIDATION, {
|
.openComponent(PORT_FORWARD_VALIDATION, {
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
tuiSwitch
|
tuiSwitch
|
||||||
size="s"
|
size="s"
|
||||||
[showIcons]="false"
|
[showIcons]="false"
|
||||||
[disabled]="toggling() || address.hostnameInfo.metadata.kind === 'mdns'"
|
[disabled]="
|
||||||
|
toggling() || address.hostnameInfo.metadata.kind === 'mdns'
|
||||||
|
"
|
||||||
[ngModel]="address.enabled"
|
[ngModel]="address.enabled"
|
||||||
(ngModelChange)="onToggleEnabled()"
|
(ngModelChange)="onToggleEnabled()"
|
||||||
/>
|
/>
|
||||||
@@ -207,10 +209,11 @@ export class InterfaceAddressItemComponent {
|
|||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
const kind = addr.hostnameInfo.metadata.kind
|
const kind = addr.hostnameInfo.metadata.kind
|
||||||
if (kind === 'public-domain') {
|
if (kind === 'public-domain' && addr.hostnameInfo.port !== null) {
|
||||||
await this.domainHealth.checkPublicDomain(
|
await this.domainHealth.checkPublicDomain(
|
||||||
addr.hostnameInfo.hostname,
|
addr.hostnameInfo.hostname,
|
||||||
this.gatewayId(),
|
this.gatewayId(),
|
||||||
|
addr.hostnameInfo.port,
|
||||||
)
|
)
|
||||||
} else if (kind === 'private-domain') {
|
} else if (kind === 'private-domain') {
|
||||||
await this.domainHealth.checkPrivateDomain(this.gatewayId())
|
await this.domainHealth.checkPrivateDomain(this.gatewayId())
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
|
computed,
|
||||||
inject,
|
inject,
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
||||||
import { TuiButton, TuiDialogContext, TuiIcon, TuiLoader } from '@taiga-ui/core'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
||||||
import { TuiButtonLoading } from '@taiga-ui/kit'
|
import { TuiButtonLoading } from '@taiga-ui/kit'
|
||||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import { PortCheckIconComponent } from 'src/app/routes/portal/components/port-check-icon.component'
|
||||||
|
import { PortCheckWarningsComponent } from 'src/app/routes/portal/components/port-check-warnings.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DnsGateway } from './dns.component'
|
import { DnsGateway } from './dns.component'
|
||||||
@@ -15,7 +19,7 @@ import { DnsGateway } from './dns.component'
|
|||||||
export type PortForwardValidationData = {
|
export type PortForwardValidationData = {
|
||||||
gateway: DnsGateway
|
gateway: DnsGateway
|
||||||
port: number
|
port: number
|
||||||
initialResults?: { portPass: boolean }
|
initialResults?: { portResult: T.CheckPortRes | null }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -30,18 +34,12 @@ export type PortForwardValidationData = {
|
|||||||
{{ 'create this port forwarding rule' | i18n }}
|
{{ 'create this port forwarding rule' | i18n }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@let portRes = portResult();
|
||||||
|
|
||||||
<table [appTable]="[null, 'External Port', 'Internal Port', null]">
|
<table [appTable]="[null, 'External Port', 'Internal Port', null]">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="status">
|
<td class="status">
|
||||||
@if (loading()) {
|
<port-check-icon [result]="portRes" [loading]="loading()" />
|
||||||
<tui-loader size="s" />
|
|
||||||
} @else if (pass() === true) {
|
|
||||||
<tui-icon class="g-positive" icon="@tui.check" />
|
|
||||||
} @else if (pass() === false) {
|
|
||||||
<tui-icon class="g-negative" icon="@tui.x" />
|
|
||||||
} @else {
|
|
||||||
<tui-icon class="g-secondary" icon="@tui.minus" />
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ context.data.port }}</td>
|
<td>{{ context.data.port }}</td>
|
||||||
<td>{{ context.data.port }}</td>
|
<td>{{ context.data.port }}</td>
|
||||||
@@ -53,19 +51,21 @@ export type PortForwardValidationData = {
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<port-check-warnings [result]="portRes" />
|
||||||
|
|
||||||
@if (!isManualMode) {
|
@if (!isManualMode) {
|
||||||
<footer class="g-buttons padding-top">
|
<footer class="g-buttons padding-top">
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
appearance="flat"
|
appearance="flat"
|
||||||
[disabled]="pass() === true"
|
[disabled]="portOk()"
|
||||||
(click)="context.completeWith()"
|
(click)="context.completeWith()"
|
||||||
>
|
>
|
||||||
{{ 'Later' | i18n }}
|
{{ 'Later' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
[disabled]="pass() !== true"
|
[disabled]="!portOk()"
|
||||||
(click)="context.completeWith()"
|
(click)="context.completeWith()"
|
||||||
>
|
>
|
||||||
{{ 'Done' | i18n }}
|
{{ 'Done' | i18n }}
|
||||||
@@ -132,8 +132,8 @@ export type PortForwardValidationData = {
|
|||||||
i18nPipe,
|
i18nPipe,
|
||||||
TableComponent,
|
TableComponent,
|
||||||
TuiButtonLoading,
|
TuiButtonLoading,
|
||||||
TuiIcon,
|
PortCheckIconComponent,
|
||||||
TuiLoader,
|
PortCheckWarningsComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PortForwardValidationComponent {
|
export class PortForwardValidationComponent {
|
||||||
@@ -144,14 +144,23 @@ export class PortForwardValidationComponent {
|
|||||||
injectContext<TuiDialogContext<void, PortForwardValidationData>>()
|
injectContext<TuiDialogContext<void, PortForwardValidationData>>()
|
||||||
|
|
||||||
readonly loading = signal(false)
|
readonly loading = signal(false)
|
||||||
readonly pass = signal<boolean | undefined>(undefined)
|
readonly portResult = signal<T.CheckPortRes | undefined>(undefined)
|
||||||
|
|
||||||
|
readonly portOk = computed(() => {
|
||||||
|
const result = this.portResult()
|
||||||
|
return (
|
||||||
|
!!result?.openInternally &&
|
||||||
|
!!result?.openExternally &&
|
||||||
|
!!result?.hairpinning
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
readonly isManualMode = !this.context.data.initialResults
|
readonly isManualMode = !this.context.data.initialResults
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const initial = this.context.data.initialResults
|
const initial = this.context.data.initialResults
|
||||||
if (initial) {
|
if (initial) {
|
||||||
this.pass.set(initial.portPass)
|
if (initial.portResult) this.portResult.set(initial.portResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +173,7 @@ export class PortForwardValidationComponent {
|
|||||||
port: this.context.data.port,
|
port: this.context.data.port,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pass.set(result.reachable)
|
this.portResult.set(result)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
import { TuiIcon, TuiLoader } from '@taiga-ui/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'port-check-icon',
|
||||||
|
template: `
|
||||||
|
@if (loading()) {
|
||||||
|
<tui-loader size="s" />
|
||||||
|
} @else {
|
||||||
|
@let res = result();
|
||||||
|
@if (res) {
|
||||||
|
@if (!res.openInternally) {
|
||||||
|
<tui-icon class="g-warning" icon="@tui.alert-triangle" />
|
||||||
|
} @else if (!res.openExternally) {
|
||||||
|
<tui-icon class="g-negative" icon="@tui.x" />
|
||||||
|
} @else {
|
||||||
|
<tui-icon class="g-positive" icon="@tui.check" />
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
<tui-icon class="g-secondary" icon="@tui.minus" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
tui-icon {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [TuiIcon, TuiLoader],
|
||||||
|
})
|
||||||
|
export class PortCheckIconComponent {
|
||||||
|
readonly result = input<T.CheckPortRes>()
|
||||||
|
readonly loading = input(false)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'port-check-warnings',
|
||||||
|
template: `
|
||||||
|
@let res = result();
|
||||||
|
@if (res) {
|
||||||
|
@if (!res.openInternally) {
|
||||||
|
<p class="g-warning">
|
||||||
|
{{
|
||||||
|
'Port status cannot be determined while service is not running'
|
||||||
|
| i18n
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
@if (res.openExternally && !res.hairpinning) {
|
||||||
|
<p class="g-warning">
|
||||||
|
{{
|
||||||
|
'This address will not work from your local network due to a router hairpinning limitation'
|
||||||
|
| i18n
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
p {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [i18nPipe],
|
||||||
|
})
|
||||||
|
export class PortCheckWarningsComponent {
|
||||||
|
readonly result = input<T.CheckPortRes>()
|
||||||
|
}
|
||||||
@@ -19,7 +19,12 @@ import { ServiceTasksComponent } from 'src/app/routes/portal/routes/services/com
|
|||||||
import { ActionService } from 'src/app/services/action.service'
|
import { ActionService } from 'src/app/services/action.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { getInstalledBaseStatus } from 'src/app/services/pkg-status-rendering.service'
|
import {
|
||||||
|
ALLOWED_STATUSES,
|
||||||
|
getInstalledBaseStatus,
|
||||||
|
INACTIVE_STATUSES,
|
||||||
|
renderPkgStatus,
|
||||||
|
} from 'src/app/services/pkg-status-rendering.service'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -49,6 +54,9 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
|||||||
</td>
|
</td>
|
||||||
<td class="g-secondary" [style.grid-row]="3">
|
<td class="g-secondary" [style.grid-row]="3">
|
||||||
{{ task().reason || ('No reason provided' | i18n) }}
|
{{ task().reason || ('No reason provided' | i18n) }}
|
||||||
|
@if (disabled()) {
|
||||||
|
<div class="g-warning">{{ disabled() }}</div>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (task().severity !== 'critical') {
|
@if (task().severity !== 'critical') {
|
||||||
@@ -66,7 +74,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
|||||||
tuiIconButton
|
tuiIconButton
|
||||||
iconStart="@tui.play"
|
iconStart="@tui.play"
|
||||||
appearance="primary-success"
|
appearance="primary-success"
|
||||||
[disabled]="!pkg()"
|
[disabled]="!!disabled()"
|
||||||
(click)="handle()"
|
(click)="handle()"
|
||||||
>
|
>
|
||||||
{{ 'Run' | i18n }}
|
{{ 'Run' | i18n }}
|
||||||
@@ -113,7 +121,9 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
host: { '[style.opacity]': 'pkg() ? null : "var(--tui-disabled-opacity)"' },
|
host: {
|
||||||
|
'[style.opacity]': '!disabled() ? null : "var(--tui-disabled-opacity)"',
|
||||||
|
},
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TuiButton, TuiAvatar, i18nPipe, TuiFade],
|
imports: [TuiButton, TuiAvatar, i18nPipe, TuiFade],
|
||||||
})
|
})
|
||||||
@@ -124,6 +134,7 @@ export class ServiceTaskComponent {
|
|||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
private readonly loader = inject(LoadingService)
|
private readonly loader = inject(LoadingService)
|
||||||
private readonly tasks = inject(ServiceTasksComponent)
|
private readonly tasks = inject(ServiceTasksComponent)
|
||||||
|
private readonly i18n = inject(i18nPipe)
|
||||||
|
|
||||||
readonly task = input.required<T.Task & { replayId: string }>()
|
readonly task = input.required<T.Task & { replayId: string }>()
|
||||||
readonly services = input.required<Record<string, PackageDataEntry>>()
|
readonly services = input.required<Record<string, PackageDataEntry>>()
|
||||||
@@ -135,6 +146,28 @@ export class ServiceTaskComponent {
|
|||||||
() => this.tasks.pkg().currentDependencies[this.task().packageId],
|
() => this.tasks.pkg().currentDependencies[this.task().packageId],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
readonly disabled = computed(() => {
|
||||||
|
const pkg = this.pkg()
|
||||||
|
if (!pkg) return this.i18n.transform('Not installed')!
|
||||||
|
|
||||||
|
const action = pkg.actions[this.task().actionId]
|
||||||
|
if (!action) return this.i18n.transform('Action not found')!
|
||||||
|
|
||||||
|
const status = renderPkgStatus(pkg).primary
|
||||||
|
|
||||||
|
if (INACTIVE_STATUSES.includes(status)) return status as string
|
||||||
|
|
||||||
|
if (!ALLOWED_STATUSES[action.allowedStatuses].has(status)) {
|
||||||
|
return `${this.i18n.transform('Action can only be executed when service is')} ${this.i18n.transform(action.allowedStatuses === 'only-running' ? 'Running' : 'Stopped')?.toLowerCase()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof action.visibility === 'object') {
|
||||||
|
return action.visibility.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
async dismiss() {
|
async dismiss() {
|
||||||
const { packageId, replayId } = this.task()
|
const { packageId, replayId } = this.task()
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import { StandardActionsService } from 'src/app/services/standard-actions.servic
|
|||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { ServiceActionComponent } from '../components/action.component'
|
import { ServiceActionComponent } from '../components/action.component'
|
||||||
import {
|
import {
|
||||||
|
ALLOWED_STATUSES,
|
||||||
|
INACTIVE_STATUSES,
|
||||||
PrimaryStatus,
|
PrimaryStatus,
|
||||||
renderPkgStatus,
|
renderPkgStatus,
|
||||||
} from 'src/app/services/pkg-status-rendering.service'
|
} from 'src/app/services/pkg-status-rendering.service'
|
||||||
@@ -30,30 +32,6 @@ import { FormDialogService } from 'src/app/services/form-dialog.service'
|
|||||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||||
|
|
||||||
const INACTIVE: PrimaryStatus[] = [
|
|
||||||
'installing',
|
|
||||||
'updating',
|
|
||||||
'removing',
|
|
||||||
'restoring',
|
|
||||||
'backing-up',
|
|
||||||
'error',
|
|
||||||
]
|
|
||||||
|
|
||||||
const ALLOWED_STATUSES: Record<T.AllowedStatuses, Set<string>> = {
|
|
||||||
'only-running': new Set(['running']),
|
|
||||||
'only-stopped': new Set(['stopped']),
|
|
||||||
any: new Set([
|
|
||||||
'running',
|
|
||||||
'stopped',
|
|
||||||
'restarting',
|
|
||||||
'restoring',
|
|
||||||
'stopping',
|
|
||||||
'starting',
|
|
||||||
'backing-up',
|
|
||||||
'task-required',
|
|
||||||
]),
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@if (package(); as pkg) {
|
@if (package(); as pkg) {
|
||||||
@@ -286,6 +264,6 @@ export default class ServiceActionsRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly isInactive = computed(
|
protected readonly isInactive = computed(
|
||||||
(pkg = this.package()) => !pkg || INACTIVE.includes(pkg.status),
|
(pkg = this.package()) => !pkg || INACTIVE_STATUSES.includes(pkg.status),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import {
|
|||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
||||||
import { TuiButton, TuiDialogContext, TuiIcon, TuiLoader } from '@taiga-ui/core'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
||||||
import { TuiButtonLoading } from '@taiga-ui/kit'
|
import { TuiButtonLoading } from '@taiga-ui/kit'
|
||||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, map } from 'rxjs'
|
import { combineLatest, map } from 'rxjs'
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
|
import { PortCheckIconComponent } from 'src/app/routes/portal/components/port-check-icon.component'
|
||||||
|
import { PortCheckWarningsComponent } from 'src/app/routes/portal/components/port-check-warnings.component'
|
||||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
@@ -58,17 +61,13 @@ function parseSocketAddr(s: string): { ip: string; port: number } {
|
|||||||
@for (iface of row.interfaces; track iface) {
|
@for (iface of row.interfaces; track iface) {
|
||||||
<div>{{ iface }}</div>
|
<div>{{ iface }}</div>
|
||||||
}
|
}
|
||||||
|
<port-check-warnings [result]="results()[i]" />
|
||||||
</td>
|
</td>
|
||||||
<td class="status">
|
<td class="status">
|
||||||
@if (loading()[i]) {
|
<port-check-icon
|
||||||
<tui-loader size="s" />
|
[result]="results()[i]"
|
||||||
} @else if (results()[i] === true) {
|
[loading]="!!loading()[i]"
|
||||||
<tui-icon class="g-positive" icon="@tui.check" />
|
/>
|
||||||
} @else if (results()[i] === false) {
|
|
||||||
<tui-icon class="g-negative" icon="@tui.x" />
|
|
||||||
} @else {
|
|
||||||
<tui-icon class="g-secondary" icon="@tui.minus" />
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ row.externalPort }}</td>
|
<td>{{ row.externalPort }}</td>
|
||||||
<td>{{ row.internalPort }}</td>
|
<td>{{ row.internalPort }}</td>
|
||||||
@@ -103,11 +102,6 @@ function parseSocketAddr(s: string): { ip: string; port: number } {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
tui-icon {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
width: 3.2rem;
|
width: 3.2rem;
|
||||||
}
|
}
|
||||||
@@ -145,8 +139,8 @@ function parseSocketAddr(s: string): { ip: string; port: number } {
|
|||||||
i18nPipe,
|
i18nPipe,
|
||||||
TableComponent,
|
TableComponent,
|
||||||
PlaceholderComponent,
|
PlaceholderComponent,
|
||||||
TuiIcon,
|
PortCheckIconComponent,
|
||||||
TuiLoader,
|
PortCheckWarningsComponent,
|
||||||
TuiButtonLoading,
|
TuiButtonLoading,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -159,7 +153,7 @@ export class PortForwardsModalComponent {
|
|||||||
injectContext<TuiDialogContext<void, PortForwardsModalData>>()
|
injectContext<TuiDialogContext<void, PortForwardsModalData>>()
|
||||||
|
|
||||||
readonly loading = signal<Record<number, boolean>>({})
|
readonly loading = signal<Record<number, boolean>>({})
|
||||||
readonly results = signal<Record<number, boolean>>({})
|
readonly results = signal<Record<number, T.CheckPortRes>>({})
|
||||||
|
|
||||||
private readonly portForwards$ = combineLatest([
|
private readonly portForwards$ = combineLatest([
|
||||||
this.patch.watch$('serverInfo', 'network', 'host', 'portForwards').pipe(
|
this.patch.watch$('serverInfo', 'network', 'host', 'portForwards').pipe(
|
||||||
@@ -254,7 +248,7 @@ export class PortForwardsModalComponent {
|
|||||||
port,
|
port,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.results.update(r => ({ ...r, [index]: result.reachable }))
|
this.results.update(r => ({ ...r, [index]: result }))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { GetPackageRes, GetPackagesRes } from '@start9labs/marketplace'
|
||||||
import {
|
import {
|
||||||
FullKeyboard,
|
FullKeyboard,
|
||||||
pauseFor,
|
pauseFor,
|
||||||
RPCErrorDetails,
|
RPCErrorDetails,
|
||||||
SetLanguageParams,
|
SetLanguageParams,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { ApiService } from './embassy-api.service'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import {
|
import {
|
||||||
AddOperation,
|
AddOperation,
|
||||||
Dump,
|
Dump,
|
||||||
@@ -16,6 +17,8 @@ import {
|
|||||||
ReplaceOperation,
|
ReplaceOperation,
|
||||||
Revision,
|
Revision,
|
||||||
} from 'patch-db-client'
|
} from 'patch-db-client'
|
||||||
|
import { from, interval, map, shareReplay, startWith, Subject, tap } from 'rxjs'
|
||||||
|
import { WebSocketSubject } from 'rxjs/webSocket'
|
||||||
import {
|
import {
|
||||||
DataModel,
|
DataModel,
|
||||||
InstallingState,
|
InstallingState,
|
||||||
@@ -23,7 +26,9 @@ import {
|
|||||||
StateInfo,
|
StateInfo,
|
||||||
UpdatingState,
|
UpdatingState,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { GetPackageRes, GetPackagesRes } from '@start9labs/marketplace'
|
import { toAuthorityUrl } from 'src/app/utils/acme'
|
||||||
|
import { AuthService } from '../auth.service'
|
||||||
|
import { Mock } from './api.fixures'
|
||||||
import {
|
import {
|
||||||
ActionRes,
|
ActionRes,
|
||||||
CheckDnsRes,
|
CheckDnsRes,
|
||||||
@@ -44,13 +49,8 @@ import {
|
|||||||
ServerState,
|
ServerState,
|
||||||
WebsocketConfig,
|
WebsocketConfig,
|
||||||
} from './api.types'
|
} from './api.types'
|
||||||
import { Mock } from './api.fixures'
|
import { ApiService } from './embassy-api.service'
|
||||||
import { from, interval, map, shareReplay, startWith, Subject, tap } from 'rxjs'
|
|
||||||
import { mockPatchData } from './mock-patch'
|
import { mockPatchData } from './mock-patch'
|
||||||
import { AuthService } from '../auth.service'
|
|
||||||
import { T } from '@start9labs/start-sdk'
|
|
||||||
import { WebSocketSubject } from 'rxjs/webSocket'
|
|
||||||
import { toAuthorityUrl } from 'src/app/utils/acme'
|
|
||||||
|
|
||||||
import markdown from './md-sample.md'
|
import markdown from './md-sample.md'
|
||||||
|
|
||||||
@@ -521,7 +521,13 @@ export class MockApiService extends ApiService {
|
|||||||
async checkPort(params: T.CheckPortParams): Promise<T.CheckPortRes> {
|
async checkPort(params: T.CheckPortParams): Promise<T.CheckPortRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
return { ip: '0.0.0.0', port: params.port, reachable: false }
|
return {
|
||||||
|
ip: '0.0.0.0',
|
||||||
|
port: params.port,
|
||||||
|
openExternally: true,
|
||||||
|
openInternally: false,
|
||||||
|
hairpinning: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkDns(params: T.CheckDnsParams): Promise<CheckDnsRes> {
|
async checkDns(params: T.CheckDnsParams): Promise<CheckDnsRes> {
|
||||||
|
|||||||
@@ -2,6 +2,30 @@ import { i18nKey } from '@start9labs/shared'
|
|||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
|
export const INACTIVE_STATUSES: PrimaryStatus[] = [
|
||||||
|
'installing',
|
||||||
|
'updating',
|
||||||
|
'removing',
|
||||||
|
'restoring',
|
||||||
|
'backing-up',
|
||||||
|
'error',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const ALLOWED_STATUSES: Record<T.AllowedStatuses, Set<string>> = {
|
||||||
|
'only-running': new Set(['running']),
|
||||||
|
'only-stopped': new Set(['stopped']),
|
||||||
|
any: new Set([
|
||||||
|
'running',
|
||||||
|
'stopped',
|
||||||
|
'restarting',
|
||||||
|
'restoring',
|
||||||
|
'stopping',
|
||||||
|
'starting',
|
||||||
|
'backing-up',
|
||||||
|
'task-required',
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
export interface PackageStatus {
|
export interface PackageStatus {
|
||||||
primary: PrimaryStatus
|
primary: PrimaryStatus
|
||||||
health: T.HealthStatus | null
|
health: T.HealthStatus | null
|
||||||
|
|||||||
Reference in New Issue
Block a user