mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
round out adding new domains
This commit is contained in:
@@ -2,7 +2,7 @@ import { Config } from '@start9labs/start-sdk/lib/config/builder/config'
|
|||||||
import { Value } from '@start9labs/start-sdk/lib/config/builder/value'
|
import { Value } from '@start9labs/start-sdk/lib/config/builder/value'
|
||||||
import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants'
|
import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants'
|
||||||
|
|
||||||
const ddnsOptions = Config.of({
|
const auth = Config.of({
|
||||||
username: Value.text({
|
username: Value.text({
|
||||||
name: 'Username',
|
name: 'Username',
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
@@ -14,7 +14,46 @@ const ddnsOptions = Config.of({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const domainSpec = Config.of({
|
const strategyUnion = Value.union(
|
||||||
|
{
|
||||||
|
name: 'Networking Strategy',
|
||||||
|
required: { default: 'router' },
|
||||||
|
},
|
||||||
|
Variants.of({
|
||||||
|
router: {
|
||||||
|
name: 'Router',
|
||||||
|
spec: Config.of({
|
||||||
|
ip: Value.select({
|
||||||
|
name: 'IP Strategy',
|
||||||
|
description: `
|
||||||
|
<h5>IPv6 Only</h5><b>Pros</b>: Ready for IPv6 Internet. Enhanced privacy, as IPv6 addresses are less correlated with geographic area
|
||||||
|
<b>Cons</b>: Your website is only accessible to people who's ISP supports IPv6
|
||||||
|
<h5>IPv6 and IPv4</h5><b>Pros</b>: Ready for IPv6 Internet. Anyone can access your website
|
||||||
|
<b>Cons</b>: IPv4 addresses are closely correlated with geographic areas
|
||||||
|
<h5>IPv4 Only</h5><b>Pros</b>: Anyone can access your website
|
||||||
|
<b>Cons</b>: IPv4 addresses are closely correlated with geographic areas
|
||||||
|
`,
|
||||||
|
required: { default: 'ipv6' },
|
||||||
|
values: {
|
||||||
|
ipv6: 'IPv6 Only',
|
||||||
|
both: 'IPv6 and IPv4',
|
||||||
|
ipv4: 'IPv4 Only',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
reverseProxy: {
|
||||||
|
name: 'Reverse Proxy',
|
||||||
|
spec: Config.of({}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const start9MeSpec = Config.of({
|
||||||
|
strategy: strategyUnion,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const customSpec = Config.of({
|
||||||
hostname: Value.text({
|
hostname: Value.text({
|
||||||
name: 'Hostname',
|
name: 'Hostname',
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
@@ -32,30 +71,32 @@ export const domainSpec = Config.of({
|
|||||||
},
|
},
|
||||||
duckdns: {
|
duckdns: {
|
||||||
name: 'Duck DNS',
|
name: 'Duck DNS',
|
||||||
spec: ddnsOptions,
|
spec: auth,
|
||||||
},
|
},
|
||||||
dyn: {
|
dyn: {
|
||||||
name: 'DynDNS',
|
name: 'DynDNS',
|
||||||
spec: ddnsOptions,
|
spec: auth,
|
||||||
},
|
},
|
||||||
easydns: {
|
easydns: {
|
||||||
name: 'easyDNS',
|
name: 'easyDNS',
|
||||||
spec: ddnsOptions,
|
spec: auth,
|
||||||
},
|
},
|
||||||
googledomains: {
|
googledomains: {
|
||||||
name: 'Google Domains',
|
name: 'Google Domains',
|
||||||
spec: ddnsOptions,
|
spec: auth,
|
||||||
},
|
},
|
||||||
namecheap: {
|
namecheap: {
|
||||||
name: 'Namecheap (IPv4 only)',
|
name: 'Namecheap (IPv4 only)',
|
||||||
spec: ddnsOptions,
|
spec: auth,
|
||||||
},
|
},
|
||||||
zoneedit: {
|
zoneedit: {
|
||||||
name: 'Zoneedit',
|
name: 'Zoneedit',
|
||||||
spec: ddnsOptions,
|
spec: auth,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
strategy: strategyUnion,
|
||||||
})
|
})
|
||||||
|
|
||||||
export type DomainSpec = typeof domainSpec.validator._TYPE
|
export type Start9MeSpec = typeof start9MeSpec.validator._TYPE
|
||||||
|
export type CustomSpec = typeof customSpec.validator._TYPE
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
class="ion-padding-start"
|
class="ion-padding-start"
|
||||||
strong
|
strong
|
||||||
size="small"
|
size="small"
|
||||||
(click)="presentAlertClaimStart9MeDomain()"
|
(click)="presentModalClaimStart9Me()"
|
||||||
>
|
>
|
||||||
<ion-icon slot="start" name="add-outline"></ion-icon>
|
<ion-icon slot="start" name="add-outline"></ion-icon>
|
||||||
Claim
|
Claim
|
||||||
@@ -34,20 +34,24 @@
|
|||||||
<div class="grid-fixed">
|
<div class="grid-fixed">
|
||||||
<ion-grid class="ion-padding">
|
<ion-grid class="ion-padding">
|
||||||
<ion-row class="grid-headings">
|
<ion-row class="grid-headings">
|
||||||
<ion-col size="3">Domain</ion-col>
|
<ion-col size="2">Domain</ion-col>
|
||||||
<ion-col size="2.5">Added</ion-col>
|
<ion-col size="2">Added</ion-col>
|
||||||
<ion-col size="2.5">DDNS Provider</ion-col>
|
<ion-col size="2">DDNS Provider</ion-col>
|
||||||
<ion-col size="2">In Use</ion-col>
|
<ion-col size="1.5">Network Strategy</ion-col>
|
||||||
<ion-col size="2"></ion-col>
|
<ion-col size="2">IP Strategy</ion-col>
|
||||||
|
<ion-col size="1.5">In Use</ion-col>
|
||||||
|
<ion-col size="1"></ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
<ion-row
|
<ion-row
|
||||||
*ngIf="domains.start9Me as start9Me"
|
*ngIf="domains.start9Me as start9Me"
|
||||||
class="ion-align-items-center grid-row-border"
|
class="ion-align-items-center grid-row-border"
|
||||||
>
|
>
|
||||||
<ion-col size="3">{{ start9Me.value }}</ion-col>
|
<ion-col size="2">{{ start9Me.value }}</ion-col>
|
||||||
<ion-col size="2.5">{{ start9Me.createdAt| date: 'medium' }}</ion-col>
|
<ion-col size="2">{{ start9Me.createdAt| date: 'short' }}</ion-col>
|
||||||
<ion-col size="2.5">Start9</ion-col>
|
<ion-col size="2">Start9</ion-col>
|
||||||
<ion-col size="2" *ngIf="start9Me.usedBy as usedBy">
|
<ion-col size="1.5">{{ start9Me.networkStrategy }}</ion-col>
|
||||||
|
<ion-col size="2">{{ start9Me.ipStrategy || 'N/A' }}</ion-col>
|
||||||
|
<ion-col size="1.5" *ngIf="start9Me.usedBy as usedBy">
|
||||||
<a
|
<a
|
||||||
*ngIf="usedBy.length as qty; else unused"
|
*ngIf="usedBy.length as qty; else unused"
|
||||||
(click)="presentAlertUsedBy(start9Me.value, usedBy)"
|
(click)="presentAlertUsedBy(start9Me.value, usedBy)"
|
||||||
@@ -58,7 +62,7 @@
|
|||||||
<span>N/A</span>
|
<span>N/A</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col size="2">
|
<ion-col size="1">
|
||||||
<ion-buttons style="float: right">
|
<ion-buttons style="float: right">
|
||||||
<ion-button size="small" (click)="presentAlertDeleteStart9Me()">
|
<ion-button size="small" (click)="presentAlertDeleteStart9Me()">
|
||||||
<ion-icon name="trash"></ion-icon>
|
<ion-icon name="trash"></ion-icon>
|
||||||
@@ -85,20 +89,24 @@
|
|||||||
<div class="grid-fixed">
|
<div class="grid-fixed">
|
||||||
<ion-grid class="ion-padding">
|
<ion-grid class="ion-padding">
|
||||||
<ion-row class="grid-headings">
|
<ion-row class="grid-headings">
|
||||||
<ion-col size="3">Domain</ion-col>
|
<ion-col size="2">Domain</ion-col>
|
||||||
<ion-col size="2.5">Added</ion-col>
|
<ion-col size="2">Added</ion-col>
|
||||||
<ion-col size="2.5">DDNS Provider</ion-col>
|
<ion-col size="2">DDNS Provider</ion-col>
|
||||||
<ion-col size="2">In Use</ion-col>
|
<ion-col size="1.5">Network Strategy</ion-col>
|
||||||
<ion-col size="2"></ion-col>
|
<ion-col size="2">IP Strategy</ion-col>
|
||||||
|
<ion-col size="1.5">In Use</ion-col>
|
||||||
|
<ion-col size="1"></ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
<ion-row
|
<ion-row
|
||||||
*ngFor="let domain of domains.custom"
|
*ngFor="let domain of domains.custom"
|
||||||
class="ion-align-items-center grid-row-border"
|
class="ion-align-items-center grid-row-border"
|
||||||
>
|
>
|
||||||
<ion-col size="3">{{ domain.value }}</ion-col>
|
<ion-col size="2">{{ domain.value }}</ion-col>
|
||||||
<ion-col size="2.5">{{ domain.createdAt| date: 'medium' }}</ion-col>
|
<ion-col size="2">{{ domain.createdAt| date: 'short' }}</ion-col>
|
||||||
<ion-col size="2.5">{{ domain.provider.unionSelectKey }}</ion-col>
|
<ion-col size="2">{{ domain.provider }}</ion-col>
|
||||||
<ion-col size="2" *ngIf="domain.usedBy as usedBy">
|
<ion-col size="1.5">{{ domain.networkStrategy }}</ion-col>
|
||||||
|
<ion-col size="2">{{ domain.ipStrategy || 'N/A' }}</ion-col>
|
||||||
|
<ion-col size="1.5" *ngIf="domain.usedBy as usedBy">
|
||||||
<a
|
<a
|
||||||
*ngIf="usedBy.length as qty; else unused"
|
*ngIf="usedBy.length as qty; else unused"
|
||||||
(click)="presentAlertUsedBy(domain.value, usedBy)"
|
(click)="presentAlertUsedBy(domain.value, usedBy)"
|
||||||
@@ -109,7 +117,7 @@
|
|||||||
<span>N/A</span>
|
<span>N/A</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col size="2">
|
<ion-col size="1">
|
||||||
<ion-buttons style="float: right">
|
<ion-buttons style="float: right">
|
||||||
<ion-button
|
<ion-button
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import { PatchDB } from 'patch-db-client'
|
|||||||
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'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { DomainSpec, domainSpec } from './domain.const'
|
import {
|
||||||
|
start9MeSpec,
|
||||||
|
Start9MeSpec,
|
||||||
|
customSpec,
|
||||||
|
CustomSpec,
|
||||||
|
} from './domain.const'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { FormContext, FormPage } from '../../../modals/form/form.page'
|
import { FormContext, FormPage } from '../../../modals/form/form.page'
|
||||||
import { getClearnetAddress } from 'src/app/util/clearnetAddress'
|
import { getClearnetAddress } from 'src/app/util/clearnetAddress'
|
||||||
@@ -23,7 +28,7 @@ export class DomainsPage {
|
|||||||
readonly server$ = this.patch.watch$('server-info')
|
readonly server$ = this.patch.watch$('server-info')
|
||||||
readonly pkgs$ = this.patch.watch$('package-data').pipe(first())
|
readonly pkgs$ = this.patch.watch$('package-data').pipe(first())
|
||||||
|
|
||||||
readonly domains$ = this.connectionService.connected$.pipe(
|
readonly domains$ = this.connectionService.websocketConnected$.pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
combineLatest([this.server$, this.pkgs$]).pipe(
|
combineLatest([this.server$, this.pkgs$]).pipe(
|
||||||
@@ -35,6 +40,8 @@ export class DomainsPage {
|
|||||||
value: `${start9MeSubdomain.value}.start9.me`,
|
value: `${start9MeSubdomain.value}.start9.me`,
|
||||||
createdAt: start9MeSubdomain.createdAt,
|
createdAt: start9MeSubdomain.createdAt,
|
||||||
provider: 'Start9',
|
provider: 'Start9',
|
||||||
|
networkStrategy: start9MeSubdomain.networkStrategy,
|
||||||
|
ipStrategy: start9MeSubdomain.ipStrategy,
|
||||||
usedBy: usedBy(
|
usedBy: usedBy(
|
||||||
start9MeSubdomain.value,
|
start9MeSubdomain.value,
|
||||||
getClearnetAddress('https', ui.domainInfo),
|
getClearnetAddress('https', ui.domainInfo),
|
||||||
@@ -45,6 +52,8 @@ export class DomainsPage {
|
|||||||
value: domain.value,
|
value: domain.value,
|
||||||
createdAt: domain.createdAt,
|
createdAt: domain.createdAt,
|
||||||
provider: domain.provider,
|
provider: domain.provider,
|
||||||
|
networkStrategy: domain.networkStrategy,
|
||||||
|
ipStrategy: domain.ipStrategy,
|
||||||
usedBy: usedBy(
|
usedBy: usedBy(
|
||||||
domain.value,
|
domain.value,
|
||||||
getClearnetAddress('https', ui.domainInfo),
|
getClearnetAddress('https', ui.domainInfo),
|
||||||
@@ -69,10 +78,10 @@ export class DomainsPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async presentModalAdd() {
|
async presentModalAdd() {
|
||||||
const options: Partial<TuiDialogOptions<FormContext<DomainSpec>>> = {
|
const options: Partial<TuiDialogOptions<FormContext<CustomSpec>>> = {
|
||||||
label: 'Custom Domain',
|
label: 'Custom Domain',
|
||||||
data: {
|
data: {
|
||||||
spec: await domainSpec.build({} as any),
|
spec: await customSpec.build({} as any),
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Save',
|
text: 'Save',
|
||||||
@@ -84,19 +93,20 @@ export class DomainsPage {
|
|||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormPage, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
presentAlertClaimStart9MeDomain() {
|
async presentModalClaimStart9Me() {
|
||||||
this.dialogs
|
const options: Partial<TuiDialogOptions<FormContext<Start9MeSpec>>> = {
|
||||||
.open(TUI_PROMPT, {
|
label: 'start9.me',
|
||||||
label: 'Confirm',
|
data: {
|
||||||
size: 's',
|
spec: await start9MeSpec.build({} as any),
|
||||||
data: {
|
buttons: [
|
||||||
content: 'Claim your start9.me domain?',
|
{
|
||||||
yes: 'Claim',
|
text: 'Save',
|
||||||
no: 'Cancel',
|
handler: async value => this.claimStart9MeDomain(value),
|
||||||
},
|
},
|
||||||
})
|
],
|
||||||
.pipe(filter(Boolean))
|
},
|
||||||
.subscribe(() => this.claimStart9MeDomain())
|
}
|
||||||
|
this.formDialog.open(FormPage, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
presentAlertDelete(hostname: string) {
|
presentAlertDelete(hostname: string) {
|
||||||
@@ -143,11 +153,17 @@ export class DomainsPage {
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async claimStart9MeDomain(): Promise<boolean> {
|
private async claimStart9MeDomain(value: Start9MeSpec): Promise<boolean> {
|
||||||
const loader = this.loader.open('Saving...').subscribe()
|
const loader = this.loader.open('Saving...').subscribe()
|
||||||
|
|
||||||
|
const networkStrategy = value.strategy.unionSelectKey
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.claimStart9MeDomain({})
|
await this.api.claimStart9MeDomain({
|
||||||
|
networkStrategy,
|
||||||
|
ipStrategy:
|
||||||
|
networkStrategy === 'router' ? value.strategy.unionValueKey.ip : null,
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
@@ -157,11 +173,30 @@ export class DomainsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async save(value: DomainSpec): Promise<boolean> {
|
private async save(value: CustomSpec): Promise<boolean> {
|
||||||
const loader = this.loader.open('Saving...').subscribe()
|
const loader = this.loader.open('Saving...').subscribe()
|
||||||
|
|
||||||
|
const networkStrategy = value.strategy.unionSelectKey
|
||||||
|
const providerName = value.provider.unionSelectKey
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.addDomain(value)
|
await this.api.addDomain({
|
||||||
|
hostname: value.hostname,
|
||||||
|
provider: {
|
||||||
|
name: providerName,
|
||||||
|
username:
|
||||||
|
providerName === 'start9'
|
||||||
|
? null
|
||||||
|
: value.provider.unionValueKey.username,
|
||||||
|
password:
|
||||||
|
providerName === 'start9'
|
||||||
|
? null
|
||||||
|
: value.provider.unionValueKey.password,
|
||||||
|
},
|
||||||
|
networkStrategy,
|
||||||
|
ipStrategy:
|
||||||
|
networkStrategy === 'router' ? value.strategy.unionValueKey.ip : null,
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import {
|
|||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
|
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
|
||||||
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
||||||
import { DomainSpec } from 'src/app/apps/ui/pages/system/domains/domain.const'
|
import {
|
||||||
|
CustomSpec,
|
||||||
|
Start9MeSpec,
|
||||||
|
} from 'src/app/apps/ui/pages/system/domains/domain.const'
|
||||||
|
|
||||||
export module RR {
|
export module RR {
|
||||||
// DB
|
// DB
|
||||||
@@ -112,13 +115,25 @@ export module RR {
|
|||||||
|
|
||||||
// domains
|
// domains
|
||||||
|
|
||||||
export type ClaimStart9MeReq = {} // net.domain.me.claim
|
export type ClaimStart9MeReq = {
|
||||||
|
networkStrategy: string
|
||||||
|
ipStrategy: string | null
|
||||||
|
} // net.domain.me.claim
|
||||||
export type ClaimStart9MeRes = null
|
export type ClaimStart9MeRes = null
|
||||||
|
|
||||||
export type DeleteStart9MeReq = {} // net.domain.me.delete
|
export type DeleteStart9MeReq = {} // net.domain.me.delete
|
||||||
export type DeleteStart9MeRes = null
|
export type DeleteStart9MeRes = null
|
||||||
|
|
||||||
export type AddDomainReq = DomainSpec // net.domain.add
|
export type AddDomainReq = {
|
||||||
|
hostname: string
|
||||||
|
provider: {
|
||||||
|
name: string
|
||||||
|
username: string | null
|
||||||
|
password: string | null
|
||||||
|
}
|
||||||
|
networkStrategy: string
|
||||||
|
ipStrategy: string | null
|
||||||
|
} // net.domain.add
|
||||||
export type AddDomainRes = null
|
export type AddDomainRes = null
|
||||||
|
|
||||||
export type DeleteDomainReq = { hostname: string } // net.domain.delete
|
export type DeleteDomainReq = { hostname: string } // net.domain.delete
|
||||||
|
|||||||
@@ -452,6 +452,8 @@ export class MockApiService extends ApiService {
|
|||||||
value: {
|
value: {
|
||||||
value: 'xyz',
|
value: 'xyz',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
networkStrategy: params.networkStrategy,
|
||||||
|
ipStrategy: params.ipStrategy,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -482,7 +484,9 @@ export class MockApiService extends ApiService {
|
|||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
value: params.hostname,
|
value: params.hostname,
|
||||||
provider: params.provider,
|
provider: params.provider.name,
|
||||||
|
networkStrategy: params.networkStrategy,
|
||||||
|
ipStrategy: params.ipStrategy,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Url } from '@start9labs/shared'
|
|||||||
import { Manifest } from '@start9labs/marketplace'
|
import { Manifest } from '@start9labs/marketplace'
|
||||||
import { BackupJob } from '../api/api.types'
|
import { BackupJob } from '../api/api.types'
|
||||||
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
import { customSmtp } from '@start9labs/start-sdk/lib/config/configConstants'
|
||||||
import { DomainSpec } from 'src/app/apps/ui/pages/system/domains/domain.const'
|
import { CustomSpec } from 'src/app/apps/ui/pages/system/domains/domain.const'
|
||||||
|
|
||||||
export interface DataModel {
|
export interface DataModel {
|
||||||
'server-info': ServerInfo
|
'server-info': ServerInfo
|
||||||
@@ -104,7 +104,9 @@ export type WiFiInfo = {
|
|||||||
|
|
||||||
export type Domain = {
|
export type Domain = {
|
||||||
value: string
|
value: string
|
||||||
provider: DomainSpec['provider']
|
provider: string
|
||||||
|
networkStrategy: string
|
||||||
|
ipStrategy: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user