mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
certificate authorities
This commit is contained in:
@@ -179,7 +179,6 @@ export default {
|
||||
177: 'Kernelspeicher',
|
||||
178: 'Leerlauf',
|
||||
179: 'I/O-Wartezeit',
|
||||
180: 'ACME',
|
||||
181: 'Gesamt',
|
||||
182: 'Verwendet',
|
||||
183: 'Verfügbar',
|
||||
@@ -294,12 +293,12 @@ export default {
|
||||
296: 'Hochladen',
|
||||
297: 'Version 1 s9pk erkannt. Dieses Format ist veraltet. Falls nötig, kann ein V1 s9pk über start-cli installiert werden.',
|
||||
298: 'Ungültige Paketdatei',
|
||||
299: 'Fügen Sie ACME-Anbieter hinzu, um SSL-(https)-Zertifikate für den Clearnet-Zugriff zu generieren.',
|
||||
299: 'Das Hinzufügen einer Domain zu StartOS bedeutet, dass du sie und ihre Subdomains verwenden kannst, um Service-Oberflächen im öffentlichen Internet zu hosten.',
|
||||
300: 'Anleitung anzeigen',
|
||||
303: 'Kontakt',
|
||||
304: 'Bearbeiten',
|
||||
305: 'ACME-Anbieter hinzufügen',
|
||||
306: 'ACME-Anbieter bearbeiten',
|
||||
305: 'Zertifizierungsstelle hinzufügen',
|
||||
306: 'Kontaktinformationen bearbeiten',
|
||||
307: 'Kontakt-E-Mails',
|
||||
308: 'Erforderlich, um ein Zertifikat von einer Zertifizierungsstelle zu erhalten',
|
||||
309: 'Alle umschalten',
|
||||
@@ -532,12 +531,12 @@ export default {
|
||||
536: 'Umbenennen',
|
||||
537: 'Zugriff',
|
||||
538: 'Domains',
|
||||
539: 'ACME-Anbieter',
|
||||
539: 'Zertifizierungsstellen',
|
||||
540: 'Domain',
|
||||
541: 'Gateway',
|
||||
542: 'Standard-ACME',
|
||||
543: 'Gateway ändern',
|
||||
544: 'Standard-ACME ändern',
|
||||
542: 'Standard-Zertifizierungsstelle',
|
||||
543: 'Zertifizierungsstelle',
|
||||
544: 'Domain bearbeiten',
|
||||
545: 'Keine Domains',
|
||||
546: 'Anbieter',
|
||||
547: 'DNS anzeigen',
|
||||
|
||||
@@ -178,7 +178,6 @@ export const ENGLISH = {
|
||||
'Kernel space': 177,
|
||||
'Idle': 178, // a CPU metric
|
||||
'I/O wait': 179,
|
||||
'ACME': 180,
|
||||
'Total': 181,
|
||||
'Used': 182,
|
||||
'Available': 183,
|
||||
@@ -293,12 +292,12 @@ export const ENGLISH = {
|
||||
'Upload': 296,
|
||||
'Version 1 s9pk detected. This package format is deprecated. You can sideload a V1 s9pk via start-cli if necessary.': 297,
|
||||
'Invalid package file': 298,
|
||||
'Add ACME providers in order to generate SSL (https) certificates for clearnet access.': 299,
|
||||
'Adding a domain to StartOS means you can use it and its subdomains to host service interfaces on the public Internet.': 299,
|
||||
'View instructions': 300,
|
||||
'Contact': 303, // as in, "contact us"
|
||||
'Edit': 304,
|
||||
'Add ACME Provider': 305,
|
||||
'Edit ACME Provider': 306,
|
||||
'Add Certificate Authority': 305,
|
||||
'Edit Contact Info': 306,
|
||||
'Contact Emails': 307,
|
||||
'Needed to obtain a certificate from a Certificate Authority': 308,
|
||||
'Toggle all': 309,
|
||||
@@ -531,12 +530,12 @@ export const ENGLISH = {
|
||||
'Rename': 536,
|
||||
'Access': 537, // as in, public or private access, almost "permission"
|
||||
'Domains': 538, // as in, internet domains
|
||||
'ACME Providers': 539,
|
||||
'Certificate Authorities': 539,
|
||||
'Domain': 540, // as in, an internat domain name
|
||||
'Gateway': 541, // as in, a device or software that connects two different networks
|
||||
'Default ACME': 542, // as in, the default ACME provider for signing certificates
|
||||
'Change gateway': 543, // as in, change the network gateway for a computer
|
||||
'Change default ACME': 544, // as in, change the default ACME provider for a domain
|
||||
'Default Certificate Authority': 542,
|
||||
'Certificate Authority': 543,
|
||||
'Edit Domain': 544,
|
||||
'No domains': 545,
|
||||
'Provider': 546,
|
||||
'Show DNS': 547,
|
||||
|
||||
@@ -179,7 +179,6 @@ export default {
|
||||
177: 'Espacio del kernel',
|
||||
178: 'Inactivo',
|
||||
179: 'Espera de E/S',
|
||||
180: 'ACME',
|
||||
181: 'Total',
|
||||
182: 'Usado',
|
||||
183: 'Disponible',
|
||||
@@ -294,12 +293,13 @@ export default {
|
||||
296: 'Subir',
|
||||
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: 'Agrega proveedores ACME para generar certificados SSL (https) para el acceso desde clearnet.',
|
||||
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',
|
||||
305: 'Agregar proveedor ACME',
|
||||
306: 'Editar proveedor ACME',
|
||||
305: 'Agregar autoridad certificadora',
|
||||
306: 'Editar información de contacto',
|
||||
307: 'Correos de contacto',
|
||||
308: 'Necesarios para obtener un certificado de una Autoridad Certificadora',
|
||||
309: 'Alternar todo',
|
||||
@@ -532,12 +532,12 @@ export default {
|
||||
536: 'Renombrar',
|
||||
537: 'Acceso',
|
||||
538: 'Dominios',
|
||||
539: 'Proveedores ACME',
|
||||
539: 'Autoridades certificadoras',
|
||||
540: 'Dominio',
|
||||
541: 'Puerta de enlace',
|
||||
542: 'ACME predeterminado',
|
||||
543: 'Cambiar puerta de enlace',
|
||||
544: 'Cambiar ACME predeterminado',
|
||||
542: 'Autoridad certificadora predeterminada',
|
||||
543: 'Autoridad certificadora',
|
||||
544: 'Editar dominio',
|
||||
545: 'Sin dominios',
|
||||
546: 'Proveedor',
|
||||
547: 'Mostrar DNS',
|
||||
|
||||
@@ -179,7 +179,6 @@ export default {
|
||||
177: 'Espace noyau',
|
||||
178: 'Inactif',
|
||||
179: 'Attente E/S',
|
||||
180: 'ACME',
|
||||
181: 'Total',
|
||||
182: 'Utilisé',
|
||||
183: 'Disponible',
|
||||
@@ -294,12 +293,12 @@ export default {
|
||||
296: 'Téléverser',
|
||||
297: 'Version 1 de s9pk détectée. Ce format de paquet est obsolète. Vous pouvez installer manuellement un s9pk V1 via start-cli si nécessaire.',
|
||||
298: 'Fichier paquet invalide',
|
||||
299: 'Ajoutez des fournisseurs ACME pour générer des certificats SSL (https) pour l’accès clearnet.',
|
||||
299: 'Ajouter un domaine à StartOS signifie que vous pouvez l’utiliser, ainsi que ses sous-domaines, pour héberger des interfaces de services sur Internet public.',
|
||||
300: 'Voir les instructions',
|
||||
303: 'Contact',
|
||||
304: 'Modifier',
|
||||
305: 'Ajouter un fournisseur ACME',
|
||||
306: 'Modifier le fournisseur ACME',
|
||||
305: 'Ajouter une autorité de certification',
|
||||
306: 'Modifier les informations de contact',
|
||||
307: 'Emails de contact',
|
||||
308: 'Nécessaire pour obtenir un certificat d’une autorité de certification',
|
||||
309: 'Tout cocher',
|
||||
@@ -532,12 +531,12 @@ export default {
|
||||
536: 'Renommer',
|
||||
537: 'Accès',
|
||||
538: 'Domaines',
|
||||
539: 'Fournisseurs ACME',
|
||||
539: 'Autorités de certification',
|
||||
540: 'Domaine',
|
||||
541: 'Passerelle',
|
||||
542: 'ACME par défaut',
|
||||
543: 'Changer de passerelle',
|
||||
544: 'Changer l’ACME par défaut',
|
||||
542: 'Autorité de certification par défaut',
|
||||
543: 'Autorité de certification',
|
||||
544: 'Modifier le domaine',
|
||||
545: 'Aucun domaine',
|
||||
546: 'Fournisseur',
|
||||
547: 'Afficher le DNS',
|
||||
|
||||
@@ -179,7 +179,6 @@ export default {
|
||||
177: 'Przestrzeń jądra',
|
||||
178: 'Bezczynność',
|
||||
179: 'Oczekiwanie na I/O',
|
||||
180: 'ACME',
|
||||
181: 'Łącznie',
|
||||
182: 'Wykorzystane',
|
||||
183: 'Dostępne',
|
||||
@@ -294,12 +293,12 @@ export default {
|
||||
296: 'Prześlij',
|
||||
297: 'Wykryto pakiet s9pk w wersji 1. Ten format pakietu jest przestarzały. Możesz zainstalować pakiet s9pk V1 przez start-cli, jeśli to konieczne.',
|
||||
298: 'Nieprawidłowy plik pakietu',
|
||||
299: 'Dodaj dostawców ACME, aby wygenerować certyfikaty SSL (https) dla dostępu przez clearnet.',
|
||||
299: 'Dodanie domeny do StartOS oznacza, że możesz używać jej i jej subdomen do hostowania interfejsów usług w publicznym Internecie.',
|
||||
300: 'Zobacz instrukcje',
|
||||
303: 'Kontakt',
|
||||
304: 'Edytuj',
|
||||
305: 'Dodaj dostawcę ACME',
|
||||
306: 'Edytuj dostawcę ACME',
|
||||
305: 'Dodaj urząd certyfikacji',
|
||||
306: 'Edytuj dane kontaktowe',
|
||||
307: 'Adresy e-mail kontaktowe',
|
||||
308: 'Wymagane do uzyskania certyfikatu od urzędu certyfikacji',
|
||||
309: 'Zaznacz wszystkie',
|
||||
@@ -532,12 +531,12 @@ export default {
|
||||
536: 'Zmień nazwę',
|
||||
537: 'Dostęp',
|
||||
538: 'Domeny',
|
||||
539: 'Dostawcy ACME',
|
||||
539: 'Urzędy certyfikacji',
|
||||
540: 'Domena',
|
||||
541: 'Brama',
|
||||
542: 'Domyślny ACME',
|
||||
543: 'Zmień bramę',
|
||||
544: 'Zmień domyślny ACME',
|
||||
542: 'Domyślny urząd certyfikacji',
|
||||
543: 'Urząd certyfikacji',
|
||||
544: 'Edytuj domenę',
|
||||
545: 'Brak domen',
|
||||
546: 'Dostawca',
|
||||
547: 'Pokaż DNS',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { toAcmeName } from 'src/app/utils/acme'
|
||||
import { toAuthorityName } from 'src/app/utils/acme'
|
||||
|
||||
@Pipe({
|
||||
name: 'acme',
|
||||
name: 'authorityName',
|
||||
})
|
||||
export class AcmePipe implements PipeTransform {
|
||||
export class AuthorityNamePipe implements PipeTransform {
|
||||
transform(value: string | null = null): string {
|
||||
return toAcmeName(value)
|
||||
return toAuthorityName(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ import {
|
||||
FormComponent,
|
||||
FormContext,
|
||||
} from 'src/app/routes/portal/components/form.component'
|
||||
import { AcmePipe } from 'src/app/routes/portal/components/interfaces/acme.pipe'
|
||||
import { AuthorityNamePipe } from 'src/app/routes/portal/components/interfaces/acme.pipe'
|
||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { toAcmeName } from 'src/app/utils/acme'
|
||||
import { toAuthorityName } from 'src/app/utils/acme'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { InterfaceActionsComponent } from './actions.component'
|
||||
import { ClearnetAddress } from './interface.utils'
|
||||
@@ -43,7 +43,7 @@ import { MaskPipe } from './mask.pipe'
|
||||
|
||||
type ClearnetForm = {
|
||||
domain: string
|
||||
acme: string
|
||||
authority: string
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -85,11 +85,15 @@ type ClearnetForm = {
|
||||
}}
|
||||
</tui-notification>
|
||||
}
|
||||
<table [appTable]="['ACME', 'URL', null]">
|
||||
<table [appTable]="['Certificate Authority', 'URL', null]">
|
||||
@for (address of clearnet(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">
|
||||
{{ interface.value().addSsl ? (address.acme | acme) : '-' }}
|
||||
{{
|
||||
interface.value().addSsl
|
||||
? (address.authority | authorityName)
|
||||
: '-'
|
||||
}}
|
||||
</td>
|
||||
<td [style.order]="-1">{{ address.url | mask }}</td>
|
||||
<td
|
||||
@@ -154,7 +158,7 @@ type ClearnetForm = {
|
||||
PlaceholderComponent,
|
||||
TableComponent,
|
||||
MaskPipe,
|
||||
AcmePipe,
|
||||
AuthorityNamePipe,
|
||||
InterfaceActionsComponent,
|
||||
i18nPipe,
|
||||
DocsLinkDirective,
|
||||
@@ -175,7 +179,7 @@ export class InterfaceClearnetComponent {
|
||||
readonly isRunning = input.required<boolean>()
|
||||
readonly isPublic = input.required<boolean>()
|
||||
|
||||
readonly acme = toSignal(
|
||||
readonly authorityUrls = toSignal(
|
||||
inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('serverInfo', 'network', 'acme')
|
||||
.pipe(map(acme => Object.keys(acme))),
|
||||
@@ -237,16 +241,16 @@ export class InterfaceClearnetComponent {
|
||||
default: null,
|
||||
patterns: [utils.Patterns.domain],
|
||||
})
|
||||
const acme = ISB.Value.select({
|
||||
name: 'ACME Provider',
|
||||
const authority = ISB.Value.select({
|
||||
name: 'Certificate Authority',
|
||||
description:
|
||||
'Select which ACME provider to use for obtaining your SSL certificate. Add new ACME providers in the System tab. Optionally use your system Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.',
|
||||
values: this.acme().reduce<Record<string, string>>(
|
||||
'Select which Certificate authority to use for obtaining your SSL certificate. Add new authority in the System tab. Optionally use your local= Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.',
|
||||
values: this.authorityUrls().reduce<Record<string, string>>(
|
||||
(obj, url) => ({
|
||||
...obj,
|
||||
[url]: toAcmeName(url),
|
||||
[url]: toAuthorityName(url),
|
||||
}),
|
||||
{ none: 'None (use system Root CA)' },
|
||||
{ local: toAuthorityName(null) },
|
||||
),
|
||||
default: '',
|
||||
})
|
||||
@@ -256,7 +260,7 @@ export class InterfaceClearnetComponent {
|
||||
data: {
|
||||
spec: await configBuilderToSpec(
|
||||
ISB.InputSpec.of(
|
||||
this.interface.value().addSsl ? { domain, acme } : { domain },
|
||||
this.interface.value().addSsl ? { domain, authority } : { domain },
|
||||
),
|
||||
),
|
||||
buttons: [
|
||||
@@ -272,11 +276,11 @@ export class InterfaceClearnetComponent {
|
||||
private async save(domainInfo: ClearnetForm): Promise<boolean> {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
const { domain, acme } = domainInfo
|
||||
const { domain, authority } = domainInfo
|
||||
|
||||
const params = {
|
||||
domain,
|
||||
acme: acme === 'none' ? null : acme,
|
||||
acme: authority === 'local' ? null : authority,
|
||||
private: false,
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ export function getAddresses(
|
||||
url,
|
||||
disabled: !h.public,
|
||||
isDomain: hostnameKind == 'domain',
|
||||
acme:
|
||||
authority:
|
||||
hostnameKind == 'domain'
|
||||
? host.domains[h.hostname.domain]?.acme || null
|
||||
: null,
|
||||
@@ -118,7 +118,7 @@ export type MappedServiceInterface = T.ServiceInterface & {
|
||||
|
||||
export type ClearnetAddress = {
|
||||
url: string
|
||||
acme: string | null
|
||||
authority: string | null
|
||||
isDomain: boolean
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
import { ACMEInfo, AcmeService } from './acme.service'
|
||||
|
||||
@Component({
|
||||
selector: 'tr[acme]',
|
||||
template: `
|
||||
<td>{{ acme().name }}</td>
|
||||
<td>{{ acme().contact.join(', ') }}</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.pencil"
|
||||
(click)="service.edit(acme())"
|
||||
>
|
||||
{{ 'Edit' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="service.remove(acme())"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
grid-area: 1 / 2 / 3;
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
grid-template-columns: 1fr min-content;
|
||||
|
||||
td:first-child {
|
||||
font: var(--tui-font-text-m);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, i18nPipe, TuiDropdown, TuiDataList, TuiTextfield],
|
||||
})
|
||||
export class AcmeItemComponent {
|
||||
protected readonly service = inject(AcmeService)
|
||||
|
||||
readonly acme = input.required<ACMEInfo>()
|
||||
|
||||
open = false
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
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 { AcmeItemComponent } from './item.component'
|
||||
import { ACMEInfo } from './acme.service'
|
||||
|
||||
@Component({
|
||||
selector: 'acme-table',
|
||||
template: `
|
||||
<table [appTable]="['Provider', 'Contact', null]">
|
||||
@for (acme of acmes(); track $index) {
|
||||
<tr [acme]="acme"></tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td [attr.colspan]="3">
|
||||
@if (acmes()) {
|
||||
<app-placeholder icon="@tui.shield-question">
|
||||
{{ 'No saved providers' | i18n }}
|
||||
</app-placeholder>
|
||||
} @else {
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
TuiSkeleton,
|
||||
i18nPipe,
|
||||
TableComponent,
|
||||
PlaceholderComponent,
|
||||
AcmeItemComponent,
|
||||
],
|
||||
})
|
||||
export class AcmeTableComponent {
|
||||
readonly acmes = input<ACMEInfo[]>()
|
||||
}
|
||||
@@ -13,18 +13,19 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { knownACME } from 'src/app/utils/acme'
|
||||
import { knownAuthorities, toAuthorityName } from 'src/app/utils/acme'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { toAcmeName } from 'src/app/utils/acme'
|
||||
|
||||
export type ACMEInfo = {
|
||||
export type Authority = {
|
||||
url: string | null
|
||||
name: string
|
||||
url: string
|
||||
contact: readonly string[]
|
||||
contact: readonly string[] | null
|
||||
}
|
||||
|
||||
export type RemoteAuthority = Authority & { url: string }
|
||||
|
||||
@Injectable()
|
||||
export class AcmeService {
|
||||
export class AuthorityService {
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
@@ -33,31 +34,36 @@ export class AcmeService {
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
private readonly dialog = inject(DialogService)
|
||||
|
||||
readonly acmes = toSignal<ACMEInfo[]>(
|
||||
readonly authorities = toSignal<Authority[]>(
|
||||
this.patch.watch$('serverInfo', 'network', 'acme').pipe(
|
||||
map(acme =>
|
||||
Object.keys(acme).map(url => ({
|
||||
map(acme => [
|
||||
{
|
||||
url: null,
|
||||
name: toAuthorityName(null),
|
||||
contact: null,
|
||||
},
|
||||
...Object.keys(acme).map(url => ({
|
||||
url,
|
||||
name: toAcmeName(url),
|
||||
name: toAuthorityName(url),
|
||||
contact:
|
||||
acme[url]?.contact.map(mailto => mailto.replace('mailto:', '')) ||
|
||||
[],
|
||||
null,
|
||||
})),
|
||||
),
|
||||
]),
|
||||
),
|
||||
)
|
||||
|
||||
async add(acmes: ACMEInfo[]) {
|
||||
const availableAcme = knownACME.filter(
|
||||
acme => !acmes.map(a => a.url).includes(acme.url),
|
||||
async add(authorities: Authority[]) {
|
||||
const availableAuthorities = knownAuthorities.filter(
|
||||
ca => !authorities.map(a => a.url).includes(ca.url),
|
||||
)
|
||||
|
||||
const addSpec = ISB.InputSpec.of({
|
||||
provider: ISB.Value.union({
|
||||
name: 'Provider',
|
||||
default: (availableAcme[0]?.url as any) || 'other',
|
||||
default: (availableAuthorities[0]?.url as any) || 'other',
|
||||
variants: ISB.Variants.of({
|
||||
...availableAcme.reduce(
|
||||
...availableAuthorities.reduce(
|
||||
(obj, curr) => ({
|
||||
...obj,
|
||||
[curr.url]: {
|
||||
@@ -85,7 +91,7 @@ export class AcmeService {
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Add ACME Provider',
|
||||
label: 'Add Certificate Authority',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(addSpec),
|
||||
buttons: [
|
||||
@@ -105,13 +111,13 @@ export class AcmeService {
|
||||
})
|
||||
}
|
||||
|
||||
async edit({ url, contact }: ACMEInfo) {
|
||||
async edit({ url, contact }: RemoteAuthority) {
|
||||
const editSpec = ISB.InputSpec.of({
|
||||
contact: this.emailListSpec(),
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Edit ACME Provider',
|
||||
label: 'Edit Contact Info',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(editSpec),
|
||||
buttons: [
|
||||
@@ -126,7 +132,7 @@ export class AcmeService {
|
||||
})
|
||||
}
|
||||
|
||||
remove({ url }: ACMEInfo) {
|
||||
remove({ url }: RemoteAuthority) {
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
.pipe(filter(Boolean))
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
import { Authority, AuthorityService } from './authority.service'
|
||||
|
||||
@Component({
|
||||
selector: 'tr[authority]',
|
||||
template: `
|
||||
@if (authority(); as authority) {
|
||||
<td>{{ authority.name }}</td>
|
||||
<td>{{ authority.url || '-' }}</td>
|
||||
<td>{{ authority.contact ? authority.contact.join(', ') : '-' }}</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>
|
||||
@if (authority.url) {
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.pencil"
|
||||
(click)="service.edit($any(authority))"
|
||||
>
|
||||
{{ 'Edit' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="service.remove($any(authority))"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
} @else {
|
||||
<tui-opt-group>
|
||||
<a
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.download"
|
||||
href="/static/local-root-ca.crt"
|
||||
>
|
||||
{{ 'Download your Root CA' | i18n }}
|
||||
</a>
|
||||
</tui-opt-group>
|
||||
}
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
grid-area: 1 / 2 / 3;
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
grid-template-columns: 1fr min-content;
|
||||
|
||||
td:first-child {
|
||||
font: var(--tui-font-text-m);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, i18nPipe, TuiDropdown, TuiDataList, TuiTextfield],
|
||||
})
|
||||
export class AuthorityItemComponent {
|
||||
protected readonly service = inject(AuthorityService)
|
||||
|
||||
readonly authority = input.required<Authority>()
|
||||
|
||||
open = false
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { AuthorityItemComponent } from './item.component'
|
||||
import { AuthorityService } from './authority.service'
|
||||
|
||||
@Component({
|
||||
selector: 'authorities-table',
|
||||
template: `
|
||||
<table [appTable]="['Provider', 'URL', 'Contact', null]">
|
||||
@for (authority of authorityService.authorities(); track $index) {
|
||||
<tr [authority]="authority"></tr>
|
||||
} @empty {
|
||||
<td [attr.colspan]="4">
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
</td>
|
||||
}
|
||||
</table>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiSkeleton, i18nPipe, TableComponent, AuthorityItemComponent],
|
||||
})
|
||||
export class AuthoritiesTableComponent {
|
||||
protected readonly authorityService = inject(AuthorityService)
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiLink, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { AcmeService } from './acme/acme.service'
|
||||
import { DomainsService } from './domains/domains.service'
|
||||
import { AuthorityService } from './authorities/authority.service'
|
||||
import { DomainService } from './domains/domain.service'
|
||||
import { DomainsTableComponent } from './domains/table.component'
|
||||
import { AcmeTableComponent } from './acme/table.component'
|
||||
import { AuthoritiesTableComponent } from './authorities/table.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -21,9 +21,9 @@ import { AcmeTableComponent } from './acme/table.component'
|
||||
<hgroup tuiTitle>
|
||||
<h3>{{ 'Domains' | i18n }}</h3>
|
||||
<p tuiSubtitle>
|
||||
<!-- @TODO translation -->
|
||||
{{
|
||||
'Adding a domain to StartOS means you can use it and its subdomains to host service interfaces on the public Internet.'
|
||||
| i18n
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
@@ -40,38 +40,38 @@ import { AcmeTableComponent } from './acme/table.component'
|
||||
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ 'ACME Providers' | i18n }}
|
||||
@if (acmeService.acmes(); as acmes) {
|
||||
{{ 'Certificate Authorities' | i18n }}
|
||||
@if (authorityService.authorities(); as authorities) {
|
||||
<button
|
||||
tuiButton
|
||||
size="xs"
|
||||
iconStart="@tui.plus"
|
||||
[style.margin-inline-start]="'auto'"
|
||||
(click)="acmeService.add(acmes)"
|
||||
(click)="authorityService.add(authorities)"
|
||||
>
|
||||
{{ 'Add' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</header>
|
||||
<acme-table [acmes]="acmeService.acmes()" />
|
||||
<authorities-table />
|
||||
</section>
|
||||
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ 'Domains' | i18n }}
|
||||
@if (domainsService.data(); as value) {
|
||||
@if (domainService.data(); as value) {
|
||||
<button
|
||||
tuiButton
|
||||
size="xs"
|
||||
iconStart="@tui.plus"
|
||||
[style.margin-inline-start]="'auto'"
|
||||
(click)="domainsService.add()"
|
||||
(click)="domainService.add()"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
}
|
||||
</header>
|
||||
<domains-table [domains]="domainsService.data()?.domains" />
|
||||
<domains-table />
|
||||
</section>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -85,11 +85,11 @@ import { AcmeTableComponent } from './acme/table.component'
|
||||
i18nPipe,
|
||||
DocsLinkDirective,
|
||||
DomainsTableComponent,
|
||||
AcmeTableComponent,
|
||||
AuthoritiesTableComponent,
|
||||
],
|
||||
providers: [AcmeService, DomainsService],
|
||||
providers: [AuthorityService, DomainService],
|
||||
})
|
||||
export default class SystemDomainsComponent {
|
||||
protected readonly acmeService = inject(AcmeService)
|
||||
protected readonly domainsService = inject(DomainsService)
|
||||
protected readonly authorityService = inject(AuthorityService)
|
||||
protected readonly domainService = inject(DomainService)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { toAcmeName } from 'src/app/utils/acme'
|
||||
import { toAuthorityName } from 'src/app/utils/acme'
|
||||
|
||||
// @TODO translations
|
||||
|
||||
@Injectable()
|
||||
export class DomainsService {
|
||||
export class DomainService {
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
@@ -46,32 +46,32 @@ export class DomainsService {
|
||||
{
|
||||
domain: 'blog.mydomain.com',
|
||||
gateway: {
|
||||
id: '',
|
||||
id: 'wireguard1',
|
||||
name: 'StartTunnel',
|
||||
},
|
||||
acme: {
|
||||
url: '',
|
||||
name: `Lert's Encrypt`,
|
||||
authority: {
|
||||
url: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
name: `Let's Encrypt`,
|
||||
},
|
||||
},
|
||||
{
|
||||
domain: 'store.mydomain.com',
|
||||
gateway: {
|
||||
id: '',
|
||||
id: 'eth0',
|
||||
name: 'Ethernet',
|
||||
},
|
||||
acme: {
|
||||
url: null,
|
||||
name: 'System',
|
||||
authority: {
|
||||
url: 'local',
|
||||
name: toAuthorityName(null),
|
||||
},
|
||||
},
|
||||
],
|
||||
acme: Object.keys(network.acme).reduce<Record<string, string>>(
|
||||
authorities: Object.keys(network.acme).reduce<Record<string, string>>(
|
||||
(obj, url) => ({
|
||||
...obj,
|
||||
[url]: toAcmeName(url),
|
||||
[url]: toAuthorityName(url),
|
||||
}),
|
||||
{ none: 'None (use system Root CA)' },
|
||||
{ local: toAuthorityName(null) },
|
||||
),
|
||||
}
|
||||
}),
|
||||
@@ -88,7 +88,7 @@ export class DomainsService {
|
||||
default: null,
|
||||
patterns: [utils.Patterns.domain],
|
||||
}),
|
||||
...this.gatewaysAndAcme(),
|
||||
...this.gatewaysAndAuthorities(),
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
@@ -107,11 +107,11 @@ export class DomainsService {
|
||||
|
||||
async edit(domain: any) {
|
||||
const editSpec = ISB.InputSpec.of({
|
||||
...this.gatewaysAndAcme(),
|
||||
...this.gatewaysAndAuthorities(),
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Edit Domain' as any, // @TODO translation
|
||||
label: 'Edit Domain',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(editSpec),
|
||||
buttons: [
|
||||
@@ -126,7 +126,7 @@ export class DomainsService {
|
||||
],
|
||||
value: {
|
||||
gateway: domain.gateway.id,
|
||||
acme: domain.acme.url,
|
||||
authority: domain.authority.url,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -172,7 +172,7 @@ export class DomainsService {
|
||||
}
|
||||
}
|
||||
|
||||
private gatewaysAndAcme() {
|
||||
private gatewaysAndAuthorities() {
|
||||
return {
|
||||
gateway: ISB.Value.select({
|
||||
name: 'Gateway',
|
||||
@@ -181,11 +181,11 @@ export class DomainsService {
|
||||
values: this.data()!.gateways,
|
||||
default: '',
|
||||
}),
|
||||
acme: ISB.Value.select({
|
||||
name: 'Default ACME',
|
||||
authority: ISB.Value.select({
|
||||
name: 'Default Certificate Authority',
|
||||
description:
|
||||
'Select the default ACME provider for this domain. This can be overridden on a case-by-case basis.',
|
||||
values: this.data()!.acme,
|
||||
'Select the default certificate authority that will sign certificates for this domain. You can override this on a case-by-case basis.',
|
||||
values: this.data()!.authorities,
|
||||
default: '',
|
||||
}),
|
||||
}
|
||||
@@ -11,66 +11,68 @@ import {
|
||||
TuiDropdown,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
import { DomainsService } from './domains.service'
|
||||
import { DomainService } from './domain.service'
|
||||
|
||||
@Component({
|
||||
selector: 'tr[domain]',
|
||||
template: `
|
||||
<td>{{ domain().domain }}</td>
|
||||
<td [style.order]="-1">{{ domain().gateway.name }}</td>
|
||||
<td>{{ domain().acme.name }}</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.pencil"
|
||||
(click)="domainsService.edit(domain())"
|
||||
>
|
||||
{{ 'Edit' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.shield"
|
||||
(click)="domainsService.showDns(domain())"
|
||||
>
|
||||
{{ 'Show DNS' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.shield"
|
||||
(click)="domainsService.testDns(domain())"
|
||||
>
|
||||
{{ 'Test DNS' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="domainsService.remove(domain())"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
@if (domain(); as domain) {
|
||||
<td>{{ domain.domain }}</td>
|
||||
<td [style.order]="-1">{{ domain.gateway.name }}</td>
|
||||
<td>{{ domain.authority.name }}</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.pencil"
|
||||
(click)="domainService.edit(domain)"
|
||||
>
|
||||
{{ 'Edit' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
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 }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
new
|
||||
iconStart="@tui.trash"
|
||||
class="g-negative"
|
||||
(click)="domainService.remove(domain)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
@@ -91,8 +93,8 @@ import { DomainsService } from './domains.service'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, i18nPipe, TuiDropdown, TuiDataList, TuiTextfield],
|
||||
})
|
||||
export class DomainsItemComponent {
|
||||
protected readonly domainsService = inject(DomainsService)
|
||||
export class DomainItemComponent {
|
||||
protected readonly domainService = inject(DomainService)
|
||||
|
||||
readonly domain = input.required<any>()
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
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 { DomainsItemComponent } from './item.component'
|
||||
import { DomainItemComponent } from './item.component'
|
||||
import { DomainService } from './domain.service'
|
||||
|
||||
@Component({
|
||||
selector: 'domains-table',
|
||||
template: `
|
||||
<table [appTable]="['Domain', 'Gateway', 'Default ACME', null]">
|
||||
@for (domain of domains(); track $index) {
|
||||
<table
|
||||
[appTable]="['Domain', 'Gateway', 'Default Certificate Authority', null]"
|
||||
>
|
||||
@for (domain of domainService.data()?.domains; track $index) {
|
||||
<tr [domain]="domain"></tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td [attr.colspan]="4">
|
||||
@if (domains()) {
|
||||
@if (domainService.data()?.domains) {
|
||||
<app-placeholder icon="@tui.globe">
|
||||
{{ 'No domains' | i18n }}
|
||||
</app-placeholder>
|
||||
@@ -32,10 +35,9 @@ import { DomainsItemComponent } from './item.component'
|
||||
i18nPipe,
|
||||
TableComponent,
|
||||
PlaceholderComponent,
|
||||
DomainsItemComponent,
|
||||
DomainItemComponent,
|
||||
],
|
||||
})
|
||||
export class DomainsTableComponent {
|
||||
// @TODO Alex proper types
|
||||
readonly domains = input<readonly any[] | null>()
|
||||
protected readonly domainService = inject(DomainService)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiCheckbox, TuiFade, TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { Session } from 'src/app/services/api/api.types'
|
||||
import { toAcmeName } from 'src/app/utils/acme'
|
||||
import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@@ -182,6 +181,4 @@ export class SessionsTableComponent<T extends Session> implements OnChanges {
|
||||
this.selected.update(selected => [...selected, session])
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly toAcmeName = toAcmeName
|
||||
}
|
||||
|
||||
@@ -38,16 +38,16 @@ export const SYSTEM_MENU = [
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: '@tui.globe',
|
||||
item: 'Domains',
|
||||
link: 'domains',
|
||||
},
|
||||
{
|
||||
icon: '@tui.door-open',
|
||||
item: 'Gateways',
|
||||
link: 'gateways',
|
||||
},
|
||||
{
|
||||
icon: '@tui.globe',
|
||||
item: 'Domains',
|
||||
link: 'domains',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ import { AuthService } from '../auth.service'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { WebSocketSubject } from 'rxjs/webSocket'
|
||||
import { toAcmeUrl } from 'src/app/utils/acme'
|
||||
import { toAuthorityUrl } from 'src/app/utils/acme'
|
||||
|
||||
import markdown from './md-sample.md'
|
||||
|
||||
@@ -1396,7 +1396,7 @@ export class MockApiService extends ApiService {
|
||||
op: PatchOp.ADD,
|
||||
path: `/serverInfo/acme`,
|
||||
value: {
|
||||
[toAcmeUrl(params.provider)]: { contact: params.contact },
|
||||
[toAuthorityUrl(params.provider)]: { contact: params.contact },
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { Mock } from './api.fixures'
|
||||
import { knownACME } from 'src/app/utils/acme'
|
||||
import { knownAuthorities } from 'src/app/utils/acme'
|
||||
const version = require('../../../../../../package.json').version
|
||||
|
||||
export const mockPatchData: DataModel = {
|
||||
@@ -28,7 +28,7 @@ export const mockPatchData: DataModel = {
|
||||
lastRegion: null,
|
||||
},
|
||||
acme: {
|
||||
[knownACME[0].url]: {
|
||||
[knownAuthorities[0].url]: {
|
||||
contact: ['mailto:support@start9.com'],
|
||||
},
|
||||
},
|
||||
@@ -160,6 +160,20 @@ export const mockPatchData: DataModel = {
|
||||
ntpServers: [],
|
||||
},
|
||||
},
|
||||
wireguard1: {
|
||||
public: false,
|
||||
ipInfo: {
|
||||
name: 'StartTunnel',
|
||||
scopeId: 2,
|
||||
deviceType: 'wireguard',
|
||||
subnets: [
|
||||
'10.0.90.12/24',
|
||||
'fe80::cd00:0000:0cde:1257:0000:211e:72cd/64',
|
||||
],
|
||||
wanIp: '203.0.113.45',
|
||||
ntpServers: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
unreadNotificationCount: 4,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
export function toAcmeName(url: string | null): string | 'System CA' {
|
||||
return knownACME.find(acme => acme.url === url)?.name || url || 'System CA'
|
||||
export function toAuthorityName(url: string | null): string | 'Local Root CA' {
|
||||
return (
|
||||
knownAuthorities.find(ca => ca.url === url)?.name || url || 'Local Root CA'
|
||||
)
|
||||
}
|
||||
|
||||
export function toAcmeUrl(name: string): string {
|
||||
return knownACME.find(acme => acme.name === name)?.url || name
|
||||
export function toAuthorityUrl(name: string): string {
|
||||
return knownAuthorities.find(ca => ca.name === name)?.url || name
|
||||
}
|
||||
|
||||
export const knownACME = [
|
||||
export const knownAuthorities = [
|
||||
{
|
||||
name: `Let's Encrypt`,
|
||||
url: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
|
||||
Reference in New Issue
Block a user