Took everything back to local now we have user, e mail client, we can register and update iot devices
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;
|
|
|
|
|
|
|
|
@ -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)
|
||||
|
@ -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
After Width: | Height: | Size: 550 KiB |
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…
Reference in new issue