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.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 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 %} +
+
+
+
+ {% 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 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 %} -
-
-
+
+
+
+ +
+ {% csrf_token %} -
-

Work on a new run:

- -
-
- - -
-
- - -
- + -
+

Continue an existing run: + +

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

Continue an existing run:

- +
+ + +
+ Tags: +
+ {% for tag in run.run_tags %} + {{ tag }} + + {% endfor %} + + + +
+
+ {% endfor %} - + +
- + + +
+ {% csrf_token %} + +
+
+ {% csrf_token %} + +
+
+ {% csrf_token %} + + +
+
+ {% csrf_token %} + +
- Manage databases
- -
-
+
+ {% csrf_token %} +

Select filters:

-

Delete an existing run:

- +
+
+ + +
+
+ + +
+
+ +
- +
+ +
+
+ + +
+
+ + +
{% 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 %} + + {% 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 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