mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
a bunch of UI cleanup around backups as well as other bug fixes and UII improvements
This commit is contained in:
@@ -34,15 +34,15 @@ import { StateService } from '../services/state.service'
|
||||
<tui-icon icon="@tui.circle-check-big" class="g-positive" />
|
||||
{{ 'Setup Complete!' | i18n }}
|
||||
</h2>
|
||||
@if (!stateService.kiosk) {
|
||||
<p tuiSubtitle>
|
||||
{{
|
||||
'http://start.local was for setup only. It will no longer work.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
</hgroup>
|
||||
@if (!stateService.kiosk) {
|
||||
<p tuiSubtitle>
|
||||
{{
|
||||
'http://start.local was for setup only. It will no longer work.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
</header>
|
||||
|
||||
@if (!result) {
|
||||
|
||||
@@ -302,15 +302,11 @@ export default {
|
||||
317: 'Originalpasswort',
|
||||
318: 'Originalpasswort eingeben',
|
||||
319: 'Sicherung wird gestartet',
|
||||
320: 'Sichern Sie StartOS und Dienstdaten, indem Sie sich mit einem Gerät im lokalen Netzwerk oder einem physischen Laufwerk verbinden, das an Ihren Server angeschlossen ist.',
|
||||
321: 'Stellen Sie StartOS und Dienstdaten von einem Gerät im lokalen Netzwerk oder einem physischen Laufwerk mit vorhandener Sicherung wieder her.',
|
||||
322: 'Letzte Sicherung',
|
||||
323: 'Ein Ordner auf einem anderen Computer, der mit demselben Netzwerk wie Ihr Start9-Server verbunden ist.',
|
||||
324: 'Ein physisches Laufwerk, das direkt an Ihren Start9-Server angeschlossen ist.',
|
||||
325: 'Dienste für Sicherung auswählen',
|
||||
326: 'Serversicherung auswählen',
|
||||
325: 'Dienste auswählen',
|
||||
326: 'Server auswählen',
|
||||
327: 'Netzwerkordner',
|
||||
328: 'Neuen öffnen',
|
||||
328: 'Neuen',
|
||||
329: 'Hostname',
|
||||
330: 'Pfad',
|
||||
331: 'URL',
|
||||
@@ -355,7 +351,6 @@ export default {
|
||||
372: 'Passwort erforderlich',
|
||||
373: 'Geben Sie das Master-Passwort ein, das zur Verschlüsselung dieser Sicherung verwendet wurde. Im nächsten Schritt wählen Sie die Dienste aus, die wiederhergestellt werden sollen.',
|
||||
374: 'Laufwerk wird entschlüsselt',
|
||||
375: 'Dienste zur Wiederherstellung auswählen',
|
||||
376: 'Für Sicherung verfügbar',
|
||||
377: 'StartOS-Sicherungen erkannt',
|
||||
378: 'Keine StartOS-Sicherungen erkannt',
|
||||
@@ -726,4 +721,5 @@ export default {
|
||||
803: 'Dieses Laufwerk verwendet ext4 und wird automatisch in btrfs konvertiert. Ein Backup wird dringend empfohlen, bevor Sie fortfahren.',
|
||||
804: 'Ich habe ein Backup meiner Daten',
|
||||
805: 'Öffentliche Domain hinzufügen',
|
||||
806: 'Ergebnis',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -301,15 +301,11 @@ export const ENGLISH: Record<string, number> = {
|
||||
'Original Password': 317,
|
||||
'Enter original password': 318,
|
||||
'Beginning backup': 319,
|
||||
'Back up StartOS and service data by connecting to a device on your local network or a physical drive connected to your server.': 320,
|
||||
'Restore StartOS and service data from a device on your local network or a physical drive connected to your server that contains an existing backup.': 321,
|
||||
'Last Backup': 322, // as in, the last time the server was backed up
|
||||
'A folder on another computer that is connected to the same network as your Start9 server.': 323,
|
||||
'A physical drive that is plugged directly into your Start9 Server.': 324,
|
||||
'Select Services to Back Up': 325,
|
||||
'Select server backup': 326,
|
||||
'Select services': 325,
|
||||
'Select server': 326,
|
||||
'Network Folders': 327,
|
||||
'Open New': 328,
|
||||
'New': 328,
|
||||
'Hostname': 329,
|
||||
'Path': 330, // as in, a URL path
|
||||
'URL': 331,
|
||||
@@ -354,7 +350,6 @@ export const ENGLISH: Record<string, number> = {
|
||||
'Password required': 372,
|
||||
'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.': 373,
|
||||
'Decrypting drive': 374,
|
||||
'Select services to restore': 375,
|
||||
'Available for backup': 376,
|
||||
'StartOS backups detected': 377,
|
||||
'No StartOS backups detected': 378,
|
||||
@@ -727,4 +722,5 @@ export const ENGLISH: Record<string, number> = {
|
||||
'This drive uses ext4 and will be automatically converted to btrfs. A backup is strongly recommended before proceeding.': 803,
|
||||
'I have a backup of my data': 804,
|
||||
'Add Public Domain': 805,
|
||||
'Result': 806,
|
||||
}
|
||||
|
||||
@@ -302,15 +302,11 @@ export default {
|
||||
317: 'Contraseña original',
|
||||
318: 'Ingresa la contraseña original',
|
||||
319: 'Iniciando copia de seguridad',
|
||||
320: 'Haz una copia de seguridad de StartOS y los datos de los servicios conectándote a un dispositivo en tu red local o a una unidad física conectada a tu servidor.',
|
||||
321: 'Restaura StartOS y los datos de los servicios desde un dispositivo en tu red local o una unidad física conectada a tu servidor que contenga una copia de seguridad existente.',
|
||||
322: 'Última copia de seguridad',
|
||||
323: 'Una carpeta en otro ordenador conectado a la misma red que tu servidor Start9.',
|
||||
324: 'Una unidad física conectada directamente a tu servidor Start9.',
|
||||
325: 'Seleccionar servicios para respaldar',
|
||||
326: 'Seleccionar copia de seguridad del servidor',
|
||||
325: 'Seleccionar servicios',
|
||||
326: 'Seleccionar servidor',
|
||||
327: 'Carpetas de red',
|
||||
328: 'Abrir nuevo',
|
||||
328: 'Nuevo',
|
||||
329: 'Nombre del host',
|
||||
330: 'Ruta',
|
||||
331: 'URL',
|
||||
@@ -355,7 +351,6 @@ export default {
|
||||
372: 'Se requiere contraseña',
|
||||
373: 'Ingresa la contraseña maestra que se usó para cifrar esta copia de seguridad. En la siguiente pantalla, podrás seleccionar los servicios individuales que deseas restaurar.',
|
||||
374: 'Descifrando unidad',
|
||||
375: 'Seleccionar servicios para restaurar',
|
||||
376: 'Disponible para copia de seguridad',
|
||||
377: 'Copias de seguridad de StartOS detectadas',
|
||||
378: 'No se detectaron copias de seguridad de StartOS',
|
||||
@@ -726,4 +721,5 @@ export default {
|
||||
803: 'Esta unidad usa ext4 y se convertirá automáticamente a btrfs. Se recomienda encarecidamente hacer una copia de seguridad antes de continuar.',
|
||||
804: 'Tengo una copia de seguridad de mis datos',
|
||||
805: 'Agregar dominio público',
|
||||
806: 'Resultado',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -302,15 +302,11 @@ export default {
|
||||
317: 'Mot de passe d’origine',
|
||||
318: 'Entrez le mot de passe d’origine',
|
||||
319: 'Démarrage de la sauvegarde',
|
||||
320: 'Sauvegardez StartOS et les données des services en connectant un appareil sur votre réseau local ou un disque physique connecté à votre serveur.',
|
||||
321: 'Restaurez StartOS et les données des services à partir d’un appareil sur votre réseau local ou d’un disque physique connecté à votre serveur contenant une sauvegarde.',
|
||||
322: 'Dernière sauvegarde',
|
||||
323: 'Un dossier sur un autre ordinateur connecté au même réseau que votre serveur Start9.',
|
||||
324: 'Un disque physique branché directement à votre serveur Start9.',
|
||||
325: 'Sélectionner les services à sauvegarder',
|
||||
326: 'Sélectionner la sauvegarde du serveur',
|
||||
325: 'Sélectionner les services',
|
||||
326: 'Sélectionner le serveur',
|
||||
327: 'Dossiers réseau',
|
||||
328: 'Ouvrir nouveau',
|
||||
328: 'Nouveau',
|
||||
329: 'Nom d’hôte',
|
||||
330: 'Chemin',
|
||||
331: 'URL',
|
||||
@@ -355,7 +351,6 @@ export default {
|
||||
372: 'Mot de passe requis',
|
||||
373: 'Entrez le mot de passe principal utilisé pour chiffrer cette sauvegarde. Vous pourrez choisir les services à restaurer à l’écran suivant.',
|
||||
374: 'Déchiffrement du disque',
|
||||
375: 'Sélectionner les services à restaurer',
|
||||
376: 'Disponible pour la sauvegarde',
|
||||
377: 'Sauvegardes StartOS détectées',
|
||||
378: 'Aucune sauvegarde StartOS détectée',
|
||||
@@ -726,4 +721,5 @@ export default {
|
||||
803: 'Ce disque utilise ext4 et sera automatiquement converti en btrfs. Il est fortement recommandé de faire une sauvegarde avant de continuer.',
|
||||
804: "J'ai une sauvegarde de mes données",
|
||||
805: 'Ajouter un domaine public',
|
||||
806: 'Résultat',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -302,15 +302,11 @@ export default {
|
||||
317: 'Oryginalne hasło',
|
||||
318: 'Wprowadź oryginalne hasło',
|
||||
319: 'Rozpoczynanie tworzenia kopii zapasowej',
|
||||
320: 'Utwórz kopię zapasową StartOS i danych serwisów, łącząc się z urządzeniem w sieci lokalnej lub z fizycznym dyskiem podłączonym do Twojego serwera.',
|
||||
321: 'Przywróć StartOS i dane serwisów z urządzenia w sieci lokalnej lub z fizycznego dysku podłączonego do Twojego serwera, który zawiera istniejącą kopię zapasową.',
|
||||
322: 'Ostatnia kopia zapasowa',
|
||||
323: 'Folder na innym komputerze, który jest podłączony do tej samej sieci co twój serwer Start9.',
|
||||
324: 'Fizyczny dysk, który jest podłączony bezpośrednio do Twojego serwera Start9.',
|
||||
325: 'Wybierz serwisy do kopii zapasowej',
|
||||
326: 'Wybierz kopię zapasową serwera',
|
||||
325: 'Wybierz serwisy',
|
||||
326: 'Wybierz serwer',
|
||||
327: 'Foldery sieciowe',
|
||||
328: 'Otwórz nowy',
|
||||
328: 'Nowy',
|
||||
329: 'Nazwa hosta',
|
||||
330: 'Ścieżka',
|
||||
331: 'URL',
|
||||
@@ -355,7 +351,6 @@ export default {
|
||||
372: 'Wymagane hasło',
|
||||
373: 'Wprowadź hasło główne, które zostało użyte do zaszyfrowania tej kopii zapasowej. Na następnym ekranie wybierzesz poszczególne serwisy, które chcesz przywrócić.',
|
||||
374: 'Odszyfrowywanie dysku',
|
||||
375: 'Wybierz serwisy do przywrócenia',
|
||||
376: 'Dostępne do kopii zapasowej',
|
||||
377: 'Wykryto kopie zapasowe StartOS',
|
||||
378: 'Nie wykryto kopii zapasowych StartOS',
|
||||
@@ -726,4 +721,5 @@ export default {
|
||||
803: 'Ten dysk używa ext4 i zostanie automatycznie skonwertowany na btrfs. Zdecydowanie zaleca się wykonanie kopii zapasowej przed kontynuowaniem.',
|
||||
804: 'Mam kopię zapasową moich danych',
|
||||
805: 'Dodaj domenę publiczną',
|
||||
806: 'Wynik',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -169,7 +169,7 @@ export class DevicesAdd {
|
||||
})
|
||||
|
||||
this.dialogs
|
||||
.open(DEVICES_CONFIG, { data: config, closable: false })
|
||||
.open(DEVICES_CONFIG, { data: config, closable: false, size: 'm' })
|
||||
.subscribe()
|
||||
}
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { QrCodeComponent } from 'ng-qrcode'
|
||||
</tui-segmented>
|
||||
</aside>
|
||||
</header>
|
||||
@if (segmented?.activeItemIndex) {
|
||||
@if (segmented?.activeItemIndex()) {
|
||||
<qr-code [value]="config" size="352" />
|
||||
} @else {
|
||||
<tui-textfield>
|
||||
|
||||
@@ -159,7 +159,9 @@ export default class Devices {
|
||||
try {
|
||||
const data = await this.api.showDeviceConfig({ subnet: subnet.range, ip })
|
||||
|
||||
this.dialogs.open(DEVICES_CONFIG, { data, closable: false }).subscribe()
|
||||
this.dialogs
|
||||
.open(DEVICES_CONFIG, { data, closable: false, size: 'm' })
|
||||
.subscribe()
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiDialogContext, TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
|
||||
import { TuiDialogContext, TuiIcon } from '@taiga-ui/core'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs'
|
||||
@@ -17,41 +17,58 @@ import { DataModel } from '../services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 class="g-title">
|
||||
{{ 'Completed' | i18n }}: {{ data.createdAt | date: 'medium' }}
|
||||
</h3>
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>{{ 'System data' | i18n }}</strong>
|
||||
<div tuiSubtitle [style.color]="system().color">
|
||||
{{ system().result | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<tui-icon [icon]="system().icon" [style.color]="system().color" />
|
||||
</div>
|
||||
@if (pkgTitles(); as titles) {
|
||||
@for (pkg of data.content.packages | keyvalue; track $index) {
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>{{ titles[pkg.key] || pkg.key }}</strong>
|
||||
<div tuiSubtitle [style.color]="getColor(pkg.value.error)">
|
||||
{{
|
||||
pkg.value.error
|
||||
? ('Failed' | i18n) + ': ' + pkg.value.error
|
||||
: ('Succeeded' | i18n)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<tui-icon
|
||||
[icon]="getIcon(pkg.value.error)"
|
||||
[style.color]="getColor(pkg.value.error)"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<p class="timestamp">{{ data.createdAt | date: 'medium' }}</p>
|
||||
<table class="g-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Title' | i18n }}</th>
|
||||
<th>{{ 'Result' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'System data' | i18n }}</td>
|
||||
<td [style.color]="system().color">
|
||||
<tui-icon [icon]="system().icon" />
|
||||
{{ system().result | i18n }}
|
||||
</td>
|
||||
</tr>
|
||||
@if (pkgTitles(); as titles) {
|
||||
@for (pkg of data.content.packages | keyvalue; track $index) {
|
||||
<tr>
|
||||
<td>{{ titles[pkg.key] || pkg.key }}</td>
|
||||
<td [style.color]="getColor(pkg.value.error)">
|
||||
<tui-icon [icon]="getIcon(pkg.value.error)" />
|
||||
{{
|
||||
pkg.value.error
|
||||
? ('Failed' | i18n) + ': ' + pkg.value.error
|
||||
: ('Succeeded' | i18n)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
.timestamp {
|
||||
color: var(--tui-text-secondary);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tui-icon {
|
||||
font-size: 1rem;
|
||||
vertical-align: sub;
|
||||
margin-inline-end: 0.25rem;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, TuiIcon, TuiCell, TuiTitle, i18nPipe],
|
||||
imports: [CommonModule, TuiIcon, i18nPipe],
|
||||
})
|
||||
export class BackupsReportModal {
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
@@ -7,7 +7,13 @@ import {
|
||||
} from '@angular/core'
|
||||
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
||||
import { ISB, utils } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiDataList, TuiDropdown, TuiInput } from '@taiga-ui/core'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
TuiInput,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
@@ -33,7 +39,18 @@ import { InterfaceAddressItemComponent } from './item.component'
|
||||
selector: 'section[gatewayGroup]',
|
||||
template: `
|
||||
<header>
|
||||
{{ 'Gateway' | i18n }}: {{ gatewayGroup().gatewayName }}
|
||||
@switch (gatewayGroup().deviceType) {
|
||||
@case ('ethernet') {
|
||||
<tui-icon icon="@tui.ethernet-port" />
|
||||
}
|
||||
@case ('wireless') {
|
||||
<tui-icon icon="@tui.wifi" />
|
||||
}
|
||||
@case ('wireguard') {
|
||||
<tui-icon icon="@tui.shield" />
|
||||
}
|
||||
}
|
||||
{{ gatewayGroup().gatewayName }}
|
||||
@if (gatewayGroup().isWireguard) {
|
||||
<button
|
||||
tuiButton
|
||||
@@ -93,6 +110,11 @@ import { InterfaceAddressItemComponent } from './item.component'
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
header tui-icon {
|
||||
font-size: 1.25rem;
|
||||
margin-inline-end: 0.375rem;
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
th:first-child {
|
||||
width: 5rem;
|
||||
@@ -104,6 +126,7 @@ import { InterfaceAddressItemComponent } from './item.component'
|
||||
TuiButton,
|
||||
TuiDropdown,
|
||||
TuiDataList,
|
||||
TuiIcon,
|
||||
TuiInput,
|
||||
TableComponent,
|
||||
PlaceholderComponent,
|
||||
|
||||
@@ -209,7 +209,7 @@ import {
|
||||
styles: `
|
||||
.plugin-icon {
|
||||
height: 1.25rem;
|
||||
margin-right: 0.25rem;
|
||||
margin-inline-end: 0.375rem;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ export class InterfaceService {
|
||||
return {
|
||||
gatewayId: g.id,
|
||||
gatewayName: g.name,
|
||||
deviceType: g.ipInfo?.deviceType || 'ethernet',
|
||||
isWireguard: g.ipInfo?.deviceType === 'wireguard',
|
||||
addresses,
|
||||
}
|
||||
@@ -314,6 +315,7 @@ export type GatewayAddress = {
|
||||
export type GatewayAddressGroup = {
|
||||
gatewayId: string
|
||||
gatewayName: string
|
||||
deviceType: string
|
||||
isWireguard: boolean
|
||||
addresses: GatewayAddress[]
|
||||
}
|
||||
|
||||
@@ -120,5 +120,5 @@ export class BackupsBackupModal {
|
||||
export const BACKUP = new PolymorpheusComponent(BackupsBackupModal)
|
||||
|
||||
export const BACKUP_OPTIONS: Partial<TuiDialogOptions<unknown>> = {
|
||||
label: 'Select Services to Back Up',
|
||||
label: 'Select services',
|
||||
}
|
||||
|
||||
@@ -235,6 +235,7 @@ export class BackupsHistoryModal {
|
||||
content: report,
|
||||
createdAt: completedAt,
|
||||
},
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
@@ -50,16 +50,11 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
<td class="content">
|
||||
<tui-line-clamp
|
||||
style="pointer-events: none"
|
||||
[linesLimit]="4"
|
||||
[linesLimit]="3"
|
||||
[lineHeight]="21"
|
||||
[content]="item.message"
|
||||
(overflownChange)="overflow = $event"
|
||||
/>
|
||||
@if (overflow) {
|
||||
<button tuiLink (click.stop)="onClick(item)">
|
||||
{{ 'View full' | i18n }}
|
||||
</button>
|
||||
}
|
||||
@if ([1, 2].includes(item.code)) {
|
||||
<button tuiLink (click.stop)="onClick(item)">
|
||||
{{
|
||||
@@ -68,6 +63,10 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
: ('View details' | i18n)
|
||||
}}
|
||||
</button>
|
||||
} @else if (overflow) {
|
||||
<button tuiLink (click.stop)="onClick(item)">
|
||||
{{ 'View full' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@@ -186,12 +185,7 @@ export class NotificationItemComponent {
|
||||
overflow = false
|
||||
|
||||
onClick(item: ServerNotification<number>) {
|
||||
if (this.overflow) {
|
||||
this.service.viewModal(item, true)
|
||||
item.seen = true
|
||||
} else if ([1, 2].includes(item.code)) {
|
||||
this.service.viewModal(item)
|
||||
item.seen = true
|
||||
}
|
||||
this.service.viewModal(item)
|
||||
item.seen = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,20 @@ import { AuthoritiesTableComponent } from './table.component'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Certificate Authorities' | i18n }}
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Certificate Authorities' | i18n }}
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
path="/start-os/trust-ca.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
<section class="g-card">
|
||||
<header>
|
||||
|
||||
@@ -9,13 +9,7 @@ import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router'
|
||||
import { DialogService, DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiMapperPipe } from '@taiga-ui/cdk'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiLink,
|
||||
TuiLoader,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiButton, TuiLoader, TuiNotification, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
@@ -35,12 +29,28 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{
|
||||
type === 'create' ? ('Create Backup' | i18n) : ('Restore Backup' | i18n)
|
||||
}}
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{
|
||||
type === 'create'
|
||||
? ('Create Backup' | i18n)
|
||||
: ('Restore Backup' | i18n)
|
||||
}}
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
[path]="
|
||||
type === 'create'
|
||||
? '/start-os/backup-create.html'
|
||||
: '/start-os/backup-restore.html'
|
||||
"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<header tuiHeader>
|
||||
@@ -51,36 +61,19 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
? ('Create Backup' | i18n)
|
||||
: ('Restore Backup' | i18n)
|
||||
}}
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
[path]="
|
||||
type === 'create'
|
||||
? '/start-os/backup-create.html'
|
||||
: '/start-os/backup-restore.html'
|
||||
"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</h3>
|
||||
<p tuiSubtitle>
|
||||
@if (type === 'create') {
|
||||
{{
|
||||
'Back up StartOS and service data by connecting to a device on your local network or a physical drive connected to your server.'
|
||||
| i18n
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
docsLink
|
||||
path="/start-os/backup-create.html"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'View instructions' | i18n"
|
||||
></a>
|
||||
} @else {
|
||||
{{
|
||||
'Restore StartOS and service data from a device on your local network or a physical drive connected to your server that contains an existing backup.'
|
||||
| i18n
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
docsLink
|
||||
path="/start-os/backup-restore.html"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'View instructions' | i18n"
|
||||
></a>
|
||||
}
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
@@ -109,30 +102,16 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
[style.height.rem]="20"
|
||||
/>
|
||||
} @else {
|
||||
<section (networkFolders)="onTarget($event)">
|
||||
{{
|
||||
'A folder on another computer that is connected to the same network as your Start9 server.'
|
||||
| i18n
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
docsLink
|
||||
path="/start-os/backup-create.html"
|
||||
fragment="#network-folder"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'View instructions' | i18n"
|
||||
></a>
|
||||
</section>
|
||||
<section (physicalFolders)="onTarget($event)">
|
||||
{{
|
||||
'A physical drive that is plugged directly into your Start9 Server.'
|
||||
| i18n
|
||||
}}
|
||||
</section>
|
||||
<section (networkFolders)="onTarget($event)"></section>
|
||||
<section (physicalFolders)="onTarget($event)"></section>
|
||||
}
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host-context(tui-root._mobile) [tuiHeader] {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
@@ -140,7 +119,6 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
RouterLink,
|
||||
TuiButton,
|
||||
TuiLoader,
|
||||
TuiLink,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
TuiNotification,
|
||||
@@ -184,12 +162,22 @@ export default class SystemBackupComponent implements OnInit {
|
||||
}
|
||||
|
||||
onTarget(target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>) {
|
||||
const component = this.type === 'create' ? BACKUP : BACKUP_RESTORE
|
||||
const label =
|
||||
this.type === 'create'
|
||||
? 'Select Services to Back Up'
|
||||
: 'Select server backup'
|
||||
|
||||
this.dialog.openComponent(component, { label, data: target }).subscribe()
|
||||
if (this.type === 'create') {
|
||||
this.dialog
|
||||
.openComponent(BACKUP, {
|
||||
label: 'Select services',
|
||||
data: target,
|
||||
size: 'm',
|
||||
})
|
||||
.subscribe()
|
||||
} else {
|
||||
this.dialog
|
||||
.openComponent(BACKUP_RESTORE, {
|
||||
label: 'Select server',
|
||||
data: target,
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { DialogService, ErrorService, i18nPipe } from '@start9labs/shared'
|
||||
import { ISB, T } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiNotificationMiddleService, TuiTooltip } from '@taiga-ui/kit'
|
||||
import { TuiButton, TuiDataList, TuiDropdown, TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
||||
import { filter } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
@@ -28,10 +28,14 @@ const ERROR =
|
||||
template: `
|
||||
<header>
|
||||
{{ 'Network Folders' | i18n }}
|
||||
<tui-icon [tuiTooltip]="cifs" />
|
||||
<ng-template #cifs><ng-content /></ng-template>
|
||||
<button tuiButton size="xs" iconStart="@tui.plus" (click)="add()">
|
||||
{{ 'Open New' | i18n }}
|
||||
<button
|
||||
tuiButton
|
||||
size="xs"
|
||||
iconStart="@tui.plus"
|
||||
[style.margin-inline-start]="'auto'"
|
||||
(click)="add()"
|
||||
>
|
||||
{{ 'New' | i18n }}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@@ -59,24 +63,29 @@ const ERROR =
|
||||
<td class="name">{{ target.entry.path.split('/').pop() }}</td>
|
||||
<td>{{ target.entry.hostname }}</td>
|
||||
<td>{{ target.entry.path }}</td>
|
||||
<td>
|
||||
<td (click)="$event.stopPropagation()">
|
||||
<button
|
||||
tuiIconButton
|
||||
tuiDropdown
|
||||
size="s"
|
||||
appearance="action-destructive"
|
||||
iconStart="@tui.trash"
|
||||
(click.stop)="forget(target, $index)"
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.ellipsis-vertical"
|
||||
[tuiDropdownOpen]="!!opens[$index]"
|
||||
(tuiDropdownOpenChange)="opens[$index] = $event"
|
||||
>
|
||||
Forget
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
size="xs"
|
||||
iconStart="@tui.pencil"
|
||||
(click.stop)="edit(target)"
|
||||
>
|
||||
Edit
|
||||
{{ 'More' | i18n }}
|
||||
<tui-data-list *tuiDropdown>
|
||||
<button tuiOption (click)="edit(target)">
|
||||
{{ 'Edit' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
class="g-negative"
|
||||
(click)="forget(target, $index)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-data-list>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -106,7 +115,7 @@ const ERROR =
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 13rem;
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
@@ -114,10 +123,6 @@ const ERROR =
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
[tuiButton] {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -166,8 +171,9 @@ const ERROR =
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
TuiTooltip,
|
||||
PlaceholderComponent,
|
||||
BackupStatusComponent,
|
||||
TableComponent,
|
||||
@@ -186,6 +192,8 @@ export class BackupNetworkComponent {
|
||||
readonly service = inject(BackupService)
|
||||
readonly networkFolders = output<MappedBackupTarget<CifsBackupTarget>>()
|
||||
|
||||
opens: Record<number, boolean> = {}
|
||||
|
||||
select(target: MappedBackupTarget<CifsBackupTarget>) {
|
||||
if (!target.entry.mountable) {
|
||||
this.dialog.openAlert(ERROR, { label: 'Unable to connect' }).subscribe()
|
||||
@@ -321,7 +329,6 @@ export class BackupNetworkComponent {
|
||||
),
|
||||
required: true,
|
||||
default: null,
|
||||
placeholder: 'My Network Folder',
|
||||
}),
|
||||
password: ISB.Value.text({
|
||||
name: this.i18n.transform('Password')!,
|
||||
@@ -331,7 +338,6 @@ export class BackupNetworkComponent {
|
||||
required: false,
|
||||
default: null,
|
||||
masked: true,
|
||||
placeholder: 'My Network Folder',
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ import {
|
||||
output,
|
||||
} from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ConvertBytesPipe, DialogService, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiTooltip } from '@taiga-ui/kit'
|
||||
import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { DiskBackupTarget } from 'src/app/services/api/api.types'
|
||||
@@ -19,11 +18,9 @@ import { BackupStatusComponent } from './status.component'
|
||||
template: `
|
||||
<header>
|
||||
{{ 'Physical Drives' | i18n }}
|
||||
<tui-icon [tuiTooltip]="drives" />
|
||||
<ng-template #drives><ng-content /></ng-template>
|
||||
</header>
|
||||
|
||||
<table [appTable]="['Status', 'Name', 'Model', 'Capacity']">
|
||||
<table [appTable]="['Status', 'Logicalname', 'Name', 'Capacity']">
|
||||
@for (target of service.drives(); track $index) {
|
||||
<tr
|
||||
tabindex="0"
|
||||
@@ -33,14 +30,9 @@ import { BackupStatusComponent } from './status.component'
|
||||
<td>
|
||||
<span [backupStatus]="target.hasAnyBackup" [physical]="true"></span>
|
||||
</td>
|
||||
<td class="name">
|
||||
{{ target.entry.label || target.entry.logicalname }}
|
||||
</td>
|
||||
<td>
|
||||
{{ target.entry.vendor || 'Unknown Vendor' }} -
|
||||
{{ target.entry.model || 'Unknown Model' }}
|
||||
</td>
|
||||
<td>{{ target.entry.capacity | convertBytes }}</td>
|
||||
<td class="name">{{ target.entry.logicalname }}</td>
|
||||
<td>{{ driveName(target.entry) }}</td>
|
||||
<td>{{ formatCapacity(target.entry.capacity) }}</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
@@ -74,10 +66,6 @@ import { BackupStatusComponent } from './status.component'
|
||||
}
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 13rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
tr {
|
||||
grid-template-columns: min-content 1fr 4rem;
|
||||
@@ -109,9 +97,6 @@ import { BackupStatusComponent } from './status.component'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TuiTooltip,
|
||||
ConvertBytesPipe,
|
||||
PlaceholderComponent,
|
||||
BackupStatusComponent,
|
||||
TableComponent,
|
||||
@@ -122,9 +107,26 @@ export class BackupPhysicalComponent {
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly type = inject(ActivatedRoute).snapshot.data['type']
|
||||
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
readonly service = inject(BackupService)
|
||||
readonly physicalFolders = output<MappedBackupTarget<DiskBackupTarget>>()
|
||||
|
||||
driveName(entry: DiskBackupTarget): string {
|
||||
return (
|
||||
[entry.vendor, entry.model].filter(Boolean).join(' ') ||
|
||||
this.i18n.transform('Unknown Drive')
|
||||
)
|
||||
}
|
||||
|
||||
formatCapacity(bytes: number): string {
|
||||
const gb = bytes / 1e9
|
||||
if (gb >= 1000) {
|
||||
return `${(gb / 1000).toFixed(1)} TB`
|
||||
}
|
||||
return `${gb.toFixed(0)} GB`
|
||||
}
|
||||
|
||||
select(target: MappedBackupTarget<DiskBackupTarget>) {
|
||||
if (this.type === 'restore' && !target.hasAnyBackup) {
|
||||
this.dialog
|
||||
|
||||
@@ -22,24 +22,42 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
</span>
|
||||
<span tuiTitle>
|
||||
{{ (pkg.value | toManifest).title }}
|
||||
<span tuiSubtitle>
|
||||
@if (progress.complete) {
|
||||
<tui-icon icon="@tui.check" class="g-positive" />
|
||||
{{ 'complete' | i18n }}
|
||||
</span>
|
||||
<span class="status">
|
||||
@if (progress.complete) {
|
||||
<tui-icon icon="@tui.check" class="g-positive" />
|
||||
{{ 'complete' | i18n }}
|
||||
} @else {
|
||||
@if ((pkg.key | tuiMapper: toStatus | async) === 'backing-up') {
|
||||
<tui-loader size="s" />
|
||||
{{ 'backing up' | i18n }}
|
||||
} @else {
|
||||
@if ((pkg.key | tuiMapper: toStatus | async) === 'backing-up') {
|
||||
<tui-loader size="s" />
|
||||
{{ 'backing up' | i18n }}
|
||||
} @else {
|
||||
{{ 'waiting' | i18n }}
|
||||
}
|
||||
<tui-icon icon="@tui.clock" />
|
||||
{{ 'waiting' | i18n }}
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-inline-start: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status tui-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
|
||||
@@ -3,12 +3,12 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import {
|
||||
DialogService,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
StartOSDiskInfo,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiCell, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { verifyPassword } from 'src/app/utils/verify-password'
|
||||
import { BackupContext } from './backup.types'
|
||||
@@ -16,31 +16,49 @@ import { RECOVER } from './recover.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@for (server of target.entry.startOs | keyvalue; track $index) {
|
||||
<button
|
||||
tuiCell
|
||||
class="g-stretch"
|
||||
(click)="onClick(server.key, server.value)"
|
||||
>
|
||||
<span tuiTitle>
|
||||
<span tuiSubtitle>
|
||||
<b>{{ 'Local Hostname' | i18n }}</b>
|
||||
: {{ server.value.hostname }}.local
|
||||
</span>
|
||||
<span tuiSubtitle>
|
||||
<b>{{ 'StartOS Version' | i18n }}</b>
|
||||
: {{ server.value.version }}
|
||||
</span>
|
||||
<span tuiSubtitle>
|
||||
<b>{{ 'Created' | i18n }}</b>
|
||||
: {{ server.value.timestamp | date: 'medium' }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<table [appTable]="['Hostname', 'StartOS Version', 'Created', null]">
|
||||
@for (server of target.entry.startOs | keyvalue; track $index) {
|
||||
<tr>
|
||||
<td class="name">{{ server.value.hostname }}.local</td>
|
||||
<td>{{ server.value.version }}</td>
|
||||
<td>{{ server.value.timestamp | date: 'medium' }}</td>
|
||||
<td>
|
||||
<button
|
||||
tuiButton
|
||||
size="s"
|
||||
(click)="onClick(server.key, server.value)"
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
tr {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--tui-text-primary);
|
||||
font: var(--tui-typography-body-m);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
grid-area: 1 / 2 / 4 / 2;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [KeyValuePipe, DatePipe, TuiCell, TuiTitle, i18nPipe],
|
||||
imports: [KeyValuePipe, DatePipe, TuiButton, TableComponent],
|
||||
})
|
||||
export class BackupRestoreComponent {
|
||||
private readonly dialog = inject(DialogService)
|
||||
@@ -82,7 +100,7 @@ export class BackupRestoreComponent {
|
||||
|
||||
this.context.$implicit.complete()
|
||||
this.dialog
|
||||
.openComponent(RECOVER, { label: 'Select services to restore', data })
|
||||
.openComponent(RECOVER, { label: 'Select services', data })
|
||||
.subscribe()
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
|
||||
@@ -13,7 +13,7 @@ import { TuiIcon } from '@taiga-ui/core'
|
||||
template: `
|
||||
@if (type === 'create') {
|
||||
<tui-icon
|
||||
[icon]="physical() ? '@tui.ethernet-port' : '@tui.signal-high'"
|
||||
[icon]="physical() ? '@tui.hard-drive' : '@tui.signal-high'"
|
||||
class="g-positive"
|
||||
/>
|
||||
{{ 'Available for backup' | i18n }}
|
||||
@@ -22,7 +22,7 @@ import { TuiIcon } from '@taiga-ui/core'
|
||||
<tui-icon icon="@tui.save" class="g-positive" />
|
||||
{{ 'StartOS backups detected' | i18n }}
|
||||
} @else {
|
||||
<tui-icon icon="@tui.save-off" class="g-negative" />
|
||||
<tui-icon icon="@tui.file-x" class="g-negative" />
|
||||
{{ 'No StartOS backups detected' | i18n }}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,8 @@ import { TuiIcon } from '@taiga-ui/core'
|
||||
|
||||
tui-icon {
|
||||
font-size: 1rem;
|
||||
min-width: 1.25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
|
||||
@@ -27,10 +27,20 @@ const ipv6 =
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'DNS Servers' | i18n }}
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'DNS Servers' | i18n }}
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
path="/start-os/dns.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
@if (data(); as d) {
|
||||
<form [formGroup]="d.form">
|
||||
@@ -76,6 +86,10 @@ const ipv6 =
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) [tuiHeader] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form header,
|
||||
form footer {
|
||||
margin: 1rem 0;
|
||||
|
||||
@@ -29,10 +29,20 @@ import { GatewaysTableComponent } from './table.component'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Gateways' | i18n }}
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Gateways' | i18n }}
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
path="/start-os/gateways.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<section class="g-card">
|
||||
@@ -121,6 +131,10 @@ import { GatewaysTableComponent } from './table.component'
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host-context(tui-root._mobile) .g-card > header > [docsLink] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.outbound {
|
||||
max-width: 24rem;
|
||||
margin-top: 2rem;
|
||||
|
||||
@@ -40,10 +40,20 @@ function detectProviderKey(host: string | undefined): string {
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
SMTP
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
SMTP
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
path="/start-os/smtp.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
@if (form$ | async; as data) {
|
||||
<form [formGroup]="data.form">
|
||||
@@ -129,6 +139,10 @@ function detectProviderKey(host: string | undefined): string {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) form:first-of-type > [tuiHeader] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form header,
|
||||
form footer {
|
||||
margin: 1rem 0;
|
||||
|
||||
@@ -26,15 +26,11 @@ import { SSHTableComponent } from './table.component'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
SSH
|
||||
</ng-container>
|
||||
@let keys = keys$ | async;
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ 'SSH Keys' | i18n }}
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
SSH
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
@@ -42,9 +38,13 @@ import { SSHTableComponent } from './table.component'
|
||||
path="/start-os/ssh.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
>
|
||||
{{ 'Documentation' | i18n }}
|
||||
</a>
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
@let keys = keys$ | async;
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ 'SSH Keys' | i18n }}
|
||||
<button
|
||||
tuiButton
|
||||
size="xs"
|
||||
|
||||
@@ -52,10 +52,20 @@ import { wifiSpec } from './wifi.const'
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
WiFi
|
||||
<div>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
WiFi
|
||||
<a
|
||||
tuiIconButton
|
||||
size="xs"
|
||||
docsLink
|
||||
path="/start-os/wifi.html"
|
||||
appearance="icon"
|
||||
iconStart="@tui.book-open-text"
|
||||
></a>
|
||||
</div>
|
||||
</ng-container>
|
||||
<section class="g-card">
|
||||
<header>
|
||||
@@ -108,7 +118,7 @@ import { wifiSpec } from './wifi.const'
|
||||
<div tuiCardLarge="compact" [wifi]="data.available"></div>
|
||||
}
|
||||
<p>
|
||||
<button tuiButton (click)="other(data)" appearance="flat">
|
||||
<button tuiButton (click)="other(data)" appearance="grayscale">
|
||||
+ {{ 'Connect to hidden network' | i18n }}
|
||||
</button>
|
||||
</p>
|
||||
@@ -131,6 +141,10 @@ import { wifiSpec } from './wifi.const'
|
||||
:host {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) header > [docsLink] {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
|
||||
@@ -36,7 +36,7 @@ export class NotificationService {
|
||||
getColor(notification: ServerNotification<number>): string {
|
||||
switch (notification.level) {
|
||||
case 'info':
|
||||
return 'var(--tui-status-info)'
|
||||
return 'var(--tui-text-secondary)'
|
||||
case 'success':
|
||||
return 'var(--tui-status-positive)'
|
||||
case 'warning':
|
||||
@@ -62,26 +62,42 @@ export class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
viewModal(notification: ServerNotification<number>, full = false) {
|
||||
viewModal(notification: ServerNotification<number>) {
|
||||
const { data, createdAt, code, title, message } = notification
|
||||
|
||||
if (code === 1) {
|
||||
// Backup Report
|
||||
this.dialogs
|
||||
.openComponent(full ? message : REPORT, {
|
||||
label: 'Backup Report',
|
||||
data: { content: data, createdAt },
|
||||
})
|
||||
.subscribe()
|
||||
} else {
|
||||
// Markdown viewer
|
||||
this.dialogs
|
||||
.openComponent(full ? message : MARKDOWN, {
|
||||
label: title as i18nKey,
|
||||
data: of(data),
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
switch (code) {
|
||||
// Backup report - structured report with per-service results
|
||||
case 1:
|
||||
this.dialogs
|
||||
.openComponent(REPORT, {
|
||||
label: 'Backup Report',
|
||||
data: { content: data, createdAt },
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
break
|
||||
|
||||
// OS update - data contains the full release notes markdown
|
||||
case 2:
|
||||
this.dialogs
|
||||
.openComponent(MARKDOWN, {
|
||||
label: title as i18nKey,
|
||||
data: of(data),
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
break
|
||||
|
||||
// General notification - show the message text
|
||||
default:
|
||||
this.dialogs
|
||||
.openComponent(MARKDOWN, {
|
||||
label: title as i18nKey,
|
||||
data: of(message),
|
||||
size: 'l',
|
||||
})
|
||||
.subscribe()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user