Role management, login logic, and debug improvements. MariaDB login now uses correct syntax.

This commit is contained in:
2025-09-11 22:30:52 +03:00
parent b37c8bb58f
commit 9fc32adb23
18 changed files with 442 additions and 78 deletions

Binary file not shown.

View File

@@ -0,0 +1,45 @@
import sqlite3
import os
def create_roles_and_users_tables(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create users table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL
)
''')
# Insert superadmin user if not exists (default password: 'admin', change after first login)
cursor.execute('''
INSERT OR IGNORE INTO users (username, password, role)
VALUES (?, ?, ?)
''', ('superadmin', 'superadmin123', 'superadmin'))
# Create roles table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
access_level TEXT NOT NULL,
description TEXT
)
''')
# Insert superadmin role if not exists
cursor.execute('''
INSERT OR IGNORE INTO roles (name, access_level, description)
VALUES (?, ?, ?)
''', ('superadmin', 'full', 'Full access to all app areas and functions'))
conn.commit()
conn.close()
if __name__ == "__main__":
# Default path to users.db in instance folder
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
if not os.path.exists(instance_folder):
os.makedirs(instance_folder)
db_path = os.path.join(instance_folder, 'users.db')
create_roles_and_users_tables(db_path)
print("Roles and users tables created. Superadmin user and role initialized.")

View File

@@ -0,0 +1,26 @@
import mariadb
import os
def get_external_db_connection():
settings_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance/external_server.conf'))
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
if __name__ == "__main__":
conn = get_external_db_connection()
cursor = conn.cursor()
cursor.execute("DROP TABLE IF EXISTS users")
cursor.execute("DROP TABLE IF EXISTS roles")
conn.commit()
conn.close()
print("Dropped users and roles tables from external database.")

View File

@@ -0,0 +1,30 @@
import sqlite3
import os
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
db_path = os.path.join(instance_folder, 'users.db')
if not os.path.exists(db_path):
print("users.db not found at", db_path)
exit(1)
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if users table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
if not cursor.fetchone():
print("No users table found in users.db.")
conn.close()
exit(1)
# Print all users
cursor.execute("SELECT id, username, password, role FROM users")
rows = cursor.fetchall()
if not rows:
print("No users found in users.db.")
else:
print("Users in users.db:")
for row in rows:
print(f"id={row[0]}, username={row[1]}, password={row[2]}, role={row[3]}")
conn.close()

View File

