Skip to content
Draft
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
9 changes: 2 additions & 7 deletions InfoLogger/public/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
Observable, WebSocketClient, QueryRouter,
Loader, RemoteData, sessionService, Notification,
} from '/js/src/index.js';
import { callRateLimiter, setBrowserTabTitle } from './common/utils.js';
import { callRateLimiter } from './common/utils.js';
import { ConfigurationService } from './services/ConfigurationService.js';
import { MODE } from './constants/mode.const.js';
import Log from './log/Log.js';
Expand Down Expand Up @@ -94,6 +94,7 @@ export default class Model extends Observable {
handleWSAuthed() {
// Tell server not to stream by default
this.ws.setFilter(() => false);
this.notify();
}

/**
Expand All @@ -115,12 +116,6 @@ export default class Model extends Observable {
this.frameworkInfo = RemoteData.failure(result.message);
} else {
this.frameworkInfo = RemoteData.success(result);
if (result['infoLogger-gui'].name && result['infoLogger-gui'].name.trim()) {
window.ILG = {
name: `ILG - ${result['infoLogger-gui'].name}`,
};
setBrowserTabTitle(window.ILG.name);
}
}
this.notify();
return;
Expand Down
2 changes: 1 addition & 1 deletion InfoLogger/public/common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function callRateLimiter(fn, time) {
* @returns {void}
*/
export function setBrowserTabTitle(title = undefined) {
if (document && title) {
if (document && title && document.title !== title) {
document.title = title;
}
}
1 change: 0 additions & 1 deletion InfoLogger/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,3 @@ mount(document.body, view, model, debug);

// Expose model to interact with it the browser's console
window.model = model;
window.ILG = { name: 'ILG - ' };
45 changes: 32 additions & 13 deletions InfoLogger/public/log/Log.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ export default class Log extends Observable {
return this.activeMode === MODE.QUERY;
}

/**
* Toggles the live mode between running and paused
*/
toggleLiveMode() {
this.download.isVisible = false;
if (this.isLiveModeRunning()) {
this.liveStop(MODE.LIVE.PAUSED);
} else {
try {
this.liveStart();
} catch (error) {
this.model.notification.show(error.toString(), 'danger', 3000);
}
}
}

/**
* Toggle a dropdown with the full SQL query
*/
Expand Down Expand Up @@ -338,7 +354,7 @@ export default class Log extends Observable {
* @returns {Promise<null|object>} null if query is aborted, result of the query otherwise
*/
async query() {
if (!this.model.frameworkInfo.isSuccess() || !this.model.frameworkInfo.payload.mysql.status.ok) {
if (!this.isQueryModeAvailable()) {
throw new Error('Query service is not available');
}

Expand All @@ -354,6 +370,7 @@ export default class Log extends Observable {
} else {
this.activeMode = MODE.QUERY;
}
this.download.isVisible = false;

const previousQueryResult = this.queryResult;
this.queryResult = RemoteData.loading();
Expand Down Expand Up @@ -463,10 +480,7 @@ export default class Log extends Observable {
if (this.queryResult.isLoading()) {
throw new Error('Query is loading, wait before starting live');
}
if (!this.model.ws.authed) {
throw new Error('WS is not yet ready');
}
if (!this.model.frameworkInfo.isSuccess() || !this.model.frameworkInfo.payload.infoLoggerServer.status.ok) {
if (!this.isLiveModeAvailable()) {
throw new Error('Live service is not available');
}
if (this.isLiveModeRunning()) {
Expand All @@ -486,6 +500,7 @@ export default class Log extends Observable {

this.model.ws.setFilter(this.model.log.filter.toStringifyFunction());

this.autoScrollLive = true;
this.notify();
}

Expand All @@ -500,6 +515,7 @@ export default class Log extends Observable {
this.activeMode = mode;
clearInterval(this.liveInterval);
this.model.ws.setFilter(() => false);
this.autoScrollLive = false;
this.notify();
}

Expand Down Expand Up @@ -568,19 +584,22 @@ export default class Log extends Observable {
}

/**
* Enables auto-scroll, this is used when entering Live mode
* Returns whether the live mode service is available
* @returns {boolean} true if the live mode service is available, false otherwise
*/
enableAutoScroll() {
this.autoScrollLive = true;
this.notify();
isLiveModeAvailable() {
return Boolean(this.model.ws?.authed
&& this.model.frameworkInfo.isSuccess()
&& this.model.frameworkInfo.payload.infoLoggerServer?.status?.ok);
}

/**
* Disable auto-scroll, this is used when leaving Live mode
* Returns whether the query mode service is available
* @returns {boolean} true if the query mode service is available, false otherwise
*/
disableAutoScroll() {
this.autoScrollLive = false;
this.notify();
isQueryModeAvailable() {
return Boolean(this.model.frameworkInfo.isSuccess()
&& this.model.frameworkInfo.payload.mysql?.status?.ok);
}

/**
Expand Down
105 changes: 31 additions & 74 deletions InfoLogger/public/log/commandLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ import { h,
} from '/js/src/index.js';
import { BUTTON } from '../constants/button-states.const.js';
import { MODE } from '../constants/mode.const.js';
import { setBrowserTabTitle } from '../common/utils.js';

let queryButtonType = BUTTON.PRIMARY;
let liveButtonType = BUTTON.DEFAULT;
let liveButtonIcon = iconMediaPlay();
/**
* Maps active modes to a live button style
* @type {Record<string, {className: string, icon: () => vnode}>}
*/
const LIVE_BUTTON_TYPES_BY_MODE = {
[MODE.QUERY]: { className: BUTTON.DEFAULT, icon: iconMediaPlay },
[MODE.LIVE.RUNNING]: { className: BUTTON.SUCCESS_ACTIVE, icon: iconMediaStop },
[MODE.LIVE.PAUSED]: { className: BUTTON.PRIMARY, icon: iconMediaPlay },
};

/**
* Component for the command buttons (Query, Live, Clear, navigation between errors and download)
Expand Down Expand Up @@ -81,10 +86,10 @@ const interactionModesGroupButton = (model) => {
NotAsked: () => h('button.btn', { disabled: true }, ''),
Loading: () => h('button.btn', { disabled: true, className: 'loading' }, 'Loading'),
Failure: () => null,
Success: (frameworkInfo) =>
Success: () =>
h('.btn-group', [
queryButton(model, frameworkInfo),
liveButton(model, frameworkInfo),
queryButton(model.log),
liveButton(model.log),
]),
});
};
Expand All @@ -94,14 +99,11 @@ const interactionModesGroupButton = (model) => {
* - services lookup
* - services result
* - query lookup
* @param {Model} model - root model of the application
* @param {RemoteData.payload} frameworkInfo - the payload containing framework information
* @param {Log} logModel - log model of the application
* @returns {vnode} - the view of the query button
*/
const queryButton = (model, frameworkInfo) => {
const { log: logModel } = model;
const queryButton = (logModel) => {
const { queryResult } = logModel;
const { mysql: { status: { ok: isDbReady = false } = {} } = {} } = frameworkInfo;

if (queryResult.isLoading()) {
return h('button.btn.bold', {
Expand All @@ -112,12 +114,14 @@ const queryButton = (model, frameworkInfo) => {
}, 'Cancel');
}

const isQueryModeAvailable = logModel.isQueryModeAvailable();

return h('button.btn.bold', {
id: 'query-button',
title: isDbReady ? 'Query database with filters (Enter)' : 'Query service not configured',
disabled: !isDbReady || queryResult.isLoading(),
className: queryButtonType,
onclick: () => toggleButtonStates(model, false),
title: isQueryModeAvailable ? 'Query database with filters (Enter)' : 'Query service not configured',
disabled: !isQueryModeAvailable,
className: logModel.isActiveModeQuery() ? BUTTON.PRIMARY : BUTTON.DEFAULT,
onclick: () => logModel.query(),
}, 'Query');
};

Expand All @@ -126,25 +130,23 @@ const queryButton = (model, frameworkInfo) => {
* - services lookup
* - services result
* - websocket status
* @param {Model} model - root model of the application
* @param {RemoteData.payload} frameworkInfo - the payload containing framework information
* @param {Log} logModel - log model of the application
* @returns {vnode} - the view of the live button
*/
const liveButton = (model, frameworkInfo) => {
const { log: logModel, ws } = model;
const { queryResult } = logModel;
const { authed: isWsAuthedAndReady = false } = ws;
const { infoLoggerServer: { status: { ok: isLiveServiceReady = false } = {} } = {} } = frameworkInfo;
const liveButton = (logModel) => {
const { queryResult, activeMode } = logModel;
const { className, icon } = LIVE_BUTTON_TYPES_BY_MODE[activeMode];

const isLiveModeReady = isLiveServiceReady && isWsAuthedAndReady;
const title = isLiveModeReady ? 'Stream logs with filtering' : 'Live service not configured';
const isLiveModeAvailable = logModel.isLiveModeAvailable();
const title = isLiveModeAvailable ? 'Stream logs with filtering' : 'Live service not configured';

return h('button.btn.bold', {
id: 'live-button',
title,
disabled: !isLiveModeReady || queryResult.isLoading(),
className: !isLiveModeReady ? 'loading' : liveButtonType,
onclick: () => toggleButtonStates(model, true),
}, 'Live', ' ', liveButtonIcon);
disabled: !isLiveModeAvailable || queryResult.isLoading(),
className: !isLiveModeAvailable ? 'loading' : className,
onclick: () => logModel.toggleLiveMode(),
}, 'Live', ' ', icon());
};

/**
Expand Down Expand Up @@ -241,48 +243,3 @@ const zoomButtonGroup = (zoom) =>
title: 'Zoom in (Ctrl/Cmd + +)',
}, h('span', { style: 'font-size:0.8em' }, iconPlus())),
]);

/**
* Method to toggle states of the buttons(Query/Live) depending on the mode the tool is running on
* @param {Model} model - root model of the application
* @param {boolean} wasLivePressed - flag to check if the live button was pressed
*/
function toggleButtonStates(model, wasLivePressed) {
model.log.download.isVisible = false; // set visibility of download dropdown to false
if (wasLivePressed) {
switch (model.log.activeMode) {
case MODE.QUERY:
case MODE.LIVE.PAUSED:
try {
model.log.liveStart();
setButtonsType(BUTTON.DEFAULT, BUTTON.SUCCESS_ACTIVE, iconMediaStop());
model.log.enableAutoScroll();
setBrowserTabTitle(`${window.ILG.name} LIVE`);
} catch (error) {
model.notification.show(error.toString(), 'danger', 3000);
}
break;
default: // MODE.LIVE.RUNNING
model.log.liveStop(MODE.LIVE.PAUSED);
setBrowserTabTitle(`${window.ILG.name} LIVE PAUSED`);
setButtonsType(BUTTON.DEFAULT, BUTTON.PRIMARY, iconMediaPlay());
model.log.disableAutoScroll();
}
} else {
model.log.query();
setBrowserTabTitle(`${window.ILG.name} QUERY`);
setButtonsType(BUTTON.PRIMARY, BUTTON.DEFAULT, iconMediaPlay());
}

/**
* Method to change types of the buttons based on the mode being run
* @param {string} queryType Type of the Query Button
* @param {string} liveType Type of the Live Button
* @param {Icon} liveIcon Icon of the Live Button
*/
function setButtonsType(queryType, liveType, liveIcon) {
queryButtonType = queryType;
liveButtonType = liveType;
liveButtonIcon = liveIcon;
}
}
2 changes: 1 addition & 1 deletion InfoLogger/public/log/tableLogsContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const tableContainerHooks = (model) => ({
const { height } = container.getBoundingClientRect();
const scrollTop = Math.max(container.scrollTop, 0); // cancel negative position due to Safari bounce scrolling
if (container.scrollTop < model.log.scrollTop) {
model.log.disableAutoScroll(); // stop auto-scrolling if user scroll sup
model.log.autoScrollLive = false; // stop auto-scrolling if user scrolls up
}
model.log.setScrollTop(scrollTop, height);
};
Expand Down
59 changes: 44 additions & 15 deletions InfoLogger/public/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,59 @@ import tableLogsScrollMap from './log/tableLogsScrollMap.js';
import aboutComponent from './about/about.component.js';
import errorComponent from './common/errorComponent.js';
import { cellContextMenu } from './log/cellContextMenu.js';
import { setBrowserTabTitle } from './common/utils.js';
import { MODE } from './constants/mode.const.js';

const BROWSER_TAB_TITLE_BY_MODE = {
[MODE.QUERY]: 'QUERY',
[MODE.LIVE.RUNNING]: 'LIVE',
[MODE.LIVE.PAUSED]: 'LIVE PAUSED',
};

/**
* Given a frameworkInfo and the active mode, it will update the browser tab title accordingly
* @param {RemoteData} frameworkInfo - the framework information
* @param {string} activeMode - the current mode of the application
* @returns {void}
*/
function updateBrowserTabTitle(frameworkInfo, activeMode) {
const instanceName = frameworkInfo.isSuccess()
? (frameworkInfo.payload['infoLogger-gui'].name || '').trim()
: '';
const prefix = instanceName ? `ILG - ${instanceName}` : 'ILG';
setBrowserTabTitle(`${prefix} ${BROWSER_TAB_TITLE_BY_MODE[activeMode]}`);
}

/**
* Main view of the application
* @param {Model} model - root model of the application
* @returns {vnode} - the view of the application
* @returns {vnode[]} - the view of the application
*/
export default (model) => [
notification(model.notification),
cellContextMenu(model),
h('.flex-column absolute-fill', [
h('.shadow-level2', [
h('header.p1.flex-row.f7.g1.justify-between', [
h('.flex-row.g3', commandLogs(model)),
h('.flex-row.g3', commandFilters(model)),
h(
'.flex-column absolute-fill',
{
oncreate: () => updateBrowserTabTitle(model.frameworkInfo, model.log.activeMode),
onupdate: () => updateBrowserTabTitle(model.frameworkInfo, model.log.activeMode),
},
[
h('.shadow-level2', [
h('header.p1.flex-row.f7.g1.justify-between', [
h('.flex-row.g3', commandLogs(model)),
h('.flex-row.g3', commandFilters(model)),
]),
h('header.f7', tableFilters(model)),
]),
h('.flex-grow.flex-row.shadow-level0.logs-container', [
aboutComponent(model),
logsTable(model),
inspectorSide(model),
]),
h('header.f7', tableFilters(model)),
]),
h('.flex-grow.flex-row.shadow-level0.logs-container', [
aboutComponent(model),
logsTable(model),
inspectorSide(model),
]),
h('footer.f7.ph1', [statusBar(model)]),
]),
h('footer.f7.ph1', [statusBar(model)]),
],
),
];

/**
Expand Down
Loading
Loading