Skip to content

Commit

Permalink
feat(webinterface): revised upload page
Browse files Browse the repository at this point in the history
replaced bootstrap selects with dropdowns and made styling more coherent
  • Loading branch information
jstucke committed Dec 10, 2024
1 parent 55936c3 commit 05db75e
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 228 deletions.
7 changes: 4 additions & 3 deletions src/test/unit/web_interface/test_app_re_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ def test_app_re_analyze_get_invalid_firmware(self, test_client):

def test_app_re_analyze_get_valid_firmware(self, test_client):
rv = test_client.get(f'/update-analysis/{TEST_FW.uid}')
assert b'<h3 class="mb-3">update analysis of TEST_FW_HID</h3>' in rv.data
assert b'<h3 class="mb-1">update analysis of</h3>' in rv.data
assert b'<h5 class="mb-3">TEST_FW_HID</h5>' in rv.data
assert (
b'value="default_plugin" unchecked' in rv.data
), 'plugins that did not run for TEST_FW should be unchecked'
b'value="default_plugin" checked' not in rv.data
), 'plugins that did not run for TEST_FW should not be checked'
assert b'value="mandatory_plugin"' not in rv.data, 'mandatory plugins should not be listed'
assert (
b'value="optional_plugin" checked' in rv.data
Expand Down
5 changes: 2 additions & 3 deletions src/test/unit/web_interface/test_app_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def test_app_upload_get(self, test_client):
assert b'<h3 class="mb-3">Upload Firmware</h3>' in rv.data
assert b'value="default_plugin" checked' in rv.data
assert b'value="mandatory_plugin"' not in rv.data
assert b'value="optional_plugin" unchecked' in rv.data
assert b'value="optional_plugin" >' in rv.data

def test_app_upload_invalid_firmware(self, test_client, intercom_task_list):
rv = test_client.post(
Expand All @@ -17,7 +17,6 @@ def test_app_upload_invalid_firmware(self, test_client, intercom_task_list):
'file': (BytesIO(b'test_file_content'), 'test_file.txt'),
'device_name': 'test_device',
'device_part': 'kernel',
'device_class': 'test_class',
'version': '',
'vendor': 'test_vendor',
'release_date': '01.01.1970',
Expand All @@ -26,7 +25,7 @@ def test_app_upload_invalid_firmware(self, test_client, intercom_task_list):
},
follow_redirects=True,
)
assert b'Please specify the version' in rv.data
assert b'Bad Request' in rv.data
assert len(intercom_task_list) == 0, 'task added to intercom but should not'

def test_app_upload_valid_firmware(self, test_client, intercom_task_list):
Expand Down
9 changes: 3 additions & 6 deletions src/web_interface/components/analysis_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _get_analysis_view(self, selected_analysis):

@roles_accepted(*PRIVILEGES['submit_analysis'])
@AppRoute('/update-analysis/<uid>', GET)
def get_update_analysis(self, uid, re_do=False, error=None):
def get_update_analysis(self, uid, re_do=False):
with get_shared_session(self.db.frontend) as frontend_db:
old_firmware = frontend_db.get_object(uid=uid)
if old_firmware is None:
Expand All @@ -142,8 +142,7 @@ def get_update_analysis(self, uid, re_do=False, error=None):
vendor_list = frontend_db.get_vendor_list()
device_name_dict = frontend_db.get_device_name_dict()

plugin_dict = self.intercom.get_available_analysis_plugins()

plugin_dict = {k: t[:3] for k, t in self.intercom.get_available_analysis_plugins().items() if k != 'unpacker'}
current_analysis_preset = _add_preset_from_firmware(plugin_dict, old_firmware)
analysis_presets = [current_analysis_preset, *list(config.frontend.analysis_preset)]

