This commit is contained in:
Takeus committed 2025-02-04 15:28:39 +01:00
1 parent a8d9727527
commit 602bb7d706
8 files changed
+174 -21

No files matched your search

+5 -4
View File
@@ -1,5 +1,5 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField from wtforms import StringField, PasswordField, IntegerField, SelectField
from wtforms.validators import DataRequired, Length, IPAddress, NumberRange from wtforms.validators import DataRequired, Length, IPAddress, NumberRange
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
@@ -9,9 +9,10 @@ class LoginForm(FlaskForm):
class NewPrefixForm(FlaskForm): class NewPrefixForm(FlaskForm):
subnet = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)]) subnet = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)])
prefix = IntegerField(validators=[DataRequired(),NumberRange(min=0,max=32)]) prefix = IntegerField(validators=[DataRequired(),NumberRange(min=0,max=32)])
description = StringField(validators=[DataRequired(),Length(max=128)]) description = StringField(validators=[Length(max=128)])
class NewAllocationForm(FlaskForm): class NewAllocationForm(FlaskForm):
ipv4 = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)]) ipv4 = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)])
hostname = StringField(validators=[DataRequired(),Length(max=128)]) host_name = StringField(validators=[DataRequired(),Length(max=128)])
description = StringField(validators=[DataRequired(),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()])
+19 -2
View File
@@ -18,6 +18,23 @@ class Network(db.Model):
class Allocation(db.Model): class Allocation(db.Model):
ipv4 = db.Column(db.String(16),primary_key=True,unique=True) ipv4 = db.Column(db.String(16),primary_key=True,unique=True)
net_id = db.Column(db.Integer(),db.ForeignKey('network.id')) net_id = db.Column(db.Integer(),db.ForeignKey('network.id'))
hardware_id = db.Column(db.Integer())
hostname = db.Column(db.String(32),unique=True) hostname = db.Column(db.String(32),unique=True)
description = db.Column(db.String(64)) description = db.Column(db.String(64))
device_id = db.Column(db.Integer(),db.ForeignKey('inventory.id'))
class Inventory(db.Model):
id = db.Column(db.Integer(),primary_key=True,unique=True)
ip_addr = db.Column(db.Text())
# Host Information
host_type = db.Column(db.Integer())
host_name = db.Column(db.String(32))
host_desc = db.Column(db.String(64))
# Hardware Information
host_model = db.Column(db.String(32))
host_manufacturer = db.Column(db.String(32))
host_serialnumber = db.Column(db.String(32))
# Network Information
host_os = db.Column(db.String(32))
host_location = db.Column(db.String(32))
host_owner = db.Column(db.String(32))
host_contact = db.Column(db.String(32))
+76 -8
View File
@@ -47,24 +47,92 @@ def networks():
return render_template('panel/networks.html', title="Prefixes", is_networks=True, app_version=app_version, prefixes=networks, form=form) return render_template('panel/networks.html', title="Prefixes", is_networks=True, app_version=app_version, prefixes=networks, form=form)
@base.route('/network/allocations/<int:net_id>',methods=['GET','POST']) @base.route('/network/<int:net_id>/allocations',methods=['GET','POST'])
@login_required @login_required
def allocations(net_id): def allocations(net_id):
db_allocations = Allocation.query.filter_by(net_id=net_id).all() db_allocations = Allocation.query.filter_by(net_id=net_id).all()
db_network = Network.query.filter_by(id=net_id).first() db_network = Network.query.filter_by(id=net_id).first()
form = NewAllocationForm() form = NewAllocationForm()
if request.method == 'POST' and form.validate_on_submit(): if request.method == 'POST' and form.validate_on_submit():
network = IPv4Network(f"{str(db_network.network_address)}/{str(db_network.prefix)}") 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))
# Check if IPv4 Address is in the Network
if IPv4Address(form.ipv4.data) in network.hosts(): if IPv4Address(form.ipv4.data) in network.hosts():
add_alloc = Allocation(ipv4=str(form.ipv4.data),net_id=db_network.id,hostname=str(form.hostname.data),description=str(form.description.data)) 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))
# Add Inventory if Host Type is not None
##if int(form.host_type.data) != 0:
## add_inv = Inventory(ip_addr=str(form.ipv4.data)+',',host_type=form.host_type.data,host_name=form.host_name.data,host_desc=form.host_desc.data)
## db.session.add(add_inv)
##
## mod_alloc = Allocation.query.filter_by(ipv4=str(form.ipv4.data)).first()
## mod_alloc.device_id = add_inv.id
db.session.add(add_alloc) db.session.add(add_alloc)
db.session.commit() db.session.commit()
return redirect(url_for('base.allocations',net_id=net_id)) return redirect(url_for('base.allocations',net_id=net_id))
flash("Invalid IPv4 Address", 'danger')
return 'nok' return redirect(url_for('base.allocations',net_id=net_id))
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)
## MODAL ROUTES
@base.get('/network/<int:net_id>/allocations/<string:ipv4>/del_modal')
@login_required
def allocation_del_modal(net_id,ipv4):
modal = {
'title': 'Delete Allocation',
'body': f'Are you sure you want to delete <b>{ipv4}</b> allocation ?',
'color': 'warning',
'action': 'delete',
'action_text': 'Delete',
'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)
@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)
## DELETE ROUTES
@base.get('/network/<int:net_id>/allocations/<string:ipv4>/delete')
@login_required
def allocation_delete(net_id,ipv4):
db_alloc = Allocation.query.filter_by(net_id=net_id,ipv4=ipv4).first()
db.session.delete(db_alloc)
db.session.commit()
flash("Allocation Deleted", 'warning')
return redirect(url_for('base.allocations',net_id=net_id))
@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:
return render_template('panel/allocations.html', title="Allocations", is_networks=True, app_version=app_version, form=form, allocations=db_allocations, network=db_network) 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'))
+1
View File
@@ -0,0 +1 @@
var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=cn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true},parseInterval:null,_:null,version:"2.0.4"};Q.onLoad=j;Q.process=kt;Q.on=ye;Q.off=be;Q.trigger=he;Q.ajax=Rn;Q.find=u;Q.findAll=x;Q.closest=g;Q.remove=z;Q.addClass=K;Q.removeClass=G;Q.toggleClass=W;Q.takeClass=Z;Q.swap=$e;Q.defineExtension=Fn;Q.removeExtension=Bn;Q.logAll=V;Q.logNone=_;Q.parseInterval=d;Q._=e;const n={addTriggerHandler:St,bodyContains:le,canAccessLocalStorage:B,findThisElement:Se,filterValues:hn,swap:$e,hasAttribute:s,getAttributeValue:te,getClosestAttributeValue:re,getClosestMatch:o,getExpressionVars:En,getHeaders:fn,getInputValues:cn,getInternalData:ie,getSwapSpecification:gn,getTriggerSpecs:st,getTarget:Ee,makeFragment:P,mergeObjects:ce,makeSettleInfo:xn,oobSwap:He,querySelectorExt:ae,settleImmediately:Kt,shouldCancel:ht,triggerEvent:he,triggerErrorEvent:fe,withExtensions:Ft};const r=["get","post","put","delete","patch"];const H=r.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function c(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function ne(){return document}function m(e,t){return e.getRootNode?e.getRootNode({composed:t}):ne()}function o(e,t){while(e&&!t(e)){e=c(e)}return e||null}function i(e,t,n){const r=te(t,n);const o=te(t,"hx-disinherit");var i=te(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function re(t,n){let r=null;o(t,function(e){return!!(r=i(t,ue(e),n))});if(r!=="unset"){return r}}function h(e,t){const n=e instanceof Element&&(e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector);return!!n&&n.call(e,t)}function T(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function q(e){const t=new DOMParser;return t.parseFromString(e,"text/html")}function L(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function A(e){const t=ne().createElement("script");se(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function N(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function I(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(N(e)){const t=A(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){O(e)}finally{e.remove()}}})}function P(e){const t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,"");const n=T(t);let r;if(n==="html"){r=new DocumentFragment;const i=q(e);L(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=q(t);L(r,i.body);r.title=i.title}else{const i=q('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){I(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function oe(e){if(e){e()}}function t(e,t){return Object.prototype.toStrinLine truncated
+1
View File
@@ -17,6 +17,7 @@
<body> <body>
{% block content %}{% endblock %} {% block content %}{% endblock %}
<!-- JAVASCRIPT --> <!-- JAVASCRIPT -->
<script src="{{ url_for('static',filename='js/htmx.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap.bundle.min.js') }}"></script> <script src="{{ url_for('static',filename='js/bootstrap.bundle.min.js') }}"></script>
</body> </body>
</html> </html>
+19
View File
@@ -0,0 +1,19 @@
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-{{modal.color}}">{{ modal.title }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>{{ modal.body|safe }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
{% if modal.action == 'delete' %}
<a href="{{modal.action_url}}" class="btn btn-{{modal.color}}">{{modal.action_text}}</a>
{% else %}
<button type="button" class="btn btn-primary">Save changes</button>
{% endif %}
</div>
</div>
</div>
+33 -6
View File
@@ -13,6 +13,7 @@
<th scope="col">IPv4</th> <th scope="col">IPv4</th>
<th scope="col">Hostname</th> <th scope="col">Hostname</th>
<th scope="col">Description</th> <th scope="col">Description</th>
<th scope="col">Device</th>
<th scope="col">Actions</th> <th scope="col">Actions</th>
</tr> </tr>
</thead> </thead>
@@ -23,9 +24,25 @@
<td>{{allocation.ipv4}}</td> <td>{{allocation.ipv4}}</td>
<td>{{allocation.hostname}}</td> <td>{{allocation.hostname}}</td>
<td><b>{{allocation.description}}</b></td> <td><b>{{allocation.description}}</b></td>
<td>
{% 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>
{% else %}
N/A
{% endif %}
</td>
<td> <td>
<a href="" class="btn btn-sm btn-outline-info" style="padding-top:1px;padding-bottom:1px">edit</a> <a href="" class="btn btn-sm btn-outline-info" style="padding-top:1px;padding-bottom:1px">edit</a>
<button class="btn btn-sm btn-outline-danger"style="padding-top:1px;padding-bottom:1px">delete</button> <button
hx-get="{{ url_for('base.allocation_del_modal',net_id=network.id,ipv4=allocation.ipv4) }}"
hx-trigger="click"
hx-target="#modal-get"
data-bs-toggle="modal"
data-bs-target="#modal-get"
class="btn btn-sm btn-outline-danger"
style="padding-top:1px;padding-bottom:1px"
> Delete
</button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -39,16 +56,16 @@
<form method="POST"> <form method="POST">
{{ form.csrf_token }} {{ form.csrf_token }}
<div class="mb-3"> <div class="mb-3">
<label for="ipv4" class="form-label">IPv4</label> <label class="form-label">IPv4</label>
{{ form.ipv4(class="form-control", placeholder=network.network_address) }} {{ form.ipv4(class="form-control", placeholder=network.network_address) }}
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="prefix" class="form-label">Hostname</label> <label class="form-label">Hostname</label>
{{ form.hostname(class="form-control", placeholder="example") }} {{ form.host_name(class="form-control", placeholder="example") }}
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="description" class="form-label">Description</label> <label class="form-label">Description</label>
{{ form.description(class="form-control", placeholder="webserver") }} {{ form.host_desc(class="form-control", placeholder="webserver") }}
</div> </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>
@@ -56,4 +73,14 @@
</div> </div>
</div> </div>
</div> </div>
<!-- MODAL -->
<div id="modal-get"
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 %}
+20 -1
View File
@@ -27,7 +27,16 @@
<td><b>{{network.description}}</b></td> <td><b>{{network.description}}</b></td>
<td> <td>
<a href="{{ url_for('base.allocations',net_id=network.id) }}" class="btn btn-sm btn-outline-info" style="padding-top:1px;padding-bottom:1px">Allocations</a> <a href="{{ url_for('base.allocations',net_id=network.id) }}" class="btn btn-sm btn-outline-info" style="padding-top:1px;padding-bottom:1px">Allocations</a>
<button class="btn btn-sm btn-outline-danger"style="padding-top:1px;padding-bottom:1px">delete</button> <button
hx-get="{{ url_for('base.network_del_modal',net_id=network.id) }}"
hx-trigger="click"
hx-target="#modal-get"
data-bs-toggle="modal"
data-bs-target="#modal-get"
class="btn btn-sm btn-outline-danger"
style="padding-top:1px;padding-bottom:1px"
> Delete
</button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -58,4 +67,14 @@
</div> </div>
</div> </div>
</div> </div>
<!-- MODAL -->
<div id="modal-get"
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 %}