mirror of https://github.com/D4-project/d4-core
commit
ff256984a3
|
@ -11,10 +11,10 @@ CYAN="\\033[1;36m"
|
|||
|
||||
. ./D4ENV/bin/activate
|
||||
|
||||
isredis=`screen -ls | egrep '[0-9]+.Redis_D4' | cut -d. -f1`
|
||||
isd4server=`screen -ls | egrep '[0-9]+.Server_D4' | cut -d. -f1`
|
||||
isworker=`screen -ls | egrep '[0-9]+.Workers_D4' | cut -d. -f1`
|
||||
isflask=`screen -ls | egrep '[0-9]+.Flask_D4' | cut -d. -f1`
|
||||
isredis=`screen -ls | egrep '[0-9]+.Redis_D4 ' | cut -d. -f1`
|
||||
isd4server=`screen -ls | egrep '[0-9]+.Server_D4 ' | cut -d. -f1`
|
||||
isworker=`screen -ls | egrep '[0-9]+.Workers_D4 ' | cut -d. -f1`
|
||||
isflask=`screen -ls | egrep '[0-9]+.Flask_D4 ' | cut -d. -f1`
|
||||
|
||||
function helptext {
|
||||
echo -e $YELLOW"
|
||||
|
@ -109,6 +109,18 @@ function checking_redis {
|
|||
return $flag_redis;
|
||||
}
|
||||
|
||||
function wait_until_redis_is_ready {
|
||||
redis_not_ready=true
|
||||
while $redis_not_ready; do
|
||||
if checking_redis; then
|
||||
redis_not_ready=false;
|
||||
else
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
echo -e $YELLOW"\t* Redis Launched"$DEFAULT
|
||||
}
|
||||
|
||||
function launch_redis {
|
||||
if [[ ! $isredis ]]; then
|
||||
launching_redis;
|
||||
|
@ -275,16 +287,19 @@ function launch_all {
|
|||
|
||||
while [ "$1" != "" ]; do
|
||||
case $1 in
|
||||
-l | --launchAuto ) launch_all;
|
||||
;;
|
||||
-k | --killAll ) helptext;
|
||||
killall;
|
||||
;;
|
||||
-h | --help ) helptext;
|
||||
exit
|
||||
;;
|
||||
* ) helptext
|
||||
exit 1
|
||||
-l | --launchAuto ) launch_all;
|
||||
;;
|
||||
-k | --killAll ) helptext;
|
||||
killall;
|
||||
;;
|
||||
-lrv | --launchRedisVerify ) launch_redis;
|
||||
wait_until_redis_is_ready;
|
||||
;;
|
||||
-h | --help ) helptext;
|
||||
exit
|
||||
;;
|
||||
* ) helptext
|
||||
exit 1
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
|
|
@ -42,6 +42,11 @@ cd web
|
|||
./update_web.sh
|
||||
~~~~
|
||||
|
||||
### API
|
||||
|
||||
[API Documentation](https://github.com/D4-project/d4-core/tree/master/server/documentation/README.md)
|
||||
|
||||
|
||||
### Notes
|
||||
|
||||
- All server logs are located in ``d4-core/server/logs/``
|
||||
|
|
|
@ -7,12 +7,36 @@
|
|||
D4 core server is a complete server to handle clients (sensors) including the decapsulation of the [D4 protocol](https://github.com/D4-project/architecture/tree/master/format), control of
|
||||
sensor registrations, management of decoding protocols and dispatching to adequate decoders/analysers.
|
||||
|
||||
## Database map
|
||||
## Database map - Metadata
|
||||
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| | |
|
||||
| | | |
|
||||
```
|
||||
DB 0 - Stats + sensor configs
|
||||
DB 1 - Users
|
||||
DB 2 - Analyzer queue
|
||||
DB 3 - Flask Cache
|
||||
```
|
||||
|
||||
### DB 1
|
||||
|
||||
##### User Management:
|
||||
| Hset Key | Field | Value |
|
||||
| ------ | ------ | ------ |
|
||||
| user:all | **user id** | **password hash** |
|
||||
| | | |
|
||||
| user:tokens | **token** | **user id** |
|
||||
| | | |
|
||||
| user_metadata:**user id** | token | **token** |
|
||||
| | change_passwd | **boolean** |
|
||||
| | role | **role** |
|
||||
|
||||
| Set Key | Value |
|
||||
| ------ | ------ |
|
||||
| user_role:**role** | **user id** |
|
||||
|
||||
|
||||
| Zrank Key | Field | Value |
|
||||
| ------ | ------ | ------ |
|
||||
| ail:all_role | **role** | **int, role priority (1=admin)** |
|
||||
|
||||
### Server
|
||||
| Key | Value |
|
||||
|
@ -63,8 +87,10 @@ sensor registrations, management of decoding protocols and dispatching to adequa
|
|||
| --- | --- | --- |
|
||||
| metadata_uuid:**uuid** | first_seen | **epoch** |
|
||||
| | last_seen | **epoch** |
|
||||
| | description | **description** |
|
||||
| | Error | **error message** |
|
||||
| | description | **description** | (optionnal)
|
||||
| | Error | **error message** | (optionnal)
|
||||
| | hmac_key | **hmac_key** | (optionnal)
|
||||
| | user_id | **user_id** | (optionnal)
|
||||
|
||||
###### Last IP
|
||||
| List Key | Value |
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# API DOCUMENTATION
|
||||
|
||||
## General
|
||||
|
||||
### Automation key
|
||||
|
||||
The authentication of the automation is performed via a secure key available in the D4 UI interface. Make sure you keep that key secret. It gives access to the entire database! The API key is available in the ``Settings`` menu under ``My Profile``.
|
||||
|
||||
The authorization is performed by using the following header:
|
||||
|
||||
~~~~
|
||||
Authorization: YOUR_API_KEY
|
||||
~~~~
|
||||
### Accept and Content-Type headers
|
||||
|
||||
When submitting data in a POST, PUT or DELETE operation you need to specify in what content-type you encoded the payload. This is done by setting the below Content-Type headers:
|
||||
|
||||
~~~~
|
||||
Content-Type: application/json
|
||||
~~~~
|
||||
|
||||
Example:
|
||||
|
||||
~~~~
|
||||
curl --header "Authorization: YOUR_API_KEY" --header "Content-Type: application/json" https://D4_URL/
|
||||
~~~~
|
||||
|
||||
## Sensor Registration
|
||||
|
||||
### Register a sensor: `api/v1/add/sensor/register`<a name="add_sensor_register"></a>
|
||||
|
||||
#### Description
|
||||
Register a sensor.
|
||||
|
||||
**Method** : `POST`
|
||||
|
||||
#### Parameters
|
||||
- `uuid`
|
||||
- sensor uuid
|
||||
- *uuid4*
|
||||
- mandatory
|
||||
|
||||
- `hmac_key`
|
||||
- sensor secret key
|
||||
- *binary*
|
||||
- mandatory
|
||||
|
||||
- `description`
|
||||
- sensor description
|
||||
- *str*
|
||||
|
||||
- `mail`
|
||||
- user mail
|
||||
- *str*
|
||||
|
||||
#### JSON response
|
||||
- `uuid`
|
||||
- sensor uuid
|
||||
- *uuid4*
|
||||
|
||||
#### Example
|
||||
```
|
||||
curl https://127.0.0.1:7000/api/v1/add/sensor/register --header "Authorization: iHc1_ChZxj1aXmiFiF1mkxxQkzawwriEaZpPqyTQj " -H "Content-Type: application/json" --data @input.json -X POST
|
||||
```
|
||||
|
||||
#### input.json Example
|
||||
```json
|
||||
{
|
||||
"uuid": "ff7ba400-e76c-4053-982d-feec42bdef38",
|
||||
"hmac_key": "...HMAC_KEY..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Expected Success Response
|
||||
**HTTP Status Code** : `200`
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "ff7ba400-e76c-4053-982d-feec42bdef38",
|
||||
}
|
||||
```
|
||||
|
||||
#### Expected Fail Response
|
||||
|
||||
**HTTP Status Code** : `400`
|
||||
```json
|
||||
{"status": "error", "reason": "Mandatory parameter(s) not provided"}
|
||||
{"status": "error", "reason": "Invalid uuid"}
|
||||
```
|
||||
|
||||
**HTTP Status Code** : `409`
|
||||
```json
|
||||
{"status": "error", "reason": "Sensor already registred"}
|
||||
```
|
|
@ -25,3 +25,17 @@ pushd redis/
|
|||
git checkout 5.0
|
||||
make
|
||||
popd
|
||||
|
||||
# LAUNCH Redis
|
||||
bash ${AIL_BIN}LAUNCH.sh -lrv &
|
||||
wait
|
||||
echo ""
|
||||
|
||||
# create default users
|
||||
pushd web/
|
||||
./create_default_user.py
|
||||
popd
|
||||
|
||||
bash LAUNCH.sh -k &
|
||||
wait
|
||||
echo ""
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import redis
|
||||
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=0)
|
||||
|
||||
def is_valid_uuid_v4(UUID):
|
||||
UUID = UUID.replace('-', '')
|
||||
try:
|
||||
uuid_test = uuid.UUID(hex=UUID, version=4)
|
||||
return uuid_test.hex == UUID
|
||||
except:
|
||||
return False
|
||||
|
||||
## TODO: add description
|
||||
def register_sensor(req_dict):
|
||||
sensor_uuid = req_dict.get('uuid', None)
|
||||
hmac_key = req_dict.get('hmac_key', None)
|
||||
user_id = req_dict.get('mail', None)
|
||||
# verify uuid
|
||||
if not is_valid_uuid_v4(sensor_uuid):
|
||||
return ({"status": "error", "reason": "Invalid uuid"}, 400)
|
||||
sensor_uuid = sensor_uuid.replace('-', '')
|
||||
# sensor already exist
|
||||
if r_serv_db.exists('metadata_uuid:{}'.format(sensor_uuid)):
|
||||
return ({"status": "error", "reason": "Sensor already registred"}, 409)
|
||||
|
||||
res = _register_sensor(sensor_uuid, hmac_key, user_id=user_id, description=None)
|
||||
return res
|
||||
|
||||
|
||||
def _register_sensor(sensor_uuid, secret_key, user_id=None, description=None):
|
||||
r_serv_db.hset('metadata_uuid:{}'.format(sensor_uuid), 'hmac_key', secret_key)
|
||||
if user_id:
|
||||
r_serv_db.hset('metadata_uuid:{}'.format(sensor_uuid), 'user_mail', user_id)
|
||||
if description:
|
||||
r_serv_db.hset('metadata_uuid:{}'.format(sensor_uuid), 'description', description)
|
||||
return ({'uuid': sensor_uuid}, 200)
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import time
|
||||
import redis
|
||||
import bcrypt
|
||||
import random
|
||||
|
||||
from flask_login import UserMixin
|
||||
|
||||
class User(UserMixin):
|
||||
|
||||
def __init__(self, id):
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
self.r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
if self.r_serv_db.hexists('user:all', id):
|
||||
self.id = id
|
||||
else:
|
||||
self.id = "__anonymous__"
|
||||
|
||||
# return True or False
|
||||
#def is_authenticated():
|
||||
|
||||
# return True or False
|
||||
#def is_anonymous():
|
||||
|
||||
@classmethod
|
||||
def get(self_class, id):
|
||||
return self_class(id)
|
||||
|
||||
def user_is_anonymous(self):
|
||||
if self.id == "__anonymous__":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_password(self, password):
|
||||
if self.user_is_anonymous():
|
||||
return False
|
||||
|
||||
rand_sleep = random.randint(1,300)/1000
|
||||
time.sleep(rand_sleep)
|
||||
|
||||
password = password.encode()
|
||||
hashed_password = self.r_serv_db.hget('user:all', self.id).encode()
|
||||
if bcrypt.checkpw(password, hashed_password):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def request_password_change(self):
|
||||
if self.r_serv_db.hget('user_metadata:{}'.format(self.id), 'change_passwd') == 'True':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_in_role(self, role):
|
||||
if self.r_serv_db.sismember('user_role:{}'.format(role), self.id):
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -1,6 +1,8 @@
|
|||
twisted[tls]
|
||||
redis
|
||||
flask
|
||||
flask-login
|
||||
bcrypt
|
||||
|
||||
#sudo python3 -m pip install --upgrade service_identity
|
||||
|
||||
|
|
|
@ -3,19 +3,35 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
import uuid
|
||||
import time
|
||||
import json
|
||||
import redis
|
||||
import time
|
||||
import uuid
|
||||
import flask
|
||||
import redis
|
||||
import random
|
||||
import datetime
|
||||
import ipaddress
|
||||
import configparser
|
||||
|
||||
import subprocess
|
||||
|
||||
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for
|
||||
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response
|
||||
from flask_login import LoginManager, current_user, login_user, logout_user, login_required
|
||||
|
||||
import bcrypt
|
||||
|
||||
# Import Role_Manager
|
||||
from Role_Manager import create_user_db, check_password_strength, check_user_role_integrity
|
||||
from Role_Manager import login_user_basic
|
||||
|
||||
sys.path.append(os.path.join(os.environ['D4_HOME'], 'lib'))
|
||||
from User import User
|
||||
|
||||
# Import Blueprint
|
||||
from blueprints.restApi import restApi
|
||||
from blueprints.settings import settings
|
||||
|
||||
baseUrl = ''
|
||||
if baseUrl != '':
|
||||
|
@ -59,21 +75,60 @@ redis_server_metadata = redis.StrictRedis(
|
|||
db=0,
|
||||
decode_responses=True)
|
||||
|
||||
redis_users = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
redis_server_analyzer = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=2,
|
||||
decode_responses=True)
|
||||
|
||||
r_cache = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=3,
|
||||
decode_responses=True)
|
||||
|
||||
with open(json_type_description_path, 'r') as f:
|
||||
json_type = json.loads(f.read())
|
||||
json_type_description = {}
|
||||
for type_info in json_type:
|
||||
json_type_description[type_info['type']] = type_info
|
||||
|
||||
Flask_dir = os.path.join(os.environ['D4_HOME'], 'web')
|
||||
|
||||
# ========= TLS =========#
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.load_cert_chain(certfile=os.path.join(Flask_dir, 'server.crt'), keyfile=os.path.join(Flask_dir, 'server.key'))
|
||||
#print(ssl_context.get_ciphers())
|
||||
# ========= =========#
|
||||
|
||||
app = Flask(__name__, static_url_path=baseUrl+'/static/')
|
||||
app.config['MAX_CONTENT_LENGTH'] = 900 * 1024 * 1024
|
||||
|
||||
# ========= session ========
|
||||
app.secret_key = str(random.getrandbits(256))
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'login'
|
||||
login_manager.init_app(app)
|
||||
# ========= =========#
|
||||
|
||||
# ========= BLUEPRINT =========#
|
||||
app.register_blueprint(restApi)
|
||||
app.register_blueprint(settings)
|
||||
# ========= =========#
|
||||
|
||||
# ========= LOGIN MANAGER ========
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.get(user_id)
|
||||
# ========= =========#
|
||||
|
||||
# ========== FUNCTIONS ============
|
||||
def is_valid_uuid_v4(header_uuid):
|
||||
try:
|
||||
|
@ -195,19 +250,131 @@ def get_uuid_disk_statistics(uuid_name, date_day='', type='', all_types_on_disk=
|
|||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
# API - JSON
|
||||
if request.path.startswith('/api/'):
|
||||
return Response(json.dumps({"status": "error", "reason": "404 Not Found"}, indent=2, sort_keys=True), mimetype='application/json'), 404
|
||||
# UI - HTML Template
|
||||
else:
|
||||
return render_template('404.html'), 404
|
||||
|
||||
@app.errorhandler(405)
|
||||
def _handle_client_error(e):
|
||||
if request.path.startswith('/api/'):
|
||||
res_dict = {"status": "error", "reason": "Method Not Allowed: The method is not allowed for the requested URL"}
|
||||
anchor_id = request.path[8:]
|
||||
anchor_id = anchor_id.replace('/', '_')
|
||||
api_doc_url = 'https://d4-project.org#{}'.format(anchor_id)
|
||||
res_dict['documentation'] = api_doc_url
|
||||
return Response(json.dumps(res_dict, indent=2, sort_keys=True), mimetype='application/json'), 405
|
||||
else:
|
||||
return
|
||||
|
||||
# ========== ROUTES ============
|
||||
@app.route('/test')
|
||||
def test():
|
||||
return 'test'
|
||||
@app.route('/login', methods=['POST', 'GET'])
|
||||
def login():
|
||||
|
||||
current_ip = request.remote_addr
|
||||
login_failed_ip = r_cache.get('failed_login_ip:{}'.format(current_ip))
|
||||
|
||||
# brute force by ip
|
||||
if login_failed_ip:
|
||||
login_failed_ip = int(login_failed_ip)
|
||||
if login_failed_ip >= 5:
|
||||
error = 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip:{}'.format(current_ip)))
|
||||
return render_template("login.html", error=error)
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
#next_page = request.form.get('next_page')
|
||||
|
||||
if username is not None:
|
||||
user = User.get(username)
|
||||
|
||||
login_failed_user_id = r_cache.get('failed_login_user_id:{}'.format(username))
|
||||
# brute force by user_id
|
||||
if login_failed_user_id:
|
||||
login_failed_user_id = int(login_failed_user_id)
|
||||
if login_failed_user_id >= 5:
|
||||
error = 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_user_id:{}'.format(username)))
|
||||
return render_template("login.html", error=error)
|
||||
|
||||
if user and user.check_password(password):
|
||||
#if not check_user_role_integrity(user.get_id()):
|
||||
# error = 'Incorrect User ACL, Please contact your administrator'
|
||||
# return render_template("login.html", error=error)
|
||||
login_user(user) ## TODO: use remember me ?
|
||||
if user.request_password_change():
|
||||
return redirect(url_for('change_password'))
|
||||
else:
|
||||
return redirect(url_for('index'))
|
||||
# login failed
|
||||
else:
|
||||
# set brute force protection
|
||||
#logger.warning("Login failed, ip={}, username={}".format(current_ip, username))
|
||||
r_cache.incr('failed_login_ip:{}'.format(current_ip))
|
||||
r_cache.expire('failed_login_ip:{}'.format(current_ip), 300)
|
||||
r_cache.incr('failed_login_user_id:{}'.format(username))
|
||||
r_cache.expire('failed_login_user_id:{}'.format(username), 300)
|
||||
|
||||
error = 'Password Incorrect'
|
||||
return render_template("login.html", error=error)
|
||||
|
||||
return 'please provide a valid username'
|
||||
|
||||
else:
|
||||
#next_page = request.args.get('next')
|
||||
error = request.args.get('error')
|
||||
return render_template("login.html" , error=error)
|
||||
|
||||
@app.route('/change_password', methods=['POST', 'GET'])
|
||||
@login_required
|
||||
def change_password():
|
||||
password1 = request.form.get('password1')
|
||||
password2 = request.form.get('password2')
|
||||
error = request.args.get('error')
|
||||
|
||||
if error:
|
||||
return render_template("change_password.html", error=error)
|
||||
|
||||
if current_user.is_authenticated and password1!=None:
|
||||
if password1==password2:
|
||||
if check_password_strength(password1):
|
||||
user_id = current_user.get_id()
|
||||
create_user_db(user_id , password1, update=True)
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
error = 'Incorrect password'
|
||||
return render_template("change_password.html", error=error)
|
||||
else:
|
||||
error = "Passwords don't match"
|
||||
return render_template("change_password.html", error=error)
|
||||
else:
|
||||
error = 'Please choose a new password'
|
||||
return render_template("change_password.html", error=error)
|
||||
|
||||
@app.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# role error template
|
||||
@app.route('/role', methods=['POST', 'GET'])
|
||||
@login_required
|
||||
def role():
|
||||
return render_template("error/403.html"), 403
|
||||
|
||||
@app.route('/')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def index():
|
||||
date = datetime.datetime.now().strftime("%Y/%m/%d")
|
||||
return render_template("index.html", date=date)
|
||||
|
||||
@app.route('/_json_daily_uuid_stats')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def _json_daily_uuid_stats():
|
||||
date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
daily_uuid = redis_server_metadata.zrange('daily_uuid:{}'.format(date), 0, -1, withscores=True)
|
||||
|
@ -219,6 +386,8 @@ def _json_daily_uuid_stats():
|
|||
return jsonify(data_daily_uuid)
|
||||
|
||||
@app.route('/_json_daily_type_stats')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def _json_daily_type_stats():
|
||||
date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
daily_uuid = redis_server_metadata.zrange('daily_type:{}'.format(date), 0, -1, withscores=True)
|
||||
|
@ -235,6 +404,8 @@ def _json_daily_type_stats():
|
|||
return jsonify(data_daily_uuid)
|
||||
|
||||
@app.route('/sensors_status')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def sensors_status():
|
||||
active_connection_filter = request.args.get('active_connection_filter')
|
||||
if active_connection_filter is None:
|
||||
|
@ -314,6 +485,8 @@ def sensors_status():
|
|||
active_connection_filter=active_connection_filter)
|
||||
|
||||
@app.route('/show_active_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def show_active_uuid():
|
||||
#swap switch value
|
||||
active_connection_filter = request.args.get('show_active_connection')
|
||||
|
@ -328,6 +501,8 @@ def show_active_uuid():
|
|||
return redirect(url_for('sensors_status', active_connection_filter=active_connection_filter))
|
||||
|
||||
@app.route('/server_management')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def server_management():
|
||||
blacklisted_ip = request.args.get('blacklisted_ip')
|
||||
unblacklisted_ip = request.args.get('unblacklisted_ip')
|
||||
|
@ -398,6 +573,8 @@ def server_management():
|
|||
blacklisted_uuid=blacklisted_uuid, unblacklisted_uuid=unblacklisted_uuid)
|
||||
|
||||
@app.route('/uuid_management')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def uuid_management():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
if is_valid_uuid_v4(uuid_sensor):
|
||||
|
@ -470,6 +647,8 @@ def uuid_management():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/blacklisted_ip')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def blacklisted_ip():
|
||||
blacklisted_ip = request.args.get('blacklisted_ip')
|
||||
unblacklisted_ip = request.args.get('unblacklisted_ip')
|
||||
|
@ -495,6 +674,8 @@ def blacklisted_ip():
|
|||
unblacklisted_ip=unblacklisted_ip, blacklisted_ip=blacklisted_ip)
|
||||
|
||||
@app.route('/blacklisted_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def blacklisted_uuid():
|
||||
blacklisted_uuid = request.args.get('blacklisted_uuid')
|
||||
unblacklisted_uuid = request.args.get('unblacklisted_uuid')
|
||||
|
@ -521,6 +702,8 @@ def blacklisted_uuid():
|
|||
|
||||
|
||||
@app.route('/uuid_change_stream_max_size')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def uuid_change_stream_max_size():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -539,6 +722,8 @@ def uuid_change_stream_max_size():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/uuid_change_description')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def uuid_change_description():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
description = request.args.get('description')
|
||||
|
@ -550,6 +735,8 @@ def uuid_change_description():
|
|||
|
||||
# # TODO: check analyser uuid dont exist
|
||||
@app.route('/add_new_analyzer')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def add_new_analyzer():
|
||||
type = request.args.get('type')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -576,6 +763,8 @@ def add_new_analyzer():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/empty_analyzer_queue')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def empty_analyzer_queue():
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
type = request.args.get('type')
|
||||
|
@ -598,6 +787,8 @@ def empty_analyzer_queue():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/remove_analyzer')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def remove_analyzer():
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
type = request.args.get('type')
|
||||
|
@ -623,6 +814,8 @@ def remove_analyzer():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/analyzer_change_max_size')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def analyzer_change_max_size():
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -641,6 +834,8 @@ def analyzer_change_max_size():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/kick_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def kick_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
if is_valid_uuid_v4(uuid_sensor):
|
||||
|
@ -650,6 +845,8 @@ def kick_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/blacklist_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def blacklist_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -670,6 +867,8 @@ def blacklist_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/unblacklist_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def unblacklist_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -693,6 +892,8 @@ def unblacklist_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/blacklist_ip')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def blacklist_ip():
|
||||
ip = request.args.get('ip')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -718,6 +919,8 @@ def blacklist_ip():
|
|||
return 'Invalid ip'
|
||||
|
||||
@app.route('/unblacklist_ip')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def unblacklist_ip():
|
||||
ip = request.args.get('ip')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -745,6 +948,8 @@ def unblacklist_ip():
|
|||
return 'Invalid ip'
|
||||
|
||||
@app.route('/blacklist_ip_by_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def blacklist_ip_by_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -756,6 +961,8 @@ def blacklist_ip_by_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/unblacklist_ip_by_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def unblacklist_ip_by_uuid():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -767,6 +974,8 @@ def unblacklist_ip_by_uuid():
|
|||
return 'Invalid uuid'
|
||||
|
||||
@app.route('/add_accepted_type')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def add_accepted_type():
|
||||
type = request.args.get('type')
|
||||
extended_type_name = request.args.get('extended_type_name')
|
||||
|
@ -786,6 +995,8 @@ def add_accepted_type():
|
|||
return 'Invalid type'
|
||||
|
||||
@app.route('/remove_accepted_type')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def remove_accepted_type():
|
||||
type = request.args.get('type')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -798,6 +1009,8 @@ def remove_accepted_type():
|
|||
return 'Invalid type'
|
||||
|
||||
@app.route('/remove_accepted_extended_type')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def remove_accepted_extended_type():
|
||||
type_name = request.args.get('type_name')
|
||||
redis_server_metadata.srem('server:accepted_extended_type', type_name)
|
||||
|
@ -805,6 +1018,8 @@ def remove_accepted_extended_type():
|
|||
|
||||
# demo function
|
||||
@app.route('/delete_data')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def delete_data():
|
||||
date = datetime.datetime.now().strftime("%Y%m%d")
|
||||
redis_server_metadata.delete('daily_type:{}'.format(date))
|
||||
|
@ -813,6 +1028,8 @@ def delete_data():
|
|||
|
||||
# demo function
|
||||
@app.route('/set_uuid_hmac_key')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def set_uuid_hmac_key():
|
||||
uuid_sensor = request.args.get('uuid')
|
||||
user = request.args.get('redirect')
|
||||
|
@ -824,6 +1041,8 @@ def set_uuid_hmac_key():
|
|||
|
||||
# demo function
|
||||
@app.route('/whois_data')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def whois_data():
|
||||
ip = request.args.get('ip')
|
||||
if is_valid_ip:
|
||||
|
@ -832,11 +1051,15 @@ def whois_data():
|
|||
return 'Invalid IP'
|
||||
|
||||
@app.route('/generate_uuid')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def generate_uuid():
|
||||
new_uuid = uuid.uuid4()
|
||||
return jsonify({'uuid': new_uuid})
|
||||
|
||||
@app.route('/get_analyser_sample')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def get_analyser_sample():
|
||||
type = request.args.get('type')
|
||||
analyzer_uuid = request.args.get('analyzer_uuid')
|
||||
|
@ -864,6 +1087,8 @@ def get_analyser_sample():
|
|||
return jsonify('Incorrect UUID')
|
||||
|
||||
@app.route('/get_uuid_type_history_json')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def get_uuid_type_history_json():
|
||||
uuid_sensor = request.args.get('uuid_sensor')
|
||||
if is_valid_uuid_v4(uuid_sensor):
|
||||
|
@ -894,6 +1119,8 @@ def get_uuid_type_history_json():
|
|||
return jsonify('Incorrect UUID')
|
||||
|
||||
@app.route('/get_uuid_stats_history_json')
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def get_uuid_stats_history_json():
|
||||
uuid_sensor = request.args.get('uuid_sensor')
|
||||
stats = request.args.get('stats')
|
||||
|
@ -925,4 +1152,4 @@ def get_uuid_stats_history_json():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host='0.0.0.0', port=7000, threaded=True)
|
||||
app.run(host='0.0.0.0', port=7000, threaded=True, ssl_context=ssl_context)
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import re
|
||||
import redis
|
||||
import bcrypt
|
||||
|
||||
from functools import wraps
|
||||
from flask_login import LoginManager, current_user, login_user, logout_user, login_required
|
||||
|
||||
from flask import request, current_app
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'role'
|
||||
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
default_passwd_file = os.path.join(os.environ['D4_HOME'], 'DEFAULT_PASSWORD')
|
||||
|
||||
regex_password = r'^(?=(.*\d){2})(?=.*[a-z])(?=.*[A-Z]).{10,100}$'
|
||||
regex_password = re.compile(regex_password)
|
||||
|
||||
###############################################################
|
||||
############### CHECK ROLE ACCESS ##################
|
||||
###############################################################
|
||||
|
||||
def login_admin(func):
|
||||
@wraps(func)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
return login_manager.unauthorized()
|
||||
elif (not current_user.is_in_role('admin')):
|
||||
return login_manager.unauthorized()
|
||||
return func(*args, **kwargs)
|
||||
return decorated_view
|
||||
|
||||
def login_user_basic(func):
|
||||
@wraps(func)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
return login_manager.unauthorized()
|
||||
elif (not current_user.is_in_role('user')):
|
||||
return login_manager.unauthorized()
|
||||
return func(*args, **kwargs)
|
||||
return decorated_view
|
||||
|
||||
|
||||
|
||||
###############################################################
|
||||
###############################################################
|
||||
###############################################################
|
||||
|
||||
def gen_password(length=30, charset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_!@#$%^&*()"):
|
||||
random_bytes = os.urandom(length)
|
||||
len_charset = len(charset)
|
||||
indices = [int(len_charset * (byte / 256.0)) for byte in random_bytes]
|
||||
return "".join([charset[index] for index in indices])
|
||||
|
||||
def gen_token(length=41, charset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"):
|
||||
random_bytes = os.urandom(length)
|
||||
len_charset = len(charset)
|
||||
indices = [int(len_charset * (byte / 256.0)) for byte in random_bytes]
|
||||
return "".join([charset[index] for index in indices])
|
||||
|
||||
def generate_new_token(user_id):
|
||||
# create user token
|
||||
current_token = r_serv_db.hget('user_metadata:{}'.format(user_id), 'token')
|
||||
if current_token:
|
||||
r_serv_db.hdel('user:tokens', current_token)
|
||||
token = gen_token(41)
|
||||
r_serv_db.hset('user:tokens', token, user_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(user_id), 'token', token)
|
||||
|
||||
def get_default_admin_token():
|
||||
if r_serv_db.exists('user_metadata:admin@admin.test'):
|
||||
return r_serv_db.hget('user_metadata:admin@admin.test', 'token')
|
||||
else:
|
||||
return ''
|
||||
|
||||
def create_user_db(username_id , password, default=False, role=None, update=False):
|
||||
password = password.encode()
|
||||
password_hash = hashing_password(password)
|
||||
|
||||
# create user token
|
||||
generate_new_token(username_id)
|
||||
|
||||
if update:
|
||||
r_serv_db.hdel('user_metadata:{}'.format(username_id), 'change_passwd')
|
||||
# remove default user password file
|
||||
if username_id=='admin@admin.test':
|
||||
os.remove(default_passwd_file)
|
||||
else:
|
||||
if default:
|
||||
r_serv_db.hset('user_metadata:{}'.format(username_id), 'change_passwd', 'True')
|
||||
if role:
|
||||
if role in get_all_role():
|
||||
for role_to_add in get_all_user_role(role):
|
||||
r_serv_db.sadd('user_role:{}'.format(role_to_add), username_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(username_id), 'role', role)
|
||||
|
||||
r_serv_db.hset('user:all', username_id, password_hash)
|
||||
|
||||
def edit_user_db(user_id, role, password=None):
|
||||
if password:
|
||||
password_hash = hashing_password(password.encode())
|
||||
r_serv_db.hset('user:all', user_id, password_hash)
|
||||
|
||||
current_role = r_serv_db.hget('user_metadata:{}'.format(user_id), 'role')
|
||||
if role != current_role:
|
||||
request_level = get_role_level(role)
|
||||
current_role = get_role_level(current_role)
|
||||
|
||||
if current_role < request_level:
|
||||
role_to_remove = get_user_role_by_range(current_role -1, request_level - 2)
|
||||
for role_id in role_to_remove:
|
||||
r_serv_db.srem('user_role:{}'.format(role_id), user_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(user_id), 'role', role)
|
||||
else:
|
||||
role_to_add = get_user_role_by_range(request_level -1, current_role)
|
||||
for role_id in role_to_add:
|
||||
r_serv_db.sadd('user_role:{}'.format(role_id), user_id)
|
||||
r_serv_db.hset('user_metadata:{}'.format(user_id), 'role', role)
|
||||
|
||||
def delete_user_db(user_id):
|
||||
if r_serv_db.exists('user_metadata:{}'.format(user_id)):
|
||||
role_to_remove =get_all_role()
|
||||
for role_id in role_to_remove:
|
||||
r_serv_db.srem('user_role:{}'.format(role_id), user_id)
|
||||
user_token = r_serv_db.hget('user_metadata:{}'.format(user_id), 'token')
|
||||
r_serv_db.hdel('user:tokens', user_token)
|
||||
r_serv_db.delete('user_metadata:{}'.format(user_id))
|
||||
r_serv_db.hdel('user:all', user_id)
|
||||
|
||||
def hashing_password(bytes_password):
|
||||
hashed = bcrypt.hashpw(bytes_password, bcrypt.gensalt())
|
||||
return hashed
|
||||
|
||||
def check_password_strength(password):
|
||||
result = regex_password.match(password)
|
||||
if result:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_all_role():
|
||||
return r_serv_db.zrange('d4:all_role', 0, -1)
|
||||
|
||||
def get_role_level(role):
|
||||
return int(r_serv_db.zscore('d4:all_role', role))
|
||||
|
||||
def get_all_user_role(user_role):
|
||||
current_role_val = get_role_level(user_role)
|
||||
return r_serv_db.zrangebyscore('d4:all_role', current_role_val, 50)
|
||||
|
||||
def get_all_user_upper_role(user_role):
|
||||
current_role_val = get_role_level(user_role)
|
||||
# remove one rank
|
||||
if current_role_val > 1:
|
||||
return r_serv_db.zrange('d4:all_role', 0, current_role_val -2)
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_user_role_by_range(inf, sup):
|
||||
return r_serv_db.zrange('d4:all_role', inf, sup)
|
||||
|
||||
def get_user_role(user_id):
|
||||
return r_serv_db.hget('user_metadata:{}'.format(user_id), 'role')
|
||||
|
||||
def check_user_role_integrity(user_id):
|
||||
user_role = get_user_role(user_id)
|
||||
all_user_role = get_all_user_role(user_role)
|
||||
res = True
|
||||
if user_role not in all_user_role:
|
||||
return False
|
||||
return res
|
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
'''
|
||||
Flask functions and routes for the rest api
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
import json
|
||||
import redis
|
||||
import random
|
||||
import datetime
|
||||
|
||||
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response
|
||||
from flask_login import login_required
|
||||
|
||||
from functools import wraps
|
||||
|
||||
sys.path.append(os.path.join(os.environ['D4_HOME'], 'lib'))
|
||||
import Sensor
|
||||
|
||||
# ============ BLUEPRINT ============
|
||||
|
||||
restApi = Blueprint('restApi', __name__, template_folder='templates')
|
||||
|
||||
# ============ VARIABLES ============
|
||||
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
r_serv_metadata = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=0,
|
||||
decode_responses=True)
|
||||
|
||||
r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
r_cache = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=3,
|
||||
decode_responses=True)
|
||||
|
||||
# ============ AUTH FUNCTIONS ============
|
||||
|
||||
def check_token_format(strg, search=re.compile(r'[^a-zA-Z0-9_-]').search):
|
||||
return not bool(search(strg))
|
||||
|
||||
def verify_token(token):
|
||||
if len(token) != 41:
|
||||
return False
|
||||
|
||||
if not check_token_format(token):
|
||||
return False
|
||||
|
||||
rand_sleep = random.randint(1,300)/1000
|
||||
time.sleep(rand_sleep)
|
||||
if r_serv_db.hexists('user:tokens', token):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_user_from_token(token):
|
||||
return r_serv_db.hget('user:tokens', token)
|
||||
|
||||
def verify_user_role(role, token):
|
||||
user_id = get_user_from_token(token)
|
||||
if user_id:
|
||||
if is_in_role(user_id, role):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_in_role(user_id, role):
|
||||
if r_serv_db.sismember('user_role:{}'.format(role), user_id):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# ============ DECORATOR ============
|
||||
|
||||
def token_required(user_role):
|
||||
def actual_decorator(funct):
|
||||
@wraps(funct)
|
||||
def api_token(*args, **kwargs):
|
||||
data = authErrors(user_role)
|
||||
if data:
|
||||
return Response(json.dumps(data[0], indent=2, sort_keys=True), mimetype='application/json'), data[1]
|
||||
else:
|
||||
return funct(*args, **kwargs)
|
||||
return api_token
|
||||
return actual_decorator
|
||||
|
||||
def get_auth_from_header():
|
||||
token = request.headers.get('Authorization').replace(' ', '') # remove space
|
||||
return token
|
||||
|
||||
def authErrors(user_role):
|
||||
# Check auth
|
||||
if not request.headers.get('Authorization'):
|
||||
return ({'status': 'error', 'reason': 'Authentication needed'}, 401)
|
||||
token = get_auth_from_header()
|
||||
data = None
|
||||
# verify token format
|
||||
|
||||
# brute force protection
|
||||
current_ip = request.remote_addr
|
||||
login_failed_ip = r_cache.get('failed_login_ip_api:{}'.format(current_ip))
|
||||
# brute force by ip
|
||||
if login_failed_ip:
|
||||
login_failed_ip = int(login_failed_ip)
|
||||
if login_failed_ip >= 5:
|
||||
return ({'status': 'error', 'reason': 'Max Connection Attempts reached, Please wait {}s'.format(r_cache.ttl('failed_login_ip_api:{}'.format(current_ip)))}, 401)
|
||||
|
||||
try:
|
||||
authenticated = False
|
||||
if verify_token(token):
|
||||
authenticated = True
|
||||
|
||||
# check user role
|
||||
if not verify_user_role(user_role, token):
|
||||
data = ({'status': 'error', 'reason': 'Access Forbidden'}, 403)
|
||||
|
||||
if not authenticated:
|
||||
r_cache.incr('failed_login_ip_api:{}'.format(current_ip))
|
||||
r_cache.expire('failed_login_ip_api:{}'.format(current_ip), 300)
|
||||
data = ({'status': 'error', 'reason': 'Authentication failed'}, 401)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
data = ({'status': 'error', 'reason': 'Malformed Authentication String'}, 400)
|
||||
if data:
|
||||
return data
|
||||
else:
|
||||
return None
|
||||
|
||||
# ============ FUNCTIONS ============
|
||||
|
||||
def is_valid_uuid_v4(header_uuid):
|
||||
try:
|
||||
header_uuid=header_uuid.replace('-', '')
|
||||
uuid_test = uuid.UUID(hex=header_uuid, version=4)
|
||||
return uuid_test.hex == header_uuid
|
||||
except:
|
||||
return False
|
||||
|
||||
def one():
|
||||
return 1
|
||||
|
||||
# ============= ROUTES ==============
|
||||
|
||||
|
||||
@restApi.route("/api/v1/add/sensor/register", methods=['POST'])
|
||||
@token_required('sensor_register')
|
||||
def add_sensor_register():
|
||||
data = request.get_json()
|
||||
res = Sensor.register_sensor(data)
|
||||
return Response(json.dumps(res[0], indent=2, sort_keys=True), mimetype='application/json'), res[1]
|
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
'''
|
||||
Flask functions and routes for the rest api
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import redis
|
||||
|
||||
from flask import Flask, render_template, jsonify, request, Blueprint, redirect, url_for, Response
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from Role_Manager import login_admin, login_user_basic
|
||||
from Role_Manager import create_user_db, edit_user_db, delete_user_db, check_password_strength, generate_new_token, gen_password, get_all_role
|
||||
|
||||
# ============ BLUEPRINT ============
|
||||
|
||||
settings = Blueprint('settings', __name__, template_folder='templates')
|
||||
|
||||
# ============ VARIABLES ============
|
||||
|
||||
email_regex = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}'
|
||||
email_regex = re.compile(email_regex)
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_PORT', 6380))
|
||||
|
||||
r_serv_metadata = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=0,
|
||||
decode_responses=True)
|
||||
|
||||
r_serv_db = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
# ============ FUNCTIONS ============
|
||||
|
||||
def one():
|
||||
return 1
|
||||
|
||||
def check_email(email):
|
||||
return email_regex.match(email)
|
||||
|
||||
def get_user_metadata(user_id):
|
||||
user_metadata = {}
|
||||
user_metadata['email'] = user_id
|
||||
user_metadata['role'] = r_serv_db.hget('user_metadata:{}'.format(user_id), 'role')
|
||||
user_metadata['api_key'] = r_serv_db.hget('user_metadata:{}'.format(user_id), 'token')
|
||||
return user_metadata
|
||||
|
||||
def get_users_metadata(list_users):
|
||||
users = []
|
||||
for user in list_users:
|
||||
users.append(get_user_metadata(user))
|
||||
return users
|
||||
|
||||
def get_all_users():
|
||||
return r_serv_db.hkeys('user:all')
|
||||
|
||||
|
||||
# ============= ROUTES ==============
|
||||
|
||||
@settings.route("/settings/", methods=['GET'])
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def settings_page():
|
||||
return redirect(url_for('settings.edit_profile'))
|
||||
|
||||
@settings.route("/settings/edit_profile", methods=['GET'])
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def edit_profile():
|
||||
user_metadata = get_user_metadata(current_user.get_id())
|
||||
admin_level = current_user.is_in_role('admin')
|
||||
return render_template("edit_profile.html", user_metadata=user_metadata,
|
||||
admin_level=admin_level)
|
||||
|
||||
@settings.route("/settings/new_token", methods=['GET'])
|
||||
@login_required
|
||||
@login_user_basic
|
||||
def new_token():
|
||||
generate_new_token(current_user.get_id())
|
||||
return redirect(url_for('settings.edit_profile'))
|
||||
|
||||
@settings.route("/settings/new_token_user", methods=['GET'])
|
||||
@login_required
|
||||
@login_admin
|
||||
def new_token_user():
|
||||
user_id = request.args.get('user_id')
|
||||
if r_serv_db.exists('user_metadata:{}'.format(user_id)):
|
||||
generate_new_token(user_id)
|
||||
return redirect(url_for('settings.users_list'))
|
||||
|
||||
@settings.route("/settings/create_user", methods=['GET'])
|
||||
@login_required
|
||||
@login_admin
|
||||
def create_user():
|
||||
user_id = request.args.get('user_id')
|
||||
error = request.args.get('error')
|
||||
error_mail = request.args.get('error_mail')
|
||||
role = None
|
||||
if r_serv_db.exists('user_metadata:{}'.format(user_id)):
|
||||
role = r_serv_db.hget('user_metadata:{}'.format(user_id), 'role')
|
||||
else:
|
||||
user_id = None
|
||||
all_roles = get_all_role()
|
||||
return render_template("create_user.html", all_roles=all_roles, user_id=user_id, user_role=role,
|
||||
error=error, error_mail=error_mail,
|
||||
admin_level=True)
|
||||
|
||||
@settings.route("/settings/create_user_post", methods=['POST'])
|
||||
@login_required
|
||||
@login_admin
|
||||
def create_user_post():
|
||||
email = request.form.get('username')
|
||||
role = request.form.get('user_role')
|
||||
password1 = request.form.get('password1')
|
||||
password2 = request.form.get('password2')
|
||||
|
||||
all_roles = get_all_role()
|
||||
|
||||
if email and len(email)< 300 and check_email(email) and role:
|
||||
if role in all_roles:
|
||||
# password set
|
||||
if password1 and password2:
|
||||
if password1==password2:
|
||||
if check_password_strength(password1):
|
||||
password = password1
|
||||
else:
|
||||
return render_template("create_user.html", all_roles=all_roles, error="Incorrect Password", admin_level=True)
|
||||
else:
|
||||
return render_template("create_user.html", all_roles=all_roles, error="Passwords don't match", admin_level=True)
|
||||
# generate password
|
||||
else:
|
||||
password = gen_password()
|
||||
|
||||
if current_user.is_in_role('admin'):
|
||||
# edit user
|
||||
if r_serv_db.exists('user_metadata:{}'.format(email)):
|
||||
if password1 and password2:
|
||||
edit_user_db(email, password=password, role=role)
|
||||
return redirect(url_for('settings.users_list', new_user=email, new_user_password=password, new_user_edited=True))
|
||||
else:
|
||||
edit_user_db(email, role=role)
|
||||
return redirect(url_for('settings.users_list', new_user=email, new_user_password='Password not changed', new_user_edited=True))
|
||||
# create user
|
||||
else:
|
||||
create_user_db(email, password, default=True, role=role)
|
||||
return redirect(url_for('settings.users_list', new_user=email, new_user_password=password, new_user_edited=False))
|
||||
|
||||
else:
|
||||
return render_template("create_user.html", all_roles=all_roles, admin_level=True)
|
||||
else:
|
||||
return render_template("create_user.html", all_roles=all_roles, error_mail=True, admin_level=True)
|
||||
|
||||
@settings.route("/settings/users_list", methods=['GET'])
|
||||
@login_required
|
||||
@login_admin
|
||||
def users_list():
|
||||
all_users = get_users_metadata(get_all_users())
|
||||
new_user = request.args.get('new_user')
|
||||
new_user_dict = {}
|
||||
if new_user:
|
||||
new_user_dict['email'] = new_user
|
||||
new_user_dict['edited'] = request.args.get('new_user_edited')
|
||||
new_user_dict['password'] = request.args.get('new_user_password')
|
||||
return render_template("users_list.html", all_users=all_users, new_user=new_user_dict, admin_level=True)
|
||||
|
||||
@settings.route("/settings/edit_user", methods=['GET'])
|
||||
@login_required
|
||||
@login_admin
|
||||
def edit_user():
|
||||
user_id = request.args.get('user_id')
|
||||
return redirect(url_for('settings.create_user', user_id=user_id))
|
||||
|
||||
@settings.route("/settings/delete_user", methods=['GET'])
|
||||
@login_required
|
||||
@login_admin
|
||||
def delete_user():
|
||||
user_id = request.args.get('user_id')
|
||||
delete_user_db(user_id)
|
||||
return redirect(url_for('settings.users_list'))
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*-coding:UTF-8 -*
|
||||
|
||||
import os
|
||||
import sys
|
||||
import redis
|
||||
import configparser
|
||||
|
||||
sys.path.append(os.path.join(os.environ['D4_HOME'], 'lib'))
|
||||
|
||||
from Role_Manager import create_user_db, edit_user_db, get_default_admin_token, gen_password
|
||||
|
||||
host_redis_metadata = os.getenv('D4_REDIS_METADATA_HOST', "localhost")
|
||||
port_redis_metadata = int(os.getenv('D4_REDIS_METADATA_HOST', 6380))
|
||||
|
||||
r_serv = redis.StrictRedis(
|
||||
host=host_redis_metadata,
|
||||
port=port_redis_metadata,
|
||||
db=1,
|
||||
decode_responses=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# create role_list
|
||||
if not r_serv.exists('d4:all_role'):
|
||||
role_dict = {'admin': 1, 'user': 2, 'sensor_register': 20}
|
||||
r_serv.zadd('d4:all_role', role_dict)
|
||||
|
||||
username = 'admin@admin.test'
|
||||
password = gen_password()
|
||||
if r_serv.exists('user_metadata:admin@admin.test'):
|
||||
edit_user_db(username, password=password, role='admin')
|
||||
else:
|
||||
create_user_db(username, password, role='admin', default=True)
|
||||
|
||||
|
||||
username2 = 'config_generator@register.test'
|
||||
password2 = gen_password()
|
||||
if r_serv.exists('user_metadata:config_generator@register.test'):
|
||||
edit_user_db(username2, password=password2, role='sensor_register')
|
||||
else:
|
||||
create_user_db(username2, password2, role='sensor_register', default=True)
|
||||
|
||||
|
||||
token = get_default_admin_token()
|
||||
|
||||
default_passwd_file = os.path.join(os.environ['D4_HOME'], 'DEFAULT_PASSWORD')
|
||||
to_write_str = '# Password Generated by default\n# This file is deleted after the first login\n#\nemail=admin@admin.test\npassword='
|
||||
to_write_str = to_write_str + password + '\nAPI_Key=' + token
|
||||
with open(default_passwd_file, 'w') as f:
|
||||
f.write(to_write_str)
|
||||
|
||||
print('new user created: {}'.format(username))
|
||||
print('password: {}'.format(password))
|
||||
print('token: {}'.format(token))
|
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>403 - D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='image/d4-logo.png') }}">
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div>
|
||||
<br>
|
||||
<br>
|
||||
<h1 class="text-center">403 Forbidden</h1>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div class="d-flex justify-content-center">
|
||||
<pre>
|
||||
,d8 ,a8888a, ad888888b,
|
||||
,d888 ,8P"' `"Y8, d8" "88
|
||||
,d8" 88 ,8P Y8, a8P
|
||||
,d8" 88 88 88 aad8"
|
||||
,d8" 88 88 88 ""Y8,
|
||||
8888888888888 `8b d8' "8b
|
||||
88 `8ba, ,ad8' Y8, a88
|
||||
88 "Y8888P" "Y888888P'
|
||||
|
||||
88888888888 88 88 88 88
|
||||
88 88 "" 88 88
|
||||
88 88 88 88
|
||||
88aaaaa ,adPPYba, 8b,dPPYba, 88,dPPYba, 88 ,adPPYb,88 ,adPPYb,88 ,adPPYba, 8b,dPPYba,
|
||||
88""""" a8" "8a 88P' "Y8 88P' "8a 88 a8" `Y88 a8" `Y88 a8P_____88 88P' `"8a
|
||||
88 8b d8 88 88 d8 88 8b 88 8b 88 8PP""""""" 88 88
|
||||
88 "8a, ,a8" 88 88b, ,a8" 88 "8a, ,d88 "8a, ,d88 "8b, ,aa 88 88
|
||||
88 `"YbbdP"' 88 8Y"Ybbd8"' 88 `"8bbdP"Y8 `"8bbdP"Y8 `"Ybbd8"' 88 88
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{% include 'navfooter.html' %}
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#nav-home").addClass("active");
|
||||
} );
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -23,22 +23,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<pre>
|
||||
|
@ -68,3 +53,11 @@
|
|||
|
||||
{% include 'navfooter.html' %}
|
||||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#nav-home").addClass("active");
|
||||
} );
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -23,22 +23,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="card-deck justify-content-center ml-0 mr-0">
|
||||
<div class="card border-dark mt-3 ml-4 mr-4">
|
||||
|
|
|
@ -23,22 +23,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="card-deck justify-content-center ml-0 mr-0">
|
||||
<div class="card border-dark mt-3 ml-4 mr-4">
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png')}}">
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="text-center">
|
||||
|
||||
|
||||
<form class="form-signin" action="{{ url_for('change_password')}}" autocomplete="off" method="post">
|
||||
<img class="mb-4" src="{{ url_for('static', filename='img/d4-logo.png')}}" width="300">
|
||||
<h1 class="h3 mb-3 text-secondary">Change Password</h1>
|
||||
<label for="inputPassword1" class="sr-only">Password</label>
|
||||
<input type="password" id="inputPassword1" name="password1" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Password" autocomplete="new-password" required autofocus>
|
||||
<label for="inputPassword2" class="sr-only">Confirm Password</label>
|
||||
<input type="password" id="inputPassword2" name="password2" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Confirm Password" value="" autocomplete="new-password" required>
|
||||
{% if error %}
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
{% endif %}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<h5 class="h3 mb-3 text-secondary">Password Requirements</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Minimal length
|
||||
<span class="badge badge-primary badge-pill">10</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Upper characters: A-Z
|
||||
<span class="badge badge-primary badge-pill">1</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Lower characters: a-z
|
||||
<span class="badge badge-primary badge-pill">1</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Digits: 0-9
|
||||
<span class="badge badge-primary badge-pill">2</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Maximum length
|
||||
<span class="badge badge-primary badge-pill">100</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
|
@ -0,0 +1,156 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png') }}">
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
{% include 'sidebar_settings.html' %}
|
||||
|
||||
<div class="col-12 col-lg-10" id="core_content">
|
||||
|
||||
<form class="form-signin" action="{{ url_for('settings.create_user_post')}}" autocomplete="off" method="post">
|
||||
|
||||
<h1 class="h3 mt-1 mb-3 text-center text-secondary">Create User</h1>
|
||||
<label for="inputEmail" class="sr-only">Email address</label>
|
||||
<input type="email" id="inputEmail" name="username" class="form-control {% if error_mail %}is-invalid{% endif %}" placeholder="Email address" autocomplete="off" required {% if user_id %}value="{{user_id}}"{% else %}{% endif %}>
|
||||
{% if error_mail %}
|
||||
<div class="invalid-feedback">
|
||||
Please provide a valid email address
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<label class="mt-3" for="role_selector">User Role</label>
|
||||
<select class="custom-select" id="role_selector" name="user_role">
|
||||
{% for role in all_roles %}
|
||||
{% if role == user_role %}
|
||||
<option value="{{role}}" selected>{{role}}</option>
|
||||
{% else %}
|
||||
<option value="{{role}}">{{role}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<div class="custom-control custom-switch mt-4 mb-3">
|
||||
<input type="checkbox" class="custom-control-input" id="set_manual_password" value="" onclick="toggle_password_fields();">
|
||||
<label class="custom-control-label" for="set_manual_password">Set Password</label>
|
||||
</div>
|
||||
|
||||
<div id="password-section">
|
||||
<h1 class="h3 mb-3 text-center text-secondary">Create Password</h1>
|
||||
<label for="inputPassword1" class="sr-only">Password</label>
|
||||
<input type="password" id="inputPassword1" name="password1" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Password" autocomplete="new-password">
|
||||
<label for="inputPassword2" class="sr-only">Confirm Password</label>
|
||||
<input type="password" id="inputPassword2" name="password2" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Confirm Password" value="" autocomplete="new-password">
|
||||
{% if error %}
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary btn-block mt-3" type="submit">Submit</button>
|
||||
|
||||
<div id="password-section-info">
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<h5 class="h3 mb-3 text-center text-secondary">Password Requirements</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Minimal length
|
||||
<span class="badge badge-primary badge-pill">10</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Upper characters: A-Z
|
||||
<span class="badge badge-primary badge-pill">1</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Lower characters: a-z
|
||||
<span class="badge badge-primary badge-pill">1</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Digits: 0-9
|
||||
<span class="badge badge-primary badge-pill">2</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Maximum length
|
||||
<span class="badge badge-primary badge-pill">100</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'navfooter.html' %}
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#password-section").hide();
|
||||
$("#password-section-info").hide();
|
||||
$("#nav-settings").addClass("active");
|
||||
$("#nav_create_user").addClass("active");
|
||||
$("#nav_user_management").removeClass("text-muted");
|
||||
|
||||
{% if error %}
|
||||
toggle_password_fields();
|
||||
{% endif %}
|
||||
} );
|
||||
|
||||
function toggle_sidebar(){
|
||||
if($('#nav_menu').is(':visible')){
|
||||
$('#nav_menu').hide();
|
||||
$('#side_menu').removeClass('border-right')
|
||||
$('#side_menu').removeClass('col-lg-2')
|
||||
$('#core_content').removeClass('col-lg-10')
|
||||
}else{
|
||||
$('#nav_menu').show();
|
||||
$('#side_menu').addClass('border-right')
|
||||
$('#side_menu').addClass('col-lg-2')
|
||||
$('#core_content').addClass('col-lg-10')
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_password_fields() {
|
||||
var password_div = $("#password-section");
|
||||
if(password_div.is(":visible")){
|
||||
$("#password-section").hide();
|
||||
$("#password-section-info").hide();
|
||||
$("#inputPassword1").prop('required',false);
|
||||
$("#inputPassword2").prop('required',false);
|
||||
} else {
|
||||
$("#password-section").show();
|
||||
$("#password-section-info").show();
|
||||
$("#inputPassword1").prop('required',true);
|
||||
$("#inputPassword2").prop('required',true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png') }}">
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
{% include 'sidebar_settings.html' %}
|
||||
|
||||
<div class="col-12 col-lg-10" id="core_content">
|
||||
|
||||
<div class="card mb-3 mt-1">
|
||||
<div class="card-header text-white bg-dark pb-1">
|
||||
<h5 class="card-title">My Profile :</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-6">
|
||||
<div class="card text-center border-secondary">
|
||||
<div class="card-body px-1 py-0">
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Email</td>
|
||||
<td>{{user_metadata['email']}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Role</td>
|
||||
<td>{{user_metadata['role']}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API Key</td>
|
||||
<td>
|
||||
{{user_metadata['api_key']}}
|
||||
<a class="ml-3" href="{{url_for('settings.new_token')}}"><i class="fa fa-random"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'navfooter.html' %}
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#nav-settings").addClass("active");
|
||||
$("#nav_edit_profile").addClass("active");
|
||||
$("#nav_my_profile").removeClass("text-muted");
|
||||
} );
|
||||
|
||||
function toggle_sidebar(){
|
||||
if($('#nav_menu').is(':visible')){
|
||||
$('#nav_menu').hide();
|
||||
$('#side_menu').removeClass('border-right')
|
||||
$('#side_menu').removeClass('col-lg-2')
|
||||
$('#core_content').removeClass('col-lg-10')
|
||||
}else{
|
||||
$('#nav_menu').show();
|
||||
$('#side_menu').addClass('border-right')
|
||||
$('#side_menu').addClass('col-lg-2')
|
||||
$('#core_content').addClass('col-lg-10')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -58,22 +58,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
|
||||
<div class="row mr-0">
|
||||
|
@ -109,6 +94,9 @@
|
|||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#nav-home").addClass("active");
|
||||
} );
|
||||
|
||||
////
|
||||
//http://bl.ocks.org/charlesdguthrie/11356441, updated and modified
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png')}}">
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="text-center">
|
||||
|
||||
|
||||
<form class="form-signin" action="{{ url_for('login')}}" method="post">
|
||||
<img class="mb-4" src="{{ url_for('static', filename='img/d4-logo.png')}}" width="300">
|
||||
<h1 class="h3 mb-3 text-secondary">Please sign in</h1>
|
||||
<label for="inputEmail" class="sr-only">Email address</label>
|
||||
<input type="email" id="inputEmail" name="username" class="form-control" placeholder="Email address" required autofocus>
|
||||
<label for="inputPassword" class="sr-only">Password</label>
|
||||
<input type="password" id="inputPassword" name="password" class="form-control {% if error %}is-invalid{% endif %}" placeholder="Password" required>
|
||||
{% if error %}
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
{% endif %}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
|
@ -0,0 +1,22 @@
|
|||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" id="nav-home" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" id="nav-sensor" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" id="nav-server" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" id="nav-settings" href="{{ url_for('settings.settings_page') }}" tabindex="-1" aria-disabled="true">Settings</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('logout') }}" tabindex="-1" aria-disabled="true"><i class="fa fa-sign-out"></i>Log Out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
|
@ -27,22 +27,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link active mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="card mt-2 mb-2">
|
||||
<div class="card-body bg-dark text-white">
|
||||
|
@ -129,6 +114,7 @@
|
|||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#nav-sensor").addClass("active");
|
||||
table = $('#myTable_1').DataTable(
|
||||
{
|
||||
"aLengthMenu": [[5, 10, 15, 20, -1], [5, 10, 15, 20, "All"]],
|
||||
|
|
|
@ -19,22 +19,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link active" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="card-deck ml-0 mr-0">
|
||||
<div class="card text-center mt-3 ml-xl-4">
|
||||
|
@ -384,6 +369,7 @@ var table
|
|||
$(document).ready(function(){
|
||||
$('#extended_type_name').hide()
|
||||
$('#analyzer_metatype_name').hide()
|
||||
$("#nav-server").addClass("active");
|
||||
|
||||
table = $('#myTable_').DataTable(
|
||||
{
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<div class="col-12 col-lg-2 p-0 bg-light border-right" id="side_menu">
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary mt-1 ml-3" onclick="toggle_sidebar();">
|
||||
<i class="fa align-left"></i>
|
||||
<span>Toggle Sidebar</span>
|
||||
</button>
|
||||
|
||||
<nav class="navbar navbar-expand navbar-light bg-light flex-md-column flex-row align-items-start py-2" id="nav_menu">
|
||||
<h5 class="d-flex text-muted w-100 py-2" id="nav_my_profile">
|
||||
<span>My Profile</span>
|
||||
</h5>
|
||||
<ul class="nav flex-md-column flex-row navbar-nav justify-content-between w-100"> <!--nav-pills-->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{url_for('settings.edit_profile')}}" id="nav_edit_profile">
|
||||
<i class="fa fa-user"></i>
|
||||
<span>My Profile</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{url_for('change_password')}}" id="nav_dashboard">
|
||||
<i class="fa fa-key"></i>
|
||||
<span>Change Password</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% if admin_level %}
|
||||
<h5 class="d-flex text-muted w-100 py-2" id="nav_user_management">
|
||||
<span>User Management</span>
|
||||
</h5>
|
||||
<ul class="nav flex-md-column flex-row navbar-nav justify-content-between w-100"> <!--nav-pills-->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{url_for('settings.create_user')}}" id="nav_create_user">
|
||||
<i class="fa fa-user-plus"></i>
|
||||
<span>Create User</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{url_for('settings.users_list')}}" id="nav_users_list">
|
||||
<i class="fa fa-users"></i>
|
||||
<span>Users List</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
</div>
|
|
@ -0,0 +1,125 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>D4-Project</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/d4-logo.png')}}">
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='font-awesome/css/font-awesome.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
||||
|
||||
<style>
|
||||
.edit_icon:hover{
|
||||
cursor: pointer;
|
||||
color: #17a2b8;
|
||||
}
|
||||
.trash_icon:hover{
|
||||
cursor: pointer;
|
||||
color: #c82333;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
{% include 'sidebar_settings.html' %}
|
||||
|
||||
<div class="col-12 col-lg-10" id="core_content">
|
||||
|
||||
{% if new_user %}
|
||||
<div class="text-center my-3 ">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{% if new_user['edited']=='True' %}
|
||||
<h5 class="card-title">User Edited</h5>
|
||||
{% else %}
|
||||
<h5 class="card-title">User Created</h5>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>User: {{new_user['email']}}</p>
|
||||
<p>Password: {{new_user['password']}}</p>
|
||||
<a href="{{url_for('settings.users_list')}}" class="btn btn-primary"><i class="fa fa-eye-slash"></i> Hide</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-responsive mt-1 table-hover table-borderless table-striped">
|
||||
<table class="table">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Api Key</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbody_last_crawled">
|
||||
{% for user in all_users %}
|
||||
<tr>
|
||||
<td>{{user['email']}}</td>
|
||||
<td>{{user['role']}}</td>
|
||||
<td>
|
||||
{{user['api_key']}}
|
||||
<a class="ml-3" href="{{url_for('settings.new_token_user')}}?user_id={{user['email']}}"><i class="fa fa-random"></i></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('settings.edit_user')}}?user_id={{user['email']}}">
|
||||
<i class="fa fa-pencil edit_icon"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('settings.delete_user')}}?user_id={{user['email']}}" class="ml-4">
|
||||
<i class="fa fa-trash trash_icon"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'navfooter.html' %}
|
||||
|
||||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$("#nav-settings").addClass("active");
|
||||
$("#nav_users_list").addClass("active");
|
||||
$("#nav_user_management").removeClass("text-muted");
|
||||
} );
|
||||
|
||||
function toggle_sidebar(){
|
||||
if($('#nav_menu').is(':visible')){
|
||||
$('#nav_menu').hide();
|
||||
$('#side_menu').removeClass('border-right')
|
||||
$('#side_menu').removeClass('col-lg-2')
|
||||
$('#core_content').removeClass('col-lg-10')
|
||||
}else{
|
||||
$('#nav_menu').show();
|
||||
$('#side_menu').addClass('border-right')
|
||||
$('#side_menu').addClass('col-lg-2')
|
||||
$('#core_content').addClass('col-lg-10')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -28,22 +28,7 @@
|
|||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||
<img src="{{ url_for('static', filename='img/d4-logo.png')}}" alt="D4 Project" style="width:80px;">
|
||||
</a>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link mr-3" href="{{ url_for('index') }}">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item" mr-3>
|
||||
<a class="nav-link mr-3" href="{{ url_for('sensors_status') }}">Sensors Status</a>
|
||||
</li>
|
||||
<li class="nav-item mr-3">
|
||||
<a class="nav-link" href="{{ url_for('server_management') }}" tabindex="-1" aria-disabled="true">Server Management</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
<div class="card text-center mt-3 ml-2 mr-2">
|
||||
<div class="card-header bg-dark text-white">
|
||||
|
|
Loading…
Reference in New Issue