mirror of https://git.48k.eu/ogcp
ogcp: add support for multi-disk partition and format
Add support for selecting different disks in the disk inspector. Add disk_inspector.html as a template to show the disk contents of a client. The view can define the variable readonly_disk_inspector to make the view non editable. Use disk_inspector.html in the following views: - client details - partition and format Update code to obtain the partitions of a client to better fit the requirements of disk_inspector.html Remove code to setup the SetupForm as the contents of the disks are now dynamically loaded through javascript.master
parent
78a26f947f
commit
d00e437b8d
|
@ -35,6 +35,7 @@ class PartitionForm(FlaskForm):
|
|||
render_kw={'readonly': True},
|
||||
default=1)
|
||||
part_type = SelectField(label=_l('Type'),
|
||||
validators=[InputRequired()],
|
||||
choices=[('LINUX', 'Linux'),
|
||||
('NTFS', 'NTFS'),
|
||||
('CACHE', 'CACHE'),
|
||||
|
@ -52,6 +53,7 @@ class PartitionForm(FlaskForm):
|
|||
('EMPTY', 'Empty')],
|
||||
default='EMPTY')
|
||||
fs = SelectField(label=_l('Filesystem'),
|
||||
validators=[InputRequired()],
|
||||
choices=[('EXT4', 'EXT4'),
|
||||
('NTFS', 'NTFS'),
|
||||
('CACHE', 'CACHE'),
|
||||
|
@ -59,23 +61,23 @@ class PartitionForm(FlaskForm):
|
|||
('FAT32', 'FAT32'),
|
||||
('EMPTY', 'Empty')],
|
||||
default='EMPTY')
|
||||
size = IntegerField(label=_l('Size (KB)'))
|
||||
size = IntegerField(label=_l('Size (KB)'), validators=[InputRequired()],)
|
||||
|
||||
class SelectClientForm(FlaskForm):
|
||||
ips = HiddenField()
|
||||
selected_client = SelectField(label=_l('Select one client as reference to '
|
||||
'define the partition scheme'))
|
||||
ok = SubmitField(label=_l('Submit'))
|
||||
submit = SubmitField(label=_l('Select'))
|
||||
|
||||
class SetupForm(FlaskForm):
|
||||
ips = HiddenField()
|
||||
disk = HiddenField()
|
||||
disk = SelectField(label='Disk', validate_choice=False)
|
||||
disk_type = SelectField(label=_l('Type'),
|
||||
choices=[('MSDOS', 'MBR'),
|
||||
('GPT', 'GPT')])
|
||||
partitions = FieldList(FormField(PartitionForm),
|
||||
min_entries=1,
|
||||
max_entries=10)
|
||||
max_entries=16)
|
||||
|
||||
class HardwareForm(FlaskForm):
|
||||
ips = HiddenField()
|
||||
|
|
|
@ -37,31 +37,10 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
{% if setup|length > 0 %}
|
||||
<table class="table">
|
||||
<tbody class="text-center">
|
||||
<tr>
|
||||
<th>{{_('Partition')}}</th>
|
||||
<th>{{_('Type')}}</th>
|
||||
<th>{{_('Filesystem')}}</th>
|
||||
<th>{{_('Size')}} (MiB)</th>
|
||||
<th>{{_('Image')}}</th>
|
||||
<th colspan="2"></th>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody class="text-center">
|
||||
{% for entry in setup %}
|
||||
<tr>
|
||||
<td>{{ entry.partition }}</td>
|
||||
<td>{{ entry.code }}</td>
|
||||
<td>{{ entry.filesystem }}</td>
|
||||
<td>{{ entry.size // 1024}}</td>
|
||||
<td>{{ entry.image }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% set show_part_images = True %}
|
||||
{% set readonly_disk_inspector = True %}
|
||||
{% include 'disk_inspector.html' %}
|
||||
|
||||
|
||||
{% include 'cache_inspector.html' %}
|
||||
|
||||
|
|
|
@ -17,6 +17,48 @@
|
|||
|
||||
{{ macros.cmd_selected_clients(selected_clients) }}
|
||||
|
||||
{% if common_disk_data|length > 0 %}
|
||||
|
||||
<div class="form-group mx-5">
|
||||
<label class="control-label">{{ _('Disk inventory') }}</label>
|
||||
<table class="table table-bordered">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>{{ _('Disk') }}</th>
|
||||
<th>{{ _('Size (MiB)') }}</th>
|
||||
<th>{{ _('Clients') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for disk, data in common_disk_data.items() %}
|
||||
{% for size, ips in data.inventory.items() %}
|
||||
<tr>
|
||||
{% if loop.first %}
|
||||
<td rowspan="{{ data.inventory | length }}">{{ disk }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if size == data.common_size %}
|
||||
<strong>{{ size // 1024 }}</strong>
|
||||
{% else %}
|
||||
{{ size // 1024 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% for ip in ips %}
|
||||
<div class="card d-inline-block" style="padding: 5px;">{{ ip }}</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>{{ _('Note: the disk size in bold represents the selected common size for partitioning') }}</p>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{{ wtf.quick_form(form,
|
||||
action=url_for('action_setup_show'),
|
||||
method='get',
|
||||
|
|
|
@ -9,230 +9,6 @@
|
|||
|
||||
<h2 class="mx-5 subhead-heading">{{_('Partition and Format')}}</h2>
|
||||
|
||||
<form method="GET" id="changeDiskForm">
|
||||
<input type="hidden" name="ips" value="{{ ips }}"/>
|
||||
<input type="hidden" name="selected_client" value="{{ base_client }}"/>
|
||||
</form>
|
||||
|
||||
<form class="form-inline" method="POST" id="setupForm">
|
||||
<table class="table">
|
||||
<thead class="text-center">
|
||||
<tr>
|
||||
<th>{{ _('Disk') }}</th>
|
||||
<th>{{ _('Partition Table Type') }}</th>
|
||||
<th>{{ _('Total Disk Size') }} (MiB)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody data-target="partitons-fieldset" id="setupTable" class="text-center">
|
||||
<tr>
|
||||
<td>
|
||||
<select form="changeDiskForm" name="disk" onchange="this.form.submit()">
|
||||
{% for disk in disks %}
|
||||
<option {% if disk == selected_disk %}selected{% endif %}
|
||||
value="{{ disk }}">{{ disk }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
{{ 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>{{ _('Filesystem') }}</th>
|
||||
<th>{{ _('Size') }} (MiB)</th>
|
||||
<th colspan="2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<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.data }}</td>
|
||||
<td>{{ partition.part_type(class_="form-control") }}</td>
|
||||
<td>{{ partition.fs(class_="form-control") }}</td>
|
||||
<td>{{ partition.size(class_="form-control", oninput="handleEdit(this)") }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" type="button" onclick="RemovePartition(this)">
|
||||
{{ _('Remove') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<button class="btn btn-primary" data-target="#partitionsTable" onclick="AddPartition(this)">
|
||||
{{ _('Add a new partition') }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success" form="setupForm">
|
||||
{{ _('Accept') }}
|
||||
</button>
|
||||
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
{{ _('Partition graph') }}
|
||||
</div>
|
||||
<div class="card-body mx-auto col-7">
|
||||
<canvas id="partitionChart" class="mb-2"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="{{ url_for('static', filename='AdminLTE/plugins/jquery/jquery.min.js') }}"></script>
|
||||
<!-- ChartJS -->
|
||||
<script src="{{ url_for('static', filename='AdminLTE/plugins/chart.js/Chart.min.js') }}"></script>
|
||||
<script>
|
||||
const usedColor = 'rgb(255, 99, 132)';
|
||||
const freeColor = 'rgb(54, 162, 235)';
|
||||
let diskSize = {{ disk_size }};
|
||||
|
||||
let chartConfig = {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Partitions',
|
||||
data: [],
|
||||
backgroundColor: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Chart.js Doughnut Chart'
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let partitionChart = new Chart(
|
||||
document.getElementById('partitionChart'),
|
||||
chartConfig,
|
||||
);
|
||||
|
||||
function addPartitionToChart(chart, label, value, bgColor) {
|
||||
chart.data.labels.push(label)
|
||||
chart.data.datasets[0].data.push(value);
|
||||
chart.data.datasets[0].backgroundColor.push(bgColor);
|
||||
}
|
||||
|
||||
function resetChart() {
|
||||
partitionChart.data.labels = [];
|
||||
partitionChart.data.datasets[0].data = [];
|
||||
partitionChart.data.datasets[0].backgroundColor = [];
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
resetChart();
|
||||
|
||||
let freeSpace = diskSize;
|
||||
let partNum = 1;
|
||||
$('#partitionsTable tr').each(function() {
|
||||
let partitionSize = parseInt($(this).find('td').eq(3).find('input').val().trim());
|
||||
if (isNaN(partitionSize)) {
|
||||
partitionSize = 0;
|
||||
}
|
||||
freeSpace -= partitionSize;
|
||||
addPartitionToChart(partitionChart, 'Partition ' + partNum, partitionSize, usedColor);
|
||||
partNum++;
|
||||
});
|
||||
|
||||
addPartitionToChart(partitionChart, 'Free', Math.max(freeSpace, 0), freeColor);
|
||||
partitionChart.update();
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
updateChart();
|
||||
});
|
||||
|
||||
function handleEdit(element) {
|
||||
const numericValue = parseInt(element.value);
|
||||
|
||||
if (isNaN(numericValue)) {
|
||||
updateChart();
|
||||
return;
|
||||
}
|
||||
|
||||
let freeSpace = diskSize;
|
||||
$('#partitionsTable tr').each(function() {
|
||||
let partitionSize = parseInt($(this).find('td').eq(3).find('input').val().trim());
|
||||
if (isNaN(partitionSize)) {
|
||||
partitionSize = 0;
|
||||
}
|
||||
freeSpace -= partitionSize;
|
||||
});
|
||||
|
||||
if (freeSpace < 0) {
|
||||
element.value = numericValue + freeSpace;
|
||||
}
|
||||
|
||||
updateChart();
|
||||
}
|
||||
|
||||
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(".form-control")[0].id;
|
||||
const elem_num = parseInt(elem_id.replace(/(.*)-(\d{1,4})/m, '$2')) + 1;
|
||||
// Increment WTForms unique identifiers
|
||||
row.find('.form-control').each(function() {
|
||||
const id = $(this).attr('id').replace(/(.*)-(\d{1,4})-(.*)/, `$1-${elem_num}-$3`);
|
||||
$(this).attr('name', id).attr('id', id).val('').removeAttr("checked");
|
||||
});
|
||||
let part_field = row.find('td').filter(':first')[0];
|
||||
part_field.innerText = elem_num + 1;
|
||||
row.show();
|
||||
oldrow.after(row);
|
||||
|
||||
updateChart();
|
||||
}
|
||||
|
||||
function RemovePartition(evt) {
|
||||
const target = $(evt).parent().parent();
|
||||
const table = target.parent();
|
||||
|
||||
if (table[0].children.length > 1) {
|
||||
target.remove();
|
||||
// Reassign rows ids
|
||||
table.find('tr').each(function(index) {
|
||||
function update_references() {
|
||||
const id = $(this).attr('id').replace(/(.*)-(\d{1,4})-(.*)/, `$1-${index}-$3`);
|
||||
$(this).attr('name', id).attr('id', id);
|
||||
}
|
||||
|
||||
let part_field = $(this).find('td').filter(':first')[0];
|
||||
part_field.innerText = index + 1;
|
||||
$(this).find('input').filter(':first').each(update_references);
|
||||
$(this).find('.form-control').each(update_references);
|
||||
});
|
||||
} else {
|
||||
table.find('tr').each(function(index) {
|
||||
$(this).find('.form-control').each(function() {
|
||||
$(this).val('').removeAttr("checked");
|
||||
});
|
||||
});
|
||||
}
|
||||
updateChart();
|
||||
}
|
||||
|
||||
</script>
|
||||
{% include 'disk_inspector.html' %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
{% if selected_disk is defined and setup_data is defined %}
|
||||
|
||||
<form class="form-inline" method="POST" id="setupForm">
|
||||
{{ disk_form.hidden_tag() }}
|
||||
{{ disk_form.ips() }}
|
||||
|
||||
<table class="table">
|
||||
<thead class="text-center">
|
||||
<tr>
|
||||
<th>{{ _('Disk') }}</th>
|
||||
<th>{{ _('Partition Table Type') }}</th>
|
||||
<th>{{ _('Total Disk Size') }} (MiB)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody data-target="partitons-fieldset" id="diskTable" class="text-center">
|
||||
<tr>
|
||||
<td>
|
||||
{{ disk_form.disk(class_="form-control", onchange="handleDiskChange(this)") }}
|
||||
</td>
|
||||
{{ disk_form.hidden_tag() }}
|
||||
<td>
|
||||
{% if readonly_disk_inspector is defined %}
|
||||
{{ disk_form.disk_type(class_="form-control", id="diskType", readonly="readonly") }}
|
||||
{% else %}
|
||||
{{ disk_form.disk_type(class_="form-control", id="diskType") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td id="diskSize">{{ disk_size }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table">
|
||||
<thead class="text-center">
|
||||
<tr>
|
||||
<th>{{ _('Partition') }}</th>
|
||||
<th>{{ _('Type') }}</th>
|
||||
<th>{{ _('Filesystem') }}</th>
|
||||
<th>{{ _('Size') }} (MiB)</th>
|
||||
{% if show_part_images is defined %}
|
||||
<th>{{ _('Image') }}</th>
|
||||
{% endif %}
|
||||
<th colspan="2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody data-target="partitons-fieldset" id="partitionsTable" class="text-center">
|
||||
{% for partition in disk_form.partitions %}
|
||||
<tr data-toggle="fieldset-entry">
|
||||
{{ partition.hidden_tag() }}
|
||||
<td>{{ partition.partition.data }}</td>
|
||||
<td>
|
||||
{% if readonly_disk_inspector is defined %}
|
||||
{{ partition.part_type(class_="form-control", readonly="readonly") }}
|
||||
{% else %}
|
||||
{{ partition.part_type(class_="form-control") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if readonly_disk_inspector is defined %}
|
||||
{{ partition.fs(class_="form-control", readonly="readonly") }}
|
||||
{% else %}
|
||||
{{ partition.fs(class_="form-control") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if readonly_disk_inspector is defined %}
|
||||
{{ partition.size(class_="form-control", oninput="handleEdit(this)", readonly="readonly") }}
|
||||
{% else %}
|
||||
{{ partition.size(class_="form-control", oninput="handleEdit(this)") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if show_part_images is defined %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<button class="btn btn-danger" type="button" onclick="RemovePartition(this, true)"
|
||||
{% if readonly_disk_inspector is defined %}style="display: none;"{% endif %}>
|
||||
{{ _('Remove') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<button class="btn btn-primary" data-target="#partitionsTable" id="addPartButton" onclick="AddPartition(this, true)"
|
||||
{% if readonly_disk_inspector is defined %}style="display: none;"{% endif %}>
|
||||
{{ _('Add a new partition') }}
|
||||
</button>
|
||||
|
||||
{% if not readonly_disk_inspector is defined %}
|
||||
<button class="btn btn-success" form="setupForm">
|
||||
{{ _('Submit') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
{{ _('Partition graph') }}
|
||||
</div>
|
||||
<div class="card-body mx-auto col-7">
|
||||
<canvas id="partitionChart" class="mb-2"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="{{ url_for('static', filename='AdminLTE/plugins/jquery/jquery.min.js') }}"></script>
|
||||
<!-- ChartJS -->
|
||||
<script src="{{ url_for('static', filename='AdminLTE/plugins/chart.js/Chart.min.js') }}"></script>
|
||||
<script>
|
||||
const usedColor = 'rgb(255, 99, 132)';
|
||||
const freeColor = 'rgb(54, 162, 235)';
|
||||
|
||||
let selectedDisk = {{selected_disk}};
|
||||
let setupData = {{setup_data|tojson|safe}}
|
||||
let diskSize = 0;
|
||||
|
||||
let chartConfig = {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Partitions',
|
||||
data: [],
|
||||
backgroundColor: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Chart.js Doughnut Chart'
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let partitionChart = new Chart(
|
||||
document.getElementById('partitionChart'),
|
||||
chartConfig,
|
||||
);
|
||||
|
||||
function addPartitionToChart(chart, label, value, bgColor) {
|
||||
chart.data.labels.push(label)
|
||||
chart.data.datasets[0].data.push(value);
|
||||
chart.data.datasets[0].backgroundColor.push(bgColor);
|
||||
}
|
||||
|
||||
function resetChart() {
|
||||
partitionChart.data.labels = [];
|
||||
partitionChart.data.datasets[0].data = [];
|
||||
partitionChart.data.datasets[0].backgroundColor = [];
|
||||
}
|
||||
|
||||
function updatePartitionChart() {
|
||||
resetChart();
|
||||
|
||||
let freeSpace = diskSize;
|
||||
let partNum = 1;
|
||||
$('#partitionsTable tr').each(function() {
|
||||
let partitionSize = parseInt($(this).find('td').eq(3).find('input').val().trim());
|
||||
if (isNaN(partitionSize)) {
|
||||
partitionSize = 0;
|
||||
}
|
||||
freeSpace -= partitionSize;
|
||||
addPartitionToChart(partitionChart, 'Partition ' + partNum, partitionSize, usedColor);
|
||||
partNum++;
|
||||
});
|
||||
|
||||
addPartitionToChart(partitionChart, 'Free', Math.max(freeSpace, 0), freeColor);
|
||||
partitionChart.update();
|
||||
}
|
||||
|
||||
function handleEdit(element) {
|
||||
const numericValue = parseInt(element.value);
|
||||
|
||||
if (isNaN(numericValue)) {
|
||||
updatePartitionChart();
|
||||
return;
|
||||
}
|
||||
|
||||
let freeSpace = diskSize;
|
||||
$('#partitionsTable tr').each(function() {
|
||||
let partitionSize = parseInt($(this).find('td').eq(3).find('input').val().trim());
|
||||
if (isNaN(partitionSize)) {
|
||||
partitionSize = 0;
|
||||
}
|
||||
freeSpace -= partitionSize;
|
||||
});
|
||||
|
||||
if (freeSpace < 0) {
|
||||
element.value = numericValue + freeSpace;
|
||||
}
|
||||
|
||||
updatePartitionChart();
|
||||
}
|
||||
|
||||
function AddPartition(evt, updateChart) {
|
||||
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(".form-control")[0].id;
|
||||
const elem_num = parseInt(elem_id.replace(/(.*)-(\d{1,4})/m, '$2')) + 1;
|
||||
// Increment WTForms unique identifiers
|
||||
row.find('.form-control').each(function() {
|
||||
const id = $(this).attr('id').replace(/(.*)-(\d{1,4})-(.*)/, `$1-${elem_num}-$3`);
|
||||
$(this).attr('name', id).attr('id', id).val('').removeAttr("checked");
|
||||
});
|
||||
let part_field = row.find('td').filter(':first')[0];
|
||||
part_field.innerText = elem_num + 1;
|
||||
row.show();
|
||||
oldrow.after(row);
|
||||
|
||||
if (updateChart) {
|
||||
updatePartitionChart();
|
||||
}
|
||||
}
|
||||
|
||||
function RemovePartition(evt, updateChart) {
|
||||
const target = $(evt).parent().parent();
|
||||
const table = target.parent();
|
||||
|
||||
if (table[0].children.length > 1) {
|
||||
target.remove();
|
||||
// Reassign rows ids
|
||||
table.find('tr').each(function(index) {
|
||||
function update_references() {
|
||||
const id = $(this).attr('id').replace(/(.*)-(\d{1,4})-(.*)/, `$1-${index}-$3`);
|
||||
$(this).attr('name', id).attr('id', id);
|
||||
}
|
||||
|
||||
let part_field = $(this).find('td').filter(':first')[0];
|
||||
part_field.innerText = index + 1;
|
||||
$(this).find('input').filter(':first').each(update_references);
|
||||
$(this).find('.form-control').each(update_references);
|
||||
});
|
||||
} else {
|
||||
table.find('tr').each(function(index) {
|
||||
$(this).find('.form-control').each(function() {
|
||||
$(this).val('').removeAttr("checked");
|
||||
});
|
||||
});
|
||||
}
|
||||
if (updateChart) {
|
||||
updatePartitionChart();
|
||||
}
|
||||
}
|
||||
|
||||
function setDiskData(diskNumber) {
|
||||
selectedDisk = diskNumber;
|
||||
let partitions = setupData[selectedDisk];
|
||||
diskSize = Math.floor(partitions[0].size / 1024);
|
||||
|
||||
$('#diskSize').text(diskSize);
|
||||
$('#diskType').val(partitions[0].code);
|
||||
|
||||
const partitionsTable = $('#partitionsTable');
|
||||
let partNumber = partitionsTable.find('tr').length;
|
||||
const targetPartNumber = partitions.length - 1;
|
||||
|
||||
while (partNumber < targetPartNumber) {
|
||||
AddPartition($('#addPartButton'), false);
|
||||
partNumber++;
|
||||
}
|
||||
|
||||
while (partNumber > targetPartNumber) {
|
||||
RemovePartition(partitionsTable.find('tr:last').find('button'), false);
|
||||
partNumber--;
|
||||
}
|
||||
|
||||
for (let i = 1; i < partitions.length; i++) {
|
||||
let p = partitions[i];
|
||||
|
||||
let row = partitionsTable.find('tr').eq(i - 1);
|
||||
|
||||
row.find('td').eq(0).text(p.partition);
|
||||
row.find('td').eq(1).find('select').val(p.code);
|
||||
row.find('td').eq(2).find('select').val(p.filesystem);
|
||||
row.find('td').eq(3).find('input').val(Math.floor(p.size / 1024));
|
||||
{% if show_part_images is defined %}
|
||||
row.find('td').eq(4).text(p.image);
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
updatePartitionChart();
|
||||
}
|
||||
|
||||
function handleDiskChange(selectElement) {
|
||||
setDiskData(selectElement.value);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
setDiskData(selectedDisk);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endif %}
|
192
ogcp/views.py
192
ogcp/views.py
|
@ -158,6 +158,26 @@ def parse_elements(checkboxes_dict):
|
|||
elements.update(elements_list.split(' '))
|
||||
return elements
|
||||
|
||||
def client_setup_add_image_names(server, setup_data):
|
||||
r = server.get('/images')
|
||||
if not r:
|
||||
raise ServerError
|
||||
if r.status_code != requests.codes.ok:
|
||||
raise ServerErrorCode
|
||||
|
||||
images = r.json()['images']
|
||||
|
||||
for disk, partitions in setup_data.items():
|
||||
for p in partitions:
|
||||
if images and p['image'] != 0:
|
||||
image = next((img for img in images if img['id'] == p['image']), None)
|
||||
if image:
|
||||
p['image'] = image['name']
|
||||
else:
|
||||
p['image'] = ""
|
||||
else:
|
||||
p['image'] = ""
|
||||
|
||||
def get_client_setup(ip):
|
||||
server = get_server_from_clients([ip])
|
||||
|
||||
|
@ -175,6 +195,7 @@ def get_client_setup(ip):
|
|||
if r.status_code != requests.codes.ok:
|
||||
raise ServerErrorCode
|
||||
db_partitions = r.json()['partitions']
|
||||
res = {}
|
||||
for partition in db_partitions:
|
||||
if partition['partition'] == 0:
|
||||
partition['code'] = PART_SCHEME_CODES.get(partition['code'], 'MSDOS')
|
||||
|
@ -183,7 +204,20 @@ def get_client_setup(ip):
|
|||
|
||||
partition['filesystem'] = FS_CODES.get(partition['filesystem'], 'EMPTY')
|
||||
|
||||
return db_partitions
|
||||
disk = partition.get('disk')
|
||||
if disk in res:
|
||||
res[disk].append(partition)
|
||||
else:
|
||||
res[disk] = [partition]
|
||||
|
||||
return res
|
||||
|
||||
@app.route('/client/setup', methods=['GET'])
|
||||
@login_required
|
||||
def get_client_setup_json():
|
||||
ip = parse_elements(request.args.to_dict())
|
||||
setup = get_client_setup(ip)
|
||||
return jsonify(setup)
|
||||
|
||||
def get_clients(state_filter=None):
|
||||
responses = multi_request('get', '/clients')
|
||||
|
@ -642,6 +676,33 @@ def action_wol():
|
|||
return redirect(url_for('commands'))
|
||||
|
||||
|
||||
def get_common_disk_data(ips):
|
||||
disk_data = {}
|
||||
|
||||
for ip in ips:
|
||||
setup_data = get_client_setup(ip)
|
||||
|
||||
for disk, partitions in setup_data.items():
|
||||
for p in partitions:
|
||||
if p.get('partition') != 0:
|
||||
continue
|
||||
if disk in disk_data:
|
||||
disk_data[disk]['common_size'] = min(disk_data[disk]['common_size'], p.get('size'))
|
||||
disk_data[disk]['inventory'].setdefault(p.get('size'), []).append(ip)
|
||||
else:
|
||||
disk_data[disk] = {
|
||||
'common_size': p.get('size'),
|
||||
'excluded': [],
|
||||
'inventory': {p.get('size'): [ip]}
|
||||
}
|
||||
break
|
||||
|
||||
for disk_id in disk_data:
|
||||
if disk_id not in setup_data:
|
||||
disk_data[disk_id]['excluded'].append(ip)
|
||||
|
||||
return disk_data
|
||||
|
||||
@app.route('/action/setup/select', methods=['GET'])
|
||||
@login_required
|
||||
def action_setup_select():
|
||||
|
@ -671,10 +732,13 @@ def action_setup_select():
|
|||
client_choices.append((ip, f"{client_name} ({ip})"))
|
||||
form.selected_client.choices = client_choices
|
||||
|
||||
common_disk_data = get_common_disk_data(ips)
|
||||
|
||||
scopes, unused = get_scopes(ips)
|
||||
selected_clients = list(get_selected_clients(scopes['scope']).items())
|
||||
|
||||
return render_template('actions/select_client.html',
|
||||
common_disk_data=common_disk_data,
|
||||
selected_clients=selected_clients,
|
||||
form=form, scopes=scopes)
|
||||
|
||||
|
@ -685,9 +749,6 @@ def action_setup_show():
|
|||
args = request.args.copy()
|
||||
disk_size = None
|
||||
|
||||
default_disk = 1
|
||||
selected_disk = int(args.pop('disk', default_disk))
|
||||
|
||||
if args.get('ip'):
|
||||
ips = {args['ip']}
|
||||
ips_str = base_client = args['ip']
|
||||
|
@ -696,59 +757,37 @@ def action_setup_show():
|
|||
ips = set(args['ips'].split(' '))
|
||||
base_client = args['selected_client']
|
||||
|
||||
for ip in ips:
|
||||
try:
|
||||
setup_data = get_client_setup(ip)
|
||||
except ServerError:
|
||||
return ogserver_down('commands')
|
||||
except ServerErrorCode:
|
||||
return ogserver_error('commands')
|
||||
try:
|
||||
setup_data = get_client_setup(base_client)
|
||||
except ServerError:
|
||||
return ogserver_down('commands')
|
||||
except ServerErrorCode:
|
||||
return ogserver_error('commands')
|
||||
|
||||
filtered_partitions = [p for p in setup_data
|
||||
if p.get('disk') == selected_disk]
|
||||
if not filtered_partitions:
|
||||
continue
|
||||
|
||||
if ip == base_client:
|
||||
target_partitions = filtered_partitions
|
||||
|
||||
client_disk_size = filtered_partitions[0]['size'] // 1024
|
||||
if disk_size:
|
||||
disk_size = min(disk_size, client_disk_size)
|
||||
else:
|
||||
disk_size = client_disk_size
|
||||
|
||||
if not target_partitions:
|
||||
if not setup_data:
|
||||
flash(_('Partition information is not available. Boot client in ogLive mode to obtain it'), category='error')
|
||||
return redirect(url_for('commands'))
|
||||
|
||||
disk_partition = 0
|
||||
disks = [d.get('disk') for d in target_partitions
|
||||
if d.get('partition') == disk_partition]
|
||||
selected_disk = 1
|
||||
common_disk_data = get_common_disk_data(ips)
|
||||
|
||||
# Use common disk size
|
||||
for disk in setup_data:
|
||||
setup_data[disk][0]['size'] = common_disk_data[disk]['common_size']
|
||||
|
||||
form = SetupForm()
|
||||
form.ips.data = ips_str
|
||||
|
||||
form.disk.choices = [(disk, disk) for disk in setup_data]
|
||||
form.disk.data = selected_disk
|
||||
# If partition table is empty, set MSDOS
|
||||
form.disk_type.data = target_partitions[0]['code'] or 1
|
||||
form.disk_type.data = setup_data[selected_disk][0]['code'] or 1
|
||||
|
||||
# Make form.partition length equal to (target_partitions - 1) length
|
||||
diff = len(target_partitions) - 1 - len(form.partitions)
|
||||
[form.partitions.append_entry() for unused in range(diff)]
|
||||
|
||||
for partition, db_part in zip(form.partitions, target_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'] // 1024
|
||||
scopes, _clients = get_scopes(ips)
|
||||
return render_template('actions/setup.html',
|
||||
selected_disk=selected_disk,
|
||||
disks=disks,
|
||||
form=form,
|
||||
disk_size=disk_size,
|
||||
ips=ips_str,
|
||||
base_client=base_client,
|
||||
setup_data=setup_data,
|
||||
disk_form=form,
|
||||
scopes=scopes)
|
||||
|
||||
@app.route('/action/setup', methods=['POST'])
|
||||
|
@ -1461,34 +1500,19 @@ def action_client_info():
|
|||
|
||||
form.submit.render_kw = {"style": "visibility:hidden;"}
|
||||
|
||||
r = server.get('/images')
|
||||
if not r:
|
||||
return ogserver_down('commands')
|
||||
if r.status_code != requests.codes.ok:
|
||||
return ogserver_error('commands')
|
||||
|
||||
images = r.json()['images']
|
||||
|
||||
ip = list(ips)[0]
|
||||
try:
|
||||
setup = get_client_setup(ip)
|
||||
setup_data = get_client_setup(ip)
|
||||
client_setup_add_image_names(server, setup_data)
|
||||
except ServerError:
|
||||
return ogserver_down('commands')
|
||||
except ServerErrorCode:
|
||||
return ogserver_error('commands')
|
||||
|
||||
if setup and setup[0].get('code') == 'MSDOS':
|
||||
setup[0]['code'] = 'MBR'
|
||||
|
||||
for entry in setup:
|
||||
if images and entry['image'] != 0:
|
||||
image = next((img for img in images if img['id'] == entry['image']), None)
|
||||
if image:
|
||||
entry['image'] = image['name']
|
||||
else:
|
||||
entry['image'] = ""
|
||||
else:
|
||||
entry['image'] = ""
|
||||
disk_form = SetupForm()
|
||||
selected_disk = 1
|
||||
disk_form.disk.choices = [(disk, disk) for disk in setup_data]
|
||||
disk_form.disk.data = selected_disk
|
||||
|
||||
r = server.get('/cache/list', payload={'clients': [ip]})
|
||||
if not r:
|
||||
|
@ -1511,7 +1535,10 @@ def action_client_info():
|
|||
scopes, clients = get_scopes(set(ips))
|
||||
|
||||
return render_template('actions/client_details.html', form=form,
|
||||
parent="commands.html", scopes=scopes, setup=setup,
|
||||
parent="commands.html", scopes=scopes,
|
||||
setup_data=setup_data,
|
||||
disk_form=disk_form,
|
||||
selected_disk=selected_disk,
|
||||
images_data=images_data,
|
||||
storage_data=storage_data,
|
||||
client_images=client_images)
|
||||
|
@ -1620,36 +1647,25 @@ def action_client_update():
|
|||
if db_client['repo_id'] != repo["id"]])
|
||||
ip = list(ips)[0]
|
||||
try:
|
||||
setup = get_client_setup(ip)
|
||||
setup_data = get_client_setup(ip)
|
||||
client_setup_add_image_names(server, setup_data)
|
||||
except ServerError:
|
||||
return ogserver_down('scopes')
|
||||
except ServerErrorCode:
|
||||
return ogserver_error('scopes')
|
||||
|
||||
if setup and setup[0].get('code') == 'MSDOS':
|
||||
setup[0]['code'] = 'MBR'
|
||||
|
||||
r = server.get('/images')
|
||||
if not r:
|
||||
return ogserver_down('scopes')
|
||||
if r.status_code != requests.codes.ok:
|
||||
return ogserver_error('scopes')
|
||||
|
||||
images = r.json()['images']
|
||||
for entry in setup:
|
||||
if images and entry['image'] != 0:
|
||||
image = next((img for img in images if img['id'] == entry['image']), None)
|
||||
if image:
|
||||
entry['image'] = image['name']
|
||||
else:
|
||||
entry['image'] = ""
|
||||
else:
|
||||
entry['image'] = ""
|
||||
disk_form = SetupForm()
|
||||
selected_disk = 1
|
||||
disk_form.disk.choices = [(disk, disk) for disk in setup_data]
|
||||
disk_form.disk.data = selected_disk
|
||||
|
||||
form.submit.render_kw = {"formaction": url_for('action_client_update')}
|
||||
|
||||
return render_template('actions/client_details.html', form=form,
|
||||
parent="scopes.html", scopes=scopes, setup=setup)
|
||||
setup_data=setup_data,
|
||||
disk_form=disk_form,
|
||||
selected_disk=selected_disk,
|
||||
parent="scopes.html", scopes=scopes)
|
||||
|
||||
def find_folder(folder_id, scopes):
|
||||
scopes = deque([scopes['scope']])
|
||||
|
@ -3048,7 +3064,7 @@ def repo_addr_is_valid(form):
|
|||
if invalid_ips:
|
||||
res = False
|
||||
flash(_(f'The following addresses are invalid: {" ".join(invalid_ips)}'), category='error')
|
||||
|
||||
|
||||
if ip_count > 16:
|
||||
res = False
|
||||
flash(_('More than 16 addresses is not supported'), category='error')
|
||||
|
|
Loading…
Reference in New Issue