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 wtforms import StringField, PasswordField, IntegerField
from wtforms import StringField, PasswordField, IntegerField, SelectField
from wtforms.validators import DataRequired, Length, IPAddress, NumberRange
class LoginForm(FlaskForm):
@@ -9,9 +9,10 @@ class LoginForm(FlaskForm):
class NewPrefixForm(FlaskForm):
subnet = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)])
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):
ipv4 = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)])
hostname = StringField(validators=[DataRequired(),Length(max=128)])
description = StringField(validators=[DataRequired(),Length(max=128)])
host_name = 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):
ipv4 = db.Column(db.String(16),primary_key=True,unique=True)
net_id = db.Column(db.Integer(),db.ForeignKey('network.id'))
hardware_id = db.Column(db.Integer())
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)
@base.route('/network/allocations/<int:net_id>',methods=['GET','POST'])
@base.route('/network/<int:net_id>/allocations',methods=['GET','POST'])
@login_required
def allocations(net_id):
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()
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))
# 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.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.commit()
return redirect(url_for('base.allocations',net_id=net_id))
return 'nok'
flash("Invalid IPv4 Address", 'danger')
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()
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_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'))
+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>
{% block content %}{% endblock %}
<!-- JAVASCRIPT -->
<script src="{{ url_for('static',filename='js/htmx.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap.bundle.min.js') }}"></script>
</body>
</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">Hostname</th>
<th scope="col">Description</th>
<th scope="col">Device</th>
<th scope="col">Actions</th>
</tr>
</thead>
@@ -23,9 +24,25 @@
<td>{{allocation.ipv4}}</td>
<td>{{allocation.hostname}}</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>
<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>
</tr>
{% endfor %}
@@ -39,16 +56,16 @@
<form method="POST">
{{ form.csrf_token }}
<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) }}
</div>
<div class="mb-3">
<label for="prefix" class="form-label">Hostname</label>
{{ form.hostname(class="form-control", placeholder="example") }}
<label class="form-label">Hostname</label>
{{ form.host_name(class="form-control", placeholder="example") }}
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
{{ form.description(class="form-control", placeholder="webserver") }}
<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>
@@ -56,4 +73,14 @@
</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 %}
+20 -1
View File
@@ -27,7 +27,16 @@
<td><b>{{network.description}}</b></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>
<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>
</tr>
{% endfor %}
@@ -58,4 +67,14 @@
</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 %}