Took everything back to local now we have user, e mail client, we can register and update iot devices

main
Kerem Yollu 11 months ago
parent 86cdadbbdf
commit 554755090b

@ -0,0 +1 @@
,key,devbian,12.07.2024 15:46,file:///home/key/.config/libreoffice/4;

@ -0,0 +1 @@
,key,devbian,08.08.2024 13:55,file:///home/key/.config/libreoffice/4;

@ -0,0 +1 @@
,key,devbian,08.08.2024 13:34,file:///home/key/.config/libreoffice/4;

@ -0,0 +1,2 @@
name,device_id,imsi,iccid,ip,port,user_id,status_id,type_id,area_id
miniUni,24070580,901405710203483,89882280000107407542,10.128.24.42,50000,1,1,1,1
1 name device_id imsi iccid ip port user_id status_id type_id area_id
2 miniUni 24070580 901405710203483 89882280000107407542 10.128.24.42 50000 1 1 1 1

@ -0,0 +1,5 @@
name,description
Roof,Placed on a roof
WashingMachine,Placed behind a washing machine
BoilerRoom,Placed on a the boiler room
Bathroom,Placed on a the bathroom
1 name description
2 Roof Placed on a roof
3 WashingMachine Placed behind a washing machine
4 BoilerRoom Placed on a the boiler room
5 Bathroom Placed on a the bathroom

@ -0,0 +1,5 @@
name,description
Active,Active an running
Ready,Registered and ready but not registered to the network
Offline,Went offline due to a reason like no more battery or
Deactivated,"Registration is overdue, and thus Deactivated by the operator"
1 name description
2 Active Active an running
3 Ready Registered and ready but not registered to the network
4 Offline Went offline due to a reason like no more battery or
5 Deactivated Registration is overdue, and thus Deactivated by the operator

@ -0,0 +1,2 @@
name,description
WaterDetector,Sensor for detection water levels.
1 name description
2 WaterDetector Sensor for detection water levels.

@ -0,0 +1,5 @@
name,description
Open,Open and need action
Pending,Pending We are waiting on someone
Done,Done we have solved the issue
Closed,Closed as no action is required anymore
1 name description
2 Open Open and need action
3 Pending Pending We are waiting on someone
4 Done Done we have solved the issue
5 Closed Closed as no action is required anymore

@ -0,0 +1,5 @@
title,content,user_id,company_id
Merhaba1,nasislin,1,1
selam,naber,1,4
semainaleyküm,aleykumselam,1,5
eyavllah,nemdek,1,6
1 title content user_id company_id
2 Merhaba1 nasislin 1 1
3 selam naber 1 4
4 semainaleyküm aleykumselam 1 5
5 eyavllah nemdek 1 6

@ -0,0 +1,6 @@
name,description
Admin,Have all rights
User,have all rights exept admin right
Company,have acces to only their project without
Extern,Have acces to given areas and without having the right to modify
Visitor,Can only see what we give him permisson to
1 name description
2 Admin Have all rights
3 User have all rights exept admin right
4 Company have acces to only their project without
5 Extern Have acces to given areas and without having the right to modify
6 Visitor Can only see what we give him permisson to

@ -1,10 +0,0 @@
[
{
"customerId": "CHE-341216444",
"rcvTime": 1723103768,
"srcImsi": "901405710203483",
"srcIP": "10.128.24.42",
"srcPort": "50000",
"payload": "JAcFgIANH4E="
}
]

@ -0,0 +1,63 @@
from flask import Flask
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_mail import Mail
from flask_migrate import Migrate
from iot.config import AppConfig
# (DATABASE) Definition
db = SQLAlchemy()
# (PASSWORD) Hashing Module to save paswords safely
bcrypt = Bcrypt()
# (LOGIN) Login manage plugin configuration
login_manager = LoginManager()
login_manager.login_view = 'user.login' # User management (current_user)
login_manager.login_message_category = 'info' # Boostrap Info Message
# (EMAIL AGENT) Definition
mail = Mail() # So that we can send mails
# (SESSION) Starts the curren flask session
session = Session()
def create_app():
# (APP) Definition
app = Flask(__name__)
# (APP) Configure configuration
app.config.from_object(AppConfig)
# (DATABASE) Initialisation
db.init_app(app)
# (PASSWORD) Initialisation
bcrypt.init_app(app)
# (LOGIN) Initialisation
login_manager.init_app(app)
# (EMAIL AGENT) Initialisation
mail.init_app(app)
# Session for variable manipulation on server side
# (DATABASE) Linking the app and the Database
Migrate(app, db)
# (BLUEPRINTS) Importing the blueprints.
# Include are made here to awoid the Circular import issues
from iot.blueprints.main.routes import main
from iot.blueprints.user.routes import user
from iot.blueprints.sensor.routes import sensor
from iot.blueprints.errors.routes import errors
# (BLUEPRINTS) Registering the blueprints.
# Giving them theie ulr_prefixes that will define their links on the webBrowser.
app.register_blueprint(main, url_prefix='/')
app.register_blueprint(user, url_prefix='/user')
app.register_blueprint(sensor, url_prefix='/sensor')
app.register_blueprint(errors, url_prefix='/errors')
# (APP) Returning the initialised app
return app

@ -0,0 +1,33 @@
from iot.app import db
class table_printable:
def __init__(self, table, link_for_item, item_to_be_linked):
self.titles = table.__table__.columns.keys()
self.lines = table.query.all()
self.link_for_item = link_for_item
self.item_to_be_linked = item_to_be_linked
self.paginate = 0
# https://www.youtube.com/watch?v=PSWf2TjTGNY&list=PL-osiE80TeTs4UjLw5MM6OjgkjFeUxCYH&index=9
class table_printable_paginate:
def __init__(self, table, current_page, per_page, link_for_item, item_to_be_linked):
self.titles = table.__table__.columns.keys()
self.lines = table.query.paginate(page=current_page, per_page=per_page)
self.link_for_item = link_for_item
self.item_to_be_linked = item_to_be_linked
self.paginate = 1
def dbAdd(dbClass):
db.session.add(dbClass)
def dbCommit():
db.session.commit()
def dbAddAndCommit(dbClass):
db.session.add(dbClass)
db.session.commit()

@ -0,0 +1,8 @@
from flask import Blueprint, render_template
import iot.theme as theme
errors = Blueprint('errors', __name__, template_folder='templates')
@errors.app_errorhandler(403)
def error_403(error):
return render_template('errors/403.html', theme=theme), 403

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="content-section">
<h1>Your dont have permission to do that (403)</h1>
<p>PLease Check your account and try again</p>
</div>
{% endblock content %}

@ -0,0 +1,35 @@
from iot.app import db, login_manager
from datetime import datetime
class Notes(db.Model):
__tablename__ = 'notes'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(75), nullable=False)
content = db.Column(db.String(500), nullable=False)
upload_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
last_update_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
class Todos(db.Model):
__tablename__ = 'todos'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(350), nullable=False)
upload_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
reminder_date = db.Column(db.DateTime, nullable=True)
deadline_date = db.Column(db.DateTime, nullable=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
class Importances(db.Model):
__tablename__ = 'importances'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), nullable=False)
upload_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
reminder_date = db.Column(db.DateTime, nullable=True)
deadline_date = db.Column(db.DateTime, nullable=True)

@ -1,12 +1,28 @@
from flask import Flask, request, jsonify from flask import render_template, Blueprint, request
from flask_login import login_user, current_user, logout_user, login_required
from flask_wtf import FlaskForm
from wtforms import StringField
import iot.theme as theme
from iot.blueprints.user.models import Users, User_Roles
import iot.blueprints.database.utils as dbUtils
import json import json
import os import os
app = Flask(__name__) main = Blueprint('main', __name__, template_folder='templates')
DATA_FILE = 'data.json' DATA_FILE = 'data.json'
@app.route('/callback', methods=['POST']) class LoginForm(FlaskForm):
username = StringField('username')
password = StringField('password')
@main.route('/', methods=['GET', 'POST'])
def index():
return render_template('/main/index.html', info="", theme=theme)
@main.route('/callback', methods=['POST'])
def callback(): def callback():
data = request.json data = request.json
@ -29,7 +45,7 @@ def callback():
return 'Callback received', 200 return 'Callback received', 200
@app.route('/data', methods=['GET']) @main.route('/data', methods=['GET'])
def get_data(): def get_data():
if os.path.exists(DATA_FILE): if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r') as f: with open(DATA_FILE, 'r') as f:
@ -38,6 +54,3 @@ def get_data():
stored_data = [] stored_data = []
return jsonify(stored_data) return jsonify(stored_data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block title %}iot{% endblock %}
{% block content %}
<h1>Hompage of iot</h1>
{{ info }}
{% endblock %}

