59 Commits
Author SHA1 Message Date
Lucas ce65441d8c Merge pull request #16 from TakeusV1/DEV
V2026.9
2026-09-16 16:07:01 +02:00
Lucas 5b7abc1510 Update README.md 2026-09-16 16:01:40 +02:00
Lucas 58a77e473b 2026.9 2026-09-16 16:01:13 +02:00
Lucas 140d51f248 2026.9 2026-09-16 15:22:36 +02:00
Lucas 80bf0fa42f 2026.9 2026-09-16 14:37:37 +02:00
Lucas e9066a2de6 Merge pull request #15 from TakeusV1/DEV
V2026.3
2026-03-16 12:44:27 +01:00
Lucas 963b960554 Update _config.py 2026-03-16 12:43:11 +01:00
Lucas aec29383c8 remove external img 2026-03-16 12:37:41 +01:00
Takeus bbec5d07db Update setup.py 2026-02-05 19:53:03 +01:00
Lucas 8403ad8f5c Merge pull request #14 from TakeusV1/DEV
V2026.1
2026-01-24 17:45:12 +01:00
Takeus b9170bdb23 Update _config.py 2026-01-22 16:52:30 +01:00
Lucas 800ff061b7 V2026.1
- Fixes
- ADD list view for allocations
- ADD hide dhcp option (list view)
2026-01-22 16:39:38 +01:00
Takeus 03e543e7f8 V1.6
- Add Notes field in inventory item
2025-11-02 21:24:20 +01:00
Takeus 5c5a174209 V1.6
- Dependency updates
- vLAN Support
- Fixes
2025-11-01 19:42:43 +01:00
Takeus 1d8b295e1a Merge pull request #13 from TakeusV1/DEV
V1.5
2025-06-19 14:10:06 +02:00
Takeus a2a86db788 V1.5
- BLACKLIST SYSTEM
- FIXES
2025-06-17 13:21:10 +02:00
Takeus 1f32755ade V1.5
- Rewrite theme system
- Fixes
2025-06-17 09:45:52 +02:00
Takeus 98ffa929ef Merge pull request #12 from TakeusV1/DEV
V1.4 - DEV
2025-06-04 10:46:10 +02:00
Takeus 6e63180e6f V1.4 - DEV
- Fixes
- Theme Support
- Dependencies update
2025-06-04 10:45:35 +02:00
Takeus 16bb7757d3 V1.4
- Fixes
- Theme Support
- Dependencies update
2025-06-04 10:43:21 +02:00
Takeus 02f2dcc654 Delete .github/workflows directory 2025-04-01 13:57:51 +02:00
Takeus 80b293b6e4 Update README.md 2025-04-01 13:56:45 +02:00
Takeus 04d9ba4f8a Update docker-compose.yml 2025-04-01 13:52:21 +02:00
Takeus e48ddd666b Update README.md 2025-04-01 13:51:35 +02:00
Takeus 3a0ea10efd Create docker-image.yml 2025-04-01 12:04:00 +02:00
Takeus 1523fc977d Merge pull request #11 from TakeusV1/DEV
Dev
2025-04-01 11:55:15 +02:00
Takeus 969b7dac2f Merge branch 'PROD' into DEV 2025-04-01 11:55:08 +02:00
Takeus a19f59e9a2 Update _config.py 2025-04-01 11:53:41 +02:00
Takeus 6bae5cec91 Update _config.py
Dynamic SECRET_KEY for docker support.
2025-04-01 11:53:00 +02:00
Takeus 275c575e90 Update docker-compose.yml 2025-04-01 11:42:32 +02:00
Takeus 2956b0abed Merge pull request #9 from cafrays/PROD
Docker Container Deployment
2025-04-01 11:41:28 +02:00
Takeus 5651121181 Merge pull request #10 from TakeusV1/DEV
Fixes
2025-03-31 14:49:39 +02:00
Takeus 88c26c931c FIxes 2025-03-31 14:49:12 +02:00
fdrock 67094b541a Adjusted container version 2025-03-29 21:52:22 -04:00
fdrock e2380e6919 Adding volumes to docker compose 2025-03-29 21:47:21 -04:00
Cafray a5c9664bf1 Update README.md 2025-03-29 15:43:30 -04:00
Cafray bd40a774d7 Update README.md 2025-03-29 15:42:43 -04:00
fdrock 47d9078473 Adding Docker Compose 2025-03-29 15:42:27 -04:00
fdrock 55aaeae1da Working on a Docker Container 2025-03-29 15:10:17 -04:00
Takeus 621b2b681a Merge pull request #8 from cafrays/PROD
Update install.md
2025-03-27 20:53:19 +01:00
Cafray 1c8ec0c159 Update install.md
Typo correction and added instructions to update app/_config.py
2025-03-27 15:44:24 -04:00
Takeus 7d60ce672b Merge pull request #7 from TakeusV1/DEV
Update setup.py
2025-03-27 20:06:52 +01:00
Takeus f3b6270504 Merge pull request #5 from cafrays/PROD
Update install.md
2025-03-27 20:06:15 +01:00
Takeus eaa6499bd0 Update setup.py 2025-03-27 20:04:40 +01:00
Cafray 1464767d4e Update install.md
Adding missing steps
2025-03-27 14:36:59 -04:00
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
51 changed files with 1269 additions and 412 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
FROM python:3.13.2-alpine3.21
WORKDIR /fipam
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app /fipam/app
COPY instance /fipam/instance
ENV APP_HOME=/fipam
ENV PYTHONPATH=${APP_HOME}
ENV PATH="$PATH:${APP_HOME}/.local/bin"
ENV GUNICORN_CMD_ARGS="--bind=0.0.0.0:5000 --workers=2 --preload"
EXPOSE 5000
ENTRYPOINT ["gunicorn","app:create_app()"]
+1
View File
@@ -0,0 +1 @@
Here you will find ideas for potential future updates.
+11
View File
@@ -0,0 +1,11 @@
#!/sbin/openrc-run
name="gunicorn-flask"
description="Gunicorn server for Flask app"
command="/usr/bin/gunicorn"
command_args="--workers 1 --bind 127.0.0.1:8000 'app:create_app()' --preload"
pidfile="/run/${name}.pid"
directory="/opt/fipam"
command_background="yes"
---------------------------------------------> /etc/init.d/flask-ipam
+13
View File
@@ -0,0 +1,13 @@
name: Flask-IPAM
services:
flask-ipam:
image: fipam_docker
container_name: flask-ipam
restart: unless-stopped
ports:
- 5000:5000
volumes:
- flask-ipam:/fipam/instance
volumes:
flask-ipam:
+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)
+11 -5
View File
@@ -10,6 +10,8 @@ from app.models import *
from app.extensions import *
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_adm import admin
@@ -18,6 +20,8 @@ def create_app():
app = Flask(__name__)
app.config.from_object(Configuration)
app.jinja_env.globals['app_version'] = app_version
login_manager.init_app(app)
db.init_app(app)
@@ -30,6 +34,8 @@ def create_app():
## BLUEPRINTS
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(admin, url_prefix='/admin')
@@ -56,7 +62,7 @@ def create_app():
"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"
"/static/img/403.png"
]
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 401
@@ -66,7 +72,7 @@ def create_app():
"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"
"/static/img/403.png"
]
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 403
@@ -76,7 +82,7 @@ def create_app():
"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"
"/static/img/404.png"
]
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 404
@@ -86,7 +92,7 @@ def create_app():
"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"
"/static/img/403.png"
]
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 500
@@ -96,7 +102,7 @@ def create_app():
"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"
"/static/img/403.png"
]
return render_template('srv_error.html',HTTP_ERROR=HTTP_ERROR), 503
+10 -6
View File
@@ -1,20 +1,24 @@
##########################################################
############# CONFIGURATION FILE | Flask IPAM ############
import random, string
## Flask Server (For DEV)
app_ip = '127.0.0.1'
app_port = 80
app_port = 5000
app_debug = True
app_maintenance = False
app_name = 'takeus-ipam'
app_version = 'V1.1'
app_release_date = '02/2025'
app_url = "http://127.0.0.1"
app_version = 'V2026.9'
app_release_date = '09/2026'
app_url = "http://0.0.0.0"
app_theme = ['default', 'spacelab', 'lumen', 'simplex', 'zephyr', 'cerulean', 'brite', 'materia', 'litera']
class Configuration:
# openssl rand -hex 32
SECRET_KEY = 'change_me'
# replace SECRET_KEY with value from "openssl rand -hex 32" or similar for STATIC sessions
# With actual configuration, every application restart, user have to re-login...
SECRET_KEY = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(32))
# DB CONFIG
SQLALCHEMY_DATABASE_URI = 'sqlite:///database.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
+32 -10
View File
@@ -1,6 +1,6 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField, SelectField
from wtforms.validators import DataRequired, Length, IPAddress, NumberRange
from wtforms import StringField, PasswordField, IntegerField, SelectField, TextAreaField
from wtforms.validators import DataRequired, Length, IPAddress
class LoginForm(FlaskForm):
username = StringField(validators=[DataRequired(),Length(max=128)])
@@ -9,24 +9,46 @@ class LoginForm(FlaskForm):
class NewPrefixForm(FlaskForm):
subnet = StringField(validators=[DataRequired(),IPAddress(ipv4=True),Length(max=128)])
prefix = SelectField(choices=[(30,'/30'),(29,'/29'),(28,'/28'),(27,'/27'),(26,'/26'),(25,'/25'),(24,'/24'),(23,'/23'),(22,'/22')],validators=[DataRequired()])
vlan_id = IntegerField()
description = StringField(validators=[Length(max=128)])
class NewAllocationForm(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):
class NewUserForm(FlaskForm):
username = StringField(validators=[DataRequired(),Length(max=128)])
password = PasswordField(validators=[DataRequired(),Length(max=128)])
is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
is_readonly = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
class ChangePasswordForm(FlaskForm):
password = PasswordField(validators=[DataRequired(),Length(max=128)])
is_admin = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
is_readonly = SelectField(choices=[(0,'No'),(1,'Yes')],validators=[DataRequired()])
class ChangeAllocationForm(FlaskForm):
hostname = StringField(validators=[Length(max=128)])
shortname = StringField(validators=[Length(max=128)])
fqdn = 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 ?')
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()
# Others
item_notes = StringField()
+20 -11
View File
@@ -3,22 +3,28 @@ 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)
username = db.Column(db.String(16),unique=True)
password = db.Column(db.String(32))
is_admin = db.Column(db.Boolean(),default=False)
is_reado = db.Column(db.Boolean(),default=False)
ui_theme = db.Column(db.String(16))
ui_lmode = db.Column(db.Boolean(),default=False)
dat_last = db.Column(db.DateTime())
class Network(db.Model):
id = db.Column(db.Integer(),primary_key=True,unique=True)
vlan_id = db.Column(db.Integer())
prefix = db.Column(db.Integer())
network_address = db.Column(db.String(16),unique=True)
hosts = db.Column(db.Integer())
description = db.Column(db.String(32))
is_blacklisted = db.Column(db.Boolean(),default=False)
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'))
hostname = db.Column(db.String(32))
hostname_full = db.Column(db.String(64))
description = db.Column(db.String(64))
is_used = db.Column(db.Boolean(),default=False)
is_dhcp = db.Column(db.Boolean(),default=False)
@@ -28,17 +34,20 @@ class Allocation(db.Model):
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_name = db.Column(db.String(64))
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))
host_model = db.Column(db.String(64))
host_manufacturer = db.Column(db.String(64))
host_serialnumber = db.Column(db.String(64))
# 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))
host_os = db.Column(db.String(64))
host_location = db.Column(db.String(64))
host_owner = db.Column(db.String(64))
host_contact = db.Column(db.String(64))
## Allocation(s)
host_allocations = db.Column(db.Text())
### Others
host_notes = db.Column(db.Text())
+7 -1
View File
@@ -26,10 +26,13 @@ def home():
return redirect(url_for('admin.home'))
is_admin = False
is_readonly = False
if int(form.is_admin.data) == 1:
is_admin = True
if int(form.is_readonly.data) == 1:
is_readonly = True
db.session.add(User(username=form.username.data, password=generate_password_hash(form.password.data), is_admin=is_admin))
db.session.add(User(username=form.username.data, password=generate_password_hash(form.password.data), is_admin=is_admin, is_reado=is_readonly))
db.session.commit()
flash("User Created", 'success')
return redirect(url_for('admin.home'))
@@ -67,8 +70,11 @@ def user_modal_edit(user_id):
if form.validate_on_submit() and request.method == 'POST':
user.password = generate_password_hash(form.password.data)
user.is_admin = False
user.is_reado = False
if int(form.is_admin.data) == 1:
user.is_admin = True
if int(form.is_readonly.data) == 1:
user.is_reado = True
db.session.commit()
flash("Informations Changed", 'success')
return redirect(url_for('admin.home'))
+16 -263
View File
@@ -1,35 +1,34 @@
from datetime import datetime
from flask import Blueprint, request, render_template, flash, redirect, url_for, session
from flask import Blueprint, render_template, redirect, url_for
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, app_theme
from app.extensions import *
from app.models import *
from app.forms import NewPrefixForm, NewAllocationForm, ChangeAllocationForm
base = Blueprint('base', __name__)
@base.route('/',methods=['GET','POST'])
@base.route('/')
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'])
@base.route('/dashboard')
@login_required
def dashboard():
db_networks = Network.query.count()
db_allocations = Allocation.query.filter_by(is_used=True).count()
db_inventory = Inventory.query.count()
db_users = User.query.count()
available_alloc = 0
db_allocations = 0
for available_ip in Network.query.all():
available_alloc += int(available_ip.hosts)
for network in Network.query.all():
if network.is_blacklisted == False:
available_alloc += int(network.hosts)
db_allocations += Allocation.query.filter_by(net_id=network.id, is_used=True).count()
return render_template(
'panel/dashboard.html',
@@ -42,261 +41,15 @@ def dashboard():
db_inventory=db_inventory,
available_alloc=available_alloc,
db_users=db_users,
Inventory=Inventory,
navcolor='dark'
)
@base.route('/network',methods=['GET','POST'])
@base.route("/theme/<string:name>",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
def change_theme(name):
if name in app_theme:
current_user.ui_theme = name
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)
return redirect(url_for('base.dashboard'))
return 'Invalid theme name', 400
+241
View File
@@ -0,0 +1,241 @@
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'),
('Notes',db_item.host_notes,'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
# Notes
if form.item_notes.data != '' and form.item_notes.data != 'None':
db_item.host_notes = form.item_notes.data
else:
db_item.host_notes = 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.item_notes.default = db_item.host_notes
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)
+333
View File
@@ -0,0 +1,333 @@
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)
if int(form.vlan_id.data) == 0:
vlan_id = None
else:
vlan_id = int(form.vlan_id.data)
add_net = Network(network_address=str(network.network_address),prefix=network.prefixlen,hosts=network.num_addresses-2,vlan_id=vlan_id,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):
# CHANGE VIEW
if request.args.get('view') == "change":
db_user = User.query.filter_by(id=current_user.id).first()
if current_user.ui_lmode == False:
db_user.ui_lmode = True
else:
db_user.ui_lmode = False
db.session.commit()
return redirect(url_for('network.allocations',net_id=net_id))
# HIDE DHCP
try:
session['hide_dhcp']
except:
session['hide_dhcp'] = False
if request.args.get('view') == "hide_dhcp":
if session['hide_dhcp'] == False:
session['hide_dhcp'] = True
else:
session['hide_dhcp'] = False
return redirect(url_for('network.allocations',net_id=net_id))
# PAGINATION MODE
if current_user.ui_lmode == False:
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')
# LIST MODE
else:
db_network = Network.query.filter_by(id=net_id).first()
db_allocations = Allocation.query.filter_by(net_id=net_id).all()
return render_template('panel/allocations.html', title="Allocations", is_networks=True, app_version=app_version, allocations=db_allocations, network=db_network, Inventory=Inventory, 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=str(request.args.get('page', type=int))+f"#{ipv4}", 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.shortname.data)
db_alloc.hostname_full = str(form.fqdn.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.hostname_full = 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),_anchor=request.args.get('item', type=str)))
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.shortname.default = db_alloc.hostname
form.fqdn.default = db_alloc.hostname_full
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=str(old_page)+f"#{ipv4}", 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.hostname_full = None
db_alloc.description = None
db_alloc.is_dhcp = False
db_alloc.is_gateway = False
db_alloc.is_decom = 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),_anchor=request.args.get('item', type=str)))
@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),_anchor=request.args.get('item', type=str)))
@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'))
@network.get('/<int:net_id>/blacklist/<action>')
@login_required
def network_blacklist(net_id, action):
if current_user.is_reado:
return abort(403)
db_network = Network.query.filter_by(id=net_id).first()
if db_network == None:
flash("Network Not Found", 'danger')
return redirect(url_for('network.networks'))
if action == 'blacklist':
db_network.is_blacklisted = True
db.session.commit()
flash("Network Blacklisted", 'warning')
elif action == 'unblacklist':
db_network.is_blacklisted = False
db.session.commit()
flash("Network Unblacklisted", 'success')
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)
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: brite
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/:root,[data-bs-theme=light]{--bs-blue:#61bcff;--bs-indigo:#828df9;--bs-purple:#be82fa;--bs-pink:#ea4998;--bs-red:#f56565;--bs-orange:#fa984a;--bs-yellow:#ffc700;--bs-green:#68d391;--bs-teal:#2ed3be;--bs-cyan:#22d2ed;--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:#a2e436;--bs-secondary:#fff;--bs-success:#68d391;--bs-info:#22d2ed;--bs-warning:#ffc700;--bs-danger:#f56565;--bs-light:#e9ecef;--bs-dark:#000;--bs-primary-rgb:162,228,54;--bs-secondary-rgb:255,255,255;--bs-success-rgb:104,211,145;--bs-info-rgb:34,210,237;--bs-warning-rgb:255,199,0;--bs-danger-rgb:245,101,101;--bs-light-rgb:233,236,239;--bs-dark-rgb:0,0,0;--bs-primary-text-emphasis:#415b16;--bs-secondary-text-emphasis:#666666;--bs-success-text-emphasis:#2a543a;--bs-info-text-emphasis:#0e545f;--bs-warning-text-emphasis:#665000;--bs-danger-text-emphasis:#622828;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#ecfad7;--bs-secondary-bg-subtle:white;--bs-success-bg-subtle:#e1f6e9;--bs-info-bg-subtle:#d3f6fb;--bs-warning-bg-subtle:#fff4cc;--bs-danger-bg-subtle:#fde0e0;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#daf4af;--bs-secondary-border-subtle:white;--bs-success-border-subtle:#c3edd3;--bs-info-border-subtle:#a7edf8;--bs-warning-border-subtle:#ffe999;--bs-danger-border-subtle:#fbc1c1;--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:0.875rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--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(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#000;--bs-link-color-rgb:0,0,0;--bs-link-decoration:underline;--bs-link-hover-color:black;--bs-link-hover-color-rgb:0,0,0;--bs-code-color:#ea4998;--bs-highlight-color:#212529;--bs-highlight-bg:#fff4cc;--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:1px;--bs-focus-ring-opacity:1;--bs-focus-ring-color:#000;--bs-form-valid-color:#68d391;--bs-form-valid-border-color:#68d391;--bs-form-invalid-color:#f56565;--bs-form-invalid-border-color:#f56565}[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:#c7ef86;--bs-secondary-text-emphasis:white;--bs-success-text-emphasis:#a4e5bd;--bs-info-text-emphasis:#7ae4f4;--bs-warning-text-emphasis:#ffdd66;--bs-danger-text-emphasis:#f9a3a3;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#202e0b;--bs-secondary-bg-subtle:#333333;--bs-success-bg-subtle:#152a1d;--bs-info-bg-subtle:#072a2f;--bs-warning-bg-subtle:#332800;--bs-danger-bg-subtle:#311414;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#618920;--bs-secondary-border-subtle:#999999;--bs-success-border-subtle:#3e7f57;--bs-info-border-subtle:#147e8e;--bs-warning-border-subtle:#997700;--bs-danger-border-subtle:#933d3d;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#c7ef86;--bs-link-hover-colorLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: cerulean
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 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 */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: litera
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/:root,[data-bs-theme=light]{--bs-blue:#4582ec;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#e83e8c;--bs-red:#d9534f;--bs-orange:#fd7e14;--bs-yellow:#f0ad4e;--bs-green:#02b875;--bs-teal:#20c997;--bs-cyan:#17a2b8;--bs-black:#000;--bs-white:#fff;--bs-gray:#868e96;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#ddd;--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:#4582ec;--bs-secondary:#adb5bd;--bs-success:#02b875;--bs-info:#17a2b8;--bs-warning:#f0ad4e;--bs-danger:#d9534f;--bs-light:#f8f9fa;--bs-dark:#343a40;--bs-primary-rgb:69,130,236;--bs-secondary-rgb:173,181,189;--bs-success-rgb:2,184,117;--bs-info-rgb:23,162,184;--bs-warning-rgb:240,173,78;--bs-danger-rgb:217,83,79;--bs-light-rgb:248,249,250;--bs-dark-rgb:52,58,64;--bs-primary-text-emphasis:#1c345e;--bs-secondary-text-emphasis:#45484c;--bs-success-text-emphasis:#014a2f;--bs-info-text-emphasis:#09414a;--bs-warning-text-emphasis:#60451f;--bs-danger-text-emphasis:#572120;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#dae6fb;--bs-secondary-bg-subtle:#eff0f2;--bs-success-bg-subtle:#ccf1e3;--bs-info-bg-subtle:#d1ecf1;--bs-warning-bg-subtle:#fcefdc;--bs-danger-bg-subtle:#f7dddc;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#b5cdf7;--bs-secondary-border-subtle:#dee1e5;--bs-success-border-subtle:#9ae3c8;--bs-info-border-subtle:#a2dae3;--bs-warning-border-subtle:#f9deb8;--bs-danger-border-subtle:#f0bab9;--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:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",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:1.1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#343a40;--bs-body-color-rgb:52,58,64;--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(52, 58, 64, 0.75);--bs-secondary-color-rgb:52,58,64;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(52, 58, 64, 0.5);--bs-tertiary-color-rgb:52,58,64;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#4582ec;--bs-link-color-rgb:69,130,236;--bs-link-decoration:underline;--bs-link-hover-color:#3768bd;--bs-link-hover-color-rgb:55,104,189;--bs-code-color:#e83e8c;--bs-highlight-color:#343a40;--bs-highlight-bg:#fcefdc;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#ddd;--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(69, 130, 236, 0.25);--bs-form-valid-color:#02b875;--bs-form-valid-border-color:#02b875;--bs-form-invalid-color:#d9534f;--bs-form-invalid-border-color:#d9534f}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#ddd;--bs-body-color-rgb:221,221,221;--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(221, 221, 221, 0.75);--bs-secondary-color-rgb:221,221,221;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(221, 221, 221, 0.5);--bs-tertiary-color-rgb:221,221,221;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#8fb4f4;--bs-secondary-text-emphasis:#ced3d7;--bs-success-text-emphasis:#67d4ac;--bs-info-text-emphasis:#74c7d4;--bs-warning-text-emphasis:#f6ce95;--bs-danger-text-emphasis:#e89895;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#ddd;--bs-primary-bg-subtle:#0e1a2f;--bs-secondary-bg-subtle:#232426;--bs-success-bg-subtle:#002517;--bs-info-bg-subtle:#052025;--bs-warning-bg-subtle:#302310;--bs-danger-bg-subtle:#2b1110;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#294e8e;--bs-secondary-border-subtle:#686d71;--bs-success-border-subtle:#016e46;--bs-info-border-subtle:#0e616e;--bs-warning-border-subtle:#90682f;--bs-danger-border-subtle:#82322f;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-coloLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: lumen
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,700;1,400&display=swap);:root,[data-bs-theme=light]{--bs-blue:#158cba;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#e83e8c;--bs-red:#ff4136;--bs-orange:#fd7e14;--bs-yellow:#ff851b;--bs-green:#28b62c;--bs-teal:#20c997;--bs-cyan:#75caeb;--bs-black:#000;--bs-white:#fff;--bs-gray:#999;--bs-gray-dark:#333;--bs-gray-100:#f6f6f6;--bs-gray-200:#f0f0f0;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#999;--bs-gray-700:#555;--bs-gray-800:#333;--bs-gray-900:#222;--bs-primary:#158cba;--bs-secondary:#f0f0f0;--bs-success:#28b62c;--bs-info:#75caeb;--bs-warning:#ff851b;--bs-danger:#ff4136;--bs-light:#f6f6f6;--bs-dark:#555;--bs-primary-rgb:21,140,186;--bs-secondary-rgb:240,240,240;--bs-success-rgb:40,182,44;--bs-info-rgb:117,202,235;--bs-warning-rgb:255,133,27;--bs-danger-rgb:255,65,54;--bs-light-rgb:246,246,246;--bs-dark-rgb:85,85,85;--bs-primary-text-emphasis:#08384a;--bs-secondary-text-emphasis:#606060;--bs-success-text-emphasis:#104912;--bs-info-text-emphasis:#2f515e;--bs-warning-text-emphasis:#66350b;--bs-danger-text-emphasis:#661a16;--bs-light-text-emphasis:#555;--bs-dark-text-emphasis:#555;--bs-primary-bg-subtle:#d0e8f1;--bs-secondary-bg-subtle:#fcfcfc;--bs-success-bg-subtle:#d4f0d5;--bs-info-bg-subtle:#e3f4fb;--bs-warning-bg-subtle:#ffe7d1;--bs-danger-bg-subtle:#ffd9d7;--bs-light-bg-subtle:#fbfbfb;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#a1d1e3;--bs-secondary-border-subtle:#f9f9f9;--bs-success-border-subtle:#a9e2ab;--bs-info-border-subtle:#c8eaf7;--bs-warning-border-subtle:#ffcea4;--bs-danger-border-subtle:#ffb3af;--bs-light-border-subtle:#f0f0f0;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--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:#222;--bs-body-color-rgb:34,34,34;--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(34, 34, 34, 0.75);--bs-secondary-color-rgb:34,34,34;--bs-secondary-bg:#f0f0f0;--bs-secondary-bg-rgb:240,240,240;--bs-tertiary-color:rgba(34, 34, 34, 0.5);--bs-tertiary-color-rgb:34,34,34;--bs-tertiary-bg:#f6f6f6;--bs-tertiary-bg-rgb:246,246,246;--bs-heading-color:inherit;--bs-link-color:#158cba;--bs-link-color-rgb:21,140,186;--bs-link-decoration:underline;--bs-link-hover-color:#117095;--bs-link-hover-color-rgb:17,112,149;--bs-code-color:#e83e8c;--bs-highlight-color:#222;--bs-highlight-bg:#ffe7d1;--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(21, 140, 186, 0.25);--bs-form-valid-color:#28b62c;--bs-form-valid-border-color:#28b62c;--bs-form-invalid-color:#ff4136;--bs-form-invalid-border-color:#ff4136}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#222;--bs-body-bg-rgb:34,34,34;--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:#333;--bs-secondary-bg-rgb:51,51,51;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b2b2b;--bs-tertiary-bg-rgb:43,43,43;--bs-primary-text-emphasis:#73bad6;--bs-secondary-text-emphasis:#f6f6f6;--bs-success-text-emphasis:#7ed380;--bs-info-text-emphasis:#acdff3;--bs-warning-text-emphasis:#ffb676;--bs-danger-text-emphasis:#ff8d86;--bs-light-text-emphasis:#f6f6f6;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#041c25;--bs-secondary-bg-subtle:#303030;--bs-success-bg-subtle:#082409;--bs-info-bg-subtle:#17282f;--bs-warning-bg-subtle:#331b05;--bs-danger-bg-subtle:#330d0b;--bs-light-bg-subtle:#333;--bs-dark-bg-subtle:#1a1a1a;--bs-primary-border-subtle:#0d5470;--bs-secondary-border-subtle:#909090;--bs-success-border-subtle:#186d1a;--bs-info-border-subtle:#46798d;--bs-warning-border-subtle:#995010;--bs-danger-border-subtle:#992720;--bs-light-border-subtle:#555;--bLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: materia
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap);:root,[data-bs-theme=light]{--bs-blue:#2196f3;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#e83e8c;--bs-red:#e51c23;--bs-orange:#fd7e14;--bs-yellow:#ff9800;--bs-green:#4caf50;--bs-teal:#20c997;--bs-cyan:#9c27b0;--bs-black:#000;--bs-white:#fff;--bs-gray:#666;--bs-gray-dark:#222;--bs-gray-100:#f8f9fa;--bs-gray-200:#eee;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#bbb;--bs-gray-600:#666;--bs-gray-700:#444;--bs-gray-800:#222;--bs-gray-900:#212121;--bs-primary:#2196f3;--bs-secondary:#fff;--bs-success:#4caf50;--bs-info:#9c27b0;--bs-warning:#ff9800;--bs-danger:#e51c23;--bs-light:#f8f9fa;--bs-dark:#222;--bs-primary-rgb:33,150,243;--bs-secondary-rgb:255,255,255;--bs-success-rgb:76,175,80;--bs-info-rgb:156,39,176;--bs-warning-rgb:255,152,0;--bs-danger-rgb:229,28,35;--bs-light-rgb:248,249,250;--bs-dark-rgb:34,34,34;--bs-primary-text-emphasis:#0d3c61;--bs-secondary-text-emphasis:#666666;--bs-success-text-emphasis:#1e4620;--bs-info-text-emphasis:#3e1046;--bs-warning-text-emphasis:#663d00;--bs-danger-text-emphasis:#5c0b0e;--bs-light-text-emphasis:#444;--bs-dark-text-emphasis:#444;--bs-primary-bg-subtle:#d3eafd;--bs-secondary-bg-subtle:white;--bs-success-bg-subtle:#dbefdc;--bs-info-bg-subtle:#ebd4ef;--bs-warning-bg-subtle:#ffeacc;--bs-danger-bg-subtle:#fad2d3;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#a6d5fa;--bs-secondary-border-subtle:white;--bs-success-border-subtle:#b7dfb9;--bs-info-border-subtle:#d7a9df;--bs-warning-border-subtle:#ffd699;--bs-danger-border-subtle:#f5a4a7;--bs-light-border-subtle:#eee;--bs-dark-border-subtle:#bbb;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:Roboto,-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;--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:#444;--bs-body-color-rgb:68,68,68;--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(68, 68, 68, 0.75);--bs-secondary-color-rgb:68,68,68;--bs-secondary-bg:#eee;--bs-secondary-bg-rgb:238,238,238;--bs-tertiary-color:rgba(68, 68, 68, 0.5);--bs-tertiary-color-rgb:68,68,68;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#2196f3;--bs-link-color-rgb:33,150,243;--bs-link-decoration:underline;--bs-link-hover-color:#1a78c2;--bs-link-hover-color-rgb:26,120,194;--bs-code-color:#e83e8c;--bs-highlight-color:#444;--bs-highlight-bg:#ffeacc;--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(33, 150, 243, 0.25);--bs-form-valid-color:#4caf50;--bs-form-valid-border-color:#4caf50;--bs-form-invalid-color:#e51c23;--bs-form-invalid-border-color:#e51c23}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212121;--bs-body-bg-rgb:33,33,33;--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:#222;--bs-secondary-bg-rgb:34,34,34;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#222222;--bs-tertiary-bg-rgb:34,34,34;--bs-primary-text-emphasis:#7ac0f8;--bs-secondary-text-emphasis:white;--bs-success-text-emphasis:#94cf96;--bs-info-text-emphasis:#c47dd0;--bs-warning-text-emphasis:#ffc166;--bs-danger-text-emphasis:#ef777b;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#071e31;--bs-secondary-bg-subtle:#333333;--bs-success-bg-subtle:#0f2310;--bs-info-bg-subtle:#1f0823;--bs-warning-bg-subtle:#331e00;--bs-danger-bg-subtle:#2e0607;--bs-light-bg-subtle:#222;--bs-dark-bg-subtle:#111111;--bs-primary-border-subtle:#145a92;--bs-secondary-border-subtle:#999999;--bs-success-border-subtle:#2e6930;--bs-info-border-subtle:#5e176a;--bs-warning-border-subtle:#995b00;--bs-danger-border-subtle:#891115;--bs-light-border-subtle:#444;--bs-dark-border-subtle:#222;--bs-heading-color:inherit;--bs-link-color:#7ac0f8;--bs-link-hover-color:#95cdf9;--bs-linLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: simplex
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap);:root,[data-bs-theme=light]{--bs-blue:#007bff;--bs-indigo:#6610f2;--bs-purple:#9b479f;--bs-pink:#e83e8c;--bs-red:#d9230f;--bs-orange:#d9831f;--bs-yellow:#ffc107;--bs-green:#469408;--bs-teal:#20c997;--bs-cyan:#029acf;--bs-black:#000;--bs-white:#fff;--bs-gray:#777;--bs-gray-dark:#373a3c;--bs-gray-100:#f8f9fa;--bs-gray-200:#ddd;--bs-gray-300:#ccc;--bs-gray-400:#bbb;--bs-gray-500:#adb5bd;--bs-gray-600:#777;--bs-gray-700:#444;--bs-gray-800:#373a3c;--bs-gray-900:#212529;--bs-primary:#d9230f;--bs-secondary:#fff;--bs-success:#469408;--bs-info:#029acf;--bs-warning:#d9831f;--bs-danger:#9b479f;--bs-light:#fff;--bs-dark:#373a3c;--bs-primary-rgb:217,35,15;--bs-secondary-rgb:255,255,255;--bs-success-rgb:70,148,8;--bs-info-rgb:2,154,207;--bs-warning-rgb:217,131,31;--bs-danger-rgb:155,71,159;--bs-light-rgb:255,255,255;--bs-dark-rgb:55,58,60;--bs-primary-text-emphasis:#570e06;--bs-secondary-text-emphasis:#666666;--bs-success-text-emphasis:#1c3b03;--bs-info-text-emphasis:#013e53;--bs-warning-text-emphasis:#57340c;--bs-danger-text-emphasis:#3e1c40;--bs-light-text-emphasis:#444;--bs-dark-text-emphasis:#444;--bs-primary-bg-subtle:#f7d3cf;--bs-secondary-bg-subtle:white;--bs-success-bg-subtle:#daeace;--bs-info-bg-subtle:#ccebf5;--bs-warning-bg-subtle:#f7e6d2;--bs-danger-bg-subtle:#ebdaec;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#bbb;--bs-primary-border-subtle:#f0a79f;--bs-secondary-border-subtle:white;--bs-success-border-subtle:#b5d49c;--bs-info-border-subtle:#9ad7ec;--bs-warning-border-subtle:#f0cda5;--bs-danger-border-subtle:#d7b5d9;--bs-light-border-subtle:#ddd;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:"Open Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--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:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fcfcfc;--bs-body-bg-rgb:252,252,252;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#ddd;--bs-secondary-bg-rgb:221,221,221;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#d9230f;--bs-link-color-rgb:217,35,15;--bs-link-decoration:underline;--bs-link-hover-color:#ae1c0c;--bs-link-hover-color-rgb:174,28,12;--bs-code-color:#e83e8c;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#ccc;--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(217, 35, 15, 0.25);--bs-form-valid-color:#469408;--bs-form-valid-border-color:#469408;--bs-form-invalid-color:#9b479f;--bs-form-invalid-border-color:#9b479f}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#ccc;--bs-body-color-rgb:204,204,204;--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(204, 204, 204, 0.75);--bs-secondary-color-rgb:204,204,204;--bs-secondary-bg:#373a3c;--bs-secondary-bg-rgb:55,58,60;--bs-tertiary-color:rgba(204, 204, 204, 0.5);--bs-tertiary-color-rgb:204,204,204;--bs-tertiary-bg:#2c3033;--bs-tertiary-bg-rgb:44,48,51;--bs-primary-text-emphasis:#e87b6f;--bs-secondary-text-emphasis:white;--bs-success-text-emphasis:#90bf6b;--bs-info-text-emphasis:#67c2e2;--bs-warning-text-emphasis:#e8b579;--bs-danger-text-emphasis:#c391c5;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#ccc;--bs-primary-bg-subtle:#2b0703;--bs-secondary-bg-subtle:#333333;--bs-success-bg-subtle:#0e1e02;--bs-info-bg-subtle:#001f29;--bs-warning-bg-subtle:#2b1a06;--bs-danger-bg-subtle:#1f0e20;--bs-light-bg-subtle:#373a3c;--bs-dark-bg-subtle:#1c1d1e;--bs-primary-border-subtle:#821509;--bs-secondary-border-subtle:#999999;--bs-success-border-subtle:#2a5905;--bs-info-border-subtle:#015c7c;--bs-warning-border-subtle:#824f13;--bs-danger-border-subtle:#5d2b5f;--bs-light-border-subtle:#444;--bs-dark-border-subtle:#373a3c;--bs-heading-color:Line truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: spacelab
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap);:root,[data-bs-theme=light]{--bs-blue:#446e9b;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#e83e8c;--bs-red:#cd0200;--bs-orange:#fd7e14;--bs-yellow:#d47500;--bs-green:#3cb521;--bs-teal:#20c997;--bs-cyan:#3399f3;--bs-black:#000;--bs-white:#fff;--bs-gray:#777;--bs-gray-dark:#333;--bs-gray-100:#f8f9fa;--bs-gray-200:#eee;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#999;--bs-gray-600:#777;--bs-gray-700:#495057;--bs-gray-800:#333;--bs-gray-900:#2d2d2d;--bs-primary:#446e9b;--bs-secondary:#999;--bs-success:#3cb521;--bs-info:#3399f3;--bs-warning:#d47500;--bs-danger:#cd0200;--bs-light:#eee;--bs-dark:#333;--bs-primary-rgb:68,110,155;--bs-secondary-rgb:153,153,153;--bs-success-rgb:60,181,33;--bs-info-rgb:51,153,243;--bs-warning-rgb:212,117,0;--bs-danger-rgb:205,2,0;--bs-light-rgb:238,238,238;--bs-dark-rgb:51,51,51;--bs-primary-text-emphasis:#1b2c3e;--bs-secondary-text-emphasis:#3d3d3d;--bs-success-text-emphasis:#18480d;--bs-info-text-emphasis:#143d61;--bs-warning-text-emphasis:#552f00;--bs-danger-text-emphasis:#520100;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#dae2eb;--bs-secondary-bg-subtle:#ebebeb;--bs-success-bg-subtle:#d8f0d3;--bs-info-bg-subtle:#d6ebfd;--bs-warning-bg-subtle:#f6e3cc;--bs-danger-bg-subtle:#f5cccc;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#b4c5d7;--bs-secondary-border-subtle:#d6d6d6;--bs-success-border-subtle:#b1e1a6;--bs-info-border-subtle:#add6fa;--bs-warning-border-subtle:#eec899;--bs-danger-border-subtle:#eb9a99;--bs-light-border-subtle:#eee;--bs-dark-border-subtle:#999;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:"Open Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--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:#777;--bs-body-color-rgb:119,119,119;--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(119, 119, 119, 0.75);--bs-secondary-color-rgb:119,119,119;--bs-secondary-bg:#eee;--bs-secondary-bg-rgb:238,238,238;--bs-tertiary-color:rgba(119, 119, 119, 0.5);--bs-tertiary-color-rgb:119,119,119;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:#2d2d2d;--bs-link-color:#3399f3;--bs-link-color-rgb:51,153,243;--bs-link-decoration:underline;--bs-link-hover-color:#297ac2;--bs-link-hover-color-rgb:41,122,194;--bs-code-color:#e83e8c;--bs-highlight-color:#777;--bs-highlight-bg:#f6e3cc;--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(68, 110, 155, 0.25);--bs-form-valid-color:#3cb521;--bs-form-valid-border-color:#3cb521;--bs-form-invalid-color:#cd0200;--bs-form-invalid-border-color:#cd0200}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#2d2d2d;--bs-body-bg-rgb:45,45,45;--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:#333;--bs-secondary-bg-rgb:51,51,51;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#303030;--bs-tertiary-bg-rgb:48,48,48;--bs-primary-text-emphasis:#8fa8c3;--bs-secondary-text-emphasis:#c2c2c2;--bs-success-text-emphasis:#8ad37a;--bs-info-text-emphasis:#85c2f8;--bs-warning-text-emphasis:#e5ac66;--bs-danger-text-emphasis:#e16766;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#0e161f;--bs-secondary-bg-subtle:#1f1f1f;--bs-success-bg-subtle:#0c2407;--bs-info-bg-subtle:#0a1f31;--bs-warning-bg-subtle:#2a1700;--bs-danger-bg-subtle:#290000;--bs-light-bg-subtle:#333;--bs-dark-bg-subtle:#1a1a1a;--bs-primary-border-subtle:#29425d;--bs-secondary-border-subtle:#5c5c5c;--bs-success-border-subtle:#246d14;--bs-info-border-subtle:#1f5c92;--bs-warning-border-subtle:#7f4600;--bs-danger-border-subtle:#7b0100;--bs-light-border-subtle:#495057;--bs-daLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+12
View File
@@ -0,0 +1,12 @@
@charset "UTF-8";/*!
* Bootswatch v5.3.8 (https://bootswatch.com)
* Theme: zephyr
* Copyright 2012-2025 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap);:root,[data-bs-theme=light]{--bs-blue:#3459e6;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#da292e;--bs-orange:#f8765f;--bs-yellow:#f4bd61;--bs-green:#2fb380;--bs-teal:#20c997;--bs-cyan:#287bb5;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--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:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#3459e6;--bs-secondary:#fff;--bs-success:#2fb380;--bs-info:#287bb5;--bs-warning:#f4bd61;--bs-danger:#da292e;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:52,89,230;--bs-secondary-rgb:255,255,255;--bs-success-rgb:47,179,128;--bs-info-rgb:40,123,181;--bs-warning-rgb:244,189,97;--bs-danger-rgb:218,41,46;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#15245c;--bs-secondary-text-emphasis:#666666;--bs-success-text-emphasis:#134833;--bs-info-text-emphasis:#103148;--bs-warning-text-emphasis:#624c27;--bs-danger-text-emphasis:#571012;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#d6defa;--bs-secondary-bg-subtle:white;--bs-success-bg-subtle:#d5f0e6;--bs-info-bg-subtle:#d4e5f0;--bs-warning-bg-subtle:#fdf2df;--bs-danger-bg-subtle:#f8d4d5;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#aebdf5;--bs-secondary-border-subtle:white;--bs-success-border-subtle:#ace1cc;--bs-info-border-subtle:#a9cae1;--bs-warning-border-subtle:#fbe5c0;--bs-danger-border-subtle:#f0a9ab;--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:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--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:var(--bs-primary-color);--bs-link-color:#3459e6;--bs-link-color-rgb:52,89,230;--bs-link-decoration:underline;--bs-link-hover-color:#2a47b8;--bs-link-hover-color-rgb:42,71,184;--bs-code-color:#d63384;--bs-highlight-color:#495057;--bs-highlight-bg:#fdf2df;--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 1px 2px rgba(0, 0, 0, 0.05);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);--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(52, 89, 230, 0.25);--bs-form-valid-color:#2fb380;--bs-form-valid-border-color:#2fb380;--bs-form-invalid-color:#da292e;--bs-form-invalid-border-color:#da292e}[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:#859bf0;--bs-secondary-text-emphasis:white;--bs-success-text-emphasis:#82d1b3;--bs-info-text-emphasis:#7eb0d3;--bs-warning-text-emphasis:#f8d7a0;--bs-danger-text-emphasis:#e97f82;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#0a122e;--bs-secondary-bg-subtle:#333333;--bs-success-bg-subtle:#09241a;--bs-info-bg-subtle:#081924;--bs-warning-bg-subtle:#312613;--bs-danger-bg-subtle:#2c0809;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#1f358a;--bs-secondary-border-subtle:#999999;--bs-success-border-subtle:#1c6b4d;--bs-info-border-subtle:#184a6d;--bs-warning-border-subtle:#92713a;--bs-danger-border-subtle:#83191c;-Line truncated
/*# sourceMappingURL=bootstrap.min.css.map */
+2 -2
View File
@@ -1,5 +1,5 @@
/*!
* Bootstrap Icons v1.11.3 (https://icons.getbootstrap.com/)
* Bootstrap Icons v1.13.1 (https://icons.getbootstrap.com/)
* Copyright 2019-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE)
*/@font-face{font-display:block;font-family:bootstrap-icons;src:url("/static/fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format("woff2"),url("/static/fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff")}.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bi-123::before{content:"\f67f"}.bi-alarm-fill::before{content:"\f101"}.bi-alarm::before{content:"\f102"}.bi-align-bottom::before{content:"\f103"}.bi-align-center::before{content:"\f104"}.bi-align-end::before{content:"\f105"}.bi-align-middle::before{content:"\f106"}.bi-align-start::before{content:"\f107"}.bi-align-top::before{content:"\f108"}.bi-alt::before{content:"\f109"}.bi-app-indicator::before{content:"\f10a"}.bi-app::before{content:"\f10b"}.bi-archive-fill::before{content:"\f10c"}.bi-archive::before{content:"\f10d"}.bi-arrow-90deg-down::before{content:"\f10e"}.bi-arrow-90deg-left::before{content:"\f10f"}.bi-arrow-90deg-right::before{content:"\f110"}.bi-arrow-90deg-up::before{content:"\f111"}.bi-arrow-bar-down::before{content:"\f112"}.bi-arrow-bar-left::before{content:"\f113"}.bi-arrow-bar-right::before{content:"\f114"}.bi-arrow-bar-up::before{content:"\f115"}.bi-arrow-clockwise::before{content:"\f116"}.bi-arrow-counterclockwise::before{content:"\f117"}.bi-arrow-down-circle-fill::before{content:"\f118"}.bi-arrow-down-circle::before{content:"\f119"}.bi-arrow-down-left-circle-fill::before{content:"\f11a"}.bi-arrow-down-left-circle::before{content:"\f11b"}.bi-arrow-down-left-square-fill::before{content:"\f11c"}.bi-arrow-down-left-square::before{content:"\f11d"}.bi-arrow-down-left::before{content:"\f11e"}.bi-arrow-down-right-circle-fill::before{content:"\f11f"}.bi-arrow-down-right-circle::before{content:"\f120"}.bi-arrow-down-right-square-fill::before{content:"\f121"}.bi-arrow-down-right-square::before{content:"\f122"}.bi-arrow-down-right::before{content:"\f123"}.bi-arrow-down-short::before{content:"\f124"}.bi-arrow-down-square-fill::before{content:"\f125"}.bi-arrow-down-square::before{content:"\f126"}.bi-arrow-down-up::before{content:"\f127"}.bi-arrow-down::before{content:"\f128"}.bi-arrow-left-circle-fill::before{content:"\f129"}.bi-arrow-left-circle::before{content:"\f12a"}.bi-arrow-left-right::before{content:"\f12b"}.bi-arrow-left-short::before{content:"\f12c"}.bi-arrow-left-square-fill::before{content:"\f12d"}.bi-arrow-left-square::before{content:"\f12e"}.bi-arrow-left::before{content:"\f12f"}.bi-arrow-repeat::before{content:"\f130"}.bi-arrow-return-left::before{content:"\f131"}.bi-arrow-return-right::before{content:"\f132"}.bi-arrow-right-circle-fill::before{content:"\f133"}.bi-arrow-right-circle::before{content:"\f134"}.bi-arrow-right-short::before{content:"\f135"}.bi-arrow-right-square-fill::before{content:"\f136"}.bi-arrow-right-square::before{content:"\f137"}.bi-arrow-right::before{content:"\f138"}.bi-arrow-up-circle-fill::before{content:"\f139"}.bi-arrow-up-circle::before{content:"\f13a"}.bi-arrow-up-left-circle-fill::before{content:"\f13b"}.bi-arrow-up-left-circle::before{content:"\f13c"}.bi-arrow-up-left-square-fill::before{content:"\f13d"}.bi-arrow-up-left-square::before{content:"\f13e"}.bi-arrow-up-left::before{content:"\f13f"}.bi-arrow-up-right-circle-fill::before{content:"\f140"}.bi-arrow-up-right-circle::before{content:"\f141"}.bi-arrow-up-right-square-fill::before{content:"\f142"}.bi-arrow-up-right-square::before{content:"\f143"}.bi-arrow-up-right::before{content:"\f144"}.bi-arrow-up-short::before{content:"\f145"}.bi-arrow-up-square-fill::before{content:"\f146"}.bi-arrow-up-square::before{content:"\f147"}.bi-arrow-up::before{content:"\f148"}.bi-arrows-angle-contract::before{content:"\f149"}.bi-arrows-angle-expand::before{content:"\f14a"}.bi-arrows-collapse::before{content:"\f14b"}.bi-arrows-expand::before{content:"\f14c"}.bi-arrows-fullscreen::before{content:"\f14d"}.bi-arrows-move::before{content:"\f14e"}.bi-aspect-ratio-fill::before{content:"\f14f"}.bi-aspect-ratio::before{content:"\f150"}.bi-asterisk::before{content:"\f151"}.bi-at::before{content:"\f152"}.bi-award-fill::before{content:"\f153"}.bi-award::before{content:"\f154"}.bi-back::before{content:"\f155"}.bi-backspace-fill::before{content:"\f156"}.bi-backspace-reverse-fill::before{content:"\f157"}.bi-backspace-reverse::before{content:"\f158"}.bi-backspace::before{content:"\f159"}.bi-badge-3d-fill::before{content:"\f15a"}.bi-badge-3d::before{content:"\f15b"}.bi-badge-4k-fill::before{content:"\f15c"}.bi-badge-4k::before{content:"\f15d"}.bi-badge-8k-fill::before{content:"\f15e"}.bi-badge-8k::before{content:"\f15f"}.bi-badge-ad-fill::before{content:"\f160"}.bi-badge-ad::before{content:"\f161"}.bi-badge-ar-fill::before{content:"\f162"}.bi-badge-ar::before{content:"\f163"}.bi-badge-cc-fill::beLine truncated
*/@font-face{font-display:block;font-family:bootstrap-icons;src:url("/static/fonts/bootstrap-icons.woff2?e34853135f9e39acf64315236852cd5a") format("woff2"),url("/static/fonts/bootstrap-icons.woff?e34853135f9e39acf64315236852cd5a") format("woff")}.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bi-123::before{content:"\f67f"}.bi-alarm-fill::before{content:"\f101"}.bi-alarm::before{content:"\f102"}.bi-align-bottom::before{content:"\f103"}.bi-align-center::before{content:"\f104"}.bi-align-end::before{content:"\f105"}.bi-align-middle::before{content:"\f106"}.bi-align-start::before{content:"\f107"}.bi-align-top::before{content:"\f108"}.bi-alt::before{content:"\f109"}.bi-app-indicator::before{content:"\f10a"}.bi-app::before{content:"\f10b"}.bi-archive-fill::before{content:"\f10c"}.bi-archive::before{content:"\f10d"}.bi-arrow-90deg-down::before{content:"\f10e"}.bi-arrow-90deg-left::before{content:"\f10f"}.bi-arrow-90deg-right::before{content:"\f110"}.bi-arrow-90deg-up::before{content:"\f111"}.bi-arrow-bar-down::before{content:"\f112"}.bi-arrow-bar-left::before{content:"\f113"}.bi-arrow-bar-right::before{content:"\f114"}.bi-arrow-bar-up::before{content:"\f115"}.bi-arrow-clockwise::before{content:"\f116"}.bi-arrow-counterclockwise::before{content:"\f117"}.bi-arrow-down-circle-fill::before{content:"\f118"}.bi-arrow-down-circle::before{content:"\f119"}.bi-arrow-down-left-circle-fill::before{content:"\f11a"}.bi-arrow-down-left-circle::before{content:"\f11b"}.bi-arrow-down-left-square-fill::before{content:"\f11c"}.bi-arrow-down-left-square::before{content:"\f11d"}.bi-arrow-down-left::before{content:"\f11e"}.bi-arrow-down-right-circle-fill::before{content:"\f11f"}.bi-arrow-down-right-circle::before{content:"\f120"}.bi-arrow-down-right-square-fill::before{content:"\f121"}.bi-arrow-down-right-square::before{content:"\f122"}.bi-arrow-down-right::before{content:"\f123"}.bi-arrow-down-short::before{content:"\f124"}.bi-arrow-down-square-fill::before{content:"\f125"}.bi-arrow-down-square::before{content:"\f126"}.bi-arrow-down-up::before{content:"\f127"}.bi-arrow-down::before{content:"\f128"}.bi-arrow-left-circle-fill::before{content:"\f129"}.bi-arrow-left-circle::before{content:"\f12a"}.bi-arrow-left-right::before{content:"\f12b"}.bi-arrow-left-short::before{content:"\f12c"}.bi-arrow-left-square-fill::before{content:"\f12d"}.bi-arrow-left-square::before{content:"\f12e"}.bi-arrow-left::before{content:"\f12f"}.bi-arrow-repeat::before{content:"\f130"}.bi-arrow-return-left::before{content:"\f131"}.bi-arrow-return-right::before{content:"\f132"}.bi-arrow-right-circle-fill::before{content:"\f133"}.bi-arrow-right-circle::before{content:"\f134"}.bi-arrow-right-short::before{content:"\f135"}.bi-arrow-right-square-fill::before{content:"\f136"}.bi-arrow-right-square::before{content:"\f137"}.bi-arrow-right::before{content:"\f138"}.bi-arrow-up-circle-fill::before{content:"\f139"}.bi-arrow-up-circle::before{content:"\f13a"}.bi-arrow-up-left-circle-fill::before{content:"\f13b"}.bi-arrow-up-left-circle::before{content:"\f13c"}.bi-arrow-up-left-square-fill::before{content:"\f13d"}.bi-arrow-up-left-square::before{content:"\f13e"}.bi-arrow-up-left::before{content:"\f13f"}.bi-arrow-up-right-circle-fill::before{content:"\f140"}.bi-arrow-up-right-circle::before{content:"\f141"}.bi-arrow-up-right-square-fill::before{content:"\f142"}.bi-arrow-up-right-square::before{content:"\f143"}.bi-arrow-up-right::before{content:"\f144"}.bi-arrow-up-short::before{content:"\f145"}.bi-arrow-up-square-fill::before{content:"\f146"}.bi-arrow-up-square::before{content:"\f147"}.bi-arrow-up::before{content:"\f148"}.bi-arrows-angle-contract::before{content:"\f149"}.bi-arrows-angle-expand::before{content:"\f14a"}.bi-arrows-collapse::before{content:"\f14b"}.bi-arrows-expand::before{content:"\f14c"}.bi-arrows-fullscreen::before{content:"\f14d"}.bi-arrows-move::before{content:"\f14e"}.bi-aspect-ratio-fill::before{content:"\f14f"}.bi-aspect-ratio::before{content:"\f150"}.bi-asterisk::before{content:"\f151"}.bi-at::before{content:"\f152"}.bi-award-fill::before{content:"\f153"}.bi-award::before{content:"\f154"}.bi-back::before{content:"\f155"}.bi-backspace-fill::before{content:"\f156"}.bi-backspace-reverse-fill::before{content:"\f157"}.bi-backspace-reverse::before{content:"\f158"}.bi-backspace::before{content:"\f159"}.bi-badge-3d-fill::before{content:"\f15a"}.bi-badge-3d::before{content:"\f15b"}.bi-badge-4k-fill::before{content:"\f15c"}.bi-badge-4k::before{content:"\f15d"}.bi-badge-8k-fill::before{content:"\f15e"}.bi-badge-8k::before{content:"\f15f"}.bi-badge-ad-fill::before{content:"\f160"}.bi-badge-ad::before{content:"\f161"}.bi-badge-ar-fill::before{content:"\f162"}.bi-badge-ar::before{content:"\f163"}.bi-badge-cc-fill::beLine truncated
+3 -9
View File
@@ -1,12 +1,6 @@
@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
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 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
*/:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--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:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--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:#212529;--bs-body-color-rgb:33,37,41;--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(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--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(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[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:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherLine truncated
/*# sourceMappingURL=bootstrap.min.css.map */
-1
View File
@@ -1 +0,0 @@
{"version":3,"sources":["../../scss/mixins/_banner.scss","../../scss/_root.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_color-mode.scss","../../scss/_reboot.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_color-bg.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_focus-ring.scss","../../scss/helpers/_icon-link.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"iBACE;;;;ACDF,MCOA,sBDEI,UAAA,QAAA,YAAA,QAAA,YAAA,QAAA,UAAA,QAAA,SAAA,QAAA,YAAA,QAAA,YAAA,QAAA,WAAA,QAAA,UAAA,QAAA,UAAA,QAAA,WAAA,KAAA,WAAA,KAAA,UAAA,QAAA,eAAA,QAIA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAIA,aAAA,QAAA,eAAA,QAAA,aAAA,QAAA,UAAA,QAAA,aAAA,QAAA,YAAA,QAAA,WAAA,QAAA,UAAA,QAIA,iBAAA,EAAA,CAAA,GAAA,CAAA,IAAA,mBAAA,GAAA,CAAA,GAAA,CAAA,IAAA,iBAAA,EAAA,CAAA,GAAA,CAAA,GAAA,cAAA,EAAA,CAAA,GAAA,CAAA,IAAA,iBAAA,GAAA,CAAA,GAAA,CAAA,EAAA,gBAAA,GAAA,CAAA,EAAA,CAAA,GAAA,eAAA,GAAA,CAAA,GAAA,CAAA,IAAA,cAAA,EAAA,CAAA,EAAA,CAAA,GAIA,2BAAA,QAAA,6BAAA,QAAA,2BAAA,QAAA,wBAAA,QAAA,2BAAA,QAAA,0BAAA,QAAA,yBAAA,QAAA,wBAAA,QAIA,uBAAA,QAAA,yBAAA,QAAA,uBAAA,QAAA,oBAAA,QAAA,uBAAA,QAAA,sBAAA,QAAA,qBAAA,QAAA,oBAAA,QAIA,2BAAA,QAAA,6BAAA,QAAA,2BAAA,QAAA,wBAAA,QAAA,2BAAA,QAAA,0BAAA,QAAA,yBAAA,QAAA,wBAAA,QAGF,eAAA,GAAA,CAAA,GAAA,CAAA,IACA,eAAA,CAAA,CAAA,CAAA,CAAA,EAMA,qBAAA,SAAA,CAAA,aAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,WAAA,CAAA,iBAAA,CAAA,KAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBACA,oBAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UACA,cAAA,2EAOA,sBAAA,0BE2OI,oBAAA,KFzOJ,sBAAA,IACA,sBAAA,IAKA,gBAAA,QACA,oBAAA,EAAA,CAAA,EAAA,CAAA,GACA,aAAA,KACA,iBAAA,GAAA,CAAA,GAAA,CAAA,IAEA,oBAAA,KACA,wBAAA,CAAA,CAAA,CAAA,CAAA,EAEA,qBAAA,uBACA,yBAAA,EAAA,CAAA,EAAA,CAAA,GACA,kBAAA,QACA,sBAAA,GAAA,CAAA,GAAA,CAAA,IAEA,oBAAA,sBACA,wBAAA,EAAA,CAAA,EAAA,CAAA,GACA,iBAAA,QACA,qBAAA,GAAA,CAAA,GAAA,CAAA,IAGA,mBAAA,QAEA,gBAAA,QACA,oBAAA,EAAA,CAAA,GAAA,CAAA,IACA,qBAAA,UAEA,sBAAA,QACA,0BAAA,EAAA,CAAA,EAAA,CAAA,IAMA,gBAAA,QACA,qBAAA,QACA,kBAAA,QAGA,kBAAA,IACA,kBAAA,MACA,kBAAA,QACA,8BAAA,qBAEA,mBAAA,SACA,sBAAA,QACA,sBAAA,OACA,sBAAA,KACA,uBAAA,KACA,uBAAA,4BACA,wBAAA,MAGA,gBAAA,EAAA,OAAA,KAAA,oBACA,mBAAA,EAAA,SAAA,QAAA,qBACA,mBAAA,EAAA,KAAA,KAAA,qBACA,sBAAA,MAAA,EAAA,IAAA,IAAA,qBAIA,sBAAA,QACA,wBAAA,KACA,sBAAA,yBAIA,sBAAA,QACA,6BAAA,QACA,wBAAA,QACA,+BAAA,QGhHE,qBHsHA,aAAA,KAGA,gBAAA,QACA,oBAAA,GAAA,CAAA,GAAA,CAAA,IACA,aAAA,QACA,iBAAA,EAAA,CAAA,EAAA,CAAA,GAEA,oBAAA,KACA,wBAAA,GAAA,CAAA,GAAA,CAAA,IAEA,qBAAA,0BACA,yBAAA,GAAA,CAAA,GAAA,CAAA,IACA,kBAAA,QACA,sBAAA,EAAA,CAAA,EAAA,CAAA,GAEA,oBAAA,yBACA,wBAAA,GAAA,CAAA,GAAA,CAAA,IACA,iBAAA,QACA,qBAAA,EAAA,CAAA,EAAA,CAAA,GAGE,2BAAA,QAAA,6BAAA,QAAA,2BAAA,QAAA,wBAAA,QAAA,2BAAA,QAAA,0BAAA,QAAA,yBAAA,QAAA,wBAAA,QAIA,uBAAA,QAAA,yBAAA,QAAA,uBAAA,QAAA,oBAAA,QAAA,uBAAA,QAAA,sBAAA,QAAA,qBAAA,QAAA,oBAAA,QAIA,2BAAA,QAAA,6BAAA,QAAA,2BAAA,QAAA,wBAAA,QAAA,2BAAA,QAAA,0BAAA,QAAA,yBAAA,QAAA,wBAAA,QAGF,mBAAA,QAEA,gBAAA,QACA,sBAAA,QACA,oBAAA,GAAA,CAAA,GAAA,CAAA,IACA,0BAAA,GAAA,CAAA,GAAA,CAAA,IAEALine truncated
+17
View File
@@ -7,3 +7,20 @@ pre {
margin: 0;
padding: 0;
}
html {
scroll-behavior: auto !important;
}
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+3 -3
View File
@@ -1,7 +1,7 @@
/*!
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Bootstrap v5.3.8 (https://getbootstrap.com/)
* Copyright 2011-2025 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
!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=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),o=t=>{t.dispatchEvent(new Event(i))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=t=>r(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,l=t=>{if(!r(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},c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=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?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();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?(p.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of p)t()}),p.push(e)):e()},_=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,b=(t,e,n=!0)=>{if(!n)return void _(t);const s=(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),_(t))};e.addEventListener(i,a),setTimeout(()=>{r||o(e)},s)},v=(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))])},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,A=/::\d+$/,E={};let T=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},O=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 x(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function k(t){const e=x(t);return t.uidEvent=e,E[e]=E[e]||{},E[e]}function L(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function S(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=N(t);return O.has(o)||(o=t),[n,s,o]}function D(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=S(e,i,n);if(e in C){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=k(t),c=l[a]||(l[a]={}),h=L(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(y,"")),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 j(s,{delegateTarget:r}),n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return j(n,{delegateTarget:t}),i.oneOff&&P.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 $(t,e,i,n,s){const o=L(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function I(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&$(t,e,i,r.callable,r.delegationSelector)}function N(t){return t=t.replace(w,""),C[t]||t}const P={on(t,e,i,n){D(t,e,i,n,!1)},one(t,e,i,n){D(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=k(t),c=l[rLine truncated
//# sourceMappingURL=bootstrap.bundle.min.js.map
+1 -1
View File
@@ -1 +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
{"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","toType","object","Object","prototype","toString","call","toLowerCase","triggerTransitionEnd","dispatchEvent","Event","isElement","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","values","find","event","normalizeParameters","originalTypeEvent","delegationFunction","isDelegated","typeEvent","getTypeEvent","addHandler","oneOff","wrapFunction","relatedTarget","delegateTarget","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","JSON","parse","decodeURIComponent","normalizeDataKey","chr","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","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","ARROW_LEFT_KEY","ARROW_RIGHT_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","ARROW_LEFT_KEY$1","ARROW_RIGHT_KEY$1","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",Line truncated
+14
View File
@@ -0,0 +1,14 @@
/*!
* Chart.js v4.5.1
* https://www.chartjs.org
* (c) 2025 Chart.js Contributors
* Released under the MIT 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).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;a<r;a++)e.call(i,t[a],a);else if(o(t))for(l=Object.keys(t),r=l.length,a=0;a<r;a++)e.call(i,t[l[a]],l[a])}function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(i=0,s=t.length;i<s;++i)if(n=t[i],o=e[i],n.datasetIndex!==o.datasetIndex||n.index!==o.index)return!1;return!0}function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(null),i=Object.keys(t),s=i.length;let n=0;for(;n<s;++n)e[i[n]]=g(t[i[n]]);return e}return t}function p(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?x(n,a,s):e[t]=g(a)}function x(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const r=(i=i||{}).merger||m;let l;for(let e=0;e<a;++e){if(l=s[e],!o(l))continue;const n=Object.keys(l);for(let e=0,s=n.length;e<s;++e)r(n[e],t,l,i)}return t}function b(t,e){return x(t,e,{merger:_})}function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?b(s,n):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=g(n))}const y={"":t=>t,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)<i}function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(z(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e.push(s),e.push(t/s));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o)||(e.min=Math.min(e.min,o),e.max=Math.max(e.max,o))}function $(t){return t*(C/180)}function Y(t){return t*(180/C)}function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Math.atan2(s,i);return o<-.5*C&&(o+=O),{angle:o,distance:n}}function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function K(t,e){return(t-e+A)%O-C}function G(t){return(t%O+O)%O}function J(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o),c=G(n-a);return n===o||n===a||s&&o===a||r>l&&h<c}function Z(t,e,i){return Math.max(e,Math.min(i,t))}function Q(t){return Z(t,-32768,32767)}function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return n<i||n===i&&t[s+1][e]===i}:s=>t[s][e]<i),st=(t,e,i)=>et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[n-1]>i;)n--;return s>0||n<t.length?t.slice(s,n):t}const ot=["push","pop","shift","splice","unshift"];function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ot.forEach((e=>{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...eLine truncated
/*!
* @kurkle/color v0.3.2
* https://github.com/kurkle/color#readme
* (c) 2023 Jukka Kurkela
* Released under the MIT License
*/function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Nt={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let Ht;function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object.keys(Wt);let s,n,o,a,r;for(s=0;s<e.length;s++){for(a=r=e[s],n=0;n<i.length;n++)o=i[n],r=r.replace(o,Wt[o]);o=parseInt(Nt[a],16),t[r]=[o>>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),Line truncated
//# sourceMappingURL=chart.umd.js.map
-20
View File
@@ -1,20 +0,0 @@
/**
* Skipped minification because the original files appears to be already minified.
* Original file: /npm/chart.js@4.4.7/dist/chart.umd.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
/*!
* Chart.js v4.4.7
* https://www.chartjs.org
* (c) 2024 Chart.js Contributors
* Released under the MIT 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).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Go},get Decimation(){return Qo},get Filler(){return ma},get Legend(){return ya},get SubTitle(){return ka},get Title(){return Ma},get Tooltip(){return Ba}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;a<r;a++)e.call(i,t[a],a);else if(o(t))for(l=Object.keys(t),r=l.length,a=0;a<r;a++)e.call(i,t[l[a]],l[a])}function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(i=0,s=t.length;i<s;++i)if(n=t[i],o=e[i],n.datasetIndex!==o.datasetIndex||n.index!==o.index)return!1;return!0}function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(null),i=Object.keys(t),s=i.length;let n=0;for(;n<s;++n)e[i[n]]=g(t[i[n]]);return e}return t}function p(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?b(n,a,s):e[t]=g(a)}function b(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const r=(i=i||{}).merger||m;let l;for(let e=0;e<a;++e){if(l=s[e],!o(l))continue;const n=Object.keys(l);for(let e=0,s=n.length;e<s;++e)r(n[e],t,l,i)}return t}function x(t,e){return b(t,e,{merger:_})}function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?x(s,n):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=g(n))}const y={"":t=>t,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)<i}function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(z(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e.push(s),e.push(t/s));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function N(t){return!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o)||(e.min=Math.min(e.min,o),e.max=Math.max(e.max,o))}function $(t){return t*(C/180)}function Y(t){return t*(180/C)}function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Math.atan2(s,i);return o<-.5*C&&(o+=O),{angle:o,distance:n}}function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function K(t,e){return(t-e+A)%O-C}function G(t){return(t%O+O)%O}function Z(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o),c=G(n-a);return n===o||n===a||s&&o===a||r>l&&h<c}function J(t,e,i){return Math.max(e,Math.min(i,t))}function Q(t){return J(t,-32768,32767)}function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return n<i||n===i&&t[s+1][e]===i}:s=>t[s][e]<i),st=(t,e,i)=>et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[n-1]>i;)n--;return s>0||n<t.length?t.slice(s,n):t}const ot=["push","pop","shift","splice","unshift"];function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ot.forEach((e=>{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){Line truncated
/*!
* @kurkle/color v0.3.2
* https://github.com/kurkle/color#readme
* (c) 2023 Jukka Kurkela
* Released under the MIT License
*/function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Nt={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let Ht;function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object.keys(Wt);let s,n,o,a,r;for(s=0;s<e.length;s++){for(a=r=e[s],n=0;n<i.length;n++)o=i[n],r=r.replace(o,Wt[o]);o=parseInt(Nt[a],16),t[r]=[o>>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),Line truncated
//# sourceMappingURL=chart.umd.js.map
+27
View File
@@ -0,0 +1,27 @@
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
function instantCenter() {
const hash = window.location.hash;
if (!hash) return;
const targetId = hash.substring(1);
const element = document.getElementById(targetId);
if (element) {
setTimeout(() => {
const elementRect = element.getBoundingClientRect();
const absoluteElementTop = elementRect.top + window.scrollY;
const middle = absoluteElementTop - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: middle,
behavior: 'auto'
});
}, 0);
}
}
window.addEventListener('load', instantCenter);
+2 -1
View File
@@ -1 +1,2 @@
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
// HTMX v4.0.0
var htmx=(()=>{const e={parse(t){if(!t)return{};if(t.startsWith("{"))return JSON.parse(t);let r=/(?:"([^"]+)"|'([^']+)'|([^\s,:]+))(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<((?:[^/]|\/(?!>))+)\/>|([^\s,]+)))?(?=\s|,|$)/g,i={};for(let s of t.matchAll(r)){let[,t,r,n,o,a,l,h]=s,c=t??r??n,u=(o??a??l??h??"true").trim();try{u=JSON.parse(u)}catch{}let d=n?.includes("."),f=d?c.split(".").reduceRight((e,t)=>({[t]:e}),u):{[c]:u};e.merge(f,i)}return i},split:e=>e.split(/,(?![^\[]*\])(?![^(]*\))(?![^<]*\/>)(?=(?:[^"']|"[^"]*"|'[^']*')*$)/),merge(t,r){"string"==typeof t&&(t=e.parse(t));for(let[i,s]of Object.entries(t)){if(["__proto__","constructor","prototype"].includes(i))continue;let t=s?.constructor===Object,n=r[i]?.constructor===Object;t&&n?e.merge(s,r[i]):r[i]=s}return r}};class t{#e=null;#t=[];admit(e,t,r){if(!this.#e)return this.#e={strategy:e,abort:r},"run";if("replace"===e||"abort"!==e&&"abort"===this.#e.strategy)return this.#t=[],this.#e.abort?.(),this.#e={strategy:e,abort:r},"run";if("queue all"===e)this.#t.push(t);else if("queue last"===e)this.#t=[t];else{if("abort"===e||"drop"===e||0!==this.#t.length)return"dropped";this.#t.push(t)}return"queued"}continue(){this.#e=null,this.#t.shift()?.()}abort(){this.#e?.abort?.()}}return new class{#r=e;#i=new Map;#s="";#n=new Set;_loc=window.location;#o;#a=Function;#l=Object.getPrototypeOf(async function(){}).constructor;#h={createHTML:e=>e,createScript:e=>e};#c;#u="a,form";#d=["get","post","put","patch","delete","query"];#f;#m;#p;#g;#x;constructor(){this.#b(),this.#y(),this.#c=this.#S("[hx-action],[hx-get],[hx-post],[hx-put],[hx-patch],[hx-delete],[hx-query]"),this.#f=(new XPathEvaluator).createExpression(`.//*[@*[${this.#v("hx-on").map(e=>`starts-with(name(), "${e}")`).join(" or ")}]]`),this.#o={HCON:e,attributeValue:this.#E.bind(this),parseTriggerSpecs:this.#w.bind(this),determineMethodAndAction:this.#A.bind(this),createRequestContext:this.#T.bind(this),collectFormData:this.#C.bind(this),getAttributeObject:this.#q.bind(this),insertContent:this.#N.bind(this),morph:this.#I.bind(this),isSoftMatch:this.#M.bind(this),initSecurity:(e,t,r)=>{e&&(this.#h=e),t&&(this.#a=t),r&&(this.#l=r)},onTrigger:this.#O.bind(this),htmxProp:this.#H.bind(this),triggerHtmxEvent:this.#L.bind(this),executeJavaScript:this.#k.bind(this)};let t=()=>this.initialize();"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):setTimeout(t)}#b(){this.version="4.0.0",this.config={logAll:!1,prefix:"data-hx-",transitions:!1,history:!0,mode:"same-origin",defaultSwap:"innerHTML",defaultFocusScroll:!1,indicatorClass:"htmx-indicator",requestClass:"htmx-request",includeIndicatorCSS:!0,defaultTimeout:6e4,extensions:"",morphIgnore:["data-htmx-powered"],morphSkip:"[hx-morph-skip]",morphSkipChildren:"[hx-morph-skip-children]",morphScanLimit:10,noSwap:[204,304],implicitInheritance:!1,defaultSettleDelay:1,allowEmptySwapAfterOOB:!1};let t=document.querySelector('meta[name="htmx-config"]');t&&e.merge(t.content,this.config),this.#s=this.config.extensions}#y(){if(!1!==this.config.includeIndicatorCSS){let e=this.config.indicatorClass,t=this.config.requestClass,r=new CSSStyleSheet;r.replaceSync(`.${e}{opacity:0;visibility: hidden} .${t} .${e}, .${t}.${e}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`),document.adoptedStyleSheets=[...document.adoptedStyleSheets,r]}}registerExtension(e,t){return!(this.#s&&!this.#s.split(/,\s*/).includes(e))&&(!this.#n.has(e)&&(this.#n.add(e),t.init&&t.init(this.#o),void Object.entries(t).forEach(([e,t])=>{this.#i.get(e)?.push(t)||this.#i.set(e,[t])})))}#V(e){let t=this.config.prefix;return!e.closest||null!=e.closest("[hx-ignore]")||t&&null!=e.closest(`[${t}ignore]`)}#P(e,t){let r=this.config.prefix;return e.getAttribute(t)??(r?e.getAttribute(t.replace("hx-",r)):null)}#R(e,t){let r=this.config.prefix&&t.replace("hx-",this.config.prefix);return e.hasAttribute(t)?t:r&&e.hasAttribute(r)?r:null}#S(e){return this.#v(e).join(",")}#v(e){let t=[e];return this.config.prefix&&t.push(e.replaceAll("hx-",this.config.prefix)),t}#j(e,t){let r=[...e.querySelectorAll?.(t)??[]];return e.matches?.(t)&&r.unshift(e),r}#B(e){return"before"===e?"beforebegin":"after"===e?"afterend":"prepend"===e?"afterbegin":"append"===e?"beforeend":e}#F(e,t){let r=[];return this.#E(e,t,void 0,(e,t)=>{e?.split(/\s*[,:]\s*/).includes("this")&&r.push(t)}),r}#E(e,t,r,i){t=this.#z(t);let s=this.#z(":inherited"),n=this.#z(":append"),o=this.#P(e,t)??this.#P(e,t+s);if(null!=o)return i?i(o,e):o;let a=CSS.escape(this.config.implicitInheritance?t:t+s),l=CSS.escape(t+s+n),h=this.#S(`[${a}],[${l}]`),c=this.#R(e,t+n)??this.#R(e,t+s+n);if(c){let r=e.getAttribute(c),s=e.parentNode?.closest?.(h);if(i&&i(r,e),s){let e=this.#E(s,t,void 0,i);return e?(e+","+r).replace(/[{}]/g,""):r}return r}let u=e.parentNode?.closest?.(h);return u?(o=this.#E(u,t,void 0,i),!i&&o&&this.config.implicitInheritance&&this.#_(e,"htmx:after:implicitInheritance",{elt:e,name:t,parent:u}),o):r}#w(t){return e.split(t).flatMap(t=>{let[,r,i]=t.maLine truncated
+6 -1
View File
@@ -22,6 +22,7 @@
<th>ID</th>
<th>Username</th>
<th>Admin User</th>
<th>RO</th>
<th>Last Connection</th>
<th>Actions</th>
</tr>
@@ -32,6 +33,7 @@
<td>{{user.id}}</td>
<td>{{user.username}}</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>
<button
@@ -80,6 +82,10 @@
<label class="form-label">Is Admin?</label>
{{ form.is_admin(class="form-control") }}
</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>
</form>
</div>
@@ -106,5 +112,4 @@
<div class="modal-content"></div>
</div>
</div>
{% include 'panel/_footer.html' %}
{% endblock %}
+8
View File
@@ -11,7 +11,15 @@
<!-- 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') }}"/>
{% if current_user.ui_theme == 'zephyr'%}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/zephyr.css') }}"/>
{% elif current_user.ui_theme == 'lumen' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/lumen.css') }}"/>
{% elif current_user.ui_theme == 'simplex' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/simplex.css') }}"/>
{% else %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css') }}"/>
{% endif %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-icons.min.css') }}"/>
</head>
<body class=" d-flex flex-column">
+19
View File
@@ -11,11 +11,30 @@
<!-- 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') }}"/>
{% if current_user.ui_theme == 'zephyr' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/zephyr.css') }}"/>
{% elif current_user.ui_theme == 'spacelab' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/spacelab.css') }}"/>
{% elif current_user.ui_theme == 'lumen' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/lumen.css') }}"/>
{% elif current_user.ui_theme == 'simplex' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/simplex.css') }}"/>
{% elif current_user.ui_theme == 'cerulean' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/cerulean.css') }}"/>
{% elif current_user.ui_theme == 'brite' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/brite.css') }}"/>
{% elif current_user.ui_theme == 'materia' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/materia.css') }}"/>
{% elif current_user.ui_theme == 'litera' %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/Bootstrap/litera.css') }}"/>
{% else %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css') }}"/>
{% endif %}
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-icons.min.css') }}"/>
</head>
<body>
{% block content %}{% endblock %}
{% include 'panel/_footer.html' %}
<!-- JAVASCRIPT -->
<script src="{{ url_for('static',filename='js/htmx.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap.bundle.min.js') }}"></script>
+1 -1
View File
@@ -1 +1 @@
<footer class="test-center" style="text-align: center;">Flask IPAM - 2025 - {{app_version}}</footer>
<footer class="footer mt-auto py-3" style="text-align: center;">Flask IPAM - {{app_version}}</footer>
+55 -3
View File
@@ -1,5 +1,5 @@
{% 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.csrf_token }}
<div class="modal-content">
@@ -8,8 +8,34 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<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_notes' %}
<div class="col-12">
{{ entry.label(class="form-label") }}<br>
{{ entry(class="form-control",placeholder='Notes, EX : Patch Panel/Switch...') }}
</div>
{% elif 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>
{{ modal.body|safe }}<hr>
{% for entry in form %}
{% if entry.id != 'csrf_token' %}
<div class="mb-3">
@@ -19,6 +45,7 @@
{% endif %}
{% endfor %}
</p>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
@@ -27,11 +54,36 @@
</div>
</form>
</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 %}
<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>
<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">
+27 -9
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>
</li>
<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 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">
<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>
<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="{{ url_for('inventory.list_items',item_type=2) }}"><i class="bi bi-server"></i> Servers</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="{{ url_for('inventory.list_items',item_type=4) }}"><i class="bi bi-ethernet"></i> Networks Devices</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>
</li>
{% if current_user.is_admin %}
@@ -28,8 +30,24 @@
</li>
{% endif %}
</ul>
<div class="d-flex">
<a class="btn btn-outline-warning" type="submit" href="{{ url_for('auth.logout') }}">logout</a>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<div class="btn-group dropstart">
<button type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
UI
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='default') }}">default</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='spacelab') }}">Spacelab</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='zephyr') }}">Zephyr</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='simplex') }}">Simplex</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='lumen') }}">Lumen</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='cerulean') }}">Cerulean</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='brite') }}">Brite</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='materia') }}">Materia</a></li>
<li><a class="dropdown-item" href="{{ url_for('base.change_theme',name='litera') }}">Litera</a></li>
</ul>
</div>
<a class="btn btn-outline-warning btn-sm" type="submit" href="{{ url_for('auth.logout') }}">logout</a>
</div>
</div>
</div>
+23 -10
View File
@@ -1,22 +1,33 @@
{% for allocation in allocations %}
{% if session['hide_dhcp'] and allocation.is_dhcp %}
{% else %}
<tr>
<td><b>{{allocation.ipv4}}</b></td>
<td id="{{allocation.ipv4}}"><b>{{allocation.ipv4}}</b></td>
<td>{{allocation.hostname}}</td>
<td>{{allocation.hostname_full}}</td>
<td>{{allocation.description}}</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>
<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 %}
N/A
{% endif %}
</td>
<td>{{allocation.is_dhcp}}</td>
<td>{{allocation.is_gateway}}</td>
<td>{{allocation.is_decom}}</td>
<td>{% if allocation.is_dhcp %}<span class="badge text-bg-secondary" style="margin-right: 0.5rem;">DHCP</span>{% endif %}{% if allocation.is_gateway %}<span class="badge text-bg-primary" style="margin-right: 0.5rem;">GATEWAY</span>{% endif %}{% if allocation.is_decom %}<span class="badge text-bg-warning" style="margin-right: 0.5rem;">DECOM</span>{% endif %}</td>
{% if current_user.is_reado == False %}
<td>
{% if allocation.is_used %}
<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}}&item={{allocation.ipv4}}"
hx-trigger="click"
hx-target="#modal-edit"
data-bs-toggle="modal"
@@ -26,12 +37,12 @@
> Edit
</button>
{% 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}}&item={{allocation.ipv4}}" class="btn btn-sm btn-outline-primary" style="padding-top:1px;padding-bottom:1px">Undecom</a>
{% 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}}&item={{allocation.ipv4}}" class="btn btn-sm btn-outline-warning" style="padding-top:1px;padding-bottom:1px">Decom</a>
{% endif %}
<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}}&item={{allocation.ipv4}}"
hx-trigger="click"
hx-target="#modal-del"
data-bs-toggle="modal"
@@ -42,7 +53,7 @@
</button>
{% else %}
<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}}&item={{allocation.ipv4}}"
hx-trigger="click"
hx-target="#modal-edit"
data-bs-toggle="modal"
@@ -53,5 +64,7 @@
</button>
{% endif %}
</td>
{% endif %}
</tr>
{% endif %}
{% endfor %}
+20 -37
View File
@@ -6,29 +6,30 @@
<div class="row">
<!-- Search BAR -->
<div class="col-12">
<div class="mb-3">
<div class="input-group mb-3">
<input class="form-control" type="search"
name="search" placeholder="Begin Typing To Search Users..."
hx-post="{{ url_for('base.allocation_search',net_id=network.id) }}"
name="search" placeholder="Begin Typing To Search Allocation(s)..."
hx-post="{{ url_for('network.allocation_search',net_id=network.id) }}"
hx-trigger="input changed delay:500ms, keyup[key=='Enter']"
hx-target="#search-results"
hx-indicator=".htmx-indicator"
>
<a class="btn btn-outline-secondary" href="?view=change">change view</a>
<a class="btn btn-outline-secondary" href="?view=hide_dhcp">Hide DHCP</a>
</div>
</div>
<!-- IPV4 TABLE -->
<div class="col-12">
<table class="table table-striped table-hover table-sm">
<table class="table table-hover table-sm">
<thead>
<tr>
<th scope="col">IPv4</th>
<th scope="col">Hostname</th>
<th scope="col">Shortname</th>
<th scope="col">FQDN</th>
<th scope="col">Description</th>
<th scope="col">Device</th>
<th scope="col">DHCP</th>
<th scope="col">GATEWAY</th>
<th scope="col">DECOM</th>
<th scope="col">Actions</th>
<th scope="col">TAG(s)</th>
{% if current_user.is_reado == False %}<th scope="col">Actions</th>{% endif %}
</tr>
</thead>
<tbody id="search-results">
@@ -36,28 +37,6 @@
</tbody>
</table>
</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 class="d-flex justify-content-between">
<nav>
@@ -68,12 +47,6 @@
{% if allocations.has_next %}
<li class="page-item"><a class="page-link" href="?page={{next_page}}">Next</a></li>
{% 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>
</nav>
<div>
@@ -101,4 +74,14 @@
<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>
<script src="{{ url_for('static',filename='js/custom.js') }}"></script>
{% endblock %}
+9 -6
View File
@@ -5,7 +5,7 @@
<div class="row">
<!-- NETWORKS -->
<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-body">
<h1 class="card-title text-center">{{db_networks}}</h1>
@@ -14,7 +14,7 @@
</div>
<!-- TOTAL IP -->
<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-body">
<h1 class="card-title text-center">{{db_allocations}} / {{ available_alloc }}</h1>
@@ -51,10 +51,8 @@
</div>
<div class="col-2"></div>
</div>
<hr>
{% include 'panel/_footer.html' %}
</div>
<script src="{{ url_for('static',filename='js/chart.umd.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/chart.umd.js') }}"></script>
<script>
/* Allocations Chart */
const ctx = document.getElementById('allocchart');
@@ -75,12 +73,17 @@
});
/* Devices Chart */
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, {
type: 'doughnut',
data: {
labels: ['Computers', 'Servers', 'VM', 'Network Devices', 'Others'],
datasets: [{
data: [1, 1, 1, 1, 1],
data: [computers, servers, virtual_machines, networks_devices, others],
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 %}
+20 -5
View File
@@ -5,13 +5,14 @@
<div class="container" style="margin-top:5vh;">
<div class="row">
<!-- 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">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Subnet</th>
<th scope="col">Prefix</th>
<th scope="col">VLAN</th>
<th scope="col">Usage</th>
<th scope="col">Description</th>
<th scope="col">Actions</th>
@@ -23,12 +24,19 @@
<th scope="row">{{network.id}}</th>
<td>{{network.network_address}}</td>
<td>{{network.prefix}}</td>
<td>{{network.vlan_id}}</td>
<td>{{Allocation.query.filter_by(net_id=network.id,is_used=True).count()}} / {{network.hosts}} </td>
<td><b>{{network.description}}</b></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 %}
{% if network.is_blacklisted %}
<a href="{{ url_for('network.network_blacklist',net_id=network.id,action='unblacklist') }}" class="btn btn-sm btn-outline-warning" style="padding-top:1px;padding-bottom:1px">UnBlacklist</a>
{% else %}
<a href="{{ url_for('network.network_blacklist',net_id=network.id,action='blacklist') }}" class="btn btn-sm btn-outline-warning" style="padding-top:1px;padding-bottom:1px">Blacklist</a>
{% endif %}
<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-target="#modal-get"
data-bs-toggle="modal"
@@ -37,6 +45,7 @@
style="padding-top:1px;padding-bottom:1px"
> Delete
</button>
{% endif %}
</td>
</tr>
{% endfor %}
@@ -44,19 +53,24 @@
</table>
</div>
<!-- PREFIX FORM -->
{% if current_user.is_reado == False %}
<div class="col-3">
<div class="card p-4 shadow rounded-3">
<h2 class="text-center mb-4">Add Network</h2>
<form method="POST">
{{ form.csrf_token }}
<div class="mb-3">
<label for="subnet" class="form-label">Subnet</label>
<label for="subnet" class="form-label">Subnet*</label>
{{ form.subnet(class="form-control", placeholder="192.168.0.0") }}
</div>
<div class="mb-3">
<label for="prefix" class="form-label">Prefix</label>
<label for="prefix" class="form-label">Prefix*</label>
{{ form.prefix(class="form-control", placeholder="24") }}
</div>
<div class="mb-3">
<label for="vlan_id" class="form-label">VLAN</label>
{{ form.vlan_id(class="form-control", placeholder="VLAN_ID (OPTIONAL)", value="0") }}
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
{{ form.description(class="form-control", placeholder="local area network...") }}
@@ -65,6 +79,7 @@
</form>
</div>
</div>
{% endif %}
</div>
</div>
<!-- MODAL -->
+17
View File
@@ -0,0 +1,17 @@
# 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>
Before starting the app ensure to install pip packages in requirements.txt Referrenced [Section](https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-22-04#step-4-configuring-gunicorn)
- `pip install Flask-Login Flask-SQLAlchemy Flask-WTF mysql-connector-python SQLAlchemy cryptography`
- Update the configuration file under _app/\_config.py_.
- `app_ip = '127.0.0.1'` to `app_ip = '0.0.0.0'` for easy web access.
- Update **SECRET_KEY='CHANGE_ME'** variable, `openssl rand -hex 32` can be used to create a random key.
- You don't need to create or continue with the `wsgi.py` section. 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()'`
- For any troubleshooting, you can add `--preload` at the end of the command for verbose logging.
- In Service File, replace "ExecStart" line with :<br>
`ExecStart=/YOUR_ENV_LOCATION/bin/gunicorn --workers 1 --bind 0.0.0.0:5000 -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`)_
+2
View File
@@ -1,3 +1,5 @@
wheel
gunicorn
Flask
Flask-Login
Flask-SQLAlchemy
Regular → Executable
+6 -3
View File
@@ -10,9 +10,12 @@ 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)
username = db.Column(db.String(16),unique=True)
password = db.Column(db.String(32))
is_admin = db.Column(db.Boolean(),default=False)
is_reado = db.Column(db.Boolean(),default=False)
ui_theme = db.Column(db.String(16))
ui_lmode = db.Column(db.Boolean(),default=False)
dat_last = db.Column(db.DateTime())
with app.app_context():
@@ -21,7 +24,7 @@ with app.app_context():
@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.add(User(username=request.form["username"],password=generate_password_hash(request.form["password"]),is_admin=True,is_reado=False))
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>"
+3 -4
View File
@@ -1,14 +1,13 @@
# Sources
---
### Images
logo : https://www.flaticon.com
### CSS
Bootstrap : https://getbootstrap.com
themes : https://bootswatch.com
Bootstrap : https://getbootstrap.com<br>
themes : https://bootswatch.com<br>
icons : https://icons.getbootstrap.com
### JS
HTMX : https://htmx.org
HTMX : https://htmx.org<br>
CHARTJS : https://www.chartjs.org