Major updates: Setup environment, database, permissions and admin access

- Created virtual environment and installed requirements
- Set up MariaDB database with user 'sa' and database 'recticel'
- Created order_for_labels table for printing functionality
- Implemented role-based permissions system with admin role
- Added admin access to print label modules and etichete functionality
- Fixed template routing issues in main_page_etichete.html
- Updated app to run on port 8781 with network access (0.0.0.0)
- Added systemd service file for background deployment
- Created installation documentation
This commit is contained in:
scheianu ionut
2025-10-07 22:07:06 +03:00
parent 957c8eca4d
commit a8811b94b7
11 changed files with 170 additions and 136 deletions

25
app.log Normal file
View File

@@ -0,0 +1,25 @@
nohup: ignoring input
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8781
* Running on http://192.168.0.132:8781
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 869-443-054
172.18.0.2 - - [07/Oct/2025 22:03:14] "GET /quality HTTP/1.1" 200 -
172.18.0.2 - - [07/Oct/2025 22:03:19] "GET /logout HTTP/1.1" 302 -
172.18.0.2 - - [07/Oct/2025 22:03:19] "GET / HTTP/1.1" 200 -
172.18.0.2 - - [07/Oct/2025 22:03:19] "GET /static/logo_login.jpg HTTP/1.1" 304 -
172.18.0.2 - - [07/Oct/2025 22:03:24] "POST / HTTP/1.1" 302 -
172.18.0.2 - - [07/Oct/2025 22:03:24] "GET /dashboard HTTP/1.1" 200 -
172.18.0.2 - - [07/Oct/2025 22:03:30] "GET /logout HTTP/1.1" 302 -
172.18.0.2 - - [07/Oct/2025 22:03:30] "GET / HTTP/1.1" 200 -
172.18.0.2 - - [07/Oct/2025 22:03:34] "POST / HTTP/1.1" 302 -
172.18.0.2 - - [07/Oct/2025 22:03:34] "GET /dashboard HTTP/1.1" 200 -
172.18.0.2 - - [07/Oct/2025 22:03:38] "GET /logout HTTP/1.1" 302 -
172.18.0.2 - - [07/Oct/2025 22:03:38] "GET / HTTP/1.1" 200 -
172.18.0.2 - - [07/Oct/2025 22:03:56] "POST / HTTP/1.1" 302 -
172.18.0.2 - - [07/Oct/2025 22:03:56] "GET /dashboard HTTP/1.1" 200 -

BIN
py_app/app/__pycache__/__init__.cpython-311.pyc Executable file → Normal file

Binary file not shown.

BIN
py_app/app/__pycache__/models.cpython-311.pyc Executable file → Normal file

Binary file not shown.

BIN
py_app/app/__pycache__/routes.cpython-311.pyc Executable file → Normal file

Binary file not shown.

View File

@@ -2,10 +2,10 @@ import mariadb
# Database connection credentials
DB_CONFIG = {
"user": "trasabilitate",
"password": "Initial01!",
"user": "sa",
"password": "12345678",
"host": "localhost",
"database": "trasabilitate_database"
"database": "recticel"
}
def recreate_order_for_labels_table():

View File