@ -0,0 +1,6 @@
from iot.blueprints.main.models import Industries
def queryIndustryNames():
choices = Industries.query.order_by(Industries.name.asc())
return choices

@ -0,0 +1,115 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, PasswordField, SubmitField, BooleanField, URLField, IntegerField, DateField, SelectField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from iot.blueprints.sensor.models import nbiotDevice
from datetime import date
class updateNbioDeviceUpdateForm(FlaskForm): # Defines the form class to be used for the user update
name = StringField('Device Name', validators=[DataRequired(), Length(min=3, max=50)])
device_id = StringField('Device Id', validators=[DataRequired(), Length(min=3, max=50)])
imsi = StringField('Imsi No', validators=[DataRequired(), Length(min=10, max=50)])
iccid = StringField('Iccid No', validators=[DataRequired(), Length(min=10, max=100)])
ip = StringField('Ip (ipv4)', validators=[DataRequired(), Length(min=7, max=15)])
port = StringField('Port', validators=[DataRequired(), Length(min=3, max=10)])
registration_date = DateField('Registration Date', format='%Y-%m-%d', default=date.today, validators=[DataRequired()])
activation_date = DateField('Activation Date', format='%Y-%m-%d', default=date.today, validators=[DataRequired()])
deactivation_date = DateField('Deactivation Date', format='%Y-%m-%d', default=date.today, validators=[DataRequired()])
user_id = SelectField('Owner', validators=[DataRequired()])
status_id = SelectField('Status', validators=[DataRequired()])
type_id = SelectField('Type', validators=[DataRequired()])
area_id = SelectField('Area', validators=[DataRequired()])
submit = SubmitField(f'Update')
def __init__(self, current_device_id, *args, **kwargs):
super().__init__(*args, **kwargs)
self.current_device = nbiotDevice.query.get(current_device_id)
def validate_name(self, input):
if input.data != self.current_device.name:
content = nbiotDevice.query.filter_by(name=input.data).first()
if content:
raise ValidationError('That device name is taken please choose another one')
def validate_device_id(self, input):
if input.data != self.current_device.device_id:
content = nbiotDevice.query.filter_by(device_id=input.data).first()
if content:
raise ValidationError('That device id is taken please choose another one')
def validate_imsi(self, input):
if input.data != self.current_device.imsi:
content = nbiotDevice.query.filter_by(imsi=input.data).first()
if content:
raise ValidationError('That IMSI id is taken please choose another one')
def validate_iccid(self, input):
if input.data != self.current_device.iccid:
content = nbiotDevice.query.filter_by(iccid=input.data).first()
if content:
raise ValidationError('That ICCID id is taken please choose another one')
def validate_ip(self, input):
if input.data != self.current_device.ip:
content = nbiotDevice.query.filter_by(ip=input.data).first()
if content:
raise ValidationError('That IP id is taken please choose another one')
class updateNbioDeviceAddForm(FlaskForm): # Defines the form class to be used for the user update
name = StringField('Device Name', validators=[DataRequired(), Length(min=3, max=50)])
device_id = StringField('Device Id', validators=[DataRequired(), Length(min=3, max=50)])
imsi = StringField('Imsi No', validators=[DataRequired(), Length(min=10, max=50)])
iccid = StringField('Iccid No', validators=[DataRequired(), Length(min=10, max=100)])
ip = StringField('Ip (ipv4)', validators=[Length(min=7, max=15)])
port = StringField('Port', validators=[Length(min=3, max=10)])
registration_date = DateField('Registration Date', format='%Y-%m-%d', default=date.today, validators=[DataRequired()])
activation_date = DateField('Activation Date', format='%Y-%m-%d', default=date.today, validators=[DataRequired()])
deactivation_date = DateField('Deactivation Date', format='%Y-%m-%d', default=date.today, validators=[DataRequired()])
user_id = SelectField('Owner', validators=[DataRequired()])
status_id = SelectField('Status', validators=[DataRequired()])
type_id = SelectField('Type', validators=[DataRequired()])
area_id = SelectField('Area', validators=[DataRequired()])
submit = SubmitField(f'Add')
def validate_name(self, input):
content = nbiotDevice.query.filter_by(name=input.data).first()
if content:
raise ValidationError('That device name is taken please choose another one')
def validate_device_id(self, input):
content = nbiotDevice.query.filter_by(device_id=input.data).first()
if content:
raise ValidationError('That device id is taken please choose another one')
def validate_imsi(self, input):
content = nbiotDevice.query.filter_by(imsi=input.data).first()
if content:
raise ValidationError('That IMSI id is taken please choose another one')
def validate_iccid(self, input):
content = nbiotDevice.query.filter_by(iccid=input.data).first()
if content:
raise ValidationError('That ICCID id is taken please choose another one')
def validate_ip(self, input):
content = nbiotDevice.query.filter_by(ip=input.data).first()
if content:
raise ValidationError('That IP id is taken please choose another one')
class updateRoleForm(FlaskForm):
id = StringField('ID', render_kw={'disabled':''})
role = StringField('Role', validators=[DataRequired(), Length(min=4, max=20)])
description = StringField('Description', validators=[DataRequired(), Length(min=4, max=200)])
submit = SubmitField()
class updateRoleForm(FlaskForm):
id = StringField('ID', render_kw={'disabled':''})
role = StringField('Role', validators=[DataRequired(), Length(min=4, max=20)])
description = StringField('Description', validators=[DataRequired(), Length(min=4, max=200)])
submit = SubmitField()

@ -0,0 +1,64 @@
from iot.app import db
from datetime import datetime
class nbiotDevice(db.Model):
__tablename__ = 'nbiotDevice'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
device_id = db.Column(db.String(30), nullable=False)
imsi = db.Column(db.String(30), nullable=False)
iccid = db.Column(db.String(50), nullable=False)
ip = db.Column(db.String(20), nullable=True)
port = db.Column(db.String(20), nullable=True)
registration_date = db.Column(db.DateTime, nullable=True)
activation_date = db.Column(db.DateTime, nullable=True)
deactivation_date = db.Column(db.DateTime, nullable=True)
created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
status_id = db.Column(db.Integer, db.ForeignKey('nbiotDeviceStatus.id'), nullable=False)
type_id = db.Column(db.Integer, db.ForeignKey('nbiotDeviceType.id'), nullable=False)
area_id = db.Column(db.Integer, db.ForeignKey('nbiotDeviceArea.id'), nullable=False)
#status = db.relationship('nbiotDeviceStatus', backref='devices')
#type = db.relationship('nbiotDeviceType', backref='devices')
#area = db.relationship('nbiotDeviceArea', backref='devices')
water_detector = db.relationship('waterDetector', backref='device', uselist=False)
class nbiotDeviceStatus(db.Model):
__tablename__ = 'nbiotDeviceStatus'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
description = db.Column(db.String(300), nullable=False)
class nbiotDeviceType(db.Model):
__tablename__ = 'nbiotDeviceType'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
description = db.Column(db.String(300), nullable=False)
class nbiotDeviceArea(db.Model):
__tablename__ = 'nbiotDeviceArea'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
description = db.Column(db.String(300), nullable=False)
class waterDetector(db.Model):
__tablename__ = 'waterDetector'
id = db.Column(db.Integer, primary_key=True)
pins = db.Column(db.Integer, nullable=True)
alarms = db.Column(db.Integer, nullable=True)
voltage = db.Column(db.Integer, nullable=True)
signal = db.Column(db.Integer, nullable=True)
sleepTime = db.Column(db.Integer, nullable=True)
scanTime = db.Column(db.Integer, nullable=True)
holdTime = db.Column(db.Integer, nullable=True)
timeStamp = db.Column(db.DateTime, nullable=False)
device_id = db.Column(db.Integer, db.ForeignKey('nbiotDevice.id'), nullable=False)