Expand All @@ -153,13 +152,12 @@ def get_update_analysis(self, uid, re_do=False, error=None):
'upload/upload.html',
device_classes=device_class_list,
vendors=vendor_list,
error=error if error is not None else {},
device_names=json.dumps(device_name_dict, sort_keys=True),
firmware=old_firmware,
analysis_plugin_dict=plugin_dict,
analysis_presets=analysis_presets,
title=title,
plugin_set=current_analysis_preset,
selected_preset=current_analysis_preset,
)

@roles_accepted(*PRIVILEGES['submit_analysis'])
Expand Down Expand Up @@ -242,7 +240,6 @@ def _add_preset_from_firmware(plugin_dict, fw: Firmware):

previously_processed_plugins = list(fw.processed_analysis.keys())
with suppress(ValueError):
plugin_dict.pop('unpacker')
previously_processed_plugins.remove('unpacker')
for plugin in previously_processed_plugins:
if plugin in plugin_dict:
Expand Down
10 changes: 5 additions & 5 deletions src/web_interface/components/io_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,22 @@ def post_upload(self):

@roles_accepted(*PRIVILEGES['submit_analysis'])
@AppRoute('/upload', GET)
def get_upload(self, error=None):
error = error or {}
def get_upload(self):
with get_shared_session(self.db.frontend) as frontend_db:
device_class_list = frontend_db.get_device_class_list()
vendor_list = frontend_db.get_vendor_list()
device_name_dict = frontend_db.get_device_name_dict()
analysis_plugins = self.intercom.get_available_analysis_plugins()
analysis_plugins = {
k: t[:3] for k, t in self.intercom.get_available_analysis_plugins().items() if k != 'unpacker'
}
return render_template(
'upload/upload.html',
device_classes=device_class_list,
vendors=vendor_list,
error=error,
analysis_presets=list(config.frontend.analysis_preset),
device_names=json.dumps(device_name_dict, sort_keys=True),
analysis_plugin_dict=analysis_plugins,
plugin_set='default',
selected_preset='default',
)

