From 3663d56917885bc2e2dacff6609fcbf451297a5f Mon Sep 17 00:00:00 2001 From: zadam Date: Wed, 14 Sep 2022 16:50:52 +0200 Subject: [PATCH 1/2] api log capture WIP --- src/public/app/widgets/script_log.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/public/app/widgets/script_log.js diff --git a/src/public/app/widgets/script_log.js b/src/public/app/widgets/script_log.js new file mode 100644 index 000000000..681a14d48 --- /dev/null +++ b/src/public/app/widgets/script_log.js @@ -0,0 +1,26 @@ +import NoteContextAwareWidget from "./note_context_aware_widget.js"; + +const TPL = ` +
+ + +
+
`; + +export default class ScriptLogWidget extends NoteContextAwareWidget { + isEnabled() { + return this.note + && this.note.mime.startsWith('application/javascript;env=') + && super.isEnabled(); + } + + doRender() { + this.$widget = $(TPL); + + this.$logContainer = this.$widget.find('.script-log-container'); + } +} From 1a3008742602de2d8a37c8d9cc208f66d5d8bd78 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 17 Sep 2022 23:06:17 +0200 Subject: [PATCH 2/2] api log implementation --- src/public/app/layouts/desktop_layout.js | 2 + .../app/services/frontend_script_api.js | 28 ++++++++ src/public/app/services/ws.js | 4 ++ src/public/app/widgets/api_log.js | 54 +++++++++++++++ src/public/app/widgets/script_log.js | 26 ------- .../type_widgets/editable_code_buttons.js | 6 ++ src/services/backend_script_api.js | 28 +++++++- src/services/spaced_update.js | 67 +++++++++++++++++++ src/services/ws.js | 2 +- 9 files changed, 188 insertions(+), 29 deletions(-) create mode 100644 src/public/app/widgets/api_log.js delete mode 100644 src/public/app/widgets/script_log.js create mode 100644 src/services/spaced_update.js diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 0e375a209..64302d2fb 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -79,6 +79,7 @@ import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js"; import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js"; import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js"; import EditableCodeButtonsWidget from "../widgets/type_widgets/editable_code_buttons.js"; +import ApiLogWidget from "../widgets/api_log.js"; export default class DesktopLayout { constructor(customWidgets) { @@ -197,6 +198,7 @@ export default class DesktopLayout { .child(new SqlResultWidget()) ) .child(new EditableCodeButtonsWidget()) + .child(new ApiLogWidget()) .child(new FindWidget()) .child( ...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js index 5ae0eaa05..6036c9c5b 100644 --- a/src/public/app/services/frontend_script_api.js +++ b/src/public/app/services/frontend_script_api.js @@ -13,6 +13,7 @@ import appContext from "./app_context.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js"; import BasicWidget from "../widgets/basic_widget.js"; +import SpacedUpdate from "./spaced_update.js"; /** * This is the main frontend API interface for scripts. It's published in the local "api" object. @@ -594,6 +595,33 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain * @returns {string} random string */ this.randomString = utils.randomString; + + this.logMessages = {}; + this.logSpacedUpdates = {}; + + /** + * Log given message to the log pane in UI + * + * @param message + */ + this.log = message => { + const {noteId} = this.startNote; + + message = utils.now() + ": " + message; + + console.log(`Script ${noteId}: ${message}`); + + this.logMessages[noteId] = this.logMessages[noteId] || []; + this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => { + const messages = this.logMessages[noteId]; + this.logMessages[noteId] = []; + + appContext.triggerEvent("apiLogMessages", {noteId, messages}); + }, 100); + + this.logMessages[noteId].push(message); + this.logSpacedUpdates[noteId].scheduleUpdate(); + }; } export default FrontendScriptApi; diff --git a/src/public/app/services/ws.js b/src/public/app/services/ws.js index 113d1f0ad..6dd34c1c6 100644 --- a/src/public/app/services/ws.js +++ b/src/public/app/services/ws.js @@ -3,6 +3,7 @@ import toastService from "./toast.js"; import server from "./server.js"; import options from "./options.js"; import frocaUpdater from "./froca_updater.js"; +import appContext from "./app_context.js"; const messageHandlers = []; @@ -118,6 +119,9 @@ async function handleMessage(event) { else if (message.type === 'consistency-checks-failed') { toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000); } + else if (message.type === 'api-log-messages') { + appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages}); + } } let entityChangeIdReachedListeners = []; diff --git a/src/public/app/widgets/api_log.js b/src/public/app/widgets/api_log.js new file mode 100644 index 000000000..72d12691a --- /dev/null +++ b/src/public/app/widgets/api_log.js @@ -0,0 +1,54 @@ +import NoteContextAwareWidget from "./note_context_aware_widget.js"; + +const TPL = ` +
+ + +
+
`; + +export default class ApiLogWidget extends NoteContextAwareWidget { + isEnabled() { + return this.note + && this.note.mime.startsWith('application/javascript;env=') + && super.isEnabled(); + } + + doRender() { + this.$widget = $(TPL); + this.$widget.addClass("hidden-api-log"); + + this.$logContainer = this.$widget.find('.api-log-container'); + } + + async refreshWithNote(note) { + this.$logContainer.empty(); + } + + apiLogMessagesEvent({messages, noteId}) { + if (!this.isNote(noteId)) { + return; + } + + this.$widget.removeClass("hidden-api-log"); + + for (const message of messages) { + this.$logContainer.append(message).append($("
")); + } + } +} diff --git a/src/public/app/widgets/script_log.js b/src/public/app/widgets/script_log.js deleted file mode 100644 index 681a14d48..000000000 --- a/src/public/app/widgets/script_log.js +++ /dev/null @@ -1,26 +0,0 @@ -import NoteContextAwareWidget from "./note_context_aware_widget.js"; - -const TPL = ` -
- - -
-
`; - -export default class ScriptLogWidget extends NoteContextAwareWidget { - isEnabled() { - return this.note - && this.note.mime.startsWith('application/javascript;env=') - && super.isEnabled(); - } - - doRender() { - this.$widget = $(TPL); - - this.$logContainer = this.$widget.find('.script-log-container'); - } -} diff --git a/src/public/app/widgets/type_widgets/editable_code_buttons.js b/src/public/app/widgets/type_widgets/editable_code_buttons.js index 6386fde43..006ace51f 100644 --- a/src/public/app/widgets/type_widgets/editable_code_buttons.js +++ b/src/public/app/widgets/type_widgets/editable_code_buttons.js @@ -87,4 +87,10 @@ export default class EditableCodeButtonsWidget extends NoteContextAwareWidget { this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env=')); } + + async noteTypeMimeChangedEvent({noteId}) { + if (this.isNote(noteId)) { + await this.refresh(); + } + } } diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index c457bbaf6..0f4f378d8 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -14,6 +14,8 @@ const appInfo = require('./app_info'); const searchService = require('./search/services/search'); const SearchContext = require("./search/search_context"); const becca = require("../becca/becca"); +const ws = require("./ws"); +const SpacedUpdate = require("./spaced_update"); /** * This is the main backend API interface for scripts. It's published in the local "api" object. @@ -288,12 +290,34 @@ function BackendScriptApi(currentNote, apiParams) { }); }; + this.logMessages = {}; + this.logSpacedUpdates = {}; + /** - * Log given message to trilium logs. + * Log given message to trilium logs and log pane in UI * * @param message */ - this.log = message => log.info(message); + this.log = message => { + log.info(message); + + const {noteId} = this.startNote; + + this.logMessages[noteId] = this.logMessages[noteId] || []; + this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => { + const messages = this.logMessages[noteId]; + this.logMessages[noteId] = []; + + ws.sendMessageToAllClients({ + type: 'api-log-messages', + noteId, + messages + }); + }, 100); + + this.logMessages[noteId].push(message); + this.logSpacedUpdates[noteId].scheduleUpdate(); + }; /** * Returns root note of the calendar. diff --git a/src/services/spaced_update.js b/src/services/spaced_update.js new file mode 100644 index 000000000..d038a09db --- /dev/null +++ b/src/services/spaced_update.js @@ -0,0 +1,67 @@ +class SpacedUpdate { + constructor(updater, updateInterval = 1000) { + this.updater = updater; + this.lastUpdated = Date.now(); + this.changed = false; + this.updateInterval = updateInterval; + } + + scheduleUpdate() { + if (!this.changeForbidden) { + this.changed = true; + setTimeout(() => this.triggerUpdate()); + } + } + + async updateNowIfNecessary() { + if (this.changed) { + this.changed = false; // optimistic... + + try { + await this.updater(); + } + catch (e) { + this.changed = true; + + throw e; + } + } + } + + isAllSavedAndTriggerUpdate() { + const allSaved = !this.changed; + + this.updateNowIfNecessary(); + + return allSaved; + } + + triggerUpdate() { + if (!this.changed) { + return; + } + + if (Date.now() - this.lastUpdated > this.updateInterval) { + this.updater(); + this.lastUpdated = Date.now(); + this.changed = false; + } + else { + // update not triggered but changes are still pending so we need to schedule another check + this.scheduleUpdate(); + } + } + + async allowUpdateWithoutChange(callback) { + this.changeForbidden = true; + + try { + await callback(); + } + finally { + this.changeForbidden = false; + } + } +} + +module.exports = SpacedUpdate; diff --git a/src/services/ws.js b/src/services/ws.js index 0231b200d..4581e8942 100644 --- a/src/services/ws.js +++ b/src/services/ws.js @@ -67,7 +67,7 @@ function sendMessageToAllClients(message) { const jsonStr = JSON.stringify(message); if (webSocketServer) { - if (message.type !== 'sync-failed') { + if (message.type !== 'sync-failed' && message.type !== 'api-log-messages') { log.info("Sending message to all clients: " + jsonStr); }