@ -0,0 +1,131 @@
from flask import render_template, Blueprint, request, flash, redirect, url_for
import iot.theme as theme
from iot.blueprints.sensor.models import nbiotDevice
import iot.blueprints.sensor.utils as sensorUtils
from iot.blueprints.sensor.forms import updateNbioDeviceUpdateForm, updateNbioDeviceAddForm
import iot.blueprints.database.utils as dbUtils
import iot.blueprints.user.utils as userUtils
from iot.app import db
import json
import os
sensor = Blueprint('sensor', __name__, template_folder='templates')
DATA_FILE = 'data.json'
sensor.route('/callback', methods=['POST'])
def callback():
data = request.json
if not data:
return jsonify({"error": "Invalid data"}), 400
# Read existing data
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r') as f:
stored_data = json.load(f)
else:
stored_data = []
# Append new data
stored_data.append(data)
# Write data back to file
with open(DATA_FILE, 'w') as f:
json.dump(stored_data, f, indent=4)
return 'Callback received', 200
@sensor.route('/data', methods=['GET'])
def get_data():
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r') as f:
stored_data = json.load(f)
else:
stored_data = []
return jsonify(stored_data)
@sensor.route('/list', methods=['GET','POST'])
def list():
page=request.args.get('page', 1, type=int)
table=dbUtils.table_printable_paginate(nbiotDevice, page, 20, 'edit/', 'id')
return(render_template('view.html', theme=theme, table=table, title="Devices List"))
@sensor.route("/edit/<int:deviceId>", methods=['GET', 'POST'])
def edit(deviceId):
if deviceId:
device = nbiotDevice.query.get_or_404(deviceId)
form = updateNbioDeviceUpdateForm(current_device_id=device.id)
form.user_id.choices = [(row.id, row.username) for row in userUtils.queryUserChoices(device.user_id)]
form.status_id.choices = [(row.id, row.name) for row in sensorUtils.queryStatusChoices(device.status_id)]
form.type_id.choices = [(row.id, row.name) for row in sensorUtils.queryTypeChoices(device.type_id)]
form.area_id.choices = [(row.id, row.name) for row in sensorUtils.queryAreaChoices(device.area_id)]
if form.validate_on_submit():
device.name=form.name.data
device.device_id=form.device_id.data
device.imsi=form.imsi.data
device.iccid=form.iccid.data
device.ip=form.ip.data
device.port=form.port.data
device.registration_date=form.registration_date.data
device.activation_date=form.activation_date.data
device.deactivation_date=form.deactivation_date.data
device.user_id=form.user_id.data
device.status_id=form.status_id.data
device.type_id=form.type_id.data
device.area_id=form.area_id.data
db.session.commit()
flash('Device has been successfully updated', 'success')
return redirect(url_for('sensor.edit', deviceId=deviceId))
elif request.method == 'GET':
form.name.data = device.name
form.device_id.data = device.device_id
form.imsi.data = device.imsi
form.iccid.data = device.iccid
form.ip.data = device.ip
form.port.data = device.port
form.registration_date.data = device.registration_date
form.activation_date.data = device.activation_date
form.deactivation_date.data = device.deactivation_date
return render_template('sensor/account.html',
theme=theme,
form=form)
else:
flash('You need to select a Device id', 'alarm')
return redirect(url_for('sensor.list'))
@sensor.route('/add', methods=['GET', 'POST'])
def add():
form = updateNbioDeviceAddForm()
form.user_id.choices = [(row.id, row.username) for row in userUtils.queryUserChoices(1)]
form.status_id.choices = [(row.id, row.name) for row in sensorUtils.queryStatusChoices(1)]
form.type_id.choices = [(row.id, row.name) for row in sensorUtils.queryTypeChoices(1)]
form.area_id.choices = [(row.id, row.name) for row in sensorUtils.queryAreaChoices(1)]
if form.validate_on_submit():
dev = nbiotDevice(
name=form.name.data,
device_id=form.device_id.data,
imsi=form.imsi.data,
iccid=form.iccid.data,
ip=form.ip.data,
port=form.port.data,
registration_date=form.registration_date.data,
activation_date=form.activation_date.data,
deactivation_date=form.deactivation_date.data,
user_id=form.user_id.data,
status_id=form.status_id.data,
type_id=form.type_id.data,
area_id=form.area_id.data)
dbUtils.dbAddAndCommit(dev)
flash('Device has been successfully added', 'success')
return render_template('sensor/account.html',
theme=theme,
form=form)

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid rounded-5 mt-5 mb-5" style="{{ theme.form.div_style }};">
<div class="row">
<div class="col-md-3 border-right">
<div class="d-flex flex-column align-items-center text-center p-3 py-5">
<h2 class="account-heading" style="color: {{ theme.orange }};">{{ current_user.username }}</h2>
<h5 class="account-heading" style="color: {{ theme.light_blue }};">{{ current_user.email_account }}</h5>
</div>
</div>
</div>
<div class="col-md-5 border-right">
<div class="p-3 py-5">
{% include 'form.html' %}
</div>
</div>
<div class="col-md-4 rounded">
<div class="p-3 py-5">
{{ info }}
</div>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block title %}iot{% endblock %}
{% block content %}
<h1>Hompage of iot</h1>
{{ info }}
{% endblock %}

@ -0,0 +1,22 @@
from iot.blueprints.sensor.models import nbiotDevice, nbiotDeviceStatus, nbiotDeviceType, nbiotDeviceArea
from sqlalchemy import case
def queryById(id):
return nbiotDevice.query.filter_by(id=id).first()
def queryStatusChoices(curretnId):
choices = nbiotDeviceStatus.query.order_by(case((nbiotDeviceStatus.id == curretnId, 0),else_=1),nbiotDeviceStatus.name.asc())
return choices
def queryTypeChoices(curretnId):
choices = nbiotDeviceType.query.order_by(case((nbiotDeviceType.id == curretnId, 0),else_=1),nbiotDeviceType.name.asc())
return choices
def queryAreaChoices(curretnId):
choices = nbiotDeviceArea.query.order_by(case((nbiotDeviceArea.id == curretnId, 0),else_=1),nbiotDeviceArea.name.asc())
return choices

