* tell user to restart server after kiosk chnage

* remove unused import

* dont show tor address on server setup

* chore: address comments

* revert mock

* chore: remove uptime block on mobile

* utiliser le futur proche

---------

Co-authored-by: waterplea <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
Matt Hill
2025-11-19 10:35:07 -07:00
committed by GitHub
parent f26791ba39
commit ad0632892e
14 changed files with 111 additions and 129 deletions

View File

@@ -39,7 +39,9 @@ import { DocsLinkDirective } from '@start9labs/shared'
"
>
<div>
<h3 style="color: #f8546a; font-weight: bold">Important!</h3>
<h2 style="font-variant-caps: all-small-caps">
Root Certificate Authority
</h2>
<p>
Download your server's Root CA and
<a
@@ -47,7 +49,7 @@ import { DocsLinkDirective } from '@start9labs/shared'
path="/user-manual/trust-ca.html"
style="color: #6866cc; font-weight: bold; text-decoration: none"
>
follow the instructions
follow instructions
</a>
to establish a secure connection with your server.
</p>
@@ -84,15 +86,15 @@ import { DocsLinkDirective } from '@start9labs/shared'
"
>
<h2 style="font-variant-caps: all-small-caps">
Access from home (LAN)
Permanent Local Address
</h2>
<p>
Visit the address below when you are connected to the same WiFi or
Local Area Network (LAN) as your server.
You must be connected to the same Local Area Network (LAN) as your
server to access this address.
</p>
<p
style="
padding: 16px;
padding: 16px 0;
font-weight: bold;
font-size: 1.1rem;
overflow: auto;
@@ -100,33 +102,6 @@ import { DocsLinkDirective } from '@start9labs/shared'
>
<code id="lan-addr"></code>
</p>
<h2 style="font-variant-caps: all-small-caps">
Access on the go (Tor)
</h2>
<p>Visit the address below when you are away from home.</p>
<p>
<span style="font-weight: bold">Note:</span>
This address will only work from a Tor-enabled browser.
<a
docsLink
path="/user-manual/connecting-remotely/tor.html"
style="color: #6866cc; font-weight: bold; text-decoration: none"
>
Follow the instructions
</a>
to get setup.
</p>
<p
style="
padding: 16px;
font-weight: bold;
font-size: 1.1rem;
overflow: auto;
"
>
<code id="tor-addr"></code>
</p>
</section>
</div>
</body>

View File

