mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
dns
This commit is contained in:
@@ -525,4 +525,5 @@ export default {
|
||||
556: '',
|
||||
557: '',
|
||||
558: '',
|
||||
559: '',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -523,5 +523,6 @@ export const ENGLISH = {
|
||||
'Address details': 555,
|
||||
'Private Domains': 556,
|
||||
'No private domains': 557,
|
||||
'New private domain': 558
|
||||
'New private domain': 558,
|
||||
'DNS Servers': 559
|
||||
} as const
|
||||
|
||||
@@ -525,4 +525,5 @@ export default {
|
||||
556: '',
|
||||
557: '',
|
||||
558: '',
|
||||
559: '',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -525,4 +525,5 @@ export default {
|
||||
556: '',
|
||||
557: '',
|
||||
558: '',
|
||||
559: '',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -525,4 +525,5 @@ export default {
|
||||
556: '',
|
||||
557: '',
|
||||
558: '',
|
||||
559: '',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import {
|
||||
DocsLinkDirective,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { ISB } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, first, switchMap } from 'rxjs'
|
||||
import { FormModule } from 'src/app/routes/portal/components/form/form.module'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormService } from 'src/app/services/form.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'DNS Servers' | i18n }}
|
||||
</ng-container>
|
||||
@if (data(); as d) {
|
||||
<form [formGroup]="d.form">
|
||||
<header tuiHeader="body-l">
|
||||
<h3 tuiTitle>
|
||||
<b>
|
||||
{{ 'DNS Servers' | i18n }}
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
path="/user-manual/dns.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.external-link"
|
||||
>
|
||||
{{ 'Documentation' | i18n }}
|
||||
</a>
|
||||
</b>
|
||||
</h3>
|
||||
</header>
|
||||
|
||||
<form-group [spec]="d.spec" />
|
||||
|
||||
@if (d.warn.length; as length) {
|
||||
<p>
|
||||
Warning. StartOS is currently using {{ d.warn.join(', ') }} for DNS.
|
||||
Therefore, {{ length > 1 ? 'they' : 'it' }} cannot use StartOS for
|
||||
DNS. This is circular. If you want to use StartOS as the DNS server
|
||||
for {{ d.warn.join(', ') }} for private domain resolution, you must
|
||||
set custom DNS servers above.
|
||||
</p>
|
||||
}
|
||||
|
||||
<footer>
|
||||
<button
|
||||
tuiButton
|
||||
size="l"
|
||||
[disabled]="d.form.invalid || d.form.pristine"
|
||||
(click)="save(d.form.value)"
|
||||
>
|
||||
{{ 'Save' | i18n }}
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
form header,
|
||||
form footer {
|
||||
margin: 1rem 0;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
FormModule,
|
||||
TuiButton,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
RouterLink,
|
||||
TitleDirective,
|
||||
i18nPipe,
|
||||
DocsLinkDirective,
|
||||
],
|
||||
})
|
||||
export default class SystemDnsComponent {
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly formService = inject(FormService)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
private readonly dnsSpec = ISB.InputSpec.of({
|
||||
strategy: ISB.Value.union({
|
||||
name: 'DNS Servers',
|
||||
default: 'defaults',
|
||||
variants: ISB.Variants.of({
|
||||
defaults: {
|
||||
name: 'Default',
|
||||
spec: ISB.InputSpec.of({
|
||||
servers: ISB.Value.list(
|
||||
ISB.List.text(
|
||||
{
|
||||
name: 'Default DNS Servers',
|
||||
},
|
||||
{},
|
||||
),
|
||||
),
|
||||
}),
|
||||
},
|
||||
custom: {
|
||||
name: 'Custom',
|
||||
spec: ISB.InputSpec.of({
|
||||
servers: ISB.Value.list(
|
||||
ISB.List.text(
|
||||
{
|
||||
name: 'DNS Servers',
|
||||
minLength: 1,
|
||||
maxLength: 3,
|
||||
},
|
||||
{ placeholder: '1.1.1.1' },
|
||||
),
|
||||
),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
readonly data = toSignal(
|
||||
combineLatest([
|
||||
this.patch.watch$('packageData').pipe(first()),
|
||||
this.patch.watch$('serverInfo', 'network'),
|
||||
]).pipe(
|
||||
switchMap(async ([pkgs, { gateways, dns }]) => {
|
||||
const spec = await configBuilderToSpec(this.dnsSpec)
|
||||
|
||||
const selection = dns.static ? 'custom' : 'defaults'
|
||||
|
||||
const form = this.formService.createForm(spec, {
|
||||
strategy: { selection, value: dns.servers },
|
||||
})
|
||||
|
||||
return {
|
||||
spec,
|
||||
form,
|
||||
warn:
|
||||
(Object.values(pkgs).some(p => p) || []) &&
|
||||
Object.values(gateways)
|
||||
.filter(g => dns.servers.includes(g.ipInfo?.lanIp))
|
||||
.map(g => g.ipInfo?.name),
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
async save(value: typeof this.dnsSpec._TYPE): Promise<void> {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.setDns({
|
||||
servers: value.strategy.value.servers,
|
||||
static: value.strategy.selection === 'custom',
|
||||
})
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { i18nKey } from '@start9labs/shared'
|
||||
|
||||
export const SYSTEM_MENU = [
|
||||
[
|
||||
{
|
||||
@@ -46,6 +48,11 @@ export const SYSTEM_MENU = [
|
||||
item: 'Certificate Authorities',
|
||||
link: 'authorities',
|
||||
},
|
||||
{
|
||||
icon: '@tui.globe',
|
||||
item: 'DNS' as i18nKey,
|
||||
link: 'dns',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
|
||||
@@ -77,6 +77,11 @@ export default [
|
||||
loadComponent: () =>
|
||||
import('./routes/authorities/authorities.component'),
|
||||
},
|
||||
{
|
||||
path: 'dns',
|
||||
title: titleResolver,
|
||||
loadComponent: () => import('./routes/dns/dns.component'),
|
||||
},
|
||||
],
|
||||
},
|
||||
] satisfies Routes
|
||||
|
||||
@@ -104,11 +104,16 @@ export namespace RR {
|
||||
export type DiskRepairReq = {} // server.disk.repair
|
||||
export type DiskRepairRes = null
|
||||
|
||||
export type TestDnsReq = {
|
||||
export type SetDnsReq = {
|
||||
servers: string[]
|
||||
static: boolean
|
||||
} // net.dns.set
|
||||
export type SetDnsRes = null
|
||||
|
||||
export type QueryDnsReq = {
|
||||
fqdn: string
|
||||
gateway: T.GatewayId // string
|
||||
} // net.dns.test
|
||||
export type TestDnsRes = string | null
|
||||
} // net.dns.query
|
||||
export type QueryDnsRes = string | null
|
||||
|
||||
export type ResetTorReq = {
|
||||
wipeState: boolean
|
||||
@@ -301,7 +306,7 @@ export namespace RR {
|
||||
gateway: T.GatewayId
|
||||
acme: string | null // URL. null means local Root CA
|
||||
}
|
||||
export type OsUiAddPublicDomainRes = TestDnsRes
|
||||
export type OsUiAddPublicDomainRes = QueryDnsRes
|
||||
|
||||
export type OsUiRemovePublicDomainReq = {
|
||||
// server.host.address.domain.public.remove
|
||||
|
||||
@@ -122,7 +122,9 @@ export abstract class ApiService {
|
||||
|
||||
abstract toggleKiosk(enable: boolean): Promise<null>
|
||||
|
||||
abstract testDns(params: RR.TestDnsReq): Promise<RR.TestDnsRes>
|
||||
abstract setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes>
|
||||
|
||||
abstract queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes>
|
||||
|
||||
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
|
||||
|
||||
|
||||
@@ -267,9 +267,16 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async testDns(params: RR.TestDnsReq): Promise<RR.TestDnsRes> {
|
||||
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'net.dns.test',
|
||||
method: 'net.dns.set',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
async queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'net.dns.query',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -462,7 +462,22 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async testDns(params: RR.TestDnsReq): Promise<RR.TestDnsRes> {
|
||||
async setDns(params: RR.SetDnsReq): Promise<RR.SetDnsRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: ReplaceOperation<RR.SetDnsReq>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/serverInfo/network/dns',
|
||||
value: params,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async queryDns(params: RR.QueryDnsReq): Promise<RR.QueryDnsRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user