import { AfterViewInit, Component, ElementRef, inject, ViewChild, DOCUMENT, } from '@angular/core' import { DialogService, DownloadHTMLService, ErrorService, i18nPipe, } from '@start9labs/shared' import { TuiIcon, TuiLoader, TuiTitle } from '@taiga-ui/core' import { TuiAvatar } from '@taiga-ui/kit' import { TuiCardLarge, TuiCell, TuiHeader } from '@taiga-ui/layout' import { ApiService } from '../services/api.service' import { StateService } from '../services/state.service' import { DocumentationComponent } from '../components/documentation.component' import { MatrixComponent } from '../components/matrix.component' import { RemoveMediaDialog } from '../components/remove-media.dialog' import { SetupCompleteRes } from '../types' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' @Component({ template: `

{{ 'Setup Complete!' | i18n }} @if (!stateService.kiosk) { {{ 'http://start.local was for setup only. It will no longer work.' | i18n }} }

@if (!result) { } @else { @if (!stateService.kiosk) { } @if (result.needsRestart) { } @if (!stateService.kiosk) {
`, styles: ` .inline-title { display: inline-flex; align-items: center; gap: 0.5rem; } [tuiCell].disabled { opacity: var(--tui-disabled-opacity); pointer-events: none; } `, imports: [ TuiCardLarge, TuiCell, TuiIcon, TuiLoader, TuiAvatar, MatrixComponent, DocumentationComponent, TuiHeader, TuiTitle, i18nPipe, ], }) export default class SuccessPage implements AfterViewInit { @ViewChild(DocumentationComponent, { read: ElementRef }) private readonly documentation?: ElementRef private readonly document = inject(DOCUMENT) private readonly errorService = inject(ErrorService) private readonly api = inject(ApiService) private readonly downloadHtml = inject(DownloadHTMLService) private readonly dialogs = inject(DialogService) private readonly i18n = inject(i18nPipe) readonly stateService = inject(StateService) result?: SetupCompleteRes lanAddress = '' downloaded = false usbRemoved = false rebooting = false rebooted = false get canOpenAddress(): boolean { if (!this.downloaded) return false if (this.result?.needsRestart && !this.rebooted) return false return true } ngAfterViewInit() { setTimeout(() => this.complete(), 500) } download() { if (this.downloaded) return const lanElem = this.document.getElementById('lan-addr') if (lanElem) lanElem.innerHTML = this.lanAddress this.document .getElementById('cert') ?.setAttribute( 'href', `data:application/octet-stream;base64,${this.result!.rootCa}`, ) const html = this.documentation?.nativeElement.innerHTML || '' this.downloadHtml.download('StartOS-info.html', html).then(() => { this.downloaded = true }) } removeMedia() { this.dialogs .openComponent( new PolymorpheusComponent(RemoveMediaDialog), { size: 's', dismissible: false, closeable: false, }, ) .subscribe(() => { this.usbRemoved = true }) } exitKiosk() { this.api.exit() } openLocalAddress() { window.open(this.lanAddress, '_blank') } async reboot() { this.rebooting = true try { await this.api.exit() await this.pollForServer() this.rebooted = true this.rebooting = false } catch (e: any) { this.errorService.handleError(e) this.rebooting = false } } private async complete() { try { this.result = await this.api.complete() if (!this.stateService.kiosk) { this.lanAddress = `http://${this.result.hostname}.local` if (!this.result.needsRestart) { await this.api.exit() } } } catch (e: any) { this.errorService.handleError(e) } } private async pollForServer(): Promise { const maxAttempts = 60 let attempts = 0 while (attempts < maxAttempts) { try { await this.api.echo({ message: 'ping' }, this.lanAddress) return } catch { await new Promise(resolve => setTimeout(resolve, 5000)) attempts++ } } throw new Error( this.i18n.transform( 'Server did not come back online. Please check your server and try accessing it manually.', ), ) } }