14 Commits
Author SHA1 Message Date
Takeus a25eb0337a Merge pull request #4 from TakeusV1/DEV
Update models.py
2025-03-26 11:21:47 +01:00
Takeus c819bf68e2 Update models.py 2025-03-26 11:21:25 +01:00
Takeus 4f2270ff3e Merge pull request #3 from TakeusV1/DEV
Dev
2025-03-26 10:48:06 +01:00
Takeus ce14203857 Update sources.md 2025-03-26 10:45:31 +01:00
Takeus 02df3a3aca Update install.md 2025-03-26 10:44:26 +01:00
Takeus bb1fe1ed17 Merge pull request #2 from TakeusV1/PROD
Add DOCS
2025-03-26 10:42:40 +01:00
Takeus 33e2f83470 Update README.md 2025-03-26 10:37:49 +01:00
Takeus f0c1109a8b Create install.md 2025-03-26 10:34:56 +01:00
Takeus eda44fa57a Create README.md 2025-03-05 11:11:14 +01:00
Takeus 5bf33d178e Update r_networks.py 2025-02-27 10:16:27 +01:00
Takeus 91c9319147 V1.2.1 2025-02-27 10:16:09 +01:00
Takeus 9351f51c1e Update r_inventory.py 2025-02-18 16:20:40 +01:00
Takeus 06b7efd58b V1.2 2025-02-18 15:29:27 +01:00
Takeus eefc23fad8 DEV V1.2 2025-02-17 16:20:21 +01:00
20 changed files with 863 additions and 342 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
# Flask IPAM (FIPAM)
An IP address management application _(Passive)_ written in **python** with the Flask web framework (and modules).<br>
The application is designed to manage **small networks** (**/30** to **/22**) destined for use in a **LAB**. <br>
_(I'm not an experienced developer, just a tinkerer)_
---
![image](https://github.com/user-attachments/assets/a211c332-8ae8-4846-99f9-ebcda0d09c61)
![image](https://github.com/user-attachments/assets/40e240bd-481f-48bd-a67e-7007894f27b9)
---
![image](https://github.com/user-attachments/assets/44dac68f-e2e4-4d53-9158-9597de0dab5b)
![image](https://github.com/user-attachments/assets/5767a3dc-d99c-4c3e-bb88-c9c92049f58a)
---
- Installation : [here](install.md)
- Sources : [here](sources.md)
+4
View File
@@ -10,6 +10,8 @@ from app.models import *
from app.extensions import * from app.extensions import *
from app.routes.r_base import base from app.routes.r_base import base
from app.routes.r_networks import network
from app.routes.r_inventory import inventory
from app.routes.r_auth import auth from app.routes.r_auth import auth
from app.routes.r_adm import admin from app.routes.r_adm import admin
@@ -30,6 +32,8 @@ def create_app():
## BLUEPRINTS ## BLUEPRINTS
app.register_blueprint(base, url_prefix='/') app.register_blueprint(base, url_prefix='/')
app.register_blueprint(network, url_prefix='/network')
app.register_blueprint(inventory, url_prefix='/inventory')
app.register_blueprint(auth, url_prefix='/auth') app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(admin, url_prefix='/admin') app.register_blueprint(admin, url_prefix='/admin')
+1 -1
View File
@@ -8,7 +8,7 @@ app_debug = True
app_maintenance = False app_maintenance = False
app_name = 'takeus-ipam' app_name = 'takeus-ipam'
app_version = 'V1.1' app_version = 'V1.2'
app_release_date = '02/2025' app_release_date = '02/2025'
app_url = "http://127.0.0.1" app_url = "http://127.0.0.1"
+26 -9
View File
@@ -1,6 +1,6 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField, SelectField from wtforms import StringField, PasswordField, IntegerField, SelectField, TextAreaField
from wtforms.validators import DataRequired, Length, IPAddress, NumberRange from wtforms.validators import DataRequired, Length, IPAddress
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField(validators=[DataRequired(),Length(max=128)]) username = StringField(validators=[DataRequired(),Length(max=128)])
@@ -11,22 +11,39 @@ class NewPrefixForm(FlaskForm):
prefix = SelectField(choices=[(30,'/30'),(29,'/29'),(28,'/28'),(27,'/27'),(26,'/26'),(25,'/25'),(24,'/24'),(23,'/23'),(22,'/22')],validators=[DataRequired()]) prefix = SelectField(choices=[(30,'/30'),(29,'/29'),(28,'/28'),(27,'/27'),(26,'/26'),(25,'/25'),(24,'/24'),(23,'/23'),(22,'/22')],validators=[DataRequired()])
description = StringField(validators=[Length(max=128)]) description = StringField(validators=[Length(max=128)])
class NewAllocationForm(FlaskForm): class NewUserForm(FlaskForm):
ipv4 = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)])
host_name = StringField(validators=[Length(max=128)])
host_desc = StringField(validators=[Length(max=128)])
#host_type = SelectField(choices=[(0,'None'),(1,'Computer'),(2,'Server'),(3,'Virtual Machine'),(4,'Network Device'),(5,'Other')],validators=[DataRequired()])
class NewUserForm (FlaskForm):
username = StringField(validators=[DataRequired(),Length(max=128)]) username = StringField(validators=[DataRequired(),Length(max=128)])
password = PasswordField(validators=[DataRequired(),Length(max=128)]) password = PasswordField(validators=[DataRequired(),Length(max=128)])
is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()]) is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
is_readonly = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
class ChangePasswordForm(FlaskForm): class ChangePasswordForm(FlaskForm):
password = PasswordField(validators=[DataRequired(),Length(max=128)]) password = PasswordField(validators=[DataRequired(),Length(max=128)])
is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()]) is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
is_readonly = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
class ChangeAllocationForm(FlaskForm): class ChangeAllocationForm(FlaskForm):
hostname = StringField(validators=[Length(max=128)]) hostname = StringField(validators=[Length(max=128)])
description = StringField(validators=[Length(max=128)]) description = StringField(validators=[Length(max=128)])
is_special = SelectField(choices=[(0,'None'),(1,'DHCP'),(2,'Gateway')],validators=[DataRequired()],label='Special USE ?') 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=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()
+13 -11
View File
@@ -3,9 +3,10 @@ from flask_login import UserMixin
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
id = db.Column(db.Integer(),primary_key=True,unique=True) id = db.Column(db.Integer(),primary_key=True,unique=True)
username = db.Column(db.String(16)) username = db.Column(db.String(16),unique=True)
password = db.Column(db.String(32),unique=True) password = db.Column(db.String(32))
is_admin = db.Column(db.Boolean(),default=False) is_admin = db.Column(db.Boolean(),default=False)
is_reado = db.Column(db.Boolean(),default=False)
dat_last = db.Column(db.DateTime()) dat_last = db.Column(db.DateTime())
class Network(db.Model): class Network(db.Model):
@@ -28,17 +29,18 @@ class Allocation(db.Model):
class Inventory(db.Model): class Inventory(db.Model):
id = db.Column(db.Integer(),primary_key=True,unique=True) id = db.Column(db.Integer(),primary_key=True,unique=True)
ip_addr = db.Column(db.Text())
# Host Information # Host Information
host_type = db.Column(db.Integer()) host_type = db.Column(db.Integer())
host_name = db.Column(db.String(32)) host_name = db.Column(db.String(64))
host_desc = db.Column(db.String(64)) host_desc = db.Column(db.String(64))
# Hardware Information # Hardware Information
host_model = db.Column(db.String(32)) host_model = db.Column(db.String(64))
host_manufacturer = db.Column(db.String(32)) host_manufacturer = db.Column(db.String(64))
host_serialnumber = db.Column(db.String(32)) host_serialnumber = db.Column(db.String(64))
# Network Information # Network Information
host_os = db.Column(db.String(32)) host_os = db.Column(db.String(64))
host_location = db.Column(db.String(32)) host_location = db.Column(db.String(64))
host_owner = db.Column(db.String(32)) host_owner = db.Column(db.String(64))
host_contact = db.Column(db.String(32)) host_contact = db.Column(db.String(64))
## Allocation(s)
host_allocations = db.Column(db.Text())
+7 -1
View File
@@ -26,10 +26,13 @@ def home():
return redirect(url_for('admin.home')) return redirect(url_for('admin.home'))
is_admin = False is_admin = False
is_readonly = False
if int(form.is_admin.data) == 1: if int(form.is_admin.data) == 1:
is_admin = True 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() db.session.commit()
flash("User Created", 'success') flash("User Created", 'success')
return redirect(url_for('admin.home')) 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': if form.validate_on_submit() and request.method == 'POST':
user.password = generate_password_hash(form.password.data) user.password = generate_password_hash(form.password.data)
user.is_admin = False user.is_admin = False
user.is_reado = False
if int(form.is_admin.data) == 1: if int(form.is_admin.data) == 1:
user.is_admin = True user.is_admin = True
if int(form.is_readonly.data) == 1:
user.is_reado = True
db.session.commit() db.session.commit()
flash("Informations Changed", 'success') flash("Informations Changed", 'success')
return redirect(url_for('admin.home')) return redirect(url_for('admin.home'))
+4 -262
View File
@@ -1,24 +1,21 @@
from datetime import datetime from flask import Blueprint, render_template, redirect, url_for
from flask import Blueprint, request, render_template, flash, redirect, url_for, session
from flask_login import login_required, current_user from flask_login import login_required, current_user
from ipaddress import IPv4Network, IPv4Address
from app._config import app_version from app._config import app_version
from app.extensions import * from app.extensions import *
from app.models import * from app.models import *
from app.forms import NewPrefixForm, NewAllocationForm, ChangeAllocationForm
base = Blueprint('base', __name__) base = Blueprint('base', __name__)
@base.route('/',methods=['GET','POST']) @base.route('/')
def home(): def home():
if current_user.is_authenticated: if current_user.is_authenticated:
return redirect(url_for('base.dashboard')) return redirect(url_for('base.dashboard'))
else: else:
return redirect(url_for('auth.login')) return redirect(url_for('auth.login'))
@base.route('/dashboard',methods=['GET','POST']) @base.route('/dashboard')
@login_required @login_required
def dashboard(): def dashboard():
@@ -42,261 +39,6 @@ def dashboard():
db_inventory=db_inventory, db_inventory=db_inventory,
available_alloc=available_alloc, available_alloc=available_alloc,
db_users=db_users, db_users=db_users,
Inventory=Inventory,
navcolor='dark' navcolor='dark'
) )
@base.route('/network',methods=['GET','POST'])
@login_required
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('base.networks'))
except:
flash("Invalid Subnet or Prefix", 'danger')
return redirect(url_for('base.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')
@base.route('/network/<int:net_id>/allocations',methods=['GET','POST'])
@login_required
def allocations(net_id):
active_page = request.args.get('page', type=int)
if active_page == None:
active_page = 1
next_page = active_page+1
prev_page = active_page-1
if request.args.get('items', type=int) != None:
session['items'] = request.args.get('items', type=int)
try:
if session['items'] == None:
session['items'] = 15
except:
session['items'] = 15
db_network = Network.query.filter_by(id=net_id).first()
db_allocations = db.paginate(db.select(Allocation).filter_by(net_id=net_id), per_page=session['items'] )
form = NewAllocationForm()
##if request.method == 'POST' and form.validate_on_submit():
## network = IPv4Network(f"{str(db_network.network_address)}/{str(db_network.prefix)}")
# Check if IPv4 Address is already allocated
## if Allocation.query.filter_by(ipv4=str(form.ipv4.data)).first() != None:
## flash("IPv4 Address Already Allocated", 'danger')
## return redirect(url_for('base.allocations', net_id=net_id, page=active_page))
## # Check if IPv4 Address is in the Network
## if IPv4Address(form.ipv4.data) in network.hosts():
## add_alloc = Allocation(ipv4=str(form.ipv4.data), net_id=db_network.id, hostname=str(form.host_name.data), description=str(form.host_desc.data))
## db.session.add(add_alloc)
## db.session.commit()
## flash("Allocation Added", 'success')
## return redirect(url_for('base.allocations', net_id=net_id, page=active_page))
# If not in Network
## flash("Invalid IPv4 Address", 'danger')
## return redirect(url_for('base.allocations', net_id=net_id, page=active_page))
# If Form Errors
##elif form.errors:
## flash(form.errors, 'danger')
return render_template('panel/allocations.html', title="Allocations", is_networks=True, app_version=app_version, form=form, allocations=db_allocations, network=db_network, Inventory=Inventory, active_page=active_page, next_page=next_page, prev_page=prev_page, navcolor='dark')
## MODAL ROUTES
@base.get('/network/<int:net_id>/del_modal')
@login_required
def network_del_modal(net_id):
db_network = Network.query.filter_by(id=net_id).first()
modal = {
'title': 'Delete Network',
'body': f'Are you sure you want to delete <b>{db_network.network_address}/{db_network.prefix}</b> network ?',
'color': 'danger',
'action': 'delete',
'action_text': 'Delete',
'action_url': url_for('base.network_delete',net_id=net_id)
}
return render_template('panel/_modal.html', modal=modal, net_id=net_id)
@base.get('/network/<int:net_id>/allocations/<string:ipv4>/del_modal')
@login_required
def allocation_del_modal(net_id,ipv4):
modal = {
'title': 'Remove Allocation',
'body': f'Are you sure you want to remove <b>{ipv4}</b> allocation ?',
'color': 'warning',
'action': 'delete',
'action_text': 'Remove',
'action_url': url_for('base.allocation_delete',net_id=net_id,ipv4=ipv4)
}
return render_template('panel/_modal.html', modal=modal, net_id=net_id, ipv4=ipv4, old_page=request.args.get('page', type=int), pagination_active=True)
@base.route('/network/<int:net_id>/allocations/<string:ipv4>/edit_modal', methods=['GET','POST'])
@login_required
def allocation_edit_modal(net_id, ipv4):
db_alloc = Allocation.query.filter_by(ipv4=ipv4).first()
form = ChangeAllocationForm()
if form.validate_on_submit() and request.method == 'POST':
db_alloc.hostname = str(form.hostname.data)
db_alloc.description = str(form.description.data)
db_alloc.is_used = True
# Special Allocation
if int(form.is_special.data) == 1:
# DHCP
db_alloc.is_dhcp = True
db_alloc.is_gateway = False
db_alloc.hostname = None
db_alloc.description = "DHCP Allocation"
elif int(form.is_special.data) == 2:
# Gateway
db_alloc.is_dhcp = False
db_alloc.is_gateway = True
else:
# None
db_alloc.is_dhcp = False
db_alloc.is_gateway = False
db.session.commit()
flash("Allocation Updated", 'success')
return redirect(url_for('base.allocations',net_id=net_id,page=request.args.get('page', type=int)))
modal = {
'title': 'Update Allocation',
'body': f'Change informations for <b>{db_alloc.ipv4}</b> allocation.',
'color': 'primary',
'modal_type': 'form',
'action_text': 'Save',
'form_url': url_for('base.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
return render_template('panel/_modal.html', modal=modal, form=form, old_page=old_page, pagination_active=True)
## MODIFICATION ROUTES
@base.get('/network/<int:net_id>/allocations/<string:ipv4>/unallocate')
@login_required
def allocation_delete(net_id,ipv4):
db_alloc = Allocation.query.filter_by(net_id=net_id,ipv4=ipv4).first()
db_alloc.is_used = False
db_alloc.hostname = None
db_alloc.description = None
db_alloc.is_dhcp = False
db_alloc.is_gateway = False
db.session.commit()
flash("Allocation Removed", 'warning')
return redirect(url_for('base.allocations',net_id=net_id,page=request.args.get('page', type=int)))
@base.get('/network/<int:net_id>/allocations/<string:ipv4>/decom/<int:action>')
@login_required
def allocation_decom(net_id,ipv4,action):
db_alloc = Allocation.query.filter_by(net_id=net_id,ipv4=ipv4).first()
if action == 1:
db_alloc.is_decom = True
else:
db_alloc.is_decom = False
db.session.commit()
return redirect(url_for('base.allocations',net_id=net_id,page=request.args.get('page', type=int)))
@base.get('/network/<int:net_id>/delete')
@login_required
def network_delete(net_id):
db_network = Network.query.filter_by(id=net_id).first()
db_alloc = Allocation.query.filter_by(net_id=db_network.id).all()
if db_network != None:
if db_alloc != None:
for alloc in db_alloc:
db.session.delete(alloc)
db.session.delete(db_network)
db.session.commit()
flash("Network Deleted", 'warning')
return redirect(url_for('base.networks'))
else:
flash("Network Not Found", 'danger')
return redirect(url_for('base.networks'))
## HTMX SEARCH
@base.post('/network/<int:net_id>/allocations/search')
@login_required
def allocation_search(net_id):
search = str(request.form['search']).lower()
db_network = Network.query.filter_by(id=net_id).first()
db_alloc = Allocation.query.filter_by(net_id=net_id).all()
obj_list = []
# Advance Search
if '*' in search:
search = search.split("*")[1]
# AND
if ',' in search:
for alloc in db_alloc:
if alloc.ipv4.lower() in search.split(','):
obj_list.append(alloc)
# BETWEEN
if '-' in search:
search = search.split('-')
if search[0].split('.')[-1] == '' or search[1].split('.')[-1] == '':
return "Invalid(s) Number(s)"
temp_1 = int(search[0].split('.')[-1])
temp_2 = int(search[1].split('.')[-1])
for alloc in db_alloc:
if int(alloc.ipv4.split('.')[-1]) <= temp_2 and int(alloc.ipv4.split('.')[-1]) >= temp_1:
obj_list.append(alloc)
# DHCP
if 'dhcp' in search:
for alloc in db_alloc:
if alloc.is_dhcp:
obj_list.append(alloc)
## GATEWAY
if 'gateway' in search:
for alloc in db_alloc:
if alloc.is_gateway:
obj_list.append(alloc)
## DECOM
if 'decom' in search:
for alloc in db_alloc:
if alloc.is_decom:
obj_list.append(alloc)
## DECOM
if 'used' in search:
for alloc in db_alloc:
if alloc.is_used:
obj_list.append(alloc)
else:
# BASIC SEARCH
for alloc in db_alloc:
if alloc.ipv4.lower().startswith(search):
obj_list.append(alloc)
elif alloc.hostname != None:
if alloc.hostname.lower().startswith(search):
obj_list.append(alloc)
elif alloc.description != None:
if alloc.description.lower().startswith(search):
obj_list.append(alloc)
return render_template('panel/_table-alloc.html',allocations=obj_list,Inventory=Inventory,network=db_network)
+234
View File
@@ -0,0 +1,234 @@
from flask import Blueprint, request, render_template, flash, redirect, url_for, abort
from flask_login import login_required, current_user
from app.extensions import *
from app.models import *
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))
db.session.add(add_net)
db.session.commit()
flash("Item ADDED", 'success')
return render_template('panel/inventory_add.html', title='ADD ITEM', form=form, navcolor='success')
@inventory.route('/list/<int:item_type>', methods=['GET','POST'])
@login_required
def list_items(item_type):
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/<int:item_id>/<int: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('inventory.list_items',item_type=request.args.get('page', type=int)))
## MODAL ROUTES
@inventory.get('/<int:item_id>/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 <b>{db_item.host_name}</b> ({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('/<int:item_id>/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 <b>{db_item.host_name}</b> ?',
'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('/<int:item_id>/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
# Manufacturer
if form.item_manufacturer.data != '' and form.item_manufacturer.data != 'None':
db_item.host_manufacturer = form.item_manufacturer.data
else:
db_item.host_serialnumber = 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 <b>{db_item.host_name}</b>.',
'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)
+269
View File
@@ -0,0 +1,269 @@
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
from app._config import app_version
from app.extensions import *
from app.models import *
from app.forms import NewPrefixForm, ChangeAllocationForm
network = Blueprint('network', __name__)
@network.route('/',methods=['GET','POST'])
@login_required
def networks():
networks = Network.query.all()
form = NewPrefixForm()
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')
@network.route('/<int:net_id>/allocations',methods=['GET','POST'])
@login_required
def allocations(net_id):
active_page = request.args.get('page', type=int)
if active_page == None:
active_page = 1
next_page = active_page+1
prev_page = active_page-1
if request.args.get('items', type=int) != None:
session['items'] = request.args.get('items', type=int)
try:
if session['items'] == None:
session['items'] = 15
except:
session['items'] = 15
db_network = Network.query.filter_by(id=net_id).first()
db_allocations = db.paginate(db.select(Allocation).filter_by(net_id=net_id), per_page=session['items'] )
return render_template('panel/allocations.html', title="Allocations", is_networks=True, app_version=app_version, allocations=db_allocations, network=db_network, Inventory=Inventory, active_page=active_page, next_page=next_page, prev_page=prev_page, navcolor='dark')
## MODAL ROUTES
@network.get('/<int:net_id>/del_modal')
@login_required
def network_del_modal(net_id):
db_network = Network.query.filter_by(id=net_id).first()
modal = {
'title': 'Delete Network',
'body': f'Are you sure you want to delete <b>{db_network.network_address}/{db_network.prefix}</b> network ?',
'color': 'danger',
'action': 'delete',
'action_text': 'Delete',
'action_url': url_for('network.network_delete',net_id=net_id)
}
return render_template('panel/_modal.html', modal=modal, net_id=net_id)
@network.get('/<int:net_id>/allocations/<string:ipv4>/del_modal')
@login_required
def allocation_del_modal(net_id,ipv4):
modal = {
'title': 'Remove Allocation',
'body': f'Are you sure you want to remove <b>{ipv4}</b> allocation ?',
'color': 'warning',
'action': 'delete',
'action_text': 'Remove',
'action_url': url_for('network.allocation_delete',net_id=net_id,ipv4=ipv4)
}
return render_template('panel/_modal.html', modal=modal, net_id=net_id, ipv4=ipv4, old_page=request.args.get('page', type=int), pagination_active=True)
@network.route('/<int:net_id>/allocations/<string:ipv4>/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()
if form.validate_on_submit() and request.method == 'POST':
db_alloc.hostname = str(form.hostname.data)
db_alloc.description = str(form.description.data)
db_alloc.is_used = True
# Special Allocation
if int(form.is_special.data) == 1:
# DHCP
db_alloc.is_dhcp = True
db_alloc.is_gateway = False
db_alloc.hostname = None
db_alloc.description = "DHCP Allocation"
elif int(form.is_special.data) == 2:
# Gateway
db_alloc.is_dhcp = False
db_alloc.is_gateway = True
else:
# None
db_alloc.is_dhcp = False
db_alloc.is_gateway = False
db.session.commit()
flash("Allocation Updated", 'success')
return redirect(url_for('network.allocations',net_id=net_id,page=request.args.get('page', type=int)))
modal = {
'title': 'Update Allocation',
'body': f'Change informations for <b>{db_alloc.ipv4}</b> allocation.',
'color': 'primary',
'modal_type': 'form',
'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('/<int:net_id>/allocations/<string:ipv4>/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
db_alloc.description = None
db_alloc.is_dhcp = False
db_alloc.is_gateway = False
db.session.commit()
flash("Allocation Removed", 'warning')
return redirect(url_for('network.allocations',net_id=net_id,page=request.args.get('page', type=int)))
@network.get('/<int:net_id>/allocations/<string:ipv4>/decom/<int:action>')
@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
else:
db_alloc.is_decom = False
db.session.commit()
return redirect(url_for('network.allocations',net_id=net_id,page=request.args.get('page', type=int)))
@network.get('/<int:net_id>/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()
if db_network != None:
if db_alloc != None:
for alloc in db_alloc:
db.session.delete(alloc)
db.session.delete(db_network)
db.session.commit()
flash("Network Deleted", 'warning')
return redirect(url_for('network.networks'))
else:
flash("Network Not Found", 'danger')
return redirect(url_for('network.networks'))
## HTMX SEARCH
@network.post('/<int:net_id>/allocations/search')
@login_required
def allocation_search(net_id):
search = str(request.form['search']).lower()
db_network = Network.query.filter_by(id=net_id).first()
db_alloc = Allocation.query.filter_by(net_id=net_id).all()
obj_list = []
# Advance Search
if '*' in search:
search = search.split("*")[1]
# AND
if ',' in search:
for alloc in db_alloc:
if alloc.ipv4.lower() in search.split(','):
obj_list.append(alloc)
# BETWEEN
if '-' in search:
search = search.split('-')
if search[0].split('.')[-1] == '' or search[1].split('.')[-1] == '':
return "Invalid(s) Number(s)"
temp_1 = int(search[0].split('.')[-1])
temp_2 = int(search[1].split('.')[-1])
for alloc in db_alloc:
if int(alloc.ipv4.split('.')[-1]) <= temp_2 and int(alloc.ipv4.split('.')[-1]) >= temp_1:
obj_list.append(alloc)
# DHCP
if 'dhcp' in search:
for alloc in db_alloc:
if alloc.is_dhcp:
obj_list.append(alloc)
## GATEWAY
if 'gateway' in search:
for alloc in db_alloc:
if alloc.is_gateway:
obj_list.append(alloc)
## DECOM
if 'decom' in search:
for alloc in db_alloc:
if alloc.is_decom:
obj_list.append(alloc)
## Used
if 'used' in search:
for alloc in db_alloc:
if alloc.is_used and alloc.is_dhcp == False:
obj_list.append(alloc)
else:
# BASIC SEARCH
for alloc in db_alloc:
if alloc.ipv4.lower().startswith(search):
obj_list.append(alloc)
elif alloc.hostname != None:
if alloc.hostname.lower().startswith(search):
obj_list.append(alloc)
elif alloc.description != None:
if alloc.description.lower().startswith(search):
obj_list.append(alloc)
return render_template('panel/_table-alloc.html',allocations=obj_list,Inventory=Inventory,network=db_network)
+6
View File
@@ -22,6 +22,7 @@
<th>ID</th> <th>ID</th>
<th>Username</th> <th>Username</th>
<th>Admin User</th> <th>Admin User</th>
<th>RO</th>
<th>Last Connection</th> <th>Last Connection</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
@@ -32,6 +33,7 @@
<td>{{user.id}}</td> <td>{{user.id}}</td>
<td>{{user.username}}</td> <td>{{user.username}}</td>
<td>{{user.is_admin}}</td> <td>{{user.is_admin}}</td>
<td>{{user.is_reado}}</td>
<td>{% if user.dat_last != None %}{{user.dat_last.strftime('%Y-%m-%d %H:%M:%S')}}{%else%}N/A{%endif%}</td> <td>{% if user.dat_last != None %}{{user.dat_last.strftime('%Y-%m-%d %H:%M:%S')}}{%else%}N/A{%endif%}</td>
<td> <td>
<button <button
@@ -80,6 +82,10 @@
<label class="form-label">Is Admin?</label> <label class="form-label">Is Admin?</label>
{{ form.is_admin(class="form-control") }} {{ form.is_admin(class="form-control") }}
</div> </div>
<div class="mb-3">
<label class="form-label">Is Readonly?</label>
{{ form.is_readonly(class="form-control") }}
</div>
<button type="submit" class="btn btn-secondary w-100">Submit</button> <button type="submit" class="btn btn-secondary w-100">Submit</button>
</form> </form>
</div> </div>
+50 -3
View File
@@ -1,5 +1,5 @@
{% if modal.modal_type == 'form' %} {% if modal.modal_type == 'form' %}
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog">
<form method="post" action="{{modal.form_url}}{% if pagination_active %}?page={{old_page}}{%endif%}"> <form method="post" action="{{modal.form_url}}{% if pagination_active %}?page={{old_page}}{%endif%}">
{{ form.csrf_token }} {{ form.csrf_token }}
<div class="modal-content"> <div class="modal-content">
@@ -8,8 +8,29 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% if modal.inventory %}
{{ modal.body|safe }}<hr>
<div class="row">
{% for entry in form %}
{% if entry.id != 'csrf_token' %}
{% if entry.id == 'item_ip_allocations' %}
<div class="col-12">
{{ entry.label(class="form-label") }}<br>
{{ entry(class="form-control",placeholder='192.168.1.1,192.168.1.2 (separate with ,)') }}
</div>
{% else %}
<div class="col-6">
<div class="mb-2">
{{ entry.label(class="form-label") }}<br>
{{ entry(class="form-control") }}
</div>
</div>
{% endif %}
{% endif %}
{% endfor %}
</div>
{% else %}
<p> <p>
{{ modal.body|safe }}<hr>
{% for entry in form %} {% for entry in form %}
{% if entry.id != 'csrf_token' %} {% if entry.id != 'csrf_token' %}
<div class="mb-3"> <div class="mb-3">
@@ -19,6 +40,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</p> </p>
{% endif %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
@@ -27,11 +49,36 @@
</div> </div>
</form> </form>
</div> </div>
{% elif modal.modal_type == 'infos' %}
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-{{modal.color}}">{{ modal.title|safe }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
{% for info in informations %}
<div class="{{info[2]}}">
<div class="mb-3">
<label for="{{info[0]}}">{{info[0]}}</label>
<input class="form-control" type="text" id="{{info[0]}}" value="{{info[1]}}" readonly>
<!--{{ key }} : {{ value }}-->
</div>
</div>
{% endfor %}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
{% else %} {% else %}
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title text-{{modal.color}}">{{ modal.title }}</h5> <h5 class="modal-title text-{{modal.color}}">{{ modal.title|safe }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
+9 -7
View File
@@ -10,16 +10,18 @@
<a class="nav-link {% if is_dash %}active{% endif %}" href="{{ url_for('base.dashboard') }}"><i class="bi bi-house-door-fill"></i> Dashboard</a> <a class="nav-link {% if is_dash %}active{% endif %}" href="{{ url_for('base.dashboard') }}"><i class="bi bi-house-door-fill"></i> Dashboard</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if is_networks %}active{% endif %}" href="{{ url_for('base.networks') }}"><i class="bi bi-globe"></i> Network(s)</a> <a class="nav-link {% if is_networks %}active{% endif %}" href="{{ url_for('network.networks') }}"><i class="bi bi-globe"></i> Network(s)</a>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle disabled" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-archive-fill"></i> Inventory</a> <a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-archive-fill"></i> Inventory</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#"><i class="bi bi-pc-display"></i> Computers</a></li> <li><a class="dropdown-item" href="{{ url_for('inventory.list_items',item_type=1) }}"><i class="bi bi-pc-display"></i> Computers</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-server"></i> Servers</a></li> <li><a class="dropdown-item" href="{{ url_for('inventory.list_items',item_type=2) }}"><i class="bi bi-server"></i> Servers</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-server"></i> Virtual Machines</a></li> <li><a class="dropdown-item" href="{{ url_for('inventory.list_items',item_type=3) }}"><i class="bi bi-server"></i> Virtual Machines</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-ethernet"></i> Networks Devices</a></li> <li><a class="dropdown-item" href="{{ url_for('inventory.list_items',item_type=4) }}"><i class="bi bi-ethernet"></i> Networks Devices</a></li>
<li><a class="dropdown-item" href="#"><i class="bi bi-collection"></i> Others</a></li> <li><a class="dropdown-item" href="{{ url_for('inventory.list_items',item_type=0) }}"><i class="bi bi-collection"></i> Others</a></li>
<li><a class="dropdown-item" href="{{ url_for('inventory.list_items',item_type=5) }}"><i class="bi bi-collection"></i> All</a></li>
{% if current_user.is_reado == False %}<li><a class="dropdown-item" href="{{ url_for('inventory.add_item') }}"><i class="bi bi-plus-circle"></i> ADD NEW</a></li>{% endif %}
</ul> </ul>
</li> </li>
{% if current_user.is_admin %} {% if current_user.is_admin %}
+17 -6
View File
@@ -5,7 +5,16 @@
<td>{{allocation.description}}</td> <td>{{allocation.description}}</td>
<td> <td>
{% if Inventory.query.filter_by(id=allocation.device_id).first() %} {% if Inventory.query.filter_by(id=allocation.device_id).first() %}
<a href="{{Inventory.query.filter_by(id=allocation.device_id).first().id}}" style="text-decoration:none;">link</a> <button
hx-get="{{ url_for('inventory.item_view_modal',item_id=allocation.device_id) }}"
hx-trigger="click"
hx-target="#modal-view"
data-bs-toggle="modal"
data-bs-target="#modal-view"
class="btn btn-sm btn-outline-primary"
style="padding-top:1px;padding-bottom:1px"
> Details
</button>
{% else %} {% else %}
N/A N/A
{% endif %} {% endif %}
@@ -13,10 +22,11 @@
<td>{{allocation.is_dhcp}}</td> <td>{{allocation.is_dhcp}}</td>
<td>{{allocation.is_gateway}}</td> <td>{{allocation.is_gateway}}</td>
<td>{{allocation.is_decom}}</td> <td>{{allocation.is_decom}}</td>
{% if current_user.is_reado == False %}
<td> <td>
{% if allocation.is_used %} {% if allocation.is_used %}
<button <button
hx-get="{{ url_for('base.allocation_edit_modal',net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" hx-get="{{ url_for('network.allocation_edit_modal',net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}"
hx-trigger="click" hx-trigger="click"
hx-target="#modal-edit" hx-target="#modal-edit"
data-bs-toggle="modal" data-bs-toggle="modal"
@@ -26,12 +36,12 @@
> Edit > Edit
</button> </button>
{% if allocation.is_decom %} {% if allocation.is_decom %}
<a href="{{ url_for('base.allocation_decom',action=2,net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" class="btn btn-sm btn-outline-primary" style="padding-top:1px;padding-bottom:1px">Undecom</a> <a href="{{ url_for('network.allocation_decom',action=2,net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" class="btn btn-sm btn-outline-primary" style="padding-top:1px;padding-bottom:1px">Undecom</a>
{% else %} {% else %}
<a href="{{ url_for('base.allocation_decom',action=1,net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" class="btn btn-sm btn-outline-warning" style="padding-top:1px;padding-bottom:1px">Decom</a> <a href="{{ url_for('network.allocation_decom',action=1,net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" class="btn btn-sm btn-outline-warning" style="padding-top:1px;padding-bottom:1px">Decom</a>
{% endif %} {% endif %}
<button <button
hx-get="{{ url_for('base.allocation_del_modal',net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" hx-get="{{ url_for('network.allocation_del_modal',net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}"
hx-trigger="click" hx-trigger="click"
hx-target="#modal-del" hx-target="#modal-del"
data-bs-toggle="modal" data-bs-toggle="modal"
@@ -42,7 +52,7 @@
</button> </button>
{% else %} {% else %}
<button <button
hx-get="{{ url_for('base.allocation_edit_modal',net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}" hx-get="{{ url_for('network.allocation_edit_modal',net_id=network.id,ipv4=allocation.ipv4) }}?page={{active_page}}"
hx-trigger="click" hx-trigger="click"
hx-target="#modal-edit" hx-target="#modal-edit"
data-bs-toggle="modal" data-bs-toggle="modal"
@@ -53,5 +63,6 @@
</button> </button>
{% endif %} {% endif %}
</td> </td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
+13 -32
View File
@@ -8,8 +8,8 @@
<div class="col-12"> <div class="col-12">
<div class="mb-3"> <div class="mb-3">
<input class="form-control" type="search" <input class="form-control" type="search"
name="search" placeholder="Begin Typing To Search Users..." name="search" placeholder="Begin Typing To Search Allocation(s)..."
hx-post="{{ url_for('base.allocation_search',net_id=network.id) }}" hx-post="{{ url_for('network.allocation_search',net_id=network.id) }}"
hx-trigger="input changed delay:500ms, keyup[key=='Enter']" hx-trigger="input changed delay:500ms, keyup[key=='Enter']"
hx-target="#search-results" hx-target="#search-results"
hx-indicator=".htmx-indicator" hx-indicator=".htmx-indicator"
@@ -18,7 +18,7 @@
</div> </div>
<!-- IPV4 TABLE --> <!-- IPV4 TABLE -->
<div class="col-12"> <div class="col-12">
<table class="table table-striped table-hover table-sm"> <table class="table table-hover table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">IPv4</th> <th scope="col">IPv4</th>
@@ -28,7 +28,7 @@
<th scope="col">DHCP</th> <th scope="col">DHCP</th>
<th scope="col">GATEWAY</th> <th scope="col">GATEWAY</th>
<th scope="col">DECOM</th> <th scope="col">DECOM</th>
<th scope="col">Actions</th> {% if current_user.is_reado == False %}<th scope="col">Actions</th>{% endif %}
</tr> </tr>
</thead> </thead>
<tbody id="search-results"> <tbody id="search-results">
@@ -36,28 +36,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- IPV4 FORM
<div class="col-3">
<div class="card p-4 shadow rounded-3">
<h2 class="text-center mb-4">Add IPv4</h2>
<form method="POST">
{{ form.csrf_token }}
<div class="mb-3">
<label class="form-label">IPv4</label>
{{ form.ipv4(class="form-control", placeholder=network.network_address) }}
</div>
<div class="mb-3">
<label class="form-label">Hostname</label>
{{ form.host_name(class="form-control", placeholder="example") }}
</div>
<div class="mb-3">
<label class="form-label">Description</label>
{{ form.host_desc(class="form-control", placeholder="webserver") }}
</div>
<button type="submit" class="btn btn-secondary w-100">Submit</button>
</form>
</div>
</div> -->
</div> </div>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<nav> <nav>
@@ -68,12 +46,6 @@
{% if allocations.has_next %} {% if allocations.has_next %}
<li class="page-item"><a class="page-link" href="?page={{next_page}}">Next</a></li> <li class="page-item"><a class="page-link" href="?page={{next_page}}">Next</a></li>
{% endif %} {% endif %}
<!--
{% for page in range(allocations.pages+1) %}
{% if page != 0%}
<li class="page-item"><a class="page-link {%if page==active_page%}active{%endif%}" href="?page={{page}}">{{page}}</a></li>
{% endif %}
{% endfor %}-->
</ul> </ul>
</nav> </nav>
<div> <div>
@@ -101,4 +73,13 @@
<div class="modal-content"></div> <div class="modal-content"></div>
</div> </div>
</div> </div>
<div id="modal-view"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
{% endblock %} {% endblock %}
+8 -3
View File
@@ -5,7 +5,7 @@
<div class="row"> <div class="row">
<!-- NETWORKS --> <!-- NETWORKS -->
<div class="col-3"> <div class="col-3">
<a href="{{ url_for('base.networks') }}" class="card text-bg-info mb-3 text-decoration-none"> <a href="{{ url_for('network.networks') }}" class="card text-bg-info mb-3 text-decoration-none">
<div class="card-header"><i class="bi bi-globe"></i> <b>Total Networks</b></div> <div class="card-header"><i class="bi bi-globe"></i> <b>Total Networks</b></div>
<div class="card-body"> <div class="card-body">
<h1 class="card-title text-center">{{db_networks}}</h1> <h1 class="card-title text-center">{{db_networks}}</h1>
@@ -14,7 +14,7 @@
</div> </div>
<!-- TOTAL IP --> <!-- TOTAL IP -->
<div class="col-3"> <div class="col-3">
<a href="{{ url_for('base.networks') }}" class="card text-bg-secondary mb-3 text-decoration-none" style="margin-bottom:1vh;"> <a href="{{ url_for('network.networks') }}" class="card text-bg-secondary mb-3 text-decoration-none" style="margin-bottom:1vh;">
<div class="card-header"><i class="bi bi-broadcast-pin"></i> <b>Total Allocations</b></div> <div class="card-header"><i class="bi bi-broadcast-pin"></i> <b>Total Allocations</b></div>
<div class="card-body"> <div class="card-body">
<h1 class="card-title text-center">{{db_allocations}} / {{ available_alloc }}</h1> <h1 class="card-title text-center">{{db_allocations}} / {{ available_alloc }}</h1>
@@ -75,12 +75,17 @@
}); });
/* Devices Chart */ /* Devices Chart */
const ctx1 = document.getElementById('deviceschart'); const ctx1 = document.getElementById('deviceschart');
var computers = JSON.parse('{{ Inventory.query.filter_by(host_type=1).count() | tojson }}');
var servers = JSON.parse('{{ Inventory.query.filter_by(host_type=2).count() | tojson }}');
var virtual_machines = JSON.parse('{{ Inventory.query.filter_by(host_type=3).count() | tojson }}');
var networks_devices = JSON.parse('{{ Inventory.query.filter_by(host_type=4).count() | tojson }}');
var others = JSON.parse('{{ Inventory.query.filter_by(host_type=0).count() | tojson }}');
new Chart(ctx1, { new Chart(ctx1, {
type: 'doughnut', type: 'doughnut',
data: { data: {
labels: ['Computers', 'Servers', 'VM', 'Network Devices', 'Others'], labels: ['Computers', 'Servers', 'VM', 'Network Devices', 'Others'],
datasets: [{ datasets: [{
data: [1, 1, 1, 1, 1], data: [computers, servers, virtual_machines, networks_devices, others],
borderWidth: 2 borderWidth: 2
}] }]
}, },
+32
View File
@@ -0,0 +1,32 @@
{% extends 'panel/_base.html' %}
{% block content %}
{% include 'panel/_nav.html' %}
{% include 'panel/_flash.html' %}
<div class="container" style="margin-top:5vh;">
<div class="row">
<div class="col-4"></div>
<div class="col-4">
<div class="card p-4 shadow rounded-3">
<h2 class="text-center mb-4">Add Item</h2>
<form method="POST">
{{ form.csrf_token }}
<div class="mb-3">
<label for="subnet" class="form-label">Name</label>
{{ form.item_name(class="form-control", placeholder="Computer A") }}
</div>
<div class="mb-3">
<label for="prefix" class="form-label">Description</label>
{{ form.item_desc(class="form-control", placeholder="Computer A for IT support...") }}
</div>
<div class="mb-3">
<label for="description" class="form-label">Item Type</label>
{{ form.item_type(class="form-control") }}
</div>
<button type="submit" class="btn btn-secondary w-100">Submit</button>
</form>
</div>
</div>
<div class="col-4"></div>
</div>
</div>
{% endblock %}
+133
View File
@@ -0,0 +1,133 @@
{% extends 'panel/_base.html' %}
{% block content %}
{% include 'panel/_nav.html' %}
{% include 'panel/_flash.html' %}
<div class="container" style="margin-top:4vh;">
<div class="row">
<div class="col-2"></div>
<div class="col-8">
<h1>{{title}}</h1>
<div class="card shadow-sm">
<div class="card-body">
<table class="table table-hover table-sm">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Hostname</th>
<th scope="col">Description</th>
<th scope="col">Type</th>
<th scope="col">Owner</th>
<th scope="col">Allocation(s)</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for item in item_list %}
<tr>
<th scope="row">{{item.id}}</th>
<td>{{item.host_name}}</td>
<td>{{item.host_desc}}</td>
{% if item.host_type == 1 %}
<td><i class="bi bi-pc-display"></i> (PC)</td>
{% elif item.host_type == 2 %}
<td><i class="bi bi-server"></i> (SRV)</td>
{% elif item.host_type == 3 %}
<td><i class="bi bi-server"></i> (VM)</td>
{% elif item.host_type == 4 %}
<td><i class="bi bi-ethernet"></i> (NT)</td>
{% elif item.host_type == 0 %}
<td>Other</td>
{% endif %}
<!--
{% if 'WINDOWS' in item.host_os|upper or 'WS' in item.host_os|upper %}
<td><i class="bi bi-windows"></i> {{item.host_os}}</td>
{% elif 'UBUNTU' in item.host_os|upper %}
<td><i class="bi bi-ubuntu"></i> {{item.host_os}}</td>
{% elif 'MACOS' in item.host_os|upper or 'APPLE' in item.host_os|upper %}
<td><i class="bi bi-apple"></i> {{item.host_os}}</td>
{% else %}
<td>{{item.host_os}}</td>
{% endif %}
-->
<td>{{item.host_owner}}</td>
<td>
{% if item.host_allocations != None %}
{% set alloc_count = item.host_allocations.split(',')|length-1 %}
{{alloc_count}}
{% else %}
{{item.host_allocations}}
{% endif %}
</td>
<td>
<button
hx-get="{{ url_for('inventory.item_view_modal',item_id=item.id) }}?page={{current_page}}"
hx-trigger="click"
hx-target="#modal-view"
data-bs-toggle="modal"
data-bs-target="#modal-view"
class="btn btn-sm btn-outline-info"
style="padding-top:1px;padding-bottom:1px"
> Details
</button>
{% if current_user.is_reado == False %}
<button
hx-get="{{ url_for('inventory.item_edit_modal',item_id=item.id) }}?page={{current_page}}"
hx-trigger="click"
hx-target="#modal-edit"
data-bs-toggle="modal"
data-bs-target="#modal-edit"
class="btn btn-sm btn-outline-primary"
style="padding-top:1px;padding-bottom:1px"
> Edit
</button>
<button
hx-get="{{ url_for('inventory.item_del_modal',item_id=item.id) }}?page={{current_page}}"
hx-trigger="click"
hx-target="#modal-del"
data-bs-toggle="modal"
data-bs-target="#modal-del"
class="btn btn-sm btn-outline-danger"
style="padding-top:1px;padding-bottom:1px"
> Delete
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-2"></div>
</div>
</div>
<!-- MODAL -->
<div id="modal-del"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
<div id="modal-edit"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
<div id="modal-view"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
{% endblock %}
+7 -3
View File
@@ -5,7 +5,7 @@
<div class="container" style="margin-top:5vh;"> <div class="container" style="margin-top:5vh;">
<div class="row"> <div class="row">
<!-- PREFIXES TABLE --> <!-- PREFIXES TABLE -->
<div class="col-9"> <div class="col-{% if current_user.is_reado == False %}9{% else %}12{% endif %}">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@@ -26,9 +26,10 @@
<td>{{Allocation.query.filter_by(net_id=network.id,is_used=True).count()}} / {{network.hosts}} </td> <td>{{Allocation.query.filter_by(net_id=network.id,is_used=True).count()}} / {{network.hosts}} </td>
<td><b>{{network.description}}</b></td> <td><b>{{network.description}}</b></td>
<td> <td>
<a href="{{ url_for('base.allocations',net_id=network.id) }}?page=1" class="btn btn-sm btn-outline-info" style="padding-top:1px;padding-bottom:1px">Allocations</a> <a href="{{ url_for('network.allocations',net_id=network.id) }}?page=1" class="btn btn-sm btn-outline-info" style="padding-top:1px;padding-bottom:1px">Allocations</a>
{% if current_user.is_reado == False %}
<button <button
hx-get="{{ url_for('base.network_del_modal',net_id=network.id) }}" hx-get="{{ url_for('network.network_del_modal',net_id=network.id) }}"
hx-trigger="click" hx-trigger="click"
hx-target="#modal-get" hx-target="#modal-get"
data-bs-toggle="modal" data-bs-toggle="modal"
@@ -37,6 +38,7 @@
style="padding-top:1px;padding-bottom:1px" style="padding-top:1px;padding-bottom:1px"
> Delete > Delete
</button> </button>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -44,6 +46,7 @@
</table> </table>
</div> </div>
<!-- PREFIX FORM --> <!-- PREFIX FORM -->
{% if current_user.is_reado == False %}
<div class="col-3"> <div class="col-3">
<div class="card p-4 shadow rounded-3"> <div class="card p-4 shadow rounded-3">
<h2 class="text-center mb-4">Add Network</h2> <h2 class="text-center mb-4">Add Network</h2>
@@ -65,6 +68,7 @@
</form> </form>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
<!-- MODAL --> <!-- MODAL -->
+11
View File
@@ -0,0 +1,11 @@
# Installation GUIDE
This documentation is very good, but needs a few changes to make it work (in our case) :
https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-22-04<br>
- You don't need to create `wsgi.py`, when asked, replace : `wsgi:app` with `'app:create_app()'` .
- EX : For starting APP with gunicorn : `gunicorn --bind 0.0.0.0:5000 'app:create_app()'`
- In Service File, replace "ExecStart" line with :<br>
`ExecStart=/YOUR_ENV_LOCATION/bin/gunicorn --workers 1 --bind unix:fipam.sock -m 007 'app:create_app()'`<br>
_(Adjust the number of workers to your needs)_
- you must first run setup.py to create the admin user.<br>
_(it will create a second database in `./var/setup-instance`. It will have to be merged in the application database in `./var/app-instance`)_
+3 -4
View File
@@ -1,14 +1,13 @@
# Sources # Sources
---
### Images ### Images
logo : https://www.flaticon.com logo : https://www.flaticon.com
### CSS ### CSS
Bootstrap : https://getbootstrap.com Bootstrap : https://getbootstrap.com<br>
themes : https://bootswatch.com themes : https://bootswatch.com<br>
icons : https://icons.getbootstrap.com icons : https://icons.getbootstrap.com
### JS ### JS
HTMX : https://htmx.org HTMX : https://htmx.org<br>
CHARTJS : https://www.chartjs.org CHARTJS : https://www.chartjs.org