Skip to content
Snippets Groups Projects
Commit 83352512 authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Implement UI to register hosts

parent 7ac9c09d
No related branches found
No related tags found
No related merge requests found
......@@ -42,8 +42,16 @@ class HostForm(FlaskForm):
# The list of IPs is dynamically created on the browser side
# depending on the selected network
ip = NoValidateSelectField('IP', choices=[])
name = StringField('Hostname')
name = StringField('Hostname',
description='hostname must be 2-20 characters long and contain only letters, numbers and dash',
validators=[validators.InputRequired(),
validators.Regexp(models.HOST_NAME_RE)],
filters=[utils.lowercase_field])
description = StringField('Description')
mac_id = SelectField('MAC')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.network_id.choices = [(str(network.id), network.address) for network in models.Network.query.all()]
self.network_id.choices = [(str(network.id), network.vlan_name) for network in models.Network.query.all()]
self.mac_id.choices = [('', '')]
self.mac_id.choices.extend([(str(mac.id), mac.address) for mac in models.Mac.query.all()])
......@@ -65,10 +65,17 @@ def index():
@bp.route('/items')
@login_required
def items_index():
def list_items():
return render_template('items.html')
@bp.route('/items/create', methods=('GET', 'POST'))
@login_groups_accepted('admin', 'create')
def create_item():
form = None
return render_template('create_item.html', form=form)
@bp.route('/view/<ics_id>')
@login_required
def view_item(ics_id):
......@@ -119,9 +126,15 @@ def retrieve_qrcodes_name(kind):
return jsonify(data=data)
@bp.route('/hosts', methods=('GET', 'POST'))
@bp.route('/hosts')
@login_required
def list_hosts():
return render_template('hosts.html')
@bp.route('/hosts/create', methods=('GET', 'POST'))
@login_groups_accepted('admin', 'create')
def hosts_index():
def create_host():
# Try to get the network_id from the URL parameters
# to display the form with the same selected network
# when reloading the page (redirect after submit)
......@@ -135,7 +148,9 @@ def hosts_index():
if form.validate_on_submit():
host = models.Host(ip=form.ip.data,
network_id=form.network_id.data,
name=form.name.data)
name=form.name.data,
description=form.description.data or None,
mac_id=form.mac_id.data or None)
current_app.logger.debug(f'Trying to create: {host!r}')
db.session.add(host)
try:
......@@ -144,14 +159,20 @@ def hosts_index():
db.session.rollback()
current_app.logger.warning(f'{e}')
flash(f'{e}', 'error')
return redirect(url_for('main.hosts_index', network_id=host.network_id))
return render_template('hosts.html', form=form)
else:
flash(f'Host {host} created!', 'success')
return redirect(url_for('main.create_host', network_id=host.network_id))
return render_template('create_host.html', form=form)
@bp.route('/_retrieve_hosts')
@login_required
def retrieve_hosts():
data = [(host.name, host.ip, host.network.vlan_name) for host in models.Host.query.all()]
data = [(host.name,
host.ip,
host.description,
str(host.mac),
host.network.vlan_name) for host in models.Host.query.all()]
return jsonify(data=data)
......
......@@ -27,6 +27,7 @@ from . import utils
ICS_ID_RE = re.compile('[A-Z]{3}[0-9]{3}')
HOST_NAME_RE = re.compile('^[a-z0-9\-]{2,20}$')
make_versioned(plugins=[FlaskUserPlugin()])
......@@ -420,12 +421,23 @@ class Host(db.Model):
else:
super().__init__(ip=ip, **kwargs)
@validates('name')
def validate_name(self, key, string):
"""Ensure the hostname matches the required format"""
if string is None:
return None
# Force the string to lowercase
lower_string = string.lower()
if HOST_NAME_RE.fullmatch(lower_string) is None:
raise ValidationError('Host name shall match [a-z0-9\-]{2,20}')
return lower_string
@property
def address(self):
return ipaddress.ip_address(self.ip)
def __str__(self):
return str(self.ip)
return str(self.name)
def __repr__(self):
return f'Host(id={self.id}, network_id={self.network_id}, ip={self.ip}, name={self.name}, description={self.description}, mac={self.mac})'
......
......@@ -15,3 +15,27 @@
{{ link_to_item(ics_id) }}
{% endfor %}
{%- endmacro %}
{% macro render_field(field) -%}
{% set field_class = kwargs.pop('class_', '') + ' form-control' %}
{% if field.errors %}
{% set field_class = field_class + ' is-invalid' %}
{% endif %}
<div class="form-group row">
{{ field.label(class_="col-sm-2 col-form-label") }}
<div class="col-sm-10">
{{ field(class_=field_class) }}
{% if field.description %}
<small class="form-text text-muted">
{{ field.description|safe }}
</small>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">
{{ field.errors|join(' / ') }}
</div>
{% endif %}
</div>
</div>
{%- endmacro %}
......@@ -23,8 +23,8 @@
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="navbar-nav mr-auto">
{% set path = request.path %}
<a class="nav-item nav-link {{ is_active(path.startswith("/items")) }}" href="{{ url_for('main.items_index') }}">Items</a>
<a class="nav-item nav-link {{ is_active(path == "/hosts") }}" href="{{ url_for('main.hosts_index') }}">Hosts</a>
<a class="nav-item nav-link {{ is_active(path.startswith("/items")) }}" href="{{ url_for('main.list_items') }}">Items</a>
<a class="nav-item nav-link {{ is_active(path.startswith("/hosts")) }}" href="{{ url_for('main.list_hosts') }}">Hosts</a>
<div class="dropdown {{ is_active(path.startswith("/qrcodes")) }}">
<a class="nav-link dropdown-toggle" href="#" id="qrcodesDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
QR Codes
......
{%- extends "base.html" %}
{% from "_helpers.html" import render_field %}
{% block title %}Hosts - CSEntry{% endblock %}
{% block main %}
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.list_hosts') }}">List hosts</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('main.create_host') }}">Register new host</a>
</li>
</ul>
<br>
<form id="hostForm" method="POST">
{{ form.hidden_tag() }}
{{ render_field(form.network_id) }}
{{ render_field(form.ip) }}
{{ render_field(form.name, class_="text-lowercase") }}
{{ render_field(form.description) }}
{{ render_field(form.mac_id) }}
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
{%- endblock %}
{% block csentry_scripts %}
<script src="{{ url_for('static', filename='js/hosts.js') }}"></script>
{% endblock %}
{%- extends "base.html" %}
{% block title %}Items - CSEntry{% endblock %}
{% block main %}
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.list_items') }}">List items</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('main.create_item') }}">Register new item</a>
</li>
</ul>
<br>
<div class="card">
<h4 class="card-header">Register new item</h4>
<div class="card-body">
</div>
</div>
{%- endblock %}
{% block csentry_scripts %}
<script src="{{ url_for('static', filename='js/items.js') }}"></script>
{% endblock %}
......@@ -3,45 +3,24 @@
{% block title %}Hosts - CSEntry{% endblock %}
{% block main %}
<div class="card">
<h4 class="card-header">Register new host</h4>
<div class="card-body">
<form id="hostForm" method="POST">
{{ form.hidden_tag() }}
<div class="form-group row">
{{ form.network_id.label(class_="col-sm-2 col-form-label") }}
<div class="col-sm-10">
{{ form.network_id(class_="form-control") }}
</div>
</div>
<div class="form-group row">
{{ form.ip.label(class_="col-sm-2 col-form-label") }}
<div class="col-sm-10">
{{ form.ip(class_="form-control") }}
</div>
</div>
<div class="form-group row">
{{ form.name.label(class_="col-sm-2 col-form-label") }}
<div class="col-sm-10">
{{ form.name(class_="form-control") }}
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
</div>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('main.list_hosts') }}">List hosts</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.create_host') }}">Register new host</a>
</li>
</ul>
<hr class="separator">
<br>
<table id="hosts_table" class="table table-bordered table-hover table-sm">
<thead>
<tr>
<th>Hostname</th>
<th>IP</th>
<th>Description</th>
<th>MAC</th>
<th>Network</th>
</tr>
</thead>
......
......@@ -3,13 +3,17 @@
{% block title %}Items - CSEntry{% endblock %}
{% block main %}
<div class="card">
<h4 class="card-header">Register new item</h4>
<div class="card-body">
</div>
</div>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('main.list_items') }}">List items</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.create_item') }}">Register new item</a>
</li>
</ul>
<br>
<hr class="separator">
<table id="items_table" class="table table-bordered table-hover table-sm">
<thead>
<tr>
......
......@@ -121,3 +121,11 @@ def get_query(query, args):
except (sa.exc.InvalidRequestError, AttributeError) as e:
raise CSEntryError('Invalid query arguments', status_code=422)
return query
def lowercase_field(value):
"""Filter to force form value to lowercase"""
try:
return value.lower()
except AttributeError:
return value
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment