update/alpha.9 (#2988)

* import marketplac preview for sideload

* fix: improve state service (#2977)

* fix: fix sideload DI

* fix: update Angular

* fix: cleanup

* fix: fix version selection

* Bump node version to fix build for Angular

* misc fixes
- update node to v22
- fix chroot-and-upgrade access to prune-images
- don't self-migrate legacy packages
- #2985
- move dataVersion to volume folder
- remove "instructions.md" from s9pk
- add "docsUrl" to manifest

* version bump

* include flavor when clicking view listing from updates tab

* closes #2980

* fix: fix select button

* bring back ssh keys

* fix: drop 'portal' from all routes

* fix: implement longtap action to select table rows

* fix description for ssh page

* replace instructions with docsLink and refactor marketplace preview

* delete unused translations

* fix patchdb diffing algorithm

* continue refactor of marketplace lib show components

* Booting StartOS instead of Setting up your server on init

* misc fixes
- closes #2990
- closes #2987

* fix build

* docsUrl and clickable service headers

* don't cleanup after update until new service install succeeds

* update types

* misc fixes

* beta.35

* sdkversion, githash for sideload, correct logs for init, startos pubkey display

* bring back reboot button on install

* misc fixes

* beta.36

* better handling of setup and init for websocket errors

* reopen init and setup logs even on graceful closure

* better logging, misc fixes

* fix build

* dont let package stats hang

* dont show docsurl in marketplace if no docsurl

* re-add needs-config

* show error if init fails, shorten hover state on header icons

* fix operator precedemce

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Mariusz Kogen <k0gen@pm.me>
This commit is contained in:
Aiden McClelland
2025-07-18 18:31:12 +00:00
committed by GitHub
parent ba2906a42e
commit 377b7b12ce
237 changed files with 5953 additions and 4777 deletions

View File

@@ -1,61 +0,0 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { TuiProgress } from '@taiga-ui/kit'
import { LogsWindowComponent } from './logs-window.component'
import { i18nPipe } from '../i18n/i18n.pipe'
@Component({
selector: 'app-initializing',
template: `
<section>
<h1 [style.font-size.rem]="2" [style.margin-bottom.rem]="2">
{{ 'Setting up your server' | i18n }}
</h1>
<div>
{{ 'Progress' | i18n }}: {{ (progress.total * 100).toFixed(0) }}%
</div>
<progress
tuiProgressBar
[style.max-width.rem]="40"
[style.margin]="'1rem auto'"
[attr.value]="progress.total"
></progress>
<p [innerHTML]="progress.message || 'Finished'"></p>
</section>
<logs-window />
`,
styles: `
section {
border-radius: 0.25rem;
padding: 1rem;
margin: 1.5rem;
text-align: center;
// @TODO Theme
background: #e0e0e0;
color: #333;
--tui-background-neutral-1: rgba(0, 0, 0, 0.1);
}
logs-window {
display: flex;
flex-direction: column;
height: 18rem;
padding: 1rem;
margin: 0 1.5rem auto;
text-align: left;
overflow: hidden;
border-radius: 2rem;
// @TODO Theme
background: #181818;
}
`,
imports: [CommonModule, LogsWindowComponent, TuiProgress, i18nPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InitializingComponent {
@Input()
progress: { total: number; message: string } = { total: 0, message: '' }
@Input()
setupType?: 'fresh' | 'restore' | 'attach' | 'transfer'
}

View File

@@ -0,0 +1,92 @@
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
input,
Input,
} from '@angular/core'
import { TuiProgress } from '@taiga-ui/kit'
import { LogsWindowComponent } from './logs-window.component'
import { i18nPipe } from '../../i18n/i18n.pipe'
@Component({
selector: 'app-initializing',
template: `
@if (error(); as err) {
<section>
<h1>{{ 'Error initializing server' | i18n }}</h1>
<p>{{ err }}</p>
</section>
} @else {
<section>
<h1 [style.font-size.rem]="2" [style.margin-bottom.rem]="2">
{{
setupType()
? ('Setting up your server' | i18n)
: ('Booting StartOS' | i18n)
}}
</h1>
<div>
{{ 'Progress' | i18n }}: {{ (progress().total * 100).toFixed(0) }}%
</div>
<progress
tuiProgressBar
[style.max-width.rem]="40"
[style.margin]="'1rem auto'"
[attr.value]="progress().total"
></progress>
<p [innerHTML]="message()"></p>
</section>
}
<logs-window />
`,
styles: `
section {
border-radius: 0.25rem;
padding: 1rem;
margin: 1.5rem;
text-align: center;
// @TODO Theme
background: #e0e0e0;
color: #333;
--tui-background-neutral-1: rgba(0, 0, 0, 0.1);
}
logs-window {
display: flex;
flex-direction: column;
height: 18rem;
padding: 1rem;
margin: 0 1.5rem auto;
text-align: left;
overflow: hidden;
border-radius: 2rem;
// @TODO Theme
background: #181818;
}
`,
imports: [LogsWindowComponent, TuiProgress, i18nPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InitializingComponent {
private readonly i18nPipe = inject(i18nPipe)
readonly progress = input<{ total: number; message: string }>({
total: 0,
message: '',
})
readonly setupType = input<
'fresh' | 'restore' | 'attach' | 'transfer' | undefined
>()
readonly error = input<string>()
readonly message = computed(() => {
return (
this.progress().message ||
(this.progress().total === 1
? this.i18nPipe.transform('Finished')
: '...')
)
})
}

View File

@@ -1,5 +1,5 @@
import { AsyncPipe } from '@angular/common'
import { Component, ElementRef, inject } from '@angular/core'
import { Component, ElementRef, inject, input } from '@angular/core'
import {
INTERSECTION_ROOT,
WaIntersectionObserver,
@@ -7,7 +7,7 @@ import {
import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
import { TuiScrollbar } from '@taiga-ui/core'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { SetupLogsService } from '../services/setup-logs.service'
import { SetupLogsService } from '../../services/setup-logs.service'
@Component({
selector: 'logs-window',

View File

@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { TuiAutoFocus } from '@taiga-ui/cdk'
@@ -14,7 +13,9 @@ import { i18nKey } from '../i18n/i18n.providers'
@Component({
template: `
<p>{{ options.message }}</p>
<p *ngIf="options.warning" class="warning">{{ options.warning }}</p>
@if (options.warning) {
<p class="warning">{{ options.warning }}</p>
}
<form (ngSubmit)="submit(value.trim())">
<tui-input
tuiAutoFocus
@@ -24,7 +25,9 @@ import { i18nKey } from '../i18n/i18n.providers'
[(ngModel)]="value"
>
{{ options.label }}
<span *ngIf="options.required !== false && options.label">*</span>
@if (options.required !== false && options.label) {
<span>*</span>
}
<input
tuiTextfieldLegacy
[class.masked]="options.useMask && masked && value"
@@ -74,7 +77,6 @@ import { i18nKey } from '../i18n/i18n.providers'
}
`,
imports: [
CommonModule,
FormsModule,
TuiInputModule,
TuiButton,

View File

@@ -1,5 +1,4 @@
import { DOCUMENT } from '@angular/common'
import { Directive, inject } from '@angular/core'
import { Directive, inject, DOCUMENT } from '@angular/core'
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import {
MutationObserverService,

View File

@@ -274,11 +274,9 @@ export default {
273: 'Git-Hash',
274: 'Lizenz',
275: 'Installiert von',
276: 'Dienst-Repository',
277: 'Paket-Repository',
278: 'Marketing-Website',
279: 'Support-Website',
280: 'Spendenlink',
278: 'Marketing',
279: 'Support',
280: 'Spenden',
281: 'Standardaktionen',
282: 'Dienst neu bauen',
283: 'Baut den Dienst-Container neu. Nur erforderlich, wenn ein Fehler in StartOS vorliegt.',
@@ -287,7 +285,6 @@ export default {
286: 'Dashboard',
287: 'dashboard',
288: 'aktionen',
289: 'anleitungen',
290: 'logs',
291: 'über',
292: 'Upload wird gestartet',
@@ -485,7 +482,7 @@ export default {
484: 'Laden Sie die Seite neu. Wenn das nicht funktioniert, beenden Sie Ihren Browser und öffnen Sie ihn erneut, um diese Seite erneut zu besuchen.',
485: 'StartOS-Benutzeroberfläche',
486: 'WiFi',
487: 'Anleitungen',
487: 'Dokumentation',
488: 'spanisch',
489: 'polnisch',
490: 'deutsch',
@@ -496,7 +493,7 @@ export default {
495: 'Validierung',
496: 'in Bearbeitung',
497: 'abgeschlossen',
498: 'Klicken Sie hier, um alle Versionen anzuzeigen',
498: 'StartOS wird gestartet',
499: 'Um loszulegen, besuche den Marktplatz und lade deinen ersten Dienst herunter',
500: 'Marktplatz anzeigen',
501: 'Willkommen bei',
@@ -522,4 +519,13 @@ export default {
521: 'Um das Problem zu beheben, siehe',
522: 'SDK Version',
523: 'Sicherungsbericht',
524: 'Ausgewählte löschen',
525: 'Keine schlüssel',
526: 'Öffentlichen SSH-Schlüssel hinzufügen',
527: 'Standardmäßig kannst du dich per SSH von jedem Gerät aus mit deinem Server verbinden, indem du dein Master-Passwort verwendest. Optional kannst du SSH-öffentliche Schlüssel hinzufügen, um bestimmten Geräten den Zugriff ohne Passworteingabe zu ermöglichen.',
528: 'Quellcode',
529: 'Upstream-Dienst',
530: 'StartOS-Paket',
531: 'Fehler beim Initialisieren des Servers',
532: 'Abgeschlossen',
} satisfies i18n

View File

@@ -273,20 +273,17 @@ export const ENGLISH = {
'Git Hash': 273,
'License': 274,
'Installed From': 275,
'Service Repository': 276,
'Package Repository': 277,
'Marketing Site': 278,
'Support Site': 279,
'Donation Link': 280,
'Marketing': 278,
'Support': 279,
'Donations': 280,
'Standard Actions': 281,
'Rebuild Service': 282, // as in, rebuild a software container
'Rebuilds the service container. Only necessary if there is a bug in StartOS': 283,
'Uninstall': 284,
'Uninstalls this service from StartOS and delete all data permanently.': 285,
'Uninstalls this service from StartOS and deletes all data permanently.': 285,
'Dashboard': 286,
'dashboard': 287,
'actions': 288,
'instructions': 289,
'logs': 290, // as in, "application logs"
'about': 291, // as in, "about this server"
'Starting upload': 292,
@@ -484,7 +481,7 @@ export const ENGLISH = {
'Refresh the page. If refreshing the page does not work, you may need to quit and re-open your browser, then revisit this page.': 484,
'StartOS UI': 485,
'WiFi': 486,
'Instructions': 487,
'Documentation': 487, // as in, a website to view documentation
'spanish': 488,
'polish': 489,
'german': 490,
@@ -495,7 +492,7 @@ export const ENGLISH = {
'Validating': 495,
'in progress': 496,
'complete': 497,
'Click to view all versions': 498,
'Booting StartOS': 498,
'To get started, visit the Marketplace and download your first service': 499,
'View Marketplace': 500,
'Welcome to': 501,
@@ -521,4 +518,13 @@ export const ENGLISH = {
'To resolve the issue, refer to': 521,
'SDK Version': 522,
'Backup Report': 523,
'Delete selected': 524,
'No keys': 525,
'Add SSH Public Key': 526,
'By default, you can SSH into your server from any device using your master password. Optionally add SSH public keys to grant specific devices access without needing to enter a password.': 527,
'Source Code': 528,
'Upstream service': 529, // as in, the URL of the source code for the original software
'StartOS package': 530, // as in, the URL of the source code for the StartOS package
'Error initializing server': 531,
'Finished': 532, // an in, complete
} as const

View File

@@ -274,11 +274,9 @@ export default {
273: 'Hash de Git',
274: 'Licencia',
275: 'Instalado desde',
276: 'Repositorio del servicio',
277: 'Repositorio del paquete',
278: 'Sitio de marketing',
279: 'Sitio de soporte',
280: 'Enlace de donación',
278: 'Marketing',
279: 'Soporte',
280: 'Donaciones',
281: 'Acciones estándar',
282: 'Reconstruir servicio',
283: 'Reconstruye el contenedor del servicio. Solo es necesario si hay un error en StartOS',
@@ -287,7 +285,6 @@ export default {
286: 'Panel de control',
287: 'panel de control',
288: 'acciones',
289: 'instrucciones',
290: 'registros',
291: 'acerca de',
292: 'Iniciando carga',
@@ -485,7 +482,7 @@ export default {
484: 'Actualiza la página. Si actualizar no funciona, puede que necesites cerrar y volver a abrir tu navegador, y luego volver a esta página.',
485: 'Interfaz de StartOS',
486: 'WiFi',
487: 'Instrucciones',
487: 'Documentación',
488: 'español',
489: 'polaco',
490: 'alemán',
@@ -496,7 +493,7 @@ export default {
495: 'Validando',
496: 'en progreso',
497: 'completo',
498: 'Haga clic para ver todas las versiones',
498: 'Iniciando StartOS',
499: 'Para comenzar, visita el Mercado y descarga tu primer servicio',
500: 'Ver Marketplace',
501: 'Bienvenido a',
@@ -522,4 +519,13 @@ export default {
521: 'Para resolver el problema, consulta',
522: 'Versión de SDK',
523: 'Informe de respaldo',
524: 'Eliminar seleccionado',
525: 'Sin llaves',
526: 'Agregar clave pública SSH',
527: 'De forma predeterminada, puedes conectarte por SSH a tu servidor desde cualquier dispositivo usando tu contraseña maestra. Opcionalmente, añade claves públicas SSH para otorgar acceso a dispositivos específicos sin necesidad de ingresar una contraseña.',
528: 'Código fuente',
529: 'Servicio original',
530: 'Paquete StartOS',
531: 'Error al inicializar el servidor',
532: 'Finalizado',
} satisfies i18n

View File

@@ -274,11 +274,9 @@ export default {
273: 'Hash Git',
274: 'Licence',
275: 'Installé depuis',
276: 'Dépôt du service',
277: 'Dépôt du paquet',
278: 'Site marketing',
279: 'Site de support',
280: 'Lien de don',
278: 'Marketing',
279: 'Support',
280: 'Dons',
281: 'Actions standards',
282: 'Reconstruire le service',
283: 'Reconstruit le conteneur du service. Nécessaire uniquement en cas de bug dans StartOS',
@@ -287,7 +285,6 @@ export default {
286: 'Tableau de bord',
287: 'tableau de bord',
288: 'actions',
289: 'instructions',
290: 'journaux',
291: 'à propos',
292: 'Début du téléversement',
@@ -485,7 +482,7 @@ export default {
484: 'Rafraîchissez la page. Si cela ne fonctionne pas, quittez puis rouvrez votre navigateur et revenez sur cette page.',
485: 'Interface de StartOS',
486: 'WiFi',
487: 'Instructions',
487: 'Documentation',
488: 'espagnol',
489: 'polonais',
490: 'allemand',
@@ -496,7 +493,7 @@ export default {
495: 'Validation',
496: 'en cours',
497: 'terminé',
498: 'Cliquez pour voir toutes les versions',
498: 'Démarrage de StartOS',
499: 'Pour commencer, visitez la bibliothèque de services et téléchargez votre premier service',
500: 'Voir la bibliothèque de services',
501: 'Bienvenue sur',
@@ -522,4 +519,13 @@ export default {
521: 'Pour résoudre le problème, consultez',
522: 'Version de SDK',
523: 'Rapport de sauvegarde',
524: 'Supprimer la sélection',
525: 'Pas de clés',
526: 'Ajouter une clé publique SSH',
527: 'Par défaut, vous pouvez accéder à votre serveur en SSH depuis nimporte quel appareil en utilisant votre mot de passe maître. Vous pouvez également ajouter des clés publiques SSH pour accorder laccès à certains appareils sans avoir à saisir de mot de passe.',
528: 'Code source',
529: 'Service en amont',
530: 'Paquet StartOS',
531: "Erreur lors de l'initialisation du serveur",
532: 'Terminé',
} satisfies i18n

View File

@@ -274,11 +274,9 @@ export default {
273: 'Hash Git',
274: 'Licencja',
275: 'Zainstalowano z',
276: 'Repozytorium serwisu',
277: 'Repozytorium pakietu',
278: 'Strona marketingowa',
279: 'Strona wsparcia',
280: 'Link do darowizny',
278: 'Marketingowa',
279: 'Wsparcia',
280: 'Darowizny',
281: 'Standardowe akcje',
282: 'Odbuduj serwis',
283: 'Odbudowuje kontener serwisu. Konieczne tylko w przypadku błędu w StartOS',
@@ -287,7 +285,6 @@ export default {
286: 'Panel',
287: 'panel',
288: 'akcje',
289: 'instrukcje',
290: 'logi',
291: 'informacje',
292: 'Rozpoczynanie przesyłania',
@@ -485,7 +482,7 @@ export default {
484: 'Odśwież stronę. Jeśli odświeżenie strony nie działa, może być konieczne zamknięcie i ponowne otwarcie przeglądarki, a następnie ponowne odwiedzenie tej strony.',
485: 'Przyłącza StartOS',
486: 'WiFi',
487: 'Instrukcje',
487: 'Dokumentacji',
488: 'hiszpański',
489: 'polski',
490: 'niemiecki',
@@ -496,7 +493,7 @@ export default {
495: 'Weryfikowanie',
496: 'w toku',
497: 'zakończono',
498: 'Kliknij, aby zobaczyć wszystkie wersje',
498: 'Uruchamianie StartOS',
499: 'Aby rozpocząć, odwiedź Marketplace i pobierz swoją pierwszą usługę',
500: 'Zobacz Rynek',
501: 'Witamy w',
@@ -522,4 +519,13 @@ export default {
521: 'Aby rozwiązać problem, zapoznaj się z',
522: 'Wersja SDK',
523: 'Raport kopii zapasowej',
524: 'Usuń wybrane',
525: 'Brak kluczy',
526: 'Dodaj klucz publiczny SSH',
527: 'Domyślnie możesz połączyć się z serwerem przez SSH z dowolnego urządzenia, używając hasła głównego. Opcjonalnie dodaj klucze publiczne SSH, aby przyznać dostęp określonym urządzeniom bez potrzeby wpisywania hasła.',
528: 'Kod źródłowy',
529: 'Usługa źródłowa',
530: 'Pakiet StartOS',
531: 'Błąd inicjalizacji serwera',
532: 'Zakończono',
} satisfies i18n

View File

@@ -5,8 +5,8 @@
export * from './classes/http-error'
export * from './classes/rpc-error'
export * from './components/logs-window.component'
export * from './components/initializing.component'
export * from './components/initializing/logs-window.component'
export * from './components/initializing/initializing.component'
export * from './components/ticker.component'
export * from './components/drive.component'
export * from './components/markdown.component'

View File

@@ -1,5 +1,4 @@
import { DOCUMENT } from '@angular/common'
import { Inject, Injectable } from '@angular/core'
import { Inject, Injectable, DOCUMENT } from '@angular/core'
@Injectable({
providedIn: 'root',

View File

@@ -1,5 +1,4 @@
import { DOCUMENT } from '@angular/common'
import { Inject, Injectable } from '@angular/core'
import { Inject, Injectable, DOCUMENT } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import {
firstValueFrom,

View File

@@ -1,19 +1,23 @@
import { StaticClassProvider } from '@angular/core'
import {
bufferTime,
catchError,
defer,
delay,
filter,
map,
Observable,
repeatWhen,
scan,
switchMap,
timer,
} from 'rxjs'
import { FollowLogsReq, FollowLogsRes, Log } from '../types/api'
import { Constructor } from '../types/constructor'
import { convertAnsi } from '../util/convert-ansi'
interface Api {
followServerLogs: (params: FollowLogsReq) => Promise<FollowLogsRes>
initFollowLogs: (params: FollowLogsReq) => Promise<FollowLogsRes>
openWebsocket$: (guid: string) => Observable<Log>
}
@@ -28,12 +32,14 @@ export function provideSetupLogsService(
}
export class SetupLogsService extends Observable<readonly string[]> {
private readonly log$ = defer(() => this.api.followServerLogs({})).pipe(
private readonly log$ = defer(() => this.api.initFollowLogs({})).pipe(
switchMap(({ guid }) => this.api.openWebsocket$(guid)),
bufferTime(500),
filter(logs => !!logs.length),
map(convertAnsi),
scan((logs: readonly string[], log) => [...logs, log], []),
repeatWhen(obs => obs.pipe(delay(500))),
catchError((_, watch$) => timer(500).pipe(switchMap(() => watch$))),
)
constructor(private readonly api: Api) {

View File

@@ -98,9 +98,6 @@ $wide-modal: 900px;
}
body {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
@@ -174,7 +171,7 @@ a {
font-weight: 300;
text-transform: uppercase;
letter-spacing: 0.06rem;
margin-bottom: 1rem;
margin: 0rem 0 1rem 0;
pointer-events: none;
}