summaryrefslogtreecommitdiffstats
path: root/ogcp
diff options
context:
space:
mode:
authorAlejandro Sirgo Rica <asirgo@soleta.eu>2024-08-23 12:17:41 +0200
committerAlejandro Sirgo Rica <asirgo@soleta.eu>2024-08-30 13:34:55 +0200
commit31766a3d07549e846c61ead624b4a45f88948fc0 (patch)
treef2f06975e74aeef719a875de805cffeb59ffaba2 /ogcp
parentbcd18241c7bf0363d00b2203c294d443b22d7807 (diff)
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.
Diffstat (limited to 'ogcp')
-rw-r--r--ogcp/forms/action_forms.py5
-rw-r--r--ogcp/templates/actions/delete_repo.html29
-rw-r--r--ogcp/templates/actions/repo_details.html20
-rw-r--r--ogcp/templates/actions/repos_add.html7
-rw-r--r--ogcp/templates/actions/repos_update.html7
-rw-r--r--ogcp/templates/repo_inspector.html63
-rw-r--r--ogcp/views.py84
7 files changed, 173 insertions, 42 deletions
diff --git a/ogcp/forms/action_forms.py b/ogcp/forms/action_forms.py
index 496be56..5a2a4b1 100644
--- a/ogcp/forms/action_forms.py
+++ b/ogcp/forms/action_forms.py
@@ -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):
diff --git a/ogcp/templates/actions/delete_repo.html b/ogcp/templates/actions/delete_repo.html
index 17902dd..8c32a8c 100644
--- a/ogcp/templates/actions/delete_repo.html
+++ b/ogcp/templates/actions/delete_repo.html
@@ -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 %}
diff --git a/ogcp/templates/actions/repo_details.html b/ogcp/templates/actions/repo_details.html
index 674abc2..212a63b 100644
--- a/ogcp/templates/actions/repo_details.html
+++ b/ogcp/templates/actions/repo_details.html
@@ -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 %}
diff --git a/ogcp/templates/actions/repos_add.html b/ogcp/templates/actions/repos_add.html
index 338f96c..19f02bf 100644
--- a/ogcp/templates/actions/repos_add.html
+++ b/ogcp/templates/actions/repos_add.html
@@ -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 %}
diff --git a/ogcp/templates/actions/repos_update.html b/ogcp/templates/actions/repos_update.html
index 15c6b64..1278648 100644
--- a/ogcp/templates/actions/repos_update.html
+++ b/ogcp/templates/actions/repos_update.html
@@ -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 %}
diff --git a/ogcp/templates/repo_inspector.html b/ogcp/templates/repo_inspector.html
new file mode 100644
index 0000000..ccedf5f
--- /dev/null
+++ b/ogcp/templates/repo_inspector.html
@@ -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> \ No newline at end of file
diff --git a/ogcp/views.py b/ogcp/views.py
index 007ca91..86006ba 100644
--- a/ogcp/views.py
+++ b/ogcp/views.py
@@ -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,