From a090d7551c2ad34fe83f3059bac4ea341c6d14eb Mon Sep 17 00:00:00 2001 From: gitjannes Date: Thu, 21 Nov 2024 15:41:02 +0100 Subject: [PATCH 01/57] added comment --- protzilla/run_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index f0499ac0..846f4f43 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -17,7 +17,7 @@ def get_available_run_names() -> list[str]: return [] return [ directory.name - for directory in paths.RUNS_PATH.iterdir() + for directory in paths.RUNS_PATH.iterdir() #not sorted the same for different os? if not directory.name.startswith(".") ] From 375b93420c05c6d3634261c26c10ba7546bee01e Mon Sep 17 00:00:00 2001 From: gitjannes Date: Fri, 22 Nov 2024 15:43:10 +0100 Subject: [PATCH 02/57] Implemented get_available_runs(), a function that returns all runs with information about these runs --- protzilla/disk_operator.py | 3 +++ protzilla/run_v2.py | 35 +++++++++++++++++++++++++++++++++++ protzilla/steps.py | 1 + 3 files changed, 39 insertions(+) diff --git a/protzilla/disk_operator.py b/protzilla/disk_operator.py index ce7f07ad..2f946f70 100644 --- a/protzilla/disk_operator.py +++ b/protzilla/disk_operator.py @@ -84,6 +84,7 @@ def write(file_path: Path, dataframe: pd.DataFrame): class KEYS: # We add this here to avoid typos and signal to the developer that accessing the keys should be done through this class only CURRENT_STEP_INDEX = "current_step_index" + FAVOURITE = "favourite" #might cause problems because of backwards-compatibility STEPS = "steps" STEP_OUTPUTS = "output" STEP_FORM_INPUTS = "form_inputs" @@ -123,6 +124,7 @@ def read_run(self, file: Path | None = None) -> StepManager: run.get(KEYS.CURRENT_STEP_INDEX, 0), len(step_manager.all_steps) - 1 ), ) + step_manager.favourite = run.get(KEYS.FAVOURITE, False) #might cause problems because of backwards-compatibility return step_manager def write_run(self, step_manager: StepManager) -> None: @@ -134,6 +136,7 @@ def write_run(self, step_manager: StepManager) -> None: self.clean_dataframes_dir(step_manager) run = {} run[KEYS.CURRENT_STEP_INDEX] = step_manager.current_step_index + run[KEYS.FAVOURITE] = step_manager.favourite #might cause problems because of backwards-compatibility run[KEYS.DF_MODE] = step_manager.df_mode run[KEYS.STEPS] = [] for step in step_manager.all_steps: diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index 846f4f43..073dbd21 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -6,6 +6,7 @@ import os import shutil +import datetime import protzilla.constants.paths as paths from protzilla.steps import Messages, Output, Plots, Step @@ -21,6 +22,40 @@ def get_available_run_names() -> list[str]: if not directory.name.startswith(".") ] +def get_available_runs() -> list[dict[str, str | list[str]]]: + if not paths.RUNS_PATH.exists(): + return [] + runs = [] + runs_favourited = [] + + for directory in paths.RUNS_PATH.iterdir(): + creation_time = directory.stat().st_ctime + modification_time = directory.stat().st_mtime + + from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) + + disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") + directory_path = os.path.join(paths.RUNS_PATH, directory.name) + yaml_path = os.path.join(directory_path, "run.yaml") + step_manager = disk_operator.read_run(yaml_path) + steps = step_manager.all_steps() + step_names = [] + for step in steps: + step_names.append(step.display_name) + + run = { + "run_name": directory.name, + "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), + "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %B %Y"), + "run_steps" : step_names + } + + if step_manager.favourite: + runs_favourited.append(run) + else: + runs.append(run) + return runs, runs_favourited + def delete_run_folder(run_name) -> None: path = os.path.join(paths.RUNS_PATH, run_name) diff --git a/protzilla/steps.py b/protzilla/steps.py index eecf0d04..b18f4b5e 100644 --- a/protzilla/steps.py +++ b/protzilla/steps.py @@ -324,6 +324,7 @@ def __init__( self.df_mode = df_mode self.disk_operator = disk_operator self.current_step_index = 0 + self.favourite = False #might cause problems because of backwards-compatibility self.importing = [] self.data_preprocessing = [] self.data_analysis = [] From 16cdfb8be9ce34e230a133d183aca495e1a9188a Mon Sep 17 00:00:00 2001 From: gitjannes Date: Fri, 22 Nov 2024 16:04:04 +0100 Subject: [PATCH 03/57] Added filter.py, where the filters will be implemented. added (temporary) filtered_index in views.py to reload the index page when filters are applied by the user --- ui/runs/filter.py | 2 ++ ui/runs/views.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 ui/runs/filter.py diff --git a/ui/runs/filter.py b/ui/runs/filter.py new file mode 100644 index 00000000..aae74211 --- /dev/null +++ b/ui/runs/filter.py @@ -0,0 +1,2 @@ +def filter_runs(runs, filters) -> None: #to be implemented + return \ No newline at end of file diff --git a/ui/runs/views.py b/ui/runs/views.py index 020063eb..88d10cb4 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -20,6 +20,7 @@ from django.urls import reverse from django.conf import settings +from protzilla.filter import filter_runs from protzilla.run import Run, get_available_run_names from protzilla.run_v2 import delete_run_folder from protzilla.run_helper import log_messages @@ -182,6 +183,25 @@ def index(request: HttpRequest, index_error: bool = False): }, ) +def filtered_index(request: HttpRequest, filter: list[str], index_error: bool = False): #should replace index completely, but is currently a different method for unforeseen dependencies on normal index + """ + Renders the main index page of the PROTzilla application. + + :param request: the request object + :type request: HttpRequest + + :return: the rendered index page + :rtype: HttpResponse + """ + return render( + request, + "runs/index.html", + context={ + "available_workflows": get_available_workflow_names(), + "available_runs": filter_runs(get_available_run_names(), filter), + }, + ) + def create(request: HttpRequest): """ From c56612140f52bc5795e534a6496e8f45b7136c23 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Fri, 22 Nov 2024 16:18:29 +0100 Subject: [PATCH 04/57] Made filtered_index more understandable --- ui/runs/views.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/runs/views.py b/ui/runs/views.py index 88d10cb4..ffc8f257 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -22,7 +22,7 @@ from protzilla.filter import filter_runs from protzilla.run import Run, get_available_run_names -from protzilla.run_v2 import delete_run_folder +from protzilla.run_v2 import delete_run_folder, get_available_runs from protzilla.run_helper import log_messages from protzilla.stepfactory import StepFactory from protzilla.steps import Step @@ -193,12 +193,17 @@ def filtered_index(request: HttpRequest, filter: list[str], index_error: bool = :return: the rendered index page :rtype: HttpResponse """ + runs, runs_favourite = get_available_runs() + filtered_runs = filter_runs(runs, filter) + filtered_runs_favourite = filter_runs(runs_favourite, filter) return render( request, "runs/index.html", context={ "available_workflows": get_available_workflow_names(), - "available_runs": filter_runs(get_available_run_names(), filter), + #"available_runs": filter_runs(get_available_runs(), filter), #this version is probably worse cause get_available_runs returns two things, why should filter expect two things in one param + "available_runs" : filtered_runs, + "available_runs_favourite": filtered_runs_favourite, }, ) From 5c1d1ab10a527dc824528a773a06a50ba479c25b Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 10:11:53 +0100 Subject: [PATCH 05/57] fixed somethings --- protzilla/run_v2.py | 5 +++-- ui/runs/filter.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index 073dbd21..29c337da 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -29,13 +29,14 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: runs_favourited = [] for directory in paths.RUNS_PATH.iterdir(): + name = directory.name creation_time = directory.stat().st_ctime modification_time = directory.stat().st_mtime from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") - directory_path = os.path.join(paths.RUNS_PATH, directory.name) + directory_path = os.path.join(paths.RUNS_PATH, name) yaml_path = os.path.join(directory_path, "run.yaml") step_manager = disk_operator.read_run(yaml_path) steps = step_manager.all_steps() @@ -44,7 +45,7 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: step_names.append(step.display_name) run = { - "run_name": directory.name, + "run_name": name, "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %B %Y"), "run_steps" : step_names diff --git a/ui/runs/filter.py b/ui/runs/filter.py index aae74211..6b285dfb 100644 --- a/ui/runs/filter.py +++ b/ui/runs/filter.py @@ -1,2 +1,2 @@ def filter_runs(runs, filters) -> None: #to be implemented - return \ No newline at end of file + return runs \ No newline at end of file From dc2012181579ddcc34f17e491817984e3c44f12a Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 10:17:00 +0100 Subject: [PATCH 06/57] fiexed import for filter.py --- ui/runs/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/runs/views.py b/ui/runs/views.py index ffc8f257..344f0f33 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -20,7 +20,7 @@ from django.urls import reverse from django.conf import settings -from protzilla.filter import filter_runs +from ui.runs.filter import filter_runs # from protzilla.run import Run, get_available_run_names from protzilla.run_v2 import delete_run_folder, get_available_runs from protzilla.run_helper import log_messages From 1d29f3a53cf9ed9c0568f04a97904cb5d6b167c5 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 10:36:28 +0100 Subject: [PATCH 07/57] Added structure to filter.py --- ui/runs/filter.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/runs/filter.py b/ui/runs/filter.py index 6b285dfb..efd4a41b 100644 --- a/ui/runs/filter.py +++ b/ui/runs/filter.py @@ -1,2 +1,10 @@ -def filter_runs(runs, filters) -> None: #to be implemented +def filter_for_name(runs, search_string) -> list[dict[str, str | list[str]]]: + return runs + +def filter_for_steps(runs, search_steps) -> list[dict[str, str | list[str]]]: + return runs + +def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: #to be implemented + runs = filter_for_name(runs, filters.name) + runs = filter_for_steps(runs, filters.steps) return runs \ No newline at end of file From a85fea397cda959f0864a700350a5efb06ac70e3 Mon Sep 17 00:00:00 2001 From: maximilianKalff <87859344+maximilianKalff@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:39:49 +0100 Subject: [PATCH 08/57] continue menu revised and delete feature implemented (#554) --- ui/runs/static/runs/index.css | 4 --- ui/runs/static/runs/index.js | 9 +++++++ ui/runs/templates/runs/index.html | 44 ++++++++++++++++++------------- ui/runs/views.py | 2 +- 4 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 ui/runs/static/runs/index.js diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index a058f32c..b8bba373 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -1,8 +1,4 @@ #contain { height:100vh; margin-top:-3.6rem; -} - -#row { - width:80%; } \ No newline at end of file diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js new file mode 100644 index 00000000..13971cdc --- /dev/null +++ b/ui/runs/static/runs/index.js @@ -0,0 +1,9 @@ +function selectRun(runName){ + document.getElementById('selectedRun').textContent = runName; + document.getElementById('run_name_id').value = runName; +}; + +function deleteRun(runName){ + document.getElementById('delete_run_name_id').value = runName; + document.getElementById('delete_run').submit(); +}; \ No newline at end of file diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index d6be8f3d..b6e75925 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -5,6 +5,10 @@ {% endblock %} +{% block js %} + +{% endblock %} + {% block content %}
@@ -48,36 +52,38 @@

Work on a new run:

+
{% csrf_token %}

Continue an existing run:

- +
+
- Manage databases -
- -
-
+ {% csrf_token %} -
-

Delete an existing run:

- -
- +
+ Manage databases
+
diff --git a/ui/runs/views.py b/ui/runs/views.py index 344f0f33..f15e1db5 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -268,7 +268,7 @@ def delete_(request: HttpRequest): :return: the rendered details page of the run :rtype: HttpResponse """ - run_name = request.POST["run_name"] + run_name = request.POST["delete_run_name"] if run_name in active_runs: del active_runs[run_name] From c841f8f304b4f51b365fa2e47047c41848c48ffe Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 10:46:44 +0100 Subject: [PATCH 09/57] added memory_mode to runinfo --- protzilla/run_v2.py | 1 + ui/runs/filter.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index 29c337da..9b5fee7e 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -48,6 +48,7 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: "run_name": name, "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %B %Y"), + "memory_mode": step_manager.df_mode, "run_steps" : step_names } diff --git a/ui/runs/filter.py b/ui/runs/filter.py index efd4a41b..d564ff57 100644 --- a/ui/runs/filter.py +++ b/ui/runs/filter.py @@ -4,7 +4,11 @@ def filter_for_name(runs, search_string) -> list[dict[str, str | list[str]]]: def filter_for_steps(runs, search_steps) -> list[dict[str, str | list[str]]]: return runs +def filter_for_memory_mode(runs, memory_mode) -> list[dict[str, str | list[str]]]: + return runs + def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: #to be implemented runs = filter_for_name(runs, filters.name) runs = filter_for_steps(runs, filters.steps) + runs = filter_for_memory_mode(runs, filter.memory_mode) return runs \ No newline at end of file From e9eae3ff94926709b9fdc168c3f8840b94a64b8b Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 11:23:59 +0100 Subject: [PATCH 10/57] new run info works --- protzilla/run_v2.py | 4 ++-- ui/runs/filter.py | 6 +++--- ui/runs/templates/runs/index.html | 14 ++++++++++---- ui/runs/urls.py | 1 + ui/runs/views.py | 6 ++++-- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index 9b5fee7e..abf5cbf9 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -39,14 +39,14 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: directory_path = os.path.join(paths.RUNS_PATH, name) yaml_path = os.path.join(directory_path, "run.yaml") step_manager = disk_operator.read_run(yaml_path) - steps = step_manager.all_steps() + steps = step_manager.all_steps step_names = [] for step in steps: step_names.append(step.display_name) run = { "run_name": name, - "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), + "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), #TODO: reutrn the pure datetime, convert in html) "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %B %Y"), "memory_mode": step_manager.df_mode, "run_steps" : step_names diff --git a/ui/runs/filter.py b/ui/runs/filter.py index d564ff57..3c130281 100644 --- a/ui/runs/filter.py +++ b/ui/runs/filter.py @@ -8,7 +8,7 @@ def filter_for_memory_mode(runs, memory_mode) -> list[dict[str, str | list[str]] return runs def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: #to be implemented - runs = filter_for_name(runs, filters.name) - runs = filter_for_steps(runs, filters.steps) - runs = filter_for_memory_mode(runs, filter.memory_mode) + #runs = filter_for_name(runs, filters.name) + #runs = filter_for_steps(runs, filters.steps) + #runs = filter_for_memory_mode(runs, filter.memory_mode) return runs \ No newline at end of file diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index b6e75925..b0d13598 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -61,25 +61,31 @@

Continue an existing run:

- +
{% csrf_token %} - +
Manage databases diff --git a/ui/runs/urls.py b/ui/runs/urls.py index a6f4c36c..23271970 100644 --- a/ui/runs/urls.py +++ b/ui/runs/urls.py @@ -5,6 +5,7 @@ app_name = "runs" urlpatterns = [ path("", views.index, name="index"), +# path("filter", views.filtered_index, name="filter"), path("create", views.create, name="create"), path("continue", views.continue_, name="continue"), path("delete", views.delete_, name="delete"), diff --git a/ui/runs/views.py b/ui/runs/views.py index f15e1db5..e8bb93aa 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -164,7 +164,7 @@ def detail(request: HttpRequest, run_name: str): ) -def index(request: HttpRequest, index_error: bool = False): +#def index(request: HttpRequest, index_error: bool = False): """ Renders the main index page of the PROTzilla application. @@ -183,7 +183,7 @@ def index(request: HttpRequest, index_error: bool = False): }, ) -def filtered_index(request: HttpRequest, filter: list[str], index_error: bool = False): #should replace index completely, but is currently a different method for unforeseen dependencies on normal index +def index(request: HttpRequest, index_error: bool = False): #should replace index completely, but is currently a different method for unforeseen dependencies on normal index """ Renders the main index page of the PROTzilla application. @@ -193,6 +193,8 @@ def filtered_index(request: HttpRequest, filter: list[str], index_error: bool = :return: the rendered index page :rtype: HttpResponse """ + #filter = request.POST["filter"] + filter = {} #nur zum testen runs, runs_favourite = get_available_runs() filtered_runs = filter_runs(runs, filter) filtered_runs_favourite = filter_runs(runs_favourite, filter) From 3e92367a7ee974b64b4e2d3abc752307c1939da8 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 11:57:59 +0100 Subject: [PATCH 11/57] fixed get_available_runs for mac --- protzilla/disk_operator.py | 10 +++++++++- protzilla/run_v2.py | 2 ++ ui/runs/views.py | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/protzilla/disk_operator.py b/protzilla/disk_operator.py index 2f946f70..b8572843 100644 --- a/protzilla/disk_operator.py +++ b/protzilla/disk_operator.py @@ -136,13 +136,21 @@ def write_run(self, step_manager: StepManager) -> None: self.clean_dataframes_dir(step_manager) run = {} run[KEYS.CURRENT_STEP_INDEX] = step_manager.current_step_index - run[KEYS.FAVOURITE] = step_manager.favourite #might cause problems because of backwards-compatibility run[KEYS.DF_MODE] = step_manager.df_mode run[KEYS.STEPS] = [] for step in step_manager.all_steps: run[KEYS.STEPS].append(self._write_step(step)) self.yaml_operator.write(self.run_file, run) + def change_favourite_run(self, step_manager: StepManager) -> None: + with ErrorHandler(): + if not self.run_dir.exists(): + self.run_dir.mkdir(parents=True, exist_ok=True) + run = {} + run[KEYS.FAVOURITE] = step_manager.favourite + self.yaml_operator.write(self.run_file, run) + + def read_workflow(self) -> StepManager: return self.read_run(self.workflow_file) diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index abf5cbf9..c13ea49c 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -29,6 +29,8 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: runs_favourited = [] for directory in paths.RUNS_PATH.iterdir(): + if directory.name.startswith("."): + continue name = directory.name creation_time = directory.stat().st_ctime modification_time = directory.stat().st_mtime diff --git a/ui/runs/views.py b/ui/runs/views.py index e8bb93aa..4bd80a3b 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -209,6 +209,17 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde }, ) +#def favourite(request: HttpRequest): + + run_name = request.POST["favourite_run_name"] + favourite_status = request.POST["favourite_status"] + + from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) + + disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") + directory_path = os.path.join(paths.RUNS_PATH, run_name) + yaml_path = os.path.join(directory_path, "run.yaml") + def create(request: HttpRequest): """ From 855f889ada2d9c382071eaba66c8cac84adb3e20 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 12:01:50 +0100 Subject: [PATCH 12/57] fixed smth --- ui/runs/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/runs/views.py b/ui/runs/views.py index 4bd80a3b..fbc6a9db 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -209,16 +209,16 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde }, ) -#def favourite(request: HttpRequest): +def favourite(request: HttpRequest): run_name = request.POST["favourite_run_name"] favourite_status = request.POST["favourite_status"] from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) - disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") - directory_path = os.path.join(paths.RUNS_PATH, run_name) - yaml_path = os.path.join(directory_path, "run.yaml") + disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") + directory_path = os.path.join(paths.RUNS_PATH, run_name) + yaml_path = os.path.join(directory_path, "run.yaml") def create(request: HttpRequest): From 3231d43240a37c9713ec2576e1bdae9f2b982350 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 14:50:29 +0100 Subject: [PATCH 13/57] added favourite in views.py --- ui/runs/urls.py | 2 +- ui/runs/views.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/runs/urls.py b/ui/runs/urls.py index 23271970..ec5491f9 100644 --- a/ui/runs/urls.py +++ b/ui/runs/urls.py @@ -6,7 +6,7 @@ urlpatterns = [ path("", views.index, name="index"), # path("filter", views.filtered_index, name="filter"), - path("create", views.create, name="create"), + path("create", views.create, name="create"), #create, continue and delete paths should be in main/urls.py, also change index.html accordingly path("continue", views.continue_, name="continue"), path("delete", views.delete_, name="delete"), path("detail/", views.detail, name="detail"), diff --git a/ui/runs/views.py b/ui/runs/views.py index fbc6a9db..f92686a7 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -20,7 +20,8 @@ from django.urls import reverse from django.conf import settings -from ui.runs.filter import filter_runs # +from ui.runs.filter import filter_runs #prob should put this file somewhere else. +import protzilla.constants.paths as paths from protzilla.run import Run, get_available_run_names from protzilla.run_v2 import delete_run_folder, get_available_runs from protzilla.run_helper import log_messages @@ -193,6 +194,8 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde :return: the rendered index page :rtype: HttpResponse """ + if request.POST["filter"]: + filter = request.POST["filter"] #filter = request.POST["filter"] filter = {} #nur zum testen runs, runs_favourite = get_available_runs() @@ -219,6 +222,11 @@ def favourite(request: HttpRequest): disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") directory_path = os.path.join(paths.RUNS_PATH, run_name) yaml_path = os.path.join(directory_path, "run.yaml") + step_manager = disk_operator.read_run(yaml_path) + step_manager.favourite = favourite_status + disk_operator.change_favourite_run(step_manager) + + return HttpResponseRedirect(reverse("runs:index")) def create(request: HttpRequest): From 4436b430bb1635d5284009ed9e5811b4550426ec Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 16:58:06 +0100 Subject: [PATCH 14/57] favourite function fixed --- ui/runs/templates/runs/index.html | 5 +++++ ui/runs/views.py | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index b0d13598..732f11f8 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -91,6 +91,11 @@