@ -0,0 +1,81 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, PasswordField, SubmitField, BooleanField, URLField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flask_login import current_user
from iot.blueprints.user.models import Users
class registrationForm(FlaskForm): # Defines the form class to be used for the user registretion
# Decalarion of the fields for the form and it's propereties
username = StringField('User Name', validators=[DataRequired(), Length(min=4, max=20)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password_confirm = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sing Up')
# Queries to be made in order to validate the form : If username exists
def validate_username(self, username):
user = Users.query.filter_by(username=username.data).first() # Database Querry
if user:
raise ValidationError('That username is taken please choose another one')
# Queries to be made in order to validate the form : If Email exists
def validate_email(self, email):
email = Users.query.filter_by(email_account=email.data).first() # Database Querry
if email:
raise ValidationError('That email is taken do you have an acocunt ?')
class loginForm(FlaskForm): # Defines the form class to be used for the user login
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Log In')
class updateAccountForm(FlaskForm): # Defines the form class to be used for the user update
username = StringField('User Name', validators=[DataRequired(), Length(min=3, max=20)])
email_account = StringField('Email Account', validators=[DataRequired(), Email()])
email_comm = StringField('Email Communication', validators=[DataRequired(), Email()])
picture = FileField('Update Profile Picture', validators=[FileAllowed(['jpg', 'png'])])
submit = SubmitField('Update')
# Queries to be made in order to validate the form : If username exists
def validate_username(self, username):
if username.data != current_user.username:
user = Users.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken please choose another one')
# Queries to be made in order to validate the form : If Email exists
def validate_email(self, email):
if email.data != current_user.email_account:
email = Users.query.filter_by(email_account=email.data).first()
if email:
raise ValidationError('That email is taken do you have an acocunt ?')
class requestResetForm(FlaskForm): # Defines the form class to be used for the reset form
email = StringField('Email', validators=[DataRequired(), Email()])
submit = SubmitField('Request Password Reset')
# Queries to be made in order to validate the form : If Email exists
def validate_email(self, email):
email = Users.query.filter_by(email_account=email.data).first()
if email is None:
raise ValidationError('There is no Account with this email your must register first.')
class resetPasswordForm(FlaskForm): # Defines the form class to be used for password reset form
password = PasswordField('Password', validators=[DataRequired()])
password_confirm = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Reset Password')
class updateRoleForm(FlaskForm):
id = StringField('ID', render_kw={'disabled':''})
role = StringField('Role', validators=[DataRequired(), Length(min=4, max=20)])
description = StringField('Description', validators=[DataRequired(), Length(min=4, max=200)])
submit = SubmitField()

@ -0,0 +1,59 @@
from flask import current_app
from itsdangerous import URLSafeTimedSerializer as Serializer
from iot.app import db, login_manager
from flask_login import UserMixin
from datetime import datetime
from iot.blueprints.main.models import Notes
from iot.blueprints.sensor.models import nbiotDevice
# The Default User Loading proccess
@login_manager.user_loader
def load_user(user_id):
return Users.query.get(int(user_id))
class Users(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email_account = db.Column(db.String(120), unique=True, nullable=False)
email_comm = db.Column(db.String(120), unique=False, nullable=False)
role = db.Column(db.String(120), unique=False, nullable=False, default='user')
image_file = db.Column(db.String(20), nullable=False, default='def_avatar.png')
password = db.Column(db.String(60), nullable=False)
upload_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
last_update_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relations One To Many
notes = db.relationship('Notes', backref='author', lazy='dynamic')
devices = db.relationship('nbiotDevice', backref='owner', lazy='dynamic')
# Here is a one to one relationship: each user can only have one role, and there can be multiple users with the same role
# type(user.role) = <class 'iot.blueprints.user.models.User_Roles'>
# type(user.role_id) = <class 'int'> #
role_id = db.Column(db.Integer, db.ForeignKey('user_roles.id'), nullable=False, default=2)
role = db.relationship('User_Roles', backref=db.backref('users', lazy='dynamic'))
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'])
return s.dumps({'user_id': self.id})
@staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return 0
return Users.query.get(user_id)
class User_Roles(db.Model):
__tablename__ = 'user_roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True, nullable=False)
description = db.Column(db.String(200), unique=False, nullable=True)

@ -0,0 +1,153 @@
from flask import render_template, url_for, flash, redirect, request, Blueprint
from flask_login import login_user, current_user, logout_user, login_required
from iot.app import db, bcrypt
import iot.theme as theme
from iot.blueprints.user.models import Users, User_Roles
import iot.blueprints.database.utils as dbUtils
import iot.blueprints.user.utils as UserUtils
from iot.blueprints.user.forms import registrationForm, loginForm, updateAccountForm, resetPasswordForm, requestResetForm, updateRoleForm
from iot.blueprints.user.utils import save_picture, send_reset_email
# Declaring a blueprint
user = Blueprint('user', __name__, template_folder='templates')
@user.route("/register", methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = registrationForm()
if form.validate_on_submit():
hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
dbUtils.dbAddAndCommit(Users(username=form.username.data, email_account=form.email.data, email_comm=form.email.data, password=hashed_pw))
flash(f'{"Your account has been created you can now log in!"}', 'success')
return redirect(url_for('user.login'))
return render_template('user/register.html',
theme=theme,
form=form)
@user.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated: # Is the user alredy authenticated?
return redirect(url_for('main.index')) # Then redirect home
form = loginForm()
if form.validate_on_submit():
user = UserUtils.dbGetMailFirst(form.email.data)
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('main.index'))
else:
flash('Login unsuccessful. Please chek your Email and Password!', 'danger')
return render_template('user/login.html',
theme=theme,
form=form)
@user.route("/logout")
def logout():
logout_user()
return redirect(url_for('main.index'))
@user.route("/account", methods=['GET', 'POST'])
@login_required
def account():
form = updateAccountForm()
if form.validate_on_submit():
if form.picture.data:
picture_file = save_picture(form.picture.data)
current_user.image_file = picture_file
current_user.username = form.username.data
current_user.email_account = form.email_account.data
current_user.email_comm = form.email_comm.data
db.session.commit()
flash('Your account has been updated!', 'success')
return redirect(url_for('user.account'))
elif request.method == 'GET':
form.username.data = current_user.username
form.email_account.data = current_user.email_account
form.email_comm.data = current_user.email_comm
image_file = url_for('static', filename='pics/' + current_user.image_file)
return render_template('user/account.html',
theme=theme,
image_file=image_file,
form=form)
@user.route("/reset_password", methods=['GET', 'POST'])
def reset_request():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = requestResetForm()
if form.validate_on_submit():
user = UserUtils.dbGetMailFirst(form.email.data)
send_reset_email(user)
flash('An Email has been sent with instruction to reset your password', 'warning')
return render_template('user/reset_request.html',
theme=theme,
form=form)
@user.route("/reset_password/<token>", methods=['GET', 'POST'])
def reset_token(token):
if current_user.is_authenticated:
return redirect(url_for('main.index'))
user = Users.verify_reset_token(token)
if user is None:
flash('That is an invalid or expired token', 'warning')
return redirect(url_for('user.reset_request'))
form = resetPasswordForm()
if form.validate_on_submit():
hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user.password = hashed_pw
db.session.commit()
flash(f'{"Your password has benn updated"}', 'success')
return redirect(url_for('user.login'))
return render_template('user/reset_token.html',
theme=theme,
form=form)
@user.route("/roles", methods=['GET', 'POST'])
@login_required
def roles():
page=request.args.get('page', 1, type=int)
table=dbUtils.table_printable_paginate(User_Roles, page, 20, 'role/edit/', 'id')
return(render_template('view.html', theme=theme, table=table, title="User Roles"))
class bottons:
def __init__(self, name, link):
self.name = name
self.link = link
@user.route("/role/edit/<int:id>", methods=['GET', 'POST'])
@login_required
def updateRole(id):
if current_user.role.id == 1:
extraButton=[]
extraButton.append(bottons('Back To Roles', url_for('user.roles')))
role = UserUtils.queryRoleById(id)
form = updateRoleForm()
if form.validate_on_submit():
role.title = form.role.data
role.description = form.description.data
dbUtils.dbCommit()
flash('The role has been updated', 'success')
return redirect(url_for('user.updateRole', id=role.id))
elif request.method == 'GET':
form.id.data = role.id
form.role.data = role.name
form.description.data = role.description
return(render_template('edit.html', theme=theme, form=form, title=current_user.role.name, extraButtons=extraButton))
else:
flash('You have no permission to do that', 'danger')
return redirect(url_for('main.index'))

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block content %}
<div class="container-fluid rounded-5 mt-5 mb-5" style="{{ theme.form.div_style }};">
<div class="row">
<div class="col-md-3 border-right">
<div class="d-flex flex-column align-items-center text-center p-3 py-5">
<img class="rounded-circle mt-4 border-info" width="150px" src="{{ image_file }}">
<h2 class="account-heading" style="color: {{ theme.orange }};">{{ current_user.username }}</h2>
<h5 class="account-heading" style="color: {{ theme.light_blue }};">{{ current_user.email_account }}</h5>
<div class="p-3 py-5">
<h4 class="account-heading" style="color: {{ theme.yellow }};">Permission</h4>
<h5 class="account-heading" style="color: {{ theme.yellow }};">{{ current_user.role.name }}</h5>
</div>
</div>
</div>
<div class="col-md-5 border-right">
<div class="p-3 py-5">
{% include 'form.html' %}
</div>
</div>
<div class="col-md-4 rounded">
<div class="p-3 py-5">
{% include 'form.html' %}
</div>
</div>
</div>
</div>
{% endblock content %}

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
{% include 'form.html' %}
<small>
<a href="{{ url_for('user.reset_request') }}">Forgot Password?</a>
</small>
</div>
{% endblock content %}

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block content %}
{% include 'form.html' %}
<div class="border-top pt-3">
<small class="text-muted">
Already Have An Account? <a class="ml-2" href="{{ url_for( 'user.login' ) }}">Login</a>
</small>
</div>
{% endblock content %}

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block title %}Reset Request{% endblock %}
{% block content %}
{% include 'form.html' %}
{% endblock content %}

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block title %}Reset Token{% endblock %}
{% block content %}
{% include 'form.html' %}
{% endblock content %}

