Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Very various code revork and refactoring #29

Merged
merged 27 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fd3a0b0
Smoke try...
leechwort Nov 25, 2023
8e4f57a
Fix problem with user name and icon
leechwort Nov 25, 2023
4f2bd1a
WIP: rework, bug fixes, renaming, etc
leechwort Nov 26, 2023
b1bb462
WIP: refactor, stage 0
leechwort Nov 28, 2023
71f3f20
WIP: refactor, stage 1
leechwort Nov 28, 2023
7355b66
WIP: refactor, stage 2
leechwort Nov 28, 2023
e22b5dc
WIP: refactor, web API separated
leechwort Nov 28, 2023
f565dc2
API extended
leechwort Nov 29, 2023
51c2e00
WIP: AI-generated stuff for user table just for test:)
leechwort Nov 29, 2023
e83d93e
Web API extended for devices
leechwort Nov 29, 2023
0f14c99
Fix and rework auth
leechwort Nov 30, 2023
6c680a7
Some hacks about development described
leechwort Nov 30, 2023
5cc8e6f
Add columns to users table
temhota Nov 30, 2023
023c763
Small fixes: delete user and device menu button
leechwort Dec 1, 2023
9248638
Config and logging refactored
leechwort Dec 1, 2023
9620375
Latest key API endpoind extended
leechwort Dec 1, 2023
73163c6
Load config from file + API + Settings page(WIP)
leechwort Dec 2, 2023
7be8bc1
Create user API fix: return code for existing user
leechwort Dec 2, 2023
f677151
Add functionality to users page
temhota Dec 2, 2023
159d55e
Fix add user
temhota Dec 2, 2023
c75ced6
Add latest key time
temhota Dec 3, 2023
00b98a2
Basic slack notifyer implemented
leechwort Dec 3, 2023
f5ffaf4
Fix slack warning
leechwort Dec 4, 2023
b87eaf1
Devices pages added
leechwort Dec 4, 2023
bdb71f4
Added "Delete device" functionality + some fixes
leechwort Dec 6, 2023
98d173c
Add settings JSON editor
leechwort Dec 6, 2023
761b657
Pylint + Autopep8 fixes
leechwort Dec 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Project-specific files
external/
log.txt
*.db

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ logging:
export FLASK_APP=application.py
flask run --debug
```

if you want to autorestart you app on Flask changes, you can do `export FLASK_DEBUG=1`. In case of `Import Error` run `pip3 install --upgrade watchdog`.
By default, this should be run by Prismo admin process, but for debugging purpose you should run this commands by
yourself.

Expand Down
40 changes: 40 additions & 0 deletions app/api/device_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from flask import Flask, Blueprint, jsonify, request
from flask import current_app as app

from models.access_log import AccessLog
from models.device import Device

import json


device_api = Blueprint("device_api", __name__)


@device_api.route("/devices/<device_id>/accesses/", methods=["GET"])
def accesses(device_id):
data = Device.get_authorized_users(device_id)
app.ee.emit('device-updated-keys', {"device_id": device_id})
return {"keys": data}


@device_api.route("/devices/<device_id>/log_operation", methods=["POST"])
def log_operation(device_id):

json_data = request.get_json()
if not json_data:
raise Exception("Invalid request, no JSON data received")
# logging.info("Received request: " + str(json_data))

operation = json_data["operation"]
if operation not in ["lock", "unlock", "deny_access"]:
raise Exception("Invalid operation")

user_key = json_data["key"]

if (operation == "unlock") and user_key is None:
raise Exception("Invalid operation")

AccessLog.add(device_id, user_key, operation)
app.ee.emit('access-log-entry-added', {"device_id": device_id,
"user_key": user_key, "operation": operation})
return "OK", 201
192 changes: 192 additions & 0 deletions app/api/web_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# pylint: disable=consider-using-f-string
# pylint: disable=broad-except
import json

from flask import Blueprint, jsonify, request
from flask import current_app as app
from models.access_log import AccessLog
from models.device import Device
from models.user import User

web_api = Blueprint("web_api", __name__)


# Logs API


@web_api.route("/api/logs", methods=["GET"])
def api_get_logs():
# Retrieve parameters from the query string
start_time = request.args.get("start_time", default=None)
end_time = request.args.get("end_time", default=None)
limit = request.args.get("limit", default=100, type=int)
offset = request.args.get("offset", default=0, type=int)

return jsonify(AccessLog.get_full_log(start_time, end_time, limit, offset))


# Devices API


@web_api.route("/api/devices/latest_key", methods=["GET"])
def api_get_latest_key():
return jsonify(Device.get_latest_key())


@web_api.route("/api/devices", methods=["GET"])
def api_get_devices():
return jsonify(Device.get_all_devices())


@web_api.route("/api/devices", methods=["POST"])
def api_add_device():
device_data = request.get_json()

try:
device = Device(device_data["device_id"], device_data.get(
"device_name"), device_data["device_type"])
device.save()
app.logger.info("Device %s added successfully" % device_data["device_id"])
return jsonify({"message": "Device added successfully"}), 201
except Exception as e:
app.logger.error("Error adding device: %s" % e)
return jsonify({"message": "Error adding device"}), 303


@web_api.route("/api/devices/<device_id>", methods=["PUT"])
def api_update_device(device_id):
device_data = request.get_json()

device = Device(device_id=device_id, device_type=None, name=None)
device.update_device(device_data.get("device_type", None), device_data.get("name", None))

return jsonify({"message": "Device updated successfully"})


@web_api.route("/api/devices/<device_id>", methods=["DELETE"])
def api_remove_device(device_id):
try:
device = Device(device_id=device_id, device_type=None, name=None)
device.delete()
app.logger.info("Device %s removed successfully" % device_id)
return jsonify({"message": "Device removed successfully"}), 200
except Exception as e:
app.logger.error("Error removing device: %s" % e)
return jsonify({"message": "Error removing device"}), 303


# Users API

@web_api.route("/api/users", methods=["GET"])
def api_get_user_permissions():
return jsonify(User.get_permissions())


def api_add_user():
user_data = request.get_json()

user = User(user_data["name"], user_data["key"], user_data.get("slack_id"))

number_of_new_user_added = user.save()

# pylint: disable=no-else-return
if number_of_new_user_added == 1:
return jsonify({"message": "User added successfully"}), 201
elif number_of_new_user_added == 0:
return jsonify({"message": "User already exists"}), 303

# Handle other cases (e.g., database error)
return jsonify({"message": "An error occurred"}), 500


@web_api.route("/api/users/<user_key>", methods=["DELETE"])
def api_delete_user(user_key):
user = User(key=user_key, name=None)
user.delete()

return jsonify({"message": "User deleted successfully"})


@web_api.route("/api/users/<user_key>/devices/<device_id>", methods=["POST"])
def api_add_user_permission(user_key, device_id):
user = User(key=user_key, name=None)
user.add_permission(device_id)

return jsonify({"message": "User permission added successfully"})


@web_api.route("/api/users/<user_key>/devices/<device_id>", methods=["DELETE"])
def api_remove_user_permission(user_key, device_id):
user = User(key=user_key, name=None)
user.remove_permission(device_id)

return jsonify({"message": "User permission removed successfully"})


# Settings API


@web_api.route("/api/settings", methods=["GET"])
def api_get_settings():
"""
Returns the current settings of the system.

Returns:
dict: The current settings of prismo.

Example:
{
"device_type": "printer",
"name": "Printer 1"
}

Raises:
Exception: If the settings cannot be retrieved.

"""
try:
settings = app.config["PRISMO"]
app.logger.info("Retrieved settings: %s", settings)
return jsonify(settings)
except Exception as e:
app.logger.error("Error retrieving settings: %s", e)
raise Exception("Error retrieving settings") from e


@web_api.route("/api/settings", methods=["PUT"])
def api_update_settings():
"""
Updates the current settings of the system.

Returns:
dict: The updated settings.
"""

try:
settings = app.config["PRISMO"]
new_settings = json.loads(request.data)
app.logger.info("New settings received: %s", new_settings)
app.config["PRISMO"] = new_settings

app.logger.info("Updated settings: %s", settings)
# Update "PRISMO" branch in settings file
try:
with open(app.config["PRISMO"]["CURRENT_CONFIG_FILE"], "r") as f:
stored_settings = json.load(f)
with open(app.config["PRISMO"]["CURRENT_CONFIG_FILE"], "w") as f:
stored_settings["PRISMO"] = new_settings
json.dump(stored_settings, f, indent=4)
app.logger.warning("Settings updated, new settings are: %s", stored_settings)
except Exception as e:
app.logger.error("Error saving settings to file: %s", e)

return jsonify(settings)
except Exception as e:
app.logger.error("Error updating settings: %s", e)
raise Exception("Error updating settings") from e

# websocket = Sock(app)
# @websocket.route('/updater_socket')
# def updater(websocket):
# # pylint: disable=redefined-outer-name
# update_firmware_full(websocket)
119 changes: 119 additions & 0 deletions app/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import json

from flask import Flask, render_template, request, redirect, url_for
from flask_login import (
LoginManager,
login_user,
logout_user,
login_required,
current_user,
)
from pyee.base import EventEmitter

from api.device_api import device_api
from api.web_api import web_api
from models.admin_user import AdminUser
from plugins.slack_notifier import SlackNotifierPlugin

app = Flask(__name__)
app.ee = EventEmitter()

app.register_blueprint(web_api)
app.register_blueprint(device_api)

app.config.from_file("config_debug.json", load=json.load)

login_manager = LoginManager()
login_manager.init_app(app)

# Plugins
slack_notifier = SlackNotifierPlugin(app.app_context())


@login_manager.user_loader
def load_user(user_id):
# pylint: disable=unused-argument
return AdminUser("", "")


@app.route("/init_app", methods=["GET", "POST"])
def init_app_route():
if request.method == "GET":
return render_template("auth/init_app.html")

username = request.form["username"]
password = request.form["password"]
AdminUser(username, password).create_user()

# if "file" in request.files:
# file = request.files["file"]
# else:
# file = None

# init_app(username, password, slat, file)

return redirect(url_for("login"))


@app.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
return redirect(url_for("users"))

if request.method == "GET":
return render_template("auth/login.html")

username = request.form["username"]
password = request.form["password"]

user = AdminUser(username)
if user is None or not user.check_password(password):
app.logger.error("Auth failed: Invalid username or password")
return render_template("auth/login.html", error="Invalid username or password")

app.logger.info("Auth success: %s", username)
login_user(user)
return redirect(url_for("users"))


@app.route("/logout")
def logout():
app.logger.info("Logout: %s", current_user.username)
logout_user()
return redirect(url_for("login"))


# App routes


@app.route("/", methods=["GET"])
def index():
# if not database_file.is_file():
# return flask.redirect(flask.url_for('admin.init_app_route'))
if current_user.is_authenticated:
return redirect(url_for("users"))
return redirect(url_for("login"))


@app.route("/users", methods=["GET"])
@login_required
def users():
return render_template("prismo/users.html")


@app.route("/devices")
@login_required
def devices():
return render_template("prismo/devices.html")


@app.route("/logs")
@login_required
def logs():
return render_template("prismo/logs.html")


@app.route("/settings", methods=["GET", "POST"])
@login_required
def settings():
return render_template("prismo/settings.html")
Loading