Continue an existing run:

+
+ {% csrf_token %} + + +
{% endblock %} \ No newline at end of file diff --git a/ui/runs/views.py b/ui/runs/views.py index f92686a7..0cfb42b1 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -3,6 +3,7 @@ import tempfile import traceback import zipfile +import json from pathlib import Path import networkx as nx @@ -194,10 +195,13 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde :return: the rendered index page :rtype: HttpResponse """ - if request.POST["filter"]: - filter = request.POST["filter"] + filterbing = request.POST.get("filter", "{}") + filter = json.loads(filterbing) + print(filterbing) + print(filter) + print(filter.get("x")) #filter = request.POST["filter"] - filter = {} #nur zum testen + #filter = {} #nur zum testen runs, runs_favourite = get_available_runs() filtered_runs = filter_runs(runs, filter) filtered_runs_favourite = filter_runs(runs_favourite, filter) From 6150878f4beecf971662e61d597ad5458baf7c99 Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Tue, 26 Nov 2024 16:59:14 +0100 Subject: [PATCH 15/57] first layout draft --- ui/runs/static/runs/index.css | 3 +- ui/runs/static/runs/index.js | 27 +++++++++++++++++- ui/runs/templates/runs/index.html | 47 +++++++++++++++++-------------- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index b8bba373..f091b97c 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -1,4 +1,5 @@ #contain { height:100vh; margin-top:-3.6rem; -} \ No newline at end of file + +} diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 13971cdc..e8409ed1 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -6,4 +6,29 @@ function selectRun(runName){ function deleteRun(runName){ document.getElementById('delete_run_name_id').value = runName; document.getElementById('delete_run').submit(); -}; \ No newline at end of file +}; + +function toggleIcon(button) { + const starIcon = ` + + + + `; + + const filledStarIcon = ` + + + + `; + + // Hole das aktuelle SVG im Button + const currentIcon = button.querySelector("svg"); + + // Tausche das SVG basierend auf dem aktuellen Icon + if (currentIcon.classList.contains("bi-star")) { + currentIcon.outerHTML = filledStarIcon; // Wechsel zu gefülltem Stern + } else { + currentIcon.outerHTML = starIcon; // Wechsel zurück zu leerem Stern + } + } + \ No newline at end of file diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index b0d13598..aea86f09 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -25,8 +25,8 @@
{% endif %} -
-
+
+
{% csrf_token %}
@@ -53,32 +53,37 @@

Work on a new run:

-
+
{% csrf_token %}
-

Continue an existing run:

- +
From 30a68dec7bdf280052de0b2d9a650edab08f748a Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 26 Nov 2024 18:14:48 +0100 Subject: [PATCH 16/57] Proof of Concept for letting user choose favourites. its very ugly --- protzilla/disk_operator.py | 8 +------- protzilla/run_v2.py | 5 +++-- ui/runs/static/runs/index.js | 6 ++++++ ui/runs/templates/runs/index.html | 27 ++++++++++++++++++++++++++- ui/runs/urls.py | 1 + ui/runs/views.py | 12 ++++++++---- 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/protzilla/disk_operator.py b/protzilla/disk_operator.py index b8572843..d27d78f4 100644 --- a/protzilla/disk_operator.py +++ b/protzilla/disk_operator.py @@ -136,19 +136,13 @@ def write_run(self, step_manager: StepManager) -> None: self.clean_dataframes_dir(step_manager) run = {} run[KEYS.CURRENT_STEP_INDEX] = step_manager.current_step_index + run[KEYS.FAVOURITE] = step_manager.favourite run[KEYS.DF_MODE] = step_manager.df_mode run[KEYS.STEPS] = [] for step in step_manager.all_steps: run[KEYS.STEPS].append(self._write_step(step)) self.yaml_operator.write(self.run_file, run) - def change_favourite_run(self, step_manager: StepManager) -> None: - with ErrorHandler(): - if not self.run_dir.exists(): - self.run_dir.mkdir(parents=True, exist_ok=True) - run = {} - run[KEYS.FAVOURITE] = step_manager.favourite - self.yaml_operator.write(self.run_file, run) def read_workflow(self) -> StepManager: diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index c13ea49c..0a5c2cb9 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -37,7 +37,7 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) - disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") + disk_operator = DiskOperator(name, "dummy_workflow_name") directory_path = os.path.join(paths.RUNS_PATH, name) yaml_path = os.path.join(directory_path, "run.yaml") step_manager = disk_operator.read_run(yaml_path) @@ -51,7 +51,8 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), #TODO: reutrn the pure datetime, convert in html) "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %B %Y"), "memory_mode": step_manager.df_mode, - "run_steps" : step_names + "run_steps" : step_names, + "favourite_status" : step_manager.favourite } if step_manager.favourite: diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index e8409ed1..65ab011d 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -8,6 +8,12 @@ function deleteRun(runName){ document.getElementById('delete_run').submit(); }; +function toggleFavouriteRun(runName, favouriteStatus){ + document.getElementById('favourites_run_name_id').value = runName; + document.getElementById('favourites_run_status_id').value = favouriteStatus; + document.getElementById('change_favourite').submit(); +}; + function toggleIcon(button) { const starIcon = ` diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 59597645..5c6cecac 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -62,10 +62,30 @@

Continue an existing run:

    + {% for run in available_runs_favourite %} +
    +
  • + + {{ run.run_name }} + {{ run.creation_date }} + {{ run.modification_date }} + {{ run.memory_mode }} + +
  • + + {% endfor %} {% for run in available_runs %}
  • -
diff --git a/ui/runs/urls.py b/ui/runs/urls.py index ec5491f9..3d490a43 100644 --- a/ui/runs/urls.py +++ b/ui/runs/urls.py @@ -9,6 +9,7 @@ path("create", views.create, name="create"), #create, continue and delete paths should be in main/urls.py, also change index.html accordingly path("continue", views.continue_, name="continue"), path("delete", views.delete_, name="delete"), + path("toggle_favourite", views.favourite, name="toggle_favourite"), path("detail/", views.detail, name="detail"), path("/plot", views.plot, name="plot"), path("/tables/", views.tables, name="tables_nokey"), diff --git a/ui/runs/views.py b/ui/runs/views.py index 0cfb42b1..3dbfe189 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -219,16 +219,20 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde def favourite(request: HttpRequest): run_name = request.POST["favourite_run_name"] - favourite_status = request.POST["favourite_status"] + favourite_status = request.POST["favourite_run_status"] + if favourite_status == "False": + favourite_status = False + else: + favourite_status = True from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) - disk_operator = DiskOperator("dummy_run_name", "dummy_workflow_name") + disk_operator = DiskOperator(run_name, "dummy_workflow_name") directory_path = os.path.join(paths.RUNS_PATH, run_name) yaml_path = os.path.join(directory_path, "run.yaml") step_manager = disk_operator.read_run(yaml_path) - step_manager.favourite = favourite_status - disk_operator.change_favourite_run(step_manager) + step_manager.favourite = not favourite_status + disk_operator.write_run(step_manager) return HttpResponseRedirect(reverse("runs:index")) From 5d4281a8c5d2de4e9d3c63a2270ded5adc1d2b41 Mon Sep 17 00:00:00 2001 From: sarah Date: Wed, 27 Nov 2024 23:10:30 +0100 Subject: [PATCH 17/57] filter methods logic implemented, working with hardcoded filter --- ui/runs/filter.py | 12 ++++++------ ui/runs/views.py | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/runs/filter.py b/ui/runs/filter.py index 3c130281..35ea60f1 100644 --- a/ui/runs/filter.py +++ b/ui/runs/filter.py @@ -1,14 +1,14 @@ def filter_for_name(runs, search_string) -> list[dict[str, str | list[str]]]: - return runs + return [run for run in runs if search_string in run["run_name"]] def filter_for_steps(runs, search_steps) -> list[dict[str, str | list[str]]]: - return runs + return [run for run in runs if all(step in run["run_steps"] for step in search_steps)] def filter_for_memory_mode(runs, memory_mode) -> list[dict[str, str | list[str]]]: - return runs + return [run for run in runs if run["memory_mode"] == memory_mode] def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: #to be implemented - #runs = filter_for_name(runs, filters.name) - #runs = filter_for_steps(runs, filters.steps) - #runs = filter_for_memory_mode(runs, filter.memory_mode) + runs = filter_for_name(runs, filters["name"]) + runs = filter_for_steps(runs, filters["steps"]) + runs = filter_for_memory_mode(runs, filters["memory_mode"]) return runs \ No newline at end of file diff --git a/ui/runs/views.py b/ui/runs/views.py index 3dbfe189..51149006 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -202,6 +202,7 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde print(filter.get("x")) #filter = request.POST["filter"] #filter = {} #nur zum testen + filter = {"name":"d", "steps":["MaxQuant Protein Groups Import", "kNN"], "memory_mode":"disk_memory"} #dummy filter for testing -> might need to be adapted for your workflows to actually show something runs, runs_favourite = get_available_runs() filtered_runs = filter_runs(runs, filter) filtered_runs_favourite = filter_runs(runs_favourite, filter) From a39b2bebba2a813ae0bd34760cf7978f65d6a456 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Mon, 2 Dec 2024 14:33:03 +0100 Subject: [PATCH 18/57] Added tag functionality for runs (no frontend tho). Tested how to get selected filters to the views.py (selection doesnt exist yet). --- protzilla/run_v2.py | 16 ++++++++++++---- ui/runs/filter.py | 14 +++++++++++--- ui/runs/templates/runs/index.html | 2 +- ui/runs/views.py | 29 ++++++++++++++++++++++------- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py index 0a5c2cb9..e010a47c 100644 --- a/protzilla/run_v2.py +++ b/protzilla/run_v2.py @@ -35,24 +35,32 @@ def get_available_runs() -> list[dict[str, str | list[str]]]: creation_time = directory.stat().st_ctime modification_time = directory.stat().st_mtime - from protzilla.disk_operator import DiskOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) + from protzilla.disk_operator import DiskOperator, YamlOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) disk_operator = DiskOperator(name, "dummy_workflow_name") directory_path = os.path.join(paths.RUNS_PATH, name) - yaml_path = os.path.join(directory_path, "run.yaml") - step_manager = disk_operator.read_run(yaml_path) + run_yaml_path = os.path.join(directory_path, "run.yaml") + step_manager = disk_operator.read_run(run_yaml_path) steps = step_manager.all_steps step_names = [] for step in steps: step_names.append(step.display_name) + tags = [] + metadata_yaml_path = os.path.join(directory_path, "metadata.yaml") + if os.path.isfile(metadata_yaml_path): + yaml_operator = YamlOperator() + metadata = yaml_operator.read(metadata_yaml_path) + tags = metadata.get("tags") + run = { "run_name": name, "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %B %Y"), #TODO: reutrn the pure datetime, convert in html) "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %B %Y"), "memory_mode": step_manager.df_mode, "run_steps" : step_names, - "favourite_status" : step_manager.favourite + "favourite_status" : step_manager.favourite, + "run_tags": tags } if step_manager.favourite: diff --git a/ui/runs/filter.py b/ui/runs/filter.py index 35ea60f1..c725deb1 100644 --- a/ui/runs/filter.py +++ b/ui/runs/filter.py @@ -4,11 +4,19 @@ def filter_for_name(runs, search_string) -> list[dict[str, str | list[str]]]: def filter_for_steps(runs, search_steps) -> list[dict[str, str | list[str]]]: return [run for run in runs if all(step in run["run_steps"] for step in search_steps)] +def filter_for_tags(runs, search_tags) -> list[dict[str, str | list[str]]]: + return [run for run in runs if all(tag in run["run_tags"] for tag in search_tags)] + def filter_for_memory_mode(runs, memory_mode) -> list[dict[str, str | list[str]]]: return [run for run in runs if run["memory_mode"] == memory_mode] def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: #to be implemented - runs = filter_for_name(runs, filters["name"]) - runs = filter_for_steps(runs, filters["steps"]) - runs = filter_for_memory_mode(runs, filters["memory_mode"]) + if "name" in filters: + runs = filter_for_name(runs, filters["name"]) + if "steps" in filters: + runs = filter_for_steps(runs, filters["steps"]) + if "tags" in filters: + runs = filter_for_tags(runs, filters["tags"]) + if "memory_mode" in filters: + runs = filter_for_memory_mode(runs, filters["memory_mode"]) return runs \ No newline at end of file diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 5c6cecac..73194519 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -123,7 +123,7 @@

Continue an existing run:

{% csrf_token %} - +
diff --git a/ui/runs/views.py b/ui/runs/views.py index 51149006..c72f3067 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -197,12 +197,10 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde """ filterbing = request.POST.get("filter", "{}") filter = json.loads(filterbing) - print(filterbing) - print(filter) - print(filter.get("x")) - #filter = request.POST["filter"] - #filter = {} #nur zum testen - filter = {"name":"d", "steps":["MaxQuant Protein Groups Import", "kNN"], "memory_mode":"disk_memory"} #dummy filter for testing -> might need to be adapted for your workflows to actually show something + print(filterbing, type(filterbing)) + print(filter, type(filter)) + #print(filter["name"]) + #filter = {"name":"d", "steps":["MaxQuant Protein Groups Import", "kNN"], "memory_mode":"disk_memory"} #dummy filter for testing -> might need to be adapted for your workflows to actually show something runs, runs_favourite = get_available_runs() filtered_runs = filter_runs(runs, filter) filtered_runs_favourite = filter_runs(runs_favourite, filter) @@ -211,7 +209,6 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde "runs/index.html", context={ "available_workflows": get_available_workflow_names(), - #"available_runs": filter_runs(get_available_runs(), filter), #this version is probably worse cause get_available_runs returns two things, why should filter expect two things in one param "available_runs" : filtered_runs, "available_runs_favourite": filtered_runs_favourite, }, @@ -237,6 +234,24 @@ def favourite(request: HttpRequest): return HttpResponseRedirect(reverse("runs:index")) +def tag(request: HttpRequest): + + run_name = request.POST["favourite_run_name"] + run_tag = request.POST["tag"] + + from protzilla.disk_operator import YamlOperator # to avoid a circular import (geht das cleaner? habs einfach kopiert von unten?) + + directory_path = os.path.join(paths.RUNS_PATH, run_name) + metadata_yaml_path = os.path.join(directory_path, "metadata.yaml") + + yaml_operator = YamlOperator() + metadata = yaml_operator.read(metadata_yaml_path) + tags = metadata.get("tags") + tags.append(run_tag) + yaml_operator.read(metadata_yaml_path, tags) + + return HttpResponseRedirect(reverse("runs:index")) + def create(request: HttpRequest): """ From 8aa0f53354ce24a9e85a59d3fbe47ca2907b2eb3 Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Thu, 5 Dec 2024 15:17:35 +0000 Subject: [PATCH 19/57] Select Visualisation, Run Selection code refactored --- ui/runs/static/runs/index.js | 53 ++++++++----------- ui/runs/templates/runs/index.html | 84 ++++++++++++++----------------- ui/runs/views.py | 5 ++ 3 files changed, 64 insertions(+), 78 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 65ab011d..24082791 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -1,40 +1,27 @@ -function selectRun(runName){ - document.getElementById('selectedRun').textContent = runName; - document.getElementById('run_name_id').value = runName; +function selectRun(runName, runId, numberOfRuns){ + for (let i = 1; i <= numberOfRuns; i++) { + let run = document.getElementById('run-' + i); + if (i == runId) { + run.style.backgroundColor = '#e8edf3'; + run.style.borderRadius = '10px'; + } + else { + run.style.backgroundColor = 'white'; + } + } + + document.getElementById('selectedRun').textContent = runName; + document.getElementById('run_name_id').value = runName; }; function deleteRun(runName){ - document.getElementById('delete_run_name_id').value = runName; - document.getElementById('delete_run').submit(); + document.getElementById('delete_run_name_id').value = runName; + document.getElementById('delete_run').submit(); }; function toggleFavouriteRun(runName, favouriteStatus){ - document.getElementById('favourites_run_name_id').value = runName; - document.getElementById('favourites_run_status_id').value = favouriteStatus; - document.getElementById('change_favourite').submit(); -}; -function toggleIcon(button) { - const starIcon = ` - - - - `; - - const filledStarIcon = ` - - - - `; - - // Hole das aktuelle SVG im Button - const currentIcon = button.querySelector("svg"); - - // Tausche das SVG basierend auf dem aktuellen Icon - if (currentIcon.classList.contains("bi-star")) { - currentIcon.outerHTML = filledStarIcon; // Wechsel zu gefülltem Stern - } else { - currentIcon.outerHTML = starIcon; // Wechsel zurück zu leerem Stern - } - } - \ No newline at end of file + document.getElementById('favourites_run_name_id').value = runName; + document.getElementById('favourites_run_status_id').value = favouriteStatus; + document.getElementById('change_favourite').submit(); +}; diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 73194519..aab0831b 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -1,3 +1,4 @@ + {% extends 'base.html' %} {% load static %} @@ -60,49 +61,42 @@

