mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
better shared hostname approach, and improve look-feel of addresses tables
This commit is contained in:
1
web/projects/shared/assets/icons/letsencrypt.svg
Normal file
1
web/projects/shared/assets/icons/letsencrypt.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M22.7 17.21h-3.83v-1.975c0-1.572-1.3-2.86-2.86-2.86s-2.86 1.3-2.86 2.86v1.975H9.33v-1.975c0-3.708 3.023-6.7 6.7-6.7 3.708 0 6.7 3.023 6.7 6.7z" fill="#ffa400"/><path d="M24.282 17.21H7.758a1.27 1.27 0 0 0-1.29 1.29V30.7A1.27 1.27 0 0 0 7.758 32h16.524a1.27 1.27 0 0 0 1.29-1.29V18.5c-.04-.725-.605-1.3-1.3-1.3zm-7.456 8.02v1.652c0 .443-.363.846-.846.846-.443 0-.846-.363-.846-.846V25.23c-.524-.282-.846-.846-.846-1.49 0-.927.766-1.693 1.693-1.693s1.693.766 1.693 1.693c.04.645-.322 1.21-.846 1.49z" fill="#003a70"/><path d="M6.066 15.395h-4a1.17 1.17 0 0 1-1.169-1.169 1.17 1.17 0 0 1 1.169-1.169h4a1.17 1.17 0 0 1 1.169 1.169 1.17 1.17 0 0 1-1.169 1.169zm2.82-6.287a1.03 1.03 0 0 1-.725-.282l-3.144-2.58c-.484-.403-.564-1.128-.16-1.652.403-.484 1.128-.564 1.652-.16l3.144 2.58c.484.403.564 1.128.16 1.652-.282.282-.605.443-.927.443zm7.134-2.74a1.17 1.17 0 0 1-1.169-1.169V1.17A1.17 1.17 0 0 1 16.02 0a1.17 1.17 0 0 1 1.169 1.169V5.2a1.17 1.17 0 0 1-1.169 1.169zm7.093 2.74c-.322 0-.685-.16-.887-.443-.403-.484-.322-1.25.16-1.652l3.144-2.58c.484-.403 1.25-.322 1.652.16s.322 1.25-.16 1.652l-3.144 2.58a1.13 1.13 0 0 1-.766.282zm6.81 6.287h-4.03a1.17 1.17 0 0 1-1.169-1.169 1.17 1.17 0 0 1 1.169-1.169h4.03a1.17 1.17 0 0 1 1.169 1.169 1.17 1.17 0 0 1-1.169 1.169z" fill="#ffa400"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -705,4 +705,8 @@ export default {
|
|||||||
775: 'Diese Adresse funktioniert nicht aus Ihrem lokalen Netzwerk aufgrund einer Router-Hairpinning-Einschränkung',
|
775: 'Diese Adresse funktioniert nicht aus Ihrem lokalen Netzwerk aufgrund einer Router-Hairpinning-Einschränkung',
|
||||||
776: 'Aktion nicht gefunden',
|
776: 'Aktion nicht gefunden',
|
||||||
777: 'Diese Domain wird auch gelten für',
|
777: 'Diese Domain wird auch gelten für',
|
||||||
|
778: 'Plugin',
|
||||||
|
779: 'Öffentlich',
|
||||||
|
780: 'Privat',
|
||||||
|
781: 'Lokal',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -705,4 +705,8 @@ export const ENGLISH: Record<string, number> = {
|
|||||||
'This address will not work from your local network due to a router hairpinning limitation': 775,
|
'This address will not work from your local network due to a router hairpinning limitation': 775,
|
||||||
'Action not found': 776,
|
'Action not found': 776,
|
||||||
'This domain will also apply to': 777,
|
'This domain will also apply to': 777,
|
||||||
|
'Plugin': 778,
|
||||||
|
'Public': 779, // as in, publicly accessible
|
||||||
|
'Private': 780, // as in, privately accessible
|
||||||
|
'Local': 781, // as in, locally accessible
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -705,4 +705,8 @@ export default {
|
|||||||
775: 'Esta dirección no funcionará desde tu red local debido a una limitación de hairpinning del router',
|
775: 'Esta dirección no funcionará desde tu red local debido a una limitación de hairpinning del router',
|
||||||
776: 'Acción no encontrada',
|
776: 'Acción no encontrada',
|
||||||
777: 'Este dominio también se aplicará a',
|
777: 'Este dominio también se aplicará a',
|
||||||
|
778: 'Plugin',
|
||||||
|
779: 'Público',
|
||||||
|
780: 'Privado',
|
||||||
|
781: 'Local',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -705,4 +705,8 @@ export default {
|
|||||||
775: "Cette adresse ne fonctionnera pas depuis votre réseau local en raison d'une limitation de hairpinning du routeur",
|
775: "Cette adresse ne fonctionnera pas depuis votre réseau local en raison d'une limitation de hairpinning du routeur",
|
||||||
776: 'Action introuvable',
|
776: 'Action introuvable',
|
||||||
777: "Ce domaine s'appliquera également à",
|
777: "Ce domaine s'appliquera également à",
|
||||||
|
778: 'Plugin',
|
||||||
|
779: 'Public',
|
||||||
|
780: 'Privé',
|
||||||
|
781: 'Local',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -705,4 +705,8 @@ export default {
|
|||||||
775: 'Ten adres nie będzie działać z Twojej sieci lokalnej z powodu ograniczenia hairpinning routera',
|
775: 'Ten adres nie będzie działać z Twojej sieci lokalnej z powodu ograniczenia hairpinning routera',
|
||||||
776: 'Nie znaleziono akcji',
|
776: 'Nie znaleziono akcji',
|
||||||
777: 'Ta domena będzie również dotyczyć',
|
777: 'Ta domena będzie również dotyczyć',
|
||||||
|
778: 'Wtyczka',
|
||||||
|
779: 'Publiczny',
|
||||||
|
780: 'Prywatny',
|
||||||
|
781: 'Lokalny',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -30,19 +30,6 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
selector: 'td[actions]',
|
selector: 'td[actions]',
|
||||||
template: `
|
template: `
|
||||||
<div class="desktop">
|
<div class="desktop">
|
||||||
@if (address().ui) {
|
|
||||||
<a
|
|
||||||
tuiIconButton
|
|
||||||
appearance="flat-grayscale"
|
|
||||||
iconStart="@tui.external-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
[attr.href]="address().enabled ? address().url : null"
|
|
||||||
[class.disabled]="!address().enabled"
|
|
||||||
>
|
|
||||||
{{ 'Open UI' | i18n }}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
@if (address().deletable) {
|
@if (address().deletable) {
|
||||||
<button
|
<button
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
@@ -87,6 +74,19 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
{{ 'Address Requirements' | i18n }}
|
{{ 'Address Requirements' | i18n }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
@if (address().ui) {
|
||||||
|
<a
|
||||||
|
tuiIconButton
|
||||||
|
appearance="flat-grayscale"
|
||||||
|
iconStart="@tui.external-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
[attr.href]="address().enabled ? address().url : null"
|
||||||
|
[class.disabled]="!address().enabled"
|
||||||
|
>
|
||||||
|
{{ 'Open UI' | i18n }}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
<button
|
<button
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
appearance="flat-grayscale"
|
appearance="flat-grayscale"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { InterfaceAddressItemComponent } from './item.component'
|
|||||||
selector: 'section[gatewayGroup]',
|
selector: 'section[gatewayGroup]',
|
||||||
template: `
|
template: `
|
||||||
<header>
|
<header>
|
||||||
{{ gatewayGroup().gatewayName }}
|
{{ 'Gateway' | i18n }}: {{ gatewayGroup().gatewayName }}
|
||||||
<button
|
<button
|
||||||
tuiDropdown
|
tuiDropdown
|
||||||
tuiButton
|
tuiButton
|
||||||
@@ -57,7 +57,14 @@ import { InterfaceAddressItemComponent } from './item.component'
|
|||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<table
|
<table
|
||||||
[appTable]="['Enabled', 'Type', 'Certificate Authority', 'URL', null]"
|
[appTable]="[
|
||||||
|
null,
|
||||||
|
'Access',
|
||||||
|
'Type',
|
||||||
|
'Certificate Authority',
|
||||||
|
'URL',
|
||||||
|
null,
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
@for (address of gatewayGroup().addresses; track $index) {
|
@for (address of gatewayGroup().addresses; track $index) {
|
||||||
<tr
|
<tr
|
||||||
@@ -69,7 +76,7 @@ import { InterfaceAddressItemComponent } from './item.component'
|
|||||||
></tr>
|
></tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5">
|
<td colspan="6">
|
||||||
<app-placeholder icon="@tui.list-x">
|
<app-placeholder icon="@tui.list-x">
|
||||||
{{ 'No addresses' | i18n }}
|
{{ 'No addresses' | i18n }}
|
||||||
</app-placeholder>
|
</app-placeholder>
|
||||||
@@ -132,7 +139,7 @@ export class InterfaceAddressesComponent {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
note: await this.getSharedHostNote(),
|
note: this.getSharedHostNote(),
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: this.i18n.transform('Save')!,
|
text: this.i18n.transform('Save')!,
|
||||||
@@ -191,7 +198,7 @@ export class InterfaceAddressesComponent {
|
|||||||
size: 's',
|
size: 's',
|
||||||
data: {
|
data: {
|
||||||
spec: await configBuilderToSpec(addSpec),
|
spec: await configBuilderToSpec(addSpec),
|
||||||
note: await this.getSharedHostNote(),
|
note: this.getSharedHostNote(),
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: this.i18n.transform('Save')!,
|
text: this.i18n.transform('Save')!,
|
||||||
@@ -235,26 +242,11 @@ export class InterfaceAddressesComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSharedHostNote(): Promise<string> {
|
private getSharedHostNote(): string {
|
||||||
const iface = this.value()
|
const names = this.value()?.sharedHostNames
|
||||||
const pkgId = this.packageId()
|
if (!names?.length) return ''
|
||||||
if (!iface || !pkgId) return ''
|
|
||||||
|
|
||||||
const pkg = await firstValueFrom(
|
return `${this.i18n.transform('This domain will also apply to')} ${names.join(', ')}`
|
||||||
this.patch.watch$('packageData', pkgId),
|
|
||||||
)
|
|
||||||
if (!pkg) return ''
|
|
||||||
|
|
||||||
const hostId = iface.addressInfo.hostId
|
|
||||||
const otherNames = Object.values(pkg.serviceInterfaces)
|
|
||||||
.filter(
|
|
||||||
si => si.addressInfo.hostId === hostId && si.id !== iface.id,
|
|
||||||
)
|
|
||||||
.map(si => si.name)
|
|
||||||
|
|
||||||
if (!otherNames.length) return ''
|
|
||||||
|
|
||||||
return `${this.i18n.transform('This domain will also apply to')} ${otherNames.join(', ')}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async savePublicDomain(
|
private async savePublicDomain(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
|
|||||||
import { TuiObfuscatePipe } from '@taiga-ui/cdk'
|
import { TuiObfuscatePipe } from '@taiga-ui/cdk'
|
||||||
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { TuiSwitch } from '@taiga-ui/kit'
|
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { GatewayAddress, MappedServiceInterface } from '../interface.service'
|
import { GatewayAddress, MappedServiceInterface } from '../interface.service'
|
||||||
import { AddressActionsComponent } from './actions.component'
|
import { AddressActionsComponent } from './actions.component'
|
||||||
@@ -36,22 +36,51 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
(ngModelChange)="onToggleEnabled()"
|
(ngModelChange)="onToggleEnabled()"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="type">
|
<td class="access">
|
||||||
<tui-icon
|
<tui-icon
|
||||||
[icon]="address.access === 'public' ? '@tui.globe' : '@tui.house'"
|
[icon]="address.access === 'public' ? '@tui.globe' : '@tui.house'"
|
||||||
/>
|
/>
|
||||||
{{ address.type }}
|
<span>
|
||||||
|
{{ (address.access === 'public' ? 'Public' : 'Local') | i18n }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="type">
|
||||||
|
<tui-badge
|
||||||
|
size="s"
|
||||||
|
[appearance]="typeAppearance(address.hostnameInfo.metadata.kind)"
|
||||||
|
>
|
||||||
|
{{ address.type }}
|
||||||
|
</tui-badge>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ address.certificate }}
|
<div class="cert">
|
||||||
|
@if (address.certificate === 'Root CA') {
|
||||||
|
<img src="assets/icons/favicon.svg" alt="" class="cert-icon" />
|
||||||
|
} @else if (address.certificate.startsWith("Let's Encrypt")) {
|
||||||
|
<img src="assets/icons/letsencrypt.svg" alt="" class="cert-icon" />
|
||||||
|
} @else if (
|
||||||
|
address.certificate !== '-' && address.certificate !== 'Self signed'
|
||||||
|
) {
|
||||||
|
<tui-icon icon="@tui.shield" class="cert-icon" />
|
||||||
|
}
|
||||||
|
{{ address.certificate }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="url">
|
<div class="url">
|
||||||
<span
|
@if (address.masked && currentlyMasked()) {
|
||||||
[title]="address.masked && currentlyMasked() ? '' : address.url"
|
<span>{{ address.url | tuiObfuscate: 'mask' }}</span>
|
||||||
>
|
} @else {
|
||||||
{{ address.url | tuiObfuscate: recipe() }}
|
<span [title]="address.url">
|
||||||
</span>
|
@if (urlParts(); as parts) {
|
||||||
|
{{ parts.prefix }}
|
||||||
|
<b>{{ parts.hostname }}</b>
|
||||||
|
{{ parts.suffix }}
|
||||||
|
} @else {
|
||||||
|
{{ address.url }}
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
@if (address.masked) {
|
@if (address.masked) {
|
||||||
<button
|
<button
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
@@ -81,12 +110,28 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
grid-template-columns: fit-content(10rem) 1fr 2rem 2rem;
|
grid-template-columns: fit-content(10rem) 1fr 2rem 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type tui-icon {
|
.access tui-icon {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
margin-right: 0.7rem;
|
margin-right: 0.7rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cert {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-icon {
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tui-icon.cert-icon {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.url {
|
.url {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -104,6 +149,7 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
|
|
||||||
:host-context(tui-root._mobile) {
|
:host-context(tui-root._mobile) {
|
||||||
padding-inline-start: 0.75rem !important;
|
padding-inline-start: 0.75rem !important;
|
||||||
|
row-gap: 0.25rem;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -129,18 +175,32 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(2) {
|
.access {
|
||||||
|
padding-right: 0;
|
||||||
|
font: var(--tui-font-text-m);
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
tui-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
font: var(--tui-font-text-m);
|
font: var(--tui-font-text-m);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--tui-text-primary);
|
color: var(--tui-text-primary);
|
||||||
padding-inline-end: 0.5rem;
|
padding-inline-end: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(3) {
|
td:nth-child(4) {
|
||||||
grid-area: 2 / 1 / 2 / 3;
|
grid-area: 2 / 1 / 2 / 3;
|
||||||
|
|
||||||
|
.cert-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(4) {
|
td:nth-child(5) {
|
||||||
grid-area: 3 / 1 / 3 / 3;
|
grid-area: 3 / 1 / 3 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +214,7 @@ import { DomainHealthService } from './domain-health.service'
|
|||||||
imports: [
|
imports: [
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
AddressActionsComponent,
|
AddressActionsComponent,
|
||||||
|
TuiBadge,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiIcon,
|
TuiIcon,
|
||||||
TuiObfuscatePipe,
|
TuiObfuscatePipe,
|
||||||
@@ -180,6 +241,33 @@ export class InterfaceAddressItemComponent {
|
|||||||
this.address()?.masked && this.currentlyMasked() ? 'mask' : 'none',
|
this.address()?.masked && this.currentlyMasked() ? 'mask' : 'none',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
readonly urlParts = computed(() => {
|
||||||
|
const { url, hostnameInfo } = this.address()
|
||||||
|
const idx = url.indexOf(hostnameInfo.hostname)
|
||||||
|
if (idx === -1) return null
|
||||||
|
return {
|
||||||
|
prefix: url.slice(0, idx),
|
||||||
|
hostname: hostnameInfo.hostname,
|
||||||
|
suffix: url.slice(idx + hostnameInfo.hostname.length),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
typeAppearance(kind: string): string {
|
||||||
|
switch (kind) {
|
||||||
|
case 'public-domain':
|
||||||
|
case 'private-domain':
|
||||||
|
return 'info'
|
||||||
|
case 'mdns':
|
||||||
|
return 'positive'
|
||||||
|
case 'ipv4':
|
||||||
|
return 'warning'
|
||||||
|
case 'ipv6':
|
||||||
|
return 'neutral'
|
||||||
|
default:
|
||||||
|
return 'neutral'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onToggleEnabled() {
|
async onToggleEnabled() {
|
||||||
const addr = this.address()
|
const addr = this.address()
|
||||||
const iface = this.value()
|
const iface = this.value()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
@if (pluginGroup().pluginPkgInfo; as pkgInfo) {
|
@if (pluginGroup().pluginPkgInfo; as pkgInfo) {
|
||||||
<img [src]="pkgInfo.icon" alt="" class="plugin-icon" />
|
<img [src]="pkgInfo.icon" alt="" class="plugin-icon" />
|
||||||
}
|
}
|
||||||
{{ pluginGroup().pluginName }}
|
{{ 'Plugin' | i18n }}: {{ pluginGroup().pluginName }}
|
||||||
@if (pluginGroup().tableAction; as action) {
|
@if (pluginGroup().tableAction; as action) {
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ function getAddressType(h: T.HostnameInfo): string {
|
|||||||
return 'IPv6'
|
return 'IPv6'
|
||||||
case 'public-domain':
|
case 'public-domain':
|
||||||
case 'private-domain':
|
case 'private-domain':
|
||||||
return h.hostname
|
return 'Domain'
|
||||||
case 'mdns':
|
case 'mdns':
|
||||||
return 'mDNS'
|
return 'mDNS'
|
||||||
case 'plugin':
|
case 'plugin':
|
||||||
@@ -337,4 +337,5 @@ export type MappedServiceInterface = T.ServiceInterface & {
|
|||||||
gatewayGroups: GatewayAddressGroup[]
|
gatewayGroups: GatewayAddressGroup[]
|
||||||
pluginGroups: PluginAddressGroup[]
|
pluginGroups: PluginAddressGroup[]
|
||||||
addSsl: boolean
|
addSsl: boolean
|
||||||
|
sharedHostNames: string[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ export default class ServiceInterfaceRoute {
|
|||||||
const binding = host.bindings[port]
|
const binding = host.bindings[port]
|
||||||
const gateways = this.gatewayService.gateways() || []
|
const gateways = this.gatewayService.gateways() || []
|
||||||
|
|
||||||
|
const sharedHostNames = Object.values(serviceInterfaces)
|
||||||
|
.filter(si => si.addressInfo.hostId === key && si.id !== iFace.id)
|
||||||
|
.map(si => si.name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...iFace,
|
...iFace,
|
||||||
gatewayGroups: this.interfaceService.getGatewayGroups(
|
gatewayGroups: this.interfaceService.getGatewayGroups(
|
||||||
@@ -132,8 +136,13 @@ export default class ServiceInterfaceRoute {
|
|||||||
host,
|
host,
|
||||||
gateways,
|
gateways,
|
||||||
),
|
),
|
||||||
pluginGroups: this.interfaceService.getPluginGroups(iFace, host, this.allPackageData()),
|
pluginGroups: this.interfaceService.getPluginGroups(
|
||||||
|
iFace,
|
||||||
|
host,
|
||||||
|
this.allPackageData(),
|
||||||
|
),
|
||||||
addSsl: !!binding?.options.addSsl,
|
addSsl: !!binding?.options.addSsl,
|
||||||
|
sharedHostNames,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,7 @@ export default class StartOsUiComponent {
|
|||||||
|
|
||||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
|
|
||||||
readonly network = toSignal(
|
readonly network = toSignal(this.patch.watch$('serverInfo', 'network'))
|
||||||
this.patch.watch$('serverInfo', 'network'),
|
|
||||||
)
|
|
||||||
|
|
||||||
readonly allPackageData = toSignal(this.patch.watch$('packageData'))
|
readonly allPackageData = toSignal(this.patch.watch$('packageData'))
|
||||||
|
|
||||||
@@ -98,6 +96,7 @@ export default class StartOsUiComponent {
|
|||||||
this.allPackageData(),
|
this.allPackageData(),
|
||||||
),
|
),
|
||||||
addSsl: true,
|
addSsl: true,
|
||||||
|
sharedHostNames: [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -652,7 +652,7 @@ export const mockPatchData: DataModel = {
|
|||||||
publicDomains: {
|
publicDomains: {
|
||||||
'bitcoin.example.com': {
|
'bitcoin.example.com': {
|
||||||
gateway: 'eth0',
|
gateway: 'eth0',
|
||||||
acme: null,
|
acme: 'https://acme-v02.api.letsencrypt.org/directory',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
privateDomains: {
|
privateDomains: {
|
||||||
|
|||||||
Reference in New Issue
Block a user