mirror of
https://github.com/TakeusV1/flask_ipam.git
synced 2026-10-13 17:15:33 +02:00
V1.0
This commit is contained in:
1 parent
0980efcd24
commit
f11aa2d6c6
22 files changed
+491
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
*.pyc
|
||||||
|
/instance
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from flask import abort, request, jsonify, make_response, render_template
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
from datetime import datetime
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
from app._config import *
|
||||||
|
from app.models import *
|
||||||
|
|
||||||
|
from app.extensions import *
|
||||||
|
|
||||||
|
from app.routes.r_base import base
|
||||||
|
from app.routes.r_auth import auth
|
||||||
|
from app.routes.r_adm import admin
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Configuration)
|
||||||
|
|
||||||
|
login_manager.init_app(app)
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
app.wsgi_app = ProxyFix(
|
||||||
|
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
|
||||||
|
)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
## BLUEPRINTS
|
||||||
|
app.register_blueprint(base, url_prefix='/')
|
||||||
|
app.register_blueprint(auth, url_prefix='/auth')
|
||||||
|
app.register_blueprint(admin, url_prefix='/admin')
|
||||||
|
|
||||||
|
## SYSTEM ROUTES
|
||||||
|
@app.before_request
|
||||||
|
def before_request():
|
||||||
|
if app_maintenance:
|
||||||
|
return abort(503)
|
||||||
|
|
||||||
|
@app.route('/app_info')
|
||||||
|
def route_app_info():
|
||||||
|
response = make_response(jsonify({'app_name': app_name, 'app_version':app_version,'release_date':app_release_date,'app_maintenance':app_maintenance,'app_debug':app_debug}), 200)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route('/app_check')
|
||||||
|
def route_app_check():
|
||||||
|
response = make_response(jsonify({'check': 1}), 200)
|
||||||
|
return response
|
||||||
|
|
||||||
|
## ERROR ROUTES
|
||||||
|
@app.errorhandler(401)
|
||||||
|
def not_found(error):
|
||||||
|
HTTP_ERROR = [
|
||||||
|
"401",
|
||||||
|
"Unauthorized",
|
||||||
|
"The server could not verify that you are authorized to access the URL requested. You are not authenticated, or your browser doesn't understand how to supply the credentials required...",
|
||||||
|
"https://takeus.ovh/static/img/login.svg"
|
||||||
|
]
|
||||||
|
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 401
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def not_found(error):
|
||||||
|
HTTP_ERROR = [
|
||||||
|
"403",
|
||||||
|
"Forbidden",
|
||||||
|
"You don't have the permission to access the requested resource. It is either read-protected or not readable by the server...",
|
||||||
|
"https://takeus.ovh/static/img/login.svg"
|
||||||
|
]
|
||||||
|
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 403
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def not_found(error):
|
||||||
|
HTTP_ERROR = [
|
||||||
|
"404",
|
||||||
|
"Not Found",
|
||||||
|
"The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again...",
|
||||||
|
"https://takeus.ovh/static/img/404.svg"
|
||||||
|
]
|
||||||
|
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 404
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def not_found(error):
|
||||||
|
HTTP_ERROR = [
|
||||||
|
"500",
|
||||||
|
"Internal Server Error",
|
||||||
|
"The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.",
|
||||||
|
"https://takeus.ovh/static/img/500.svg"
|
||||||
|
]
|
||||||
|
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 500
|
||||||
|
|
||||||
|
@app.errorhandler(503)
|
||||||
|
def not_found(error):
|
||||||
|
HTTP_ERROR = [
|
||||||
|
"503",
|
||||||
|
"Service Unavailable",
|
||||||
|
"The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.",
|
||||||
|
"https://takeus.ovh/static/img/503.svg"
|
||||||
|
]
|
||||||
|
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 503
|
||||||
|
|
||||||
|
## FLASK LOGIN
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.query.filter_by(id=int(user_id)).first()
|
||||||
|
|
||||||
|
return app
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
##########################################################
|
||||||
|
############# CONFIGURATION FILE | Flask IPAM ############
|
||||||
|
|
||||||
|
## Flask Server (For DEV)
|
||||||
|
app_ip = '127.0.0.1'
|
||||||
|
app_port = 80
|
||||||
|
app_debug = True
|
||||||
|
app_maintenance = False
|
||||||
|
|
||||||
|
app_name = 'takeus-ipam'
|
||||||
|
app_version = 'V1.0'
|
||||||
|
app_release_date = '02/2025'
|
||||||
|
app_url = "http://127.0.0.1"
|
||||||
|
|
||||||
|
class Configuration:
|
||||||
|
# openssl rand -hex 32
|
||||||
|
SECRET_KEY = 'change_me'
|
||||||
|
# DB CONFIG
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///database.db'
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
SQLALCHEMY_ENGINE_OPTIONS = {"pool_pre_ping": True}
|
||||||
|
# CK CONFIG
|
||||||
|
SESSION_COOKIE_SECURE = False # SSL REQUIRED
|
||||||
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from flask_login import LoginManager
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_turnstile import Turnstile
|
||||||
|
|
||||||
|
login_manager = LoginManager()
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
turnstile = Turnstile()
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, PasswordField
|
||||||
|
from wtforms.validators import DataRequired, Length
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
username = StringField(validators=[DataRequired(),Length(max=128)])
|
||||||
|
password = PasswordField(validators=[DataRequired(),Length(max=128)])
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from app.extensions import db
|
||||||
|
from flask_login import UserMixin
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer(),primary_key=True,unique=True)
|
||||||
|
username = db.Column(db.String(16))
|
||||||
|
password = db.Column(db.String(32),unique=True)
|
||||||
|
is_admin = db.Column(db.Boolean(),default=False)
|
||||||
|
dat_last = db.Column(db.DateTime())
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from flask import Blueprint, request, render_template, flash, url_for, redirect, abort
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from app._config import app_version
|
||||||
|
|
||||||
|
from app.extensions import *
|
||||||
|
from app.models import *
|
||||||
|
|
||||||
|
admin = Blueprint('admin', __name__)
|
||||||
|
|
||||||
|
@admin.route('/')
|
||||||
|
@login_required
|
||||||
|
def home():
|
||||||
|
if current_user.is_admin:
|
||||||
|
return redirect(url_for('admin.dash'))
|
||||||
|
else:
|
||||||
|
return abort(403)
|
||||||
|
|
||||||
|
@admin.route('/dashboard')
|
||||||
|
@login_required
|
||||||
|
def dash():
|
||||||
|
if current_user.is_admin == False:
|
||||||
|
return abort(403)
|
||||||
|
|
||||||
|
return f"Welcome {current_user.username} to the admin dashboard"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from flask import Blueprint, request, render_template, flash, abort, redirect, url_for, jsonify
|
||||||
|
from flask_login import login_required, current_user, login_user, logout_user
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
|
from app._config import *
|
||||||
|
|
||||||
|
from app.extensions import *
|
||||||
|
from app.models import *
|
||||||
|
from app.forms import LoginForm
|
||||||
|
|
||||||
|
auth = Blueprint('auth', __name__)
|
||||||
|
|
||||||
|
@auth.route("/login",methods=['GET','POST'])
|
||||||
|
def login():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('base.dashboard'))
|
||||||
|
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.filter_by(username=form.username.data).first()
|
||||||
|
if user != None:
|
||||||
|
if user.password != None:
|
||||||
|
if check_password_hash(user.password,form.password.data):
|
||||||
|
login_user(user)
|
||||||
|
|
||||||
|
user.dat_last = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return redirect(url_for("base.dashboard"))
|
||||||
|
|
||||||
|
flash("Bad username or password !", 'danger')
|
||||||
|
return render_template("auth/login.html", title="Login", form=form, bad_password=True)
|
||||||
|
return render_template('auth/login.html', title="Login", form=form)
|
||||||
|
|
||||||
|
@auth.route("/change",methods=['GET','POST'])
|
||||||
|
@login_required
|
||||||
|
def change():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@auth.route("/logout")
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from flask import Blueprint, request, render_template, flash, redirect, url_for
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from app._config import app_version
|
||||||
|
|
||||||
|
from app.extensions import *
|
||||||
|
from app.models import *
|
||||||
|
|
||||||
|
base = Blueprint('base', __name__)
|
||||||
|
|
||||||
|
@base.route('/',methods=['GET','POST'])
|
||||||
|
def home():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('base.dashboard'))
|
||||||
|
else:
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
@base.route('/dashboard',methods=['GET','POST'])
|
||||||
|
@login_required
|
||||||
|
def dashboard():
|
||||||
|
|
||||||
|
return render_template('panel/dashboard.html', title="Dashboard", is_dash=True, app_version=app_version)
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
@charset "UTF-8";/*!
|
||||||
|
* Bootswatch v5.3.3 (https://bootswatch.com)
|
||||||
|
* Theme: cerulean
|
||||||
|
* Copyright 2012-2024 Thomas Park
|
||||||
|
* Licensed under MIT
|
||||||
|
* Based on Bootstrap
|
||||||
|
*//*!
|
||||||
|
* Bootstrap v5.3.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2024 The Bootstrap Authors
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/:root,[data-bs-theme=light]{--bs-blue:#033c73;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#e83e8c;--bs-red:#c71c22;--bs-orange:#fd7e14;--bs-yellow:#dd5600;--bs-green:#73a839;--bs-teal:#20c997;--bs-cyan:#2fa4e7;--bs-black:#000;--bs-white:#fff;--bs-gray:#868e96;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#868e96;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#2fa4e7;--bs-secondary:#e9ecef;--bs-success:#73a839;--bs-info:#033c73;--bs-warning:#dd5600;--bs-danger:#c71c22;--bs-light:#f8f9fa;--bs-dark:#343a40;--bs-primary-rgb:47,164,231;--bs-secondary-rgb:233,236,239;--bs-success-rgb:115,168,57;--bs-info-rgb:3,60,115;--bs-warning-rgb:221,86,0;--bs-danger-rgb:199,28,34;--bs-light-rgb:248,249,250;--bs-dark-rgb:52,58,64;--bs-primary-text-emphasis:#13425c;--bs-secondary-text-emphasis:#5d5e60;--bs-success-text-emphasis:#2e4317;--bs-info-text-emphasis:#01182e;--bs-warning-text-emphasis:#582200;--bs-danger-text-emphasis:#500b0e;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#d5edfa;--bs-secondary-bg-subtle:#fbfbfc;--bs-success-bg-subtle:#e3eed7;--bs-info-bg-subtle:#cdd8e3;--bs-warning-bg-subtle:#f8ddcc;--bs-danger-bg-subtle:#f4d2d3;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#acdbf5;--bs-secondary-border-subtle:#f6f7f9;--bs-success-border-subtle:#c7dcb0;--bs-info-border-subtle:#9ab1c7;--bs-warning-border-subtle:#f1bb99;--bs-danger-border-subtle:#e9a4a7;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#495057;--bs-body-color-rgb:73,80,87;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(73, 80, 87, 0.75);--bs-secondary-color-rgb:73,80,87;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(73, 80, 87, 0.5);--bs-tertiary-color-rgb:73,80,87;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:#2fa4e7;--bs-link-color:#2fa4e7;--bs-link-color-rgb:47,164,231;--bs-link-decoration:underline;--bs-link-hover-color:#2683b9;--bs-link-hover-color-rgb:38,131,185;--bs-code-color:#e83e8c;--bs-highlight-color:#495057;--bs-highlight-bg:#f8ddcc;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(47, 164, 231, 0.25);--bs-form-valid-color:#73a839;--bs-form-valid-border-color:#73a839;--bs-form-invalid-color:#c71c22;--bs-form-invalid-border-color:#c71c22}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#82c8f1;--bs-secondary-text-emphasis:#f2f4f5;--bs-success-text-emphasis:#abcb88;--bs-info-text-emphasis:#688aab;--bs-warning-text-emphasis:#eb9a66;--bs-danger-text-emphasis:#dd777a;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#09212e;--bs-secondary-bg-subtle:#2f2f30;--bs-success-bg-subtle:#17220b;--bs-info-bg-subtle:#010c17;--bs-warning-bg-subtle:#2c1100;--bs-danger-bg-subtle:#280607;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#1c628b;--bs-secondary-border-subtle:#8c8e8f;--bs-success-border-subtle:#456522;--bs-info-border-subtle:#022445;--bs-warning-border-subtle:#853400;--bs-danger-border-subtle:#771114;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inheriLine truncated
|
||||||
|
/*# sourceMappingURL=bootstrap.min.css.map */
|
||||||
Whitespace-only changes.
+7
@@ -0,0 +1,7 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap v5.3.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
*/
|
||||||
|
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slLine truncated
|
||||||
|
//# sourceMappingURL=bootstrap.bundle.min.js.map
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"names":["elementMap","Map","Data","set","element","key","instance","has","instanceMap","get","size","console","error","Array","from","keys","remove","delete","TRANSITION_END","parseSelector","selector","window","CSS","escape","replace","match","id","triggerTransitionEnd","dispatchEvent","Event","isElement","object","jquery","nodeType","getElement","length","document","querySelector","isVisible","getClientRects","elementIsVisible","getComputedStyle","getPropertyValue","closedDetails","closest","summary","parentNode","isDisabled","Node","ELEMENT_NODE","classList","contains","disabled","hasAttribute","getAttribute","findShadowRoot","documentElement","attachShadow","getRootNode","root","ShadowRoot","noop","reflow","offsetHeight","getjQuery","jQuery","body","DOMContentLoadedCallbacks","isRTL","dir","defineJQueryPlugin","plugin","callback","$","name","NAME","JQUERY_NO_CONFLICT","fn","jQueryInterface","Constructor","noConflict","readyState","addEventListener","push","execute","possibleCallback","args","defaultValue","executeAfterTransition","transitionElement","waitForTransition","emulatedDuration","transitionDuration","transitionDelay","floatTransitionDuration","Number","parseFloat","floatTransitionDelay","split","getTransitionDurationFromElement","called","handler","target","removeEventListener","setTimeout","getNextActiveElement","list","activeElement","shouldGetNext","isCycleAllowed","listLength","index","indexOf","Math","max","min","namespaceRegex","stripNameRegex","stripUidRegex","eventRegistry","uidEvent","customEvents","mouseenter","mouseleave","nativeEvents","Set","makeEventUid","uid","getElementEvents","findHandler","events","callable","delegationSelector","Object","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","call","this","handlers","previousFunction","domElements","querySelectorAll","domElement","hydrateObj","EventHandler","off","type","apply","bootstrapDelegationHandler","bootstrapHandler","removeHandler","Boolean","removeNamespacedHandlers","namespace","storeElementEvent","handlerKey","entries","includes","on","one","inNamespace","isNamespace","startsWith","elementEvent","slice","keyHandlers","trigger","jQueryEvent","bubbles","nativeDispatch","defaultPrevented","isPropagationStopped","isImmediatePropagationStopped","isDefaultPrevented","evt","cancelable","preventDefault","obj","meta","value","_unused","defineProperty","configurable","normalizeData","toString","JSON","parse","decodeURIComponent","normalizeDataKey","chr","toLowerCase","Manipulator","setDataAttribute","setAttribute","removeDataAttribute","removeAttribute","getDataAttributes","attributes","bsKeys","dataset","filter","pureKey","charAt","getDataAttribute","Config","Default","DefaultType","Error","_getConfig","config","_mergeConfigObj","_configAfterMerge","_typeCheckConfig","jsonConfig","constructor","configTypes","property","expectedTypes","valueType","prototype","RegExp","test","TypeError","toUpperCase","BaseComponent","super","_element","_config","DATA_KEY","dispose","EVENT_KEY","propertyName","getOwnPropertyNames","_queueCallback","isAnimated","getInstance","getOrCreateInstance","VERSION","eventName","getSelector","hrefAttribute","trim","map","sel","join","SelectorEngine","concat","Element","findOne","children","child","matches","parents","ancestor","prev","previous","previousElementSibling","next","nextElementSibling","focusableChildren","focusables","el","getSelectorFromElement","getElementFromSelector","getMultipleElementsFromSelector","enableDismissTrigger","component","method","clickEvent","tagName","EVENT_CLOSE","EVENT_CLOSED","Alert","close","_destroyElement","each","data","undefined","SELECTOR_DATA_TOGGLE","Button","toggle","button","EVENT_TOUCHSTART","EVENT_TOUCHMOVE","EVENT_TOUCHEND","EVENT_POINTERDOWN","EVENT_POINTERUP","endCallback","leftCallback","rightCallback","Swipe","isSupported","_deltaX","_supportPointerEvents","PointerEvent","_initEvents","_start","_eventIsPointerPenTouch","clientX","touches","_end","_handleSwipe","_move","absDeltaX","abs","direction","add","pointerType","navigator","maxTouchPoints","DATA_API_KEY","ORDER_NEXT","ORDER_PREV","DIRECTION_LEFT","DIRECTION_RIGHT","EVENT_SLIDE","EVENT_SLID","EVENT_KEYDOWN","EVENT_MOUSEENTER","EVENT_MOUSELEAVE","EVENT_DRAG_START","EVENT_LOAD_DATA_API","EVENT_CLICK_DATA_API","CLASS_NAME_CAROUSEL","CLASS_NAME_ACTIVE","SELECTOR_ACTIVE","SELECTOR_ITEM","SELECTOR_ACTIVE_ITEM","KEY_TO_DIRECTION","ArrowLeft","ArrowRight","interval","keyboard","pause","ride","touch","wrap","Carousel","_interval","_activeElement","_isSliding","touchTimeout","_swipeHelper","_indicatorsElement","_addEventListeners","cycle","_slide","nextWhenVisible","hidden","_clearInterval","_updateInterval","setInterval","_maybeEnableCycle","to","items","_getItems","activeIndex","_getItemIndex","_getActive","order","defaultInterval","_keydown","_addTouLine truncated
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<!-- BASE -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>IPAM - {% block styles %}{{ title }}{% endblock %}</title>
|
||||||
|
<meta name="author" content="Takeus">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<!-- LINKS -->
|
||||||
|
<link rel="icon" type="image/png" href="{{ url_for('static',filename='img/logo.png') }}" />
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static',filename='css/custom.css') }}"/>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css') }}"/>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
|
</head>
|
||||||
|
<body class=" d-flex flex-column">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(to right, #87CEEB, #4682B4);
|
||||||
|
background-size: 100% 300%;
|
||||||
|
animation: gradientAnimation 5s ease infinite;
|
||||||
|
}
|
||||||
|
@keyframes gradientAnimation {
|
||||||
|
0% {background-position: 0% 50%;}
|
||||||
|
50% {background-position: 100% 50%;}
|
||||||
|
100% {background-position: 0% 50%;}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<!-- JAVASCRIPT -->
|
||||||
|
<script src="{{ url_for('static',filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!-- Flash message -->
|
||||||
|
{% with errors = get_flashed_messages(with_categories=true,category_filter=["success","warning","danger","error"]) %}
|
||||||
|
{% if errors %}
|
||||||
|
{% for category, message in errors %}
|
||||||
|
<div class="alert alert-dismissible alert-{{ category }} m-0" style="position:fixed;top:5%;left:38%;z-index:10;min-width:25%;">
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
<p class="mb-0"><b>{{ message }}</b></a></p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<!-- END|Flash message -->
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{% extends 'auth/_base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{% include "auth/_flash.html" %}
|
||||||
|
<div class="container d-flex justify-content-center align-items-center vh-100">
|
||||||
|
<div class="card p-4 shadow-lg rounded-3">
|
||||||
|
<h2 class="text-center mb-4">Login Page</h2>
|
||||||
|
<form method="POST">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-person"></i></span>
|
||||||
|
{% if bad_password %}
|
||||||
|
{{ form.username(class="form-control is-invalid", placeholder="username") }}
|
||||||
|
{% else %}
|
||||||
|
{{ form.username(class="form-control", placeholder="username") }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-lock"></i></span>
|
||||||
|
{% if bad_password %}
|
||||||
|
{{ form.password(class="form-control is-invalid", placeholder="password") }}
|
||||||
|
{% else %}
|
||||||
|
{{ form.password(class="form-control", placeholder="password") }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<!-- BASE -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>IPAM - {% block styles %}{{ title }}{% endblock %}</title>
|
||||||
|
<meta name="author" content="Takeus">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<!-- LINKS -->
|
||||||
|
<link rel="icon" type="image/png" href="{{ url_for('static',filename='img/logo.png') }}" />
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static',filename='css/custom.css') }}"/>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css') }}"/>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
<!-- JAVASCRIPT -->
|
||||||
|
<script src="{{ url_for('static',filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<nav class="navbar navbar-expand-lg bg-dark bg-body-tertiary" data-bs-theme="dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">Flask IPAM</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if is_dash %}active{% endif %}" href="{{ url_for('base.dashboard') }}">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">Network</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="#">Subnets</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#">Attributions</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<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">
|
||||||
|
<li><a class="dropdown-item" href="#"><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="#"><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="#"><i class="bi bi-collection"></i> Others</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% if current_user.admin %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" href="">Admin</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<form class="d-flex" role="search">
|
||||||
|
<a class="btn btn-outline-warning" type="submit" href="{{ url_for('auth.logout') }}">logout</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends 'panel/_base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
{% include 'panel/_nav.html' %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<!-- BASE -->
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Takeus - {{HTTP_ERROR[0]}} Error</title>
|
||||||
|
<meta name="author" content="Takeus">
|
||||||
|
<meta name="copyright" content="Takeus 2024">
|
||||||
|
<!-- CSS/LINKS -->
|
||||||
|
<link rel="icon" type="image/png" href="{{ url_for('static',filename='/img/logo.png') }}" />
|
||||||
|
<link rel="stylesheet" href="https://bootswatch.com/5/zephyr/bootstrap.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="px-4 pt-5 my-5 text-center">
|
||||||
|
<h1 class="display-4 fw-bold">{{HTTP_ERROR[0]}} <u>{{HTTP_ERROR[1]}}</u></h1>
|
||||||
|
<div class="col-lg-6 mx-auto">
|
||||||
|
<p class="lead mb-4">{{HTTP_ERROR[2]}}</p>
|
||||||
|
{% if HTTP_ERROR[0] != "503" %}
|
||||||
|
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center mb-5">
|
||||||
|
<a type="button" class="btn btn-primary btn-lg px-4 me-sm-3" href="/">Home</a>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-lg px-4" onclick="location.reload();">Reload</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="container px-5">
|
||||||
|
<img src="{{HTTP_ERROR[3]}}" class="img-fluid" alt="403" loading="lazy" width="300">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Flask
|
||||||
|
Flask-Login
|
||||||
|
Flask-SQLAlchemy
|
||||||
|
Flask-WTF
|
||||||
|
mysql-connector-python
|
||||||
|
SQLAlchemy
|
||||||
|
cryptography
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from app._config import *
|
||||||
|
from flask import Flask, request
|
||||||
|
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Configuration)
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
id = db.Column(db.Integer(),primary_key=True,unique=True)
|
||||||
|
username = db.Column(db.String(16))
|
||||||
|
password = db.Column(db.String(32),unique=True)
|
||||||
|
is_admin = db.Column(db.Boolean(),default=False)
|
||||||
|
dat_last = db.Column(db.DateTime())
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
@app.route('/',methods=['GET','POST'])
|
||||||
|
def setup():
|
||||||
|
if request.method == "POST":
|
||||||
|
db.session.add(User(username=request.form["username"],password=generate_password_hash(request.form["password"]),is_admin=True))
|
||||||
|
db.session.commit()
|
||||||
|
return 'Done, you can delete setup.py'
|
||||||
|
return "<h1>admin user creation</h1><br><form method='post'><input name='username' placeholder='username'><br><input type='password' name='password' placeholder='password'><br><button type='submit'>submit</button></form>"
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host=app_ip, port=app_port, debug=app_debug)
|
||||||
Reference in new issue
Block a user