Work on a new run:

Continue an existing run: {{ available_runs.0.run_name }}

- -
    - {% for run in available_runs_favourite %} -
    -
  • - - {{ run.run_name }} - {{ run.creation_date }} - {{ run.modification_date }} - {{ run.memory_mode }} - -
  • - - {% endfor %} - {% for run in available_runs %} -
    -
  • - - {{ run.run_name }} - {{ run.creation_date }} - {{ run.modification_date }} - {{ run.memory_mode }} - -
  • - - {% endfor %} -
+ + +
    + {% for run in all_available_runs %} +
    +
  • + + {{ run.run_name }} + {{ run.creation_date }} + {{ run.modification_date }} + {{ run.memory_mode }} + +
  • + {% endfor %} +
@@ -123,9 +117,9 @@

Continue an existing run:

{% csrf_token %} - +
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/ui/runs/views.py b/ui/runs/views.py index c72f3067..480eacfa 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -204,15 +204,20 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde runs, runs_favourite = get_available_runs() filtered_runs = filter_runs(runs, filter) filtered_runs_favourite = filter_runs(runs_favourite, filter) + all_available_runs = filtered_runs_favourite + filtered_runs return render( request, "runs/index.html", context={ "available_workflows": get_available_workflow_names(), + #"available_runs": filter_runs(get_available_runs(), filter), #this version is probably worse cause get_available_runs returns two things, why should filter expect two things in one param "available_runs" : filtered_runs, "available_runs_favourite": filtered_runs_favourite, + "all_available_runs": all_available_runs, }, ) + + def favourite(request: HttpRequest): From 6bd2e3ecd8211d8c10ec65301e0f6b22f9b11125 Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Thu, 5 Dec 2024 15:37:31 +0000 Subject: [PATCH 20/57] Selection Header & Filter input value fixed --- ui/runs/templates/runs/index.html | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index aab0831b..dd3eae6e 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -53,7 +53,7 @@

Work on a new run:

- +
{% csrf_token %} @@ -61,18 +61,21 @@

Work on a new run:

Continue an existing run: {{ available_runs.0.run_name }}

- + + +
    +
  • + + Name + Created + Last modified + Memory mode +
  • +
+
    {% for run in all_available_runs %} -
  • +
    {% endfor %}
@@ -117,7 +121,7 @@

Continue an existing run:

{% csrf_token %} - + From 9c131d958e1c61a2f0977ae78c16fdff164f85e0 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Tue, 10 Dec 2024 17:56:52 +0100 Subject: [PATCH 21/57] Reworked index.html to only show available runs. Added new html page for creating runs. Index now has interface for filter selection (WIP) --- ui/runs/static/runs/MultiSelect.css | 150 ++++++++++++ ui/runs/static/runs/MultiSelect.js | 255 ++++++++++++++++++++ ui/runs/static/runs/index.css | 5 +- ui/runs/static/runs/index.js | 1 + ui/runs/templates/runs/create_run_menu.html | 40 +++ ui/runs/templates/runs/index.html | 155 +++++++++--- ui/runs/templates/runs/index2.html | 144 +++++++++++ ui/runs/urls.py | 1 + ui/runs/views.py | 30 ++- 9 files changed, 740 insertions(+), 41 deletions(-) create mode 100644 ui/runs/static/runs/MultiSelect.css create mode 100644 ui/runs/static/runs/MultiSelect.js create mode 100644 ui/runs/templates/runs/create_run_menu.html create mode 100644 ui/runs/templates/runs/index2.html diff --git a/ui/runs/static/runs/MultiSelect.css b/ui/runs/static/runs/MultiSelect.css new file mode 100644 index 00000000..944a8ef3 --- /dev/null +++ b/ui/runs/static/runs/MultiSelect.css @@ -0,0 +1,150 @@ +.multi-select { + display: flex; + box-sizing: border-box; + flex-direction: column; + position: relative; + width: 100%; + user-select: none; + } + .multi-select .multi-select-header { + border: 1px solid #dee2e6; + padding: 7px 30px 7px 12px; + overflow: hidden; + gap: 7px; + min-height: 45px; + } + .multi-select .multi-select-header::after { + content: ""; + display: block; + position: absolute; + top: 50%; + right: 15px; + transform: translateY(-50%); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23949ba3' viewBox='0 0 16 16'%3E%3Cpath d='M8 13.1l-8-8 2.1-2.2 5.9 5.9 5.9-5.9 2.1 2.2z'/%3E%3C/svg%3E"); + height: 12px; + width: 12px; + } + .multi-select .multi-select-header.multi-select-header-active { + border-color: #c1c9d0; + } + .multi-select .multi-select-header.multi-select-header-active::after { + transform: translateY(-50%) rotate(180deg); + } + .multi-select .multi-select-header.multi-select-header-active + .multi-select-options { + display: flex; + } + .multi-select .multi-select-header .multi-select-header-placeholder { + color: #65727e; + } + .multi-select .multi-select-header .multi-select-header-option { + display: inline-flex; + align-items: center; + background-color: #f3f4f7; + font-size: 14px; + padding: 3px 8px; + border-radius: 5px; + } + .multi-select .multi-select-header .multi-select-header-max { + font-size: 14px; + color: #65727e; + } + .multi-select .multi-select-options { + display: none; + box-sizing: border-box; + flex-flow: wrap; + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 999; + margin-top: 5px; + padding: 5px; + background-color: #fff; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; + } + .multi-select .multi-select-options::-webkit-scrollbar { + width: 5px; + } + .multi-select .multi-select-options::-webkit-scrollbar-track { + background: #f0f1f3; + } + .multi-select .multi-select-options::-webkit-scrollbar-thumb { + background: #cdcfd1; + } + .multi-select .multi-select-options::-webkit-scrollbar-thumb:hover { + background: #b2b6b9; + } + .multi-select .multi-select-options .multi-select-option, .multi-select .multi-select-options .multi-select-all { + padding: 4px 12px; + height: 42px; + } + .multi-select .multi-select-options .multi-select-option .multi-select-option-radio, .multi-select .multi-select-options .multi-select-all .multi-select-option-radio { + margin-right: 14px; + height: 16px; + width: 16px; + border: 1px solid #ced4da; + border-radius: 4px; + } + .multi-select .multi-select-options .multi-select-option .multi-select-option-text, .multi-select .multi-select-options .multi-select-all .multi-select-option-text { + box-sizing: border-box; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: inherit; + font-size: 16px; + line-height: 20px; + } + .multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-radio, .multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-radio { + border-color: #40c979; + background-color: #40c979; + } + .multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-radio::after, .multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-radio::after { + content: ""; + display: block; + width: 3px; + height: 7px; + margin: 0.12em 0 0 0.27em; + border: solid #fff; + border-width: 0 0.15em 0.15em 0; + transform: rotate(45deg); + } + .multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-text, .multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-text { + color: #40c979; + } + .multi-select .multi-select-options .multi-select-option:hover, .multi-select .multi-select-options .multi-select-option:active, .multi-select .multi-select-options .multi-select-all:hover, .multi-select .multi-select-options .multi-select-all:active { + background-color: #f3f4f7; + } + .multi-select .multi-select-options .multi-select-all { + border-bottom: 1px solid #f1f3f5; + border-radius: 0; + } + .multi-select .multi-select-options .multi-select-search { + padding: 7px 10px; + border: 1px solid #dee2e6; + border-radius: 5px; + margin: 10px 10px 5px 10px; + width: 100%; + outline: none; + font-size: 16px; + } + .multi-select .multi-select-options .multi-select-search::placeholder { + color: #b2b5b9; + } + .multi-select .multi-select-header, .multi-select .multi-select-option, .multi-select .multi-select-all { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: center; + border-radius: 5px; + cursor: pointer; + display: flex; + align-items: center; + width: 100%; + font-size: 16px; + color: #212529; + } \ No newline at end of file diff --git a/ui/runs/static/runs/MultiSelect.js b/ui/runs/static/runs/MultiSelect.js new file mode 100644 index 00000000..f1332750 --- /dev/null +++ b/ui/runs/static/runs/MultiSelect.js @@ -0,0 +1,255 @@ + +class MultiSelect { + + constructor(element, options = {}) { + let defaults = { + placeholder: 'Select item(s)', + max: null, + search: true, + selectAll: true, + listAll: true, + closeListOnItemSelect: false, + name: '', + width: '', + height: '', + dropdownWidth: '', + dropdownHeight: '', + data: [], + onChange: function() {}, + onSelect: function() {}, + onUnselect: function() {} + }; + this.options = Object.assign(defaults, options); + this.selectElement = typeof element === 'string' ? document.querySelector(element) : element; + for(const prop in this.selectElement.dataset) { + if (this.options[prop] !== undefined) { + this.options[prop] = this.selectElement.dataset[prop]; + } + } + this.name = this.selectElement.getAttribute('name') ? this.selectElement.getAttribute('name') : 'multi-select-' + Math.floor(Math.random() * 1000000); + if (!this.options.data.length) { + let options = this.selectElement.querySelectorAll('option'); + for (let i = 0; i < options.length; i++) { + this.options.data.push({ + value: options[i].value, + text: options[i].innerHTML, + selected: options[i].selected, + html: options[i].getAttribute('data-html') + }); + } + } + this.element = this._template(); + this.selectElement.replaceWith(this.element); + this._updateSelected(); + this._eventHandlers(); + } + + _template() { + let optionsHTML = ''; + for (let i = 0; i < this.data.length; i++) { + optionsHTML += ` +
+ + ${this.data[i].html ? this.data[i].html : this.data[i].text} +
+ `; + } + let selectAllHTML = ''; + if (this.options.selectAll === true || this.options.selectAll === 'true') { + selectAllHTML = `
+ + Select all +
`; + } + let template = ` +
+ ${this.selectedValues.map(value => ``).join('')} +
+ ${this.options.max ? this.selectedValues.length + '/' + this.options.max : ''} + ${this.placeholder} +
+
+ ${this.options.search === true || this.options.search === 'true' ? '' : ''} + ${selectAllHTML} + ${optionsHTML} +
+
+ `; + let element = document.createElement('div'); + element.innerHTML = template; + return element; + } + + _eventHandlers() { + let headerElement = this.element.querySelector('.multi-select-header'); + this.element.querySelectorAll('.multi-select-option').forEach(option => { + option.onclick = () => { + let selected = true; + if (!option.classList.contains('multi-select-selected')) { + if (this.options.max && this.selectedValues.length >= this.options.max) { + return; + } + option.classList.add('multi-select-selected'); + if (this.options.listAll === true || this.options.listAll === 'true') { + if (this.element.querySelector('.multi-select-header-option')) { + let opt = Array.from(this.element.querySelectorAll('.multi-select-header-option')).pop(); + opt.insertAdjacentHTML('afterend', `${option.querySelector('.multi-select-option-text').innerHTML}`); + } else { + headerElement.insertAdjacentHTML('afterbegin', `${option.querySelector('.multi-select-option-text').innerHTML}`); + } + } + this.element.querySelector('.multi-select').insertAdjacentHTML('afterbegin', ``); + this.data.filter(data => data.value == option.dataset.value)[0].selected = true; + } else { + option.classList.remove('multi-select-selected'); + this.element.querySelectorAll('.multi-select-header-option').forEach(headerOption => headerOption.dataset.value == option.dataset.value ? headerOption.remove() : ''); + this.element.querySelector(`input[value="${option.dataset.value}"]`).remove(); + this.data.filter(data => data.value == option.dataset.value)[0].selected = false; + selected = false; + } + if (this.options.listAll === false || this.options.listAll === 'false') { + if (this.element.querySelector('.multi-select-header-option')) { + this.element.querySelector('.multi-select-header-option').remove(); + } + headerElement.insertAdjacentHTML('afterbegin', `${this.selectedValues.length} selected`); + } + if (!this.element.querySelector('.multi-select-header-option')) { + headerElement.insertAdjacentHTML('afterbegin', `${this.placeholder}`); + } else if (this.element.querySelector('.multi-select-header-placeholder')) { + this.element.querySelector('.multi-select-header-placeholder').remove(); + } + if (this.options.max) { + this.element.querySelector('.multi-select-header-max').innerHTML = this.selectedValues.length + '/' + this.options.max; + } + if (this.options.search === true || this.options.search === 'true') { + this.element.querySelector('.multi-select-search').value = ''; + } + this.element.querySelectorAll('.multi-select-option').forEach(option => option.style.display = 'flex'); + if (this.options.closeListOnItemSelect === true || this.options.closeListOnItemSelect === 'true') { + headerElement.classList.remove('multi-select-header-active'); + } + this.options.onChange(option.dataset.value, option.querySelector('.multi-select-option-text').innerHTML, option); + if (selected) { + this.options.onSelect(option.dataset.value, option.querySelector('.multi-select-option-text').innerHTML, option); + } else { + this.options.onUnselect(option.dataset.value, option.querySelector('.multi-select-option-text').innerHTML, option); + } + }; + }); + headerElement.onclick = () => headerElement.classList.toggle('multi-select-header-active'); + if (this.options.search === true || this.options.search === 'true') { + let search = this.element.querySelector('.multi-select-search'); + search.oninput = () => { + this.element.querySelectorAll('.multi-select-option').forEach(option => { + option.style.display = option.querySelector('.multi-select-option-text').innerHTML.toLowerCase().indexOf(search.value.toLowerCase()) > -1 ? 'flex' : 'none'; + }); + }; + } + if (this.options.selectAll === true || this.options.selectAll === 'true') { + let selectAllButton = this.element.querySelector('.multi-select-all'); + selectAllButton.onclick = () => { + let allSelected = selectAllButton.classList.contains('multi-select-selected'); + this.element.querySelectorAll('.multi-select-option').forEach(option => { + let dataItem = this.data.find(data => data.value == option.dataset.value); + if (dataItem && ((allSelected && dataItem.selected) || (!allSelected && !dataItem.selected))) { + option.click(); + } + }); + selectAllButton.classList.toggle('multi-select-selected'); + }; + } + if (this.selectElement.id && document.querySelector('label[for="' + this.selectElement.id + '"]')) { + document.querySelector('label[for="' + this.selectElement.id + '"]').onclick = () => { + headerElement.classList.toggle('multi-select-header-active'); + }; + } + document.addEventListener('click', event => { + if (!event.target.closest('.' + this.name) && !event.target.closest('label[for="' + this.selectElement.id + '"]')) { + headerElement.classList.remove('multi-select-header-active'); + } + }); + } + + _updateSelected() { + if (this.options.listAll === true || this.options.listAll === 'true') { + this.element.querySelectorAll('.multi-select-option').forEach(option => { + if (option.classList.contains('multi-select-selected')) { + this.element.querySelector('.multi-select-header').insertAdjacentHTML('afterbegin', `${option.querySelector('.multi-select-option-text').innerHTML}`); + } + }); + } else { + if (this.selectedValues.length > 0) { + this.element.querySelector('.multi-select-header').insertAdjacentHTML('afterbegin', `${this.selectedValues.length} selected`); + } + } + if (this.element.querySelector('.multi-select-header-option')) { + this.element.querySelector('.multi-select-header-placeholder').remove(); + } + } + + get selectedValues() { + return this.data.filter(data => data.selected).map(data => data.value); + } + + get selectedItems() { + return this.data.filter(data => data.selected); + } + + set data(value) { + this.options.data = value; + } + + get data() { + return this.options.data; + } + + set selectElement(value) { + this.options.selectElement = value; + } + + get selectElement() { + return this.options.selectElement; + } + + set element(value) { + this.options.element = value; + } + + get element() { + return this.options.element; + } + + set placeholder(value) { + this.options.placeholder = value; + } + + get placeholder() { + return this.options.placeholder; + } + + set name(value) { + this.options.name = value; + } + + get name() { + return this.options.name; + } + + set width(value) { + this.options.width = value; + } + + get width() { + return this.options.width; + } + + set height(value) { + this.options.height = value; + } + + get height() { + return this.options.height; + } + +} +document.querySelectorAll('[data-multi-select]').forEach(select => new MultiSelect(select)); \ No newline at end of file diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index f091b97c..8d568163 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -1,5 +1,4 @@ #contain { - height:100vh; - margin-top:-3.6rem; - + height:-100vh; + } diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 24082791..f7c4bd8e 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -25,3 +25,4 @@ function toggleFavouriteRun(runName, favouriteStatus){ document.getElementById('favourites_run_status_id').value = favouriteStatus; document.getElementById('change_favourite').submit(); }; + diff --git a/ui/runs/templates/runs/create_run_menu.html b/ui/runs/templates/runs/create_run_menu.html new file mode 100644 index 00000000..7c76addc --- /dev/null +++ b/ui/runs/templates/runs/create_run_menu.html @@ -0,0 +1,40 @@ +{% extends 'base.html' %} +{% load static %} + +{% block css %} + +{% endblock %} + +{% block js %} + +{% endblock %} + +{% block content %} +
+
+
+
+ {% csrf_token %} +
+

Work on a new run:

+ +
+
+ + +
+
+ + +
+ +
+
+{% endblock %} diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index dd3eae6e..038ff3dd 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -4,14 +4,18 @@ {% block css %} + {% endblock %} {% block js %} + {% endblock %} {% block content %} -
+ +
+
{% if messages %}
@@ -26,40 +30,20 @@

{% endif %} -
-
-
+
+
+
+ +
+ {% csrf_token %} -
-

Work on a new run:

- -
-
- - -
-
- - -
- + -
- - -
{% csrf_token %}

Continue an existing run: - {{ available_runs.0.run_name }} + {{ all_available_runs.0.run_name }}

@@ -74,7 +58,7 @@

Continue an existing run: -
    +
      {% for run in all_available_runs %}

- +
+
+ {% csrf_token %} +
+

Select filters:

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
-
- {% csrf_token %} - - -
+ +
+
+
+ + +
+ + {% endblock %} diff --git a/ui/runs/templates/runs/index2.html b/ui/runs/templates/runs/index2.html new file mode 100644 index 00000000..0892433f --- /dev/null +++ b/ui/runs/templates/runs/index2.html @@ -0,0 +1,144 @@ + +{% extends 'base.html' %} +{% load static %} + +{% block css %} + +{% endblock %} + +{% block js %} + +{% endblock %} + +{% block content %} +
+
+
+ {% if messages %} +
+ {% for message in messages %} + + {% endfor %} +
+
+ {% endif %} +
+
+
+
+
+
+ {% csrf_token %} +
+

Work on a new run:

+ +
+
+ + +
+
+ + +
+ +
+
+ + +
+
+ {% csrf_token %} +
+

Continue an existing run: + {{ all_available_runs.0.run_name }} +

+ + +
    +
  • + + Name + Created + Last modified + Memory mode +
  • +
+ + +
    + {% for run in all_available_runs %} +
  • + + {{ run.run_name }} + {{ run.creation_date }} + {{ run.modification_date }} + {{ run.memory_mode }} + +
  • +
    + {% endfor %} +
+ +
+ + +
+
+ {% csrf_token %} + +
+
+ {% csrf_token %} + + +
+ Manage databases +
+
+
+

Continue an existing run: +

+
+
+ +
+ +
+
+
+ {% csrf_token %} + + +
+
+
+
+ +{% endblock %} diff --git a/ui/runs/urls.py b/ui/runs/urls.py index 3d490a43..4d073f8f 100644 --- a/ui/runs/urls.py +++ b/ui/runs/urls.py @@ -6,6 +6,7 @@ urlpatterns = [ path("", views.index, name="index"), # path("filter", views.filtered_index, name="filter"), + path("create_run_menu", views.create_run_menu, name="create_run_menu"), path("create", views.create, name="create"), #create, continue and delete paths should be in main/urls.py, also change index.html accordingly path("continue", views.continue_, name="continue"), path("delete", views.delete_, name="delete"), diff --git a/ui/runs/views.py b/ui/runs/views.py index 480eacfa..bde19941 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -209,7 +209,7 @@ def index(request: HttpRequest, index_error: bool = False): #should replace inde request, "runs/index.html", context={ - "available_workflows": get_available_workflow_names(), + #"available_workflows": get_available_workflow_names(), #"available_runs": filter_runs(get_available_runs(), filter), #this version is probably worse cause get_available_runs returns two things, why should filter expect two things in one param "available_runs" : filtered_runs, "available_runs_favourite": filtered_runs_favourite, @@ -257,6 +257,26 @@ def tag(request: HttpRequest): return HttpResponseRedirect(reverse("runs:index")) +def create_run_menu(request: HttpRequest): + """ + Continues an existing run. The user is redirected to the detail page of the run and + can resume working on the run. + + :param request: the request object + :type request: HttpRequest + + :return: the rendered details page of the run + :rtype: HttpResponse + """ + + return render( + request, + "runs/create_run_menu.html", + context={ + "available_workflows": get_available_workflow_names(), + }, + ) + def create(request: HttpRequest): """ @@ -324,6 +344,14 @@ def delete_(request: HttpRequest): try: delete_run_folder(run_name) + display_message( + { + "level": 40, + "msg": f"Couldn't delete the run '{run_name}' . Please check the permissions for this file or try running Protzilla as administrator.", + "trace": "hihihihihhahahaho", + }, + request, + ) except Exception as e: display_message( { From f6aa54e51c8d80efb225c251f0a3bf6594772000 Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Thu, 12 Dec 2024 08:48:55 +0100 Subject: [PATCH 22/57] Header fixed, Button hover effect, first run always selected, deselect runs --- ui/runs/static/runs/index.js | 41 ++++++++++- ui/runs/templates/runs/index.html | 109 ++++++++++++++++-------------- 2 files changed, 99 insertions(+), 51 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index f7c4bd8e..4f3161b0 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -1,9 +1,46 @@ +// === EVENT LISTENER === + +document.addEventListener('DOMContentLoaded', () => { + // Delete Btns + const delete_btns = document.querySelectorAll('.delete-btn'); + + delete_btns.forEach(button => { + const svg = button.querySelector('svg'); + + button.addEventListener('mouseover', () => { + button.classList.add('btn-red'); + button.classList.remove('btn-grey'); + if (svg) svg.setAttribute('fill', 'white'); + }); + + button.addEventListener('mouseout', () => { + button.classList.add('btn-grey'); + button.classList.remove('btn-red'); + if (svg) svg.setAttribute('fill', 'grey'); + }); + }); + + // mark first run as selected + let first_run = document.getElementById('run-1'); + first_run.style.backgroundColor = '#e8edf3'; + first_run.style.borderRadius = '10px'; + +}); + + +// === FUNCTIONS === function selectRun(runName, runId, numberOfRuns){ for (let i = 1; i <= numberOfRuns; i++) { let run = document.getElementById('run-' + i); if (i == runId) { - run.style.backgroundColor = '#e8edf3'; - run.style.borderRadius = '10px'; + if (run.style.backgroundColor == 'rgb(232, 237, 243)') { + run.style.backgroundColor = 'white'; + } + else { + run.style.backgroundColor = 'rgb(232, 237, 243)'; + run.style.borderRadius = '10px'; + } + } else { run.style.backgroundColor = 'white'; diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 038ff3dd..7d4e21b3 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -9,7 +9,6 @@ {% block js %} - {% endblock %} {% block content %} @@ -48,18 +47,31 @@

Continue an existing run:
    -
  • - - Name - Created - Last modified - Memory mode +
  • + + + + Name + Created + Last modified + Memory mode + +
    {% for run in all_available_runs %} +
  • -
    {% endfor %}
@@ -134,7 +145,7 @@

Select filters:

- +
@@ -168,43 +179,43 @@

Select filters:

+ {% endblock %} From 397d70d12b84f80edfcfe5ae2d0286bcbc2c6592 Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Thu, 12 Dec 2024 09:16:23 +0100 Subject: [PATCH 23/57] Minor visual changes (text in rows centered; thicker header line, etc.) --- ui/runs/static/runs/index.css | 25 +++++++++++++++++++++++++ ui/runs/static/runs/index.js | 10 ++-------- ui/runs/templates/runs/index.html | 8 ++++++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index 8d568163..f3974efa 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -2,3 +2,28 @@ height:-100vh; } + +/* === RUN SELECTION === */ +ul { + margin: 0; + padding: 0; +} + +ul li { + display: flex; + justify-content: space-between; + align-items: center; + height: 40px; + padding: 0 10px; +} + +.header-list li{ + margin-top: 20px; +} + +/* Continue btn postioning */ + +input[value="Continue"] { + margin-top: 20px; + float: right; +} \ No newline at end of file diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 4f3161b0..9f3f20db 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -33,14 +33,8 @@ function selectRun(runName, runId, numberOfRuns){ for (let i = 1; i <= numberOfRuns; i++) { let run = document.getElementById('run-' + i); if (i == runId) { - if (run.style.backgroundColor == 'rgb(232, 237, 243)') { - run.style.backgroundColor = 'white'; - } - else { - run.style.backgroundColor = 'rgb(232, 237, 243)'; - run.style.borderRadius = '10px'; - } - + run.style.backgroundColor = 'rgb(232, 237, 243)'; + run.style.borderRadius = '10px'; } else { run.style.backgroundColor = 'white'; diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 7d4e21b3..73ebd314 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -45,7 +45,7 @@

Continue an existing run: {{ all_available_runs.0.run_name }}

- +
  • @@ -71,7 +71,11 @@

    Continue an existing run:
      {% for run in all_available_runs %} -
      + {% if forloop.counter == 1 %} +
      + {% else %} +
      + {% endif %}
    • - {{ run.run_name }} - {{ run.creation_date }} - {{ run.modification_date }} - {{ run.memory_mode }} - +
    • +
      + + {{ run.run_name }} + {{ run.creation_date }} + {{ run.modification_date }} + {{ run.memory_mode }} + +
      +
      + + Tags: {{run.tags}} +

      Description:

      + +
      +
    • {% endfor %}

-
{% csrf_token %} @@ -158,7 +166,7 @@

Select filters:

- + ---> {% endblock %} diff --git a/ui/runs/views_helper.py b/ui/runs/views_helper.py index fcb76563..3c827bd8 100644 --- a/ui/runs/views_helper.py +++ b/ui/runs/views_helper.py @@ -131,6 +131,14 @@ def filter_for_memory_mode(runs, memory_mode) -> list[dict[str, str | list[str]] return [run for run in runs if run["memory_mode"] == memory_mode] def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: + """ + Filters runs according to the given filters. + + :param runs: List of runs. + :param filters: List of filters. + + :return: List of runs. + """ if filters["name"]: runs = filter_for_name(runs, filters["name"]) if filters["steps"]: From fc7496309385b2702511a858fe95b7ee2c91535b Mon Sep 17 00:00:00 2001 From: sarah Date: Thu, 19 Dec 2024 13:33:00 +0100 Subject: [PATCH 44/57] merge chaos --- ui/runs/templates/runs/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 37dac96b..62af40fd 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -131,7 +131,6 @@

Continue an existing run: {% csrf_token %} -
{% csrf_token %} From 0b7ee185ea9908e80296cb69ab086005b3c861c9 Mon Sep 17 00:00:00 2001 From: sarah Date: Sun, 5 Jan 2025 17:49:15 +0100 Subject: [PATCH 45/57] changed button positioning --- ui/runs/static/runs/index.css | 6 ++++++ ui/runs/templates/runs/index.html | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index 1abf9d46..fe7d002f 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -89,3 +89,9 @@ input[value="Continue"] { white-space: nowrap; } +.button-container { + display: flex; + justify-content: space-evenly; + gap: 10px; + margin-top: 10px; +} diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 62af40fd..0806d089 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -181,8 +181,10 @@

Select filters:

- - +
+ + +

From a1e01349154bd1f4ec3b7c20239f6d92e1c54ded Mon Sep 17 00:00:00 2001 From: sarah Date: Sun, 5 Jan 2025 18:31:55 +0100 Subject: [PATCH 46/57] adapted memory mode filter to other selection style --- ui/runs/static/runs/index.js | 11 +++++++++-- ui/runs/templates/runs/index.html | 11 +++++++---- ui/runs/views.py | 3 ++- ui/runs/views_helper.py | 5 +++-- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 57a9f349..e3863d59 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -102,19 +102,26 @@ function clearFilters() { // Clear text inputs form.querySelectorAll('input[type="text"]').forEach(input => input.value = ''); - // For search_steps + // Clear search_steps const searchStepsMultiSelect = new MultiSelect(document.getElementById('search_steps')); searchStepsMultiSelect.data.forEach(option => { option.selected = false; // Mark as unselected }); searchStepsMultiSelect._updateSelected(); - // For search_tags + // Clear search_tags const searchTagsMultiSelect = new MultiSelect(document.getElementById('search_tags')); searchTagsMultiSelect.data.forEach(option => { option.selected = false; // Mark as unselected }); searchTagsMultiSelect._updateSelected(); + // Clear df_mode + const dfModeMultiSelect = new MultiSelect(document.getElementById('df_mode')); + dfModeMultiSelect.data.forEach(option => { + option.selected = false; // Mark as unselected + }); + searchTagsMultiSelect._updateSelected(); + form.submit(); } \ No newline at end of file diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 0806d089..58e11439 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -146,8 +146,8 @@

Continue an existing run:
{% csrf_token %} -

Select filters:

+
@@ -173,9 +173,12 @@

Select filters:

- + {% for mode in all_memory_modes %} + + {% endfor %}
diff --git a/ui/runs/views.py b/ui/runs/views.py index e5f37506..fd4b0484 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -178,7 +178,7 @@ def index(request: HttpRequest, index_error: bool = False): filter_run_name = request.POST.get("search_run_name", "") filter_steps = request.POST.getlist("search_steps[]", []) #not search_steps beacuse the multi-select overrides the way the values are stored in the
+
+ {% csrf_token %} +

Sort entries:

+
+ + +
+ +
diff --git a/ui/runs/views.py b/ui/runs/views.py index fd4b0484..73aec4e0 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -47,8 +47,15 @@ get_filled_form_by_method, get_filled_form_by_request, ) +from .views_helper import sort_runs active_runs: dict[str, Run] = {} +SORTING_METHODS = { + "alphabetically A-Z": (lambda run: run["run_name"].lower(), False), + "alphabetically Z-A": (lambda run: run["run_name"].lower(), True), + "by date created": (lambda run: run["creation_date"], False), + "by date modified": (lambda run: run["modification_date"], False), +} def detail(request: HttpRequest, run_name: str): @@ -189,8 +196,13 @@ def index(request: HttpRequest, index_error: bool = False): runs, runs_favourite, all_tags = get_available_runinfo() filtered_runs = filter_runs(runs, filter) filtered_runs_favourite = filter_runs(runs_favourite, filter) - all_available_runs = filtered_runs_favourite + filtered_runs + sort_methods = request.POST.getlist("sort_methods[]", []) + # TODO sorting method! + sorted_runs = sort_runs(filtered_runs, sort_methods, SORTING_METHODS) + sorted_runs_favorite = sort_runs(filtered_runs_favourite, sort_methods, SORTING_METHODS) + + all_available_runs = sorted_runs_favorite + sorted_runs return render( request, @@ -206,6 +218,7 @@ def index(request: HttpRequest, index_error: bool = False): "all_possible_step_names": get_all_possible_step_names(), "all_tags": all_tags, "all_memory_modes": ["standard", "low memory"], + "all_sorting_methods": SORTING_METHODS.keys(), }, ) diff --git a/ui/runs/views_helper.py b/ui/runs/views_helper.py index df26b349..0e0247f5 100644 --- a/ui/runs/views_helper.py +++ b/ui/runs/views_helper.py @@ -7,6 +7,8 @@ from protzilla.utilities import name_to_title from ui.runs.utilities.alert import build_trace_alert +from typing import Callable + def parameters_from_post(post): d = dict(post) @@ -150,6 +152,13 @@ def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: runs = filter_for_memory_mode(runs, filters["memory_mode"]) return runs +def sort_runs(runs: list[dict[str, str | list[str]]], sorting_methods: list[str], all_sorting_methods: dict[str, tuple[Callable[[dict[str, str | list[str]]], str], bool]]): + for method in sorting_methods: + if method in all_sorting_methods: + key_func, reverse = all_sorting_methods[method] # Extract the key function and reverse flag + runs = sorted(runs, key=key_func, reverse=reverse) + return runs + def display_message(message: dict, request): """ Displays a message in the frontend. From 527c6e3b746ed4c7fef7a93c9cda49c296fd3a6f Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Mon, 6 Jan 2025 10:54:45 +0100 Subject: [PATCH 48/57] "multiple runs selected bug" fixed & run stays selected when reloading the page --- ui/runs/static/runs/index.css | 1 - ui/runs/static/runs/index.js | 17 ++++++++++------- ui/runs/templates/runs/index.html | 5 +++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index fe7d002f..6789ce65 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -66,7 +66,6 @@ input[value="Continue"] { .list-item.expanded { background-color: #f5f5f5; - border-left: 4px solid #bc3838; margin-bottom: 10px; padding-bottom: 10px; } diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index e3863d59..1959633f 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -20,16 +20,16 @@ document.addEventListener('DOMContentLoaded', () => { }); }); - // mark first run as selected - let first_run = document.getElementById('run-1'); - first_run.style.backgroundColor = '#e8edf3'; - first_run.style.borderRadius = '10px'; - + // clear filter const clearButton = document.getElementById('clearFilters'); if (clearButton) { clearButton.addEventListener('click', clearFilters); } - + + //show details of selected run, when reloading + let selected_run = document.getElementById(localStorage.getItem("selected_run")); + selected_run.click(); + }); @@ -80,6 +80,7 @@ function toggleDetails(element) { if (item !== element) { item.classList.remove('expanded'); item.querySelector('.details').style.display = 'none'; + item.style.height = "40px"; } }); @@ -91,8 +92,10 @@ function toggleDetails(element) { } else { element.classList.add('expanded'); details.style.display = 'block'; - element.style.height = "250px"; + element.style.height = "250px"; } + + localStorage.setItem("selected_run", element.getAttribute('id')); } function clearFilters() { diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 58e11439..a3ce9257 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -99,6 +99,8 @@

Continue an existing run: + +
Tags:
@@ -111,9 +113,8 @@

Continue an existing run: {% endfor %} - +

-

Description:

From e6e598886c6d2e9d1de1ba71c13701198afa79bb Mon Sep 17 00:00:00 2001 From: maximilianKalff Date: Mon, 6 Jan 2025 11:02:14 +0100 Subject: [PATCH 49/57] bug fixed, caused by previous bug fix :)) --- ui/runs/static/runs/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 1959633f..9add865d 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -27,8 +27,10 @@ document.addEventListener('DOMContentLoaded', () => { } //show details of selected run, when reloading - let selected_run = document.getElementById(localStorage.getItem("selected_run")); - selected_run.click(); + if (localStorage.getItem("selected_run") != null) { + let selected_run = document.getElementById(localStorage.getItem("selected_run")); + selected_run.click(); + } }); @@ -89,13 +91,14 @@ function toggleDetails(element) { element.classList.remove('expanded'); details.style.display = 'none'; element.style.height = "40px"; + localStorage.setItem("selected_run", null); + } else { element.classList.add('expanded'); details.style.display = 'block'; element.style.height = "250px"; + localStorage.setItem("selected_run", element.getAttribute('id')); } - - localStorage.setItem("selected_run", element.getAttribute('id')); } function clearFilters() { From 03594cf00ddb066117552fc558c2db00a1f0dd69 Mon Sep 17 00:00:00 2001 From: sarah Date: Wed, 8 Jan 2025 12:52:36 +0100 Subject: [PATCH 50/57] bugfix when adding first tag fixed --- protzilla/run.py | 2 +- ui/runs/views.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/protzilla/run.py b/protzilla/run.py index 51bd161b..de99a9b6 100644 --- a/protzilla/run.py +++ b/protzilla/run.py @@ -57,7 +57,7 @@ def get_available_runinfo() -> tuple[list[dict[str, str | list[str]]], list[dict tags = metadata.get("tags", set()) favourite = metadata.get("favourite", False) - for tag in tags: + for tag in tags: all_tags.add(tag) run = { diff --git a/ui/runs/views.py b/ui/runs/views.py index 73aec4e0..0dd6e7e8 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -278,7 +278,8 @@ def add_tag(request: HttpRequest): else: metadata = yaml_operator.read(metadata_yaml_path) tags_from_metadata = metadata.get("tags") - tags.update(tags_from_metadata) + if tags_from_metadata: + tags.update(tags_from_metadata) tags.add(run_tag) metadata["tags"]= tags yaml_operator.write(Path(metadata_yaml_path), metadata) From fa0e73adb07ce533bd0872c25944763a8271cd7d Mon Sep 17 00:00:00 2001 From: sarah Date: Wed, 8 Jan 2025 14:14:08 +0100 Subject: [PATCH 51/57] sorting in columns WIP --- ui/runs/static/runs/index.js | 32 ++++++++++++++++++++++++++++++- ui/runs/templates/runs/index.html | 30 ++++++++++++++--------------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 9add865d..ca1de3f1 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -130,4 +130,34 @@ function clearFilters() { searchTagsMultiSelect._updateSelected(); form.submit(); -} \ No newline at end of file +} + +function sortList(columnIndex, order) { + // Select the list items (excluding the header) + const list = document.querySelector('#run_selection'); + const items = Array.from(list.querySelectorAll('li')).filter(item => item !== list.querySelector('li')); // Exclude the header + + // Sort the items based on the content of the 'columnIndex' (0 for 'Name') + items.sort((a, b) => { + const textA = a.children[columnIndex].innerText.trim().toLowerCase(); // Get text of the target span (e.g., Name) + const textB = b.children[columnIndex].innerText.trim().toLowerCase(); // Get text of the target span (e.g., Name) + + if (order === 'asc') { + return textA.localeCompare(textB); // Sort A-Z + } else { + return textB.localeCompare(textA); // Sort Z-A + } + }); + + // Re-insert the sorted items back into the list + items.forEach(item => list.appendChild(item)); +} + +// Event listeners for sorting buttons +document.querySelector('button[onclick="sortList(0, \'asc\' )"]').addEventListener('click', function() { + sortList(0, 'asc'); // Sorting by Name (A-Z) +}); + +document.querySelector('button[onclick="sortList(0, \'desc\' )"]').addEventListener('click', function() { + sortList(0, 'desc'); // Sorting by Name (Z-A) +}); diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index d497a970..f33d071c 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -38,13 +38,9 @@ {% csrf_token %} -
- {% csrf_token %} -
-

Continue an existing run: - -

- +

Continue an existing run: + +

  • @@ -55,7 +51,11 @@

    Continue an existing run: - Name + + Name + + + Created Last modified Memory mode @@ -67,16 +67,16 @@

    Continue an existing run:

+
-
    + + {% csrf_token %} +
    +
      {% for run in all_available_runs %} - {% if forloop.counter == 1 %} -
      - {% else %} -
      - {% endif %}
    • +
    - +
    {% endfor %}
From f9f8b308f9f346cdacec2f55b1cf96ff3b9e034f Mon Sep 17 00:00:00 2001 From: sarah Date: Thu, 9 Jan 2025 16:53:45 +0100 Subject: [PATCH 52/57] implemented sorting by clicking on column header --- ui/runs/static/runs/index.js | 32 +----- ui/runs/templates/runs/index.html | 155 ++++++++++++++++-------------- ui/runs/views.py | 18 +--- ui/runs/views_helper.py | 28 +++++- 4 files changed, 111 insertions(+), 122 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index ca1de3f1..9add865d 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -130,34 +130,4 @@ function clearFilters() { searchTagsMultiSelect._updateSelected(); form.submit(); -} - -function sortList(columnIndex, order) { - // Select the list items (excluding the header) - const list = document.querySelector('#run_selection'); - const items = Array.from(list.querySelectorAll('li')).filter(item => item !== list.querySelector('li')); // Exclude the header - - // Sort the items based on the content of the 'columnIndex' (0 for 'Name') - items.sort((a, b) => { - const textA = a.children[columnIndex].innerText.trim().toLowerCase(); // Get text of the target span (e.g., Name) - const textB = b.children[columnIndex].innerText.trim().toLowerCase(); // Get text of the target span (e.g., Name) - - if (order === 'asc') { - return textA.localeCompare(textB); // Sort A-Z - } else { - return textB.localeCompare(textA); // Sort Z-A - } - }); - - // Re-insert the sorted items back into the list - items.forEach(item => list.appendChild(item)); -} - -// Event listeners for sorting buttons -document.querySelector('button[onclick="sortList(0, \'asc\' )"]').addEventListener('click', function() { - sortList(0, 'asc'); // Sorting by Name (A-Z) -}); - -document.querySelector('button[onclick="sortList(0, \'desc\' )"]').addEventListener('click', function() { - sortList(0, 'desc'); // Sorting by Name (Z-A) -}); +} \ No newline at end of file diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index f33d071c..ab40de06 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -42,8 +42,12 @@

Continue an existing run:

-
    -
  • + + {% csrf_token %} +
      +
    • + + - - Name - - + + + + + + + + + - Created - Last modified - Memory mode - + + + + + + + + + + + + + + +
    • -
    +
+ + + +
@@ -190,67 +254,12 @@

Select filters:

-
- {% csrf_token %} -

Sort entries:

-
- - -
- -
- -{% endblock %} + + +{% endblock %} \ No newline at end of file diff --git a/ui/runs/views.py b/ui/runs/views.py index 0dd6e7e8..1da4bdc4 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -50,13 +50,6 @@ from .views_helper import sort_runs active_runs: dict[str, Run] = {} -SORTING_METHODS = { - "alphabetically A-Z": (lambda run: run["run_name"].lower(), False), - "alphabetically Z-A": (lambda run: run["run_name"].lower(), True), - "by date created": (lambda run: run["creation_date"], False), - "by date modified": (lambda run: run["modification_date"], False), -} - def detail(request: HttpRequest, run_name: str): """ @@ -195,12 +188,11 @@ def index(request: HttpRequest, index_error: bool = False): runs, runs_favourite, all_tags = get_available_runinfo() filtered_runs = filter_runs(runs, filter) - filtered_runs_favourite = filter_runs(runs_favourite, filter) + filtered_runs_favourite = filter_runs(runs_favourite, filter) - sort_methods = request.POST.getlist("sort_methods[]", []) - # TODO sorting method! - sorted_runs = sort_runs(filtered_runs, sort_methods, SORTING_METHODS) - sorted_runs_favorite = sort_runs(filtered_runs_favourite, sort_methods, SORTING_METHODS) + sort_by_click_method = request.POST.get("sort_by", "") + sorted_runs = sort_runs(filtered_runs, sort_by_click_method) + sorted_runs_favorite = sort_runs(filtered_runs_favourite, sort_by_click_method) all_available_runs = sorted_runs_favorite + sorted_runs @@ -218,7 +210,7 @@ def index(request: HttpRequest, index_error: bool = False): "all_possible_step_names": get_all_possible_step_names(), "all_tags": all_tags, "all_memory_modes": ["standard", "low memory"], - "all_sorting_methods": SORTING_METHODS.keys(), + "current_sort": sort_by_click_method, }, ) diff --git a/ui/runs/views_helper.py b/ui/runs/views_helper.py index 0e0247f5..32f82b85 100644 --- a/ui/runs/views_helper.py +++ b/ui/runs/views_helper.py @@ -9,6 +9,17 @@ from typing import Callable +SORTING_METHODS = { + "name_asc": (lambda run: run["run_name"].lower(), False), + "name_desc": (lambda run: run["run_name"].lower(), True), + "created_asc": (lambda run: run["creation_date"], False), + "created_desc": (lambda run: run["creation_date"], True), + "last_modified_asc": (lambda run: run["modification_date"], False), + "last_modified_desc": (lambda run: run["modification_date"], True), + "memory_mode_asc": (lambda run: run["memory_mode"].lower(), False), + "memory_mode_desc": (lambda run: run["memory_mode"].lower(), True), +} + def parameters_from_post(post): d = dict(post) @@ -152,11 +163,18 @@ def filter_runs(runs, filters) -> list[dict[str, str | list[str]]]: runs = filter_for_memory_mode(runs, filters["memory_mode"]) return runs -def sort_runs(runs: list[dict[str, str | list[str]]], sorting_methods: list[str], all_sorting_methods: dict[str, tuple[Callable[[dict[str, str | list[str]]], str], bool]]): - for method in sorting_methods: - if method in all_sorting_methods: - key_func, reverse = all_sorting_methods[method] # Extract the key function and reverse flag - runs = sorted(runs, key=key_func, reverse=reverse) +def sort_runs(runs: list[dict[str, str | list[str]]], sorting_method: str): + """ + Filters runs according to the given sorting method. Favorite and normal are handled seperately at the moment. + + :param runs: List of runs. + :param sorting_method: One method as in dictionary SORTING_METHODS. + + :return: List of runs. + """ + if sorting_method in SORTING_METHODS: + key_func, reverse = SORTING_METHODS[sorting_method] # Extract the key function and reverse flag + runs = sorted(runs, key=key_func, reverse=reverse) return runs def display_message(message: dict, request): From 3b40f60246a26fa14d42012b660ac4c4df8af099 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Wed, 15 Jan 2025 14:25:13 +0100 Subject: [PATCH 53/57] swapped continue and delete button --- ui/runs/templates/runs/index.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index ab40de06..7072d82f 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -116,14 +116,14 @@

Continue an existing run: - + @@ -157,11 +157,7 @@

Continue an existing run: {{ run.creation_date }} {{ run.modification_date }} {{ run.memory_mode }} - + @@ -178,8 +174,12 @@

Continue an existing run: {% endfor %} + -
From 01340a067e04890c8aaab142075d94d7e88bccf2 Mon Sep 17 00:00:00 2001 From: sarah Date: Wed, 15 Jan 2025 14:34:07 +0100 Subject: [PATCH 54/57] changed lines between list items --- ui/runs/templates/runs/index.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 7072d82f..5e0e7999 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -131,16 +131,18 @@

Continue an existing run: -
-
{% csrf_token %}
    {% for run in all_available_runs %} + {% if forloop.counter == 1 %} +
    + {% else %} +
    + {% endif %}
  • -
-
{% endfor %} From edb1bbb6a532d0b8d77fe2c3b8faa25f73c40d34 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Wed, 15 Jan 2025 14:49:37 +0100 Subject: [PATCH 55/57] removed margin from continue button --- ui/runs/static/runs/index.css | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/runs/static/runs/index.css b/ui/runs/static/runs/index.css index 6789ce65..030b609f 100644 --- a/ui/runs/static/runs/index.css +++ b/ui/runs/static/runs/index.css @@ -31,7 +31,6 @@ ul li div { /* Continue btn postioning */ input[value="Continue"] { - margin-top: 20px; float: right; } From ac812503d10468a339381060ed721b1685bb1710 Mon Sep 17 00:00:00 2001 From: gitjannes Date: Wed, 15 Jan 2025 15:02:14 +0100 Subject: [PATCH 56/57] made continue button color-changing --- ui/runs/static/runs/index.js | 4 ++-- ui/runs/templates/runs/index.html | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js index 9add865d..a24171f6 100644 --- a/ui/runs/static/runs/index.js +++ b/ui/runs/static/runs/index.js @@ -2,9 +2,9 @@ document.addEventListener('DOMContentLoaded', () => { // Delete Btns - const delete_btns = document.querySelectorAll('.delete-btn'); + const colorchange_btns = document.querySelectorAll('.colorchange-btn'); - delete_btns.forEach(button => { + colorchange_btns.forEach(button => { const svg = button.querySelector('svg'); button.addEventListener('mouseover', () => { diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html index 5e0e7999..e55bd500 100644 --- a/ui/runs/templates/runs/index.html +++ b/ui/runs/templates/runs/index.html @@ -159,7 +159,7 @@

Continue an existing run: {{ run.creation_date }} {{ run.modification_date }} {{ run.memory_mode }} - + @@ -168,7 +168,7 @@

Continue an existing run:
{% for tag in run.run_tags %} {{ tag }} - -