Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/logs revamp #2075

Merged
merged 1 commit into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion frontend/projects/shared/styles/shared.scss
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,22 @@ ion-modal {

.color-primary-shade {
color: var(--ion-color-primary-shade)
}
}

@keyframes ellipsis-dot {
25% {
content: '';
}

50% {
content: '.';
}

75% {
content: '..';
}

100% {
content: '...';
}
}
20 changes: 14 additions & 6 deletions frontend/projects/ui/src/app/components/logs/logs.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ion-buttons slot="start">
<ion-back-button [defaultHref]="defaultBack"></ion-back-button>
</ion-buttons>
<ion-title>{{ title }}</ion-title>
<ion-title>{{ pageTitle }}</ion-title>
</ion-toolbar>
</ion-header>

Expand All @@ -15,7 +15,7 @@
>
<ion-infinite-scroll
id="scroller"
[disabled]="!needInfinite"
[disabled]="infiniteStatus !== 1"
position="top"
threshold="1000"
(ionInfinite)="doInfinite($event)"
Expand All @@ -33,10 +33,18 @@

<ng-container *ngIf="!loading">
<div id="bottom-div"></div>
<div *ngIf="websocketFail" class="ion-text-center ion-padding">
<ion-text color="warning"> Websocket failed.... </ion-text>
</div>

<p
*ngIf="websocketStatus === 'reconnecting'"
class="ion-text-center loading-dots"
>
<ion-text color="success">Reconnecting</ion-text>
</p>
<p
*ngIf="websocketStatus === 'disconnected'"
class="ion-text-center loading-dots"
>
<ion-text color="warning">Waiting for network connectivity</ion-text>
</p>
<div
[ngStyle]="{
position: 'fixed',
Expand Down
144 changes: 94 additions & 50 deletions frontend/projects/ui/src/app/components/logs/logs.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { Component, Input, ViewChild } from '@angular/core'
import { IonContent, LoadingController } from '@ionic/angular'
import { bufferTime, takeUntil, tap } from 'rxjs'
import {
bufferTime,
catchError,
filter,
finalize,
from,
Observable,
switchMap,
takeUntil,
tap,
} from 'rxjs'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
import {
LogsRes,
Expand All @@ -13,6 +23,7 @@ import {
} from '@start9labs/shared'
import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConnectionService } from 'src/app/services/connection.service'

