From 5dd2b5c6dcd51438f71da05af399999ba512e0ca Mon Sep 17 00:00:00 2001 From: Javier Sánchez Parra Date: Wed, 28 Jul 2021 17:14:51 +0200 Subject: Add full scheme partitioning support The initial "Partition & Format" (aka setup) form only allows to modify one partition at a time. This commit updates it to allow to modify the whole disk partition schema in one go, without pop-ups and transitions. This is a remake of the previous form using FieldList de WTForms and javascript to duplicate / remove FieldList adapted to the attributes available in WTForms. --- ogcp/forms/action_forms.py | 32 ++++----- ogcp/static/js/ogcp.js | 22 +++++++ ogcp/templates/actions/setup.html | 122 +++++++++++++++------------------- ogcp/views.py | 133 +++++++++++--------------------------- 4 files changed, 121 insertions(+), 188 deletions(-) diff --git a/ogcp/forms/action_forms.py b/ogcp/forms/action_forms.py index 2286941..c5b4909 100644 --- a/ogcp/forms/action_forms.py +++ b/ogcp/forms/action_forms.py @@ -7,7 +7,7 @@ from wtforms import ( Form, SubmitField, HiddenField, SelectField, BooleanField, IntegerField, - StringField, RadioField + StringField, RadioField, FormField, FieldList ) from wtforms.validators import InputRequired from flask_wtf import FlaskForm @@ -21,9 +21,8 @@ class WOLForm(FlaskForm): submit = SubmitField(label=_('Submit')) class PartitionForm(FlaskForm): - ips = HiddenField() - disk = HiddenField() - partition = HiddenField() + partition = SelectField(label=_('Partition'), + choices=range(1,10)) part_type = SelectField(label=_('Type'), choices=[('LINUX', 'Linux'), ('NTFS', 'NTFS'), @@ -31,28 +30,19 @@ class PartitionForm(FlaskForm): fs = SelectField(label=_('Filesystem'), choices=[('EXT4', 'EXT4'), ('NTFS', 'NTFS'), - ('DISK', 'Disk'), ('EMPTY', 'Empty')]) size = IntegerField(label=_('Size (KB)')) format_partition = BooleanField(label=_('Format')) - modify = SubmitField(label=_('Modify')) - delete = SubmitField(label=_('Delete')) - -class NewPartitionForm(FlaskForm): +class SetupForm(FlaskForm): ips = HiddenField() - part_type = SelectField(label=_('Type'), - choices=[('LINUX', 'Linux'), - ('NTFS', 'NTFS'), - ('EMPTY', 'Empty')]) - fs = SelectField(label=_('Filesystem'), - choices=[('EXT4', 'EXT4'), - ('NTFS', 'NTFS'), - ('DISK', 'Disk'), - ('EMPTY', 'Empty')]) - size = IntegerField(label=_('Size (KB)')) - create = SubmitField(label=_('Create')) - + disk = HiddenField() + disk_type = SelectField(label=_('Type'), + choices=[('MSDOS', 'MSDOS'), + ('GPT', 'GPT')]) + partitions = FieldList(FormField(PartitionForm), + min_entries=1, + max_entries=10) class HardwareForm(FlaskForm): ips = HiddenField() diff --git a/ogcp/static/js/ogcp.js b/ogcp/static/js/ogcp.js index f9c8e56..00ab61c 100644 --- a/ogcp/static/js/ogcp.js +++ b/ogcp/static/js/ogcp.js @@ -53,3 +53,25 @@ function updateScopes(scopes) { function unfoldAll() { $('#scopes .collapse').collapse('show'); } + +function AddPartition(evt) { + const target = $($(evt).data("target")); + const oldrow = target.find("[data-toggle=fieldset-entry]:last"); + const row = oldrow.clone(true, true); + const elem_id = row.find(":input")[0].id; + const elem_prefix = elem_id.replace(/(.*)-(\d{1,4})/m, '$1')// max 4 digits for ids in list + const elem_num = parseInt(elem_id.replace(/(.*)-(\d{1,4})/m, '$2')) + 1; + // Increment WTForms unique identifiers + row.children(':input').each(function() { + const id = $(this).attr('id').replace(elem_prefix+'-' + (elem_num - 1), + elem_prefix+'-' + (elem_num)); + $(this).attr('name', id).attr('id', id).val('').removeAttr("checked"); + }); + row.show(); + oldrow.after(row); +} + +function RemovePartition(evt) { + const target = $(evt).parent().parent(); + target.remove(); +} diff --git a/ogcp/templates/actions/setup.html b/ogcp/templates/actions/setup.html index 6856ed1..c1633c6 100644 --- a/ogcp/templates/actions/setup.html +++ b/ogcp/templates/actions/setup.html @@ -4,79 +4,61 @@

{{_('Partition and Format')}}

- - - - - - - - - - + +
TypeFilesytemSize (KB)Format?
+ + + + + + - - {% for form in forms %} - - - {{ form.hidden_tag() }} - - - - - - - - - {% endfor %} - -
Partition TableTotal Disk Size (KB)
{{ form.part_type(class_="form-control") }}{{ form.fs(class_="form-control") }}{{ form.size(class_="form-control") }}{{ form.format_partition(class_="form-control") }}{{ form.modify(class_="form-control btn-primary") }}{{ form.delete(class_="form-control btn-danger") }}
+ + + {{ form.hidden_tag() }} + {{ form.disk_type(class_="form-control") }} + {{ disk_size }} + + + + + + + + + + + + + + - + + {% for partition in form.partitions %} + + {{ partition.hidden_tag() }} + + + + + + + + {% endfor %} + +
PartitionTypeFilesytemSize (KB)Format?
{{ partition.partition(class_="form-control") }}{{ partition.part_type(class_="form-control") }}{{ partition.fs(class_="form-control") }}{{ partition.size(class_="form-control") }}{{ partition.format_partition(class_="form-control") }} + +
+ - - + {% endblock %} diff --git a/ogcp/views.py b/ogcp/views.py index ad5a8cb..ba1fe6c 100644 --- a/ogcp/views.py +++ b/ogcp/views.py @@ -9,7 +9,7 @@ from flask import ( g, render_template, url_for, flash, redirect, request, jsonify, make_response ) from ogcp.forms.action_forms import ( - WOLForm, PartitionForm, NewPartitionForm, ClientDetailsForm, HardwareForm, + WOLForm, SetupForm, ClientDetailsForm, HardwareForm, SessionForm, ImageRestoreForm, ImageCreateForm, SoftwareForm, BootModeForm, RoomForm, DeleteRoomForm, CenterForm ) @@ -46,6 +46,7 @@ PART_TYPE_CODES = { } PART_SCHEME_CODES = { + 0: 'EMPTY', 1: 'MSDOS', 2: 'GPT' } @@ -232,85 +233,65 @@ def action_wol(): @app.route('/action/setup', methods=['GET']) @login_required -def action_setup_show(ips=None, new_partition_form=None): +def action_setup_show(ips=None): if not ips: ips = parse_ips(request.args.to_dict()) db_partitions = get_client_setup(ips) - forms = [PartitionForm() for _ in db_partitions] - forms = list(forms) + form = SetupForm() - if not new_partition_form: - new_partition_form = NewPartitionForm() - new_partition_form.ips.data = " ".join(ips) - new_partition_form.create.render_kw = {"formaction": url_for('action_setup_create')} + form.ips.data = " ".join(ips) + form.disk.data = db_partitions[0]['disk'] + # If partition table is empty, set MSDOS + form.disk_type.data = db_partitions[0]['code'] or 1 - for form, db_part in zip(forms, db_partitions): - form.ips.data = " ".join(ips) - form.disk.data = db_part['disk'] - form.partition.data = db_part['partition'] - # XXX: Should separate whole disk form from partition setup form - if db_part['filesystem'] == "DISK": - form.part_type.choices = list(PART_SCHEME_CODES.values()) - form.fs.render_kw = {'disabled': ''} - - form.part_type.data = db_part['code'] - form.fs.data = db_part['filesystem'] - form.size.data = db_part['size'] - form.modify.render_kw = {"formaction": url_for('action_setup_modify')} - form.delete.render_kw = {"formaction": url_for('action_setup_delete')} + disk_size = db_partitions[0]['size'] + + # Make form.partition length equal to (db_partitions - 1) length + diff = len(db_partitions) - 1 - len(form.partitions) + [form.partitions.append_entry() for _ in range(diff)] + + for partition, db_part in zip(form.partitions, db_partitions[1:]): + partition.partition.data = str(db_part['partition']) + partition.part_type.data = db_part['code'] + partition.fs.data = db_part['filesystem'] + partition.size.data = db_part['size'] scopes, _clients = get_scopes(ips) return render_template('actions/setup.html', - forms=forms, - new_partition_form=new_partition_form, + form=form, + disk_size=disk_size, scopes=scopes) -@app.route('/action/setup/create', methods=['POST']) -@login_required -def action_setup_create(): - form = NewPartitionForm(request.form) - ips = form.ips.data.split(' ') - - if form.validate(): - # TODO: create the real partition - flash(_('Partition created successfully'), category='info') - return redirect(url_for('action_setup_show', ips=ips)) - - flash(_('Invalid partition configuration'), category='error') - # This return will maintain the new partition form state, but will break - # the F5 behavior in the browser - return action_setup_show(ips, form) - -@app.route('/action/setup/modify', methods=['POST']) +@app.route('/action/setup', methods=['POST']) @login_required def action_setup_modify(): - form = PartitionForm(request.form) + form = SetupForm(request.form) if form.validate(): ips = form.ips.data.split(' ') db_partitions = get_client_setup(ips) payload = {'clients': ips, 'disk': str(form.disk.data), + 'type': str(form.disk_type.data), 'cache': str(0), 'cache_size': str(0), 'partition_setup': []} - for db_part in db_partitions: - if db_part['partition'] == 0: - # Set partition scheme - payload['type'] = db_part['code'] - continue - partition_setup = {'partition': str(db_part['partition']), - 'code': db_part['code'], - 'filesystem': db_part['filesystem'], - 'size': str(db_part['size']), - 'format': str(int(False))} + required_partitions = ["1", "2", "3", "4"] + for partition in form.partitions: + print(partition) + partition_setup = {'partition': str(partition.partition.data), + 'code': str(partition.part_type.data), + 'filesystem': str(partition.fs.data), + 'size': str(partition.size.data), + 'format': str(int(partition.format_partition.data))} payload['partition_setup'].append(partition_setup) + if partition.partition.data in required_partitions: + required_partitions.remove(partition.partition.data) - # Ensure a 4 partition setup - for i in range(len(db_partitions), 5): + for partition in required_partitions: empty_part = { - 'partition': str(i), + 'partition': partition, 'code': 'EMPTY', 'filesystem': 'EMPTY', 'size': '0', @@ -318,48 +299,6 @@ def action_setup_modify(): } payload['partition_setup'].append(empty_part) - modified_part = payload['partition_setup'][int(form.partition.data) - 1] - modified_part['filesystem'] = str(form.fs.data) - modified_part['code'] = str(form.part_type.data) - modified_part['size'] = str(form.size.data) - modified_part['format'] = str(int(form.format_partition.data)) - - r = g.server.post('/setup', payload=payload) - if r.status_code == requests.codes.ok: - return redirect(url_for("scopes")) - return make_response("400 Bad Request", 400) - -@app.route('/action/setup/delete', methods=['POST']) -@login_required -def action_setup_delete(): - form = PartitionForm(request.form) - if form.validate(): - ips = form.ips.data.split(' ') - db_partitions = get_client_setup(ips) - - payload = {'clients': ips, - 'disk': str(form.disk.data), - 'cache': str(0), - 'cache_size': str(0), - 'partition_setup': []} - - for db_part in db_partitions: - if db_part['partition'] == 0: - # Skip if this is disk setup. - continue - partition_setup = {'partition': str(db_part['partition']), - 'code': db_part['code'], - 'filesystem': db_part['filesystem'], - 'size': str(db_part['size']), - 'format': str(int(False))} - payload['partition_setup'].append(partition_setup) - - modified_part = payload['partition_setup'][int(form.partition.data) - 1] - modified_part['filesystem'] = FS_CODES[1] - modified_part['code'] = PART_TYPE_CODES[0] - modified_part['size'] = str(0) - modified_part['format'] = str(int(True)) - r = g.server.post('/setup', payload=payload) if r.status_code == requests.codes.ok: return redirect(url_for("scopes")) -- cgit v1.2.3-18-g5258