diff --git a/protzilla/disk_operator.py b/protzilla/disk_operator.py
index ce7f07ad..afd343f7 100644
--- a/protzilla/disk_operator.py
+++ b/protzilla/disk_operator.py
@@ -140,6 +140,8 @@ def write_run(self, step_manager: StepManager) -> None:
run[KEYS.STEPS].append(self._write_step(step))
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.py b/protzilla/run.py
index 1f451e89..de99a9b6 100644
--- a/protzilla/run.py
+++ b/protzilla/run.py
@@ -4,9 +4,14 @@
import threading
import traceback
+import os
+import shutil
+import datetime
+
import protzilla.constants.paths as paths
from protzilla.steps import Messages, Output, Plots, Step
from protzilla.utilities import format_trace
+from protzilla.disk_operator import DiskOperator, YamlOperator
def get_available_run_names() -> list[str]:
@@ -19,6 +24,68 @@ def get_available_run_names() -> list[str]:
if not directory.name.startswith(".")
]
+def get_available_runinfo() -> tuple[list[dict[str, str | list[str]]], list[dict[str, str | list[str]]], set[str]]:
+ if not paths.RUNS_PATH.exists():
+ return []
+ runs = []
+ runs_favourited = []
+ all_tags = set()
+ for directory in paths.RUNS_PATH.iterdir(): #not sorted the same for different os?
+ if directory.name.startswith("."):
+ continue
+ name = directory.name
+ creation_time = directory.stat().st_ctime
+ modification_time = directory.stat().st_mtime
+
+ disk_operator = DiskOperator(name, "dummy_workflow_name")
+ directory_path = os.path.join(paths.RUNS_PATH, name)
+ 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)
+
+ # empty initialization to ensure backwardscompatibility for runs without metadata.yaml
+ favourite = False
+ tags = set()
+
+ 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", set())
+ favourite = metadata.get("favourite", False)
+
+ for tag in tags:
+ all_tags.add(tag)
+
+ run = {
+ "run_name": name,
+ "creation_date": datetime.datetime.fromtimestamp(creation_time).strftime("%d %m %Y"), #TODO: reutrn the pure datetime, convert in html)
+ "modification_date": datetime.datetime.fromtimestamp(modification_time).strftime("%d %m %Y"),
+ "memory_mode": step_manager.df_mode,
+ "run_steps" : step_names,
+ "favourite_status" : favourite,
+ "run_tags": tags
+ }
+
+ if favourite:
+ runs_favourited.append(run)
+ else:
+ runs.append(run)
+
+ for run in runs + runs_favourited:
+ possible_tags = list(all_tags - run["run_tags"])
+ run["addable_tags"] = possible_tags
+
+ return (runs, runs_favourited, all_tags)
+
+def delete_run_folder(run_name) -> None:
+ path = os.path.join(paths.RUNS_PATH, run_name)
+
+ if os.path.isdir(path):
+ shutil.rmtree(path)
class Run:
class ErrorHandlingContextManager:
diff --git a/protzilla/run_v2.py b/protzilla/run_v2.py
index f0499ac0..c4b6835b 100644
--- a/protzilla/run_v2.py
+++ b/protzilla/run_v2.py
@@ -4,9 +4,6 @@
import threading
import traceback
-import os
-import shutil
-
import protzilla.constants.paths as paths
from protzilla.steps import Messages, Output, Plots, Step
from protzilla.utilities import format_trace
@@ -21,12 +18,6 @@ def get_available_run_names() -> list[str]:
if not directory.name.startswith(".")
]
-def delete_run_folder(run_name) -> None:
- path = os.path.join(paths.RUNS_PATH, run_name)
-
- if os.path.isdir(path):
- shutil.rmtree(path)
-
class Run:
class ErrorHandlingContextManager:
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.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', ``);
+ } else {
+ headerElement.insertAdjacentHTML('afterbegin', ``);
+ }
+ }
+ 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', ``);
+ }
+ if (!this.element.querySelector('.multi-select-header-option')) {
+ headerElement.insertAdjacentHTML('afterbegin', ``);
+ } 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', ``);
+ }
+ });
+ } else {
+ if (this.selectedValues.length > 0) {
+ this.element.querySelector('.multi-select-header').insertAdjacentHTML('afterbegin', ``);
+ }
+ }
+ 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 a058f32c..030b609f 100644
--- a/ui/runs/static/runs/index.css
+++ b/ui/runs/static/runs/index.css
@@ -1,8 +1,95 @@
#contain {
- height:100vh;
- margin-top:-3.6rem;
+ height:-100vh;
+
+}
+
+/* === RUN SELECTION === */
+ul {
+ margin: 0;
+ padding: 0;
+}
+
+ul li div {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 40px;
+ padding: 0 10px;
+}
+
+.header-list li{
+ margin-top: 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 40px;
+ padding: 0 10px;
}
-#row {
- width:80%;
-}
\ No newline at end of file
+
+
+/* Continue btn postioning */
+
+input[value="Continue"] {
+ float: right;
+}
+
+/* Arkordeon */
+
+/* Details-Container */
+.details {
+ display: none;
+ background-color: #f9f9f9;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ height: 200px;
+ margin: 10px;
+ position: relative;
+}
+
+.list-item {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: stretch;
+}
+
+/* Hauptzeile in der Liste */
+.list-item > .main-content {
+ display: inline;
+ justify-content: space-between;
+ align-items: stretch;
+ width: 100%;
+}
+
+.list-item.expanded {
+ background-color: #f5f5f5;
+ margin-bottom: 10px;
+ padding-bottom: 10px;
+}
+
+
+.tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+ margin-top: 5px;
+}
+
+.tag {
+ display: inline-block;
+ position: relative;
+ border-radius: 5px;
+ clip-path: polygon(0 0, calc(100% - 15px) 0, 100% 50%, calc(100% - 15px) 100%, 0 100%);
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ white-space: nowrap;
+}
+
+.button-container {
+ display: flex;
+ justify-content: space-evenly;
+ gap: 10px;
+ margin-top: 10px;
+}
diff --git a/ui/runs/static/runs/index.js b/ui/runs/static/runs/index.js
new file mode 100644
index 00000000..a24171f6
--- /dev/null
+++ b/ui/runs/static/runs/index.js
@@ -0,0 +1,133 @@
+// === EVENT LISTENER ===
+
+document.addEventListener('DOMContentLoaded', () => {
+ // Delete Btns
+ const colorchange_btns = document.querySelectorAll('.colorchange-btn');
+
+ colorchange_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');
+ });
+ });
+
+ // clear filter
+ const clearButton = document.getElementById('clearFilters');
+ if (clearButton) {
+ clearButton.addEventListener('click', clearFilters);
+ }
+
+ //show details of selected run, when reloading
+ if (localStorage.getItem("selected_run") != null) {
+ let selected_run = document.getElementById(localStorage.getItem("selected_run"));
+ selected_run.click();
+ }
+
+});
+
+
+// === 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 = 'rgb(232, 237, 243)';
+ 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();
+};
+
+function toggleFavouriteRun(runName){
+
+ document.getElementById('favourites_run_name_id').value = runName;
+ document.getElementById('change_favourite').submit();
+};
+
+function addTag(runName){
+ document.getElementById('add_tag_run_name_id').value = runName;
+ console.log(runName);
+ document.getElementById('add_tag_name_id').value = document.getElementById('tag_name_id_' + runName).value;
+ document.getElementById('add_tag').submit();
+};
+
+function deleteTag(runName, tagName){
+
+ document.getElementById('delete_tag_run_name_id').value = runName;
+ document.getElementById('delete_tag_name_id').value = tagName;
+ document.getElementById('delete_tag').submit();
+};
+
+function toggleDetails(element) {
+ document.querySelectorAll('.list-item.expanded').forEach(item => {
+ if (item !== element) {
+ item.classList.remove('expanded');
+ item.querySelector('.details').style.display = 'none';
+ item.style.height = "40px";
+ }
+ });
+
+ const details = element.querySelector('.details');
+ if (element.classList.contains('expanded')) {
+ 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'));
+ }
+}
+
+function clearFilters() {
+ const form = document.getElementById('filtertest');
+
+
+ // Clear text inputs
+ form.querySelectorAll('input[type="text"]').forEach(input => input.value = '');
+
+ // Clear search_steps
+ const searchStepsMultiSelect = new MultiSelect(document.getElementById('search_steps'));
+ searchStepsMultiSelect.data.forEach(option => {
+ option.selected = false; // Mark as unselected
+ });
+ searchStepsMultiSelect._updateSelected();
+
+ // 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/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 %}
+
+
+
+{% endblock %}
diff --git a/ui/runs/templates/runs/index.html b/ui/runs/templates/runs/index.html
index d6be8f3d..e55bd500 100644
--- a/ui/runs/templates/runs/index.html
+++ b/ui/runs/templates/runs/index.html
@@ -1,12 +1,20 @@
+
{% extends 'base.html' %}
{% load static %}
{% block css %}
+
+{% endblock %}
+
+{% block js %}
+
{% endblock %}
{% block content %}
-
+
+
+
{% if messages %}
@@ -21,64 +29,238 @@
{% endif %}
-
-
-
+
+
+
+
+
Continue an existing run:
+ {{ all_available_runs.0.run_name }}
+
+
+
+
+
+
+
{% endblock %}
\ No newline at end of file
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 %}
+
+ {{ message | safe }}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
Continue an existing run:
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/ui/runs/urls.py b/ui/runs/urls.py
index a6f4c36c..6e72bc07 100644
--- a/ui/runs/urls.py
+++ b/ui/runs/urls.py
@@ -5,9 +5,13 @@
app_name = "runs"
urlpatterns = [
path("", views.index, name="index"),
- path("create", views.create, name="create"),
+ 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 | probably not actually
path("continue", views.continue_, name="continue"),
path("delete", views.delete_, name="delete"),
+ path("add_tag", views.add_tag, name="add_tag"),
+ path("delete_tag", views.delete_tag, name="delete_tag"),
+ 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 020063eb..1da4bdc4 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
@@ -20,8 +21,9 @@
from django.urls import reverse
from django.conf import settings
-from protzilla.run import Run, get_available_run_names
-from protzilla.run_v2 import delete_run_folder
+import protzilla.constants.paths as paths
+from protzilla.run import Run, delete_run_folder, get_available_runinfo
+from protzilla.disk_operator import DiskOperator, YamlOperator
from protzilla.run_helper import log_messages
from protzilla.stepfactory import StepFactory
from protzilla.steps import Step
@@ -38,17 +40,17 @@
make_name_field,
make_sidebar,
)
-from ui.runs.views_helper import display_message, display_messages, parameters_from_post
+from ui.runs.views_helper import display_message, display_messages, parameters_from_post, get_all_possible_step_names, filter_runs
from .form_mapping import (
get_empty_plot_form_by_method,
get_filled_form_by_method,
get_filled_form_by_request,
)
+from .views_helper import sort_runs
active_runs: dict[str, Run] = {}
-
def detail(request: HttpRequest, run_name: str):
"""
Renders the details page of a specific run.
@@ -173,14 +175,153 @@ def index(request: HttpRequest, index_error: bool = False):
:return: the rendered index page
:rtype: HttpResponse
"""
+ 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