diff --git a/frontend/app/components/Header/HealthStatus/HealthStatus.tsx b/frontend/app/components/Header/HealthStatus/HealthStatus.tsx index 317b36279..283ba6f21 100644 --- a/frontend/app/components/Header/HealthStatus/HealthStatus.tsx +++ b/frontend/app/components/Header/HealthStatus/HealthStatus.tsx @@ -22,6 +22,7 @@ export interface IServiceStats { function HealthStatus() { const healthResponseSaved = localStorage.getItem(healthResponseKey) || '{}'; const [healthResponse, setHealthResponse] = React.useState(JSON.parse(healthResponseSaved)); + const [isError, setIsError] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false); const lastAskedSaved = localStorage.getItem(lastAskedKey); const [lastAsked, setLastAsked] = React.useState(lastAskedSaved); @@ -36,6 +37,7 @@ function HealthStatus() { setLastAsked(asked.toString()); } catch (e) { console.error(e); + setIsError(true); } finally { setIsLoading(false); } @@ -51,7 +53,7 @@ function HealthStatus() { } }, []); - const icon = healthResponse?.overallHealth ? 'pulse' : ('exclamation-circle-fill' as const); + const icon = !isError && healthResponse?.overallHealth ? 'pulse' : ('exclamation-circle-fill' as const); return ( <>
@@ -71,6 +73,7 @@ function HealthStatus() { isLoading={isLoading} lastAsked={lastAsked} setShowModal={setShowModal} + isError={isError} />
{showModal ? ( diff --git a/frontend/app/components/Header/HealthStatus/HealthWidget.tsx b/frontend/app/components/Header/HealthStatus/HealthWidget.tsx index 50b4de76d..6a184f854 100644 --- a/frontend/app/components/Header/HealthStatus/HealthWidget.tsx +++ b/frontend/app/components/Header/HealthStatus/HealthWidget.tsx @@ -10,12 +10,14 @@ function HealthWidget({ isLoading, lastAsked, setShowModal, + isError, }: { healthResponse: { overallHealth: boolean; healthMap: Record }; getHealth: Function; isLoading: boolean; lastAsked: string | null; setShowModal: (visible: boolean) => void; + isError?: boolean; }) { const [lastAskedDiff, setLastAskedDiff] = React.useState(0); const healthOk = healthResponse?.overallHealth; @@ -28,8 +30,8 @@ function HealthWidget({ setLastAskedDiff(diffInMinutes); }, [lastAsked]); - const title = healthOk ? 'All Systems Operational' : 'Service disruption'; - const icon = healthOk ? ('check-circle-fill' as const) : ('exclamation-circle-fill' as const); + const title = !isError && healthOk ? 'All Systems Operational' : 'Service disruption'; + const icon = !isError && healthOk ? ('check-circle-fill' as const) : ('exclamation-circle-fill' as const); const problematicServices = Object.values(healthResponse?.healthMap || {}).filter( (service: Record) => !service.healthOk @@ -65,10 +67,12 @@ function HealthWidget({ + {isError &&
Error getting service health status
} +
- {!healthOk ? ( + {!isError && !healthOk ? ( <>
Observed installation Issue with the following diff --git a/frontend/app/player/common/types.ts b/frontend/app/player/common/types.ts index 7df4f6f6b..308ec0659 100644 --- a/frontend/app/player/common/types.ts +++ b/frontend/app/player/common/types.ts @@ -7,7 +7,7 @@ export interface Indexed { } export interface Moveable { - move(time: number, isJump?: boolean): void + move(time: number): void } export interface Cleanable { diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index 9423b5785..55d38432c 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -1,4 +1,5 @@ import type { Store, Moveable, Interval } from '../common/types'; +import MessageManager from 'App/player/web/MessageManager' const fps = 60 const performance: { now: () => number } = window.performance || { now: Date.now.bind(Date) } @@ -54,18 +55,18 @@ export default class Animator { private animationFrameRequestId: number = 0 - constructor(private store: Store, private mm: Moveable) { + constructor(private store: Store, private mm: MessageManager) { // @ts-ignore window.playerJump = this.jump.bind(this) } - private setTime(time: number, isJump?: boolean) { + private setTime(time: number) { this.store.update({ time, completed: false, }) - this.mm.move(time, isJump) + this.mm.move(time) } private startAnimation() { @@ -183,11 +184,11 @@ export default class Animator { jump = (time: number) => { if (this.store.get().playing) { cancelAnimationFrame(this.animationFrameRequestId) - this.setTime(time, true) + this.setTime(time) this.startAnimation() this.store.update({ livePlay: time === this.store.get().endTime }) } else { - this.setTime(time, true) + this.setTime(time) this.store.update({ livePlay: time === this.store.get().endTime }) } } diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 2772ccbd2..b343eef96 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -289,7 +289,7 @@ export default class MessageManager { this.activityManager = new ActivityManager(this.session.duration.milliseconds); } - move(t: number, isJump?: boolean, index?: number): void { + move(t: number, index?: number): void { const stateToUpdate: Partial = {}; /* == REFACTOR_ME == */ const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); @@ -339,7 +339,7 @@ export default class MessageManager { if (!!lastResize) { this.setSize(lastResize) } - this.pagesManager.moveReady(t, isJump).then(() => { + this.pagesManager.moveReady(t).then(() => { const lastScroll = this.scrollManager.moveGetLast(t, index); if (!!lastScroll && this.screen.window) { diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts index 709692d20..7ed1e3400 100644 --- a/frontend/app/player/web/WebLivePlayer.ts +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -56,7 +56,7 @@ export default class WebLivePlayer extends WebPlayer { const bytes = await requestEFSDom(this.session.sessionId) const fileReader = new MFileReader(bytes, this.session.startedAt) for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { - this.messageManager.distributeMessage(msg, msg._index) + this.messageManager.distributeMessage(msg) } this.wpState.update({ liveTimeTravel: true, diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index a5c2f2c2f..d54781028 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -142,7 +142,7 @@ export default class DOMManager extends ListWalker { private setNodeAttribute(msg: { id: number, name: string, value: string }) { let { name, value } = msg; const vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("SetNodeAttribute: Node not found", msg); return } if (vn.node.tagName === "INPUT" && name === "name") { // Otherwise binds local autocomplete values (maybe should ignore on the tracker level) @@ -169,7 +169,7 @@ export default class DOMManager extends ListWalker { this.removeBodyScroll(msg.id, vn) } - private applyMessage = (msg: Message, isJump?: boolean): Promise | undefined => { + private applyMessage = (msg: Message): Promise | undefined => { let vn: VNode | undefined let doc: Document | null let styleSheet: CSSStyleSheet | PostponedStyleSheet | undefined @@ -230,14 +230,14 @@ export default class DOMManager extends ListWalker { return case MType.RemoveNode: vn = this.vElements.get(msg.id) || this.vTexts.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } - if (!vn.parentNode) { logger.error("Parent node not found", msg); return } + if (!vn) { logger.error("RemoveNode: Node not found", msg); return } + if (!vn.parentNode) { logger.error("RemoveNode: Parent node not found", msg); return } vn.parentNode.removeChild(vn) this.vElements.delete(msg.id) this.vTexts.delete(msg.id) return case MType.SetNodeAttribute: - if (isJump && msg.name === 'href') this.attrsBacktrack.push(msg) + if (msg.name === 'href') this.attrsBacktrack.push(msg) else this.setNodeAttribute(msg) return case MType.StringDict: @@ -247,7 +247,7 @@ export default class DOMManager extends ListWalker { this.stringDict[msg.nameKey] === undefined && logger.error("No dictionary key for msg 'name': ", msg) this.stringDict[msg.valueKey] === undefined && logger.error("No dictionary key for msg 'value': ", msg) if (this.stringDict[msg.nameKey] === undefined || this.stringDict[msg.valueKey] === undefined ) { return } - if (isJump && this.stringDict[msg.nameKey] === 'href') this.attrsBacktrack.push(msg) + if (this.stringDict[msg.nameKey] === 'href') this.attrsBacktrack.push(msg) else { this.setNodeAttribute({ id: msg.id, @@ -258,12 +258,12 @@ export default class DOMManager extends ListWalker { return case MType.RemoveNodeAttribute: vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("RemoveNodeAttribute: Node not found", msg); return } vn.removeAttribute(msg.name) return case MType.SetInputValue: vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("SetInoputValue: Node not found", msg); return } const nodeWithValue = vn.node if (!(nodeWithValue instanceof HTMLInputElement || nodeWithValue instanceof HTMLTextAreaElement @@ -283,13 +283,13 @@ export default class DOMManager extends ListWalker { return case MType.SetInputChecked: vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("SetInputChecked: Node not found", msg); return } (vn.node as HTMLInputElement).checked = msg.checked return case MType.SetNodeData: case MType.SetCssData: // mbtodo: remove css transitions when timeflow is not natural (on jumps) vn = this.vTexts.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("SetCssData: Node not found", msg); return } vn.setData(msg.data) if (vn.node instanceof HTMLStyleElement) { doc = this.screen.document @@ -304,7 +304,7 @@ export default class DOMManager extends ListWalker { // @deprecated since 4.0.2 in favor of adopted_ss_insert/delete_rule + add_owner as being common case for StyleSheets case MType.CssInsertRule: vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("CssInsertRule: Node not found", msg); return } if (!(vn instanceof VStyleElement)) { logger.warn("Non-style node in CSS rules message (or sheet is null)", msg, vn); return @@ -313,7 +313,7 @@ export default class DOMManager extends ListWalker { return case MType.CssDeleteRule: vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("CssDeleteRule: Node not found", msg); return } if (!(vn instanceof VStyleElement)) { logger.warn("Non-style node in CSS rules message (or sheet is null)", msg, vn); return @@ -324,7 +324,7 @@ export default class DOMManager extends ListWalker { case MType.CreateIFrameDocument: vn = this.vElements.get(msg.frameID) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("CreateIFrameDocument: Node not found", msg); return } vn.enforceInsertion() const host = vn.node if (host instanceof HTMLIFrameElement) { @@ -384,7 +384,7 @@ export default class DOMManager extends ListWalker { if (!vn) { // non-constructed case vn = this.vElements.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("AdoptedSsAddOwner: Node not found", msg); return } if (!(vn instanceof VStyleElement)) { logger.error("Non-style owner", msg); return } this.ppStyleSheets.set(msg.sheetID, new PostponedStyleSheet(vn.node)) return @@ -411,13 +411,13 @@ export default class DOMManager extends ListWalker { return } vn = this.vRoots.get(msg.id) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("AdoptedSsRemoveOwner: Node not found", msg); return } //@ts-ignore vn.node.adoptedStyleSheets = [...vn.node.adoptedStyleSheets].filter(s => s !== styleSheet) return case MType.LoadFontFace: vn = this.vRoots.get(msg.parentID) - if (!vn) { logger.error("Node not found", msg); return } + if (!vn) { logger.error("LoadFontFace: Node not found", msg); return } if (vn instanceof VShadowRoot) { logger.error(`Node ${vn} expected to be a Document`, msg); return } let descr: Object try { @@ -460,7 +460,7 @@ export default class DOMManager extends ListWalker { } } - async moveReady(t: number, isJump?: boolean): Promise { + async moveReady(t: number): Promise { // MBTODO (back jump optimisation): // - store intemediate virtual dom state // - cancel previous moveReady tasks (is it possible?) if new timestamp is less @@ -474,16 +474,13 @@ export default class DOMManager extends ListWalker { * */ // http://0.0.0.0:3333/5/session/8452905874437457 // 70 iframe, 8 create element - STYLE tag - await this.moveWait(t, (msg) => { - this.applyMessage(msg, isJump) - }) + await this.moveWait(t, this.applyMessage) + + this.attrsBacktrack.forEach(msg => { + this.applyBacktrack(msg) + }) + this.attrsBacktrack = [] - if (isJump) { - this.attrsBacktrack.forEach(msg => { - this.applyBacktrack(msg) - }) - this.attrsBacktrack = [] - } this.vRoots.forEach(rt => rt.applyChanges()) // MBTODO (optimisation): affected set // Thinkabout (read): css preload // What if we go back before it is ready? We'll have two handlres? diff --git a/frontend/app/player/web/managers/PagesManager.ts b/frontend/app/player/web/managers/PagesManager.ts index b30f40372..dbc64bb72 100644 --- a/frontend/app/player/web/managers/PagesManager.ts +++ b/frontend/app/player/web/managers/PagesManager.ts @@ -33,14 +33,14 @@ export default class PagesManager extends ListWalker { this.forEach(page => page.sort(comparator)) } - moveReady(t: number, isJump?: boolean): Promise { + moveReady(t: number): Promise { const requiredPage = this.moveGetLast(t) if (requiredPage != null) { this.currentPage = requiredPage this.currentPage.reset() // Otherwise it won't apply create_document } if (this.currentPage != null) { - return this.currentPage.moveReady(t, isJump) + return this.currentPage.moveReady(t) } return Promise.resolve() } diff --git a/frontend/app/services/HealthService.ts b/frontend/app/services/HealthService.ts index 019863bb3..7d2b3cc7f 100644 --- a/frontend/app/services/HealthService.ts +++ b/frontend/app/services/HealthService.ts @@ -5,6 +5,5 @@ export default class HealthService extends BaseService { return this.client.get('/health') .then(r => r.json()) .then(j => j.data || {}) - .catch(Promise.reject) } } \ No newline at end of file