mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
Add logs window to setup wizard loading screen (#2076)
* add logs window to setup wizard loading screen * fix type error * Update frontend/projects/setup-wizard/src/app/services/api/live-api.service.ts Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com> --------- Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
This commit is contained in:
committed by
Aiden McClelland
parent
e53c90f8f0
commit
873f2b2814
@@ -3,10 +3,11 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { LoadingPage, ToMessagePipe } from './loading.page'
|
||||
import { LogsWindowComponent } from './logs-window/logs-window.component'
|
||||
import { LoadingPageRoutingModule } from './loading-routing.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, FormsModule, IonicModule, LoadingPageRoutingModule],
|
||||
declarations: [LoadingPage, ToMessagePipe],
|
||||
declarations: [LoadingPage, ToMessagePipe, LogsWindowComponent],
|
||||
})
|
||||
export class LoadingPageModule {}
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
<p>{{ progress.decimal | toMessage }}</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
|
||||
<div class="logs-container">
|
||||
<logs-window></logs-window>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
ion-card-title {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
margin-top: 24px;
|
||||
height: 280px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
border-radius: 31px;
|
||||
margin-inline: 10px;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<ion-content
|
||||
[scrollEvents]="true"
|
||||
(ionScroll)="handleScroll($event)"
|
||||
(ionScrollEnd)="handleScrollEnd()"
|
||||
class="ion-padding"
|
||||
color="light"
|
||||
>
|
||||
<div id="container"></div>
|
||||
</ion-content>
|
||||
|
||||
<div id="template"></div>
|
||||
@@ -0,0 +1,10 @@
|
||||
// Hide scrollbar for Chrome, Safari and Opera
|
||||
ion-content::part(scroll)::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide scrollbar for IE, Edge and Firefox
|
||||
ion-content::part(scroll) {
|
||||
-ms-overflow-style: none; // IE and Edge
|
||||
scrollbar-width: none; // Firefox
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { IonContent } from '@ionic/angular'
|
||||
import { from, map, switchMap, takeUntil } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { Log, toLocalIsoString } from '@start9labs/shared'
|
||||
import { TuiDestroyService } from '@taiga-ui/cdk'
|
||||
|
||||
var Convert = require('ansi-to-html')
|
||||
var convert = new Convert({
|
||||
bg: 'transparent',
|
||||
})
|
||||
|
||||
@Component({
|
||||
selector: 'logs-window',
|
||||
templateUrl: 'logs-window.component.html',
|
||||
styleUrls: ['logs-window.component.scss'],
|
||||
providers: [TuiDestroyService],
|
||||
})
|
||||
export class LogsWindowComponent {
|
||||
@ViewChild(IonContent)
|
||||
private content?: IonContent
|
||||
|
||||
autoScroll = true
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly destroy$: TuiDestroyService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
from(this.api.followLogs())
|
||||
.pipe(
|
||||
switchMap(guid =>
|
||||
this.api.openLogsWebsocket$(guid).pipe(
|
||||
map(log => {
|
||||
const container = document.getElementById('container')
|
||||
const newLogs = document.getElementById('template')?.cloneNode()
|
||||
|
||||
if (!(newLogs instanceof HTMLElement)) return
|
||||
|
||||
newLogs.innerHTML = this.convertToAnsi(log)
|
||||
|
||||
container?.append(newLogs)
|
||||
|
||||
if (this.autoScroll) {
|
||||
setTimeout(() => {
|
||||
this.content?.scrollToBottom(250)
|
||||
}, 0)
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
handleScroll(e: any) {
|
||||
if (e.detail.deltaY < 0) this.autoScroll = false
|
||||
}
|
||||
|
||||
async handleScrollEnd() {
|
||||
const elem = await this.content?.getScrollElement()
|
||||
if (elem && elem.scrollHeight - elem.scrollTop - elem.clientHeight < 64) {
|
||||
this.autoScroll = true
|
||||
}
|
||||
}
|
||||
|
||||
private convertToAnsi(log: Log) {
|
||||
return `<span style="color: #FFF; font-weight: bold;">${toLocalIsoString(
|
||||
new Date(log.timestamp),
|
||||
)}</span> ${convert.toHtml(log.message)}<br />`
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as jose from 'node-jose'
|
||||
import { DiskListResponse, StartOSDiskInfo } from '@start9labs/shared'
|
||||
import { DiskListResponse, StartOSDiskInfo, Log } from '@start9labs/shared'
|
||||
import { Observable } from 'rxjs'
|
||||
|
||||
export abstract class ApiService {
|
||||
pubkey?: jose.JWK.Key
|
||||
|
||||
@@ -11,6 +13,8 @@ export abstract class ApiService {
|
||||
abstract execute(setupInfo: ExecuteReq): Promise<void> // setup.execute
|
||||
abstract complete(): Promise<CompleteRes> // setup.complete
|
||||
abstract exit(): Promise<void> // setup.exit
|
||||
abstract followLogs(): Promise<string> // setup.logs.follow
|
||||
abstract openLogsWebsocket$(guid: string): Observable<Log>
|
||||
|
||||
async encrypt(toEncrypt: string): Promise<Encrypted> {
|
||||
if (!this.pubkey) throw new Error('No pubkey found!')
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
encodeBase64,
|
||||
HttpService,
|
||||
isRpcError,
|
||||
Log,
|
||||
RpcError,
|
||||
RPCOptions,
|
||||
} from '@start9labs/shared'
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
CompleteRes,
|
||||
} from './api.service'
|
||||
import * as jose from 'node-jose'
|
||||
import { webSocket } from 'rxjs/webSocket'
|
||||
import { Observable } from 'rxjs'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -87,6 +90,14 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async followLogs(): Promise<string> {
|
||||
return this.rpcRequest({ method: 'setup.logs.follow', params: {} })
|
||||
}
|
||||
|
||||
openLogsWebsocket$(guid: string): Observable<Log> {
|
||||
return webSocket(`http://start.local/ws/${guid}`)
|
||||
}
|
||||
|
||||
async complete() {
|
||||
const res = await this.rpcRequest<CompleteRes>({
|
||||
method: 'setup.complete',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { encodeBase64, pauseFor } from '@start9labs/shared'
|
||||
import { encodeBase64, Log, pauseFor } from '@start9labs/shared'
|
||||
import {
|
||||
ApiService,
|
||||
CifsRecoverySource,
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CompleteRes,
|
||||
} from './api.service'
|
||||
import * as jose from 'node-jose'
|
||||
import { interval, map, Observable } from 'rxjs'
|
||||
|
||||
let tries: number
|
||||
|
||||
@@ -146,6 +147,20 @@ export class MockApiService extends ApiService {
|
||||
await pauseFor(1000)
|
||||
}
|
||||
|
||||
async followLogs(): Promise<string> {
|
||||
await pauseFor(1000)
|
||||
return 'fake-guid'
|
||||
}
|
||||
|
||||
openLogsWebsocket$(guid: string): Observable<Log> {
|
||||
return interval(500).pipe(
|
||||
map(() => ({
|
||||
timestamp: new Date().toISOString(),
|
||||
message: 'fake log entry',
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
async complete(): Promise<CompleteRes> {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user