mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
refactor: fix multiple comments (#3013)
* refactor: fix multiple comments * styling changes, add documentation to sidebar * translations for dns page * refactor: subtle colors * rearrange service page --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -36,7 +36,7 @@ import { MarketplaceItemComponent } from './item.component'
|
||||
<marketplace-item
|
||||
[style.pointer-events]="'none'"
|
||||
[data]="pkg().sdkVersion || 'Unknown'"
|
||||
label="SDK Version"
|
||||
label="SDK version"
|
||||
icon=""
|
||||
/>
|
||||
<!-- git hash -->
|
||||
|
||||
@@ -491,7 +491,7 @@ export default {
|
||||
518: 'Verwerfen',
|
||||
520: 'Update verfügbar',
|
||||
521: 'Um das Problem zu beheben, siehe',
|
||||
522: 'SDK Version',
|
||||
522: 'SDK version',
|
||||
523: 'Sicherungsbericht',
|
||||
524: 'Ausgewählte löschen',
|
||||
525: 'Keine SSH-Schlüssel',
|
||||
@@ -577,4 +577,12 @@ export default {
|
||||
610: 'Dynamisches DNS',
|
||||
611: 'Keine Service-Schnittstellen',
|
||||
612: 'Grund',
|
||||
613: 'Private Gateways für die StartOS-Benutzeroberfläche können nicht deaktiviert werden',
|
||||
614: 'CA-Fingerabdruck',
|
||||
615: 'DHCP-Server',
|
||||
616: 'DHCP-Server können nicht bearbeitet werden',
|
||||
617: 'Statisch',
|
||||
618: 'Statische Server',
|
||||
619: 'Warnung. StartOS verwendet derzeit das folgende Gateway für DNS',
|
||||
620: 'Wenn Sie dieses Gateway für die Auflösung privater Domains verwenden möchten, legen Sie alternative statische DNS-Server mit dem obigen Formular fest.',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -254,9 +254,9 @@ export const ENGLISH = {
|
||||
'unknown %': 270,
|
||||
'Not provided': 271,
|
||||
'Links': 272,
|
||||
'Git Hash': 273,
|
||||
'Git hash': 273,
|
||||
'License': 274,
|
||||
'Installed From': 275,
|
||||
'Installed from': 275,
|
||||
'Marketing': 278,
|
||||
'Support': 279,
|
||||
'Donations': 280,
|
||||
@@ -490,7 +490,7 @@ export const ENGLISH = {
|
||||
'Dismiss': 518, // as in, dismiss or delete a task
|
||||
'Update available': 520,
|
||||
'To resolve the issue, refer to': 521,
|
||||
'SDK Version': 522,
|
||||
'SDK version': 522,
|
||||
'Backup Report': 523,
|
||||
'Delete selected': 524,
|
||||
'No SSH keys': 525,
|
||||
@@ -576,4 +576,12 @@ export const ENGLISH = {
|
||||
'Dynamic DNS': 610,
|
||||
'No service interfaces': 611, // as in, there are no available interfaces (API, UI, etc) for this software application
|
||||
'Reason': 612, // as in, an explanation for something
|
||||
'Cannot disable private gateways for StartOS UI': 613,
|
||||
'CA fingerprint': 614, // as in, the unique, fixed-length digital identifier generated from a certificate's data using a cryptographic hash function
|
||||
'DHCP Servers': 615,
|
||||
'Cannot edit DHCP servers': 616,
|
||||
'Static': 617, // as in, unchanging
|
||||
'Static Servers': 618, // as in, servers that do not change
|
||||
'Warning. StartOS is currently using the following gateway for DNS': 619,
|
||||
'If you intend to use this gateway for private domain resolution, set alternative static DNS servers using the form above.': 620,
|
||||
} as const
|
||||
|
||||
@@ -577,4 +577,12 @@ export default {
|
||||
610: 'DNS dinámico',
|
||||
611: 'Sin interfaces de servicio',
|
||||
612: 'Razón',
|
||||
613: 'No se pueden deshabilitar las puertas de enlace privadas para la interfaz de usuario de StartOS',
|
||||
614: 'Huella digital de la CA',
|
||||
615: 'Servidores DHCP',
|
||||
616: 'No se pueden editar los servidores DHCP',
|
||||
617: 'Estático',
|
||||
618: 'Servidores estáticos',
|
||||
619: 'Advertencia. StartOS está utilizando actualmente la siguiente puerta de enlace para DNS',
|
||||
620: 'Si deseas usar esta puerta de enlace para la resolución de dominios privados, configura servidores DNS estáticos alternativos usando el formulario anterior.',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -577,4 +577,12 @@ export default {
|
||||
610: 'DNS dynamique',
|
||||
611: 'Aucune interface de service',
|
||||
612: 'Raison',
|
||||
613: "Impossible de désactiver les passerelles privées pour l'interface utilisateur StartOS",
|
||||
614: 'Empreinte de l’AC',
|
||||
615: 'Serveurs DHCP',
|
||||
616: 'Impossible de modifier les serveurs DHCP',
|
||||
617: 'Statique',
|
||||
618: 'Serveurs statiques',
|
||||
619: 'Avertissement. StartOS utilise actuellement la passerelle suivante pour le DNS',
|
||||
620: 'Si vous souhaitez utiliser cette passerelle pour la résolution de domaines privés, définissez des serveurs DNS statiques alternatifs à l’aide du formulaire ci-dessus.',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -577,4 +577,12 @@ export default {
|
||||
610: 'Dynamiczny DNS',
|
||||
611: 'Brak interfejsów usług',
|
||||
612: 'Powód',
|
||||
613: 'Nie można wyłączyć prywatnych bram dla interfejsu użytkownika StartOS',
|
||||
614: 'Odcisk palca CA',
|
||||
615: 'Serwery DHCP',
|
||||
616: 'Nie można edytować serwerów DHCP',
|
||||
617: 'Statyczny',
|
||||
618: 'Serwery statyczne',
|
||||
619: 'Ostrzeżenie. StartOS obecnie używa następującej bramy do DNS',
|
||||
620: 'Jeśli zamierzasz używać tej bramy do rozwiązywania domen prywatnych, ustaw alternatywne statyczne serwery DNS za pomocą powyższego formularza.',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -88,13 +88,6 @@
|
||||
--start9-base-5: rgba(60, 62, 64, 1);
|
||||
}
|
||||
|
||||
[tuiAppearance][data-appearance^='primary']:not([tuiCheckbox]._readonly) {
|
||||
@include taiga.appearance-disabled {
|
||||
background: var(--tui-status-neutral);
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
[tuiAppearance][data-appearance='primary-success'] {
|
||||
color: var(--tui-text-primary-on-accent-1);
|
||||
background: var(--tui-status-positive);
|
||||
@@ -108,8 +101,7 @@
|
||||
}
|
||||
|
||||
@include taiga.appearance-disabled {
|
||||
background: var(--tui-status-neutral);
|
||||
color: #333;
|
||||
opacity: var(--tui-disabled-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
</div>
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>Git Hash</strong>
|
||||
<strong>{{ 'Git hash' | i18n }}</strong>
|
||||
<div tuiSubtitle tuiFade>{{ gitHash }}</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -35,7 +35,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
</div>
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>CA fingerprint</strong>
|
||||
<strong>{{ 'CA fingerprint' | i18n }}</strong>
|
||||
<div tuiSubtitle tuiFade>{{ server.caFingerprint }}</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
input,
|
||||
inject,
|
||||
} from '@angular/core'
|
||||
import { TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiSkeleton, TuiSwitch } from '@taiga-ui/kit'
|
||||
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiSkeleton, TuiSwitch, TuiTooltip } from '@taiga-ui/kit'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { i18nPipe, LoadingService, ErrorService } from '@start9labs/shared'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
@@ -19,8 +19,15 @@ import { InterfaceComponent } from './interface.component'
|
||||
template: `
|
||||
<header>{{ 'Gateways' | i18n }}</header>
|
||||
@for (gateway of gateways(); track $index) {
|
||||
<label tuiCell="s">
|
||||
<span tuiTitle>{{ gateway.ipInfo.name }}</span>
|
||||
<label tuiCell="s" [style.background]="">
|
||||
<span tuiTitle [style.opacity]="1">{{ gateway.ipInfo.name }}</span>
|
||||
@if (!interface.packageId() && !gateway.public) {
|
||||
<tui-icon
|
||||
[tuiTooltip]="
|
||||
'Cannot disable private gateways for StartOS UI' | i18n
|
||||
"
|
||||
/>
|
||||
}
|
||||
<input
|
||||
type="checkbox"
|
||||
tuiSwitch
|
||||
@@ -47,6 +54,10 @@ import { InterfaceComponent } from './interface.component'
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
[tuiCell]:has([tuiTooltip]) {
|
||||
background: none !important;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -58,6 +69,8 @@ import { InterfaceComponent } from './interface.component'
|
||||
TuiCell,
|
||||
TuiTitle,
|
||||
TuiSkeleton,
|
||||
TuiIcon,
|
||||
TuiTooltip,
|
||||
],
|
||||
})
|
||||
export class InterfaceGatewaysComponent {
|
||||
|
||||
@@ -52,7 +52,7 @@ import { MarketplaceSidebarService } from '../services/sidebar.service'
|
||||
|
||||
:host {
|
||||
cursor: pointer;
|
||||
animation: animateIn 400ms calc(var(--animation-order) * 200ms) both;
|
||||
animation: animateIn 400ms calc(var(--animation-order) * 50ms) both;
|
||||
}
|
||||
|
||||
tui-drawer {
|
||||
|
||||
@@ -31,7 +31,7 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
styles: `
|
||||
:host {
|
||||
min-height: 12rem;
|
||||
grid-column: span 3;
|
||||
grid-column: span 4;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
|
||||
@@ -3,48 +3,48 @@ import {
|
||||
Component,
|
||||
inject,
|
||||
Input,
|
||||
DOCUMENT,
|
||||
} from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiBadge } from '@taiga-ui/kit'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { InterfaceService } from 'src/app/routes/portal/components/interfaces/interface.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { InterfaceService } from '../../../components/interfaces/interface.service'
|
||||
|
||||
@Component({
|
||||
selector: 'tr[serviceInterface]',
|
||||
template: `
|
||||
<td>
|
||||
<strong>{{ info.name }}</strong>
|
||||
</td>
|
||||
<td><ng-content /></td>
|
||||
<td>
|
||||
<tui-badge size="m" [appearance]="appearance">{{ info.type }}</tui-badge>
|
||||
</td>
|
||||
<td class="g-secondary" [style.grid-area]="'2 / span 4'">
|
||||
<td class="g-secondary" [style.grid-area]="'2 / 1 / 2 / 3'">
|
||||
{{ info.description }}
|
||||
</td>
|
||||
<td>
|
||||
@if (info.type === 'ui') {
|
||||
<button
|
||||
<a
|
||||
tuiIconButton
|
||||
iconStart="@tui.external-link"
|
||||
appearance="flat-grayscale"
|
||||
[disabled]="disabled"
|
||||
(click)="openUI()"
|
||||
></button>
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
[attr.href]="disabled ? null : href"
|
||||
(click.stop)="(0)"
|
||||
></a>
|
||||
}
|
||||
<a
|
||||
tuiIconButton
|
||||
iconStart="@tui.settings"
|
||||
appearance="flat-grayscale"
|
||||
[routerLink]="info.routerLink"
|
||||
></a>
|
||||
</td>
|
||||
`,
|
||||
styles: `
|
||||
strong {
|
||||
:host {
|
||||
clip-path: inset(0 round 0.75rem);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--tui-background-neutral-1);
|
||||
}
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ import { InterfaceService } from '../../../components/interfaces/interface.servi
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
grid-area: 3 / span 4;
|
||||
grid-area: 1 / 3 / span 2 / 3;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
flex-direction: row-reverse;
|
||||
@@ -68,7 +68,7 @@ import { InterfaceService } from '../../../components/interfaces/interface.servi
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content) 1fr;
|
||||
grid-template-columns: min-content;
|
||||
align-items: center;
|
||||
padding: 1rem 0.5rem;
|
||||
gap: 0.5rem;
|
||||
@@ -80,16 +80,13 @@ import { InterfaceService } from '../../../components/interfaces/interface.servi
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, TuiBadge, RouterLink],
|
||||
imports: [TuiButton, TuiBadge],
|
||||
})
|
||||
export class ServiceInterfaceItemComponent {
|
||||
private readonly interfaceService = inject(InterfaceService)
|
||||
private readonly document = inject(DOCUMENT)
|
||||
|
||||
@Input({ required: true })
|
||||
info!: T.ServiceInterface & {
|
||||
routerLink: string
|
||||
}
|
||||
info!: T.ServiceInterface
|
||||
|
||||
@Input({ required: true })
|
||||
pkg!: PackageDataEntry
|
||||
@@ -110,11 +107,9 @@ export class ServiceInterfaceItemComponent {
|
||||
|
||||
get href() {
|
||||
const host = this.pkg.hosts[this.info.addressInfo.hostId]
|
||||
if (!host) return ''
|
||||
return this.interfaceService.launchableAddress(this.info, host)
|
||||
}
|
||||
|
||||
openUI() {
|
||||
this.document.defaultView?.open(this.href, '_blank', 'noreferrer')
|
||||
return host
|
||||
? this.interfaceService.launchableAddress(this.info, host)
|
||||
: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
computed,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { TuiTable } from '@taiga-ui/addon-table'
|
||||
import { tuiDefaultSort } from '@taiga-ui/cdk'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
@@ -27,11 +28,17 @@ import { PlaceholderComponent } from '../../../components/placeholder.component'
|
||||
<tbody>
|
||||
@for (info of interfaces(); track $index) {
|
||||
<tr
|
||||
tabindex="-1"
|
||||
serviceInterface
|
||||
[info]="info"
|
||||
[pkg]="pkg()"
|
||||
[disabled]="disabled()"
|
||||
></tr>
|
||||
[routerLink]="info.routerLink"
|
||||
>
|
||||
<a [routerLink]="info.routerLink">
|
||||
<strong>{{ info.name }}</strong>
|
||||
</a>
|
||||
</tr>
|
||||
} @empty {
|
||||
<app-placeholder icon="@tui.monitor-x">
|
||||
{{ 'No service interfaces' | i18n }}
|
||||
@@ -42,7 +49,7 @@ import { PlaceholderComponent } from '../../../components/placeholder.component'
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
grid-column: span 6;
|
||||
grid-column: span 7;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
@@ -52,6 +59,7 @@ import { PlaceholderComponent } from '../../../components/placeholder.component'
|
||||
TuiTable,
|
||||
i18nPipe,
|
||||
PlaceholderComponent,
|
||||
RouterLink,
|
||||
],
|
||||
})
|
||||
export class ServiceInterfacesComponent {
|
||||
|
||||
@@ -50,7 +50,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
size="m"
|
||||
[max]="100"
|
||||
[class.g-positive]="phase.progress === true"
|
||||
[value]="isIndeterminate(phase.progress) ? undefined : percent"
|
||||
[attr.value]="isIndeterminate(phase.progress) ? undefined : percent"
|
||||
></progress>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
grid-column: span 2;
|
||||
grid-column: span 3;
|
||||
min-height: 12rem;
|
||||
}
|
||||
|
||||
@@ -78,14 +78,14 @@ import {
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
:host {
|
||||
min-height: 0;
|
||||
}
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
div {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
text-align: left;
|
||||
|
||||
@@ -23,35 +23,26 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
@Component({
|
||||
selector: 'tr[task]',
|
||||
template: `
|
||||
<td tuiFade>
|
||||
<td tuiFade class="row">
|
||||
<tui-avatar size="xs"><img [src]="pkg()?.icon" alt="" /></tui-avatar>
|
||||
<span>{{ pkgTitle() }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ pkg()?.actions?.[task().actionId]?.name }}
|
||||
<td [style.grid-row]="2">
|
||||
<strong>{{ pkg()?.actions?.[task().actionId]?.name }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<td class="row">
|
||||
@if (task().severity === 'critical') {
|
||||
<strong [style.color]="'var(--tui-status-warning)'">
|
||||
{{ 'Required' | i18n }}
|
||||
</strong>
|
||||
<strong class="g-warning">{{ 'Required' | i18n }}</strong>
|
||||
} @else if (task().severity === 'important') {
|
||||
<strong [style.color]="'var(--tui-status-info)'">
|
||||
{{ 'Recommended' | i18n }}
|
||||
</strong>
|
||||
<strong class="g-info">{{ 'Recommended' | i18n }}</strong>
|
||||
} @else {
|
||||
<strong>
|
||||
{{ 'Optional' | i18n }}
|
||||
</strong>
|
||||
<strong>{{ 'Optional' | i18n }}</strong>
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
[style.color]="'var(--tui-text-secondary)'"
|
||||
[style.grid-area]="'2 / span 4'"
|
||||
>
|
||||
<td class="g-secondary" [style.grid-row]="3">
|
||||
{{ task().reason || ('No reason provided' | i18n) }}
|
||||
</td>
|
||||
<td>
|
||||
<td [style.grid-area]="'2 / 2 / 4'">
|
||||
@if (task().severity !== 'critical') {
|
||||
<button
|
||||
tuiIconButton
|
||||
@@ -76,24 +67,25 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
grid-area: 3 / span 4;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-inline-start: 0.5rem;
|
||||
line-height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
padding: 1rem 0rem 1rem 0.5rem;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 1fr min-content;
|
||||
padding: 1rem 0.5rem;
|
||||
|
||||
.row {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
td {
|
||||
display: flex;
|
||||
|
||||
@@ -39,7 +39,7 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
styles: `
|
||||
:host {
|
||||
min-height: 12rem;
|
||||
grid-column: span 6;
|
||||
grid-column: span 10;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
|
||||
@@ -32,7 +32,7 @@ import { distinctUntilChanged } from 'rxjs/operators'
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
grid-column: span 4;
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@@ -55,11 +55,13 @@ import { distinctUntilChanged } from 'rxjs/operators'
|
||||
text-transform: uppercase;
|
||||
color: var(--tui-text-secondary);
|
||||
font: var(--tui-font-text-ui-xs);
|
||||
font-size: min(4cqw, 0.75rem);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: min(6vw, 2.5rem);
|
||||
font-size: min(20cqw, 2.5rem);
|
||||
margin: 1rem 0;
|
||||
color: var(--tui-text-primary);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiComparator, TuiTable } from '@taiga-ui/addon-table'
|
||||
import { TuiButton, TuiLoader } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map, shareReplay } from 'rxjs'
|
||||
import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
import { DepErrorService } from 'src/app/services/dep-error.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { getInstalledPrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { getManifest } from 'src/app/utils/get-package-data'
|
||||
|
||||
import { ServiceComponent } from './service.component'
|
||||
import { ServicesService } from './services.service'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -124,13 +129,17 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export default class DashboardComponent {
|
||||
readonly services = toSignal(inject(ServicesService))
|
||||
readonly errors = toSignal(inject(DepErrorService).depErrors$)
|
||||
readonly services = toSignal(
|
||||
inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('packageData')
|
||||
.pipe(
|
||||
map(pkgs => Object.values(pkgs).sort(byName)),
|
||||
shareReplay(1),
|
||||
),
|
||||
)
|
||||
|
||||
readonly name: TuiComparator<PackageDataEntry> = (a, b) =>
|
||||
getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase()
|
||||
? -1
|
||||
: 1
|
||||
readonly name: TuiComparator<PackageDataEntry> = byName
|
||||
|
||||
readonly status: TuiComparator<PackageDataEntry> = (a, b) =>
|
||||
getInstalledPrimaryStatus(b) > getInstalledPrimaryStatus(a) ? -1 : 1
|
||||
@@ -140,3 +149,9 @@ export default class DashboardComponent {
|
||||
|
||||
sorter = this.name
|
||||
}
|
||||
|
||||
function byName(a: PackageDataEntry, b: PackageDataEntry) {
|
||||
return getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase()
|
||||
? -1
|
||||
: 1
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map, Observable, shareReplay } from 'rxjs'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { getManifest } from 'src/app/utils/get-package-data'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ServicesService extends Observable<readonly PackageDataEntry[]> {
|
||||
private readonly services$ = inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('packageData')
|
||||
.pipe(
|
||||
map(pkgs =>
|
||||
Object.values(pkgs).sort((a, b) =>
|
||||
getManifest(b).title.toLowerCase() >
|
||||
getManifest(a).title.toLowerCase()
|
||||
? -1
|
||||
: 1,
|
||||
),
|
||||
),
|
||||
shareReplay(1),
|
||||
)
|
||||
|
||||
constructor() {
|
||||
super(subscriber => this.services$.subscribe(subscriber))
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,14 @@ export default class ServiceAboutRoute {
|
||||
{
|
||||
header: 'General',
|
||||
items: [
|
||||
{
|
||||
name: 'Title',
|
||||
value: manifest.title,
|
||||
},
|
||||
{
|
||||
name: 'ID' as i18nKey,
|
||||
value: manifest.id,
|
||||
},
|
||||
{
|
||||
name: 'Version',
|
||||
value: manifest.version,
|
||||
@@ -82,18 +90,14 @@ export default class ServiceAboutRoute {
|
||||
action: () => this.copyService.copy(manifest.version),
|
||||
},
|
||||
{
|
||||
name: 'Installed From',
|
||||
value: pkg.registry || NOT_PROVIDED,
|
||||
},
|
||||
{
|
||||
name: 'Git Hash',
|
||||
name: 'Git hash',
|
||||
value: manifest.gitHash || '-',
|
||||
icon: manifest.gitHash ? '@tui.copy' : '',
|
||||
action: () =>
|
||||
manifest.gitHash && this.copyService.copy(manifest.gitHash),
|
||||
},
|
||||
{
|
||||
name: 'SDK Version',
|
||||
name: 'SDK version',
|
||||
value: manifest.sdkVersion || '-',
|
||||
icon: manifest.sdkVersion ? '@tui.copy' : '',
|
||||
action: () =>
|
||||
@@ -106,6 +110,10 @@ export default class ServiceAboutRoute {
|
||||
icon: '@tui.chevron-right',
|
||||
action: () => this.markdown.subscribe(),
|
||||
},
|
||||
{
|
||||
name: 'Installed from',
|
||||
value: pkg.registry || NOT_PROVIDED,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -124,10 +132,6 @@ export default class ServiceAboutRoute {
|
||||
{
|
||||
header: 'Links',
|
||||
items: [
|
||||
{
|
||||
name: 'Marketing',
|
||||
value: manifest.marketingSite || NOT_PROVIDED,
|
||||
},
|
||||
{
|
||||
name: 'Documentation',
|
||||
value: manifest.docsUrl || NOT_PROVIDED,
|
||||
@@ -136,6 +140,10 @@ export default class ServiceAboutRoute {
|
||||
name: 'Support',
|
||||
value: manifest.supportSite || NOT_PROVIDED,
|
||||
},
|
||||
{
|
||||
name: 'Marketing',
|
||||
value: manifest.marketingSite || NOT_PROVIDED,
|
||||
},
|
||||
{
|
||||
name: 'Donations',
|
||||
value: manifest.donationUrl || NOT_PROVIDED,
|
||||
|
||||
@@ -31,10 +31,16 @@ const INACTIVE: PrimaryStatus[] = [
|
||||
@Component({
|
||||
template: `
|
||||
@if (service()) {
|
||||
<div *title class="title">
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
<div routerLink="./" class="m-header">
|
||||
<tui-avatar size="xs" [style.margin-inline-end.rem]="0.75">
|
||||
<div
|
||||
*title
|
||||
class="title"
|
||||
[style.--background]="'url(' + service()?.icon + ')'"
|
||||
>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
<div routerLink="./">
|
||||
<tui-avatar size="xs" [style.margin]="'0 0.75rem 0.125rem 0'">
|
||||
<img alt="" [src]="service()?.icon" />
|
||||
</tui-avatar>
|
||||
<span tuiFade>{{ manifest()?.title }}</span>
|
||||
@@ -45,33 +51,52 @@ const INACTIVE: PrimaryStatus[] = [
|
||||
<tui-avatar><img alt="" [src]="service()?.icon" /></tui-avatar>
|
||||
<span tuiTitle>
|
||||
<strong tuiFade>{{ manifest()?.title }}</strong>
|
||||
<span tuiSubtitle [style.textTransform]="'none'">
|
||||
{{ manifest()?.version }}
|
||||
</span>
|
||||
<span tuiSubtitle>{{ manifest()?.version }}</span>
|
||||
</span>
|
||||
</header>
|
||||
<nav [attr.inert]="isInactive() ? '' : null">
|
||||
@for (item of nav; track $index) {
|
||||
<a
|
||||
tuiCell
|
||||
tuiAppearance="action-grayscale"
|
||||
routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
[routerLink]="item.title === 'dashboard' ? './' : item.title"
|
||||
>
|
||||
<tui-icon [icon]="item.icon" />
|
||||
<span tuiTitle>{{ item.title | i18n }}</span>
|
||||
@if (item.title === 'dashboard') {
|
||||
<a routerLink="interface" routerLinkActive="active"></a>
|
||||
}
|
||||
</a>
|
||||
@if (item.title === 'Documentation') {
|
||||
<a
|
||||
tuiCell
|
||||
tuiAppearance="action-grayscale"
|
||||
[href]="manifest()?.docsUrl"
|
||||
target="_blank"
|
||||
noreferrer
|
||||
>
|
||||
<tui-icon [icon]="item.icon" />
|
||||
<span tuiTitle>
|
||||
<span>
|
||||
{{ item.title | i18n }}
|
||||
</span>
|
||||
</span>
|
||||
<tui-icon icon="@tui.external-link" [style.font-size.rem]="1" />
|
||||
</a>
|
||||
} @else {
|
||||
<a
|
||||
tuiCell
|
||||
tuiAppearance="action-grayscale"
|
||||
routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
[routerLink]="item.title === 'dashboard' ? './' : item.title"
|
||||
>
|
||||
<tui-icon [icon]="item.icon" />
|
||||
<span tuiTitle>{{ item.title | i18n }}</span>
|
||||
@if (item.title === 'dashboard') {
|
||||
<a routerLink="interface" routerLinkActive="active"></a>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
</aside>
|
||||
}
|
||||
<router-outlet />
|
||||
`,
|
||||
host: { class: 'g-page' },
|
||||
host: {
|
||||
class: 'g-page',
|
||||
'[style.--background]': '"url(" + service()?.icon + ")"',
|
||||
},
|
||||
styles: `
|
||||
:host {
|
||||
display: flex;
|
||||
@@ -88,9 +113,31 @@ const INACTIVE: PrimaryStatus[] = [
|
||||
}
|
||||
}
|
||||
|
||||
[tuiSubtitle] {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
header {
|
||||
margin: 0 -0.5rem;
|
||||
margin: -0.5rem -0.5rem 0;
|
||||
padding-top: 1rem;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 -1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
header::before,
|
||||
.title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--background);
|
||||
background-size: 1px;
|
||||
mask: linear-gradient(to bottom, black, transparent);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.title::before {
|
||||
mask: linear-gradient(to bottom right, black, transparent);
|
||||
}
|
||||
|
||||
nav[inert] a:not(:first-child) {
|
||||
@@ -110,11 +157,6 @@ const INACTIVE: PrimaryStatus[] = [
|
||||
}
|
||||
}
|
||||
|
||||
.m-header {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
@@ -150,7 +192,8 @@ const INACTIVE: PrimaryStatus[] = [
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[tuiTitle] {
|
||||
[tuiTitle],
|
||||
tui-icon:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -181,6 +224,7 @@ export class ServiceOutletComponent {
|
||||
{ title: 'actions', icon: '@tui.clapperboard' },
|
||||
{ title: 'logs', icon: '@tui.logs' },
|
||||
{ title: 'about', icon: '@tui.info' },
|
||||
{ title: 'Documentation', icon: '@tui.book-open-text' },
|
||||
]
|
||||
|
||||
protected readonly service = toSignal(
|
||||
|
||||
@@ -50,11 +50,11 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
|
||||
</service-status>
|
||||
|
||||
@if (status() !== 'backingUp') {
|
||||
<service-health-checks [checks]="health()" />
|
||||
<service-uptime
|
||||
class="g-card"
|
||||
[started]="$any(pkg.status)?.started"
|
||||
/>
|
||||
<service-interfaces [pkg]="pkg" [disabled]="status() !== 'running'" />
|
||||
|
||||
@if (errors() | async; as errors) {
|
||||
<service-dependencies
|
||||
@@ -63,8 +63,8 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
|
||||
[errors]="errors"
|
||||
/>
|
||||
}
|
||||
<service-interfaces [pkg]="pkg" [disabled]="status() !== 'running'" />
|
||||
|
||||
<service-health-checks [checks]="health()" />
|
||||
<service-tasks
|
||||
#tasks="elementRef"
|
||||
tuiElement
|
||||
@@ -109,7 +109,7 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
|
||||
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
grid-auto-rows: max-content;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@@ -51,14 +51,8 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
|
||||
<form-group [spec]="d.spec" />
|
||||
|
||||
@if (d.warn.length; as length) {
|
||||
<p>
|
||||
Warning. StartOS is currently using {{ d.warn.join(', ') }} for DNS.
|
||||
Therefore, {{ length > 1 ? 'they' : 'it' }} cannot use StartOS for
|
||||
DNS. This is circular. If you want to use StartOS as the DNS server
|
||||
for {{ d.warn.join(', ') }} for private domain resolution, you must
|
||||
set custom DNS servers above.
|
||||
</p>
|
||||
@for (warn of d.warn; track $index) {
|
||||
<p>{{ warn }}</p>
|
||||
}
|
||||
|
||||
<footer>
|
||||
@@ -122,20 +116,20 @@ export default class SystemDnsComponent {
|
||||
name: 'DHCP',
|
||||
spec: ISB.InputSpec.of({
|
||||
servers: ISB.Value.dynamicText(() => ({
|
||||
name: 'DHCP Servers',
|
||||
name: this.i18n.transform('DHCP Servers'),
|
||||
default: null,
|
||||
required: true,
|
||||
disabled: 'Cannot edit DHCP servers',
|
||||
disabled: this.i18n.transform('Cannot edit DHCP servers'),
|
||||
})),
|
||||
}),
|
||||
},
|
||||
static: {
|
||||
name: 'Static',
|
||||
name: this.i18n.transform('Static'),
|
||||
spec: ISB.InputSpec.of({
|
||||
servers: ISB.Value.list(
|
||||
ISB.List.text(
|
||||
{
|
||||
name: 'Static Servers',
|
||||
name: this.i18n.transform('Static Servers'),
|
||||
minLength: 1,
|
||||
maxLength: 3,
|
||||
},
|
||||
@@ -157,13 +151,12 @@ export default class SystemDnsComponent {
|
||||
const spec = await configBuilderToSpec(this.dnsSpec)
|
||||
|
||||
const dhcpServers = { servers: dns.dhcpServers.join(', ') }
|
||||
const staticServers = { servers: dns.staticServers || [] }
|
||||
|
||||
const current: (typeof this.dnsSpec._TYPE)['strategy'] =
|
||||
dns.staticServers
|
||||
? {
|
||||
selection: 'static',
|
||||
value: staticServers,
|
||||
value: { servers: dns.staticServers || [] },
|
||||
other: {
|
||||
dhcp: dhcpServers,
|
||||
},
|
||||
@@ -175,19 +168,29 @@ export default class SystemDnsComponent {
|
||||
|
||||
const form = this.formService.createForm(spec, { strategy: current })
|
||||
|
||||
let warn: string[] = []
|
||||
|
||||
if (
|
||||
Object.values(pkgs).some(p =>
|
||||
Object.values(p.hosts).some(h => h?.privateDomains.length),
|
||||
)
|
||||
) {
|
||||
Object.values(gateways)
|
||||
.filter(g =>
|
||||
(dns.staticServers || dns.dhcpServers).some(d =>
|
||||
g.ipInfo?.lanIp.includes(d),
|
||||
),
|
||||
)
|
||||
.map(
|
||||
g =>
|
||||
`${this.i18n.transform('Warning. StartOS is currently using the following gateway for DNS')}: ${g.ipInfo!.name}. ${this.i18n.transform('If you intend to use this gateway for private domain resolution, set alternative static DNS servers using the form above.')}`,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
spec,
|
||||
form,
|
||||
warn:
|
||||
(Object.values(pkgs).some(p =>
|
||||
Object.values(p.hosts).some(h => h?.privateDomains.length),
|
||||
) ||
|
||||
[]) &&
|
||||
Object.values(gateways)
|
||||
.filter(g =>
|
||||
dns.dhcpServers.some(d => g.ipInfo?.lanIp.includes(d)),
|
||||
)
|
||||
.map(g => g.ipInfo?.name),
|
||||
warn,
|
||||
}
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -24,7 +24,9 @@ import { getServerInfo } from 'src/app/utils/get-server-info'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Change Password' | i18n }}
|
||||
</ng-container>
|
||||
<header tuiHeader>
|
||||
|
||||
@@ -17,7 +17,9 @@ import { SessionsTableComponent } from './table.component'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Active Sessions' | i18n }}
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ import { wifiSpec } from './wifi.const'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
WiFi
|
||||
</ng-container>
|
||||
<header tuiHeader>
|
||||
|
||||
@@ -146,7 +146,7 @@ export class MarketplaceService {
|
||||
}
|
||||
|
||||
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {
|
||||
console.warn('FETCHING REGISTRY: ', url)
|
||||
console.log('FETCHING REGISTRY: ', url)
|
||||
return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe(
|
||||
map(([info, packages]) => ({ info, packages, url })),
|
||||
catchError(e => {
|
||||
|
||||
@@ -86,6 +86,7 @@ hr {
|
||||
}
|
||||
|
||||
.g-card {
|
||||
container: card / inline-size;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user