|
@@ -98,6 +96,7 @@ import { GatewayWithId } from './pd.service'
export class DnsComponent {
private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)
+ private readonly i18n = inject(i18nPipe)
readonly ddns = false
@@ -126,6 +125,8 @@ export class DnsComponent {
const segments = subdomain.split('.')
+ const subdomains = this.i18n.transform('subdomains of')
+
return [
{
host: subdomain,
@@ -135,12 +136,12 @@ export class DnsComponent {
const parent = segments.slice(i + 1).join('.')
return {
host: `*.${parent}`,
- purpose: `subdomains of ${parent}`,
+ purpose: `${subdomains} ${parent}`,
}
}),
{
host: '*',
- purpose: `subdomains of ${domain}`,
+ purpose: `${subdomains} ${domain}`,
},
]
})
diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts
index b14d45c07..d0b511369 100644
--- a/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts
+++ b/web/projects/ui/src/app/routes/portal/components/interfaces/public-domains/pd.service.ts
@@ -4,6 +4,7 @@ import {
ErrorService,
i18nKey,
LoadingService,
+ i18nPipe,
} from '@start9labs/shared'
import { toSignal } from '@angular/core/rxjs-interop'
import { ISB, T, utils } from '@start9labs/start-sdk'
@@ -18,8 +19,6 @@ import { toAuthorityName } from 'src/app/utils/acme'
import { InterfaceComponent } from '../interface.component'
import { DNS } from './dns.component'
-// @TODO translations
-
export type PublicDomain = {
fqdn: string
gateway: GatewayWithId | null
@@ -40,6 +39,7 @@ export class PublicDomainService {
private readonly formDialog = inject(FormDialogService)
private readonly dialog = inject(DialogService)
private readonly interface = inject(InterfaceComponent)
+ private readonly i18n = inject(i18nPipe)
readonly data = toSignal(
this.patch.watch$('serverInfo', 'network').pipe(
@@ -61,9 +61,10 @@ export class PublicDomainService {
async add() {
const addSpec = ISB.InputSpec.of({
fqdn: ISB.Value.text({
- name: 'Domain',
- description:
+ name: this.i18n.transform('Domain'),
+ description: this.i18n.transform(
'Enter a fully qualified domain name. For example, if you control domain.com, you could enter domain.com or subdomain.domain.com or another.subdomain.domain.com.',
+ ),
required: true,
default: null,
patterns: [utils.Patterns.domain],
@@ -140,7 +141,7 @@ export class PublicDomainService {
showDns(fqdn: string, gateway: GatewayWithId, message: i18nKey) {
this.dialog
.openComponent(DNS, {
- label: 'DNS Records' as i18nKey,
+ label: 'DNS Records',
size: 'l',
data: {
fqdn,
@@ -177,7 +178,9 @@ export class PublicDomainService {
}
const wanIp = gateway.ipInfo.wanIp
- let message = `Create one of the DNS records below to cause ${fqdn} to resolve to ${wanIp}`
+ let message = this.i18n.transform(
+ 'Create one of the DNS records below.',
+ ) as i18nKey
if (!ip) {
setTimeout(
@@ -185,7 +188,7 @@ export class PublicDomainService {
this.showDns(
fqdn,
gateway,
- `No DNS detected for ${fqdn}. ${message}` as i18nKey,
+ `${this.i18n.transform('No DNS record detected for')} ${fqdn}. ${message}` as i18nKey,
),
250,
)
@@ -195,7 +198,7 @@ export class PublicDomainService {
this.showDns(
fqdn,
gateway,
- `Invalid DNS. ${fqdn} is currently resolving to ${ip}. ${message}` as i18nKey,
+ `${this.i18n.transform('Invalid DNS record')}. ${fqdn} ${this.i18n.transform('resolves to')} ${ip}. ${message}` as i18nKey,
),
250,
)
@@ -203,8 +206,8 @@ export class PublicDomainService {
setTimeout(
() =>
this.dialog.openAlert(
- `${fqdn} is successfully resolving to ${wanIp}` as i18nKey,
- { label: 'DNS detected!' as i18nKey, appearance: 'positive' },
+ `${fqdn} ${this.i18n.transform('resolves to')} ${wanIp}` as i18nKey,
+ { label: 'DNS record detected!', appearance: 'positive' },
),
250,
)
@@ -224,8 +227,10 @@ export class PublicDomainService {
return {
gateway: ISB.Value.dynamicSelect(() => ({
- name: 'Gateway',
- description: 'Select a gateway to use for this domain.',
+ name: this.i18n.transform('Gateway'),
+ description: this.i18n.transform(
+ 'Select a gateway to use for this domain.',
+ ),
values: data.gateways.reduce>(
(obj, gateway) => ({
...obj,
@@ -241,9 +246,10 @@ export class PublicDomainService {
.map(g => g.id),
})),
authority: ISB.Value.select({
- name: 'Certificate Authority',
- description:
- 'Select the Certificate Authority that will issue the SSL/TLS certificate for this domain',
+ name: this.i18n.transform('Certificate Authority'),
+ description: this.i18n.transform(
+ 'Select a Certificate Authority to issue SSL/TLS certificates for this domain',
+ ),
values: data.authorities,
default: '',
}),
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts
index ebbd04786..c24b4b286 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/authorities/authority.service.ts
@@ -55,7 +55,7 @@ export class AuthorityService {
const addSpec = ISB.InputSpec.of({
provider: ISB.Value.union({
- name: 'Provider',
+ name: this.i18n.transform('Provider'),
default: (availableAuthorities[0]?.url as any) || 'other',
variants: ISB.Variants.of({
...availableAuthorities.reduce(
@@ -69,7 +69,7 @@ export class AuthorityService {
{},
),
other: {
- name: 'Other',
+ name: this.i18n.transform('Other'),
spec: ISB.InputSpec.of({
url: ISB.Value.text({
name: 'URL',
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts
index e5c29abdd..3d94acee4 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/gateways.component.ts
@@ -72,82 +72,82 @@ export default class GatewaysComponent {
private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)
private readonly formDialog = inject(FormDialogService)
+ private readonly i18n = inject(i18nPipe)
async add() {
+ const spec = ISB.InputSpec.of({
+ name: ISB.Value.text({
+ name: this.i18n.transform('Name'),
+ description: this.i18n.transform(
+ 'A name to easily identify the gateway',
+ ),
+ required: true,
+ default: null,
+ }),
+ type: ISB.Value.select({
+ name: this.i18n.transform('Type'),
+ description: `-**${this.i18n.transform('private')}**: ${this.i18n.transform('select this option if the gateway is configured for private access to authorized clients only. StartTunnel is a private gateway.')}\n-**${this.i18n.transform('public')}**: ${this.i18n.transform('select this option if the gateway is configured for unfettered public access.')}`,
+ default: 'private',
+ values: {
+ private: this.i18n.transform('private'),
+ public: this.i18n.transform('public'),
+ },
+ }),
+ config: ISB.Value.union({
+ name: this.i18n.transform('Wireguard Config File'),
+ default: 'paste',
+ variants: ISB.Variants.of({
+ paste: {
+ name: this.i18n.transform('Copy/Paste'),
+ spec: ISB.InputSpec.of({
+ file: ISB.Value.textarea({
+ name: this.i18n.transform('File Contents'),
+ default: null,
+ required: true,
+ }),
+ }),
+ },
+ upload: {
+ name: this.i18n.transform('Upload'),
+ spec: ISB.InputSpec.of({
+ file: ISB.Value.file({
+ name: this.i18n.transform('File'),
+ required: true,
+ extensions: ['.conf'],
+ }),
+ }),
+ },
+ }),
+ }),
+ })
+
this.formDialog.open(FormComponent, {
label: 'Add gateway',
data: {
- spec: await configBuilderToSpec(gatewaySpec),
+ spec: await configBuilderToSpec(spec),
buttons: [
{
- text: 'Save',
- handler: (input: typeof gatewaySpec._TYPE) => this.save(input),
+ text: this.i18n.transform('Save'),
+ handler: async (input: typeof spec._TYPE) => {
+ const loader = this.loader.open('Saving').subscribe()
+
+ try {
+ await this.api.addTunnel({
+ name: input.name,
+ config: '' as string, // @TODO alex/matt when types arrive
+ public: input.type === 'public',
+ })
+ return true
+ } catch (e: any) {
+ this.errorService.handleError(e)
+ return false
+ } finally {
+ loader.unsubscribe()
+ }
+ },
},
],
},
})
}
-
- private async save(input: typeof gatewaySpec._TYPE): Promise {
- const loader = this.loader.open('Saving').subscribe()
-
- try {
- await this.api.addTunnel({
- name: input.name,
- config: '' as string, // @TODO alex/matt when types arrive
- public: input.type === 'public',
- })
- return true
- } catch (e: any) {
- this.errorService.handleError(e)
- return false
- } finally {
- loader.unsubscribe()
- }
- }
}
-
-const gatewaySpec = ISB.InputSpec.of({
- name: ISB.Value.text({
- name: 'Name',
- description: 'A name to easily identify the gateway',
- required: true,
- default: null,
- }),
- type: ISB.Value.select({
- name: 'Type',
- description:
- '-**Private**: select this option if the gateway is configured for private access to authorized clients only. StartTunnel is a private gateway.\n-**Public**: select this option if the gateway is configured for unfettered public access.',
- default: 'private',
- values: {
- private: 'Private',
- public: 'Public',
- },
- }),
- config: ISB.Value.union({
- name: 'Wireguard Config',
- default: 'paste',
- variants: ISB.Variants.of({
- paste: {
- name: 'Paste File Contents',
- spec: ISB.InputSpec.of({
- file: ISB.Value.textarea({
- name: 'Paste File Contents',
- default: null,
- required: true,
- }),
- }),
- },
- upload: {
- name: 'Upload File',
- spec: ISB.InputSpec.of({
- file: ISB.Value.file({
- name: 'File',
- required: true,
- extensions: ['.conf'],
- }),
- }),
- },
- }),
- }),
-})
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts
index 43fb1fef5..75fd5c5ca 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/gateways/item.component.ts
@@ -143,6 +143,7 @@ export class GatewaysItemComponent {
private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)
private readonly formDialog = inject(FormDialogService)
+ private readonly i18n = inject(i18nPipe)
readonly gateway = input.required()
@@ -169,7 +170,7 @@ export class GatewaysItemComponent {
const { ipInfo, id } = this.gateway()
const renameSpec = ISB.InputSpec.of({
label: ISB.Value.text({
- name: 'Label',
+ name: this.i18n.transform('Name'),
required: true,
default: ipInfo?.name || null,
}),
diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/ssh/ssh.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/ssh/ssh.component.ts
index 486679627..c400ec30b 100644
--- a/web/projects/ui/src/app/routes/portal/routes/system/routes/ssh/ssh.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/ssh/ssh.component.ts
@@ -107,14 +107,29 @@ export default class SystemSSHComponent {
protected tableKeys = viewChild>('table')
async add(all: readonly SSHKey[]) {
+ const spec = ISB.InputSpec.of({
+ key: ISB.Value.text({
+ name: this.i18n.transform('Public Key'),
+ required: true,
+ default: null,
+ patterns: [
+ {
+ regex:
+ '^(ssh-(rsa|ed25519|dss|ecdsa)|ecdsa-sha2-nistp(256|384|521))\\s+[A-Za-z0-9+/=]+(\\s[^\\s]+)?$',
+ description: this.i18n.transform('must be a valid SSH public key'),
+ },
+ ],
+ }),
+ })
+
this.formDialog.open(FormComponent, {
label: 'Add SSH key',
data: {
- spec: await configBuilderToSpec(SSHSpec),
+ spec: await configBuilderToSpec(spec),
buttons: [
{
text: this.i18n.transform('Save'),
- handler: async ({ key }: typeof SSHSpec._TYPE) => {
+ handler: async ({ key }: typeof spec._TYPE) => {
const loader = this.loader.open('Saving').subscribe()
try {
@@ -157,18 +172,3 @@ export default class SystemSSHComponent {
})
}
}
-
-const SSHSpec = ISB.InputSpec.of({
- key: ISB.Value.text({
- name: 'Public Key',
- required: true,
- default: null,
- patterns: [
- {
- regex:
- '^(ssh-(rsa|ed25519|dss|ecdsa)|ecdsa-sha2-nistp(256|384|521))\\s+[A-Za-z0-9+/=]+(\\s[^\\s]+)?$',
- description: 'must be a valid SSH public key',
- },
- ],
- }),
-})
|