diff --git a/app/forms.py b/app/forms.py index 6534572..67b5295 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,6 +1,6 @@ from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, IntegerField, SelectField -from wtforms.validators import DataRequired, Length, IPAddress, NumberRange +from wtforms import StringField, PasswordField, IntegerField, SelectField, TextAreaField +from wtforms.validators import DataRequired, Length, IPAddress class LoginForm(FlaskForm): username = StringField(validators=[DataRequired(),Length(max=128)]) @@ -15,10 +15,12 @@ class NewUserForm(FlaskForm): username = StringField(validators=[DataRequired(),Length(max=128)]) password = PasswordField(validators=[DataRequired(),Length(max=128)]) is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()]) + is_readonly = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()]) class ChangePasswordForm(FlaskForm): password = PasswordField(validators=[DataRequired(),Length(max=128)]) is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()]) + is_readonly = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()]) class ChangeAllocationForm(FlaskForm): hostname = StringField(validators=[Length(max=128)]) @@ -26,6 +28,22 @@ class ChangeAllocationForm(FlaskForm): is_special = SelectField(choices=[(0,'None'),(1,'DHCP'),(2,'Gateway')],validators=[DataRequired()],label='Special USE ?') class NewInventoryItem(FlaskForm): - item_name = StringField(validators=[DataRequired(),Length(max=128)]) - item_desc = PasswordField(validators=[Length(max=128)]) - item_type = SelectField(choices=[(1,'Computer'),(2,'Server'),(3,'Virtual Machine'), (4,'Network Device'), (0,'Other')],validators=[DataRequired()]) \ No newline at end of file + item_name = StringField(validators=[DataRequired(),Length(max=64)]) + item_desc = StringField(validators=[Length(max=64)]) + item_type = SelectField(choices=[(1,'Computer'),(2,'Server'),(3,'Virtual Machine'), (4,'Network Device'), (0,'Other')],validators=[DataRequired()]) + +class ChangeInventoryItem(FlaskForm): + item_name = StringField(validators=[DataRequired(),Length(max=64)]) + item_desc = StringField(validators=[Length(max=64)]) + item_type = SelectField(choices=[(1,'Computer'),(2,'Server'),(3,'Virtual Machine'), (4,'Network Device'), (0,'Other')],validators=[DataRequired()]) + # Hardware Information + item_model = StringField(validators=[Length(max=64)]) + item_manufacturer = StringField(validators=[Length(max=64)]) + item_serial_number = StringField(validators=[Length(max=64)]) + # Network Information + item_os = StringField(validators=[Length(max=64)]) + item_location = StringField(validators=[Length(max=64)]) + item_owner = StringField(validators=[Length(max=64)]) + item_contact = StringField(validators=[Length(max=64)]) + # Allocation(s) + item_ip_allocations = StringField() \ No newline at end of file diff --git a/app/models.py b/app/models.py index 8366b87..0dffc92 100644 --- a/app/models.py +++ b/app/models.py @@ -6,6 +6,7 @@ class User(UserMixin, db.Model): username = db.Column(db.String(16)) password = db.Column(db.String(32),unique=True) is_admin = db.Column(db.Boolean(),default=False) + is_reado = db.Column(db.Boolean(),default=False) dat_last = db.Column(db.DateTime()) class Network(db.Model): @@ -40,4 +41,6 @@ class Inventory(db.Model): host_os = db.Column(db.String(64)) host_location = db.Column(db.String(64)) host_owner = db.Column(db.String(64)) - host_contact = db.Column(db.String(64)) \ No newline at end of file + host_contact = db.Column(db.String(64)) + ## Allocation(s) + host_allocations = db.Column(db.Text()) \ No newline at end of file diff --git a/app/routes/r_adm.py b/app/routes/r_adm.py index 6035aec..c1f186e 100644 --- a/app/routes/r_adm.py +++ b/app/routes/r_adm.py @@ -26,10 +26,13 @@ def home(): return redirect(url_for('admin.home')) is_admin = False + is_readonly = False if int(form.is_admin.data) == 1: is_admin = True + if int(form.is_readonly.data) == 1: + is_readonly = True - db.session.add(User(username=form.username.data, password=generate_password_hash(form.password.data), is_admin=is_admin)) + db.session.add(User(username=form.username.data, password=generate_password_hash(form.password.data), is_admin=is_admin, is_reado=is_readonly)) db.session.commit() flash("User Created", 'success') return redirect(url_for('admin.home')) @@ -67,8 +70,11 @@ def user_modal_edit(user_id): if form.validate_on_submit() and request.method == 'POST': user.password = generate_password_hash(form.password.data) user.is_admin = False + user.is_reado = False if int(form.is_admin.data) == 1: user.is_admin = True + if int(form.is_readonly.data) == 1: + user.is_reado = True db.session.commit() flash("Informations Changed", 'success') return redirect(url_for('admin.home')) diff --git a/app/routes/r_inventory.py b/app/routes/r_inventory.py index 2afa9e6..fbdc093 100644 --- a/app/routes/r_inventory.py +++ b/app/routes/r_inventory.py @@ -1,19 +1,18 @@ -from datetime import datetime -from flask import Blueprint, request, render_template, flash, redirect, url_for, session +from flask import Blueprint, request, render_template, flash, redirect, url_for, abort from flask_login import login_required, current_user -from ipaddress import IPv4Network, IPv4Address - -from app._config import app_version from app.extensions import * from app.models import * -from app.forms import NewInventoryItem +from app.forms import NewInventoryItem, ChangeInventoryItem inventory = Blueprint('inventory', __name__) @inventory.route('/add', methods=['GET','POST']) @login_required def add_item(): + if current_user.is_reado: + return abort(403) + form = NewInventoryItem() if form.validate_on_submit() and request.method == 'POST': add_net = Inventory(host_type=int(form.item_type.data),host_name=str(form.item_name.data),host_desc=str(form.item_desc.data)) @@ -21,22 +20,210 @@ def add_item(): db.session.commit() flash("Item ADDED", 'success') - return render_template('panel/inventory_add.html', title='ADD ITEM', form=form) + return render_template('panel/inventory_add.html', title='ADD ITEM', form=form, navcolor='success') @inventory.route('/list/', methods=['GET','POST']) @login_required def list_items(item_type): - db_items = Inventory.query.filter_by(host_type=item_type).all() - return render_template('panel/inventory_view.html', Title='View Items', item_list=db_items) + if item_type == 5: + db_items = Inventory.query.all() + else: + db_items = Inventory.query.filter_by(host_type=item_type).all() + + if item_type == 0: + title = 'Others' + elif item_type == 1: + title = 'Computers' + elif item_type == 2: + title = 'Servers' + elif item_type == 3: + title = 'VM\'s' + elif item_type == 4: + title = 'Network' + else: + title = 'All' + + return render_template('panel/inventory_view.html', title=title, item_list=db_items, current_page=item_type, navcolor='success') @inventory.route('/action//', methods=['GET','POST']) @login_required def action_item(item_id,action): + if current_user.is_reado: + return abort(403) if action == 1: db_item = Inventory.query.filter_by(id=item_id).first() - + db_allocs = Allocation.query.filter_by(device_id=db_item.id).all() + # Delete Link Allocation(s) + if db_allocs != []: + for alloc in db_allocs: + alloc.device_id = None + db.session.delete(db_item) db.session.commit() - return redirect(url_for('base.dashboard')) \ No newline at end of file + return redirect(url_for('inventory.list_items',item_type=request.args.get('page', type=int))) + +## MODAL ROUTES + +@inventory.get('//view_modal') +@login_required +def item_view_modal(item_id): + db_item = Inventory.query.filter_by(id=item_id).first() + modal = { + 'title': f'Informations of {db_item.host_name} ({db_item.id})', + 'modal_type': 'infos', + 'color': 'info', + 'action': 'delete', + 'action_text': 'Delete', + 'action_url': url_for('inventory.action_item',item_id=item_id,action=1) + } + + if db_item.host_type == 0: + host_type = 'Others' + elif db_item.host_type == 1: + host_type = 'Computers' + elif db_item.host_type == 2: + host_type = 'Servers' + elif db_item.host_type == 3: + host_type = 'VM\'s' + elif db_item.host_type == 4: + host_type = 'Network' + else: + host_type = 'Error' + + informations = [ + ('Type',host_type,'col-6'), + ('Name',db_item.host_name,'col-6'), + ('Description',db_item.host_desc,'col-6'), + ('Model',db_item.host_model,'col-6'), + ('Manufacturer',db_item.host_manufacturer,'col-6'), + ('SerialNumber',db_item.host_serialnumber,'col-6'), + ('OS',db_item.host_os,'col-6'), + ('Location',db_item.host_location,'col-6'), + ('Owner',db_item.host_owner,'col-6'), + ('Contact',db_item.host_contact,'col-6'), + ('Allocations',db_item.host_allocations,'col-12'), + ] + + return render_template('panel/_modal.html', modal=modal, item_id=item_id, informations=informations, old_page=request.args.get('page', type=int), pagination_active=True) + +@inventory.get('//del_modal') +@login_required +def item_del_modal(item_id): + db_item = Inventory.query.filter_by(id=item_id).first() + modal = { + 'title': 'Delete Item', + 'body': f'Are you sure you want to delete {db_item.host_name} ?', + 'color': 'danger', + 'action': 'delete', + 'action_text': 'Delete', + 'action_url': url_for('inventory.action_item',item_id=item_id,action=1) + } + return render_template('panel/_modal.html', modal=modal, item_id=item_id, old_page=request.args.get('page', type=int), pagination_active=True) + +@inventory.route('//edit_modal', methods=['GET','POST']) +@login_required +def item_edit_modal(item_id): + if current_user.is_reado: + return abort(403) + + db_item = Inventory.query.filter_by(id=item_id).first() + + form = ChangeInventoryItem() + if form.validate_on_submit() and request.method == 'POST': + + db_item.host_type = form.item_type.data + db_item.host_name = form.item_name.data + # Description + if form.item_desc.data != '' and form.item_desc.data != 'None': + db_item.host_desc = form.item_desc.data + else: + db_item.host_desc = None + # Model + if form.item_model.data != '' and form.item_model.data != 'None': + db_item.host_model = form.item_model.data + else: + db_item.host_model = None + # Serial Number + if form.item_serial_number.data != '' and form.item_serial_number.data != 'None': + db_item.host_serialnumber = form.item_serial_number.data + else: + db_item.host_serialnumber = None + # OS + if form.item_os.data != '' and form.item_os.data != 'None': + db_item.host_os = form.item_os.data + else: + db_item.host_os = None + # Location + if form.item_location.data != '' and form.item_location.data != 'None': + db_item.host_location = form.item_location.data + else: + db_item.host_location = None + # Owner + if form.item_owner.data != '' and form.item_owner.data != 'None': + db_item.host_owner = form.item_owner.data + else: + db_item.host_owner = None + # Contact + if form.item_contact.data != '' and form.item_contact.data != 'None': + db_item.host_contact = form.item_contact.data + else: + db_item.host_contact = None + + # Allocations + db_existing_allocs = Allocation.query.filter_by(device_id=db_item.id).all() + # Unlink old Allocation(s) + if db_existing_allocs != []: + for alloc in db_existing_allocs: + alloc.device_id = None + + if form.item_ip_allocations.data != '' and form.item_ip_allocations.data != 'None': + + allocs = form.item_ip_allocations.data.split(',') + put_allocs_db = "" + for alloc in allocs: + db_alloc = Allocation.query.filter_by(ipv4=alloc).first() + if db_alloc != None: + put_allocs_db += db_alloc.ipv4+',' + db_alloc.device_id = db_item.id + elif allocs[-1] == '': + pass + else: + flash('Unknown IPv4', 'warning') + return redirect(url_for('inventory.list_items',item_type=request.args.get('page', type=int))) + + db_item.host_allocations = put_allocs_db + + else: + db_item.host_allocations = None + + db.session.commit() + flash("Item Updated", 'success') + return redirect(url_for('inventory.list_items',item_type=request.args.get('page', type=int))) + + # Setting default values + form.item_name.default = db_item.host_name + form.item_desc.default = db_item.host_desc + form.item_type.default = db_item.host_type + form.item_model.default =db_item.host_model + form.item_manufacturer.default = db_item.host_manufacturer + form.item_serial_number.default = db_item.host_serialnumber + form.item_os.default = db_item.host_os + form.item_location.default = db_item.host_location + form.item_owner.default = db_item.host_owner + form.item_contact.default = db_item.host_contact + form.item_ip_allocations.default = db_item.host_allocations + form.process() + + modal = { + 'title': 'Update Item', + 'body': f'Change informations for {db_item.host_name}.', + 'color': 'primary', + 'modal_type': 'form', + 'action_text': 'Save', + 'form_url': url_for('inventory.item_edit_modal',item_id=item_id), + 'inventory':True, + } + + return render_template('panel/_modal.html', modal=modal, form=form, old_page=request.args.get('page', type=int), pagination_active=True) \ No newline at end of file diff --git a/app/routes/r_networks.py b/app/routes/r_networks.py index 81a980d..98cb0ed 100644 --- a/app/routes/r_networks.py +++ b/app/routes/r_networks.py @@ -1,5 +1,4 @@ -from datetime import datetime -from flask import Blueprint, request, render_template, flash, redirect, url_for, session +from flask import Blueprint, request, render_template, flash, redirect, url_for, session, abort from flask_login import login_required, current_user from ipaddress import IPv4Network, IPv4Address @@ -16,25 +15,26 @@ network = Blueprint('network', __name__) def networks(): networks = Network.query.all() form = NewPrefixForm() - if request.method == 'POST' and form.validate_on_submit(): - try: - network = IPv4Network(f"{str(form.subnet.data)}/{str(form.prefix.data)}", False) - - add_net = Network(network_address=str(network.network_address),prefix=network.prefixlen,hosts=network.num_addresses-2,description=str(form.description.data)) - db.session.add(add_net) - db.session.commit() - - for ip in network.hosts(): - db.session.add(Allocation(ipv4=str(ip), net_id=add_net.id)) - - db.session.commit() - return redirect(url_for('network.networks')) - - except: - flash("Invalid Subnet or Prefix", 'danger') - return redirect(url_for('network.networks')) - elif form.errors: - flash(form.errors, 'danger') + if current_user.is_reado == False: + if request.method == 'POST' and form.validate_on_submit(): + try: + network = IPv4Network(f"{str(form.subnet.data)}/{str(form.prefix.data)}", False) + + add_net = Network(network_address=str(network.network_address),prefix=network.prefixlen,hosts=network.num_addresses-2,description=str(form.description.data)) + db.session.add(add_net) + db.session.commit() + + for ip in network.hosts(): + db.session.add(Allocation(ipv4=str(ip), net_id=add_net.id)) + + db.session.commit() + return redirect(url_for('network.networks')) + + except: + flash("Invalid Subnet or Prefix", 'danger') + return redirect(url_for('network.networks')) + elif form.errors: + flash(form.errors, 'danger') return render_template('panel/networks.html', title="Prefixes", is_networks=True, app_version=app_version, prefixes=networks, form=form, Allocation=Allocation, navcolor='dark') @@ -95,6 +95,9 @@ def allocation_del_modal(net_id,ipv4): @network.route('//allocations//edit_modal', methods=['GET','POST']) @login_required def allocation_edit_modal(net_id, ipv4): + if current_user.is_reado: + return abort(403) + db_alloc = Allocation.query.filter_by(ipv4=ipv4).first() form = ChangeAllocationForm() @@ -130,15 +133,28 @@ def allocation_edit_modal(net_id, ipv4): 'action_text': 'Save', 'form_url': url_for('network.allocation_edit_modal',net_id=db_alloc.net_id,ipv4=db_alloc.ipv4), } + old_page = request.args.get('page', type=int) if old_page == 0: old_page = 1 + + form.hostname.default = db_alloc.hostname + form.description.default = db_alloc.description + if db_alloc.is_dhcp: + form.is_special.default = 1 + elif db_alloc.is_gateway: + form.is_special.default = 2 + form.process() + return render_template('panel/_modal.html', modal=modal, form=form, old_page=old_page, pagination_active=True) ## MODIFICATION ROUTES @network.get('//allocations//unallocate') @login_required def allocation_delete(net_id,ipv4): + if current_user.is_reado: + return abort(403) + db_alloc = Allocation.query.filter_by(net_id=net_id,ipv4=ipv4).first() db_alloc.is_used = False db_alloc.hostname = None @@ -152,6 +168,9 @@ def allocation_delete(net_id,ipv4): @network.get('//allocations//decom/') @login_required def allocation_decom(net_id,ipv4,action): + if current_user.is_reado: + return abort(403) + db_alloc = Allocation.query.filter_by(net_id=net_id,ipv4=ipv4).first() if action == 1: db_alloc.is_decom = True @@ -163,6 +182,9 @@ def allocation_decom(net_id,ipv4,action): @network.get('//delete') @login_required def network_delete(net_id): + if current_user.is_reado: + return abort(403) + db_network = Network.query.filter_by(id=net_id).first() db_alloc = Allocation.query.filter_by(net_id=db_network.id).all() diff --git a/app/templates/admin/home.html b/app/templates/admin/home.html index fc6bccc..3ec7fa4 100644 --- a/app/templates/admin/home.html +++ b/app/templates/admin/home.html @@ -22,6 +22,7 @@ ID Username Admin User + RO Last Connection Actions @@ -32,6 +33,7 @@ {{user.id}} {{user.username}} {{user.is_admin}} + {{user.is_reado}} {% if user.dat_last != None %}{{user.dat_last.strftime('%Y-%m-%d %H:%M:%S')}}{%else%}N/A{%endif%} diff --git a/app/templates/panel/_modal.html b/app/templates/panel/_modal.html index ac918b8..7b49fd3 100644 --- a/app/templates/panel/_modal.html +++ b/app/templates/panel/_modal.html @@ -1,5 +1,5 @@ {% if modal.modal_type == 'form' %} -