@ -0,0 +1,43 @@
import os
import secrets
from PIL import Image
from flask import url_for, current_app
from flask_mail import Message
from iot.app import mail
from iot.blueprints.user.models import Users, User_Roles
from sqlalchemy import case
def dbGetMailFirst(mail):
return Users.query.filter_by(email_account=mail).first()
def save_picture(form_picture):
random_hex = secrets.token_hex(8)
_, f_ext = os.path.splitext(form_picture.filename)
picture_fn = random_hex + f_ext
picture_path = os.path.join(current_app.root_path, 'static/pics', picture_fn)
output_size = (125, 125)
i = Image.open(form_picture)
i.thumbnail(output_size)
i.save(picture_path)
return picture_fn
def send_reset_email(user):
token = user.get_reset_token()
msg = Message('Password Reset Request',
sender='noreply@demo.com',
recipients=[user.email_account])
msg.body = f'''To reset your password, visit the following link:
{url_for('user.reset_token', token=token, _external=True)}
If you didn't make this request, then simply ingnore this email and no chancges will be made.
'''
mail.send(msg)
def queryRoleById(id):
return User_Roles.query.get_or_404(id)
def queryUserChoices(curretnId):
choices = Users.query.order_by(case((Users.id == curretnId, 0),else_=1),Users.username.asc())
return choices

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,132 @@
{
"README" : [
"This JSON document represents the main configuration options.",
"Please, be mindful, read each option's description before setting its value."
],
"source_description" : [
"Connection parameters to your MySQL database",
"Please ensure, that you have defined all parameters properly.",
"Ensure, that details like 'charset' are included (if necessary).",
"Note, any parameter, acceptable by the node mysql module can be placed here.",
"Check the 'source.host' parameter, that can be either '127.0.0.1' or 'localhost', depends on your setup."
],
"source" : {
"host" : "127.0.0.1",
"port" : 3306,
"database" : "world",
"charset" : "utf8mb4",
"supportBigNumbers": true,
"user" : "root",
"password" : "root"
},
"target_description" : [
"Connection parameters to your PostgreSQL database",
"Please ensure, that you have defined all parameters properly.",
"Ensure, that details like 'charset' are included (if necessary).",
"Note, any parameter, acceptable by the node pg module can be placed here.",
"Check the 'target.host' parameter, that can be either '127.0.0.1' or 'localhost', depends on your setup."
],
"target" : {
"host" : "127.0.0.1",
"port" : 5432,
"database" : "world",
"charset" : "UTF8",
"user" : "postgres",
"password" : "postgres"
},
"max_each_db_connection_pool_size_description" : [
"Maximal amount of simultaneous connections to your MySQL and PostgreSQL servers each.",
"Example: 20 means, that Nmig will create a pool of 20 connections to the source MySQL server",
"and 20 connections to the target PostgreSQL server.",
"40 connections to both servers."
],
"max_each_db_connection_pool_size" : 20,
"number_of_simultaneously_running_loader_processes_description": [
"Number of loader processes that will run simultaneously.",
"Acceptable values:",
"1. 'DEFAULT' - when set to 'DEFAULT', Nmig will run 2 loader processes.",
"2. Any positive integer.",
"Note:",
"1.",
"The 'loader process' is a term referred to a data-transfer (from MySQL to PostgreSQL) procedure.",
"Note that each procedure uses two Nodejs processes: 'data-reader' to stream data out of MySQL",
"and apply some transformations, and 'data-writer' to stream data to PostgreSQL.",
"This way Nmig implements truly parallel streaming pipeline.",
"Such an implementation guaranties significant performance boost in comparison with",
"a regular, non-parallel streaming pipeline.",
"2.",
"Usually, migration gets accomplished faster with only 2 loader processes,",
"even if more CPU cores are available.",
"Yet, it is worth an effort to 'play' with this parameter prior running migration in production,",
"to check how many processes work best in your case.",
"When trying Nmig on your test-database, try both decreasing and increasing this number.",
"3.",
"'number_of_simultaneously_running_loader_processes' will never be greater",
"than a number of logical CPU cores of the machine running Nmig.",
"If greater number chosen - Nmig will run one loader process per each available CPU core."
],
"number_of_simultaneously_running_loader_processes": "DEFAULT",
"loader_max_old_space_size_description" : [
"V8 memory limit of the 'loader process'.",
"Note, all the 'loader process' params are equally applicable",
"to both 'data-reader' and 'data-writer' processes.",
"Possible values are:",
"1. Any positive integer, representing memory limit (in MB).",
"2. 'DEFAULT', representing V8 default memory limit of your current hardware."
],
"loader_max_old_space_size" : "DEFAULT",
"streams_high_water_mark_description": [
"Buffer level when stream.write() starts returning false.",
"This number is a number of JavaScript objects."
],
"streams_high_water_mark": 16384,
"encoding_description" : [
"JavaScript encoding type.",
"If not supplied, then utf8 will be used as a default."
],
"encoding" : "utf8",
"schema_description" : [
"A name of the schema, that will contain all migrated tables.",
"If not supplied, then a new schema will be created automatically."
],
"schema" : "public",
"exclude_tables_description": [
"List (Array) of tables, that will not be migrated.",
"By default, nmig will migrate all tables."
],
"exclude_tables": [],
"include_tables_description": [
"List (Array) of tables, that will be migrated.",
"By default, nmig will migrate all tables."
],
"include_tables": [],
"migrate_only_data_description" : [
"In order to skip schema migration, and just migrate data into a preset tables",
" - set this parameter true."
],
"migrate_only_data" : false,
"delimiter_description" : [
"Specifies the character, that separates columns within each record.",
"This must be a single one-byte character.",
"The default is comma."
],
"delimiter" : ",",
"enable_extra_config_description" : [
"In order to enable the additional configuration options, placed at extra_config.json",
" - set this parameter true."
],
"enable_extra_config" : false
}

