Compare commits

...
6 changed files with 447 additions and 458 deletions
+4 -5
View File
@@ -1,13 +1,11 @@
<div [attr.class]="layout | async"> <div [attr.class]="layout | async">
<div class="tfw-grid-main-components"> <div class="tfw-grid-main-components">
<div class="tfw-header"><app-header></app-header></div> <div class="tfw-header"><app-header></app-header></div>
<div [ngClass]="{'hide-attribute': hideMessages | async}" <div [ngClass]="{'hide-attribute': hideMessages | async}" class="tfw-messages">
class="tfw-messages" <app-messages></app-messages>
#tfwmessages>
<app-messages (newMessageEvent)="scrollMessagesToBottom()"></app-messages>
</div> </div>
<div class="tfw-web tao-grid-top-left" <div class="tfw-web tao-grid-top-left"
[ngClass]="{'deploy-blur': deploying || (polling | async)}"> [ngClass]="{'deploy-blur': deploying || (iframeReloadPoller.isPolling | async)}">
<div *ngIf="iframeUrl | async" class="iframe-container"> <div *ngIf="iframeUrl | async" class="iframe-container">
<div *ngIf="showUrlBar | async" class="urlbar-container"> <div *ngIf="showUrlBar | async" class="urlbar-container">
<button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">&#8635;</button> <button class="refresh btn btn-sm rounded-circle" (click)="reloadIframe()">&#8635;</button>
@@ -20,6 +18,7 @@
</div> </div>
<iframe class="iframe" <iframe class="iframe"
#webiframe #webiframe
name="webiframe"
scrolling="yes" scrolling="yes"
frameborder="0" frameborder="0"
(load)="iframeLoad()" (load)="iframeLoad()"
+18 -51
View File
@@ -1,12 +1,13 @@
import { Component, OnDestroy, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core'; import { Component, OnDestroy, OnInit, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { DeploymentNotificationService } from '../services/deployment-notification.service'; import { DeploymentNotificationService } from '../services/deployment-notification.service';
import { Subscription, BehaviorSubject } from 'rxjs'; import { Subscription } from 'rxjs';
import { WebSocketService } from '../services/websocket.service'; import { WebSocketService } from '../services/websocket.service';
import { WebSocketMessage } from '../message-types/websocket-message'; import { WebSocketMessage } from '../message-types/websocket-message';
import { DashboardConfigService } from '../services/config.service'; import { DashboardConfigService, ConfigReadyService } from '../services/config.service';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { delay, retryWhen, tap } from 'rxjs/operators';
import { FSMUpdateService } from '../services/fsmupdate.service'; import { FSMUpdateService } from '../services/fsmupdate.service';
import { MessagesComponent } from '../messages/messages.component';
import { StatusCodePoller } from './statuscodepoller';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@@ -15,10 +16,9 @@ import { FSMUpdateService } from '../services/fsmupdate.service';
}) })
export class DashboardComponent implements OnInit, OnDestroy { export class DashboardComponent implements OnInit, OnDestroy {
deploying = false; deploying = false;
polling = new BehaviorSubject<boolean>(false);
deploymentNotificationSubscription: Subscription; deploymentNotificationSubscription: Subscription;
@ViewChild('webiframe', {static: false}) webiframe: ElementRef; @ViewChild('webiframe', {static: false}) webiframe: ElementRef;
@ViewChild('tfwmessages', {static: false}) messages: ElementRef; @ViewChild(MessagesComponent, {static: false}) messages: MessagesComponent;
@ViewChild('urlbar', {static: false}) urlbar: ElementRef; @ViewChild('urlbar', {static: false}) urlbar: ElementRef;
layout = this.configService.layout; layout = this.configService.layout;
@@ -27,7 +27,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
iframeUrl = this.configService.iframeUrl; iframeUrl = this.configService.iframeUrl;
actualIframeUrl = this.iframeUrl.value; actualIframeUrl = this.iframeUrl.value;
terminalMenuItem = this.configService.terminalMenuItem; terminalMenuItem = this.configService.terminalMenuItem;
iframeReloadSubscription: Subscription; iframeReloadPoller: StatusCodePoller;
command_handlers = { command_handlers = {
'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this), 'dashboard.reloadFrontend': this.reloadFrontendHandlder.bind(this),
@@ -38,6 +38,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
private webSocketService: WebSocketService, private webSocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private http: HttpClient, private http: HttpClient,
private configReadyService: ConfigReadyService,
private configService: DashboardConfigService, private configService: DashboardConfigService,
private fsmUpdateService: FSMUpdateService) {} private fsmUpdateService: FSMUpdateService) {}
@@ -45,6 +46,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.webSocketService.connect(); this.webSocketService.connect();
this.configService.init(); this.configService.init();
this.subscribeCheckSolution(); this.subscribeCheckSolution();
this.iframeReloadPoller = new StatusCodePoller(this.iframeUrl, this.http);
this.hideIframeUntilResponseOk(); this.hideIframeUntilResponseOk();
this.subscribeResizeOnLayoutChange(); this.subscribeResizeOnLayoutChange();
this.initCommandHandling(); this.initCommandHandling();
@@ -62,14 +64,16 @@ export class DashboardComponent implements OnInit, OnDestroy {
} }
hideIframeUntilResponseOk() { hideIframeUntilResponseOk() {
// TODO: hide iframe and show it after this whole deal... this.iframeReloadPoller.ok.subscribe(() => this.reloadIframe());
this.reloadIframeWhenResponseOk(); this.configReadyService.configDone.subscribe(() =>
this.iframeReloadPoller.start()
);
} }
subscribeResizeOnLayoutChange() { subscribeResizeOnLayoutChange() {
this.configService.layout.subscribe(() => { this.configService.layout.subscribe(() => {
this.emitResizeEvent(); this.emitResizeEvent();
setTimeout(() => this.scrollMessagesToBottom(), 0); setTimeout(() => this.messages.scrollToBottom(), 0);
}); });
} }
@@ -85,7 +89,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
(deploying) => { (deploying) => {
this.deploying = deploying; this.deploying = deploying;
if (!deploying && this.configService.reloadIframeOnDeploy.value) { if (!deploying && this.configService.reloadIframeOnDeploy.value) {
this.reloadIframeWhenResponseOk(); this.iframeReloadPoller.start();
} }
}); });
} }
@@ -99,7 +103,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
} }
reloadIframeHandler(message: WebSocketMessage) { reloadIframeHandler(message: WebSocketMessage) {
setTimeout(() => this.reloadIframeNoSubmit(), 200); setTimeout(() => this.reloadIframe(), 200);
} }
setLayout(layout: string) { setLayout(layout: string) {
@@ -114,14 +118,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
reloadIframe() { reloadIframe() {
setTimeout(() => { setTimeout(() => {
this.webiframe.nativeElement.contentWindow.location.reload(true); this.webiframe.nativeElement.contentWindow.frames.location.href = this.iframeUrl.value;
});
}
reloadIframeNoSubmit() {
// Sometimes it is needed to reload the iframe without resending the previous form data
setTimeout(() => {
this.webiframe.nativeElement.contentWindow.location = this.webiframe.nativeElement.contentWindow.location.href;
}); });
} }
@@ -132,17 +129,9 @@ export class DashboardComponent implements OnInit, OnDestroy {
this.terminalMenuItem.next(item); this.terminalMenuItem.next(item);
} }
scrollMessagesToBottom() {
const element = this.messages.nativeElement;
// This must be done in the Angular event loop to avoid messing up
// change detection (not in the template like ConsoleComponent does)
element.scrollTop = element.scrollHeight;
}
iframeLoad() { iframeLoad() {
if (this.webiframe && this.iframeUrl.value) { if (this.webiframe && this.iframeUrl.value) {
const href = this.webiframe.nativeElement.contentWindow.frames.location.href; const href = this.webiframe.nativeElement.contentWindow.frames.location.href;
console.log(href, this.webiframe.nativeElement.contentWindow);
const match = href.match(/.*?\/\/.*?(\/.*)/); const match = href.match(/.*?\/\/.*?(\/.*)/);
if (match !== null) { if (match !== null) {
// iframes on Firefox can have an about:blank // iframes on Firefox can have an about:blank
@@ -162,35 +151,13 @@ export class DashboardComponent implements OnInit, OnDestroy {
) { ) {
return; return;
} }
this.webiframe.nativeElement.contentWindow.frames.location.href = this.urlbar.nativeElement.value; this.iframeUrl.next(this.urlbar.nativeElement.value);
}
reloadIframeWhenResponseOk() {
if (this.polling.value) {
this.iframeReloadSubscription.unsubscribe();
}
this.polling.next(true);
this.iframeReloadSubscription = this.http.get(this.actualIframeUrl, {observe: 'response'}).pipe(
retryWhen(errors =>
errors.pipe(
tap(
response => {
if (response.status === 200) {
this.iframeReloadSubscription.unsubscribe();
this.polling.next(false);
this.reloadIframe();
}}),
delay(1000)
))).subscribe();
} }
ngOnDestroy() { ngOnDestroy() {
if (this.deploymentNotificationSubscription) { if (this.deploymentNotificationSubscription) {
this.deploymentNotificationSubscription.unsubscribe(); this.deploymentNotificationSubscription.unsubscribe();
} }
this.iframeReloadPoller.stop();
if (this.iframeReloadSubscription) {
this.iframeReloadSubscription.unsubscribe();
}
} }
} }
+63
View File
@@ -0,0 +1,63 @@
import { HttpClient } from '@angular/common/http';
import { Subject, Subscription, BehaviorSubject } from 'rxjs';
import { retryWhen, delay, tap, skip } from 'rxjs/operators';
export class StatusCodePoller {
http: HttpClient;
url: BehaviorSubject<string>;
pollFreq: number;
okCode: number;
stopOnOk: boolean;
pollSubscription: Subscription;
urlSubscription: Subscription;
poll = new Subject<any>();
ok = new Subject<any>();
isPolling = new BehaviorSubject<boolean>(false);
constructor(
url: BehaviorSubject<string>, http: HttpClient,
pollFreq = 1000, okCode = 200,
stopOnOk = true
) {
this.url = url;
this.http = http;
this.pollFreq = pollFreq;
this.okCode = okCode;
this.stopOnOk = stopOnOk;
}
start() {
this.stop();
if (this.url.value === '') {
return;
}
this.urlSubscription = this.url.pipe(skip(1)).subscribe(() => this.stop());
this.isPolling.next(true);
this.pollSubscription = this.http.get(this.url.value, {observe: 'response'}).pipe(
retryWhen(errors =>
errors.pipe(
tap(
response => {
this.poll.next(response);
if (response.status === this.okCode) {
if (this.stopOnOk) {
this.stop();
}
this.ok.next(response);
}
}),
delay(this.pollFreq)
))).subscribe();
}
stop() {
this.isPolling.next(false);
if (this.pollSubscription) {
this.pollSubscription.unsubscribe();
}
if (this.urlSubscription) {
this.urlSubscription.unsubscribe();
}
}
}
-4
View File
@@ -1,9 +1,5 @@
import { WebSocketMessage } from './websocket-message'; import { WebSocketMessage } from './websocket-message';
export interface MessageConfig extends WebSocketMessage {
originator?: string;
}
export interface MessageData { export interface MessageData {
originator?: string; originator?: string;
timestamp?: Date; timestamp?: Date;
+9 -2
View File
@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit, EventEmitter, Output } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit, EventEmitter, Output, ElementRef } from '@angular/core';
import { MessageData, Message } from '../message-types/bot-messages'; import { MessageData, Message } from '../message-types/bot-messages';
import { MarkdownService } from '../services/markdown.service'; import { MarkdownService } from '../services/markdown.service';
import { WebSocketService } from '../services/websocket.service'; import { WebSocketService } from '../services/websocket.service';
@@ -19,7 +19,8 @@ export class MessagesComponent implements OnInit {
constructor( constructor(
private markdownService: MarkdownService, private markdownService: MarkdownService,
private websocketService: WebSocketService, private websocketService: WebSocketService,
private changeDetectorRef: ChangeDetectorRef private changeDetectorRef: ChangeDetectorRef,
private ref: ElementRef
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -27,6 +28,7 @@ export class MessagesComponent implements OnInit {
message => { message => {
this.writeMessage(message); this.writeMessage(message);
this.newMessageEvent.emit(); this.newMessageEvent.emit();
this.scrollToBottom();
}); });
this.websocketService.connect(); this.websocketService.connect();
@@ -41,6 +43,11 @@ export class MessagesComponent implements OnInit {
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
} }
scrollToBottom() {
const element = this.ref.nativeElement.parentElement;
element.scrollTop = element.scrollHeight;
}
transformMessage(message: MessageData) { transformMessage(message: MessageData) {
message.message = this.convertMarkdownToHTML(message.message); message.message = this.convertMarkdownToHTML(message.message);
if (!message.timestamp) { if (!message.timestamp) {
+353 -396
View File
File diff suppressed because it is too large Load Diff