@@ -7,7 +7,7 @@ import {
DOCUMENT,
} from '@angular/core'
import { DownloadHTMLService, ErrorService } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
import { TuiButton, TuiIcon, TuiLoader, TuiSurface } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout'
import { DocumentationComponent } from 'src/app/components/documentation.component'
import { MatrixComponent } from 'src/app/components/matrix.component'
@@ -31,10 +31,16 @@ import { StateService } from 'src/app/services/state.service'
<h3>You can now safely unplug your old StartOS data drive</h3>
}
<h3>
http://start.local was for setup purposes only. It will no longer
work.
</h3>
<button tuiCardLarge tuiSurface="floating" (click)="download()">
<strong class="caps">Download address info</strong>
<span>
start.local was for setup purposes only. It will no longer work.
For future reference, this file contains your server's permanent
local address, as well as its Root Certificate Authority (Root CA).
</span>
<strong class="caps">
Download
@@ -48,17 +54,18 @@ import { StateService } from 'src/app/services/state.service'
target="_blank"
[attr.href]="disableLogin ? null : lanAddress"
>
<strong class="caps">Trust your Root CA</strong>
<span>
In the new tab, follow instructions to trust your server's Root CA
and log in.
</span>
<strong class="caps">
Open
Open Local Address
<tui-icon icon="@tui.external-link" />
</strong>
</a>
<app-documentation hidden [lanAddress]="lanAddress" />
} @else {
<tui-loader />
}
</section>
`,
@@ -97,6 +104,10 @@ import { StateService } from 'src/app/services/state.service'
opacity: var(--tui-disabled-opacity);
pointer-events: none;
}
h3 {
text-align: left;
}
`,
imports: [
TuiCardLarge,
@@ -105,6 +116,7 @@ import { StateService } from 'src/app/services/state.service'
TuiSurface,
MatrixComponent,
DocumentationComponent,
TuiLoader,
],
})
export default class SuccessPage implements AfterViewInit {
@@ -117,7 +129,6 @@ export default class SuccessPage implements AfterViewInit {
readonly stateService = inject(StateService)
torAddresses?: string[]
lanAddress?: string
cert?: string
disableLogin = this.stateService.setupType === 'fresh'
@@ -127,10 +138,8 @@ export default class SuccessPage implements AfterViewInit {
}
download() {
const torElem = this.document.getElementById('tor-addr')
const lanElem = this.document.getElementById('lan-addr')
if (torElem) torElem.innerHTML = this.torAddresses?.join('\n') || ''
if (lanElem) lanElem.innerHTML = this.lanAddress || ''
this.document
@@ -155,9 +164,6 @@ export default class SuccessPage implements AfterViewInit {
try {
const ret = await this.api.complete()
if (!this.stateService.kiosk) {
this.torAddresses = ret.torAddresses.map(a =>
a.replace(/^https:/, 'http:'),
)
this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:')
this.cert = ret.rootCa

View File

@@ -484,7 +484,7 @@ export default {
512: 'Der Kiosk-Modus ist auf diesem Gerät nicht verfügbar',
513: 'Aktivieren',
514: 'Deaktivieren',
515: 'Du verwendest derzeit einen Kiosk. Wenn du den Kiosk-Modus deaktivierst, wird die Verbindung zum Kiosk getrennt.',
515: 'Diese Änderung wird nach dem nächsten Neustart wirksam',
516: 'Empfohlen',
517: 'Möchten Sie diese Aufgabe wirklich verwerfen?',
518: 'Verwerfen',

View File

@@ -483,7 +483,7 @@ export const ENGLISH = {
'Kiosk Mode is unavailable on this device': 512,
'Enable': 513,
'Disable': 514,
'You are currently using a kiosk. Disabling Kiosk Mode will result in the kiosk disconnecting.': 515,
'This change will take effect after the next boot': 515,
'Recommended': 516, // as in, we recommend this
'Are you sure you want to dismiss this task?': 517,
'Dismiss': 518, // as in, dismiss or delete a task

View File

@@ -484,7 +484,7 @@ export default {
512: 'El modo quiosco no está disponible en este dispositivo',
513: 'Activar',
514: 'Desactivar',
515: 'Actualmente estás utilizando un quiosco. Desactivar el modo quiosco provocará su desconexión.',
515: 'Este cambio tendrá efecto después del próximo inicio',
516: 'Recomendado',
517: '¿Estás seguro de que deseas descartar esta tarea?',
518: 'Descartar',

View File

@@ -484,7 +484,7 @@ export default {
512: 'Le mode kiosque nest pas disponible sur cet appareil',
513: 'Activer',
514: 'Désactiver',
515: 'Vous utilisez actuellement un kiosque. Désactiver le mode kiosque entraînera sa déconnexion.',
515: 'Ce changement va prendre effet après le prochain démarrage',
516: 'Recommandé',
517: 'Êtes-vous sûr de vouloir ignorer cette tâche ?',
518: 'Ignorer',

View File

@@ -484,7 +484,7 @@ export default {
512: 'Tryb kiosku jest niedostępny na tym urządzeniu',
513: 'Włącz',
514: 'Wyłącz',
515: 'Obecnie używasz kiosku. Wyłączenie trybu kiosku spowoduje jego rozłączenie.',
515: 'Ta zmiana zacznie obowiązywać po następnym uruchomieniu',
516: 'Zalecane',
517: 'Czy na pewno chcesz odrzucić to zadanie?',
518: 'Odrzuć',

View File

@@ -10,7 +10,7 @@ import { I18N, i18nKey } from './i18n.providers'
export class i18nPipe implements PipeTransform {
private readonly i18n = inject(I18N)
transform(englishKey: i18nKey | null | undefined): string {
transform(englishKey: i18nKey | null | undefined | ''): string {
englishKey = englishKey || ('' as i18nKey)
return this.i18n()?.[ENGLISH[englishKey]] || englishKey

View File

@@ -1,17 +1,17 @@
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
computed,
input,
} from '@angular/core'
import { i18nKey, i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { i18nPipe } from '@start9labs/shared'
import { TuiLoader } from '@taiga-ui/core'
import { ServiceUptimeComponent } from 'src/app/routes/portal/routes/services/components/uptime.component'
import { getProgressText } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
import { InstallingInfo } from 'src/app/services/patch-db/data-model'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import {
getInstalledPrimaryStatus,
PrimaryRendering,
PrimaryStatus,
} from 'src/app/services/pkg-status-rendering.service'
@Component({
@@ -19,23 +19,27 @@ import {
template: `
<header>{{ 'Status' | i18n }}</header>
<div>
@if (installingInfo) {
@if (info()) {
<h3>
<tui-loader size="s" [inheritColor]="true" />
{{ 'Installing' | i18n }}
<span class="loading-dots"></span>
{{ getText(installingInfo.progress.overall) | i18n }}
{{ info() | i18n }}
</h3>
} @else {
<h3 [class]="class">
{{ text | i18n }}
@if (text === 'Task Required') {
<h3 [class]="class()">
{{ text() || 'Unknown' | i18n }}
@if (text() === 'Task Required') {
<small>{{ 'See below' | i18n }}</small>
}
@if (rendering?.showDots) {
@if (rendering().showDots) {
<span class="loading-dots"></span>
}
@if ($any(pkg().status)?.started; as started) {
<service-uptime [started]="started" />
}
</h3>
}
<ng-content />
@@ -76,6 +80,12 @@ import {
margin: 0 0.25rem -0.125rem 0;
}
service-uptime {
display: none;
width: fit-content;
margin: 0.5rem 0.125rem;
}
:host-context(tui-root._mobile) {
:host {
min-height: 0;
@@ -94,32 +104,33 @@ import {
small {
text-align: left;
}
service-uptime {
display: flex;
}
}
`,
host: { class: 'g-card' },
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiLoader, i18nPipe],
imports: [TuiLoader, i18nPipe, ServiceUptimeComponent],
})
export class ServiceStatusComponent {
@Input({ required: true })
status?: PrimaryStatus
readonly pkg = input.required<PackageDataEntry>()
readonly connected = input(false)
@Input()
installingInfo?: InstallingInfo
protected readonly status = computed((pkg = this.pkg()) =>
pkg?.stateInfo.state === 'installed'
? getInstalledPrimaryStatus(pkg)
: pkg?.stateInfo.state,
)
@Input()
connected = false
protected readonly rendering = computed(() => PrimaryRendering[this.status()])
protected readonly text = computed(
() => this.connected() && this.rendering().display,
)
private readonly i18n = inject(i18nPipe)
get text(): i18nKey {
return this.connected ? this.rendering?.display || 'Unknown' : 'Unknown'
}
get class(): string | null {
if (!this.connected) return null
switch (this.rendering?.color) {
protected readonly class = computed(() => {
switch (this.connected() && this.rendering().color) {
case 'danger':
return 'g-negative'
case 'warning':
@@ -131,13 +142,10 @@ export class ServiceStatusComponent {
default:
return null
}
}
})
get rendering() {
return this.status && PrimaryRendering[this.status]
}
getText(progress: T.Progress): i18nKey {
return getProgressText(progress)
}
protected readonly info = computed(
(progress = this.pkg().stateInfo.installingInfo?.progress.overall) =>
progress ? getProgressText(progress) : '',
)
}

View File

@@ -79,6 +79,10 @@ import { getManifest } from 'src/app/utils/get-package-data'
overflow: hidden;
}
td:not(:last-child) {
padding-inline-end: 1.5rem;
}
td:last-child {
white-space: nowrap;
text-align: right;

View File

@@ -66,7 +66,8 @@ import { distinctUntilChanged } from 'rxjs/operators'
color: var(--tui-text-primary);
}
:host-context(table) {
:host-context(table),
:host-context(service-status) {
padding: 0;
header {

View File

@@ -39,11 +39,7 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
} @else if (installing()) {
<service-install-progress [pkg]="pkg" />
} @else if (installed()) {
<service-status
[connected]="!!connected()"
[installingInfo]="pkg.stateInfo.installingInfo"
[status]="status()"
>
<service-status [connected]="!!connected()" [pkg]="pkg">
@if (connected()) {
<service-controls [pkg]="pkg" [status]="status()" />
}
@@ -51,10 +47,8 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
@if (status() !== 'backingUp') {
<service-health-checks [checks]="health()" />
<service-uptime
class="g-card"
[started]="$any(pkg.status)?.started"
/>
<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,7 +57,6 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
[errors]="errors"
/>
}
<service-interfaces [pkg]="pkg" [disabled]="status() !== 'running'" />
<service-tasks
#tasks="elementRef"
@@ -91,7 +84,7 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
</button>
}
} @else if (removing()) {
<service-status [connected]="!!connected()" [status]="status()" />
<service-status [connected]="!!connected()" [pkg]="pkg" />
}
}
`,
@@ -139,6 +132,10 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
> * {
grid-column: span 1;
}
service-uptime {
display: none;
}
}
`,
host: { class: 'g-subpage' },

View File

@@ -4,7 +4,6 @@ import {
Component,
inject,
INJECTOR,
DOCUMENT,
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms'
@@ -151,7 +150,7 @@ import { SystemWipeComponent } from './wipe.component'
</span>
</span>
@if (server.kiosk !== null) {
<button tuiButton appearance="primary" (click)="tryToggleKiosk()">
<button tuiButton appearance="primary" (click)="toggleKiosk()">
{{ server.kiosk ? ('Disable' | i18n) : ('Enable' | i18n) }}
</button>
}
@@ -242,7 +241,6 @@ export default class SystemGeneralComponent {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService)
private readonly isTor = inject(ConfigService).isTor()
private readonly document = inject(DOCUMENT)
private readonly dialog = inject(DialogService)
private readonly i18n = inject(i18nPipe)
private readonly injector = inject(INJECTOR)
@@ -326,28 +324,6 @@ export default class SystemGeneralComponent {
.subscribe(() => this.resetTor(this.wipe))
}
async tryToggleKiosk() {
if (
this.server()?.kiosk &&
['localhost', '127.0.0.1'].includes(this.document.location.hostname)
) {
return this.dialog
.openConfirm({
label: 'Warning',
data: {
content:
'You are currently using a kiosk. Disabling Kiosk Mode will result in the kiosk disconnecting.',
yes: 'Disable',
no: 'Cancel',
},
})
.pipe(filter(Boolean))
.subscribe(async () => this.toggleKiosk())
}
this.toggleKiosk()
}
async onRepair() {
this.dialog
.openConfirm({
@@ -370,7 +346,7 @@ export default class SystemGeneralComponent {
})
}
private async toggleKiosk() {
async toggleKiosk() {
const kiosk = this.server()?.kiosk
const loader = this.loader
@@ -379,6 +355,11 @@ export default class SystemGeneralComponent {
try {
await this.api.toggleKiosk(!kiosk)
this.dialog
.openAlert('This change will take effect after the next boot', {
label: 'Restart to apply',
})
.subscribe()
} catch (e: any) {
this.errorService.handleError(e)
} finally {

View File

@@ -1,16 +1,20 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import {
ChangeDetectionStrategy,
Component,
inject,
viewChild,
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { RouterLink } from '@angular/router'
import { verify } from '@start9labs/argon2'
import {
DialogService,
ErrorService,
i18nKey,
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { ISB } from '@start9labs/start-sdk'
import { TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiAlertService, TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiHeader } from '@taiga-ui/layout'
import { PatchDB } from 'patch-db-client'
import { from } from 'rxjs'
@@ -70,13 +74,14 @@ import { getServerInfo } from 'src/app/utils/get-server-info'
],
})
export default class SystemPasswordComponent {
private readonly dialog = inject(DialogService)
private readonly alerts = inject(TuiAlertService)
private readonly loader = inject(LoadingService)
private readonly errorService = inject(ErrorService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService)
private readonly i18n = inject(i18nPipe)
readonly form = viewChild(FormComponent)
readonly spec = toSignal(from(configBuilderToSpec(this.passwordSpec())))
readonly buttons = [
{
@@ -119,7 +124,12 @@ export default class SystemPasswordComponent {
try {
await this.api.resetPassword({ oldPassword, newPassword })
this.dialog.openAlert('Password changed').subscribe()
this.form()?.form.reset()
this.alerts
.open(this.i18n.transform('Password changed'), {
appearance: 'positive',
})
.subscribe()
} catch (e: any) {
this.errorService.handleError(e)
} finally {