ogcp: add support for multi-ip repositories

Add support for the new API REST for repository management where
the address is a list of ips instead of a single string.

Add dynamic address creation in /action/repo/update and
/action/repo/add forms through delete and add buttons in the form.

Update /image/restore and /cache/fetch to use repository_id.

Add additional repository form validations.
master
Alejandro Sirgo Rica 2024-08-23 12:17:41 +02:00
parent bcd18241c7
commit 31766a3d07
7 changed files with 173 additions and 42 deletions

View File

@ -122,7 +122,10 @@ class RepoForm(FlaskForm):
server = HiddenField()
repo_id = HiddenField()
name = StringField(label=_l('Name'))
ip = StringField(label=_l('IP'))
addr = FieldList(
StringField(label=_l('Address')),
label=_l('Addresses'),
)
submit = SubmitField(label=_l('Submit'))
class FolderForm(FlaskForm):

View File

@ -9,9 +9,28 @@
<h2 class="mx-5 subhead-heading">{{_('Delete repo')}}</h2>
{{ wtf.quick_form(form,
action=url_for('action_repo_delete'),
method='post',
button_map={'submit': 'danger'},
extra_classes="mx-5") }}
<form class="form mx-5" method="POST">
{{ form.hidden_tag() }}
{{ form.server() }}
{{ form.repo_id() }}
<div class="form-group">
{{ form.name.label }}
{{ form.name(class="form-control") }}
</div>
<div class="form-group" id="ip-fields">
{{ form.addr.label }}
{% for addr in form.addr %}
{{ addr(class="form-control mb-2") }}
{% endfor %}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
{% endblock %}

View File

@ -9,5 +9,23 @@
<h2 class="mx-5 subhead-heading">{{_('Repo details')}}</h2>
{{ wtf.quick_form(form, extra_classes="mx-5") }}
<form class="form mx-5" method="POST">
{{ form.hidden_tag() }}
{{ form.server() }}
{{ form.repo_id() }}
<div class="form-group">
{{ form.name.label }}
{{ form.name(class="form-control") }}
</div>
<div class="form-group" id="ip-fields">
{{ form.addr.label }}
{% for addr in form.addr %}
{{ addr(class="form-control mb-2") }}
{% endfor %}
</div>
</form>
{% endblock %}

View File

@ -9,9 +9,6 @@
<h2 class="mx-5 subhead-heading">{{_('Add repo')}}</h2>
{{ wtf.quick_form(form,
action=url_for('action_repo_add'),
method='post',
button_map={'submit': 'primary'},
extra_classes="mx-5") }}
{% include 'repo_inspector.html' %}
{% endblock %}

View File

@ -9,9 +9,6 @@
<h2 class="mx-5 subhead-heading">{{_('Update repo')}}</h2>
{{ wtf.quick_form(form,
action=url_for('action_repo_update'),
method='post',
button_map={'submit': 'primary'},
extra_classes="mx-5") }}
{% include 'repo_inspector.html' %}
{% endblock %}

View File

@ -0,0 +1,63 @@
<form class="form mx-5" method="POST">
{{ form.hidden_tag() }}
{{ form.server() }}
{{ form.repo_id() }}
<div class="form-group">
{{ form.name.label }}
{{ form.name(class="form-control", required=True) }}
</div>
<div class="form-group">
{{ form.addr.label }}
<div id="ip-fields">
{% for addr in form.addr %}
<div class="d-flex align-items-center mb-2">
{{ addr(class="form-control me-2", placeholder=_("Enter IP Address"), required=True) }}
<button type="button" class="btn btn-danger" onclick="removeIPField(this)">{{_('Remove') }}</button>
</div>
{% endfor %}
</div>
<button type="button" class="btn btn-primary" onclick="addIPField()">{{_('Add address') }}</button>
{{ form.submit(class="btn btn-success") }}
</div>
</form>
<script>
function addIPField() {
const container = document.createElement('div');
container.classList.add('d-flex', 'align-items-center', 'mb-2');
const newField = document.createElement('input');
newField.setAttribute('type', 'text');
newField.setAttribute('name', 'addr-' + document.querySelectorAll('input[name^="addr-"]').length);
newField.classList.add('form-control', 'me-2');
newField.setAttribute('placeholder', '{{ _('Enter IP Address') }}');
newField.required = true;
const removeButton = document.createElement('button');
removeButton.setAttribute('type', 'button');
removeButton.classList.add('btn', 'btn-danger');
removeButton.innerText = '{{ _('Remove') }}';
removeButton.onclick = function() {
removeIPField(this);
};
container.appendChild(newField);
container.appendChild(removeButton);
document.getElementById('ip-fields').appendChild(container);
}
function removeIPField(elem) {
const ipFieldsContainer = document.getElementById('ip-fields');
const ipFieldDivs = ipFieldsContainer.querySelectorAll('.d-flex');
if (ipFieldDivs.length <= 1) {
return;
}
const parentDiv = elem.parentElement;
parentDiv.remove();
}
</script>