@@ -0,0 +1,34 @@
import sqlite3
import os
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key' # Use the same key as in __init__.py
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
if not os.path.exists(instance_folder):
os.makedirs(instance_folder)
db_path = os.path.join(instance_folder, 'users.db')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create users table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL
)
''')
# Insert superadmin user if not exists
cursor.execute('''
INSERT OR IGNORE INTO users (username, password, role)
VALUES (?, ?, ?)
''', ('superadmin', 'superadmin123', 'superadmin'))
conn.commit()
conn.close()
print("Internal users.db seeded with superadmin user.")

View File

@@ -9,9 +9,17 @@ from reportlab.pdfgen import canvas
from flask import Blueprint, render_template, request, redirect, url_for, flash
import csv
from .warehouse import add_location
from .settings import settings_handler, edit_access_roles_handler
bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__)
bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__)
@bp.route('/update_role_access/<role>', methods=['POST'])
def update_role_access(role):
from .settings import update_role_access_handler
return update_role_access_handler(role)
@bp.route('/store_articles')
def store_articles():
@@ -48,40 +56,47 @@ def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username, password=password).first()
user = None
print("Raw form input:", repr(username), repr(password))
# Only check external MariaDB for user authentication
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SHOW TABLES LIKE 'users'")
if cursor.fetchone():
cursor.execute("SELECT username, password, role FROM users WHERE username=%s AND password=%s", (username.strip(), password.strip()))
row = cursor.fetchone()
print("External DB query result:", row)
if row:
user = {'username': row[0], 'password': row[1], 'role': row[2]}
conn.close()
except Exception as e:
print("External DB error:", e)
if user:
session['user'] = user.username
session['role'] = user.role
session['user'] = user['username']
session['role'] = user['role']
print("Logged in as:", session.get('user'), session.get('role'))
return redirect(url_for('main.dashboard'))
else:
print("Login failed for:", username, password)
flash('Invalid credentials. Please try again.')
return render_template('login.html')
@bp.route('/dashboard')
def dashboard():
print("Session user:", session.get('user'), session.get('role'))
if 'user' not in session:
return redirect(url_for('main.login'))
return render_template('dashboard.html')
@bp.route('/settings')
def settings():
if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.')
return redirect(url_for('main.dashboard'))
return settings_handler()
# Fetch all users from the database
users = User.query.all()
# Load external database settings from the instance folder
external_settings = {}
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
if (os.path.exists(settings_file)):
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
external_settings[key] = value
return render_template('settings.html', users=users, external_settings=external_settings)
# Route for editing access roles (superadmin only)
@bp.route('/edit_access_roles')
def edit_access_roles():
return edit_access_roles_handler()
@bp.route('/quality')
def quality():

76
py_app/app/settings.py Normal file
View File

@@ -0,0 +1,76 @@
from flask import render_template, request, session, redirect, url_for, flash
from .models import User
from . import db
# Settings module logic
import sqlite3
import os
def ensure_roles_table():
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
if not os.path.exists(instance_folder):
os.makedirs(instance_folder)
db_path = os.path.join(instance_folder, 'users.db')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
access_level TEXT NOT NULL,
description TEXT
)
""")
cursor.execute("""
INSERT OR IGNORE INTO roles (name, access_level, description)
VALUES (?, ?, ?)
""", ('superadmin', 'full', 'Full access to all app areas and functions'))
conn.commit()
conn.close()
# List of roles (should match your app's roles)
ROLES = [
'superadmin', 'admin', 'manager', 'warehouse_manager', 'warehouse_worker', 'quality_manager', 'quality_worker'
]
# Helper to check if current user is superadmin
def is_superadmin():
return session.get('role') == 'superadmin'
# Route handler for editing access roles
def edit_access_roles_handler():
if not is_superadmin():
flash('Access denied: Superadmin only.')
return redirect(url_for('main.dashboard'))
ensure_roles_table()
return render_template('edit_access_roles.html', roles=ROLES)
# Handler for updating role access (stub, to be implemented)
def update_role_access_handler(role):
if not is_superadmin():
flash('Access denied: Superadmin only.')
return redirect(url_for('main.dashboard'))
if role == 'superadmin':
flash('Superadmin access cannot be changed.')
return redirect(url_for('main.edit_access_roles'))
access_level = request.form.get('access_level')
# TODO: Save access_level for the role in the database or config
flash(f'Access for role {role} updated to {access_level}.')
return redirect(url_for('main.edit_access_roles'))
def settings_handler():
if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.')
return redirect(url_for('main.dashboard'))
users = User.query.all()
# Load external database settings from the instance folder
external_settings = {}
import os
from flask import current_app
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
if os.path.exists(settings_file):
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
external_settings[key] = value
return render_template('settings.html', users=users, external_settings=external_settings)
# Add more settings-related functions here as needed

View File

@@ -25,6 +25,22 @@
<a href="{{ url_for('warehouse.import_locations_csv') }}" class="btn" style="padding: 4px 12px; font-size: 0.95em;">Go to Import Page</a>
</div>
</div>
<!-- Delete Location Area -->
{% if session['role'] in ['administrator', 'management'] %}
<div style="margin-top: 32px; padding: 12px; border-top: 1px solid #eee;">
<label style="font-weight:bold;">Delete location from table</label>
<div style="font-size:0.95em; margin-bottom:8px;">To delete a location, enter the ID of the location and press delete.<br>To delete 2 or multiple locations, enter the IDs separated by "," and then press delete.</div>
<form method="POST" style="display:flex; gap:8px; align-items:center;" onsubmit="return confirmDeleteLocations();">
<input type="text" name="delete_ids" placeholder="e.g. 5,7,12" style="width:160px;">
<button type="submit" name="delete_locations" value="1" class="btn" style="padding:4px 16px;">Delete Locations</button>
</form>
<script>
function confirmDeleteLocations() {
return confirm('Do you really want to delete the selected locations?');
}
</script>
</div>
{% endif %}
</div>
<!-- Locations Table Card -->
<div class="card scan-table-card">

View File

@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block title %}Edit Access Roles{% endblock %}
{% block content %}
<div class="card" style="max-width: 700px; margin: 32px auto;">
<h3>Role Access Management</h3>
<p>Configure which roles can view or execute functions on each app page and feature.</p>
<table class="scan-table" style="width:100%;">
<thead>
<tr>
<th>Role</th>
<th>Access Level</th>
<th>Editable</th>
</tr>
</thead>
<tbody>
<tr>
<td>superadmin</td>
<td>Full access to all pages and functions</td>
<td><span style="color:#888;">Not editable</span></td>
</tr>
{% for role in roles %}
{% if role != 'superadmin' %}
<tr>
<td>{{ role }}</td>
<td>
<form method="POST" action="{{ url_for('main.update_role_access', role=role) }}">
<select name="access_level">
<option value="view">View Only</option>
<option value="execute">View & Execute</option>
<option value="none">No Access</option>
</select>
<button type="submit" class="btn">Save</button>
</form>
</td>
<td>Editable</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<p style="margin-top:16px; color:#888;">Only superadmin users can view and manage role access.</p>
</div>
{% endblock %}

View File

@@ -37,9 +37,9 @@
</div>
</form>
<!-- Popup Modal -->
<div id="popup-modal" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.3); z-index:9999; align-items:center; justify-content:center;">
<div style="background:#fff; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; text-align:center;">
<h3>Performing the creation of the warehouse locations</h3>
<div id="popup-modal" class="popup" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:var(--app-overlay-bg, rgba(30,41,59,0.85)); z-index:9999; align-items:center; justify-content:center;">
<div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;">
<h3 style="color:var(--app-label-text);">Performing the creation of the warehouse locations</h3>
</div>
</div>
<script>

View File

@@ -8,10 +8,10 @@
<h3>Manage Users</h3>
<ul class="user-list">
{% for user in users %}
<li data-user-id="{{ user.id }}">
<li data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">
<span class="user-name">{{ user.username }}</span>
<span class="user-role">Role: {{ user.role }}</span>
<button class="btn edit-btn">Edit Rights</button>
<button class="btn edit-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">Edit User</button>
<button class="btn delete-btn">Delete User</button>
</li>
{% endfor %}
@@ -35,51 +35,37 @@
<button type="submit" class="btn">Save/Update External Database Info Settings</button>
</form>
</div>
<div class="card" style="margin-top: 32px;">
<h3>Edit Access Roles</h3>
<p>Manage which roles can view or execute functions on each app page and feature.</p>
<a href="{{ url_for('main.edit_access_roles') }}" class="btn">Edit Access Roles</a>
</div>
</div>
<!-- Popup for creating a new user -->
<div id="create-user-popup" class="popup">
<div class="popup-content">
<h3>Create User</h3>
<form id="create-user-form" method="POST" action="{{ url_for('main.create_user') }}">
<!-- Popup for creating/editing a user -->
<div id="user-popup" class="popup" style="display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; background:var(--app-overlay-bg, rgba(30,41,59,0.85)); z-index:9999; align-items:center; justify-content:center;">
<div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;">
<h3 id="user-popup-title">Create/Edit User</h3>
<form id="user-form" method="POST" action="{{ url_for('main.create_user') }}">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="email">Email Address:</label>
<input type="email" id="email" name="email" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<label for="role">Role:</label>
<select id="role" name="role" required>
<option value="superadmin">Superadmin</option>
<option value="administrator">Administrator</option>
<option value="quality">Quality</option>
<option value="warehouse">Warehouse</option>
<option value="scan">Scan</option>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
<option value="warehouse_manager">Warehouse Manager</option>
<option value="warehouse_worker">Warehouse Worker</option>
<option value="quality_manager">Quality Manager</option>
<option value="quality_worker">Quality Worker</option>
</select>
<button type="submit" class="btn">Create</button>
<button type="button" id="close-popup-btn" class="btn cancel-btn">Cancel</button>
</form>
</div>
</div>
<!-- Popup for editing a user -->
<div id="edit-user-popup" class="popup">
<div class="popup-content">
<h3>Edit User</h3>
<form id="edit-user-form" method="POST" action="{{ url_for('main.edit_user') }}">
<input type="hidden" id="edit-user-id" name="user_id">
<label for="edit-username">Username:</label>
<input type="text" id="edit-username" name="username" readonly>
<label for="edit-password">New Password:</label>
<input type="password" id="edit-password" name="password">
<label for="edit-role">Role:</label>
<select id="edit-role" name="role" required>
<option value="superadmin">Superadmin</option>
<option value="administrator">Administrator</option>
<option value="quality">Quality</option>
<option value="warehouse">Warehouse</option>
<option value="scan">Scan</option>
</select>
<button type="submit" class="btn">Update</button>
<button type="button" id="close-edit-popup-btn" class="btn cancel-btn">Cancel</button>
<button type="submit" class="btn">Save</button>
<button type="button" id="close-user-popup-btn" class="btn cancel-btn">Cancel</button>
</form>
</div>
</div>
@@ -95,4 +81,27 @@
</form>
</div>
</div>
<script>
document.getElementById('create-user-btn').onclick = function() {
document.getElementById('user-popup').style.display = 'flex';
document.getElementById('user-popup-title').innerText = 'Create User';
document.getElementById('user-form').reset();
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.create_user") }}');
};
document.getElementById('close-user-popup-btn').onclick = function() {
document.getElementById('user-popup').style.display = 'none';
};
// Edit User button logic
Array.from(document.getElementsByClassName('edit-user-btn')).forEach(function(btn) {
btn.onclick = function() {
document.getElementById('user-popup').style.display = 'flex';
document.getElementById('user-popup-title').innerText = 'Edit User';
document.getElementById('username').value = btn.getAttribute('data-username');
document.getElementById('email').value = btn.getAttribute('data-email');
document.getElementById('role').value = btn.getAttribute('data-role');
document.getElementById('password').value = '';
document.getElementById('user-form').setAttribute('action', '/edit_user/' + btn.getAttribute('data-user-id'));
};
});
</script>
{% endblock %}

View File

@@ -76,13 +76,32 @@ def recreate_warehouse_locations_table():
conn.commit()
conn.close()
def delete_locations_by_ids(ids_str):
ids = [id.strip() for id in ids_str.split(',') if id.strip().isdigit()]
if not ids:
return "No valid IDs provided."
conn = get_db_connection()
cursor = conn.cursor()
deleted = 0
for id in ids:
cursor.execute("DELETE FROM warehouse_locations WHERE id = ?", (id,))
if cursor.rowcount:
deleted += 1
conn.commit()
conn.close()
return f"Deleted {deleted} location(s)."
def create_locations_handler():
message = None
if request.method == "POST":
location_code = request.form.get("location_code")
size = request.form.get("size")
description = request.form.get("description")
message = add_location(location_code, size, description)
if request.form.get("delete_locations"):
ids_str = request.form.get("delete_ids", "")
message = delete_locations_by_ids(ids_str)
else:
location_code = request.form.get("location_code")
size = request.form.get("size")
description = request.form.get("description")
message = add_location(location_code, size, description)
locations = get_locations()
return render_template("create_locations.html", locations=locations, message=message)

Binary file not shown.

View File

@@ -4,19 +4,9 @@ from app.models import User
app = create_app()
with app.app_context():
# Add default users
users = [
User(username='superadmin', password='superadmin123', role='superadmin'),
User(username='admin', password='admin123', role='administrator'),
User(username='quality_user', password='quality123', role='quality'),
User(username='warehouse_user', password='warehouse123', role='warehouse'),
User(username='scan_user', password='scan123', role='scan'),
]
# Add users to the database
for user in users:
if not User.query.filter_by(username=user.username).first():
db.session.add(user)
# Add only the superadmin user
user = User(username='superadmin', password='superadmin123', role='superadmin')
if not User.query.filter_by(username=user.username).first():
db.session.add(user)
db.session.commit()
print("Database seeded with default users.")
print("Database seeded with only the superadmin user.")

61
py_app/static/style.css Normal file
View File

@@ -0,0 +1,61 @@
/* Theme variables for popup and cards */
:root {
--app-overlay-bg: rgba(30,41,59,0.85);
--app-card-bg: #f8fafc;
--app-card-text: #1e293b;
--app-input-bg: #e2e8f0;
--app-input-text: #1e293b;
--app-label-text: #334155;
}
body.light-mode {
background: #f8fafc;
color: #1e293b;
}
body.dark-mode {
background: #1e293b;
color: #f8fafc;
}
.popup {
background: var(--app-overlay-bg) !important;
}
.popup-content {
background: var(--app-card-bg) !important;
color: var(--app-card-text) !important;
}
.popup-content label,
#user-popup-title {
color: var(--app-label-text) !important;
}
.popup-content input,
.popup-content select {
background: var(--app-input-bg) !important;
color: var(--app-input-text) !important;
border: 1px solid #cbd5e1;
border-radius: 4px;
padding: 8px;
margin-bottom: 12px;
width: 90%;
font-size: 1em;
}
.card {
background: var(--app-card-bg);
color: var(--app-card-text);
box-shadow: 0 2px 8px #333;
border-radius: 8px;
}
.btn {
background: #1e293b;
color: #f8fafc;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
}
.btn.cancel-btn {
background: #e11d48;
color: #fff;
}