From 909f72c7ec9f711cbadf87749d791d34ca316060 Mon Sep 17 00:00:00 2001 From: Muhammad Labeeb <72980976+mlabeeb03@users.noreply.github.com> Date: Tue, 13 May 2025 15:25:25 +0500 Subject: [PATCH] feat: only allow relevant pages to cancel running command (#18) --- ...ammad.labeeb_improve_cancellation_flows.md | 2 + tutordeck/server/app.py | 11 +----- tutordeck/server/static/js/deck.js | 37 +++++++++++++++++++ tutordeck/server/static/js/logs.js | 18 ++++++--- tutordeck/server/static/scss/deck.scss | 26 +++++++++++++ tutordeck/server/templates/advanced.html | 2 + tutordeck/server/templates/index.html | 12 +++--- tutordeck/server/templates/local_launch.html | 1 + tutordeck/server/templates/plugin.html | 2 + 9 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 changelog.d/20250508_153631_muhammad.labeeb_improve_cancellation_flows.md diff --git a/changelog.d/20250508_153631_muhammad.labeeb_improve_cancellation_flows.md b/changelog.d/20250508_153631_muhammad.labeeb_improve_cancellation_flows.md new file mode 100644 index 0000000..ada8794 --- /dev/null +++ b/changelog.d/20250508_153631_muhammad.labeeb_improve_cancellation_flows.md @@ -0,0 +1,2 @@ +- [Feature] Only allow command cancellation from relevant page. (by @mlabeeb03) +- [Feature] Add link to developer panel while command is in progress. (by @mlabeeb03) \ No newline at end of file diff --git a/tutordeck/server/app.py b/tutordeck/server/app.py index aa73200..624df05 100644 --- a/tutordeck/server/app.py +++ b/tutordeck/server/app.py @@ -9,7 +9,6 @@ from markdown import markdown from quart import ( Quart, Response, - abort, g, jsonify, make_response, @@ -126,7 +125,6 @@ async def plugin_installed_list() -> str: @app.get("/plugin/") async def plugin(name: str) -> Response: # TODO check that plugin exists - show_logs = request.args.get("show_logs") seq_command_executed = request.args.get("seq_command_executed") author = next( ( @@ -151,7 +149,6 @@ async def plugin(name: str) -> Response: is_installed=name in g.installed_plugins, author_name=author, plugin_description=description, - show_logs=show_logs, seq_command_executed=seq_command_executed, plugin_config_unique=tutorclient.Client.plugin_config_unique(name), plugin_config_defaults=tutorclient.Client.plugin_config_defaults(name), @@ -214,7 +211,6 @@ async def plugin_install(name: str) -> WerkzeugResponse: url_for( "plugin", name=name, - show_logs=True, ) ) @@ -226,14 +222,13 @@ async def plugin_upgrade(name: str) -> WerkzeugResponse: url_for( "plugin", name=name, - show_logs=True, ) ) @app.post("/plugins/update") async def plugins_update() -> WerkzeugResponse: - tutorclient.CliPool.run_parallel(app, ["plugins", "update"]) + tutorclient.CliPool.run_sequential(["plugins", "update"]) return redirect(url_for("plugin_store")) @@ -286,7 +281,6 @@ async def cli_local_launch() -> str: tutorclient.CliPool.run_parallel(app, ["local", "launch", "--non-interactive"]) return await render_template( "local_launch.html", - show_logs=True, ) @@ -349,7 +343,6 @@ async def cli_stop() -> Response: async def advanced() -> str: return await render_template( "advanced.html", - show_logs=True, ) @@ -366,7 +359,5 @@ async def command() -> WerkzeugResponse: form = await request.form command_string = form.get("command", "") command_args = command_string.split() - if tutorclient.CliPool.is_thread_alive(): - abort(400, description="Command execution already in progress") tutorclient.CliPool.run_parallel(app, command_args) return redirect(url_for("advanced")) diff --git a/tutordeck/server/static/js/deck.js b/tutordeck/server/static/js/deck.js index a8ba56a..2e77cce 100644 --- a/tutordeck/server/static/js/deck.js +++ b/tutordeck/server/static/js/deck.js @@ -113,3 +113,40 @@ function setToastContent(cmd) { toastFooter.style.display = config.showFooter ? "flex" : "none"; } } + +// Each page defines its own relevant commands, we use them to check +// if the currently running commands belong the currently opened page or not +let relevantCommands = []; +let onDeveloperPage = false; +function onRelevantPage(command) { + if (onDeveloperPage) { + // Developer page is relevant to all commands + return true; + } + return relevantCommands.some((prefix) => command.startsWith(prefix)); +} + +function activateInputs() { + document.querySelectorAll("button").forEach((button) => { + button.disabled = false; + }); + document.querySelectorAll("input").forEach((input) => { + input.disabled = false; + }); + document.querySelectorAll(".form-switch").forEach((formSwitch) => { + formSwitch.style.opacity = 1; + }); + document.getElementById("warning-command-running").style.display = "none"; +} +function deactivateInputs() { + document.querySelectorAll("button").forEach((button) => { + button.disabled = true; + }); + document.querySelectorAll("input").forEach((input) => { + input.disabled = true; + }); + document.querySelectorAll(".form-switch").forEach((formSwitch) => { + formSwitch.style.opacity = 0.5; + }); + document.getElementById("warning-command-running").style.display = "flex"; +} diff --git a/tutordeck/server/static/js/logs.js b/tutordeck/server/static/js/logs.js index cdf7c99..7f14255 100644 --- a/tutordeck/server/static/js/logs.js +++ b/tutordeck/server/static/js/logs.js @@ -23,14 +23,21 @@ htmx.on("htmx:sseBeforeMessage", function (evt) { const data = JSON.parse(evt.detail.data); const command = data.command; - // This means a parallel command just started its execution - if (!executedNewCommand && data.thread_alive) { - ShowCancelCommandButton(); + // This means a parallel command is executing + if (data.thread_alive) { + // Check if we are on the same page on which the actual command was executed + // Each page defines its relevant commands which are sent to `onRelevantPage` function to check if we are on the relevant page + if (onRelevantPage(command)) { + ShowCancelCommandButton(); + logsElement.style.display = "block"; + } else { + // If we are not on relevant page we don't show the cancel button and disable all inputs + deactivateInputs(); + } executedNewCommand = true; } - const parallelCommandCompleted = - executedNewCommand && !data.thread_alive; + const parallelCommandCompleted = executedNewCommand && !data.thread_alive; const onPluginPage = typeof pluginName !== "undefined"; // Note that sequential commands are only executed on the plugins page @@ -40,6 +47,7 @@ htmx.on("htmx:sseBeforeMessage", function (evt) { parallelCommandCompleted || (onPluginPage && sequentialCommandExecuted) ) { + activateInputs(); // There are certain commands for which we do not show the toast message // Only show the toast if it was set in the `setToastContent` function and if the command ran successfully if (data.stdout.includes("Success!")) { diff --git a/tutordeck/server/static/scss/deck.scss b/tutordeck/server/static/scss/deck.scss index 2c8be6d..93990fc 100644 --- a/tutordeck/server/static/scss/deck.scss +++ b/tutordeck/server/static/scss/deck.scss @@ -23,6 +23,11 @@ $green-1: #edfff7; opacity: 1; } +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + @mixin command { width: 9em; height: 3em; @@ -355,6 +360,26 @@ main { margin-left: 1em; } + img { + width: 2em; + } + } + #warning-command-running { + display: flex; + justify-content: center; + align-items: center; + display: none; + border: 1px solid $gray-2; + border-radius: 0.5em; + align-items: center; + font-size: 1.25em; + color: $gray-4; + padding: 0.5em 1em; + margin-top: 1em; + span { + margin-left: 1em; + } + img { width: 2em; } @@ -689,6 +714,7 @@ main { .tutor-logs-container { #tutor-logs { + display: none; width: 100%; background-color: black; color: white; diff --git a/tutordeck/server/templates/advanced.html b/tutordeck/server/templates/advanced.html index 4e9b437..1e2c5d5 100644 --- a/tutordeck/server/templates/advanced.html +++ b/tutordeck/server/templates/advanced.html @@ -34,6 +34,8 @@ Search for any tutor command and execute it with a single click. {% block scripts %} {% block scripts %}{% endblock %} diff --git a/tutordeck/server/templates/local_launch.html b/tutordeck/server/templates/local_launch.html index 6e5fe71..852f7ea 100644 --- a/tutordeck/server/templates/local_launch.html +++ b/tutordeck/server/templates/local_launch.html @@ -24,6 +24,7 @@ This will run Launch Platform to apply all plugin changes. This may take a few m {% block scripts %}