View File

@ -30,6 +30,7 @@ from ogcp.og_server import OGServer, servers
from flask_babel import lazy_gettext as _l
from flask_babel import gettext, _
from ogcp import app, ogcp_cfg_path
from wtforms import StringField
import unicodedata
import ipaddress
import requests
@ -952,17 +953,10 @@ def action_image_restore():
if requires_cache and not image_fits_in_cache(server, clients_info, image):
return redirect(url_for('commands'))
try:
repository = get_repository(image['repo_id'], server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
payload = {'disk': disk,
'partition': partition,
'name': image['name'],
'repository': repository['ip'],
'repository_id': image['repo_id'],
'clients': ips,
'type': form.method.data,
'profile': str(image['software_id']),
@ -1347,15 +1341,8 @@ def action_image_fetch():
flash(_(f'Image to fetch was not found'), category='error')
return redirect(url_for('commands'))
try:
repository = get_repository(image['repo_id'], server)
except ServerError:
return ogserver_down('commands')
except ServerErrorCode:
return ogserver_error('commands')
payload = {'clients': ips,
'repository': repository['ip'],
'repository_id': image['repo_id'],
'type': form.method.data,
'image': image['name']}
@ -3040,6 +3027,34 @@ def manage_repos():
responses = multi_request('get', '/repositories')
return render_template('repos.html', repos_resp=responses)
def repo_addr_is_valid(form):
invalid_ips = []
empty_ips = 0
ip_count = 0
for ip in form.addr:
ip_count += 1
ip = ip.data.strip()
if not ip:
empty_ips += 1
continue
if not is_valid_ip(ip):
invalid_ips.append('"' + ip + '"')
res = True
if empty_ips > 0:
res = False
flash(_(f'{empty_ips} addresses are invalid'), category='error')
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')
return res
@app.route('/action/repo/add', methods=['POST', 'GET'])
@login_required
def action_repo_add():
@ -3048,8 +3063,14 @@ def action_repo_add():
if not form.validate():
flash(form.errors, category='error')
return redirect(url_for('manage_repos'))
if not repo_addr_is_valid(form):
return redirect(url_for('manage_repos'))
addr = [ip.data.strip() for ip in form.addr]
payload = {"name": form.name.data,
"ip": form.ip.data,
"addr": addr,
"center": 1}
server = get_server_from_ip_port(form.server.data)
r = server.post('/repository/add', payload)
@ -3068,6 +3089,7 @@ def action_repo_add():
return redirect(url_for('manage_repos'))
form.server.data = params['repos-server']
form.addr.append_entry('')
responses = multi_request('get', '/repositories')
return render_template('actions/repos_add.html', form=form,
@ -3079,9 +3101,15 @@ def action_repo_update():
form = RepoForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = { 'repo_id': int(form.repo_id.data),
if not repo_addr_is_valid(form):
return redirect(url_for('manage_repos'))
addr = [ip.data.strip() for ip in form.addr]
payload = { 'id': int(form.repo_id.data),
'name': form.name.data,
'ip': form.ip.data,
'addr': addr,
'center': 1}
r = server.post('/repository/update', payload)
if not r:
@ -3115,7 +3143,8 @@ def action_repo_update():
form.server.data = server_ip_port
form.repo_id.data = repo_id
form.name.data = repository['name']
form.ip.data = repository['ip']
for addr in repository['addr']:
form.addr.append_entry(addr)
responses = multi_request('get', '/repositories')
return render_template('actions/repos_update.html', form=form,
@ -3127,7 +3156,7 @@ def action_repo_delete():
form = RepoForm(request.form)
if request.method == 'POST':
server = get_server_from_ip_port(form.server.data)
payload = { 'id': form.repo_id.data }
payload = { 'id': int(form.repo_id.data) }
r = server.post('/repository/delete', payload)
if not r:
return ogserver_down('manage_repos')
@ -3165,8 +3194,11 @@ def action_repo_delete():
form.repo_id.data = repo_id
form.name.data = repository['name']
form.name.render_kw = {'readonly': True}
form.ip.data = repository['ip']
form.ip.render_kw = {'readonly': True}
form.name.data = repository['name']
for addr in repository['addr']:
form.addr.append_entry(addr)
for field in form.addr:
field.render_kw = {'readonly': True}
responses = multi_request('get', '/repositories')
return render_template('actions/delete_repo.html', form=form,
repos_resp=responses)
@ -3199,8 +3231,10 @@ def action_repo_info():
return ogserver_error('manage_repos')
form.name.data = repository['name']
form.name.render_kw = {'readonly': True}
form.ip.data = repository['ip']
form.ip.render_kw = {'readonly': True}
for addr in repository['addr']:
form.addr.append_entry(addr)
for field in form.addr:
field.render_kw = {'readonly': True}
form.submit.render_kw = {"style": "visibility:hidden;"}
responses = multi_request('get', '/repositories')
return render_template('actions/repo_details.html', form=form,