mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
domains mostly finished
This commit is contained in:
19
web/package-lock.json
generated
19
web/package-lock.json
generated
@@ -62,6 +62,7 @@
|
||||
"patch-db-client": "file:../patch-db/client",
|
||||
"pbkdf2": "^3.1.2",
|
||||
"rxjs": "^7.8.2",
|
||||
"tldts": "^7.0.11",
|
||||
"ts-matches": "^6.3.2",
|
||||
"tslib": "^2.8.1",
|
||||
"uuid": "^8.3.2",
|
||||
@@ -12185,6 +12186,24 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "7.0.11",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.11.tgz",
|
||||
"integrity": "sha512-7k7JV/LZpGhFUu2t+YDaMZ1wdPPRNpaCYNQ0NQbSLY3Rbgy+XbCdkXyqRiS9TLXiYAsrv0yiA0OvnxmgRFCdNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.0.11"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "7.0.11",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.11.tgz",
|
||||
"integrity": "sha512-65eeOpBwWBabh0XqT+zB0vEllq/V3XcrF2fhgMXWWFfNw1yxEjeYg9Vv/B/UNozd0CTR/TohO1ubfn6O6mBW3w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"@taiga-ui/addon-table": "4.47.0",
|
||||
"@taiga-ui/cdk": "4.47.0",
|
||||
"@taiga-ui/core": "4.47.0",
|
||||
"@taiga-ui/dompurify": "4.1.11",
|
||||
"@taiga-ui/event-plugins": "4.6.0",
|
||||
"@taiga-ui/experimental": "4.47.0",
|
||||
"@taiga-ui/icons": "4.47.0",
|
||||
@@ -59,7 +60,6 @@
|
||||
"@taiga-ui/layout": "4.47.0",
|
||||
"@taiga-ui/legacy": "4.47.0",
|
||||
"@taiga-ui/polymorpheus": "4.9.0",
|
||||
"@taiga-ui/dompurify": "4.1.11",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"base64-js": "^1.5.1",
|
||||
"buffer": "^6.0.3",
|
||||
@@ -68,8 +68,8 @@
|
||||
"core-js": "^3.42.0",
|
||||
"cron": "^2.2.0",
|
||||
"cronstrue": "^2.21.0",
|
||||
"dompurify": "^3.1.7",
|
||||
"deep-equality-data-structures": "1.5.1",
|
||||
"dompurify": "^3.1.7",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"fuse.js": "^6.4.6",
|
||||
"jose": "^4.9.0",
|
||||
@@ -83,17 +83,18 @@
|
||||
"patch-db-client": "file:../patch-db/client",
|
||||
"pbkdf2": "^3.1.2",
|
||||
"rxjs": "^7.8.2",
|
||||
"tldts": "^7.0.11",
|
||||
"ts-matches": "^6.3.2",
|
||||
"tslib": "^2.8.1",
|
||||
"uuid": "^8.3.2",
|
||||
"zone.js": "^0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-experts/hawkeye": "^1.7.2",
|
||||
"@angular/build": "^20.1.0",
|
||||
"@angular/cli": "^20.1.0",
|
||||
"@angular/compiler-cli": "^20.1.0",
|
||||
"@angular/language-service": "^20.1.0",
|
||||
"@angular-experts/hawkeye": "^1.7.2",
|
||||
"@types/dompurify": "3.0.5",
|
||||
"@types/estree": "^0.0.51",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
109: 'Privat',
|
||||
110: 'Fügen Sie eine Onion-Adresse hinzu, um dieses Interface anonym im Darknet verfügbar zu machen. Onion-Adressen sind nur über das Tor-Netzwerk erreichbar.',
|
||||
111: 'Keine Onion-Adressen',
|
||||
112: 'Neue Onion-Adresse',
|
||||
112: 'Neue onion-adresse',
|
||||
113: 'Privater Schlüssel (optional)',
|
||||
114: 'Optional können Sie einen base64-codierten ed25519-Schlüssel angeben, um die Tor V3 (.onion)-Adresse zu generieren. Wenn nicht angegeben, wird ein zufälliger Schlüssel erstellt.',
|
||||
115: 'Verarbeite 10.000 Logs',
|
||||
@@ -303,7 +303,7 @@ export default {
|
||||
308: 'Erforderlich, um ein Zertifikat von einer Zertifizierungsstelle zu erhalten',
|
||||
309: 'Alle umschalten',
|
||||
310: 'Fertig',
|
||||
311: 'Master-Passwort erforderlich',
|
||||
311: 'Master-passwort erforderlich',
|
||||
312: 'Geben Sie Ihr Master-Passwort ein, um diese Sicherung zu verschlüsseln.',
|
||||
313: 'Master-Passwort',
|
||||
314: 'Master-Passwort eingeben',
|
||||
@@ -539,6 +539,5 @@ export default {
|
||||
544: 'Domain bearbeiten',
|
||||
545: 'Keine Domains',
|
||||
546: 'Anbieter',
|
||||
547: 'DNS anzeigen',
|
||||
548: 'DNS testen',
|
||||
547: 'DNS verwalten',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -15,7 +15,7 @@ export const ENGLISH = {
|
||||
'Change Password': 13,
|
||||
'General Settings': 14,
|
||||
'Manage your overall setup and preferences': 15,
|
||||
'Browser Tab Title': 16,
|
||||
'Browser tab title': 16,
|
||||
'Language': 17,
|
||||
'Disk Repair': 18,
|
||||
'Attempt automatic repair': 19,
|
||||
@@ -103,7 +103,7 @@ export const ENGLISH = {
|
||||
'You have unsaved changes. Are you sure you want to leave?': 101,
|
||||
'Leave': 102,
|
||||
'Are you sure?': 103,
|
||||
'Select Domain': 104,
|
||||
'Select domain': 104,
|
||||
'Local': 105,
|
||||
'Local addresses can only be accessed by devices connected to the same LAN as your server, either directly or using a VPN.': 106,
|
||||
'Learn More': 107,
|
||||
@@ -111,7 +111,7 @@ export const ENGLISH = {
|
||||
'Private': 109,
|
||||
'Add an onion address to anonymously expose this interface on the darknet. Onion addresses can only be reached over the Tor network.': 110,
|
||||
'No onion addresses': 111,
|
||||
'New Onion Address': 112,
|
||||
'New onion address': 112,
|
||||
'Private Key (optional)': 113,
|
||||
'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) address. If not provided, a random key will be generated and used.': 114,
|
||||
'Processing 10,000 logs': 115,
|
||||
@@ -297,16 +297,16 @@ export const ENGLISH = {
|
||||
'Contact': 303, // as in, "contact us"
|
||||
'Edit': 304,
|
||||
'Add Certificate Authority': 305,
|
||||
'Edit Contact Info': 306,
|
||||
'Edit contact info': 306,
|
||||
'Contact Emails': 307,
|
||||
'Needed to obtain a certificate from a Certificate Authority': 308,
|
||||
'Toggle all': 309,
|
||||
'Done': 310,
|
||||
'Master Password Needed': 311,
|
||||
'Master password needed': 311,
|
||||
'Enter your master password to encrypt this backup.': 312,
|
||||
'Master Password': 313,
|
||||
'Enter master password': 314,
|
||||
'Original Password Needed': 315,
|
||||
'Original password needed': 315,
|
||||
'This backup was created with a different password. Enter the original password that was used to encrypt this backup.': 316,
|
||||
'Original Password': 317,
|
||||
'Enter original password': 318,
|
||||
@@ -363,7 +363,7 @@ export const ENGLISH = {
|
||||
'Ready to restore': 369,
|
||||
'Local Hostname': 370,
|
||||
'Created': 371,
|
||||
'Password Required': 372,
|
||||
'Password required': 372,
|
||||
'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.': 373,
|
||||
'Decrypting drive': 374,
|
||||
'Select services to restore': 375,
|
||||
@@ -395,7 +395,7 @@ export const ENGLISH = {
|
||||
'Terminate selected': 401,
|
||||
'Terminating sessions': 402,
|
||||
'No sessions': 403,
|
||||
'Password Needed': 404,
|
||||
'Password needed': 404,
|
||||
'Connected': 405,
|
||||
'Forget': 406, // as in, delete or remove
|
||||
'WiFi Credentials': 407,
|
||||
@@ -526,7 +526,7 @@ export const ENGLISH = {
|
||||
'Finished': 532, // an in, complete
|
||||
'Gateways': 533, // as in, a device or software that connects two different networks
|
||||
'Gateways connect your server to the Internet. They process outbound traffic, and under certain conditions, they also permit inbound traffic.': 534,
|
||||
'Add Gateway': 535, // as in, add a new network gateway to StartOS
|
||||
'Add gateway': 535, // as in, add a new network gateway to StartOS
|
||||
'Rename': 536,
|
||||
'Access': 537, // as in, public or private access, almost "permission"
|
||||
'Domains': 538, // as in, internet domains
|
||||
@@ -535,9 +535,8 @@ export const ENGLISH = {
|
||||
'Gateway': 541, // as in, a device or software that connects two different networks
|
||||
'Default Certificate Authority': 542,
|
||||
'Certificate Authority': 543,
|
||||
'Edit Domain': 544,
|
||||
'Edit domain': 544,
|
||||
'No domains': 545,
|
||||
'Provider': 546,
|
||||
'Show DNS': 547,
|
||||
'Test DNS': 548,
|
||||
'Manage DNS': 547,
|
||||
} as const
|
||||
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
109: 'Privado',
|
||||
110: 'Agrega una dirección onion para exponer esta interfaz de forma anónima en la darknet. Las direcciones onion solo se pueden acceder a través de la red Tor.',
|
||||
111: 'Sin direcciones onion',
|
||||
112: 'Nueva dirección Onion',
|
||||
112: 'Nueva dirección onion',
|
||||
113: 'Clave privada (opcional)',
|
||||
114: 'Opcionalmente proporciona una clave privada ed25519 codificada en base64 para generar la dirección Tor V3 (.onion). Si no se proporciona, se generará una clave aleatoria.',
|
||||
115: 'Procesando 10,000 registros',
|
||||
@@ -294,7 +294,6 @@ export default {
|
||||
297: 'Se detectó un paquete s9pk de versión 1. Este formato está obsoleto. Puedes instalarlo manualmente con start-cli si es necesario.',
|
||||
298: 'Archivo de paquete inválido',
|
||||
299: 'Agregar un dominio a StartOS significa que puedes usarlo y sus subdominios para alojar interfaces de servicios en Internet público.',
|
||||
|
||||
300: 'Ver instrucciones',
|
||||
303: 'Contacto',
|
||||
304: 'Editar',
|
||||
@@ -540,6 +539,5 @@ export default {
|
||||
544: 'Editar dominio',
|
||||
545: 'Sin dominios',
|
||||
546: 'Proveedor',
|
||||
547: 'Mostrar DNS',
|
||||
548: 'Probar DNS',
|
||||
547: 'Administrar DNS',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -539,6 +539,5 @@ export default {
|
||||
544: 'Modifier le domaine',
|
||||
545: 'Aucun domaine',
|
||||
546: 'Fournisseur',
|
||||
547: 'Afficher le DNS',
|
||||
548: 'Tester le DNS',
|
||||
547: 'Gérer le DNS',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
109: 'Prywatny',
|
||||
110: 'Dodaj adres onion, aby anonimowo udostępnić ten interfejs w sieci Tor. Adresy onion są dostępne tylko przez sieć Tor.',
|
||||
111: 'Brak adresów onion',
|
||||
112: 'Nowy adres Onion',
|
||||
112: 'Nowy adres onion',
|
||||
113: 'Klucz prywatny (opcjonalnie)',
|
||||
114: 'Opcjonalnie podaj klucz prywatny ed25519 zakodowany w base64, aby wygenerować adres Tor V3 (.onion). Jeśli nie zostanie podany, zostanie wygenerowany i użyty losowy klucz.',
|
||||
115: 'Przetwarzanie 10 000 logów',
|
||||
@@ -539,6 +539,5 @@ export default {
|
||||
544: 'Edytuj domenę',
|
||||
545: 'Brak domen',
|
||||
546: 'Dostawca',
|
||||
547: 'Pokaż DNS',
|
||||
548: 'Test DNS',
|
||||
547: 'Zarządzaj DNS',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -12,6 +12,8 @@ import { distinctUntilChanged, map, merge, Subject } from 'rxjs'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
// @TODO translations
|
||||
|
||||
@Component({
|
||||
selector: 'refresh-alert',
|
||||
template: `
|
||||
|
||||
@@ -221,7 +221,7 @@ export class InterfaceClearnetComponent {
|
||||
host: this.interface.value().addressInfo.hostId,
|
||||
})
|
||||
} else {
|
||||
await this.api.serverRemoveDomain(params)
|
||||
await this.api.osUiRemoveDomain(params)
|
||||
}
|
||||
return true
|
||||
} catch (e: any) {
|
||||
@@ -256,7 +256,7 @@ export class InterfaceClearnetComponent {
|
||||
})
|
||||
|
||||
this.formDialog.open<FormContext<ClearnetForm>>(FormComponent, {
|
||||
label: 'Select Domain',
|
||||
label: 'Select domain',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(
|
||||
ISB.InputSpec.of(
|
||||
@@ -292,7 +292,7 @@ export class InterfaceClearnetComponent {
|
||||
host: this.interface.value().addressInfo.hostId,
|
||||
})
|
||||
} else {
|
||||
await this.api.serverAddDomain(params)
|
||||
await this.api.osUiAddDomain(params)
|
||||
}
|
||||
return true
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -179,7 +179,7 @@ export class InterfaceTorComponent {
|
||||
|
||||
async add() {
|
||||
this.formDialog.open<FormContext<OnionForm>>(FormComponent, {
|
||||
label: 'New Onion Address',
|
||||
label: 'New onion address',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(
|
||||
ISB.InputSpec.of({
|
||||
|
||||
@@ -133,7 +133,7 @@ export class TabsComponent {
|
||||
)
|
||||
|
||||
more(content: TemplateRef<any>) {
|
||||
this.dialogs.open(content, { label: 'Start OS' }).subscribe({
|
||||
this.dialogs.open(content, { label: 'StartOS' }).subscribe({
|
||||
complete: () => this.update(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export class BackupsBackupComponent {
|
||||
|
||||
this.dialog
|
||||
.openPrompt<string>({
|
||||
label: 'Master Password Needed',
|
||||
label: 'Master password needed',
|
||||
data: {
|
||||
message: 'Enter your master password to encrypt this backup.',
|
||||
label: 'Master Password',
|
||||
@@ -169,7 +169,7 @@ export class BackupsBackupComponent {
|
||||
|
||||
this.dialog
|
||||
.openPrompt<string>({
|
||||
label: 'Original Password Needed',
|
||||
label: 'Original password needed',
|
||||
data: {
|
||||
message:
|
||||
'This backup was created with a different password. Enter the original password that was used to encrypt this backup.',
|
||||
|
||||
@@ -55,7 +55,7 @@ export class BackupRestoreComponent {
|
||||
onClick(serverId: string, { passwordHash }: StartOSDiskInfo) {
|
||||
this.dialog
|
||||
.openPrompt<string>({
|
||||
label: 'Password Required',
|
||||
label: 'Password required',
|
||||
data: {
|
||||
message:
|
||||
'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.',
|
||||
|
||||
@@ -117,7 +117,7 @@ export class AuthorityService {
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Edit Contact Info',
|
||||
label: 'Edit contact info',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(editSpec),
|
||||
buttons: [
|
||||
|
||||
@@ -19,7 +19,9 @@ import { Authority, AuthorityService } from './authority.service'
|
||||
@if (authority(); as authority) {
|
||||
<td>{{ authority.name }}</td>
|
||||
<td>{{ authority.url || '-' }}</td>
|
||||
<td>{{ authority.contact ? authority.contact.join(', ') : '-' }}</td>
|
||||
<td class="hidden">
|
||||
{{ authority.contact ? authority.contact.join(', ') : '-' }}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
tuiIconButton
|
||||
@@ -73,7 +75,7 @@ import { Authority, AuthorityService } from './authority.service'
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
grid-area: 1 / 2 / 3;
|
||||
grid-area: 1 / 2 / 4;
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -85,6 +87,10 @@ import { Authority, AuthorityService } from './authority.service'
|
||||
font: var(--tui-font-text-m);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
|
||||
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { MappedDomain } from './domain.service'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
|
||||
@Component({
|
||||
selector: 'dns',
|
||||
template: `
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ $any('Using IP') | i18n }}
|
||||
</header>
|
||||
|
||||
@let subdomain = context.data.subdomain;
|
||||
@let wanIp = context.data.gateway.ipInfo?.wanIp || ('Error' | i18n);
|
||||
|
||||
<table [appTable]="['Type', $any('Host'), 'Value', 'Purpose']">
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>{{ subdomain || '@' }}</td>
|
||||
<td>{{ wanIp }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>
|
||||
{{ subdomain ? '*.' + subdomain : '*' }}
|
||||
</td>
|
||||
<td>{{ wanIp }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
@if (context.data.gateway.ipInfo?.deviceType !== 'wireguard') {
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ $any('Using Dynamic DNS') | i18n }}
|
||||
</header>
|
||||
<table [appTable]="['Type', $any('Host'), 'Value', 'Purpose']">
|
||||
<tr>
|
||||
<td>ALIAS</td>
|
||||
<td>{{ subdomain || '@' }}</td>
|
||||
<td>[Dynamic DNS Address]</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ALIAS</td>
|
||||
<td>{{ subdomain ? '*.' + subdomain : '*' }}</td>
|
||||
<td>[Dynamic DNS Address]</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
}
|
||||
|
||||
<button tuiButton size="l" (click)="testDns()">
|
||||
{{ 'Test' | i18n }}
|
||||
</button>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, i18nPipe, TableComponent],
|
||||
})
|
||||
export class DnsComponent {
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
readonly context = injectContext<TuiDialogContext<void, MappedDomain>>()
|
||||
|
||||
async testDns() {
|
||||
const loader = this.loader.open().subscribe()
|
||||
|
||||
try {
|
||||
await this.api.testDomain({
|
||||
fqdn: this.context.data.fqdn,
|
||||
gateway: this.context.data.gateway.id,
|
||||
})
|
||||
return true
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
return false
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DNS = new PolymorpheusComponent(DnsComponent)
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { ISB, utils } from '@start9labs/start-sdk'
|
||||
import { ISB, T, utils } from '@start9labs/start-sdk'
|
||||
import { filter, map } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
@@ -15,9 +15,26 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { toAuthorityName } from 'src/app/utils/acme'
|
||||
import { parse } from 'tldts'
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
import { DNS } from './dns.component'
|
||||
|
||||
// @TODO translations
|
||||
|
||||
export type MappedDomain = {
|
||||
fqdn: string
|
||||
subdomain: string | null
|
||||
gateway: {
|
||||
id: string
|
||||
name: string | null
|
||||
ipInfo: T.IpInfo | null
|
||||
}
|
||||
authority: {
|
||||
url: string | null
|
||||
name: string | null
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DomainService {
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
@@ -30,57 +47,45 @@ export class DomainService {
|
||||
|
||||
readonly data = toSignal(
|
||||
this.patch.watch$('serverInfo', 'network').pipe(
|
||||
map(network => {
|
||||
return {
|
||||
gateways: Object.entries(network.networkInterfaces).reduce<
|
||||
Record<string, string>
|
||||
>(
|
||||
(obj, [id, n]) => ({
|
||||
...obj,
|
||||
[id]: n.ipInfo?.name || '',
|
||||
}),
|
||||
{},
|
||||
),
|
||||
// @TODO use real data
|
||||
domains: [
|
||||
{
|
||||
domain: 'blog.mydomain.com',
|
||||
map(({ networkInterfaces, domains, acme }) => ({
|
||||
gateways: Object.entries(networkInterfaces).reduce<
|
||||
Record<string, string>
|
||||
>(
|
||||
(obj, [id, n]) => ({
|
||||
...obj,
|
||||
[id]: n.ipInfo?.name || '',
|
||||
}),
|
||||
{},
|
||||
),
|
||||
domains: Object.entries(domains).map(
|
||||
([fqdn, { gateway, acme }]) =>
|
||||
({
|
||||
fqdn,
|
||||
subdomain: parse(fqdn).subdomain,
|
||||
gateway: {
|
||||
id: 'wireguard1',
|
||||
name: 'StartTunnel',
|
||||
id: gateway,
|
||||
ipInfo: networkInterfaces[gateway]?.ipInfo || null,
|
||||
},
|
||||
authority: {
|
||||
url: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
name: `Let's Encrypt`,
|
||||
url: acme,
|
||||
name: toAuthorityName(acme),
|
||||
},
|
||||
},
|
||||
{
|
||||
domain: 'store.mydomain.com',
|
||||
gateway: {
|
||||
id: 'eth0',
|
||||
name: 'Ethernet',
|
||||
},
|
||||
authority: {
|
||||
url: 'local',
|
||||
name: toAuthorityName(null),
|
||||
},
|
||||
},
|
||||
],
|
||||
authorities: Object.keys(network.acme).reduce<Record<string, string>>(
|
||||
(obj, url) => ({
|
||||
...obj,
|
||||
[url]: toAuthorityName(url),
|
||||
}),
|
||||
{ local: toAuthorityName(null) },
|
||||
),
|
||||
}
|
||||
}),
|
||||
}) as MappedDomain,
|
||||
),
|
||||
authorities: Object.keys(acme).reduce<Record<string, string>>(
|
||||
(obj, url) => ({
|
||||
...obj,
|
||||
[url]: toAuthorityName(url),
|
||||
}),
|
||||
{ local: toAuthorityName(null) },
|
||||
),
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
async add() {
|
||||
const addSpec = ISB.InputSpec.of({
|
||||
domain: ISB.Value.text({
|
||||
fqdn: ISB.Value.text({
|
||||
name: 'Domain',
|
||||
description:
|
||||
'Enter a domain/subdomain. For example, if you control domain.com, you could enter domain.com or subdomain.domain.com or another.subdomain.domain.com. In any case, the domain you enter and all possible subdomains of the domain will be available for assignment in StartOS',
|
||||
@@ -92,26 +97,31 @@ export class DomainService {
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Add Domain' as any,
|
||||
label: 'Add domain',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(addSpec),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save',
|
||||
handler: (input: typeof addSpec._TYPE) => this.save(input),
|
||||
handler: (input: typeof addSpec._TYPE) =>
|
||||
this.save({
|
||||
fqdn: input.fqdn,
|
||||
gateway: input.gateway,
|
||||
acme: input.authority === 'local' ? null : input.authority,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async edit(domain: any) {
|
||||
async edit(domain: MappedDomain) {
|
||||
const editSpec = ISB.InputSpec.of({
|
||||
...this.gatewaysAndAuthorities(),
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Edit Domain',
|
||||
label: 'Edit domain',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(editSpec),
|
||||
buttons: [
|
||||
@@ -119,20 +129,21 @@ export class DomainService {
|
||||
text: 'Save',
|
||||
handler: (input: typeof editSpec._TYPE) =>
|
||||
this.save({
|
||||
domain: domain.domain,
|
||||
...input,
|
||||
fqdn: domain.fqdn,
|
||||
gateway: input.gateway,
|
||||
acme: input.authority === 'local' ? null : input.authority,
|
||||
}),
|
||||
},
|
||||
],
|
||||
value: {
|
||||
gateway: domain.gateway.id,
|
||||
authority: domain.authority.url,
|
||||
authority: domain.authority.url || 'local',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
remove(domain: any) {
|
||||
remove(fqdn: string) {
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
.pipe(filter(Boolean))
|
||||
@@ -140,7 +151,7 @@ export class DomainService {
|
||||
const loader = this.loader.open('Deleting').subscribe()
|
||||
|
||||
try {
|
||||
// @TODO API
|
||||
await this.api.removeDomain({ fqdn })
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
@@ -149,20 +160,17 @@ export class DomainService {
|
||||
})
|
||||
}
|
||||
|
||||
showDns(domain: any) {
|
||||
// @TODO
|
||||
showDns(domain: MappedDomain) {
|
||||
this.dialog
|
||||
.openComponent(DNS, { label: 'Manage DNS', data: domain })
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
testDns(domain: any) {
|
||||
// @TODO
|
||||
}
|
||||
|
||||
// @TODO different endpoints for create and edit?
|
||||
private async save(params: any) {
|
||||
private async save(params: RR.AddDomainReq) {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
// @TODO API
|
||||
await this.api.addDomain(params)
|
||||
return true
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
TuiDropdown,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
import { DomainService } from './domain.service'
|
||||
import { DomainService, MappedDomain } from './domain.service'
|
||||
|
||||
@Component({
|
||||
selector: 'tr[domain]',
|
||||
template: `
|
||||
@if (domain(); as domain) {
|
||||
<td>{{ domain.domain }}</td>
|
||||
<td [style.order]="-1">{{ domain.gateway.name }}</td>
|
||||
<td>{{ domain.fqdn }}</td>
|
||||
<td [style.order]="-1">{{ domain.gateway.ipInfo?.name || '-' }}</td>
|
||||
<td>{{ domain.authority.name }}</td>
|
||||
<td>
|
||||
<button
|
||||
@@ -47,15 +47,7 @@ import { DomainService } from './domain.service'
|
||||
iconStart="@tui.eye"
|
||||
(click)="domainService.showDns(domain)"
|
||||
>
|
||||
{{ 'Show DNS' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.arrow-up-down"
|
||||
(click)="domainService.testDns(domain)"
|
||||
>
|
||||
{{ 'Test DNS' | i18n }}
|
||||
{{ 'Manage DNS' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group>
|
||||
@@ -64,7 +56,7 @@ import { DomainService } from './domain.service'
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="domainService.remove(domain)"
|
||||
(click)="domainService.remove(domain.fqdn)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
@@ -96,7 +88,7 @@ import { DomainService } from './domain.service'
|
||||
export class DomainItemComponent {
|
||||
protected readonly domainService = inject(DomainService)
|
||||
|
||||
readonly domain = input.required<any>()
|
||||
readonly domain = input.required<MappedDomain>()
|
||||
|
||||
open = false
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { map } from 'rxjs'
|
||||
import { ISB } from '@start9labs/start-sdk'
|
||||
import { GatewayWithID } from './item.component'
|
||||
import { GatewayPlus } from './item.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -87,19 +87,24 @@ export default class GatewaysComponent {
|
||||
.watch$('serverInfo', 'network', 'networkInterfaces')
|
||||
.pipe(
|
||||
map(gateways =>
|
||||
Object.entries(gateways).map(
|
||||
([id, val]) =>
|
||||
({
|
||||
...val,
|
||||
id,
|
||||
}) as GatewayWithID,
|
||||
),
|
||||
Object.entries(gateways)
|
||||
.filter(([_, val]) => !!val.ipInfo)
|
||||
.map(
|
||||
([id, val]) =>
|
||||
({
|
||||
...val,
|
||||
id,
|
||||
ipv4: val.ipInfo?.subnets
|
||||
.filter(s => !s.includes('::'))
|
||||
.map(s => s.split('/')[0]),
|
||||
}) as GatewayPlus,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
async add() {
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Add Gateway',
|
||||
label: 'Add gateway',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(gatewaySpec),
|
||||
buttons: [
|
||||
|
||||
@@ -24,55 +24,64 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
|
||||
export type GatewayWithID = T.NetworkInterfaceInfo & {
|
||||
export type GatewayPlus = T.NetworkInterfaceInfo & {
|
||||
id: string
|
||||
ipInfo: T.IpInfo
|
||||
ipv4: string[]
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tr[proxy]',
|
||||
template: `
|
||||
<td [style.grid-column]="'span 2'">{{ proxy().ipInfo.name }}</td>
|
||||
<td class="type">{{ proxy().ipInfo.deviceType || '-' }}</td>
|
||||
<td [style.order]="-2">
|
||||
{{ proxy().public ? ('Public' | i18n) : ('Private' | i18n) }}
|
||||
</td>
|
||||
<!-- // @TODO show both LAN IPs? -->
|
||||
<td class="lan">{{ proxy().ipInfo.subnets[0] }}</td>
|
||||
<td class="wan">{{ proxy().ipInfo.wanIp }}</td>
|
||||
<td>
|
||||
<button
|
||||
tuiIconButton
|
||||
tuiDropdown
|
||||
size="s"
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.ellipsis-vertical"
|
||||
[tuiAppearanceState]="open ? 'hover' : null"
|
||||
[(tuiDropdownOpen)]="open"
|
||||
@if (proxy(); as proxy) {
|
||||
<td [style.grid-column]="'span 2'">{{ proxy.ipInfo.name }}</td>
|
||||
<td class="type">{{ proxy.ipInfo.deviceType || '-' }}</td>
|
||||
<td [style.order]="-2">
|
||||
{{ proxy.public ? ('Public' | i18n) : ('Private' | i18n) }}
|
||||
</td>
|
||||
<td class="lan">{{ proxy.ipv4.join(', ') }}</td>
|
||||
<td
|
||||
class="wan"
|
||||
[style.color]="
|
||||
proxy.ipInfo.wanIp ? 'var(--tui-text-warning)' : undefined
|
||||
"
|
||||
>
|
||||
{{ 'More' | i18n }}
|
||||
<tui-data-list size="s" *tuiTextfieldDropdown>
|
||||
<tui-opt-group>
|
||||
<button tuiOption new iconStart="@tui.pencil" (click)="rename()">
|
||||
{{ 'Rename' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
@if (proxy().ipInfo.deviceType === 'wireguard') {
|
||||
{{ proxy.ipInfo.wanIp || ('Error' | i18n) }}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
tuiIconButton
|
||||
tuiDropdown
|
||||
size="s"
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.ellipsis-vertical"
|
||||
[tuiAppearanceState]="open ? 'hover' : null"
|
||||
[(tuiDropdownOpen)]="open"
|
||||
>
|
||||
{{ 'More' | i18n }}
|
||||
<tui-data-list size="s" *tuiTextfieldDropdown>
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="remove()"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
<button tuiOption new iconStart="@tui.pencil" (click)="rename()">
|
||||
{{ 'Rename' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
}
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
@if (proxy.ipInfo.deviceType === 'wireguard') {
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="remove()"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
}
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
@@ -106,7 +115,7 @@ export type GatewayWithID = T.NetworkInterfaceInfo & {
|
||||
grid-column: span 2;
|
||||
|
||||
&::before {
|
||||
content: 'LAN IPs: ';
|
||||
content: 'LAN IP: ';
|
||||
color: var(--tui-text-primary);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +142,7 @@ export class GatewaysItemComponent {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
|
||||
readonly proxy = input.required<GatewayWithID>()
|
||||
readonly proxy = input.required<GatewayPlus>()
|
||||
|
||||
open = false
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { GatewaysItemComponent, GatewayWithID } from './item.component'
|
||||
import { GatewaysItemComponent, GatewayPlus } from './item.component'
|
||||
|
||||
@Component({
|
||||
selector: '[gateways]',
|
||||
@@ -13,7 +13,7 @@ import { GatewaysItemComponent, GatewayWithID } from './item.component'
|
||||
'Name',
|
||||
'Type',
|
||||
'Access',
|
||||
$any('LAN IPs'),
|
||||
$any('LAN IP'),
|
||||
$any('WAN IP'),
|
||||
null,
|
||||
]"
|
||||
@@ -25,7 +25,7 @@ import { GatewaysItemComponent, GatewayWithID } from './item.component'
|
||||
<td colspan="5">
|
||||
@if (gateways()) {
|
||||
<app-placeholder icon="@tui.door-closed-locked">
|
||||
<!-- @TODO Matt finalize text and add translations -->
|
||||
<!-- @TODO translation -->
|
||||
No gateways
|
||||
</app-placeholder>
|
||||
} @else {
|
||||
@@ -45,6 +45,6 @@ import { GatewaysItemComponent, GatewayWithID } from './item.component'
|
||||
PlaceholderComponent,
|
||||
],
|
||||
})
|
||||
export class GatewaysTableComponent<T extends GatewayWithID> {
|
||||
export class GatewaysTableComponent<T extends GatewayPlus> {
|
||||
readonly gateways = input<readonly T[] | null>(null)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.app-window" />
|
||||
<span tuiTitle>
|
||||
<strong>{{ 'Browser Tab Title' | i18n }}</strong>
|
||||
<strong>{{ 'Browser tab title' | i18n }}</strong>
|
||||
<span tuiSubtitle>
|
||||
{{ 'Customize the name appearing in your browser tab' | i18n }}
|
||||
</span>
|
||||
@@ -302,7 +302,7 @@ export default class SystemGeneralComponent {
|
||||
onTitle() {
|
||||
const sub = this.dialog
|
||||
.openPrompt<string>({
|
||||
label: 'Browser Tab Title',
|
||||
label: 'Browser tab title',
|
||||
data: {
|
||||
label: 'Device Name',
|
||||
message:
|
||||
|
||||
@@ -144,7 +144,7 @@ export class WifiTableComponent {
|
||||
await this.component.saveAndConnect(network.ssid)
|
||||
} else {
|
||||
this.formDialog.open<FormContext<WiFiForm>>(FormComponent, {
|
||||
label: 'Password Needed',
|
||||
label: 'Password needed',
|
||||
data: {
|
||||
spec: wifiSpec.spec,
|
||||
buttons: [
|
||||
|
||||
@@ -234,7 +234,28 @@ export namespace RR {
|
||||
}
|
||||
export type CreateBackupRes = null
|
||||
|
||||
// tunnel
|
||||
// network
|
||||
|
||||
export type AddDomainReq = {
|
||||
fqdn: string
|
||||
gateway: string
|
||||
acme: string | null
|
||||
} // net.domain.add
|
||||
export type AddDomainRes = null
|
||||
|
||||
export type RemoveDomainReq = {
|
||||
fqdn: string
|
||||
} // net.domain.remove
|
||||
export type RemoveDomainRes = null
|
||||
|
||||
export type TestDomainReq = {
|
||||
fqdn: string
|
||||
gateway: string
|
||||
} // net.domain.test
|
||||
export type TestDomainRes = {
|
||||
root: boolean
|
||||
wildcard: boolean
|
||||
}
|
||||
|
||||
export type AddTunnelReq = {
|
||||
name: string
|
||||
@@ -255,7 +276,7 @@ export namespace RR {
|
||||
export type RemoveTunnelRes = null
|
||||
|
||||
export type InitAcmeReq = {
|
||||
provider: 'letsencrypt' | 'letsencrypt-staging' | string
|
||||
provider: string
|
||||
contact: string[]
|
||||
}
|
||||
export type InitAcmeRes = null
|
||||
@@ -288,19 +309,19 @@ export namespace RR {
|
||||
export type ServerRemoveOnionReq = ServerAddOnionReq // server.host.address.onion.remove
|
||||
export type RemoveOnionRes = null
|
||||
|
||||
export type ServerAddDomainReq = {
|
||||
export type OsUiAddDomainReq = {
|
||||
// server.host.address.domain.add
|
||||
domain: string // FQDN
|
||||
private: boolean
|
||||
acme: string | null // "letsencrypt" | "letsencrypt-staging" | Url | null
|
||||
acme: string | null // Url | null
|
||||
}
|
||||
export type AddDomainRes = null
|
||||
export type OsUiAddDomainRes = null
|
||||
|
||||
export type ServerRemoveDomainReq = {
|
||||
export type OsUiRemoveDomainReq = {
|
||||
// server.host.address.domain.remove
|
||||
domain: string // FQDN
|
||||
}
|
||||
export type RemoveDomainRes = null
|
||||
export type OsUiRemoveDomainRes = null
|
||||
|
||||
export type PkgBindingSetPublicReq = ServerBindingSetPublicReq & {
|
||||
// package.host.binding.set-public
|
||||
@@ -316,17 +337,19 @@ export namespace RR {
|
||||
|
||||
export type PkgRemoveOnionReq = PkgAddOnionReq // package.host.address.onion.remove
|
||||
|
||||
export type PkgAddDomainReq = ServerAddDomainReq & {
|
||||
export type PkgAddDomainReq = OsUiAddDomainReq & {
|
||||
// package.host.address.domain.add
|
||||
package: T.PackageId // string
|
||||
host: T.HostId // string
|
||||
}
|
||||
export type PkgAddDomainRes = null
|
||||
|
||||
export type PkgRemoveDomainReq = ServerRemoveDomainReq & {
|
||||
export type PkgRemoveDomainReq = OsUiRemoveDomainReq & {
|
||||
// package.host.address.domain.remove
|
||||
package: T.PackageId // string
|
||||
host: T.HostId // string
|
||||
}
|
||||
export type PkgRemoveDomainRes = null
|
||||
|
||||
export type GetPackageLogsReq = FetchLogsReq & { id: string } // package.logs
|
||||
export type GetPackageLogsRes = FetchLogsRes
|
||||
@@ -624,32 +647,6 @@ export type DependencyErrorTransitive = {
|
||||
// @TODO 041
|
||||
|
||||
// export namespace RR041 {
|
||||
// // ** domains **
|
||||
|
||||
// export type ClaimStart9ToReq = { gatewayId: string } // net.domain.me.claim
|
||||
// export type ClaimStart9ToRes = null
|
||||
|
||||
// export type DeleteStart9ToReq = {} // net.domain.me.delete
|
||||
// export type DeleteStart9ToRes = null
|
||||
|
||||
// export type AddDomainReq = {
|
||||
// hostname: string
|
||||
// provider: {
|
||||
// name: string
|
||||
// username: string | null
|
||||
// password: string | null
|
||||
// }
|
||||
// gatewayId: string
|
||||
// } // net.domain.add
|
||||
// export type AddDomainRes = null
|
||||
|
||||
// export type DeleteDomainReq = { hostname: string } // net.domain.delete
|
||||
// export type DeleteDomainRes = null
|
||||
|
||||
// // port forwards
|
||||
|
||||
// export type OverridePortReq = { target: number; port: number } // net.port-forwards.override
|
||||
// export type OverridePortRes = null
|
||||
|
||||
// // ** automated backups **
|
||||
|
||||
@@ -731,20 +728,6 @@ export type DependencyErrorTransitive = {
|
||||
|
||||
// @TODO 041 types
|
||||
|
||||
// export type AppMetrics = {
|
||||
// memory: {
|
||||
// percentageUsed: MetricData
|
||||
// used: MetricData
|
||||
// }
|
||||
// cpu: {
|
||||
// percentageUsed: MetricData
|
||||
// }
|
||||
// disk: {
|
||||
// percentageUsed: MetricData
|
||||
// used: MetricData
|
||||
// }
|
||||
// }
|
||||
|
||||
// export type RemoteBackupTarget = CifsBackupTarget | CloudBackupTarget
|
||||
// export type BackupTarget = RemoteBackupTarget | DiskBackupTarget
|
||||
|
||||
|
||||
@@ -184,33 +184,15 @@ export abstract class ApiService {
|
||||
|
||||
// @TODO 041
|
||||
|
||||
// abstract setOutboundProxy(
|
||||
// params: RR.SetOutboundTunnelReq,
|
||||
// ): Promise<RR.SetOutboundTunnelRes>
|
||||
|
||||
// ** domains **
|
||||
|
||||
// @TODO 041
|
||||
|
||||
// abstract claimStart9ToDomain(
|
||||
// params: RR.ClaimStart9ToReq,
|
||||
// ): Promise<RR.ClaimStart9ToRes>
|
||||
abstract addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes>
|
||||
|
||||
// abstract deleteStart9ToDomain(
|
||||
// params: RR.DeleteStart9ToReq,
|
||||
// ): Promise<RR.DeleteStart9ToRes>
|
||||
abstract removeDomain(params: RR.RemoveDomainReq): Promise<RR.RemoveDomainRes>
|
||||
|
||||
// abstract addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes>
|
||||
|
||||
// abstract deleteDomain(params: RR.DeleteDomainReq): Promise<RR.DeleteDomainRes>
|
||||
|
||||
// ** port forwards **
|
||||
|
||||
// @TODO 041
|
||||
|
||||
// abstract overridePortForward(
|
||||
// params: RR.OverridePortReq,
|
||||
// ): Promise<RR.OverridePortRes>
|
||||
abstract testDomain(params: RR.TestDomainReq): Promise<RR.TestDomainRes>
|
||||
|
||||
// wifi
|
||||
|
||||
@@ -387,13 +369,13 @@ export abstract class ApiService {
|
||||
params: RR.ServerRemoveOnionReq,
|
||||
): Promise<RR.RemoveOnionRes>
|
||||
|
||||
abstract serverAddDomain(
|
||||
params: RR.ServerAddDomainReq,
|
||||
): Promise<RR.AddDomainRes>
|
||||
abstract osUiAddDomain(
|
||||
params: RR.OsUiAddDomainReq,
|
||||
): Promise<RR.OsUiAddDomainRes>
|
||||
|
||||
abstract serverRemoveDomain(
|
||||
params: RR.ServerRemoveDomainReq,
|
||||
): Promise<RR.RemoveDomainRes>
|
||||
abstract osUiRemoveDomain(
|
||||
params: RR.OsUiRemoveDomainReq,
|
||||
): Promise<RR.OsUiRemoveDomainRes>
|
||||
|
||||
abstract pkgBindingSetPubic(
|
||||
params: RR.PkgBindingSetPublicReq,
|
||||
@@ -405,9 +387,9 @@ export abstract class ApiService {
|
||||
params: RR.PkgRemoveOnionReq,
|
||||
): Promise<RR.RemoveOnionRes>
|
||||
|
||||
abstract pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes>
|
||||
abstract pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.PkgAddDomainRes>
|
||||
|
||||
abstract pkgRemoveDomain(
|
||||
params: RR.PkgRemoveDomainReq,
|
||||
): Promise<RR.RemoveDomainRes>
|
||||
): Promise<RR.PkgRemoveDomainRes>
|
||||
}
|
||||
|
||||
@@ -358,41 +358,19 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'net.tunnel.remove', params })
|
||||
}
|
||||
|
||||
// async setOutboundProxy(
|
||||
// params: RR.SetOutboundTunnelReq,
|
||||
// ): Promise<RR.SetOutboundTunnelRes> {
|
||||
// return this.rpcRequest({ method: 'server.proxy.set-outbound', params })
|
||||
// }
|
||||
|
||||
// domains
|
||||
|
||||
// async claimStart9ToDomain(
|
||||
// params: RR.ClaimStart9ToReq,
|
||||
// ): Promise<RR.ClaimStart9ToRes> {
|
||||
// return this.rpcRequest({ method: 'net.domain.me.claim', params })
|
||||
// }
|
||||
async addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes> {
|
||||
return this.rpcRequest({ method: 'net.domain.add', params })
|
||||
}
|
||||
|
||||
// async deleteStart9ToDomain(
|
||||
// params: RR.DeleteStart9ToReq,
|
||||
// ): Promise<RR.DeleteStart9ToRes> {
|
||||
// return this.rpcRequest({ method: 'net.domain.me.delete', params })
|
||||
// }
|
||||
async removeDomain(params: RR.RemoveDomainReq): Promise<RR.RemoveDomainRes> {
|
||||
return this.rpcRequest({ method: 'net.domain.remove', params })
|
||||
}
|
||||
|
||||
// async addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes> {
|
||||
// return this.rpcRequest({ method: 'net.domain.add', params })
|
||||
// }
|
||||
|
||||
// async deleteDomain(params: RR.DeleteDomainReq): Promise<RR.DeleteDomainRes> {
|
||||
// return this.rpcRequest({ method: 'net.domain.delete', params })
|
||||
// }
|
||||
|
||||
// port forwards
|
||||
|
||||
// async overridePortForward(
|
||||
// params: RR.OverridePortReq,
|
||||
// ): Promise<RR.OverridePortRes> {
|
||||
// return this.rpcRequest({ method: 'net.port-forwards.override', params })
|
||||
// }
|
||||
async testDomain(params: RR.TestDomainReq): Promise<RR.TestDomainRes> {
|
||||
return this.rpcRequest({ method: 'net.domain.test', params })
|
||||
}
|
||||
|
||||
// wifi
|
||||
|
||||
@@ -685,18 +663,18 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async serverAddDomain(
|
||||
params: RR.ServerAddDomainReq,
|
||||
): Promise<RR.AddDomainRes> {
|
||||
async osUiAddDomain(
|
||||
params: RR.OsUiAddDomainReq,
|
||||
): Promise<RR.OsUiAddDomainRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'server.host.address.domain.add',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
async serverRemoveDomain(
|
||||
params: RR.ServerRemoveDomainReq,
|
||||
): Promise<RR.RemoveDomainRes> {
|
||||
async osUiRemoveDomain(
|
||||
params: RR.OsUiRemoveDomainReq,
|
||||
): Promise<RR.OsUiRemoveDomainRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'server.host.address.domain.remove',
|
||||
params,
|
||||
@@ -728,7 +706,7 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
|
||||
async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.PkgAddDomainRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'package.host.address.domain.add',
|
||||
params,
|
||||
@@ -737,7 +715,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
async pkgRemoveDomain(
|
||||
params: RR.PkgRemoveDomainReq,
|
||||
): Promise<RR.RemoveDomainRes> {
|
||||
): Promise<RR.PkgRemoveDomainRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'package.host.address.domain.remove',
|
||||
params,
|
||||
|
||||
@@ -601,113 +601,50 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
// async setOutboundProxy(
|
||||
// params: RR.SetOutboundTunnelReq,
|
||||
// ): Promise<RR.SetOutboundTunnelRes> {
|
||||
// await pauseFor(2000)
|
||||
|
||||
// const patch: ReplaceOperation<string | null>[] = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/outboundInterface',
|
||||
// value: params.id,
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
|
||||
// domains
|
||||
|
||||
// async claimStart9ToDomain(
|
||||
// params: RR.ClaimStart9ToReq,
|
||||
// ): Promise<RR.ClaimStart9ToRes> {
|
||||
// await pauseFor(2000)
|
||||
async addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/start9To',
|
||||
// value: {
|
||||
// subdomain: 'xyz',
|
||||
// gatewayId: params.gatewayId,
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/serverInfo/network/domains`,
|
||||
value: {
|
||||
[params.fqdn]: {
|
||||
gateway: params.gateway,
|
||||
acme: params.acme,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
return null
|
||||
}
|
||||
|
||||
// async deleteStart9ToDomain(
|
||||
// params: RR.DeleteStart9ToReq,
|
||||
// ): Promise<RR.DeleteStart9ToRes> {
|
||||
// await pauseFor(2000)
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/start9To',
|
||||
// value: null,
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
async removeDomain(params: RR.RemoveDomainReq): Promise<RR.RemoveDomainRes> {
|
||||
await pauseFor(2000)
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/serverInfo/network/domains',
|
||||
value: {},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
return null
|
||||
}
|
||||
|
||||
// async addDomain(params: RR.AddDomainReq): Promise<RR.AddDomainRes> {
|
||||
// await pauseFor(2000)
|
||||
async testDomain(params: RR.TestDomainReq): Promise<RR.TestDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: `/serverInfo/network/domains`,
|
||||
// value: {
|
||||
// [params.hostname]: {
|
||||
// gatewayId: params.gatewayId,
|
||||
// provider: params.provider.name,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
|
||||
// async deleteDomain(params: RR.DeleteDomainReq): Promise<RR.DeleteDomainRes> {
|
||||
// await pauseFor(2000)
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/domains',
|
||||
// value: {},
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
|
||||
// port forwards
|
||||
|
||||
// async overridePortForward(
|
||||
// params: RR.OverridePortReq,
|
||||
// ): Promise<RR.OverridePortRes> {
|
||||
// await pauseFor(2000)
|
||||
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/wanConfig/forwards/0/override',
|
||||
// value: params.port,
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
return {
|
||||
root: true,
|
||||
wildcard: true,
|
||||
}
|
||||
}
|
||||
|
||||
// wifi
|
||||
|
||||
@@ -1496,7 +1433,9 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async serverAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
|
||||
async osUiAddDomain(
|
||||
params: RR.OsUiAddDomainReq,
|
||||
): Promise<RR.OsUiAddDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: Operation<any>[] = [
|
||||
@@ -1529,9 +1468,9 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async serverRemoveDomain(
|
||||
params: RR.PkgRemoveDomainReq,
|
||||
): Promise<RR.RemoveDomainRes> {
|
||||
async osUiRemoveDomain(
|
||||
params: RR.OsUiRemoveDomainReq,
|
||||
): Promise<RR.OsUiRemoveDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: RemoveOperation[] = [
|
||||
@@ -1613,7 +1552,7 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
|
||||
async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.PkgAddDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: Operation<any>[] = [
|
||||
@@ -1648,7 +1587,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async pkgRemoveDomain(
|
||||
params: RR.PkgRemoveDomainReq,
|
||||
): Promise<RR.RemoveDomainRes> {
|
||||
): Promise<RR.PkgRemoveDomainRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: RemoveOperation[] = [
|
||||
|
||||
@@ -32,6 +32,16 @@ export const mockPatchData: DataModel = {
|
||||
contact: ['mailto:support@start9.com'],
|
||||
},
|
||||
},
|
||||
domains: {
|
||||
'cloud.private.com': {
|
||||
gateway: 'eth0',
|
||||
acme: null,
|
||||
},
|
||||
'public.com': {
|
||||
gateway: 'wireguard1',
|
||||
acme: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
},
|
||||
},
|
||||
host: {
|
||||
bindings: {
|
||||
80: {
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import { Languages } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
|
||||
export type DataModel = T.Public & { ui: UIData; packageData: AllPackageData }
|
||||
export type DataModel = T.Public & {
|
||||
ui: UIData
|
||||
packageData: AllPackageData
|
||||
serverInfo: T.ServerInfo & {
|
||||
network: T.NetworkInfo & {
|
||||
domains: {
|
||||
[fqdn: string]: {
|
||||
gateway: string
|
||||
acme: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type UIData = {
|
||||
name: string | null
|
||||
@@ -11,22 +24,6 @@ export type UIData = {
|
||||
language: Languages
|
||||
}
|
||||
|
||||
export type NetworkInfo = T.NetworkInfo & {
|
||||
// @TODO 041
|
||||
// start9To: {
|
||||
// subdomain: string
|
||||
// gatewayId: string
|
||||
// } | null
|
||||
// domains: {
|
||||
// [key: string]: Domain
|
||||
// }
|
||||
// wanConfig: {
|
||||
// upnp: boolean
|
||||
// forwards: PortForward[]
|
||||
// }
|
||||
// outboundProxy: string | null
|
||||
}
|
||||
|
||||
export type PackageDataEntry<T extends StateInfo = StateInfo> =
|
||||
T.PackageDataEntry & {
|
||||
stateInfo: T
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
// const options: Partial<
|
||||
// TuiDialogOptions<FormContext<typeof config.validator._TYPE>>
|
||||
// > = {
|
||||
// label: 'Outbound Proxy',
|
||||
// label: 'Outbound proxy',
|
||||
// data: {
|
||||
// spec: await configBuilderToSpec(config),
|
||||
// buttons: [
|
||||
|
||||
Reference in New Issue
Block a user