@@ -27,84 +27,8 @@ from .print_module import get_unprinted_orders_data
bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__)
def format_cell_data(cell):
"""Helper function to format cell data, especially dates and times"""
# Import date and datetime at the top of the function
from datetime import datetime, date
if isinstance(cell, datetime):
# Format datetime as dd/mm/yyyy
return cell.strftime('%d/%m/%Y')
elif isinstance(cell, date):
# Format date as dd/mm/yyyy
return cell.strftime('%d/%m/%Y')
elif isinstance(cell, timedelta):
# Convert timedelta to HH:MM:SS format
total_seconds = int(cell.total_seconds())
hours, remainder = divmod(total_seconds, 3600)
minutes, seconds = divmod(remainder, 60)
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
elif isinstance(cell, str):
# Handle string dates in yyyy-mm-dd or yyyy-mm-dd HH:MM:SS
import re
match = re.match(r'^(\d{4})-(\d{2})-(\d{2})(.*)$', cell)
if match:
year, month, day, rest = match.groups()
formatted = f"{day}/{month}/{year}"
if rest.strip():
# If there is a time part, keep it after the date
formatted += rest
return formatted
return cell
else:
return cell
@bp.route('/store_articles')
def store_articles():
return render_template('store_articles.html')
@bp.route('/get_pairing_keys')
def get_pairing_keys():
"""Return all pairing keys as JSON for client selection."""
keys_path = os.path.join(current_app.instance_path, 'pairing_keys.json')
try:
if os.path.exists(keys_path):
with open(keys_path, 'r') as f:
keys = json.load(f)
else:
keys = []
except Exception as e:
print(f"Error loading pairing keys: {e}")
return jsonify([]), 200
return jsonify(keys)
@bp.route('/warehouse_reports')
def warehouse_reports():
return render_template('warehouse_reports.html')
def get_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError("The external_server.conf file is missing in the instance folder.")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
# Create a database connection
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
@bp.route('/login', methods=['GET', 'POST'])
@bp.route('/', methods=['GET', 'POST'])
def login():
import sqlite3
if request.method == 'POST':
@@ -190,6 +114,83 @@ def login():
flash('Invalid credentials. Please try again.')
return render_template('login.html')
def format_cell_data(cell):
"""Helper function to format cell data, especially dates and times"""
# Import date and datetime at the top of the function
from datetime import datetime, date
if isinstance(cell, datetime):
# Format datetime as dd/mm/yyyy
return cell.strftime('%d/%m/%Y')
elif isinstance(cell, date):
# Format date as dd/mm/yyyy
return cell.strftime('%d/%m/%Y')
elif isinstance(cell, timedelta):
# Convert timedelta to HH:MM:SS format
total_seconds = int(cell.total_seconds())
hours, remainder = divmod(total_seconds, 3600)
minutes, seconds = divmod(remainder, 60)
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
elif isinstance(cell, str):
# Handle string dates in yyyy-mm-dd or yyyy-mm-dd HH:MM:SS
import re
match = re.match(r'^(\d{4})-(\d{2})-(\d{2})(.*)$', cell)
if match:
year, month, day, rest = match.groups()
formatted = f"{day}/{month}/{year}"
if rest.strip():
# If there is a time part, keep it after the date
formatted += rest
return formatted
return cell
else:
return cell
@bp.route('/store_articles')
def store_articles():
return render_template('store_articles.html')
@bp.route('/get_pairing_keys')
def get_pairing_keys():
"""Return all pairing keys as JSON for client selection."""
keys_path = os.path.join(current_app.instance_path, 'pairing_keys.json')
try:
if os.path.exists(keys_path):
with open(keys_path, 'r') as f:
keys = json.load(f)
else:
keys = []
except Exception as e:
print(f"Error loading pairing keys: {e}")
return jsonify([]), 200
return jsonify(keys)
@bp.route('/warehouse_reports')
def warehouse_reports():
return render_template('warehouse_reports.html')
def get_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError("The external_server.conf file is missing in the instance folder.")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
# Create a database connection
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
@bp.route('/dashboard')
def dashboard():
print("Session user:", session.get('user'), session.get('role'))
@@ -203,21 +204,21 @@ def settings():
@bp.route('/quality')
def quality():
if 'role' not in session or session['role'] not in ['superadmin', 'quality']:
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'quality']:
flash('Access denied: Quality users only.')
return redirect(url_for('main.dashboard'))
return render_template('quality.html')
@bp.route('/warehouse')
def warehouse():
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse']:
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'admin', 'warehouse']:
flash('Access denied: Warehouse users only.')
return redirect(url_for('main.dashboard'))
return render_template('main_page_warehouse.html')
@bp.route('/scan', methods=['GET', 'POST'])
def scan():
if 'role' not in session or session['role'] not in ['superadmin', 'scan']:
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'admin', 'scan']:
flash('Access denied: Scan users only.')
return redirect(url_for('main.dashboard'))
@@ -952,14 +953,14 @@ def test_database():
@bp.route('/etichete')
def etichete():
if 'role' not in session or session['role'] not in ['superadmin', 'etichete']:
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'etichete']:
flash('Access denied: Etichete users only.')
return redirect(url_for('main.dashboard'))
return render_template('main_page_etichete.html')
@bp.route('/upload_data')
def upload_data():
return render_template('upload_data.html')
return render_template('upload_orders.html')
@bp.route('/print_module')
def print_module():
@@ -1817,7 +1818,7 @@ def create_template():
@bp.route('/get_database_tables', methods=['GET'])
def get_database_tables():
"""Get list of database tables for template creation"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied'}), 403
try:
@@ -1867,7 +1868,7 @@ def get_database_tables():
@bp.route('/get_table_columns/<table_name>', methods=['GET'])
def get_table_columns(table_name):
"""Get column names and descriptions for a specific table"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied'}), 403
try:
@@ -2002,41 +2003,7 @@ def generate_pdf():
return jsonify({'message': 'PDF generated successfully!', 'pdf_path': f'/static/label_templates/label_template.pdf'})
# Order Labels Upload Module Routes
@bp.route('/upload_orders', methods=['GET', 'POST'])
def upload_orders():
"""Route for uploading orders CSV files for label generation"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse', 'warehouse_manager']:
flash('Access denied: Warehouse management permissions required.')
return redirect(url_for('main.dashboard'))
from app.order_labels import upload_orders_handler
return upload_orders_handler()
@bp.route('/view_orders')
def view_orders():
"""Route for viewing uploaded orders"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse', 'warehouse_manager', 'warehouse_worker']:
flash('Access denied: Warehouse access required.')
return redirect(url_for('main.dashboard'))
from app.order_labels import get_orders_from_database
orders = get_orders_from_database(200) # Get last 200 orders
return render_template('view_orders.html', orders=orders)
@bp.route('/print_lost_labels')
def print_lost_labels():
"""Route for printing lost labels - allows searching and reprinting specific orders"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse', 'warehouse_manager', 'warehouse_worker']:
flash('Access denied: Warehouse access required.')
return redirect(url_for('main.dashboard'))
from app.order_labels import get_orders_from_database
orders = get_orders_from_database(500) # Get more orders for searching
return render_template('print_lost_labels.html', orders=orders)
@bp.route('/db_test')
def db_test():
"""Simple database test page"""
# ...existing code...
return render_template('db_test.html')
@bp.route('/get_unprinted_orders', methods=['GET'])
@@ -2067,9 +2034,9 @@ def generate_labels_pdf(order_id, paper_saving_mode='true'):
"""Generate PDF labels for a specific order"""
print(f"DEBUG: generate_labels_pdf called for order_id: {order_id}")
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
print(f"DEBUG: Access denied for role: {session.get('role')}")
return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403
return jsonify({'error': 'Access denied. Required roles: superadmin, admin, administrator, warehouse_manager, etichete'}), 403
try:
from .pdf_generator import generate_order_labels_pdf, update_order_printed_status
@@ -2148,8 +2115,8 @@ def generate_labels_pdf(order_id, paper_saving_mode='true'):
def generate_label_pdf():
"""Generate a single label PDF for thermal printing via QZ Tray"""
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied. Required roles: superadmin, admin, administrator, warehouse_manager, etichete'}), 403
try:
from .pdf_generator import LabelPDFGenerator
@@ -2197,9 +2164,9 @@ def update_printed_status(order_id):
"""Update printed status for direct printing (without PDF generation)"""
print(f"DEBUG: update_printed_status called for order_id: {order_id}")
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
print(f"DEBUG: Access denied for role: {session.get('role')}")
return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403
return jsonify({'error': 'Access denied. Required roles: superadmin, admin, administrator, warehouse_manager, etichete'}), 403
try:
from .pdf_generator import update_order_printed_status
@@ -2312,7 +2279,7 @@ def get_order_data(order_id):
"""Get specific order data for preview"""
print(f"DEBUG: get_order_data called for order_id: {order_id}")
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
return jsonify({'error': 'Access denied'}), 403
try:

View File

@@ -14,8 +14,8 @@
<h3>View Orders</h3>
<p>Upload new orders or view existing orders and manage label data for printing.</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="{{ url_for('main.upload_orders') }}" class="btn">Upload Orders</a>
<a href="{{ url_for('main.view_orders') }}" class="btn">View Orders</a>
<a href="{{ url_for('main.upload_data') }}" class="btn">Upload Orders</a>
<a href="{{ url_for('main.get_unprinted_orders') }}" class="btn">View Orders</a>
</div>
</div>
@@ -25,7 +25,7 @@
<p>Access the print module to print labels.</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="{{ url_for('main.print_module') }}" class="btn">Launch Printing Module</a>
<a href="{{ url_for('main.print_lost_labels') }}" class="btn">Launch lost labels printing module</a>
<a href="{{ url_for('main.print_module') }}" class="btn">Launch lost labels printing module</a>
</div>
</div>

View File

@@ -1,5 +1,5 @@
server_domain=localhost
port=3602
database_name=trasabilitate_database
username=trasabilitate
password=Initial01!
database_name=recticel
username=sa
password=12345678

View File

@@ -3,4 +3,4 @@ from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
app.run(debug=True, port=8781, host='0.0.0.0')

15
recticel-app.service Normal file
View File

@@ -0,0 +1,15 @@
[Unit]
Description=Recticel Quality App
After=network.target mariadb.service
[Service]
Type=simple
User=ske087
WorkingDirectory=/home/ske087/quality_recticel
Environment=PATH=/home/ske087/quality_recticel/recticel/bin
ExecStart=/home/ske087/quality_recticel/recticel/bin/python py_app/run.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

27
to_do_install.txt Normal file
View File

@@ -0,0 +1,27 @@
# Steps to Prepare Environment for Installing Python Requirements
1. Change ownership of the project directory (if needed):
sudo chown -R $USER:$USER /home/ske087/quality_recticel
2. Install Python venv module:
sudo apt install -y python3-venv
3. Create and activate the virtual environment:
python3 -m venv recticel
source recticel/bin/activate
4. Install MariaDB server and development libraries:
sudo apt install -y mariadb-server libmariadb-dev
5. Create MariaDB database and user:
sudo mysql -e "CREATE DATABASE recticel; CREATE USER 'sa'@'localhost' IDENTIFIED BY '12345678'; GRANT ALL PRIVILEGES ON recticel.* TO 'sa'@'localhost'; FLUSH PRIVILEGES;"
6. Install build tools (for compiling Python packages):
sudo apt install -y build-essential
7. Install Python development headers:
sudo apt install -y python3-dev
8. Install Python requirements:
pip install -r py_app/requirements.txt