Merge remote-tracking branch 'origin/release'

This commit is contained in:
Edly 2025-05-13 10:25:56 +00:00
commit de5ac9bee0
9 changed files with 91 additions and 20 deletions

View File

@ -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)

View File

@ -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/<name>")
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"))

View File

@ -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";
}

View File

@ -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!")) {

View File

@ -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;

View File

@ -34,6 +34,8 @@ Search for any tutor command and execute it with a single click.
{% block scripts %}
<script>
onDeveloperPage = true;
logsElement.style.display = "block";
runCommandButton = document.querySelector('.run-command-button')
cancelCommandButton = document.querySelector('.cancel-command-button')
const toggleButtons = ({run = false, cancel = false} = {}) => {

View File

@ -49,7 +49,13 @@
</nav>
<section>
<header>{% block workspace_header %}{% endblock %}</header>
<header>
<div id="warning-command-running">
<img src="{{ url_for('static', filename='/img/Featured icon.svg')}}" alt="">
<span>Command execution in progress. <a href="{{ url_for('advanced') }}">Click here</a> to view details.</span>
</div>
{% block workspace_header %}{% endblock %}
</header>
<section>
{% block workspace_content %}
{% endblock %}
@ -111,10 +117,6 @@
{% endif %}
<script>
const logsElement = document.getElementById("tutor-logs");
show_logs = '{{ show_logs }}' === 'True';
if (!show_logs){
logsElement.style.display = 'none';
}
</script>
{% block scripts %}{% endblock %}
<script src="{{ url_for('static', filename='js/logs.js') }}"></script>

View File

@ -24,6 +24,7 @@ This will run Launch Platform to apply all plugin changes. This may take a few m
{% block scripts %}
<script>
relevantCommands = ["tutor local launch --non-interactive"];
localLaunchButton = document.getElementById('local-launch-button')
cancelLocalLaunchButton = document.getElementById('cancel-local-launch-button')
const toggleButtons = ({run = false, cancel = false} = {}) => {

View File

@ -53,6 +53,8 @@
pluginInstallButton = document.getElementById('plugin-install-button');
cancelCommandButton = document.getElementById('cancel-command-button');
relevantCommands = ["tutor plugins install", "tutor plugins upgrade"];
const toggleButtons = ({install = false, upgrade = false, cancel = false} = {}) => {
pluginInstallButton.style.display = install ? 'block' : 'none';
pluginUpgradeButton.style.display = upgrade ? 'block' : 'none';