# ---- file download
Expand Down
83 changes: 52 additions & 31 deletions src/web_interface/static/js/upload.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,72 @@
$(function () {
$(() => {
$('#release_date').datepicker({
format: 'yyyy-mm-dd',
todayHighlight: true
});
});

function add_device_class_options(selected_device_class, selected_vendor, data) {
$('#device_name').empty();
const deviceClassButton = document.getElementById("device_class_select_button");
let deviceNameList = $('#device_name_list');
deviceNameList.empty();
if (data.hasOwnProperty(selected_device_class)) {
if (data[selected_device_class].hasOwnProperty(selected_vendor)) {
var device_classes = data[selected_device_class][selected_vendor];
let device_classes = data[selected_device_class][selected_vendor];
// remove duplicates
device_classes = [...new Set(device_classes)];
device_classes.sort();
for (var key in device_classes) {
if (device_classes.hasOwnProperty(key)) {
$('#device_name').append('<option>' + device_classes[key] + '</option>');
if (device_classes.length > 0) {
for (let index in device_classes) {
deviceNameList.append(`
<a class="dropdown-item" href="#" onClick="updateInput('device_name', this)">
${device_classes[index]}
</a>
`);
}
deviceClassButton.disabled = false;
return;
}
}
}
$('#device_name').append('<option>' + 'new entry' + '</option>');
}
function update_text_input(element, this_text_input) {
if (element.options[element.selectedIndex].value == 'new entry') {
this_text_input.style.display = 'initial';
this_text_input.value = '';
} else {
this_text_input.style.display = 'none';
this_text_input.value = element.options[element.selectedIndex].value;
}
deviceClassButton.disabled = true;
}

function update_device_names() {
var vendor_dropdown = document.getElementById('vendor');
var device_class_dropdown = document.getElementById('device_class');
if ((vendor_dropdown.selectedIndex != -1) && (device_class_dropdown.selectedIndex != -1)) {
document.getElementById('device_name').disabled = false;
var selected_device_class = device_class_dropdown.options[device_class_dropdown.selectedIndex].value;
var selected_vendor = vendor_dropdown.options[vendor_dropdown.selectedIndex].value;
add_device_class_options(selected_device_class, selected_vendor, device_names);
const deviceClassInput = document.getElementById("device_class");
const vendorInput = document.getElementById("vendor");
const vendor = vendorInput.value.trim();
const device_class = deviceClassInput.value.trim();
if (vendor.length > 0 && device_class.length > 0) {
add_device_class_options(device_class, vendor, device_names);
}
}
function change_selected_plugins(selected_theme) {
for (var plugin in plugin_dict) {
if (plugin_dict.hasOwnProperty(plugin)) {
plugin_checkbox = document.getElementById(plugin);
if (plugin_checkbox != null) {
plugin_in_theme = plugin_dict[plugin][2][selected_theme];
plugin_checkbox.firstElementChild.firstElementChild.checked = plugin_in_theme;
}

function change_selected_plugins(preset_name) {
for (const [plugin_name, plugin_data] of Object.entries(plugin_dict)) {
const plugin_checkbox = document.getElementById(plugin_name);
if (plugin_checkbox != null) {
// plugin_data is a tuple, and the third element is the dict containing preset info
let [_, __, preset] = plugin_data;
plugin_checkbox.firstElementChild.firstElementChild.checked = preset[preset_name];
}
}
}

function updateInput(input_id, element, do_update = false) {
const input = document.getElementById(input_id);
input.value = element.innerText;
if (do_update) {
update_device_names();
}
}

function filterFunction(input) {
let filter = input.value.toLowerCase();
input.parentElement.querySelectorAll(".dropdown-item").forEach(element => {
if (!element.innerText.toLowerCase().includes(filter)) {
element.style.display = "none";
} else {
element.style.display = "";
}
});
}
43 changes: 33 additions & 10 deletions src/web_interface/templates/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,46 @@ <h5 class="card-title mb-3"><i class="fas fa-{{ icon }}"></i> {{ panel_title }}<
{% macro stats_table_row(label, value, percent=False, link=None, tooltip=None) %}
<tr>
<td style="text-align: left; padding:5px"
{%- if link %} onclick="location.href='{{ link }}'"{% endif -%}
{%- if tooltip %} data-toggle="tooltip" title="{{ tooltip }}"{% endif -%}
{%- if link %} onclick="location.href='{{ link }}'"{% endif -%}
{%- if tooltip %} data-toggle="tooltip" title="{{ tooltip }}"{% endif -%}
>
{{ label }}
</td>
<td style="text-align: right; padding:5px">{{ value }}{% if percent %}%{% endif %}</td>
</tr>
{%- endmacro %}

{% macro upload_input(key, label, error, additional_classes='') %}
<label class="control-label" for="{{ key }}">{{ label }}:</label>
<div class="form-group">
<div class="{{ additional_classes }}">
{{ caller() }}
{% if key in error %}
<span class="form-text text-danger">{{ error[key] }}</span>
{% endif %}
{% macro upload_input(key, label) %}
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text upload-label" for="{{ label_target }}">
{{ label }}
</label>
</div>

{{ caller() }}
</div>
{%- endmacro %}

{% macro upload_dropdown(input_id, options, update_device_list=False, include_filter=False) %}
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
{% if include_filter %}
<div class="dropdown-menu" style="padding-top: 0;">
<input class="form-control-sm" type="text" placeholder="filter..."
onkeyup="filterFunction(this)">
{% else %}
<div class="dropdown-menu">
{% endif %}
{% for option in options | sort %}
<a class="dropdown-item" href="#"
onclick="updateInput('{{ input_id }}', this, {{ update_device_list | tojson }})">
{{ option }}
</a>
{% endfor %}
</div>
</div>
{%- endmacro %}
Expand Down
Loading

0 comments on commit 05db75e

Please sign in to comment.