var Convert = require('ansi-to-html')
var convert = new Convert({
Expand Down Expand Up @@ -40,66 +51,41 @@ export class LogsComponent {
@Input() fetchLogs!: (params: ServerLogsReq) => Promise<LogsRes>
@Input() context!: string
@Input() defaultBack!: string
@Input() title!: string
@Input() pageTitle!: string

loading = true
needInfinite = false
infiniteStatus: 0 | 1 | 2 = 0
startCursor?: string
isOnBottom = true
autoScroll = true
websocketFail = false
websocketStatus:
| 'connecting'
| 'connected'
| 'reconnecting'
| 'disconnected' = 'connecting'
limit = 400
count = 0

constructor(
private readonly errToast: ErrorToastService,
private readonly destroy$: DestroyService,
private readonly api: ApiService,
private readonly loadingCtrl: LoadingController,
private readonly downloadHtml: DownloadHTMLService,
private readonly connectionService: ConnectionService,
) {}

async ngOnInit() {
try {
const { 'start-cursor': startCursor, guid } = await this.followLogs({
limit: this.limit,
})

this.startCursor = startCursor

const config: WebSocketSubjectConfig<Log> = {
url: `/rpc/${guid}`,
openObserver: {
next: () => {
this.websocketFail = false
},
},
}

let totalLogs = 0

this.api
.openLogsWebsocket$(config)
.pipe(
tap(_ => {
totalLogs++
if (totalLogs === this.limit) this.needInfinite = true
}),
bufferTime(500),
tap(msgs => {
this.loading = false
this.processRes({ entries: msgs })
}),
takeUntil(this.destroy$),
)
.subscribe({
error: () => {
this.websocketFail = true
if (this.isOnBottom) this.scrollToBottom()
},
})
} catch (e: any) {
this.errToast.present(e)
}
from(this.followLogs({ limit: this.limit }))
.pipe(
switchMap(({ 'start-cursor': startCursor, guid }) => {
this.startCursor = startCursor
return this.connect$(guid)
}),
takeUntil(this.destroy$),
finalize(() => console.log('CLOSING')),
)
.subscribe()
}

async doInfinite(e: any): Promise<void> {
Expand All @@ -119,7 +105,7 @@ export class LogsComponent {
}

handleScroll(e: any) {
if (e.detail.deltaY < 0) this.autoScroll = false
if (e.detail.deltaY < -50) this.autoScroll = false
}

handleScrollEnd() {
Expand All @@ -130,7 +116,7 @@ export class LogsComponent {
}

scrollToBottom() {
this.content?.scrollToBottom(250)
this.content?.scrollToBottom(200)
}

async download() {
Expand Down Expand Up @@ -160,6 +146,65 @@ export class LogsComponent {
}
}

private reconnect$(): Observable<Log[]> {
return from(this.followLogs({})).pipe(
tap(_ => this.recordConnectionChange()),
switchMap(({ guid }) => this.connect$(guid, true)),
)
}

private connect$(guid: string, reconnect = false) {
const config: WebSocketSubjectConfig<Log> = {
url: `/rpc/${guid}`,
openObserver: {
next: () => {
this.websocketStatus = 'connected'
},
},
}

return this.api.openLogsWebsocket$(config).pipe(
tap(_ => this.count++),
bufferTime(1000),
tap(msgs => {
this.loading = false
this.processRes({ entries: msgs })
if (this.infiniteStatus === 0 && this.count >= this.limit)
this.infiniteStatus = 1
}),
catchError(() => {
this.recordConnectionChange(false)
return this.connectionService.connected$.pipe(
tap(
connected =>
(this.websocketStatus = connected
? 'reconnecting'
: 'disconnected'),
),
filter(Boolean),
switchMap(() => this.reconnect$()),
)
}),
)
}

private recordConnectionChange(success = true) {
const container = document.getElementById('container')
const elem = document.getElementById('template')?.cloneNode()
if (!(elem instanceof HTMLElement)) return
elem.innerHTML = `<div style="padding: ${
success ? '36px 0' : '36px 0 0 0'
}; color: ${success ? '#2fdf75' : '#ff4961'}; text-align: center;">${
success ? 'Reconnected' : 'Disconnected'
} at ${toLocalIsoString(new Date())}</div>`
container?.append(elem)
if (this.isOnBottom) {
setTimeout(() => {
this.scrollToBottom()
}, 25)
}
}

private processRes(res: LogsRes) {
const { entries, 'start-cursor': startCursor } = res

Expand All @@ -180,7 +225,7 @@ export class LogsComponent {
container?.prepend(newLogs)
const afterContainerHeight = container?.scrollHeight || 0

// scroll down
// maintain scroll height
setTimeout(() => {
this.content?.scrollToPoint(
0,
Expand All @@ -189,12 +234,11 @@ export class LogsComponent {
}, 25)

if (entries.length < this.limit) {
this.needInfinite = false
this.infiniteStatus = 2
}
} else {
container?.append(newLogs)
if (this.autoScroll) {
// scroll to bottom
setTimeout(() => {
this.scrollToBottom()
}, 25)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
[followLogs]="followLogs()"
[defaultBack]="'/services/' + pkgId"
[context]="pkgId"
title="Service Logs"
pageTitle="Service Logs"
class="ion-page"
></logs>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[fetchLogs]="fetchLogs()"
[followLogs]="followLogs()"
context="kernel"
defaultBack="embassy"
title="Kernel Logs"
defaultBack="system"
pageTitle="Kernel Logs"
class="ion-page"
></logs>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[fetchLogs]="fetchLogs()"
[followLogs]="followLogs()"
context="eos"
defaultBack="embassy"
title="OS Logs"
defaultBack="system"
pageTitle="OS Logs"
class="ion-page"
></logs>
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export class MockApiService extends ApiService {
map((_, index) => {
// mock fire open observer
if (index === 0) config.openObserver?.next(new Event(''))
if (index === 100) throw new Error('HAAHHA')
return Mock.ServerLogs[0]
}),
)
Expand Down
18 changes: 0 additions & 18 deletions frontend/projects/ui/src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -295,24 +295,6 @@ h2 {
line-height: unset;
}

@keyframes ellipsis-dot {
25% {
content: '';
}

50% {
content: '.';
}

75% {
content: '..';
}

100% {
content: '...';
}
}

@keyframes flickerAnimation {
0% {
opacity: 1;
Expand Down