mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
dns
This commit is contained in:
@@ -525,4 +525,5 @@ export default {
|
|||||||
556: '',
|
556: '',
|
||||||
557: '',
|
557: '',
|
||||||
558: '',
|
558: '',
|
||||||
|
559: '',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -523,5 +523,6 @@ export const ENGLISH = {
|
|||||||
'Address details': 555,
|
'Address details': 555,
|
||||||
'Private Domains': 556,
|
'Private Domains': 556,
|
||||||
'No private domains': 557,
|
'No private domains': 557,
|
||||||
'New private domain': 558
|
'New private domain': 558,
|
||||||
|
'DNS Servers': 559
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -525,4 +525,5 @@ export default {
|
|||||||
556: '',
|
556: '',
|
||||||
557: '',
|
557: '',
|
||||||
558: '',
|
558: '',
|
||||||
|
559: '',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -525,4 +525,5 @@ export default {
|
|||||||
556: '',
|
556: '',
|
||||||
557: '',
|
557: '',
|
||||||
558: '',
|
558: '',
|
||||||
|
559: '',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -525,4 +525,5 @@ export default {
|
|||||||
556: '',
|
556: '',
|
||||||
557: '',
|
557: '',
|
||||||
558: '',
|
558: '',
|
||||||
|
559: '',
|
||||||
} satisfies i18n
|
} 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 = [
|
export const SYSTEM_MENU = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -46,6 +48,11 @@ export const SYSTEM_MENU = [
|
|||||||
item: 'Certificate Authorities',
|
item: 'Certificate Authorities',
|
||||||
link: 'authorities',
|
link: 'authorities',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: '@tui.globe',
|
||||||
|
item: 'DNS' as i18nKey,
|
||||||
|
link: 'dns',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ export default [
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./routes/authorities/authorities.component'),
|
import('./routes/authorities/authorities.component'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'dns',
|
||||||
|
title: titleResolver,
|
||||||
|
loadComponent: () => import('./routes/dns/dns.component'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] satisfies Routes
|
] satisfies Routes
|
||||||
|
|||||||
@@ -104,11 +104,16 @@ export namespace RR {
|
|||||||
export type DiskRepairReq = {} // server.disk.repair
|
export type DiskRepairReq = {} // server.disk.repair
|
||||||
export type DiskRepairRes = null
|
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
|
fqdn: string
|
||||||
gateway: T.GatewayId // string
|
} // net.dns.query
|
||||||
} // net.dns.test
|
export type QueryDnsRes = string | null
|
||||||
export type TestDnsRes = string | null
|
|
||||||
|
|
||||||
export type ResetTorReq = {
|
export type ResetTorReq = {
|
||||||
wipeState: boolean
|
wipeState: boolean
|
||||||
@@ -301,7 +306,7 @@ export namespace RR {
|
|||||||
gateway: T.GatewayId
|
gateway: T.GatewayId
|
||||||
acme: string | null // URL. null means local Root CA
|
acme: string | null // URL. null means local Root CA
|
||||||
}
|
}
|
||||||
export type OsUiAddPublicDomainRes = TestDnsRes
|
export type OsUiAddPublicDomainRes = QueryDnsRes
|
||||||
|
|
||||||
export type OsUiRemovePublicDomainReq = {
|
export type OsUiRemovePublicDomainReq = {
|
||||||
// server.host.address.domain.public.remove
|
// server.host.address.domain.public.remove
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ export abstract class ApiService {
|
|||||||
|
|
||||||
abstract toggleKiosk(enable: boolean): Promise<null>
|
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>
|
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({
|
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,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,7 +462,22 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
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)
|
await pauseFor(2000)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|||||||
Reference in New Issue
Block a user