diff options
author | Javier Sánchez Parra <jsanchez@soleta.eu> | 2021-07-28 17:14:51 +0200 |
---|---|---|
committer | OpenGnSys Support Team <soporte-og@soleta.eu> | 2021-07-28 17:19:51 +0200 |
commit | 5dd2b5c6dcd51438f71da05af399999ba512e0ca (patch) | |
tree | e9f10561cda118a143211dabc6a524c89e9fd446 /ogcp | |
parent | 4b4edf4aeeed5d6177c1d78812a497e7ad1a206d (diff) |
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.
Diffstat (limited to 'ogcp')
-rw-r--r-- | ogcp/forms/action_forms.py | 32 | ||||
-rw-r--r-- | ogcp/static/js/ogcp.js | 22 | ||||
-rw-r--r-- | ogcp/templates/actions/setup.html | 122 | ||||
-rw-r--r-- | 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 @@ <h1 class="m-5">{{_('Partition and Format')}}</h1> -<table class="table"> - <thead class="text-center"> - <tr> - <th>Type</th> - <th>Filesytem</th> - <th>Size (KB)</th> - <th>Format?</th> - <th colspan="2"></th> - </tr> - </thead> +<form class="form-inline" method="POST" id="setupForm"> + <table class="table"> + <thead class="text-center"> + <tr> + <th>Partition Table</th> + <th>Total Disk Size (KB)</th> + </tr> + </thead> - <tbody> - {% for form in forms %} - <form class="form-inline" method="POST"> - <tr> - {{ form.hidden_tag() }} - <td>{{ form.part_type(class_="form-control") }}</td> - <td>{{ form.fs(class_="form-control") }}</td> - <td>{{ form.size(class_="form-control") }}</td> - <td>{{ form.format_partition(class_="form-control") }}</td> - <td>{{ form.modify(class_="form-control btn-primary") }}</td> - <td>{{ form.delete(class_="form-control btn-danger") }}</td> - </tr> - </form> - {% endfor %} - </tbody> -</table> + <tbody data-target="partitons-fieldset" id="setupTable" class="text-center"> + <tr> + {{ form.hidden_tag() }} + <td>{{ form.disk_type(class_="form-control") }}</td> + <td>{{ disk_size }}</td> + </tr> + </tbody> + </table> + <table class="table"> + <thead class="text-center"> + <tr> + <th>Partition</th> + <th>Type</th> + <th>Filesytem</th> + <th>Size (KB)</th> + <th>Format?</th> + <th colspan="2"></th> + </tr> + </thead> -<button class="btn btn-primary" data-toggle="modal" data-target="#newPartitionModal"> - {{ _('Add a new Partition') }} -</button> + <tbody data-target="partitons-fieldset" id="partitionsTable" class="text-center"> + {% for partition in form.partitions %} + <tr data-toggle="fieldset-entry"> + {{ partition.hidden_tag() }} + <td>{{ partition.partition(class_="form-control") }}</td> + <td>{{ partition.part_type(class_="form-control") }}</td> + <td>{{ partition.fs(class_="form-control") }}</td> + <td>{{ partition.size(class_="form-control") }}</td> + <td>{{ partition.format_partition(class_="form-control") }}</td> + <td> + <button class="btn btn-danger" onclick="RemovePartition(this)"> + {{ _('Remove') }} + </button> + </td> + </tr> + {% endfor %} + </tbody> + </table> +</form> -<!-- Modal --> -<div class="modal fade" id="newPartitionModal" tabindex="-1" aria-hidden="true"> - <div class="modal-dialog"> - <div class="modal-content"> - <form class="form" method="POST"> - <div class="modal-header"> - <h5 class="modal-title" id="exampleModalLabel">{{ _('Create a new partition') }}</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body"> - {{ new_partition_form.hidden_tag() }} +<button class="btn btn-primary" data-target="#partitionsTable" onclick="AddPartition(this)"> + {{ _('Add a new partition') }} +</button> - <div class="form-group"> - <label for="{{ new_partition_form.part_type.id }}">{{ new_partition_form.part_type.label }}</label> - {{ new_partition_form.part_type(class_="form-control") }} - </div> - <div class="form-group"> - <label for="{{ new_partition_form.fs.id }}">{{ new_partition_form.fs.label }}</label> - {{ new_partition_form.fs(class_="form-control") }} - </div> - <div class="form-group"> - <label for="{{ new_partition_form.size.id }}">{{ new_partition_form.size.label }}</label> - {% if new_partition_form.size.errors %} - {{ new_partition_form.size(class_="form-control is-invalid") }} - {% else %} - {{ new_partition_form.size(class_="form-control") }} - {% endif %} - {% for error in new_partition_form.size.errors %} - <div class="invalid-feedback">{{ error }}</div> - {% endfor %} - </div> - </div> - <div class="modal-footer"> - {{ new_partition_form.create(class_="btn btn-primary") }} - <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> - </div> - </div> - </form> - </div> -</div> +<button class="btn btn-success" form="setupForm"> + {{ _('Accept') }} +</button> {% 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")) |