mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
feat: inline domain health checks and improve address UX
- addPublicDomain returns DNS query + port check results (AddPublicDomainRes) so frontend skips separate API calls after adding a domain - addPrivateDomain returns check_dns result for the gateway - Support multiple ports per domain in validation modal (deduplicated) - Run port checks concurrently via futures::future::join_all - Add note to add-domain dialog showing other interfaces on same host - Add addXForwardedHeaders to knownProtocols in SDK Host.ts - Add plugin filter kind, pluginId filter, matchesAny, and docs to getServiceInterface.ts - Add PassthroughInfo type and passthroughs field to NetworkInfo - Pluralize "port forwarding rules" in i18n dictionaries
This commit is contained in:
@@ -31,6 +31,7 @@ export interface FormContext<T> {
|
||||
buttons: ActionButton<T>[]
|
||||
value?: T
|
||||
operations?: Operation[]
|
||||
note?: string
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -43,6 +44,9 @@ export interface FormContext<T> {
|
||||
(tuiValueChanges)="markAsDirty()"
|
||||
>
|
||||
<form-group [spec]="spec" />
|
||||
@if (note) {
|
||||
<p class="note">{{ note }}</p>
|
||||
}
|
||||
<footer>
|
||||
<ng-content />
|
||||
@for (button of buttons; track $index) {
|
||||
@@ -70,6 +74,12 @@ export interface FormContext<T> {
|
||||
</form>
|
||||
`,
|
||||
styles: `
|
||||
.note {
|
||||
color: var(--tui-text-secondary);
|
||||
font: var(--tui-font-text-s);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
@@ -106,6 +116,7 @@ export class FormComponent<T extends Record<string, any>> implements OnInit {
|
||||
@Input() buttons = this.context?.data.buttons || []
|
||||
@Input() operations = this.context?.data.operations || []
|
||||
@Input() value?: T = this.context?.data.value
|
||||
@Input() note = this.context?.data.note || ''
|
||||
|
||||
form = new FormGroup({})
|
||||
|
||||
|
||||
@@ -185,11 +185,32 @@ export class InterfaceAddressesComponent {
|
||||
: {}),
|
||||
})
|
||||
|
||||
let note = ''
|
||||
const pkgId = this.packageId()
|
||||
if (pkgId) {
|
||||
const pkg = await firstValueFrom(
|
||||
this.patch.watch$('packageData', pkgId),
|
||||
)
|
||||
if (pkg) {
|
||||
const hostId = iface.addressInfo.hostId
|
||||
const otherNames = Object.values(pkg.serviceInterfaces)
|
||||
.filter(
|
||||
si =>
|
||||
si.addressInfo.hostId === hostId && si.id !== iface.id,
|
||||
)
|
||||
.map(si => si.name)
|
||||
if (otherNames.length) {
|
||||
note = `This domain also applies to ${otherNames.join(', ')}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Add public domain',
|
||||
size: 's',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(addSpec),
|
||||
note,
|
||||
buttons: [
|
||||
{
|
||||
text: this.i18n.transform('Save')!,
|
||||
@@ -207,18 +228,22 @@ export class InterfaceAddressesComponent {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
let configured: boolean
|
||||
if (this.packageId()) {
|
||||
await this.api.pkgAddPrivateDomain({
|
||||
configured = await this.api.pkgAddPrivateDomain({
|
||||
fqdn,
|
||||
gateway: gatewayId,
|
||||
package: this.packageId(),
|
||||
host: iface?.addressInfo.hostId || '',
|
||||
})
|
||||
} else {
|
||||
await this.api.osUiAddPrivateDomain({ fqdn, gateway: gatewayId })
|
||||
configured = await this.api.osUiAddPrivateDomain({
|
||||
fqdn,
|
||||
gateway: gatewayId,
|
||||
})
|
||||
}
|
||||
|
||||
await this.domainHealth.checkPrivateDomain(gatewayId)
|
||||
await this.domainHealth.checkPrivateDomain(gatewayId, configured)
|
||||
|
||||
return true
|
||||
} catch (e: any) {
|
||||
@@ -244,23 +269,18 @@ export class InterfaceAddressesComponent {
|
||||
}
|
||||
|
||||
try {
|
||||
let res
|
||||
if (this.packageId()) {
|
||||
await this.api.pkgAddPublicDomain({
|
||||
res = await this.api.pkgAddPublicDomain({
|
||||
...params,
|
||||
package: this.packageId(),
|
||||
host: iface?.addressInfo.hostId || '',
|
||||
})
|
||||
} else {
|
||||
await this.api.osUiAddPublicDomain(params)
|
||||
res = await this.api.osUiAddPublicDomain(params)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
await this.domainHealth.checkPublicDomain(fqdn, gatewayId, res)
|
||||
|
||||
return true
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from '@taiga-ui/kit'
|
||||
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 { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
@@ -29,8 +28,11 @@ export type DnsGateway = T.NetworkInterfaceInfo & {
|
||||
export type DomainValidationData = {
|
||||
fqdn: string
|
||||
gateway: DnsGateway
|
||||
port: number
|
||||
initialResults?: { dnsPass: boolean; portResult: T.CheckPortRes | null }
|
||||
ports: number[]
|
||||
initialResults?: {
|
||||
dnsPass: boolean
|
||||
portResults: (T.CheckPortRes | null)[]
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -92,32 +94,50 @@ export type DomainValidationData = {
|
||||
<h2>{{ 'Port Forwarding' | i18n }}</h2>
|
||||
<p>
|
||||
{{ 'In your gateway' | i18n }} "{{ gatewayName }}",
|
||||
{{ 'create this port forwarding rule' | i18n }}
|
||||
{{ 'create these port forwarding rules' | i18n }}
|
||||
</p>
|
||||
|
||||
@let portRes = portResult();
|
||||
|
||||
<table [appTable]="[null, 'External Port', 'Internal Port', null]">
|
||||
<tr>
|
||||
<td class="status">
|
||||
<port-check-icon [result]="portRes" [loading]="portLoading()" />
|
||||
</td>
|
||||
<td>{{ context.data.port }}</td>
|
||||
<td>{{ context.data.port }}</td>
|
||||
<td>
|
||||
<button
|
||||
tuiButton
|
||||
size="s"
|
||||
[loading]="portLoading()"
|
||||
(click)="testPort()"
|
||||
>
|
||||
{{ 'Test' | i18n }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@for (port of context.data.ports; track port; let i = $index) {
|
||||
<tr>
|
||||
<td class="status">
|
||||
<port-check-icon
|
||||
[result]="portResults()[i]"
|
||||
[loading]="!!portLoadings()[i]"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ port }}</td>
|
||||
<td>{{ port }}</td>
|
||||
<td>
|
||||
<button
|
||||
tuiButton
|
||||
size="s"
|
||||
[loading]="!!portLoadings()[i]"
|
||||
(click)="testPort(i)"
|
||||
>
|
||||
{{ 'Test' | i18n }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
|
||||
<port-check-warnings [result]="portRes" />
|
||||
@if (anyNotRunning()) {
|
||||
<p class="g-warning">
|
||||
{{
|
||||
'Port status cannot be determined while service is not running'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
@if (anyNoHairpinning()) {
|
||||
<p class="g-warning">
|
||||
{{
|
||||
'This address will not work from your local network due to a router hairpinning limitation'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (!isManualMode) {
|
||||
<footer class="g-buttons padding-top">
|
||||
@@ -216,7 +236,6 @@ export type DomainValidationData = {
|
||||
TuiIcon,
|
||||
TuiLoader,
|
||||
PortCheckIconComponent,
|
||||
PortCheckWarningsComponent,
|
||||
],
|
||||
})
|
||||
export class DomainValidationComponent {
|
||||
@@ -232,16 +251,28 @@ export class DomainValidationComponent {
|
||||
parse(this.context.data.fqdn).domain || this.context.data.fqdn
|
||||
|
||||
readonly dnsLoading = signal(false)
|
||||
readonly portLoading = signal(false)
|
||||
readonly portLoadings = signal<boolean[]>(
|
||||
this.context.data.ports.map(() => false),
|
||||
)
|
||||
readonly dnsPass = signal<boolean | undefined>(undefined)
|
||||
readonly portResult = signal<T.CheckPortRes | undefined>(undefined)
|
||||
readonly portResults = signal<(T.CheckPortRes | undefined)[]>(
|
||||
this.context.data.ports.map(() => undefined),
|
||||
)
|
||||
|
||||
readonly anyNotRunning = computed(() =>
|
||||
this.portResults().some(r => r && !r.openInternally),
|
||||
)
|
||||
|
||||
readonly anyNoHairpinning = computed(() =>
|
||||
this.portResults().some(r => r && r.openExternally && !r.hairpinning),
|
||||
)
|
||||
|
||||
readonly allPass = computed(() => {
|
||||
const result = this.portResult()
|
||||
const results = this.portResults()
|
||||
return (
|
||||
this.dnsPass() === true &&
|
||||
!!result?.openInternally &&
|
||||
!!result?.openExternally
|
||||
results.length > 0 &&
|
||||
results.every(r => !!r?.openInternally && !!r?.openExternally)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -251,7 +282,9 @@ export class DomainValidationComponent {
|
||||
const initial = this.context.data.initialResults
|
||||
if (initial) {
|
||||
this.dnsPass.set(initial.dnsPass)
|
||||
if (initial.portResult) this.portResult.set(initial.portResult)
|
||||
this.portResults.set(
|
||||
initial.portResults.map(r => r ?? undefined),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,20 +304,32 @@ export class DomainValidationComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async testPort() {
|
||||
this.portLoading.set(true)
|
||||
async testPort(index: number) {
|
||||
this.portLoadings.update(l => {
|
||||
const copy = [...l]
|
||||
copy[index] = true
|
||||
return copy
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await this.api.checkPort({
|
||||
gateway: this.context.data.gateway.id,
|
||||
port: this.context.data.port,
|
||||
port: this.context.data.ports[index]!,
|
||||
})
|
||||
|
||||
this.portResult.set(result)
|
||||
this.portResults.update(r => {
|
||||
const copy = [...r]
|
||||
copy[index] = result
|
||||
return copy
|
||||
})
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
this.portLoading.set(false)
|
||||
this.portLoadings.update(l => {
|
||||
const copy = [...l]
|
||||
copy[index] = false
|
||||
return copy
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,33 +19,45 @@ export class DomainHealthService {
|
||||
async checkPublicDomain(
|
||||
fqdn: string,
|
||||
gatewayId: string,
|
||||
port: number,
|
||||
portOrRes: number | T.AddPublicDomainRes,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const gateway = await this.getGatewayData(gatewayId)
|
||||
if (!gateway) return
|
||||
|
||||
const [dnsPass, portResult] = await Promise.all([
|
||||
this.api
|
||||
.queryDns({ fqdn })
|
||||
.then(ip => ip === gateway.ipInfo.wanIp)
|
||||
.catch(() => false),
|
||||
this.api
|
||||
.checkPort({ gateway: gatewayId, port })
|
||||
.catch((): null => null),
|
||||
])
|
||||
let dnsPass: boolean
|
||||
let ports: number[]
|
||||
let portResults: (T.CheckPortRes | null)[]
|
||||
|
||||
const portOk =
|
||||
!!portResult?.openInternally &&
|
||||
!!portResult?.openExternally &&
|
||||
!!portResult?.hairpinning
|
||||
if (typeof portOrRes === 'number') {
|
||||
ports = [portOrRes]
|
||||
const [dns, portResult] = await Promise.all([
|
||||
this.api
|
||||
.queryDns({ fqdn })
|
||||
.then(ip => ip === gateway.ipInfo.wanIp)
|
||||
.catch(() => false),
|
||||
this.api
|
||||
.checkPort({ gateway: gatewayId, port: portOrRes })
|
||||
.catch((): null => null),
|
||||
])
|
||||
dnsPass = dns
|
||||
portResults = [portResult]
|
||||
} else {
|
||||
dnsPass = portOrRes.dns === gateway.ipInfo.wanIp
|
||||
ports = portOrRes.port.map(r => r.port)
|
||||
portResults = portOrRes.port
|
||||
}
|
||||
|
||||
if (!dnsPass || !portOk) {
|
||||
const allPortsOk = portResults.every(
|
||||
r => !!r?.openInternally && !!r?.openExternally && !!r?.hairpinning,
|
||||
)
|
||||
|
||||
if (!dnsPass || !allPortsOk) {
|
||||
setTimeout(
|
||||
() =>
|
||||
this.openPublicDomainModal(fqdn, gateway, port, {
|
||||
this.openPublicDomainModal(fqdn, gateway, ports, {
|
||||
dnsPass,
|
||||
portResult,
|
||||
portResults,
|
||||
}),
|
||||
250,
|
||||
)
|
||||
@@ -55,14 +67,17 @@ export class DomainHealthService {
|
||||
}
|
||||
}
|
||||
|
||||
async checkPrivateDomain(gatewayId: string): Promise<void> {
|
||||
async checkPrivateDomain(
|
||||
gatewayId: string,
|
||||
prefetchedConfigured?: boolean,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const gateway = await this.getGatewayData(gatewayId)
|
||||
if (!gateway) return
|
||||
|
||||
const configured = await this.api
|
||||
.checkDns({ gateway: gatewayId })
|
||||
.catch(() => false)
|
||||
const configured =
|
||||
prefetchedConfigured ??
|
||||
(await this.api.checkDns({ gateway: gatewayId }).catch(() => false))
|
||||
|
||||
if (!configured) {
|
||||
setTimeout(
|
||||
@@ -84,7 +99,7 @@ export class DomainHealthService {
|
||||
const gateway = await this.getGatewayData(gatewayId)
|
||||
if (!gateway) return
|
||||
|
||||
this.openPublicDomainModal(fqdn, gateway, port)
|
||||
this.openPublicDomainModal(fqdn, gateway, [port])
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
}
|
||||
@@ -149,14 +164,17 @@ export class DomainHealthService {
|
||||
private openPublicDomainModal(
|
||||
fqdn: string,
|
||||
gateway: DnsGateway,
|
||||
port: number,
|
||||
initialResults?: { dnsPass: boolean; portResult: T.CheckPortRes | null },
|
||||
ports: number[],
|
||||
initialResults?: {
|
||||
dnsPass: boolean
|
||||
portResults: (T.CheckPortRes | null)[]
|
||||
},
|
||||
) {
|
||||
this.dialog
|
||||
.openComponent(DOMAIN_VALIDATION, {
|
||||
label: 'Address Requirements',
|
||||
size: 'm',
|
||||
data: { fqdn, gateway, port, initialResults },
|
||||
data: { fqdn, gateway, ports, initialResults },
|
||||
})
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export type PortForwardValidationData = {
|
||||
<h2>{{ 'Port Forwarding' | i18n }}</h2>
|
||||
<p>
|
||||
{{ 'In your gateway' | i18n }} "{{ gatewayName }}",
|
||||
{{ 'create this port forwarding rule' | i18n }}
|
||||
{{ 'create these port forwarding rules' | i18n }}
|
||||
</p>
|
||||
|
||||
@let portRes = portResult();
|
||||
|
||||
@@ -340,11 +340,13 @@ export abstract class ApiService {
|
||||
|
||||
abstract osUiAddPublicDomain(
|
||||
params: T.AddPublicDomainParams,
|
||||
): Promise<string | null>
|
||||
): Promise<T.AddPublicDomainRes>
|
||||
|
||||
abstract osUiRemovePublicDomain(params: T.RemoveDomainParams): Promise<null>
|
||||
|
||||
abstract osUiAddPrivateDomain(params: T.AddPrivateDomainParams): Promise<null>
|
||||
abstract osUiAddPrivateDomain(
|
||||
params: T.AddPrivateDomainParams,
|
||||
): Promise<boolean>
|
||||
|
||||
abstract osUiRemovePrivateDomain(params: T.RemoveDomainParams): Promise<null>
|
||||
|
||||
@@ -354,13 +356,15 @@ export abstract class ApiService {
|
||||
|
||||
abstract pkgAddPublicDomain(
|
||||
params: PkgAddPublicDomainReq,
|
||||
): Promise<string | null>
|
||||
): Promise<T.AddPublicDomainRes>
|
||||
|
||||
abstract pkgRemovePublicDomain(
|
||||
params: PkgRemovePublicDomainReq,
|
||||
): Promise<null>
|
||||
|
||||
abstract pkgAddPrivateDomain(params: PkgAddPrivateDomainReq): Promise<null>
|
||||
abstract pkgAddPrivateDomain(
|
||||
params: PkgAddPrivateDomainReq,
|
||||
): Promise<boolean>
|
||||
|
||||
abstract pkgRemovePrivateDomain(
|
||||
params: PkgRemovePrivateDomainReq,
|
||||
|
||||
@@ -630,7 +630,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
async osUiAddPublicDomain(
|
||||
params: T.AddPublicDomainParams,
|
||||
): Promise<string | null> {
|
||||
): Promise<T.AddPublicDomainRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'server.host.address.domain.public.add',
|
||||
params,
|
||||
@@ -644,7 +644,9 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async osUiAddPrivateDomain(params: T.AddPrivateDomainParams): Promise<null> {
|
||||
async osUiAddPrivateDomain(
|
||||
params: T.AddPrivateDomainParams,
|
||||
): Promise<boolean> {
|
||||
return this.rpcRequest({
|
||||
method: 'server.host.address.domain.private.add',
|
||||
params,
|
||||
@@ -669,7 +671,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
async pkgAddPublicDomain(
|
||||
params: PkgAddPublicDomainReq,
|
||||
): Promise<string | null> {
|
||||
): Promise<T.AddPublicDomainRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'package.host.address.domain.public.add',
|
||||
params,
|
||||
@@ -683,7 +685,9 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async pkgAddPrivateDomain(params: PkgAddPrivateDomainReq): Promise<null> {
|
||||
async pkgAddPrivateDomain(
|
||||
params: PkgAddPrivateDomainReq,
|
||||
): Promise<boolean> {
|
||||
return this.rpcRequest({
|
||||
method: 'package.host.address.domain.private.add',
|
||||
params,
|
||||
|
||||
@@ -1440,7 +1440,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async osUiAddPublicDomain(
|
||||
params: T.AddPublicDomainParams,
|
||||
): Promise<string | null> {
|
||||
): Promise<T.AddPublicDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: Operation<any>[] = [
|
||||
@@ -1465,7 +1465,18 @@ export class MockApiService extends ApiService {
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
return {
|
||||
dns: null,
|
||||
port: [
|
||||
{
|
||||
ip: '0.0.0.0',
|
||||
port: 443,
|
||||
openExternally: false,
|
||||
openInternally: false,
|
||||
hairpinning: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async osUiRemovePublicDomain(params: T.RemoveDomainParams): Promise<null> {
|
||||
@@ -1482,7 +1493,9 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async osUiAddPrivateDomain(params: T.AddPrivateDomainParams): Promise<null> {
|
||||
async osUiAddPrivateDomain(
|
||||
params: T.AddPrivateDomainParams,
|
||||
): Promise<boolean> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: Operation<any>[] = [
|
||||
@@ -1505,7 +1518,7 @@ export class MockApiService extends ApiService {
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
return false
|
||||
}
|
||||
|
||||
async osUiRemovePrivateDomain(params: T.RemoveDomainParams): Promise<null> {
|
||||
@@ -1535,7 +1548,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async pkgAddPublicDomain(
|
||||
params: PkgAddPublicDomainReq,
|
||||
): Promise<string | null> {
|
||||
): Promise<T.AddPublicDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: Operation<any>[] = [
|
||||
@@ -1560,7 +1573,18 @@ export class MockApiService extends ApiService {
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
return {
|
||||
dns: null,
|
||||
port: [
|
||||
{
|
||||
ip: '0.0.0.0',
|
||||
port: 443,
|
||||
openExternally: false,
|
||||
openInternally: false,
|
||||
hairpinning: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async pkgRemovePublicDomain(params: PkgRemovePublicDomainReq): Promise<null> {
|
||||
@@ -1577,7 +1601,9 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async pkgAddPrivateDomain(params: PkgAddPrivateDomainReq): Promise<null> {
|
||||
async pkgAddPrivateDomain(
|
||||
params: PkgAddPrivateDomainReq,
|
||||
): Promise<boolean> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: Operation<any>[] = [
|
||||
@@ -1600,7 +1626,7 @@ export class MockApiService extends ApiService {
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
return false
|
||||
}
|
||||
|
||||
async pkgRemovePrivateDomain(
|
||||
|
||||
@@ -212,6 +212,7 @@ export const mockPatchData: DataModel = {
|
||||
},
|
||||
},
|
||||
},
|
||||
passthroughs: [],
|
||||
defaultOutbound: 'eth0',
|
||||
dns: {
|
||||
dhcpServers: ['1.1.1.1', '8.8.8.8'],
|
||||
|
||||
Reference in New Issue
Block a user