@ -0,0 +1,122 @@
[
{
"id": 1,
"name": "Africa",
"translations": {
"kr": "아프리카",
"pt-BR": "África",
"pt": "África",
"nl": "Afrika",
"hr": "Afrika",
"fa": "آفریقا",
"de": "Afrika",
"es": "África",
"fr": "Afrique",
"ja": "アフリカ",
"it": "Africa",
"cn": "非洲",
"tr": "Afrika"
},
"wikiDataId": "Q15"
},
{
"id": 2,
"name": "Americas",
"translations": {
"kr": "아메리카",
"pt-BR": "América",
"pt": "América",
"nl": "Amerika",
"hr": "Amerika",
"fa": "قاره آمریکا",
"de": "Amerika",
"es": "América",
"fr": "Amérique",
"ja": "アメリカ州",
"it": "America",
"cn": "美洲",
"tr": "Amerika"
},
"wikiDataId": "Q828"
},
{
"id": 3,
"name": "Asia",
"translations": {
"kr": "아시아",
"pt-BR": "Ásia",
"pt": "Ásia",
"nl": "Azië",
"hr": "Ázsia",
"fa": "آسیا",
"de": "Asien",
"es": "Asia",
"fr": "Asie",
"ja": "アジア",
"it": "Asia",
"cn": "亚洲",
"tr": "Asya"
},
"wikiDataId": "Q48"
},
{
"id": 4,
"name": "Europe",
"translations": {
"kr": "유럽",
"pt-BR": "Europa",
"pt": "Europa",
"nl": "Europa",
"hr": "Európa",
"fa": "اروپا",
"de": "Europa",
"es": "Europa",
"fr": "Europe",
"ja": "ヨーロッパ",
"it": "Europa",
"cn": "欧洲",
"tr": "Avrupa"
},
"wikiDataId": "Q46"
},
{
"id": 5,
"name": "Oceania",
"translations": {
"kr": "오세아니아",
"pt-BR": "Oceania",
"pt": "Oceania",
"nl": "Oceanië en Australië",
"hr": "Óceánia és Ausztrália",
"fa": "اقیانوسیه",
"de": "Ozeanien und Australien",
"es": "Oceanía",
"fr": "Océanie",
"ja": "オセアニア",
"it": "Oceania",
"cn": "大洋洲",
"tr": "Okyanusya"
},
"wikiDataId": "Q55643"
},
{
"id": 6,
"name": "Polar",
"translations": {
"kr": "남극",
"pt-BR": "Antártida",
"pt": "Antártida",
"nl": "Antarctica",
"hr": "Antarktika",
"fa": "جنوبگان",
"de": "Antarktika",
"es": "Antártida",
"fr": "Antarctique",
"ja": "南極大陸",
"it": "Antartide",
"cn": "南極洲",
"tr": "Antarktika"
},
"wikiDataId": "Q51"
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,419 @@
[
{
"id": 19,
"name": "Australia and New Zealand",
"region_id": "5",
"translations": {
"korean": "오스트랄라시아",
"portuguese": "Australásia",
"dutch": "Australazië",
"croatian": "Australazija",
"persian": "استرالزی",
"german": "Australasien",
"spanish": "Australasia",
"french": "Australasie",
"japanese": "オーストララシア",
"italian": "Australasia",
"chinese": "澳大拉西亞"
},
"wikiDataId": "Q45256"
},
{
"id": 7,
"name": "Caribbean",
"region_id": "2",
"translations": {
"korean": "카리브",
"portuguese": "Caraíbas",
"dutch": "Caraïben",
"croatian": "Karibi",
"persian": "کارائیب",
"german": "Karibik",
"spanish": "Caribe",
"french": "Caraïbes",
"japanese": "カリブ海地域",
"italian": "Caraibi",
"chinese": "加勒比地区"
},
"wikiDataId": "Q664609"
},
{
"id": 9,
"name": "Central America",
"region_id": "2",
"translations": {
"korean": "중앙아메리카",
"portuguese": "América Central",
"dutch": "Centraal-Amerika",
"croatian": "Srednja Amerika",
"persian": "آمریکای مرکزی",
"german": "Zentralamerika",
"spanish": "América Central",
"french": "Amérique centrale",
"japanese": "中央アメリカ",
"italian": "America centrale",
"chinese": "中美洲"
},
"wikiDataId": "Q27611"
},
{
"id": 10,
"name": "Central Asia",
"region_id": "3",
"translations": {
"korean": "중앙아시아",
"portuguese": "Ásia Central",
"dutch": "Centraal-Azië",
"croatian": "Srednja Azija",
"persian": "آسیای میانه",
"german": "Zentralasien",
"spanish": "Asia Central",
"french": "Asie centrale",
"japanese": "中央アジア",
"italian": "Asia centrale",
"chinese": "中亚"
},
"wikiDataId": "Q27275"
},
{
"id": 4,
"name": "Eastern Africa",
"region_id": "1",
"translations": {
"korean": "동아프리카",
"portuguese": "África Oriental",
"dutch": "Oost-Afrika",
"croatian": "Istočna Afrika",
"persian": "شرق آفریقا",
"german": "Ostafrika",
"spanish": "África Oriental",
"french": "Afrique de l'Est",
"japanese": "東アフリカ",
"italian": "Africa orientale",
"chinese": "东部非洲"
},
"wikiDataId": "Q27407"
},
{
"id": 12,
"name": "Eastern Asia",
"region_id": "3",
"translations": {
"korean": "동아시아",
"portuguese": "Ásia Oriental",
"dutch": "Oost-Azië",
"croatian": "Istočna Azija",
"persian": "شرق آسیا",
"german": "Ostasien",
"spanish": "Asia Oriental",
"french": "Asie de l'Est",
"japanese": "東アジア",
"italian": "Asia orientale",
"chinese": "東亞"
},
"wikiDataId": "Q27231"
},
{
"id": 15,
"name": "Eastern Europe",
"region_id": "4",
"translations": {
"korean": "동유럽",
"portuguese": "Europa de Leste",
"dutch": "Oost-Europa",
"croatian": "Istočna Europa",
"persian": "شرق اروپا",
"german": "Osteuropa",
"spanish": "Europa Oriental",
"french": "Europe de l'Est",
"japanese": "東ヨーロッパ",
"italian": "Europa orientale",
"chinese": "东欧"
},
"wikiDataId": "Q27468"
},
{
"id": 20,
"name": "Melanesia",
"region_id": "5",
"translations": {
"korean": "멜라네시아",
"portuguese": "Melanésia",
"dutch": "Melanesië",
"croatian": "Melanezija",
"persian": "ملانزی",
"german": "Melanesien",
"spanish": "Melanesia",
"french": "Mélanésie",
"japanese": "メラネシア",
"italian": "Melanesia",
"chinese": "美拉尼西亚"
},
"wikiDataId": "Q37394"
},
{
"id": 21,
"name": "Micronesia",
"region_id": "5",
"translations": {
"korean": "미크로네시아",
"portuguese": "Micronésia",
"dutch": "Micronesië",
"croatian": "Mikronezija",
"persian": "میکرونزی",
"german": "Mikronesien",
"spanish": "Micronesia",
"french": "Micronésie",
"japanese": "ミクロネシア",
"italian": "Micronesia",
"chinese": "密克罗尼西亚群岛"
},
"wikiDataId": "Q3359409"
},
{
"id": 2,
"name": "Middle Africa",
"region_id": "1",
"translations": {
"korean": "중앙아프리카",
"portuguese": "África Central",
"dutch": "Centraal-Afrika",
"croatian": "Srednja Afrika",
"persian": "مرکز آفریقا",
"german": "Zentralafrika",
"spanish": "África Central",
"french": "Afrique centrale",
"japanese": "中部アフリカ",
"italian": "Africa centrale",
"chinese": "中部非洲"
},
"wikiDataId": "Q27433"
},
{
"id": 1,
"name": "Northern Africa",
"region_id": "1",
"translations": {
"korean": "북아프리카",
"portuguese": "Norte de África",
"dutch": "Noord-Afrika",
"croatian": "Sjeverna Afrika",
"persian": "شمال آفریقا",
"german": "Nordafrika",
"spanish": "Norte de África",
"french": "Afrique du Nord",
"japanese": "北アフリカ",
"italian": "Nordafrica",
"chinese": "北部非洲"
},
"wikiDataId": "Q27381"
},
{
"id": 6,
"name": "Northern America",
"region_id": "2",
"translations": {
"korean": "북미",
"portuguese": "América Setentrional",
"dutch": "Noord-Amerika",
"persian": "شمال آمریکا",
"german": "Nordamerika",
"spanish": "América Norteña",
"french": "Amérique septentrionale",
"japanese": "北部アメリカ",
"italian": "America settentrionale",
"chinese": "北美地區"
},
"wikiDataId": "Q2017699"
},
{
"id": 18,
"name": "Northern Europe",
"region_id": "4",
"translations": {
"korean": "북유럽",
"portuguese": "Europa Setentrional",
"dutch": "Noord-Europa",
"croatian": "Sjeverna Europa",
"persian": "شمال اروپا",
"german": "Nordeuropa",
"spanish": "Europa del Norte",
"french": "Europe du Nord",
"japanese": "北ヨーロッパ",
"italian": "Europa settentrionale",
"chinese": "北歐"
},
"wikiDataId": "Q27479"
},
{
"id": 22,
"name": "Polynesia",
"region_id": "5",
"translations": {
"korean": "폴리네시아",
"portuguese": "Polinésia",
"dutch": "Polynesië",
"croatian": "Polinezija",
"persian": "پلی‌نزی",
"german": "Polynesien",
"spanish": "Polinesia",
"french": "Polynésie",
"japanese": "ポリネシア",
"italian": "Polinesia",
"chinese": "玻里尼西亞"
},
"wikiDataId": "Q35942"
},
{
"id": 8,
"name": "South America",
"region_id": "2",
"translations": {
"korean": "남아메리카",
"portuguese": "América do Sul",
"dutch": "Zuid-Amerika",
"croatian": "Južna Amerika",
"persian": "آمریکای جنوبی",
"german": "Südamerika",
"spanish": "América del Sur",
"french": "Amérique du Sud",
"japanese": "南アメリカ",
"italian": "America meridionale",
"chinese": "南美洲"
},
"wikiDataId": "Q18"
},
{
"id": 13,
"name": "South-Eastern Asia",
"region_id": "3",
"translations": {
"korean": "동남아시아",
"portuguese": "Sudeste Asiático",
"dutch": "Zuidoost-Azië",
"croatian": "Jugoistočna Azija",
"persian": "جنوب شرق آسیا",
"german": "Südostasien",
"spanish": "Sudeste Asiático",
"french": "Asie du Sud-Est",
"japanese": "東南アジア",
"italian": "Sud-est asiatico",
"chinese": "东南亚"
},
"wikiDataId": "Q11708"
},
{
"id": 5,
"name": "Southern Africa",
"region_id": "1",
"translations": {
"korean": "남아프리카",
"portuguese": "África Austral",
"dutch": "Zuidelijk Afrika",
"croatian": "Južna Afrika",
"persian": "جنوب آفریقا",
"german": "Südafrika",
"spanish": "África austral",
"french": "Afrique australe",
"japanese": "南部アフリカ",
"italian": "Africa australe",
"chinese": "南部非洲"
},
"wikiDataId": "Q27394"
},
{
"id": 14,
"name": "Southern Asia",
"region_id": "3",
"translations": {
"korean": "남아시아",
"portuguese": "Ásia Meridional",
"dutch": "Zuid-Azië",
"croatian": "Južna Azija",
"persian": "جنوب آسیا",
"german": "Südasien",
"spanish": "Asia del Sur",
"french": "Asie du Sud",
"japanese": "南アジア",
"italian": "Asia meridionale",
"chinese": "南亚"
},
"wikiDataId": "Q771405"
},
{
"id": 16,
"name": "Southern Europe",
"region_id": "4",
"translations": {
"korean": "남유럽",
"portuguese": "Europa meridional",
"dutch": "Zuid-Europa",
"croatian": "Južna Europa",
"persian": "جنوب اروپا",
"german": "Südeuropa",
"spanish": "Europa del Sur",
"french": "Europe du Sud",
"japanese": "南ヨーロッパ",
"italian": "Europa meridionale",
"chinese": "南欧"
},
"wikiDataId": "Q27449"
},
{
"id": 3,
"name": "Western Africa",
"region_id": "1",
"translations": {
"korean": "서아프리카",
"portuguese": "África Ocidental",
"dutch": "West-Afrika",
"croatian": "Zapadna Afrika",
"persian": "غرب آفریقا",
"german": "Westafrika",
"spanish": "África Occidental",
"french": "Afrique de l'Ouest",
"japanese": "西アフリカ",
"italian": "Africa occidentale",
"chinese": "西非"
},
"wikiDataId": "Q4412"
},
{
"id": 11,
"name": "Western Asia",
"region_id": "3",
"translations": {
"korean": "서아시아",
"portuguese": "Sudoeste Asiático",
"dutch": "Zuidwest-Azië",
"croatian": "Jugozapadna Azija",
"persian": "غرب آسیا",
"german": "Vorderasien",
"spanish": "Asia Occidental",
"french": "Asie de l'Ouest",
"japanese": "西アジア",
"italian": "Asia occidentale",
"chinese": "西亚"
},
"wikiDataId": "Q27293"
},
{
"id": 17,
"name": "Western Europe",
"region_id": "4",
"translations": {
"korean": "서유럽",
"portuguese": "Europa Ocidental",
"dutch": "West-Europa",
"croatian": "Zapadna Europa",
"persian": "غرب اروپا",
"german": "Westeuropa",
"spanish": "Europa Occidental",
"french": "Europe de l'Ouest",
"japanese": "西ヨーロッパ",
"italian": "Europa occidentale",
"chinese": "西欧"
},
"wikiDataId": "Q27496"
}
]

@ -0,0 +1,52 @@
import csv
import argparse
def compareAndAdd(base_file, field_base, compare_file, field_compare, field_to_add, output_csv):
# Read the base CSV file into a list of dictionaries
with open(base_file, 'r', newline='', encoding='utf-8') as csv_file_base:
csv_base = list(csv.DictReader(csv_file_base))
# Read the compare CSV file into a list of dictionaries
with open(compare_file, 'r', newline='', encoding='utf-8') as csv_file_compare:
csv_compare = list(csv.DictReader(csv_file_compare))
# Create a mapping of field_compare to the field_to_add from the compare CSV
compare_mapping = {row[field_compare]: row[field_to_add] for row in csv_compare}
# Define the output file's fieldnames
fieldnames = list(csv_base[0].keys()) + [field_to_add]
# Open the output CSV file to write the combined data
with open(output_csv, 'w', newline='', encoding='utf-8') as new_file:
csv_writer = csv.DictWriter(new_file, fieldnames=fieldnames, delimiter=',')
csv_writer.writeheader()
# Iterate through the base CSV and add the field_to_add from the compare CSV if field_base matches field_compare
for line_base in csv_base:
key = line_base[field_base]
if key in compare_mapping:
line_base[field_to_add] = compare_mapping[key]
else:
line_base[field_to_add] = None # or some default value if no match is found
csv_writer.writerow(line_base)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Compare and add fields from one file to another.')
parser.add_argument('base_file', help='The path to the base file')
parser.add_argument('field_base', help='The field name in the base file to compare')
parser.add_argument('compare_file', help='The path to the compare file')
parser.add_argument('field_compare', help='The field name in the compare file to compare')
parser.add_argument('field_to_add', help='The field name in the compare file to add to the base file')
parser.add_argument('output_file', help='The path to the output file')
args = parser.parse_args()
compareAndAdd(
args.base_file,
args.field_base,
args.compare_file,
args.field_compare,
args.field_to_add,
args.output_file
)

@ -0,0 +1,86 @@
import pandas as pd
import argparse
def modify_column(base_excel, sheet_base, field_to_modify, text_before, text_after, output_excel, output_sheet):
# Read the base Excel sheet into a DataFrame
df_base = pd.read_excel(base_excel, sheet_name=sheet_base)
# Modify the specified column by adding text before the existing content
df_base[field_to_modify] = text_before + df_base[field_to_modify].astype(str) + text_after
# Write the modified DataFrame to the output Excel file
with pd.ExcelWriter(output_excel, engine='openpyxl') as writer:
df_base.to_excel(writer, sheet_name=output_sheet, index=False)
def compareAndAdd(base_file, sheet_base, field_base, compare_file, sheet_compare, field_compare, field_to_add, output_excel, output_sheet):
# Read the base Excel sheet into a DataFrame
df_base = pd.read_excel(base_file, sheet_name=sheet_base)
# Read the compare Excel sheet into a DataFrame
df_compare = pd.read_excel(compare_file, sheet_name=sheet_compare)
# Create a mapping of field_compare to field_to_add from the compare DataFrame
compare_mapping = df_compare.set_index(field_compare)[field_to_add].to_dict()
# Add the field_to_add to the base DataFrame
df_base[field_to_add] = df_base[field_base].map(compare_mapping)
# Write the combined DataFrame to the output Excel file
with pd.ExcelWriter(output_excel, engine='openpyxl') as writer:
df_base.to_excel(writer, sheet_name=output_sheet, index=False)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Compare and add fields or modify a column in an Excel file.')
subparsers = parser.add_subparsers(dest='command', help='Sub-command help')
parser_compare = subparsers.add_parser('compareAndAdd', help='Compare and add fields from one Excel file to another.')
parser_compare.add_argument('base_file', help='The path to the base file')
parser_compare.add_argument('sheet_base', help='The sheet name in the base Excel file', default='Sheet1')
parser_compare.add_argument('field_base', help='The field name in the base file to compare')
parser_compare.add_argument('compare_file', help='The path to the compare file')
parser_compare.add_argument('sheet_compare', help='The sheet name in the compare Excel file', default='Sheet1')
parser_compare.add_argument('field_compare', help='The field name in the compare file to compare')
parser_compare.add_argument('field_to_add', help='The field name in the compare file to add to the base file')
parser_compare.add_argument('output_file', help='The path to the output file')
parser_compare.add_argument('output_sheet', help='The sheet name for the output Excel file')
# Subparser for the modify_column function
parser_modify = subparsers.add_parser('modify', help='Modify a column in an Excel file by adding text before existing content.')
parser_modify.add_argument('base_excel', help='The path to the base Excel file')
parser_modify.add_argument('sheet_base', help='The sheet name in the base Excel file')
parser_modify.add_argument('field_to_modify', help='The field (column) to modify')
parser_modify.add_argument('text_before', help='The text to add before the existing content')
parser_modify.add_argument('text_after', help='The text to add after the existing content')
parser_modify.add_argument('output_excel', help='The path to the output Excel file')
parser_modify.add_argument('output_sheet', help='The sheet name for the output Excel file')
args = parser.parse_args()
if args.command == 'compareAndAdd':
compareAndAdd(
args.base_file,
args.sheet_base,
args.field_base,
args.compare_file,
args.sheet_compare,
args.field_compare,
args.field_to_add,
args.output_file,
args.output_sheet
)
elif args.command == 'modify':
modify_column(
args.base_excel,
args.sheet_base,
args.field_to_modify,
args.text_before,
args.text_after,
args.output_excel,
args.output_sheet
)

@ -0,0 +1,88 @@
import json
import math
def printList(data):
print(f"\t\t Lenght: {len(data)}")
def printDict(data):
print(f"\t\t Lenght: {len(data)}")
def getListIndexElements(data, index):
i=0
item=data[index]
print(f"===================================")
print(f"Type: {type(item)}")
print(f"Item No: {index}")
print(f"===================================")
for key in item.keys():
content = item[str(key)]
if type(content) is int:
print(f" -> {key} | Integer | {content}")
elif type(content) is str:
print(f" -> {key} | String | {len(content)}")
elif type(content) is list:
print(f" -> {key} | List | {len(content)}")
elif type(content) is dict:
print(f" -> {key} | Dictionary | {len(content)}")
print(f"===================================")
def getLongerString(data, key, ceilMutpilpe):
len_max = 0
longest_string=""
for item in data:
content = item[key]
if isinstance(content, str):
len_cur = len(content)
if len_cur > len_max:
len_max = len_cur
longest_string = content
return (ceilMutpilpe * math.ceil(len_max/ceilMutpilpe))
def getLongerDict(data, key, ceilMutpilpe):
len_max = 0
longest_string=""
for item in data:
content = str(item[key])
if isinstance(content, str):
len_cur = len(content)
if len_cur > len_max:
len_max = len_cur
longest_string = content
return (ceilMutpilpe * math.ceil(len_max/ceilMutpilpe))
def printSqlAlchemyOnlyString(data, index, primaryKey, dbName):
item = data[index]
print(f"Type: {type(item)}")
print(f"Item No: {index}")
print(f"=============================================")
for key in item.keys():
content = item[str(key)]
if type(content) is int:
if key == primaryKey:
print(f"{key} = {dbName}Column({dbName}Integer, primary_key=True)")
else:
print(f"{key} = {dbName}Column({dbName}Integer, nullable=True)")
elif type(content) is str:
content_lenght = getLongerString(data, key,5)
print(f"{key} = {dbName}Column({dbName}String({content_lenght}), unique=False, nullable=True)")
elif type(content) is list:
content_lenght = getLongerDict(data, key,5)
if content_lenght < 255:
print(f"{key} = {dbName}Column({dbName}String({content_lenght}), unique=False, nullable=True)")
else:
print(f"{key} = {dbName}Column({dbName}Text, unique=False, nullable=True)")
elif type(content) is dict:
content_lenght = getLongerDict(data, key,5)
if content_lenght < 255:
print(f"{key} = {dbName}Column({dbName}String({content_lenght}), unique=False, nullable=True)")
else:
print(f"{key} = {dbName}Column({dbName}Text, unique=False, nullable=True)")
print(f"=============================================")
with open("iot/blueprints/utils/countries/states.json") as json_data:
data = json.load(json_data)
#getListIndexElements(data,0)
printSqlAlchemyOnlyString(data,0,'id','db.')

@ -0,0 +1,37 @@
import json
mydict = {
"people": [
{
"name": "Bob",
"age": 28,
"weight": 80
},
{
"name": "Anna",
"age": 34,
"weight": 60
},
{
"name": "Charles",
"age": 45,
"weight": 90
},
{
"name": "Daniel",
"age": 21,
"weight": 120
}
]
}
json_string = json.dumps(mydict, indent=2)
def writeJson():
with open("iot/blueprints/utils/json_test.json", 'w') as f:
f.write(json_string)
def readJson():
with open("iot/blueprints/utils/json_test.json", 'r') as f:
json_object = json.loads(f.read())
print(json_object['people'][0]['name'])

@ -0,0 +1,17 @@
import re
def escape_single_quotes(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# Escape single quotes inside string literals
content = re.sub(r"''", r"'", content)
content = re.sub(r"(`[^`]*`)", r"'\1'", content)
content = re.sub(r"'`([^`]*)`'", r"'\1'", content)
content = re.sub(r"\'", r"''", content)
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
# Usage
escape_single_quotes("/home/key/git/countries-states-cities-database/sql/world.sql")

@ -0,0 +1,20 @@
import os
class AppConfig:
# (FLASK) Sectret key wich will be used to secure some requests an connections
SECRET_KEY = 'IOT_SECRET_KEY'
TEMPLATE_FOLDER = 'templates'
STATIC_FOLDER = 'static'
STATIC_URL_PATH = '/'
# (SQLALCHEMY) COnfiguration
SQLALCHEMY_DATABASE_URI = 'sqlite:///./test.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
# (MAIL AGENT) Configure mail Server to send EMails.
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_USERNAME = 'kerem.yollu@gmail.com'
MAIL_PASSWORD = 'eoilsjxjwmnfjbbj'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,488 @@
/*!
* Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors
* Copyright 2011-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root {
--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-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--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-bg: #fff;
--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-2xl: 2rem;
--bs-border-radius-pill: 50rem;
--bs-link-color: #0d6efd;
--bs-link-hover-color: #0a58ca;
--bs-code-color: #d63384;
--bs-highlight-bg: #fff3cd;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: 1px solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: var(--bs-link-color);
text-decoration: underline;
}
a:hover {
color: var(--bs-link-hover-color);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,485 @@
/*!
* Bootstrap Reboot v5.2.3 (https://getbootstrap.com/)
* Copyright 2011-2022 The Bootstrap Authors
* Copyright 2011-2022 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root {
--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-white-rgb: 255, 255, 255;
--bs-black-rgb: 0, 0, 0;
--bs-body-color-rgb: 33, 37, 41;
--bs-body-bg-rgb: 255, 255, 255;
--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-bg: #fff;
--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-2xl: 2rem;
--bs-border-radius-pill: 50rem;
--bs-link-color: #0d6efd;
--bs-link-hover-color: #0a58ca;
--bs-code-color: #d63384;
--bs-highlight-bg: #fff3cd;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
border: 0;
border-top: 1px solid;
opacity: 0.25;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.1875em;
background-color: var(--bs-highlight-bg);
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: var(--bs-link-color);
text-decoration: underline;
}
a:hover {
color: var(--bs-link-hover-color);
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: var(--bs-font-monospace);
font-size: 1em;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: var(--bs-code-color);
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.1875rem 0.375rem;
font-size: 0.875em;
color: var(--bs-body-bg);
background-color: var(--bs-body-color);
border-radius: 0.25rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
display: none !important;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
::file-selector-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save