Compare commits
1 Commits
87469ecb8e
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| b87c398977 |
@@ -1,61 +0,0 @@
|
||||
# Git files
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
recticel/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Backup files
|
||||
backup/
|
||||
*.bak
|
||||
*.backup
|
||||
*.old
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Application specific
|
||||
instance/*.db
|
||||
chrome_extension/
|
||||
VS code/
|
||||
tray/
|
||||
|
||||
# Scripts not needed in container
|
||||
*.sh
|
||||
!docker-entrypoint.sh
|
||||
|
||||
# Service files
|
||||
*.service
|
||||
|
||||
# Config that will be generated
|
||||
instance/external_server.conf
|
||||
13
.env.example
13
.env.example
@@ -1,13 +0,0 @@
|
||||
# Environment Configuration for Recticel Quality Application
|
||||
# Copy this file to .env and adjust the values as needed
|
||||
|
||||
# Database Configuration
|
||||
MYSQL_ROOT_PASSWORD=rootpassword
|
||||
DB_PORT=3306
|
||||
|
||||
# Application Configuration
|
||||
APP_PORT=8781
|
||||
|
||||
# Initialization Flags (set to "false" after first successful deployment)
|
||||
INIT_DB=true
|
||||
SEED_DB=true
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -28,19 +28,4 @@ VS code/obj/
|
||||
|
||||
# Backup files
|
||||
*.backup
|
||||
|
||||
# Docker deployment
|
||||
.env
|
||||
*.env
|
||||
!.env.example
|
||||
logs/
|
||||
*.log
|
||||
app.log
|
||||
backup_*.sql
|
||||
instance/external_server.conf
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
.docker/
|
||||
|
||||
*.backup2
|
||||
|
||||
41
Dockerfile
41
Dockerfile
@@ -1,41 +0,0 @@
|
||||
# Dockerfile for Recticel Quality Application
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
FLASK_APP=run.py \
|
||||
FLASK_ENV=production
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
default-libmysqlclient-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy requirements and install Python dependencies
|
||||
COPY py_app/requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY py_app/ .
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p /app/instance /srv/quality_recticel/logs
|
||||
|
||||
# Create a script to wait for database and initialize
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 8781
|
||||
|
||||
# Use the entrypoint script
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
# Run gunicorn
|
||||
CMD ["gunicorn", "--config", "gunicorn.conf.py", "wsgi:application"]
|
||||
93
Makefile
93
Makefile
@@ -1,93 +0,0 @@
|
||||
.PHONY: help build up down restart logs logs-web logs-db clean reset shell shell-db status health
|
||||
|
||||
help: ## Show this help message
|
||||
@echo "Recticel Quality Application - Docker Commands"
|
||||
@echo ""
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
build: ## Build the Docker images
|
||||
docker-compose build
|
||||
|
||||
up: ## Start all services
|
||||
docker-compose up -d
|
||||
@echo "✅ Services started. Access the app at http://localhost:8781"
|
||||
@echo "Default login: superadmin / superadmin123"
|
||||
|
||||
down: ## Stop all services
|
||||
docker-compose down
|
||||
|
||||
restart: ## Restart all services
|
||||
docker-compose restart
|
||||
|
||||
logs: ## View logs from all services
|
||||
docker-compose logs -f
|
||||
|
||||
logs-web: ## View logs from web application
|
||||
docker-compose logs -f web
|
||||
|
||||
logs-db: ## View logs from database
|
||||
docker-compose logs -f db
|
||||
|
||||
status: ## Show status of all services
|
||||
docker-compose ps
|
||||
|
||||
health: ## Check health of services
|
||||
@echo "=== Service Health Status ==="
|
||||
@docker inspect recticel-app | grep -A 5 '"Health"' || echo "Web app: Running"
|
||||
@docker inspect recticel-db | grep -A 5 '"Health"' || echo "Database: Running"
|
||||
|
||||
shell: ## Open shell in web application container
|
||||
docker-compose exec web bash
|
||||
|
||||
shell-db: ## Open MariaDB console
|
||||
docker-compose exec db mariadb -u trasabilitate -p trasabilitate
|
||||
|
||||
clean: ## Stop services and remove containers (keeps data)
|
||||
docker-compose down
|
||||
|
||||
reset: ## Complete reset - removes all data including database
|
||||
@echo "⚠️ WARNING: This will delete all data!"
|
||||
@read -p "Are you sure? [y/N] " -n 1 -r; \
|
||||
echo; \
|
||||
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
|
||||
docker-compose down -v; \
|
||||
rm -rf logs/*; \
|
||||
rm -f instance/external_server.conf; \
|
||||
echo "✅ Reset complete"; \
|
||||
fi
|
||||
|
||||
deploy: build up ## Build and deploy (fresh start)
|
||||
@echo "✅ Deployment complete!"
|
||||
@sleep 5
|
||||
@make status
|
||||
|
||||
rebuild: ## Rebuild and restart web application
|
||||
docker-compose up -d --build web
|
||||
|
||||
backup-db: ## Backup database to backup.sql
|
||||
docker-compose exec -T db mariadb-dump -u trasabilitate -pInitial01! trasabilitate > backup_$(shell date +%Y%m%d_%H%M%S).sql
|
||||
@echo "✅ Database backed up"
|
||||
|
||||
restore-db: ## Restore database from backup.sql (provide BACKUP=filename)
|
||||
@if [ -z "$(BACKUP)" ]; then \
|
||||
echo "❌ Usage: make restore-db BACKUP=backup_20231215_120000.sql"; \
|
||||
exit 1; \
|
||||
fi
|
||||
docker-compose exec -T db mariadb -u trasabilitate -pInitial01! trasabilitate < $(BACKUP)
|
||||
@echo "✅ Database restored from $(BACKUP)"
|
||||
|
||||
install: ## Initial installation and setup
|
||||
@echo "=== Installing Recticel Quality Application ==="
|
||||
@if [ ! -f .env ]; then \
|
||||
cp .env.example .env; \
|
||||
echo "✅ Created .env file"; \
|
||||
fi
|
||||
@mkdir -p logs instance
|
||||
@echo "✅ Created directories"
|
||||
@make deploy
|
||||
@echo ""
|
||||
@echo "=== Installation Complete ==="
|
||||
@echo "Access the application at: http://localhost:8781"
|
||||
@echo "Default login: superadmin / superadmin123"
|
||||
@echo ""
|
||||
@echo "⚠️ Remember to change the default passwords!"
|
||||
88
deploy.sh
88
deploy.sh
@@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Quick deployment script for Recticel Quality Application
|
||||
|
||||
set -e
|
||||
|
||||
echo "================================================"
|
||||
echo " Recticel Quality Application"
|
||||
echo " Docker Deployment"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# Check if Docker is installed
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "❌ Docker is not installed. Please install Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Docker Compose is installed
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
echo "❌ Docker Compose is not installed. Please install Docker Compose first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Docker and Docker Compose are installed"
|
||||
echo ""
|
||||
|
||||
# Create .env if it doesn't exist
|
||||
if [ ! -f .env ]; then
|
||||
echo "Creating .env file from template..."
|
||||
cp .env.example .env
|
||||
echo "✅ Created .env file"
|
||||
echo "⚠️ Please review .env and update passwords before production use"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Create necessary directories
|
||||
echo "Creating necessary directories..."
|
||||
mkdir -p logs instance
|
||||
echo "✅ Directories created"
|
||||
echo ""
|
||||
|
||||
# Stop any existing services
|
||||
echo "Stopping any existing services..."
|
||||
docker-compose down 2>/dev/null || true
|
||||
echo ""
|
||||
|
||||
# Build and start services
|
||||
echo "Building Docker images..."
|
||||
docker-compose build
|
||||
echo ""
|
||||
|
||||
echo "Starting services..."
|
||||
docker-compose up -d
|
||||
echo ""
|
||||
|
||||
# Wait for services to be ready
|
||||
echo "Waiting for services to be ready..."
|
||||
sleep 10
|
||||
|
||||
# Check status
|
||||
echo ""
|
||||
echo "================================================"
|
||||
echo " Deployment Status"
|
||||
echo "================================================"
|
||||
docker-compose ps
|
||||
echo ""
|
||||
|
||||
# Show access information
|
||||
echo "================================================"
|
||||
echo " ✅ Deployment Complete!"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
echo "Application URL: http://localhost:8781"
|
||||
echo ""
|
||||
echo "Default Login Credentials:"
|
||||
echo " Username: superadmin"
|
||||
echo " Password: superadmin123"
|
||||
echo ""
|
||||
echo "⚠️ IMPORTANT: Change the default password after first login!"
|
||||
echo ""
|
||||
echo "Useful Commands:"
|
||||
echo " View logs: docker-compose logs -f"
|
||||
echo " Stop services: docker-compose down"
|
||||
echo " Restart: docker-compose restart"
|
||||
echo " Shell access: docker-compose exec web bash"
|
||||
echo ""
|
||||
echo "For more information, see DOCKER_DEPLOYMENT.md"
|
||||
echo ""
|
||||
@@ -1,77 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# MariaDB Database Service
|
||||
db:
|
||||
image: mariadb:11.3
|
||||
container_name: recticel-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
|
||||
MYSQL_DATABASE: trasabilitate
|
||||
MYSQL_USER: trasabilitate
|
||||
MYSQL_PASSWORD: Initial01!
|
||||
ports:
|
||||
- "${DB_PORT:-3306}:3306"
|
||||
volumes:
|
||||
- /srv/docker-test/mariadb:/var/lib/mysql
|
||||
- ./init-db.sql:/docker-entrypoint-initdb.d/01-init.sql
|
||||
networks:
|
||||
- recticel-network
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
# Flask Web Application Service
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: recticel-app
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# Database connection settings
|
||||
DB_HOST: db
|
||||
DB_PORT: 3306
|
||||
DB_NAME: trasabilitate
|
||||
DB_USER: trasabilitate
|
||||
DB_PASSWORD: Initial01!
|
||||
|
||||
# Application settings
|
||||
FLASK_ENV: production
|
||||
FLASK_APP: run.py
|
||||
|
||||
# Initialization flags (set to "false" after first run if needed)
|
||||
INIT_DB: "true"
|
||||
SEED_DB: "true"
|
||||
ports:
|
||||
- "${APP_PORT:-8781}:8781"
|
||||
volumes:
|
||||
# Mount logs directory for persistence
|
||||
- /srv/docker-test/logs:/srv/quality_recticel/logs
|
||||
# Mount instance directory for config persistence
|
||||
- /srv/docker-test/instance:/app/instance
|
||||
# Mount app code for easy updates (DISABLED - causes config issues)
|
||||
# Uncomment only for development, not production
|
||||
# - /srv/docker-test/app:/app
|
||||
networks:
|
||||
- recticel-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8781/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
networks:
|
||||
recticel-network:
|
||||
driver: bridge
|
||||
|
||||
# Note: Using bind mounts to /srv/docker-test/ instead of named volumes
|
||||
# This allows easier access and management of persistent data
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "==================================="
|
||||
echo "Recticel Quality App - Starting"
|
||||
echo "==================================="
|
||||
|
||||
# Wait for MariaDB to be ready
|
||||
echo "Waiting for MariaDB to be ready..."
|
||||
until python3 << END
|
||||
import mariadb
|
||||
import sys
|
||||
import time
|
||||
|
||||
max_retries = 30
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
conn = mariadb.connect(
|
||||
user="${DB_USER}",
|
||||
password="${DB_PASSWORD}",
|
||||
host="${DB_HOST}",
|
||||
port=int("${DB_PORT}"),
|
||||
database="${DB_NAME}"
|
||||
)
|
||||
conn.close()
|
||||
print("✅ Database connection successful!")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
retry_count += 1
|
||||
print(f"Database not ready yet (attempt {retry_count}/{max_retries}). Waiting...")
|
||||
time.sleep(2)
|
||||
|
||||
print("❌ Failed to connect to database after 30 attempts")
|
||||
sys.exit(1)
|
||||
END
|
||||
do
|
||||
echo "Retrying database connection..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Create external_server.conf from environment variables
|
||||
echo "Creating database configuration..."
|
||||
cat > /app/instance/external_server.conf << EOF
|
||||
server_domain=${DB_HOST}
|
||||
port=${DB_PORT}
|
||||
database_name=${DB_NAME}
|
||||
username=${DB_USER}
|
||||
password=${DB_PASSWORD}
|
||||
EOF
|
||||
|
||||
echo "✅ Database configuration created"
|
||||
|
||||
# Run database initialization if needed
|
||||
if [ "${INIT_DB}" = "true" ]; then
|
||||
echo "Initializing database schema..."
|
||||
python3 /app/app/db_create_scripts/setup_complete_database.py || echo "⚠️ Database may already be initialized"
|
||||
fi
|
||||
|
||||
# Seed the database with superadmin user
|
||||
if [ "${SEED_DB}" = "true" ]; then
|
||||
echo "Seeding database with superadmin user..."
|
||||
python3 /app/seed.py || echo "⚠️ Database may already be seeded"
|
||||
fi
|
||||
|
||||
echo "==================================="
|
||||
echo "Starting application..."
|
||||
echo "==================================="
|
||||
|
||||
# Execute the CMD
|
||||
exec "$@"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
20
init-db.sql
20
init-db.sql
@@ -1,20 +0,0 @@
|
||||
-- MariaDB Initialization Script for Recticel Quality Application
|
||||
-- This script creates the database and user if they don't exist
|
||||
|
||||
-- Create database if it doesn't exist
|
||||
CREATE DATABASE IF NOT EXISTS trasabilitate CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Create user if it doesn't exist (MariaDB 10.2+)
|
||||
CREATE USER IF NOT EXISTS 'trasabilitate'@'%' IDENTIFIED BY 'Initial01!';
|
||||
|
||||
-- Grant all privileges on the database to the user
|
||||
GRANT ALL PRIVILEGES ON trasabilitate.* TO 'trasabilitate'@'%';
|
||||
|
||||
-- Flush privileges to ensure they take effect
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Select the database
|
||||
USE trasabilitate;
|
||||
|
||||
-- The actual table creation will be handled by the Python setup script
|
||||
-- This ensures compatibility with the existing setup_complete_database.py
|
||||
466
logs/access.log
466
logs/access.log
@@ -1,466 +0,0 @@
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:16 +0300] "GET /print_module HTTP/1.1" 200 69964 "https://quality.moto-adv.com/etichete" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 105688
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:17 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2579
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:17 +0300] "GET /get_unprinted_orders HTTP/1.1" 200 3 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5560
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET /dashboard HTTP/1.1" 302 189 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 15566
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 40767
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 12665
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 14360
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5758
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 26983
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:28 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 30870
|
||||
192.168.0.132 - - [15/Oct/2025:00:30:46 +0300] "POST / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 48983
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:01 +0300] "POST / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7519
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:14 +0300] "POST / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 62448
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:33 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 24735
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:33 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 40939
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:33 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 26109
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:39 +0300] "GET /main_scan HTTP/1.1" 200 1981 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 38217
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:47 +0300] "GET /fg_scan HTTP/1.1" 200 23188 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 34772
|
||||
192.168.0.132 - - [15/Oct/2025:00:31:47 +0300] "GET /static/css/scan.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2544
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:14 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 17315
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:15 +0300] "GET /dashboard HTTP/1.1" 302 189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2603
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:15 +0300] "GET / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 40000
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:19 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9291
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:19 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9739
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:21 +0300] "GET /quality HTTP/1.1" 200 8292 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 10366
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:24 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2422
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:26 +0300] "GET /main_scan HTTP/1.1" 200 1981 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6923
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:27 +0300] "GET /fg_scan HTTP/1.1" 200 23188 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 30751
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:31 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2426
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:33 +0300] "GET /etichete HTTP/1.1" 200 2420 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 54927
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:35 +0300] "GET /upload_data HTTP/1.1" 200 10558 "https://quality.moto-adv.com/etichete" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 21518
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:38 +0300] "GET /etichete HTTP/1.1" 200 2420 "https://quality.moto-adv.com/upload_data" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2995
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:40 +0300] "GET /print_module HTTP/1.1" 200 70135 "https://quality.moto-adv.com/etichete" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 58228
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:40 +0300] "GET /static/qz-tray.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 16021
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:40 +0300] "GET /static/JsBarcode.all.min.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28667
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:40 +0300] "GET /static/html2canvas.min.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 35231
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:40 +0300] "GET /get_pairing_keys HTTP/1.1" 200 118 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 3023
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:41 +0300] "GET /get_unprinted_orders HTTP/1.1" 200 3 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 8089
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:45 +0300] "GET /etichete HTTP/1.1" 200 2420 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 40846
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:49 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/etichete" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9861
|
||||
192.168.0.132 - - [15/Oct/2025:01:29:50 +0300] "GET /settings HTTP/1.1" 200 9105 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 29372
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:41 +0300] "HEAD / HTTP/1.1" 200 0 "-" "-" 51821
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:41 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 1706
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:42 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12219
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:42 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 5333
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:42 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13946
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:42 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2079
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:42 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 27067
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:42 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2104
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:43 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10666
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:43 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10094
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:43 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 51915
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:45 +0300] "GET /main_scan HTTP/1.1" 200 1978 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 38423
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:47 +0300] "GET /fg_scan HTTP/1.1" 200 23185 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 32657
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:47 +0300] "GET /static/css/scan.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2641
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:52 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 8104
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:53 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 2178
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:53 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 1912
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:53 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 2623
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:53 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 2380
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:53 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 32438
|
||||
192.168.0.132 - - [15/Oct/2025:15:39:54 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 3180
|
||||
192.168.0.132 - - [15/Oct/2025:15:46:15 +0300] "POST /fg_scan HTTP/1.1" 200 23790 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 41768
|
||||
192.168.0.132 - - [15/Oct/2025:15:46:58 +0300] "POST /fg_scan HTTP/1.1" 200 24394 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 70762
|
||||
192.168.0.132 - - [15/Oct/2025:15:47:44 +0300] "POST /fg_scan HTTP/1.1" 200 24997 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 15544
|
||||
192.168.0.132 - - [15/Oct/2025:15:48:03 +0300] "POST /fg_scan HTTP/1.1" 200 25601 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 41106
|
||||
192.168.0.132 - - [15/Oct/2025:15:48:26 +0300] "POST /fg_scan HTTP/1.1" 200 26205 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 91583
|
||||
192.168.0.132 - - [15/Oct/2025:15:49:21 +0300] "POST /fg_scan HTTP/1.1" 200 26810 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12878
|
||||
192.168.0.132 - - [15/Oct/2025:15:49:38 +0300] "POST /fg_scan HTTP/1.1" 200 27413 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 76806
|
||||
192.168.0.132 - - [15/Oct/2025:15:49:48 +0300] "POST /fg_scan HTTP/1.1" 200 28017 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14211
|
||||
192.168.0.132 - - [15/Oct/2025:15:49:56 +0300] "POST /fg_scan HTTP/1.1" 200 28621 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13928
|
||||
192.168.0.132 - - [15/Oct/2025:15:50:02 +0300] "POST /fg_scan HTTP/1.1" 200 29225 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13590
|
||||
192.168.0.132 - - [15/Oct/2025:15:50:55 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10067
|
||||
192.168.0.132 - - [15/Oct/2025:15:50:57 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2988
|
||||
192.168.0.132 - - [15/Oct/2025:15:50:57 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2528
|
||||
192.168.0.132 - - [15/Oct/2025:15:51:06 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 3070
|
||||
192.168.0.132 - - [15/Oct/2025:15:51:06 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 9653
|
||||
192.168.0.132 - - [15/Oct/2025:15:51:07 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 3316
|
||||
192.168.0.132 - - [15/Oct/2025:15:51:07 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2520
|
||||
192.168.0.132 - - [15/Oct/2025:15:51:11 +0300] "GET /main_scan HTTP/1.1" 200 1978 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6865
|
||||
192.168.0.132 - - [15/Oct/2025:15:51:16 +0300] "GET /fg_scan HTTP/1.1" 200 29225 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 5743
|
||||
192.168.0.132 - - [15/Oct/2025:15:58:53 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 41424
|
||||
192.168.0.132 - - [15/Oct/2025:15:58:54 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2982
|
||||
192.168.0.132 - - [15/Oct/2025:15:58:54 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 9731
|
||||
192.168.0.132 - - [15/Oct/2025:15:58:56 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2857
|
||||
192.168.0.132 - - [15/Oct/2025:15:58:56 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2509
|
||||
192.168.0.132 - - [15/Oct/2025:15:58:57 +0300] "GET /warehouse HTTP/1.1" 200 2421 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8579
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:55 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" 1639
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:56 +0300] "GET /sitemap.xml HTTP/1.1" 404 207 "-" "fasthttp" 1943
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:56 +0300] "GET /sitemap.xml HTTP/1.1" 404 207 "-" "fasthttp" 1420
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:56 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "-" "fasthttp" 1322
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:56 +0300] "GET /robots.txt HTTP/1.1" 404 207 "-" "fasthttp" 1374
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:57 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36" 2550
|
||||
192.168.0.132 - - [15/Oct/2025:16:46:57 +0300] "GET /sitemap.xml HTTP/1.1" 404 207 "-" "fasthttp" 1346
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1787
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET /static/css/login.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1981
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET /static/script.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2223
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2254
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET /static/logo_login.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2182
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2082
|
||||
192.168.0.132 - - [15/Oct/2025:22:42:50 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2049
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:10 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 6531
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:10 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 9664
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:11 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 12203
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:25 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2443
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:25 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2433
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:29 +0300] "GET /quality HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2319
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:29 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2372
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:37 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2324
|
||||
192.168.0.132 - - [15/Oct/2025:22:43:37 +0300] "GET / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2029
|
||||
192.168.0.132 - - [15/Oct/2025:22:44:04 +0300] "POST / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 12509
|
||||
192.168.0.132 - - [15/Oct/2025:22:44:22 +0300] "POST / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 7961
|
||||
192.168.0.132 - - [15/Oct/2025:22:44:45 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 6237
|
||||
192.168.0.132 - - [15/Oct/2025:22:44:45 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2471
|
||||
192.168.0.132 - - [15/Oct/2025:22:44:47 +0300] "GET /quality HTTP/1.1" 200 8292 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 10556
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:00 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2462
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:07 +0300] "GET /settings HTTP/1.1" 200 9105 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 26538
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:11 +0300] "GET /settings HTTP/1.1" 302 207 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 2098
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:11 +0300] "GET /dashboard HTTP/1.1" 302 189 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1426
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:11 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1645
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:12 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1632
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:12 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 2022
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:12 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1980
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:12 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 2032
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:12 +0300] "GET / HTTP/1.1" 200 1189 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1610
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:13 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 2004
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:13 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1969
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:13 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1959
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:13 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)" 1970
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:39 +0300] "POST /delete_user HTTP/1.1" 302 205 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 8755
|
||||
192.168.0.132 - - [15/Oct/2025:22:45:39 +0300] "GET /settings HTTP/1.1" 200 8603 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 26163
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:05 +0300] "POST /create_user HTTP/1.1" 302 205 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 9269
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:05 +0300] "GET /settings HTTP/1.1" 200 9120 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 27376
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:16 +0300] "GET /dashboard HTTP/1.1" 200 2527 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2442
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:17 +0300] "GET /quality HTTP/1.1" 200 8292 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 10378
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:20 +0300] "GET /get_report_data?report=1 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 5109
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:37 +0300] "GET /generate_report?report=6&date=2025-10-15 HTTP/1.1" 200 260 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 6128
|
||||
192.168.0.132 - - [15/Oct/2025:22:46:50 +0300] "GET /get_report_data?report=5 HTTP/1.1" 200 318 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 4085
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:19 +0300] "GET /settings HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1978
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:19 +0300] "GET /dashboard HTTP/1.1" 302 189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2113
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:19 +0300] "GET / HTTP/1.1" 200 1189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1970
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:19 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1712
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:24 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5754
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:24 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9594
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:24 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2385
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:24 +0300] "GET /static/script.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2150
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:24 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2539
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:24 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2473
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:28 +0300] "GET /main_scan HTTP/1.1" 200 1978 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6850
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:29 +0300] "GET /fg_scan HTTP/1.1" 200 29225 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5666
|
||||
192.168.0.132 - - [16/Oct/2025:00:04:30 +0300] "GET /static/css/scan.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2455
|
||||
192.168.0.132 - - [16/Oct/2025:00:05:32 +0300] "GET /dashboard HTTP/1.1" 200 2524 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2311
|
||||
192.168.0.132 - - [16/Oct/2025:00:05:34 +0300] "GET /quality HTTP/1.1" 200 8289 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 10335
|
||||
192.168.0.132 - - [16/Oct/2025:00:05:36 +0300] "GET /get_report_data?report=1 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 4252
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:42 +0300] "HEAD / HTTP/1.1" 200 0 "-" "-" 59333
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:43 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2227
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:43 +0300] "GET /static/css/login.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12268
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:43 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2160
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:43 +0300] "GET /static/logo_login.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 27228
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:43 +0300] "GET /static/script.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 27216
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:43 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 27723
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:44 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2630
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:45 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 11458
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:45 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 47192
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:45 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2496
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:53 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 8221
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:53 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 2406
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:53 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 1986
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:53 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 1922
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:53 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 2275
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:53 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 6991
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:54 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" 1579
|
||||
192.168.0.132 - - [16/Oct/2025:08:51:58 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7862
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:00 +0300] "GET /quality HTTP/1.1" 200 8731 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10339
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:04 +0300] "GET /get_report_data?report=1 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4612
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:12 +0300] "GET /generate_report?report=6&date=2025-10-15 HTTP/1.1" 200 260 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 24036
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:17 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 49162
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:24 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8085
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:28 +0300] "GET /fg_quality HTTP/1.1" 200 22317 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 21185
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:28 +0300] "GET /static/fg_quality.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2306
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:40 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 62567
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:41 +0300] "GET /main_scan HTTP/1.1" 200 2433 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7821
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:44 +0300] "GET /fg_scan HTTP/1.1" 200 29680 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 86590
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:44 +0300] "GET /static/css/scan.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12851
|
||||
192.168.0.132 - - [16/Oct/2025:08:52:59 +0300] "POST /fg_scan HTTP/1.1" 200 30284 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 39788
|
||||
192.168.0.132 - - [16/Oct/2025:08:53:13 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 62427
|
||||
192.168.0.132 - - [16/Oct/2025:08:53:16 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 48080
|
||||
192.168.0.132 - - [16/Oct/2025:08:53:18 +0300] "GET /quality HTTP/1.1" 200 8731 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10833
|
||||
192.168.0.132 - - [16/Oct/2025:08:53:34 +0300] "GET /get_report_data?report=4 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7108
|
||||
192.168.0.132 - - [16/Oct/2025:08:53:42 +0300] "GET /get_report_data?report=2 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4601
|
||||
192.168.0.132 - - [16/Oct/2025:08:53:52 +0300] "GET /generate_report?report=7&start_date=2025-10-10&end_date=2025-10-16 HTTP/1.1" 200 278 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 15891
|
||||
192.168.0.132 - - [16/Oct/2025:09:00:50 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 62494
|
||||
192.168.0.132 - - [16/Oct/2025:09:00:52 +0300] "GET /main_scan HTTP/1.1" 200 2433 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6812
|
||||
192.168.0.132 - - [16/Oct/2025:09:00:53 +0300] "GET /fg_scan HTTP/1.1" 200 30284 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6748
|
||||
192.168.0.132 - - [16/Oct/2025:09:01:38 +0300] "POST /fg_scan HTTP/1.1" 200 30889 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 40541
|
||||
192.168.0.132 - - [16/Oct/2025:09:01:47 +0300] "POST /fg_scan HTTP/1.1" 200 31493 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12457
|
||||
192.168.0.132 - - [16/Oct/2025:09:01:56 +0300] "POST /fg_scan HTTP/1.1" 200 32097 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13517
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:04 +0300] "POST /fg_scan HTTP/1.1" 200 32702 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 16164
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:12 +0300] "POST /fg_scan HTTP/1.1" 200 32701 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13839
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:18 +0300] "POST /fg_scan HTTP/1.1" 200 32702 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13921
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:27 +0300] "POST /fg_scan HTTP/1.1" 200 32703 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14679
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:33 +0300] "POST /fg_scan HTTP/1.1" 200 32703 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 44686
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:41 +0300] "POST /fg_scan HTTP/1.1" 200 32703 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13734
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:49 +0300] "POST /fg_scan HTTP/1.1" 200 32704 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 47336
|
||||
192.168.0.132 - - [16/Oct/2025:09:02:56 +0300] "POST /fg_scan HTTP/1.1" 200 32706 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14351
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:03 +0300] "POST /fg_scan HTTP/1.1" 200 32707 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 45428
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:09 +0300] "POST /fg_scan HTTP/1.1" 200 32708 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 44776
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:15 +0300] "POST /fg_scan HTTP/1.1" 200 32710 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14234
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:23 +0300] "POST /fg_scan HTTP/1.1" 200 32711 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 78555
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:29 +0300] "POST /fg_scan HTTP/1.1" 200 32727 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14674
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:37 +0300] "POST /fg_scan HTTP/1.1" 200 32728 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14033
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:48 +0300] "POST /fg_scan HTTP/1.1" 200 32729 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 45638
|
||||
192.168.0.132 - - [16/Oct/2025:09:03:56 +0300] "POST /fg_scan HTTP/1.1" 200 32730 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 15193
|
||||
192.168.0.132 - - [16/Oct/2025:09:04:02 +0300] "POST /fg_scan HTTP/1.1" 200 32731 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13520
|
||||
192.168.0.132 - - [16/Oct/2025:09:04:09 +0300] "POST /fg_scan HTTP/1.1" 200 32731 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14452
|
||||
192.168.0.132 - - [16/Oct/2025:09:04:16 +0300] "POST /fg_scan HTTP/1.1" 200 32747 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 13658
|
||||
192.168.0.132 - - [16/Oct/2025:09:04:23 +0300] "POST /fg_scan HTTP/1.1" 200 32748 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 15046
|
||||
192.168.0.132 - - [16/Oct/2025:09:04:30 +0300] "POST /fg_scan HTTP/1.1" 200 32749 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14806
|
||||
192.168.0.132 - - [16/Oct/2025:09:04:39 +0300] "POST /fg_scan HTTP/1.1" 200 32748 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 15571
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:05 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2680
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:07 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8030
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:09 +0300] "GET /quality HTTP/1.1" 200 8731 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10612
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:18 +0300] "GET /get_report_data?report=1 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4695
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:32 +0300] "GET /generate_report?report=6&date=2025-10-16 HTTP/1.1" 200 260 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6501
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:42 +0300] "GET /generate_report?report=6&date=2025-10-15 HTTP/1.1" 200 260 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6382
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:50 +0300] "GET /generate_report?report=7&start_date=2025-10-15&end_date=2025-10-16 HTTP/1.1" 200 278 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14148
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:56 +0300] "GET /get_report_data?report=3 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4641
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:58 +0300] "GET /get_report_data?report=3 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4582
|
||||
192.168.0.132 - - [16/Oct/2025:09:11:59 +0300] "GET /get_report_data?report=3 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4522
|
||||
192.168.0.132 - - [16/Oct/2025:09:12:05 +0300] "GET /generate_report?report=8&date=2025-10-16 HTTP/1.1" 200 283 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6487
|
||||
192.168.0.132 - - [16/Oct/2025:11:00:59 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 8136
|
||||
192.168.0.132 - - [16/Oct/2025:11:00:59 +0300] "GET /static/css/login.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2333
|
||||
192.168.0.132 - - [16/Oct/2025:11:00:59 +0300] "GET /static/logo_login.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 11439
|
||||
192.168.0.132 - - [16/Oct/2025:11:00:59 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1919
|
||||
192.168.0.132 - - [16/Oct/2025:11:00:59 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1830
|
||||
192.168.0.132 - - [16/Oct/2025:11:00:59 +0300] "GET /static/script.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1831
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8149
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2140
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 1975
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 1852
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12593
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4655
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:19 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 1982
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:22 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12596
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:36 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6126
|
||||
192.168.0.132 - - [16/Oct/2025:13:33:53 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6013
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:08 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 5729
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:08 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 9454
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:08 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 21784
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:11 +0300] "GET /etichete HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2607
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:11 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2399
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:17 +0300] "GET /etichete HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2524
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:17 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2449
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:19 +0300] "GET /warehouse HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2532
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:19 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2454
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:20 +0300] "GET /main_scan HTTP/1.1" 200 2433 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6946
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:24 +0300] "GET /fg_scan HTTP/1.1" 200 32748 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6140
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:25 +0300] "GET /static/css/scan.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 12829
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:31 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2378
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:35 +0300] "GET /fg_quality HTTP/1.1" 200 22317 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 21547
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:35 +0300] "GET /static/fg_quality.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2529
|
||||
192.168.0.132 - - [16/Oct/2025:13:34:48 +0300] "GET /generate_fg_report?report=6&date=2025-10-15 HTTP/1.1" 200 1005 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14935
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:05 +0300] "GET /generate_fg_report?report=7&start_date=2025-10-01&end_date=2025-10-16 HTTP/1.1" 200 299 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 5653
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:10 +0300] "GET /get_fg_report_data?report=2 HTTP/1.1" 200 3262 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7121
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:20 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2455
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:23 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2794
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:23 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8466
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:42 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 5707
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:43 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 9588
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:46 +0300] "GET /etichete HTTP/1.1" 200 2872 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8919
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:49 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/etichete" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2510
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:50 +0300] "GET /warehouse HTTP/1.1" 200 2876 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8327
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:54 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/warehouse" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2740
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:54 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/warehouse" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 8441
|
||||
192.168.0.132 - - [16/Oct/2025:13:35:59 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6225
|
||||
192.168.0.132 - - [16/Oct/2025:13:36:21 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6115
|
||||
192.168.0.132 - - [16/Oct/2025:13:36:32 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6034
|
||||
192.168.0.132 - - [16/Oct/2025:13:36:51 +0300] "POST / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6110
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:47 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1690
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:47 +0300] "GET /static/logo_login.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2181
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:47 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1802
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:47 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1999
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:47 +0300] "GET /static/script.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1929
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:47 +0300] "GET /static/css/login.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2006
|
||||
192.168.0.132 - - [16/Oct/2025:13:51:48 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1467
|
||||
192.168.0.132 - - [16/Oct/2025:13:52:04 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5385
|
||||
192.168.0.132 - - [16/Oct/2025:13:52:04 +0300] "GET /dashboard HTTP/1.1" 200 2935 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2389
|
||||
192.168.0.132 - - [16/Oct/2025:13:52:04 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2263
|
||||
192.168.0.132 - - [16/Oct/2025:13:52:17 +0300] "GET /settings HTTP/1.1" 200 10008 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 29183
|
||||
192.168.0.132 - - [16/Oct/2025:13:52:22 +0300] "GET /user_management_simple HTTP/1.1" 200 40501 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 53904
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:17 +0300] "POST /create_user_simple HTTP/1.1" 302 233 "https://quality.moto-adv.com/user_management_simple" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7772
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:17 +0300] "GET /user_management_simple HTTP/1.1" 200 43469 "https://quality.moto-adv.com/user_management_simple" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 53063
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:21 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/user_management_simple" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2427
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:21 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/user_management_simple" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2058
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:39 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5679
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:39 +0300] "GET /dashboard HTTP/1.1" 200 2933 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9512
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:45 +0300] "GET /main_scan HTTP/1.1" 200 2434 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6796
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:46 +0300] "GET /fg_scan HTTP/1.1" 200 32749 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6066
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:47 +0300] "GET /static/css/scan.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2323
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:49 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2310
|
||||
192.168.0.132 - - [16/Oct/2025:13:53:49 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2088
|
||||
192.168.0.132 - - [16/Oct/2025:16:38:47 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2044
|
||||
192.168.0.132 - - [16/Oct/2025:16:38:49 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5727
|
||||
192.168.0.132 - - [16/Oct/2025:16:38:49 +0300] "GET /dashboard HTTP/1.1" 200 2933 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2374
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:01 +0300] "GET /main_scan HTTP/1.1" 200 2434 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2304
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:05 +0300] "GET /fg_scan HTTP/1.1" 200 32749 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6086
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:14 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2310
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:14 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2055
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:32 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5846
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:32 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9387
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:40 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2287
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:42 +0300] "GET /fg_quality HTTP/1.1" 200 22317 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 21146
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:42 +0300] "GET /static/fg_quality.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2363
|
||||
192.168.0.132 - - [16/Oct/2025:16:39:45 +0300] "GET /get_fg_report_data?report=5 HTTP/1.1" 200 3262 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6944
|
||||
192.168.0.132 - - [16/Oct/2025:16:40:01 +0300] "GET /get_fg_report_data?report=1 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6236
|
||||
192.168.0.132 - - [16/Oct/2025:16:40:11 +0300] "GET /get_fg_report_data?report=2 HTTP/1.1" 200 3262 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7015
|
||||
192.168.0.132 - - [16/Oct/2025:16:40:24 +0300] "GET /get_fg_report_data?report=1 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6368
|
||||
192.168.0.132 - - [16/Oct/2025:16:40:49 +0300] "GET /get_fg_report_data?report=4 HTTP/1.1" 200 1611 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5934
|
||||
192.168.0.132 - - [16/Oct/2025:21:15:08 +0300] "GET /main_scan HTTP/1.1" 302 189 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2141
|
||||
192.168.0.132 - - [16/Oct/2025:21:15:08 +0300] "GET /print_module HTTP/1.1" 200 70419 "https://quality.moto-adv.com/etichete" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 54374
|
||||
192.168.0.132 - - [16/Oct/2025:21:15:08 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1995
|
||||
192.168.0.132 - - [16/Oct/2025:21:15:09 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1783
|
||||
192.168.0.132 - - [16/Oct/2025:21:15:09 +0300] "GET /get_unprinted_orders HTTP/1.1" 200 3 "https://quality.moto-adv.com/print_module" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5239
|
||||
192.168.0.132 - - [17/Oct/2025:02:39:07 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)" 1670
|
||||
192.168.0.132 - - [17/Oct/2025:02:39:11 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "-" "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)" 1959
|
||||
192.168.0.132 - - [17/Oct/2025:02:39:46 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "-" "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)" 1418
|
||||
192.168.0.132 - - [17/Oct/2025:03:56:17 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/120.0" 1711
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:25 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1428
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:37 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5639
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:37 +0300] "GET /dashboard HTTP/1.1" 200 2935 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2322
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:37 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2445
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:37 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 12977
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:37 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2398
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:37 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 27154
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:42 +0300] "GET /settings HTTP/1.1" 200 10517 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28159
|
||||
192.168.0.132 - - [17/Oct/2025:20:46:51 +0300] "GET /user_management_simple HTTP/1.1" 200 43469 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 54418
|
||||
192.168.0.132 - - [18/Oct/2025:14:01:44 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2011
|
||||
192.168.0.132 - - [18/Oct/2025:14:25:59 +0300] "GET / HTTP/1.1" 200 1627 "-" "-" 1667
|
||||
192.168.0.132 - - [18/Oct/2025:14:44:53 +0300] "GET /.env HTTP/1.1" 404 207 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0" 1440
|
||||
192.168.0.132 - - [18/Oct/2025:14:44:54 +0300] "POST / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0" 2620
|
||||
192.168.0.132 - - [18/Oct/2025:16:52:25 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)" 8041
|
||||
192.168.0.132 - - [19/Oct/2025:02:26:25 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 zgrab/0.x" 1656
|
||||
192.168.0.132 - - [19/Oct/2025:02:26:28 +0300] "GET / HTTP/1.1" 200 1627 "http://quality.moto-adv.com/" "Mozilla/5.0 zgrab/0.x" 1671
|
||||
192.168.0.132 - - [19/Oct/2025:03:06:04 +0300] "GET / HTTP/1.1" 200 1627 "http://quality.moto-adv.com/" "Mozilla/5.0 zgrab/0.x" 8039
|
||||
192.168.0.132 - - [19/Oct/2025:03:06:10 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 zgrab/0.x" 1639
|
||||
192.168.0.132 - - [19/Oct/2025:05:41:14 +0300] "GET /robots.txt HTTP/1.1" 404 207 "-" "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0; +https://openai.com/gptbot)" 1928
|
||||
192.168.0.132 - - [20/Oct/2025:00:49:20 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)" 1627
|
||||
192.168.0.132 - - [20/Oct/2025:00:49:28 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "-" "Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)" 1371
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /quality HTTP/1.1" 302 189 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2168
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 1986
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2691
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2467
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2174
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2198
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4845
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:33 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 1730
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:35 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 5838
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:35 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2350
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:35 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 18882
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:41 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7780
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:43 +0300] "GET /quality HTTP/1.1" 200 8731 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 10372
|
||||
192.168.0.132 - - [20/Oct/2025:10:28:51 +0300] "GET /get_report_data?report=2 HTTP/1.1" 200 151 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 4299
|
||||
192.168.0.132 - - [20/Oct/2025:10:29:00 +0300] "GET /generate_report?report=8&date=2025-10-16 HTTP/1.1" 200 283 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6147
|
||||
192.168.0.132 - - [20/Oct/2025:10:29:13 +0300] "GET /get_report_data?report=5 HTTP/1.1" 200 318 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 3976
|
||||
192.168.0.132 - - [20/Oct/2025:10:30:49 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7789
|
||||
192.168.0.132 - - [20/Oct/2025:10:30:55 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2344
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:00 +0300] "GET /settings HTTP/1.1" 302 207 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2319
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:00 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2371
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:04 +0300] "GET /main_scan HTTP/1.1" 200 2433 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6895
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:07 +0300] "GET /fg_scan HTTP/1.1" 200 32748 "https://quality.moto-adv.com/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6094
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:07 +0300] "GET /static/css/scan.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2419
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:33 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2359
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:42 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2335
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:49 +0300] "GET /fg_quality HTTP/1.1" 200 22317 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2458
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:49 +0300] "GET /static/fg_quality.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 2509
|
||||
192.168.0.132 - - [20/Oct/2025:10:31:53 +0300] "GET /get_fg_report_data?report=2 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6278
|
||||
192.168.0.132 - - [20/Oct/2025:10:32:19 +0300] "GET /get_fg_report_data?report=2 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 6303
|
||||
192.168.0.132 - - [20/Oct/2025:10:32:26 +0300] "GET /generate_fg_report?report=7&start_date=2025-10-15&end_date=2025-10-20 HTTP/1.1" 200 299 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 14413
|
||||
192.168.0.132 - - [20/Oct/2025:10:33:00 +0300] "GET /get_fg_report_data?report=5 HTTP/1.1" 200 3262 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" 7108
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:36 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1710
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:36 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2041
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:36 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2084
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:36 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 4555
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:37 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1872
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:37 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1883
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:37 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1410
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:42 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5474
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:42 +0300] "GET /dashboard HTTP/1.1" 200 2935 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2416
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:42 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 21312
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:47 +0300] "GET /reports HTTP/1.1" 200 3280 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7823
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:49 +0300] "GET /fg_quality HTTP/1.1" 200 22449 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2366
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:49 +0300] "GET /static/fg_quality.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2388
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:52 +0300] "GET /get_fg_report_data?report=1 HTTP/1.1" 200 166 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 4316
|
||||
192.168.0.132 - - [20/Oct/2025:11:40:56 +0300] "GET /generate_fg_report?report=6&date=2025-10-16 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6882
|
||||
192.168.0.132 - - [20/Oct/2025:11:42:47 +0300] "GET /get_fg_report_data?report=1 HTTP/1.1" 200 166 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 4306
|
||||
192.168.0.132 - - [20/Oct/2025:11:42:55 +0300] "GET /generate_fg_report?report=6&date=2025-10-16 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 15831
|
||||
192.168.0.132 - - [20/Oct/2025:11:43:02 +0300] "GET /get_fg_report_data?report=2 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6448
|
||||
192.168.0.132 - - [20/Oct/2025:11:43:18 +0300] "GET /generate_fg_report?report=7&date=2025-10-16 HTTP/1.1" 200 103 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 3378
|
||||
192.168.0.132 - - [20/Oct/2025:11:43:25 +0300] "GET /test_fg_database HTTP/1.1" 200 1546 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7625
|
||||
192.168.0.132 - - [20/Oct/2025:11:43:28 +0300] "GET /logout HTTP/1.1" 302 189 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2213
|
||||
192.168.0.132 - - [20/Oct/2025:11:43:28 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1986
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET /fg_quality HTTP/1.1" 302 189 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1944
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1999
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET /static/script.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2319
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET /static/css/login.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2110
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2149
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET /static/logo_login.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2058
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:02 +0300] "GET /static/style.css HTTP/1.1" 304 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2163
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:03 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1751
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:11 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5765
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:11 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2368
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:11 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2294
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:29 +0300] "GET /dashboard HTTP/1.1" 200 2932 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2407
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:36 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2285
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:39 +0300] "GET /fg_quality HTTP/1.1" 200 22317 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 20956
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:39 +0300] "GET /static/fg_quality.js HTTP/1.1" 304 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2523
|
||||
192.168.0.132 - - [20/Oct/2025:13:09:58 +0300] "GET /generate_fg_report?report=7&start_date=2025-10-14&end_date=2025-10-20 HTTP/1.1" 200 299 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 14570
|
||||
192.168.0.132 - - [20/Oct/2025:13:10:05 +0300] "GET /reports HTTP/1.1" 200 3277 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7771
|
||||
192.168.0.132 - - [20/Oct/2025:13:10:07 +0300] "GET /fg_quality HTTP/1.1" 200 22317 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2488
|
||||
192.168.0.132 - - [20/Oct/2025:13:10:14 +0300] "GET /generate_fg_report?report=7&start_date=2025-10-15&end_date=2025-10-20 HTTP/1.1" 200 299 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 5450
|
||||
192.168.0.132 - - [20/Oct/2025:13:10:19 +0300] "GET /get_fg_report_data?report=2 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6302
|
||||
192.168.0.132 - - [20/Oct/2025:15:06:43 +0300] "GET / HTTP/1.1" 200 1627 "http://172.67.151.21:80/" "-" 1675
|
||||
192.168.0.132 - - [21/Oct/2025:05:11:27 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/120.0" 1728
|
||||
192.168.0.132 - - [22/Oct/2025:03:53:30 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/120.0" 1650
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:19 +0300] "GET / HTTP/1.1" 200 1627 "http://quality.moto-adv.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 1665
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:19 +0300] "GET /app/ HTTP/1.1" 404 207 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 1970
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:19 +0300] "GET /login/ HTTP/1.1" 404 207 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 1510
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:20 +0300] "GET /static/style.css HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 2398
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:20 +0300] "GET /static/script.js HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 2044
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:21 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 2019
|
||||
192.168.0.132 - - [22/Oct/2025:10:47:22 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" 2009
|
||||
192.168.0.132 - - [22/Oct/2025:11:22:49 +0300] "GET /user_management_simple HTTP/1.1" 302 189 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1925
|
||||
192.168.0.132 - - [22/Oct/2025:11:22:49 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2032
|
||||
192.168.0.132 - - [22/Oct/2025:11:22:50 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1773
|
||||
192.168.0.132 - - [22/Oct/2025:18:45:12 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 1959
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:31 +0300] "GET / HTTP/1.1" 200 1627 "https://quality.moto-adv.com/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 59557
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:32 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 13236
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:32 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 27376
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:32 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28792
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:32 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 27400
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:32 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 35000
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:39 +0300] "POST / HTTP/1.1" 302 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 12245
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:39 +0300] "GET /dashboard HTTP/1.1" 200 2935 "https://quality.moto-adv.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 47232
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:39 +0300] "GET /static/scan_me.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 26952
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:44 +0300] "GET /reports HTTP/1.1" 200 3280 "https://quality.moto-adv.com/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 8066
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:46 +0300] "GET /fg_quality HTTP/1.1" 200 22449 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 21126
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:46 +0300] "GET /static/fg_quality.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2489
|
||||
192.168.0.132 - - [22/Oct/2025:21:02:59 +0300] "GET /generate_fg_report?report=6&date=2025-10-16 HTTP/1.1" 200 2422 "https://quality.moto-adv.com/fg_quality" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 35769
|
||||
192.168.0.132 - - [22/Oct/2025:21:03:18 +0300] "GET /quality HTTP/1.1" 200 8860 "https://quality.moto-adv.com/reports" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 10411
|
||||
192.168.0.132 - - [23/Oct/2025:00:18:40 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36" 59084
|
||||
192.168.0.132 - - [23/Oct/2025:00:18:41 +0300] "POST / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36" 5974
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /.well-known/change-password HTTP/1.1" 404 207 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 3174
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /.well-known/resource-that-should-not-exist-whose-status-code-should-not-be-200 HTTP/1.1" 404 207 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1381
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET / HTTP/1.1" 200 1627 "-" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 8205
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2287
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2370
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 2078
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 4452
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:55 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1950
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:56 +0300] "GET /favicon.ico HTTP/1.1" 404 207 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36" 1364
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:58 +0300] "GET / HTTP/1.1" 200 1627 "https://www.google.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.52 Mobile Safari/537.36" 45825
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:59 +0300] "GET /static/css/login.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.52 Mobile Safari/537.36" 2111
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:59 +0300] "GET /static/style.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.52 Mobile Safari/537.36" 2136
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:59 +0300] "GET /static/logo_login.jpg HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.52 Mobile Safari/537.36" 4653
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:59 +0300] "GET /static/script.js HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.52 Mobile Safari/537.36" 1971
|
||||
192.168.0.132 - - [24/Oct/2025:19:45:59 +0300] "GET /static/css/base.css HTTP/1.1" 200 0 "https://quality.moto-adv.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.52 Mobile Safari/537.36" 3201
|
||||
158
logs/error.log
158
logs/error.log
@@ -1,158 +0,0 @@
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Starting gunicorn 23.0.0
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Listening at: http://0.0.0.0:8781 (284017)
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Using worker: sync
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Trasabilitate Application server is ready. Listening on: [('0.0.0.0', 8781)]
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:05 +0300] [284040] [INFO] Booting worker with pid: 284040
|
||||
[2025-10-15 00:30:05 +0300] [284040] [INFO] Worker spawned (pid: 284040)
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:05 +0300] [284041] [INFO] Booting worker with pid: 284041
|
||||
[2025-10-15 00:30:05 +0300] [284041] [INFO] Worker spawned (pid: 284041)
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:05 +0300] [284042] [INFO] Booting worker with pid: 284042
|
||||
[2025-10-15 00:30:05 +0300] [284042] [INFO] Worker spawned (pid: 284042)
|
||||
[2025-10-15 00:30:05 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:05 +0300] [284043] [INFO] Booting worker with pid: 284043
|
||||
[2025-10-15 00:30:05 +0300] [284043] [INFO] Worker spawned (pid: 284043)
|
||||
[2025-10-15 00:30:06 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:06 +0300] [284044] [INFO] Booting worker with pid: 284044
|
||||
[2025-10-15 00:30:06 +0300] [284044] [INFO] Worker spawned (pid: 284044)
|
||||
[2025-10-15 00:30:06 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:06 +0300] [284047] [INFO] Booting worker with pid: 284047
|
||||
[2025-10-15 00:30:06 +0300] [284047] [INFO] Worker spawned (pid: 284047)
|
||||
[2025-10-15 00:30:06 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:06 +0300] [284052] [INFO] Booting worker with pid: 284052
|
||||
[2025-10-15 00:30:06 +0300] [284052] [INFO] Worker spawned (pid: 284052)
|
||||
[2025-10-15 00:30:06 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:06 +0300] [284053] [INFO] Booting worker with pid: 284053
|
||||
[2025-10-15 00:30:06 +0300] [284053] [INFO] Worker spawned (pid: 284053)
|
||||
[2025-10-15 00:30:06 +0300] [284017] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 00:30:06 +0300] [284054] [INFO] Booting worker with pid: 284054
|
||||
[2025-10-15 00:30:06 +0300] [284054] [INFO] Worker spawned (pid: 284054)
|
||||
[2025-10-15 01:04:40 +0300] [284041] [INFO] Worker exiting (pid: 284041)
|
||||
[2025-10-15 01:04:40 +0300] [284040] [INFO] Worker exiting (pid: 284040)
|
||||
[2025-10-15 01:04:40 +0300] [284054] [INFO] Worker exiting (pid: 284054)
|
||||
[2025-10-15 01:04:40 +0300] [284017] [INFO] Handling signal: term
|
||||
[2025-10-15 01:04:40 +0300] [284053] [INFO] Worker exiting (pid: 284053)
|
||||
[2025-10-15 01:04:40 +0300] [284042] [INFO] Worker exiting (pid: 284042)
|
||||
[2025-10-15 01:04:40 +0300] [284047] [INFO] Worker exiting (pid: 284047)
|
||||
[2025-10-15 01:04:40 +0300] [284043] [INFO] Worker exiting (pid: 284043)
|
||||
[2025-10-15 01:04:40 +0300] [284044] [INFO] Worker exiting (pid: 284044)
|
||||
[2025-10-15 01:04:40 +0300] [284052] [INFO] Worker exiting (pid: 284052)
|
||||
[2025-10-15 01:04:40 +0300] [284017] [ERROR] Worker (pid:284041) was sent SIGTERM!
|
||||
[2025-10-15 01:04:41 +0300] [284017] [INFO] Shutting down: Master
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Starting gunicorn 23.0.0
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Listening at: http://0.0.0.0:8781 (288316)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Using worker: sync
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Trasabilitate Application server is ready. Listening on: [('0.0.0.0', 8781)]
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288317] [INFO] Booting worker with pid: 288317
|
||||
[2025-10-15 01:28:55 +0300] [288317] [INFO] Worker spawned (pid: 288317)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288318] [INFO] Booting worker with pid: 288318
|
||||
[2025-10-15 01:28:55 +0300] [288318] [INFO] Worker spawned (pid: 288318)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288319] [INFO] Booting worker with pid: 288319
|
||||
[2025-10-15 01:28:55 +0300] [288319] [INFO] Worker spawned (pid: 288319)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288320] [INFO] Booting worker with pid: 288320
|
||||
[2025-10-15 01:28:55 +0300] [288320] [INFO] Worker spawned (pid: 288320)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288321] [INFO] Booting worker with pid: 288321
|
||||
[2025-10-15 01:28:55 +0300] [288321] [INFO] Worker spawned (pid: 288321)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288322] [INFO] Booting worker with pid: 288322
|
||||
[2025-10-15 01:28:55 +0300] [288322] [INFO] Worker spawned (pid: 288322)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288323] [INFO] Booting worker with pid: 288323
|
||||
[2025-10-15 01:28:55 +0300] [288323] [INFO] Worker spawned (pid: 288323)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288324] [INFO] Booting worker with pid: 288324
|
||||
[2025-10-15 01:28:55 +0300] [288324] [INFO] Worker spawned (pid: 288324)
|
||||
[2025-10-15 01:28:55 +0300] [288316] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-15 01:28:55 +0300] [288325] [INFO] Booting worker with pid: 288325
|
||||
[2025-10-15 01:28:55 +0300] [288325] [INFO] Worker spawned (pid: 288325)
|
||||
[2025-10-16 00:06:02 +0300] [288316] [INFO] Handling signal: term
|
||||
[2025-10-16 00:06:02 +0300] [288317] [INFO] Worker exiting (pid: 288317)
|
||||
[2025-10-16 00:06:02 +0300] [288318] [INFO] Worker exiting (pid: 288318)
|
||||
[2025-10-16 00:06:02 +0300] [288319] [INFO] Worker exiting (pid: 288319)
|
||||
[2025-10-16 00:06:02 +0300] [288320] [INFO] Worker exiting (pid: 288320)
|
||||
[2025-10-16 00:06:02 +0300] [288322] [INFO] Worker exiting (pid: 288322)
|
||||
[2025-10-16 00:06:02 +0300] [288321] [INFO] Worker exiting (pid: 288321)
|
||||
[2025-10-16 00:06:02 +0300] [288323] [INFO] Worker exiting (pid: 288323)
|
||||
[2025-10-16 00:06:02 +0300] [288324] [INFO] Worker exiting (pid: 288324)
|
||||
[2025-10-16 00:06:02 +0300] [288325] [INFO] Worker exiting (pid: 288325)
|
||||
[2025-10-16 00:06:03 +0300] [288316] [INFO] Shutting down: Master
|
||||
[2025-10-16 02:34:31 +0300] [299414] [INFO] Starting gunicorn 23.0.0
|
||||
[2025-10-16 02:34:31 +0300] [299414] [INFO] Listening at: http://0.0.0.0:8781 (299414)
|
||||
[2025-10-16 02:34:31 +0300] [299414] [INFO] Using worker: sync
|
||||
[2025-10-16 02:34:31 +0300] [299414] [INFO] Trasabilitate Application server is ready. Listening on: [('0.0.0.0', 8781)]
|
||||
[2025-10-16 02:34:31 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:31 +0300] [299432] [INFO] Booting worker with pid: 299432
|
||||
[2025-10-16 02:34:31 +0300] [299432] [INFO] Worker spawned (pid: 299432)
|
||||
[2025-10-16 02:34:31 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:31 +0300] [299438] [INFO] Booting worker with pid: 299438
|
||||
[2025-10-16 02:34:31 +0300] [299438] [INFO] Worker spawned (pid: 299438)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299439] [INFO] Booting worker with pid: 299439
|
||||
[2025-10-16 02:34:32 +0300] [299439] [INFO] Worker spawned (pid: 299439)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299440] [INFO] Booting worker with pid: 299440
|
||||
[2025-10-16 02:34:32 +0300] [299440] [INFO] Worker spawned (pid: 299440)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299441] [INFO] Booting worker with pid: 299441
|
||||
[2025-10-16 02:34:32 +0300] [299441] [INFO] Worker spawned (pid: 299441)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299442] [INFO] Booting worker with pid: 299442
|
||||
[2025-10-16 02:34:32 +0300] [299442] [INFO] Worker spawned (pid: 299442)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299443] [INFO] Booting worker with pid: 299443
|
||||
[2025-10-16 02:34:32 +0300] [299443] [INFO] Worker spawned (pid: 299443)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299444] [INFO] Booting worker with pid: 299444
|
||||
[2025-10-16 02:34:32 +0300] [299444] [INFO] Worker spawned (pid: 299444)
|
||||
[2025-10-16 02:34:32 +0300] [299414] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-16 02:34:32 +0300] [299445] [INFO] Booting worker with pid: 299445
|
||||
[2025-10-16 02:34:32 +0300] [299445] [INFO] Worker spawned (pid: 299445)
|
||||
[2025-10-22 20:52:12 +0300] [299414] [INFO] Handling signal: term
|
||||
[2025-10-22 20:52:12 +0300] [299432] [INFO] Worker exiting (pid: 299432)
|
||||
[2025-10-22 20:52:12 +0300] [299439] [INFO] Worker exiting (pid: 299439)
|
||||
[2025-10-22 20:52:12 +0300] [299441] [INFO] Worker exiting (pid: 299441)
|
||||
[2025-10-22 20:52:12 +0300] [299438] [INFO] Worker exiting (pid: 299438)
|
||||
[2025-10-22 20:52:12 +0300] [299442] [INFO] Worker exiting (pid: 299442)
|
||||
[2025-10-22 20:52:12 +0300] [299444] [INFO] Worker exiting (pid: 299444)
|
||||
[2025-10-22 20:52:12 +0300] [299440] [INFO] Worker exiting (pid: 299440)
|
||||
[2025-10-22 20:52:12 +0300] [299445] [INFO] Worker exiting (pid: 299445)
|
||||
[2025-10-22 20:52:12 +0300] [299443] [INFO] Worker exiting (pid: 299443)
|
||||
[2025-10-22 20:52:13 +0300] [299414] [INFO] Shutting down: Master
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Starting gunicorn 23.0.0
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Listening at: http://0.0.0.0:8781 (317525)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Using worker: sync
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Trasabilitate Application server is ready. Listening on: [('0.0.0.0', 8781)]
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317543] [INFO] Booting worker with pid: 317543
|
||||
[2025-10-22 21:02:07 +0300] [317543] [INFO] Worker spawned (pid: 317543)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317547] [INFO] Booting worker with pid: 317547
|
||||
[2025-10-22 21:02:07 +0300] [317547] [INFO] Worker spawned (pid: 317547)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317548] [INFO] Booting worker with pid: 317548
|
||||
[2025-10-22 21:02:07 +0300] [317548] [INFO] Worker spawned (pid: 317548)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317549] [INFO] Booting worker with pid: 317549
|
||||
[2025-10-22 21:02:07 +0300] [317549] [INFO] Worker spawned (pid: 317549)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317550] [INFO] Booting worker with pid: 317550
|
||||
[2025-10-22 21:02:07 +0300] [317550] [INFO] Worker spawned (pid: 317550)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317551] [INFO] Booting worker with pid: 317551
|
||||
[2025-10-22 21:02:07 +0300] [317551] [INFO] Worker spawned (pid: 317551)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317552] [INFO] Booting worker with pid: 317552
|
||||
[2025-10-22 21:02:07 +0300] [317552] [INFO] Worker spawned (pid: 317552)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317553] [INFO] Booting worker with pid: 317553
|
||||
[2025-10-22 21:02:07 +0300] [317553] [INFO] Worker spawned (pid: 317553)
|
||||
[2025-10-22 21:02:07 +0300] [317525] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-22 21:02:07 +0300] [317556] [INFO] Booting worker with pid: 317556
|
||||
[2025-10-22 21:02:07 +0300] [317556] [INFO] Worker spawned (pid: 317556)
|
||||
@@ -1,133 +0,0 @@
|
||||
# Quick Database Setup for Trasabilitate Application
|
||||
|
||||
This script provides a complete one-step database setup for quick deployment of the Trasabilitate application.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running the setup script, ensure:
|
||||
|
||||
1. **MariaDB is installed and running**
|
||||
2. **Database and user are created**:
|
||||
```sql
|
||||
CREATE DATABASE trasabilitate;
|
||||
CREATE USER 'trasabilitate'@'localhost' IDENTIFIED BY 'Initial01!';
|
||||
GRANT ALL PRIVILEGES ON trasabilitate.* TO 'trasabilitate'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
3. **Python virtual environment is activated**:
|
||||
```bash
|
||||
source ../recticel/bin/activate
|
||||
```
|
||||
4. **Python dependencies are installed**:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Quick Setup (Recommended)
|
||||
```bash
|
||||
cd /srv/quality_recticel/py_app
|
||||
source ../recticel/bin/activate
|
||||
python3 app/db_create_scripts/setup_complete_database.py
|
||||
```
|
||||
|
||||
### What the script creates:
|
||||
|
||||
#### MariaDB Tables:
|
||||
- `scan1_orders` - Quality scanning data for process 1
|
||||
- `scanfg_orders` - Quality scanning data for finished goods
|
||||
- `order_for_labels` - Label printing orders
|
||||
- `warehouse_locations` - Warehouse location management
|
||||
- `permissions` - System permissions
|
||||
- `role_permissions` - Role-permission mappings
|
||||
- `role_hierarchy` - User role hierarchy
|
||||
- `permission_audit_log` - Permission change audit trail
|
||||
|
||||
#### Database Triggers:
|
||||
- Auto-increment approved/rejected quantities based on quality codes
|
||||
- Triggers for both scan1_orders and scanfg_orders tables
|
||||
|
||||
#### SQLite Tables:
|
||||
- `users` - User authentication (in instance/users.db)
|
||||
- `roles` - User roles (in instance/users.db)
|
||||
|
||||
#### Configuration:
|
||||
- Updates `instance/external_server.conf` with correct database settings
|
||||
- Creates default superadmin user (username: `superadmin`, password: `superadmin123`)
|
||||
|
||||
#### Permission System:
|
||||
- 7 user roles (superadmin, admin, manager, quality_manager, warehouse_manager, quality_worker, warehouse_worker)
|
||||
- 25+ granular permissions for different application areas
|
||||
- Complete role hierarchy with inheritance
|
||||
|
||||
## After Setup
|
||||
|
||||
1. **Start the application**:
|
||||
```bash
|
||||
python3 run.py
|
||||
```
|
||||
|
||||
2. **Access the application**:
|
||||
- Local: http://127.0.0.1:8781
|
||||
- Network: http://192.168.0.205:8781
|
||||
|
||||
3. **Login with superadmin**:
|
||||
- Username: `superadmin`
|
||||
- Password: `superadmin123`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **Database connection failed**:
|
||||
- Check if MariaDB is running: `sudo systemctl status mariadb`
|
||||
- Verify database exists: `sudo mysql -e "SHOW DATABASES;"`
|
||||
- Check user privileges: `sudo mysql -e "SHOW GRANTS FOR 'trasabilitate'@'localhost';"`
|
||||
|
||||
2. **Import errors**:
|
||||
- Ensure virtual environment is activated
|
||||
- Install missing dependencies: `pip install -r requirements.txt`
|
||||
|
||||
3. **Permission denied**:
|
||||
- Make script executable: `chmod +x app/db_create_scripts/setup_complete_database.py`
|
||||
- Check file ownership: `ls -la app/db_create_scripts/`
|
||||
|
||||
### Manual Database Recreation:
|
||||
|
||||
If you need to completely reset the database:
|
||||
|
||||
```bash
|
||||
# Drop and recreate database
|
||||
sudo mysql -e "DROP DATABASE IF EXISTS trasabilitate; CREATE DATABASE trasabilitate; GRANT ALL PRIVILEGES ON trasabilitate.* TO 'trasabilitate'@'localhost'; FLUSH PRIVILEGES;"
|
||||
|
||||
# Remove SQLite database
|
||||
rm -f instance/users.db
|
||||
|
||||
# Run setup script
|
||||
python3 app/db_create_scripts/setup_complete_database.py
|
||||
```
|
||||
|
||||
## Script Features
|
||||
|
||||
- ✅ **Comprehensive**: Creates all necessary database structure
|
||||
- ✅ **Safe**: Uses `IF NOT EXISTS` clauses to prevent conflicts
|
||||
- ✅ **Verified**: Includes verification step to confirm setup
|
||||
- ✅ **Informative**: Detailed output showing each step
|
||||
- ✅ **Error handling**: Clear error messages and troubleshooting hints
|
||||
- ✅ **Idempotent**: Can be run multiple times safely
|
||||
|
||||
## Development Notes
|
||||
|
||||
The script combines functionality from these individual scripts:
|
||||
- `create_scan_1db.py`
|
||||
- `create_scanfg_orders.py`
|
||||
- `create_order_for_labels_table.py`
|
||||
- `create_warehouse_locations_table.py`
|
||||
- `create_permissions_tables.py`
|
||||
- `create_roles_table.py`
|
||||
- `create_triggers.py`
|
||||
- `create_triggers_fg.py`
|
||||
- `populate_permissions.py`
|
||||
|
||||
For development or debugging, you can still run individual scripts if needed.
|
||||
@@ -1,319 +0,0 @@
|
||||
# Recticel Quality Application - Docker Deployment Guide
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
This is a complete Docker-based deployment solution for the Recticel Quality Application. It includes:
|
||||
- **Flask Web Application** (Python 3.10)
|
||||
- **MariaDB 11.3 Database** with automatic initialization
|
||||
- **Gunicorn WSGI Server** for production-ready performance
|
||||
- **Automatic database schema setup** using existing setup scripts
|
||||
- **Superadmin user seeding** for immediate access
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- At least 2GB free disk space
|
||||
- Ports 8781 and 3306 available (or customize in .env)
|
||||
|
||||
### 1. Clone and Prepare
|
||||
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
```
|
||||
|
||||
### 2. Configure Environment (Optional)
|
||||
|
||||
Create a `.env` file from the example:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` to customize settings:
|
||||
```env
|
||||
MYSQL_ROOT_PASSWORD=your_secure_root_password
|
||||
DB_PORT=3306
|
||||
APP_PORT=8781
|
||||
INIT_DB=true
|
||||
SEED_DB=true
|
||||
```
|
||||
|
||||
### 3. Build and Deploy
|
||||
|
||||
Start all services:
|
||||
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
This will:
|
||||
1. ✅ Build the Flask application Docker image
|
||||
2. ✅ Pull MariaDB 11.3 image
|
||||
3. ✅ Create and initialize the database
|
||||
4. ✅ Run all database schema creation scripts
|
||||
5. ✅ Seed the superadmin user
|
||||
6. ✅ Start the web application on port 8781
|
||||
|
||||
### 4. Verify Deployment
|
||||
|
||||
Check service status:
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
View logs:
|
||||
```bash
|
||||
# All services
|
||||
docker-compose logs -f
|
||||
|
||||
# Just the web app
|
||||
docker-compose logs -f web
|
||||
|
||||
# Just the database
|
||||
docker-compose logs -f db
|
||||
```
|
||||
|
||||
### 5. Access the Application
|
||||
|
||||
Open your browser and navigate to:
|
||||
```
|
||||
http://localhost:8781
|
||||
```
|
||||
|
||||
**Default Login:**
|
||||
- Username: `superadmin`
|
||||
- Password: `superadmin123`
|
||||
|
||||
## 🔧 Management Commands
|
||||
|
||||
### Start Services
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Stop Services
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Stop and Remove All Data (including database)
|
||||
```bash
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### Restart Services
|
||||
```bash
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
### View Real-time Logs
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### Rebuild After Code Changes
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
### Access Database Console
|
||||
```bash
|
||||
docker-compose exec db mariadb -u trasabilitate -p trasabilitate
|
||||
# Password: Initial01!
|
||||
```
|
||||
|
||||
### Execute Commands in App Container
|
||||
```bash
|
||||
docker-compose exec web bash
|
||||
```
|
||||
|
||||
## 📁 Data Persistence
|
||||
|
||||
The following data is persisted across container restarts:
|
||||
|
||||
- **Database Data:** Stored in Docker volume `mariadb_data`
|
||||
- **Application Logs:** Mapped to `./logs` directory
|
||||
- **Instance Config:** Mapped to `./instance` directory
|
||||
|
||||
## 🔐 Security Considerations
|
||||
|
||||
### Production Deployment Checklist:
|
||||
|
||||
1. **Change Default Passwords:**
|
||||
- Update `MYSQL_ROOT_PASSWORD` in `.env`
|
||||
- Update database password in `docker-compose.yml`
|
||||
- Change superadmin password after first login
|
||||
|
||||
2. **Use Environment Variables:**
|
||||
- Never commit `.env` file to version control
|
||||
- Use secrets management for production
|
||||
|
||||
3. **Network Security:**
|
||||
- If database access from host is not needed, remove the port mapping:
|
||||
```yaml
|
||||
# Comment out in docker-compose.yml:
|
||||
# ports:
|
||||
# - "3306:3306"
|
||||
```
|
||||
|
||||
4. **SSL/TLS:**
|
||||
- Configure reverse proxy (nginx/traefik) for HTTPS
|
||||
- Update gunicorn SSL configuration if needed
|
||||
|
||||
5. **Firewall:**
|
||||
- Only expose necessary ports
|
||||
- Use firewall rules to restrict access
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
If the app can't connect to the database:
|
||||
|
||||
```bash
|
||||
# Check database health
|
||||
docker-compose exec db healthcheck.sh --connect
|
||||
|
||||
# Check database logs
|
||||
docker-compose logs db
|
||||
|
||||
# Verify database is accessible
|
||||
docker-compose exec db mariadb -u trasabilitate -p -e "SHOW DATABASES;"
|
||||
```
|
||||
|
||||
### Application Not Starting
|
||||
|
||||
```bash
|
||||
# Check application logs
|
||||
docker-compose logs web
|
||||
|
||||
# Verify database initialization
|
||||
docker-compose exec web python3 -c "import mariadb; print('MariaDB module OK')"
|
||||
|
||||
# Restart with fresh initialization
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If port 8781 or 3306 is already in use, edit `.env`:
|
||||
|
||||
```env
|
||||
APP_PORT=8782
|
||||
DB_PORT=3307
|
||||
```
|
||||
|
||||
Then restart:
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Reset Everything
|
||||
|
||||
To start completely fresh:
|
||||
|
||||
```bash
|
||||
# Stop and remove all containers, networks, and volumes
|
||||
docker-compose down -v
|
||||
|
||||
# Remove any local data
|
||||
rm -rf logs/* instance/external_server.conf
|
||||
|
||||
# Start fresh
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
## 🔄 Updating the Application
|
||||
|
||||
### Update Application Code
|
||||
|
||||
1. Make your code changes
|
||||
2. Rebuild and restart:
|
||||
```bash
|
||||
docker-compose up -d --build web
|
||||
```
|
||||
|
||||
### Update Database Schema
|
||||
|
||||
If you need to run migrations or schema updates:
|
||||
|
||||
```bash
|
||||
docker-compose exec web python3 /app/app/db_create_scripts/setup_complete_database.py
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
Both services have health checks configured:
|
||||
|
||||
```bash
|
||||
# Check overall status
|
||||
docker-compose ps
|
||||
|
||||
# Detailed health status
|
||||
docker inspect recticel-app | grep -A 10 Health
|
||||
docker inspect recticel-db | grep -A 10 Health
|
||||
```
|
||||
|
||||
### Resource Usage
|
||||
|
||||
```bash
|
||||
# View resource consumption
|
||||
docker stats recticel-app recticel-db
|
||||
```
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Docker Compose Network │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │ MariaDB │ │ Flask App │ │
|
||||
│ │ Container │◄─┤ Container │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ Port: 3306 │ │ Port: 8781 │ │
|
||||
│ └──────┬───────┘ └──────┬──────┘ │
|
||||
│ │ │ │
|
||||
└─────────┼─────────────────┼─────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
[Volume: [Logs &
|
||||
mariadb_data] Instance]
|
||||
```
|
||||
|
||||
## 📝 Environment Variables
|
||||
|
||||
### Database Configuration
|
||||
- `MYSQL_ROOT_PASSWORD`: MariaDB root password
|
||||
- `DB_HOST`: Database hostname (default: `db`)
|
||||
- `DB_PORT`: Database port (default: `3306`)
|
||||
- `DB_NAME`: Database name (default: `trasabilitate`)
|
||||
- `DB_USER`: Database user (default: `trasabilitate`)
|
||||
- `DB_PASSWORD`: Database password (default: `Initial01!`)
|
||||
|
||||
### Application Configuration
|
||||
- `FLASK_ENV`: Flask environment (default: `production`)
|
||||
- `FLASK_APP`: Flask app entry point (default: `run.py`)
|
||||
- `APP_PORT`: Application port (default: `8781`)
|
||||
|
||||
### Initialization Flags
|
||||
- `INIT_DB`: Run database initialization (default: `true`)
|
||||
- `SEED_DB`: Seed superadmin user (default: `true`)
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the logs: `docker-compose logs -f`
|
||||
2. Verify environment configuration
|
||||
3. Ensure all prerequisites are met
|
||||
4. Review this documentation
|
||||
|
||||
## 📄 License
|
||||
|
||||
[Your License Here]
|
||||
@@ -1,346 +0,0 @@
|
||||
# Recticel Quality Application - Docker Solution Summary
|
||||
|
||||
## 📦 What Has Been Created
|
||||
|
||||
A complete, production-ready Docker deployment solution for your Recticel Quality Application with the following components:
|
||||
|
||||
### Core Files Created
|
||||
|
||||
1. **`Dockerfile`** - Multi-stage Flask application container
|
||||
- Based on Python 3.10-slim
|
||||
- Installs all dependencies from requirements.txt
|
||||
- Configures Gunicorn WSGI server
|
||||
- Exposes port 8781
|
||||
|
||||
2. **`docker-compose.yml`** - Complete orchestration configuration
|
||||
- MariaDB 11.3 database service
|
||||
- Flask web application service
|
||||
- Automatic networking between services
|
||||
- Health checks for both services
|
||||
- Volume persistence for database and logs
|
||||
|
||||
3. **`docker-entrypoint.sh`** - Smart initialization script
|
||||
- Waits for database to be ready
|
||||
- Creates database configuration file
|
||||
- Runs database schema initialization
|
||||
- Seeds superadmin user
|
||||
- Starts the application
|
||||
|
||||
4. **`init-db.sql`** - MariaDB initialization
|
||||
- Creates database and user
|
||||
- Sets up permissions automatically
|
||||
|
||||
5. **`.env.example`** - Configuration template
|
||||
- Database passwords
|
||||
- Port configurations
|
||||
- Initialization flags
|
||||
|
||||
6. **`.dockerignore`** - Build optimization
|
||||
- Excludes unnecessary files from Docker image
|
||||
- Reduces image size
|
||||
|
||||
7. **`deploy.sh`** - One-command deployment script
|
||||
- Checks prerequisites
|
||||
- Creates configuration
|
||||
- Builds and starts services
|
||||
- Shows deployment status
|
||||
|
||||
8. **`Makefile`** - Convenient management commands
|
||||
- `make install` - First-time installation
|
||||
- `make up` - Start services
|
||||
- `make down` - Stop services
|
||||
- `make logs` - View logs
|
||||
- `make shell` - Access container
|
||||
- `make backup-db` - Backup database
|
||||
- And many more...
|
||||
|
||||
9. **`DOCKER_DEPLOYMENT.md`** - Complete documentation
|
||||
- Quick start guide
|
||||
- Management commands
|
||||
- Troubleshooting
|
||||
- Security considerations
|
||||
- Architecture diagrams
|
||||
|
||||
### Enhanced Files
|
||||
|
||||
10. **`setup_complete_database.py`** - Updated to support Docker
|
||||
- Now reads from environment variables
|
||||
- Fallback to config file for non-Docker deployments
|
||||
- Maintains backward compatibility
|
||||
|
||||
## 🎯 Key Features
|
||||
|
||||
### 1. Single-Command Deployment
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
This single command will:
|
||||
- ✅ Build Docker images
|
||||
- ✅ Create MariaDB database
|
||||
- ✅ Initialize all database tables and triggers
|
||||
- ✅ Seed superadmin user
|
||||
- ✅ Start the application
|
||||
|
||||
### 2. Complete Isolation
|
||||
- Application runs in its own container
|
||||
- Database runs in its own container
|
||||
- No system dependencies needed except Docker
|
||||
- No Python/MariaDB installation on host required
|
||||
|
||||
### 3. Data Persistence
|
||||
- Database data persists across restarts (Docker volume)
|
||||
- Application logs accessible on host
|
||||
- Configuration preserved
|
||||
|
||||
### 4. Production Ready
|
||||
- Gunicorn WSGI server (not Flask dev server)
|
||||
- Health checks for monitoring
|
||||
- Automatic restart on failure
|
||||
- Proper logging configuration
|
||||
- Resource isolation
|
||||
|
||||
### 5. Easy Management
|
||||
```bash
|
||||
# Start
|
||||
docker compose up -d
|
||||
|
||||
# Stop
|
||||
docker compose down
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Backup database
|
||||
make backup-db
|
||||
|
||||
# Restore database
|
||||
make restore-db BACKUP=backup_20231215.sql
|
||||
|
||||
# Access shell
|
||||
make shell
|
||||
|
||||
# Complete reset
|
||||
make reset
|
||||
```
|
||||
|
||||
## 🚀 Deployment Options
|
||||
|
||||
### Option 1: Quick Deploy (Recommended for Testing)
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### Option 2: Using Makefile (Recommended for Management)
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
make install # First time only
|
||||
make up # Start services
|
||||
make logs # Monitor
|
||||
```
|
||||
|
||||
### Option 3: Using Docker Compose Directly
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
cp .env.example .env
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
The deployment **requires** Docker to be installed on the target system:
|
||||
|
||||
### Installing Docker on Ubuntu/Debian:
|
||||
```bash
|
||||
# Update package index
|
||||
sudo apt-get update
|
||||
|
||||
# Install dependencies
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
|
||||
# Add Docker's official GPG key
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
# Set up the repository
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Install Docker Engine
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# Add current user to docker group (optional, to run without sudo)
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
After installation, log out and back in for group changes to take effect.
|
||||
|
||||
### Installing Docker on CentOS/RHEL:
|
||||
```bash
|
||||
sudo yum install -y yum-utils
|
||||
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ Docker Compose Stack │
|
||||
│ │
|
||||
│ ┌────────────────────┐ ┌───────────────────┐ │
|
||||
│ │ MariaDB 11.3 │ │ Flask App │ │
|
||||
│ │ Container │◄─────┤ Container │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ - Port: 3306 │ │ - Port: 8781 │ │
|
||||
│ │ - Volume: DB Data │ │ - Gunicorn WSGI │ │
|
||||
│ │ - Auto Init │ │ - Python 3.10 │ │
|
||||
│ │ - Health Checks │ │ - Health Checks │ │
|
||||
│ └──────────┬─────────┘ └─────────┬─────────┘ │
|
||||
│ │ │ │
|
||||
└─────────────┼──────────────────────────┼─────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
[mariadb_data] [logs directory]
|
||||
Docker Volume Host filesystem
|
||||
```
|
||||
|
||||
## 🔐 Security Features
|
||||
|
||||
1. **Database Isolation**: Database not exposed to host by default (can be configured)
|
||||
2. **Password Management**: All passwords in `.env` file (not committed to git)
|
||||
3. **User Permissions**: Proper MariaDB user with limited privileges
|
||||
4. **Network Isolation**: Services communicate on private Docker network
|
||||
5. **Production Mode**: Flask runs in production mode with Gunicorn
|
||||
|
||||
## 📊 What Gets Deployed
|
||||
|
||||
### Database Schema
|
||||
All tables from `setup_complete_database.py`:
|
||||
- `scan1_orders` - First scan orders
|
||||
- `scanfg_orders` - Final goods scan orders
|
||||
- `order_for_labels` - Label orders
|
||||
- `warehouse_locations` - Warehouse locations
|
||||
- `permissions` - Permission system
|
||||
- `role_permissions` - Role-based access
|
||||
- `role_hierarchy` - Role hierarchy
|
||||
- `permission_audit_log` - Audit logging
|
||||
- Plus SQLAlchemy tables: `users`, `roles`
|
||||
|
||||
### Initial Data
|
||||
- Superadmin user: `superadmin` / `superadmin123`
|
||||
|
||||
### Application Features
|
||||
- Complete Flask web application
|
||||
- Gunicorn WSGI server (4-8 workers depending on CPU)
|
||||
- Static file serving
|
||||
- Session management
|
||||
- Database connection pooling
|
||||
|
||||
## 🔄 Migration from Existing Deployment
|
||||
|
||||
If you have an existing non-Docker deployment:
|
||||
|
||||
### 1. Backup Current Data
|
||||
```bash
|
||||
# Backup database
|
||||
mysqldump -u trasabilitate -p trasabilitate > backup.sql
|
||||
|
||||
# Backup any uploaded files or custom data
|
||||
cp -r py_app/instance backup_instance/
|
||||
```
|
||||
|
||||
### 2. Deploy Docker Solution
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### 3. Restore Data (if needed)
|
||||
```bash
|
||||
# Restore database
|
||||
docker compose exec -T db mariadb -u trasabilitate -pInitial01! trasabilitate < backup.sql
|
||||
```
|
||||
|
||||
### 4. Stop Old Service
|
||||
```bash
|
||||
# Stop systemd service
|
||||
sudo systemctl stop trasabilitate
|
||||
sudo systemctl disable trasabilitate
|
||||
```
|
||||
|
||||
## 🎓 Learning Resources
|
||||
|
||||
- Docker Compose docs: https://docs.docker.com/compose/
|
||||
- Gunicorn configuration: https://docs.gunicorn.org/
|
||||
- MariaDB Docker: https://hub.docker.com/_/mariadb
|
||||
|
||||
## ✅ Testing Checklist
|
||||
|
||||
After deployment, verify:
|
||||
|
||||
- [ ] Services are running: `docker compose ps`
|
||||
- [ ] App is accessible: http://localhost:8781
|
||||
- [ ] Can log in with superadmin
|
||||
- [ ] Database contains tables: `make shell-db` then `SHOW TABLES;`
|
||||
- [ ] Logs are being written: `ls -la logs/`
|
||||
- [ ] Can restart services: `docker compose restart`
|
||||
- [ ] Data persists after restart
|
||||
|
||||
## 🆘 Support Commands
|
||||
|
||||
```bash
|
||||
# View all services
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Restart a specific service
|
||||
docker compose restart web
|
||||
|
||||
# Access web container shell
|
||||
docker compose exec web bash
|
||||
|
||||
# Access database
|
||||
docker compose exec db mariadb -u trasabilitate -p
|
||||
|
||||
# Check resource usage
|
||||
docker stats
|
||||
|
||||
# Remove everything and start fresh
|
||||
docker compose down -v
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
## 📝 Next Steps
|
||||
|
||||
1. **Install Docker** on the target server (if not already installed)
|
||||
2. **Review and customize** `.env` file after copying from `.env.example`
|
||||
3. **Run deployment**: `./deploy.sh`
|
||||
4. **Change default passwords** after first login
|
||||
5. **Set up reverse proxy** (nginx/traefik) for HTTPS if needed
|
||||
6. **Configure backups** using `make backup-db`
|
||||
7. **Monitor logs** regularly with `make logs`
|
||||
|
||||
## 🎉 Benefits of This Solution
|
||||
|
||||
1. **Portable**: Works on any system with Docker
|
||||
2. **Reproducible**: Same deployment every time
|
||||
3. **Isolated**: No conflicts with system packages
|
||||
4. **Easy Updates**: Just rebuild and restart
|
||||
5. **Scalable**: Can easily add more services
|
||||
6. **Professional**: Production-ready configuration
|
||||
7. **Documented**: Complete documentation included
|
||||
8. **Maintainable**: Simple management commands
|
||||
|
||||
---
|
||||
|
||||
**Your Flask application is now ready for modern, containerized deployment! 🚀**
|
||||
@@ -1,280 +0,0 @@
|
||||
# ✅ Docker Solution - Files Created
|
||||
|
||||
## 📦 Complete Docker Deployment Package
|
||||
|
||||
Your Flask application has been packaged into a complete Docker solution. Here's everything that was created:
|
||||
|
||||
### Core Docker Files
|
||||
|
||||
```
|
||||
/srv/quality_recticel/
|
||||
├── Dockerfile # Flask app container definition
|
||||
├── docker-compose.yml # Multi-container orchestration
|
||||
├── docker-entrypoint.sh # Container initialization script
|
||||
├── init-db.sql # MariaDB initialization
|
||||
├── .dockerignore # Build optimization
|
||||
└── .env.example # Configuration template
|
||||
```
|
||||
|
||||
### Deployment & Management
|
||||
|
||||
```
|
||||
├── deploy.sh # One-command deployment script
|
||||
├── Makefile # Management commands (make up, make down, etc.)
|
||||
├── README-DOCKER.md # Quick start guide
|
||||
├── DOCKER_DEPLOYMENT.md # Complete deployment documentation
|
||||
└── DOCKER_SOLUTION_SUMMARY.md # This comprehensive summary
|
||||
```
|
||||
|
||||
### Modified Files
|
||||
|
||||
```
|
||||
py_app/app/db_create_scripts/
|
||||
└── setup_complete_database.py # Updated to support Docker env vars
|
||||
```
|
||||
|
||||
## 🎯 What This Deployment Includes
|
||||
|
||||
### Services
|
||||
1. **Flask Web Application**
|
||||
- Python 3.10
|
||||
- Gunicorn WSGI server (production-ready)
|
||||
- Auto-generated database configuration
|
||||
- Health checks
|
||||
- Automatic restart on failure
|
||||
|
||||
2. **MariaDB 11.3 Database**
|
||||
- Automatic initialization
|
||||
- User and database creation
|
||||
- Data persistence (Docker volume)
|
||||
- Health checks
|
||||
|
||||
### Features
|
||||
- ✅ Single-command deployment
|
||||
- ✅ Automatic database schema setup
|
||||
- ✅ Superadmin user seeding
|
||||
- ✅ Data persistence across restarts
|
||||
- ✅ Container health monitoring
|
||||
- ✅ Log collection and management
|
||||
- ✅ Production-ready configuration
|
||||
- ✅ Easy backup and restore
|
||||
- ✅ Complete isolation from host system
|
||||
|
||||
## 🚀 How to Deploy
|
||||
|
||||
### Prerequisites
|
||||
**Install Docker first:**
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
sudo usermod -aG docker $USER
|
||||
# Log out and back in
|
||||
```
|
||||
|
||||
### Deploy
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
That's it! Your application will be available at http://localhost:8781
|
||||
|
||||
## 📋 Usage Examples
|
||||
|
||||
### Basic Operations
|
||||
```bash
|
||||
# Start services
|
||||
docker compose up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Stop services
|
||||
docker compose down
|
||||
|
||||
# Restart
|
||||
docker compose restart
|
||||
|
||||
# Check status
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
### Using Makefile (Recommended)
|
||||
```bash
|
||||
make install # First-time setup
|
||||
make up # Start services
|
||||
make down # Stop services
|
||||
make logs # View logs
|
||||
make logs-web # View only web logs
|
||||
make logs-db # View only database logs
|
||||
make shell # Access app container
|
||||
make shell-db # Access database console
|
||||
make backup-db # Backup database
|
||||
make status # Show service status
|
||||
make help # Show all commands
|
||||
```
|
||||
|
||||
### Advanced Operations
|
||||
```bash
|
||||
# Rebuild after code changes
|
||||
docker compose up -d --build web
|
||||
|
||||
# Access application shell
|
||||
docker compose exec web bash
|
||||
|
||||
# Run database commands
|
||||
docker compose exec db mariadb -u trasabilitate -p trasabilitate
|
||||
|
||||
# View resource usage
|
||||
docker stats recticel-app recticel-db
|
||||
|
||||
# Complete reset (removes all data!)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## 🗂️ Data Storage
|
||||
|
||||
### Persistent Data
|
||||
- **Database**: Stored in Docker volume `mariadb_data`
|
||||
- **Logs**: Mounted to `./logs` directory
|
||||
- **Config**: Mounted to `./instance` directory
|
||||
|
||||
### Backup Database
|
||||
```bash
|
||||
docker compose exec -T db mariadb-dump -u trasabilitate -pInitial01! trasabilitate > backup.sql
|
||||
```
|
||||
|
||||
### Restore Database
|
||||
```bash
|
||||
docker compose exec -T db mariadb -u trasabilitate -pInitial01! trasabilitate < backup.sql
|
||||
```
|
||||
|
||||
## 🔐 Default Credentials
|
||||
|
||||
### Application
|
||||
- URL: http://localhost:8781
|
||||
- Username: `superadmin`
|
||||
- Password: `superadmin123`
|
||||
- **⚠️ Change after first login!**
|
||||
|
||||
### Database
|
||||
- Host: `localhost:3306` (from host) or `db:3306` (from containers)
|
||||
- Database: `trasabilitate`
|
||||
- User: `trasabilitate`
|
||||
- Password: `Initial01!`
|
||||
- Root Password: Set in `.env` file
|
||||
|
||||
## 📊 Service Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ recticel-network (Docker) │
|
||||
│ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ recticel-db │ │ recticel-app │ │
|
||||
│ │ (MariaDB 11.3) │◄───────┤ (Flask/Python) │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ - Internal DB │ │ - Gunicorn │ │
|
||||
│ │ - Health Check │ │ - Health Check │ │
|
||||
│ │ - Auto Init │ │ - Auto Config │ │
|
||||
│ └────────┬────────┘ └────────┬────────┘ │
|
||||
│ │ │ │
|
||||
│ │ 3306 (optional) 8781 │ │
|
||||
└────────────┼──────────────────────────┼────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
[mariadb_data] [Host: 8781]
|
||||
Docker Volume Application Access
|
||||
```
|
||||
|
||||
## 🎓 Quick Reference
|
||||
|
||||
### Environment Variables (.env)
|
||||
```env
|
||||
MYSQL_ROOT_PASSWORD=rootpassword # MariaDB root password
|
||||
DB_PORT=3306 # Database port (external)
|
||||
APP_PORT=8781 # Application port
|
||||
INIT_DB=true # Run DB initialization
|
||||
SEED_DB=true # Seed superadmin user
|
||||
```
|
||||
|
||||
### Important Ports
|
||||
- `8781`: Flask application (web interface)
|
||||
- `3306`: MariaDB database (optional external access)
|
||||
|
||||
### Log Locations
|
||||
- Application logs: `./logs/access.log` and `./logs/error.log`
|
||||
- Container logs: `docker compose logs`
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Can't connect to application
|
||||
```bash
|
||||
# Check if services are running
|
||||
docker compose ps
|
||||
|
||||
# Check web logs
|
||||
docker compose logs web
|
||||
|
||||
# Verify port not in use
|
||||
netstat -tuln | grep 8781
|
||||
```
|
||||
|
||||
### Database connection issues
|
||||
```bash
|
||||
# Check database health
|
||||
docker compose exec db healthcheck.sh --connect
|
||||
|
||||
# View database logs
|
||||
docker compose logs db
|
||||
|
||||
# Test database connection
|
||||
docker compose exec web python3 -c "import mariadb; print('OK')"
|
||||
```
|
||||
|
||||
### Port already in use
|
||||
Edit `.env` file:
|
||||
```env
|
||||
APP_PORT=8782 # Change to available port
|
||||
DB_PORT=3307 # Change if needed
|
||||
```
|
||||
|
||||
### Start completely fresh
|
||||
```bash
|
||||
docker compose down -v
|
||||
rm -rf logs/* instance/external_server.conf
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
## 📖 Documentation Files
|
||||
|
||||
1. **README-DOCKER.md** - Quick start guide (start here!)
|
||||
2. **DOCKER_DEPLOYMENT.md** - Complete deployment guide
|
||||
3. **DOCKER_SOLUTION_SUMMARY.md** - Comprehensive overview
|
||||
4. **FILES_CREATED.md** - This file
|
||||
|
||||
## ✨ Benefits
|
||||
|
||||
- **No System Dependencies**: Only Docker required
|
||||
- **Portable**: Deploy on any system with Docker
|
||||
- **Reproducible**: Consistent deployments every time
|
||||
- **Isolated**: No conflicts with other applications
|
||||
- **Production-Ready**: Gunicorn, health checks, proper logging
|
||||
- **Easy Management**: Simple commands, one-line deployment
|
||||
- **Persistent**: Data survives container restarts
|
||||
- **Scalable**: Easy to add more services
|
||||
|
||||
## 🎉 Success!
|
||||
|
||||
Your Recticel Quality Application is now containerized and ready for deployment!
|
||||
|
||||
**Next Steps:**
|
||||
1. Install Docker (if not already installed)
|
||||
2. Run `./deploy.sh`
|
||||
3. Access http://localhost:8781
|
||||
4. Log in with superadmin credentials
|
||||
5. Change default passwords
|
||||
6. Enjoy your containerized application!
|
||||
|
||||
For detailed instructions, see **README-DOCKER.md** or **DOCKER_DEPLOYMENT.md**.
|
||||
@@ -1,73 +0,0 @@
|
||||
# 🚀 Quick Start - Docker Deployment
|
||||
|
||||
## What You Need
|
||||
- A server with Docker installed
|
||||
- 2GB free disk space
|
||||
- Ports 8781 and 3306 available
|
||||
|
||||
## Deploy in 3 Steps
|
||||
|
||||
### 1️⃣ Install Docker (if not already installed)
|
||||
|
||||
**Ubuntu/Debian:**
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
Then log out and back in.
|
||||
|
||||
### 2️⃣ Deploy the Application
|
||||
```bash
|
||||
cd /srv/quality_recticel
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### 3️⃣ Access Your Application
|
||||
Open browser: **http://localhost:8781**
|
||||
|
||||
**Login:**
|
||||
- Username: `superadmin`
|
||||
- Password: `superadmin123`
|
||||
|
||||
## 🎯 Done!
|
||||
|
||||
Your complete application with database is now running in Docker containers.
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Stop services
|
||||
docker compose down
|
||||
|
||||
# Restart services
|
||||
docker compose restart
|
||||
|
||||
# Backup database
|
||||
docker compose exec -T db mariadb-dump -u trasabilitate -pInitial01! trasabilitate > backup.sql
|
||||
```
|
||||
|
||||
## 📚 Full Documentation
|
||||
|
||||
See `DOCKER_DEPLOYMENT.md` for complete documentation.
|
||||
|
||||
## 🆘 Problems?
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
docker compose ps
|
||||
|
||||
# View detailed logs
|
||||
docker compose logs -f web
|
||||
|
||||
# Start fresh
|
||||
docker compose down -v
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** This is a production-ready deployment using Gunicorn WSGI server, MariaDB 11.3, and proper health checks.
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to add modules column to external database and migrate existing users
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import mariadb
|
||||
|
||||
def migrate_external_database():
|
||||
"""Add modules column to external database and update existing users"""
|
||||
try:
|
||||
# Read external database configuration from instance folder
|
||||
config_file = os.path.join(os.path.dirname(__file__), 'instance/external_server.conf')
|
||||
if not os.path.exists(config_file):
|
||||
print("External database configuration file not found at instance/external_server.conf")
|
||||
return False
|
||||
|
||||
with open(config_file, 'r') as f:
|
||||
lines = f.read().strip().split('\n')
|
||||
|
||||
# Parse the config file format "key=value"
|
||||
config = {}
|
||||
for line in lines:
|
||||
if '=' in line and not line.strip().startswith('#'):
|
||||
key, value = line.split('=', 1)
|
||||
config[key.strip()] = value.strip()
|
||||
|
||||
host = config.get('server_domain', 'localhost')
|
||||
port = int(config.get('port', '3306'))
|
||||
database = config.get('database_name', '')
|
||||
user = config.get('username', '')
|
||||
password = config.get('password', '')
|
||||
|
||||
if not all([host, database, user, password]):
|
||||
print("Missing required database configuration values.")
|
||||
return False
|
||||
|
||||
print(f"Connecting to external database: {host}:{port}/{database}")
|
||||
|
||||
# Connect to external database
|
||||
conn = mariadb.connect(
|
||||
user=user,
|
||||
password=password,
|
||||
host=host,
|
||||
port=port,
|
||||
database=database
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if users table exists
|
||||
cursor.execute("SHOW TABLES LIKE 'users'")
|
||||
if not cursor.fetchone():
|
||||
print("Users table not found in external database.")
|
||||
conn.close()
|
||||
return False
|
||||
|
||||
# Check if modules column already exists
|
||||
cursor.execute("DESCRIBE users")
|
||||
columns = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
if 'modules' not in columns:
|
||||
print("Adding modules column to users table...")
|
||||
cursor.execute("ALTER TABLE users ADD COLUMN modules TEXT")
|
||||
print("Modules column added successfully.")
|
||||
else:
|
||||
print("Modules column already exists.")
|
||||
|
||||
# Get current users and convert their roles
|
||||
cursor.execute("SELECT id, username, role FROM users")
|
||||
users = cursor.fetchall()
|
||||
|
||||
role_mapping = {
|
||||
'superadmin': ('superadmin', None),
|
||||
'administrator': ('admin', None),
|
||||
'admin': ('admin', None),
|
||||
'quality': ('manager', '["quality"]'),
|
||||
'warehouse': ('manager', '["warehouse"]'),
|
||||
'warehouse_manager': ('manager', '["warehouse"]'),
|
||||
'scan': ('worker', '["quality"]'),
|
||||
'etichete': ('manager', '["labels"]'),
|
||||
'quality_manager': ('manager', '["quality"]'),
|
||||
'quality_worker': ('worker', '["quality"]'),
|
||||
}
|
||||
|
||||
print(f"Migrating {len(users)} users...")
|
||||
|
||||
for user_id, username, old_role in users:
|
||||
if old_role in role_mapping:
|
||||
new_role, modules_json = role_mapping[old_role]
|
||||
|
||||
cursor.execute("UPDATE users SET role = ?, modules = ? WHERE id = ?",
|
||||
(new_role, modules_json, user_id))
|
||||
|
||||
print(f" {username}: {old_role} -> {new_role} with modules {modules_json}")
|
||||
else:
|
||||
print(f" {username}: Unknown role '{old_role}', keeping as-is")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("External database migration completed successfully!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error migrating external database: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("External Database Migration for Simplified 4-Tier Permission System")
|
||||
print("=" * 70)
|
||||
|
||||
success = migrate_external_database()
|
||||
|
||||
if success:
|
||||
print("\n✅ Migration completed successfully!")
|
||||
print("\nUsers can now log in with the new simplified permission system.")
|
||||
print("Role structure: superadmin → admin → manager → worker")
|
||||
print("Modules: quality, warehouse, labels")
|
||||
else:
|
||||
print("\n❌ Migration failed. Please check the error messages above.")
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migration script to convert from complex permission system to simplified 4-tier system
|
||||
This script will:
|
||||
1. Add 'modules' column to users table
|
||||
2. Convert existing roles to new 4-tier system
|
||||
3. Assign appropriate modules based on old roles
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add the app directory to Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
def get_db_connections():
|
||||
"""Get both internal SQLite and external database connections"""
|
||||
connections = {}
|
||||
|
||||
# Internal SQLite database
|
||||
internal_db_path = os.path.join(os.path.dirname(__file__), 'instance/users.db')
|
||||
if os.path.exists(internal_db_path):
|
||||
connections['internal'] = sqlite3.connect(internal_db_path)
|
||||
print(f"Connected to internal SQLite database: {internal_db_path}")
|
||||
|
||||
# External database (try to connect using existing method)
|
||||
try:
|
||||
import mariadb
|
||||
|
||||
# Read external database configuration
|
||||
config_file = os.path.join(os.path.dirname(__file__), '../external_database_settings')
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r') as f:
|
||||
lines = f.read().strip().split('\n')
|
||||
if len(lines) >= 5:
|
||||
host = lines[0].strip()
|
||||
port = int(lines[1].strip())
|
||||
database = lines[2].strip()
|
||||
user = lines[3].strip()
|
||||
password = lines[4].strip()
|
||||
|
||||
conn = mariadb.connect(
|
||||
user=user,
|
||||
password=password,
|
||||
host=host,
|
||||
port=port,
|
||||
database=database
|
||||
)
|
||||
connections['external'] = conn
|
||||
print(f"Connected to external MariaDB database: {host}:{port}/{database}")
|
||||
except Exception as e:
|
||||
print(f"Could not connect to external database: {e}")
|
||||
|
||||
return connections
|
||||
|
||||
def role_mapping():
|
||||
"""Map old roles to new 4-tier system"""
|
||||
return {
|
||||
# Old role -> (new_role, modules)
|
||||
'superadmin': ('superadmin', []), # All modules by default
|
||||
'administrator': ('admin', []), # All modules by default
|
||||
'admin': ('admin', []), # All modules by default
|
||||
'quality': ('manager', ['quality']),
|
||||
'warehouse': ('manager', ['warehouse']),
|
||||
'warehouse_manager': ('manager', ['warehouse']),
|
||||
'scan': ('worker', ['quality']), # Assume scan users are quality workers
|
||||
'etichete': ('manager', ['labels']),
|
||||
'quality_manager': ('manager', ['quality']),
|
||||
'quality_worker': ('worker', ['quality']),
|
||||
}
|
||||
|
||||
def migrate_database(conn, db_type):
|
||||
"""Migrate a specific database"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
print(f"Migrating {db_type} database...")
|
||||
|
||||
# Check if users table exists
|
||||
if db_type == 'internal':
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
|
||||
else: # external/MariaDB
|
||||
cursor.execute("SHOW TABLES LIKE 'users'")
|
||||
|
||||
if not cursor.fetchone():
|
||||
print(f"No users table found in {db_type} database")
|
||||
return
|
||||
|
||||
# Check if modules column already exists
|
||||
try:
|
||||
if db_type == 'internal':
|
||||
cursor.execute("PRAGMA table_info(users)")
|
||||
columns = [row[1] for row in cursor.fetchall()]
|
||||
else: # external/MariaDB
|
||||
cursor.execute("DESCRIBE users")
|
||||
columns = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
if 'modules' not in columns:
|
||||
print(f"Adding modules column to {db_type} database...")
|
||||
if db_type == 'internal':
|
||||
cursor.execute("ALTER TABLE users ADD COLUMN modules TEXT")
|
||||
else: # external/MariaDB
|
||||
cursor.execute("ALTER TABLE users ADD COLUMN modules TEXT")
|
||||
else:
|
||||
print(f"Modules column already exists in {db_type} database")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error checking/adding modules column in {db_type}: {e}")
|
||||
return
|
||||
|
||||
# Get current users
|
||||
cursor.execute("SELECT id, username, role FROM users")
|
||||
users = cursor.fetchall()
|
||||
|
||||
print(f"Found {len(users)} users in {db_type} database")
|
||||
|
||||
# Convert roles and assign modules
|
||||
mapping = role_mapping()
|
||||
updates = []
|
||||
|
||||
for user_id, username, old_role in users:
|
||||
if old_role in mapping:
|
||||
new_role, modules = mapping[old_role]
|
||||
modules_json = json.dumps(modules) if modules else None
|
||||
updates.append((new_role, modules_json, user_id, username))
|
||||
print(f" {username}: {old_role} -> {new_role} with modules {modules}")
|
||||
else:
|
||||
print(f" {username}: Unknown role '{old_role}', keeping as-is")
|
||||
|
||||
# Apply updates
|
||||
for new_role, modules_json, user_id, username in updates:
|
||||
try:
|
||||
cursor.execute("UPDATE users SET role = ?, modules = ? WHERE id = ?",
|
||||
(new_role, modules_json, user_id))
|
||||
print(f" Updated {username} successfully")
|
||||
except Exception as e:
|
||||
print(f" Error updating {username}: {e}")
|
||||
|
||||
conn.commit()
|
||||
print(f"Migration completed for {db_type} database")
|
||||
|
||||
def main():
|
||||
"""Main migration function"""
|
||||
print("Starting migration to simplified 4-tier permission system...")
|
||||
print("="*60)
|
||||
|
||||
connections = get_db_connections()
|
||||
|
||||
if not connections:
|
||||
print("No database connections available. Please check your configuration.")
|
||||
return
|
||||
|
||||
for db_type, conn in connections.items():
|
||||
try:
|
||||
migrate_database(conn, db_type)
|
||||
print()
|
||||
except Exception as e:
|
||||
print(f"Error migrating {db_type} database: {e}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
print("Migration completed!")
|
||||
print("\nNew role structure:")
|
||||
print("- superadmin: Full system access")
|
||||
print("- admin: Full app access (except role_permissions and download_extension)")
|
||||
print("- manager: Module-based access (can have multiple modules)")
|
||||
print("- worker: Limited module access (one module only)")
|
||||
print("\nAvailable modules: quality, warehouse, labels")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the new simplified 4-tier permission system
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
||||
|
||||
from permissions_simple import check_access, validate_user_modules, get_user_accessible_pages
|
||||
|
||||
def test_permission_system():
|
||||
"""Test the new permission system with various scenarios"""
|
||||
print("Testing Simplified 4-Tier Permission System")
|
||||
print("=" * 50)
|
||||
|
||||
# Test cases: (role, modules, page, expected_result)
|
||||
test_cases = [
|
||||
# Superadmin tests
|
||||
('superadmin', [], 'dashboard', True),
|
||||
('superadmin', [], 'role_permissions', True),
|
||||
('superadmin', [], 'quality', True),
|
||||
('superadmin', [], 'warehouse', True),
|
||||
|
||||
# Admin tests
|
||||
('admin', [], 'dashboard', True),
|
||||
('admin', [], 'role_permissions', False), # Restricted for admin
|
||||
('admin', [], 'download_extension', False), # Restricted for admin
|
||||
('admin', [], 'quality', True),
|
||||
('admin', [], 'warehouse', True),
|
||||
|
||||
# Manager tests
|
||||
('manager', ['quality'], 'quality', True),
|
||||
('manager', ['quality'], 'quality_reports', True),
|
||||
('manager', ['quality'], 'warehouse', False), # No warehouse module
|
||||
('manager', ['warehouse'], 'warehouse', True),
|
||||
('manager', ['warehouse'], 'quality', False), # No quality module
|
||||
('manager', ['quality', 'warehouse'], 'quality', True), # Multiple modules
|
||||
('manager', ['quality', 'warehouse'], 'warehouse', True),
|
||||
|
||||
# Worker tests
|
||||
('worker', ['quality'], 'quality', True),
|
||||
('worker', ['quality'], 'quality_reports', False), # Workers can't access reports
|
||||
('worker', ['quality'], 'warehouse', False), # No warehouse module
|
||||
('worker', ['warehouse'], 'move_orders', True),
|
||||
('worker', ['warehouse'], 'create_locations', False), # Workers can't create locations
|
||||
|
||||
# Invalid role test
|
||||
('invalid_role', ['quality'], 'quality', False),
|
||||
]
|
||||
|
||||
print("Testing access control:")
|
||||
print("-" * 30)
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for role, modules, page, expected in test_cases:
|
||||
result = check_access(role, modules, page)
|
||||
status = "PASS" if result == expected else "FAIL"
|
||||
print(f"{status}: {role:12} {str(modules):20} {page:18} -> {result} (expected {expected})")
|
||||
|
||||
if result == expected:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
print(f"\nResults: {passed} passed, {failed} failed")
|
||||
|
||||
# Test module validation
|
||||
print("\nTesting module validation:")
|
||||
print("-" * 30)
|
||||
|
||||
validation_tests = [
|
||||
('superadmin', ['quality'], True), # Superadmin can have any modules
|
||||
('admin', ['warehouse'], True), # Admin can have any modules
|
||||
('manager', ['quality'], True), # Manager can have one module
|
||||
('manager', ['quality', 'warehouse'], True), # Manager can have multiple modules
|
||||
('manager', [], False), # Manager must have at least one module
|
||||
('worker', ['quality'], True), # Worker can have one module
|
||||
('worker', ['quality', 'warehouse'], False), # Worker cannot have multiple modules
|
||||
('worker', [], False), # Worker must have exactly one module
|
||||
('invalid_role', ['quality'], False), # Invalid role
|
||||
]
|
||||
|
||||
for role, modules, expected in validation_tests:
|
||||
is_valid, error_msg = validate_user_modules(role, modules)
|
||||
status = "PASS" if is_valid == expected else "FAIL"
|
||||
print(f"{status}: {role:12} {str(modules):20} -> {is_valid} (expected {expected})")
|
||||
if error_msg:
|
||||
print(f" Error: {error_msg}")
|
||||
|
||||
# Test accessible pages for different users
|
||||
print("\nTesting accessible pages:")
|
||||
print("-" * 30)
|
||||
|
||||
user_tests = [
|
||||
('superadmin', []),
|
||||
('admin', []),
|
||||
('manager', ['quality']),
|
||||
('manager', ['warehouse']),
|
||||
('worker', ['quality']),
|
||||
('worker', ['warehouse']),
|
||||
]
|
||||
|
||||
for role, modules in user_tests:
|
||||
accessible_pages = get_user_accessible_pages(role, modules)
|
||||
print(f"{role:12} {str(modules):20} -> {len(accessible_pages)} pages: {', '.join(accessible_pages[:5])}{'...' if len(accessible_pages) > 5 else ''}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_permission_system()
|
||||
263
py_app/ENHANCED_PRINT_CONTROLLER.md
Executable file
263
py_app/ENHANCED_PRINT_CONTROLLER.md
Executable file
@@ -0,0 +1,263 @@
|
||||
# Enhanced Print Controller - Features & Usage
|
||||
|
||||
## Overview
|
||||
The print module now includes an advanced Print Controller with real-time monitoring, error detection, pause/resume functionality, and automatic reprint capabilities for handling printer issues like paper jams or running out of paper.
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. **Real-Time Progress Modal**
|
||||
- Visual progress bar showing percentage completion
|
||||
- Live counter showing "X / Y" labels printed
|
||||
- Status messages updating in real-time
|
||||
- Detailed event log with timestamps
|
||||
|
||||
### 2. **Print Status Log**
|
||||
- Timestamped entries for all print events
|
||||
- Color-coded status messages:
|
||||
- **Green**: Successful operations
|
||||
- **Yellow**: Warnings and paused states
|
||||
- **Red**: Errors and failures
|
||||
- Auto-scrolling to show latest events
|
||||
- Scrollable history of all print activities
|
||||
|
||||
### 3. **Control Buttons**
|
||||
|
||||
#### **⏸️ Pause Button**
|
||||
- Pauses printing between labels
|
||||
- Useful for:
|
||||
- Checking printer paper level
|
||||
- Inspecting print quality
|
||||
- Loading more paper
|
||||
- Progress bar turns yellow when paused
|
||||
|
||||
#### **▶️ Resume Button**
|
||||
- Resumes printing from where it was paused
|
||||
- Appears when print job is paused
|
||||
- Progress bar returns to normal green
|
||||
|
||||
#### **🔄 Reprint Last Button**
|
||||
- Available after each successful print
|
||||
- Reprints the last completed label
|
||||
- Useful when:
|
||||
- Label came out damaged
|
||||
- Print quality was poor
|
||||
- Label fell on the floor
|
||||
|
||||
#### **❌ Cancel Button**
|
||||
- Stops the entire print job
|
||||
- Shows how many labels were completed
|
||||
- Progress bar turns red
|
||||
- Database not updated on cancellation
|
||||
|
||||
### 4. **Automatic Error Detection**
|
||||
- Detects when a label fails to print
|
||||
- Automatically pauses the job
|
||||
- Shows error message in log
|
||||
- Progress bar turns red
|
||||
- Prompts user to check printer
|
||||
|
||||
### 5. **Automatic Recovery**
|
||||
When a print error occurs:
|
||||
1. Job pauses automatically
|
||||
2. Error is logged with timestamp
|
||||
3. User checks and fixes printer issue (refill paper, clear jam)
|
||||
4. User clicks "Resume"
|
||||
5. Failed label is automatically retried
|
||||
6. If retry succeeds, continues with next label
|
||||
7. If retry fails, user can cancel or try again
|
||||
|
||||
### 6. **Smart Print Management**
|
||||
- Tracks current label being printed
|
||||
- Remembers last successfully printed label
|
||||
- Maintains list of failed labels
|
||||
- Prevents duplicate printing
|
||||
- Sequential label numbering (CP00000777/001, 002, 003...)
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
### Normal Printing Workflow
|
||||
|
||||
1. **Start Print Job**
|
||||
- Select an order from the table
|
||||
- Click "Print Label (QZ Tray)" button
|
||||
- Print Controller modal appears
|
||||
|
||||
2. **Monitor Progress**
|
||||
- Watch progress bar fill (green)
|
||||
- Check "X / Y" counter
|
||||
- Read status messages
|
||||
- View timestamped log entries
|
||||
|
||||
3. **Completion**
|
||||
- All labels print successfully
|
||||
- Database updates automatically
|
||||
- Table refreshes to show new status
|
||||
- Modal closes automatically
|
||||
- Success notification appears
|
||||
|
||||
### Handling Paper Running Out Mid-Print
|
||||
|
||||
**Scenario**: Printing 20 labels, paper runs out after label 12
|
||||
|
||||
1. **Detection**
|
||||
- Label 13 fails to print
|
||||
- Controller detects error
|
||||
- Job pauses automatically
|
||||
- Progress bar turns red
|
||||
- Log shows: "✗ Label 13 failed: Print error"
|
||||
- Status: "⚠️ ERROR - Check printer (paper jam/out of paper)"
|
||||
|
||||
2. **User Action**
|
||||
- Check printer
|
||||
- See paper is empty
|
||||
- Load new paper roll
|
||||
- Ensure paper is feeding correctly
|
||||
|
||||
3. **Resume Printing**
|
||||
- Click "▶️ Resume" button
|
||||
- Controller automatically retries label 13
|
||||
- Log shows: "Retrying label 13..."
|
||||
- If successful: "✓ Label 13 printed successfully (retry)"
|
||||
- Continues with labels 14-20
|
||||
- Job completes normally
|
||||
|
||||
### Handling Print Quality Issues
|
||||
|
||||
**Scenario**: Label 5 of 10 prints too light
|
||||
|
||||
1. **During Print**
|
||||
- Wait for label 5 to complete
|
||||
- "🔄 Reprint Last" button appears
|
||||
- Click button
|
||||
- Label 5 reprints with current settings
|
||||
|
||||
2. **Adjust & Continue**
|
||||
- Adjust printer darkness setting
|
||||
- Click "▶️ Resume" if paused
|
||||
- Continue printing labels 6-10
|
||||
|
||||
### Manual Pause for Inspection
|
||||
|
||||
**Scenario**: Want to check label quality mid-batch
|
||||
|
||||
1. Click "⏸️ Pause" button
|
||||
2. Progress bar turns yellow
|
||||
3. Remove and inspect last printed label
|
||||
4. If good: Click "▶️ Resume"
|
||||
5. If bad:
|
||||
- Click "🔄 Reprint Last"
|
||||
- Adjust printer settings
|
||||
- Click "▶️ Resume"
|
||||
|
||||
### Emergency Cancellation
|
||||
|
||||
**Scenario**: Wrong order selected or major printer malfunction
|
||||
|
||||
1. Click "❌ Cancel" button
|
||||
2. Printing stops immediately
|
||||
3. Log shows labels completed (e.g., "7 of 25")
|
||||
4. Progress bar turns red
|
||||
5. Modal stays open for 2 seconds
|
||||
6. Warning notification appears
|
||||
7. Database NOT updated
|
||||
8. Can reprint the order later
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Print Controller State
|
||||
```javascript
|
||||
{
|
||||
isPaused: false, // Whether job is paused
|
||||
isCancelled: false, // Whether job is cancelled
|
||||
currentLabel: 0, // Current label number being printed
|
||||
totalLabels: 0, // Total labels in job
|
||||
lastPrintedLabel: 0, // Last successfully printed label
|
||||
failedLabels: [], // Array of failed label numbers
|
||||
orderData: null, // Order information
|
||||
printerName: null // Selected printer name
|
||||
}
|
||||
```
|
||||
|
||||
### Event Log Format
|
||||
```
|
||||
[17:47:15] Starting print job: 10 labels
|
||||
[17:47:15] Printer: Thermal Printer A
|
||||
[17:47:15] Order: CP00000777
|
||||
[17:47:16] Sending label 1 to printer...
|
||||
[17:47:16] ✓ Label 1 printed successfully
|
||||
```
|
||||
|
||||
### Error Detection
|
||||
- Try/catch around each print operation
|
||||
- Errors trigger automatic pause
|
||||
- Failed label number recorded
|
||||
- Automatic retry on resume
|
||||
|
||||
### Progress Calculation
|
||||
```javascript
|
||||
percentage = (currentLabel / totalLabels) * 100
|
||||
```
|
||||
|
||||
### Color States
|
||||
- **Green**: Normal printing
|
||||
- **Yellow**: Paused (manual or automatic)
|
||||
- **Red**: Error or cancelled
|
||||
|
||||
## Benefits
|
||||
|
||||
1. ✅ **Prevents Wasted Labels**: Automatic recovery from errors
|
||||
2. ✅ **Reduces Operator Stress**: Clear status and easy controls
|
||||
3. ✅ **Handles Paper Depletion**: Auto-pause and retry on paper out
|
||||
4. ✅ **Quality Control**: Easy reprint of damaged labels
|
||||
5. ✅ **Transparency**: Full log of all print activities
|
||||
6. ✅ **Flexibility**: Pause anytime for inspection
|
||||
7. ✅ **Safety**: Cancel button for emergencies
|
||||
8. ✅ **Accuracy**: Sequential numbering maintained even with errors
|
||||
|
||||
## Common Scenarios Handled
|
||||
|
||||
| Issue | Detection | Solution |
|
||||
|-------|-----------|----------|
|
||||
| Paper runs out | Print error on next label | Auto-pause, user refills, resume |
|
||||
| Paper jam | Print error detected | Auto-pause, user clears jam, resume |
|
||||
| Poor print quality | Visual inspection | Reprint last label |
|
||||
| Wrong order selected | User realizes mid-print | Cancel job |
|
||||
| Need to inspect labels | User decision | Pause, inspect, resume |
|
||||
| Label falls on floor | Visual observation | Reprint last label |
|
||||
| Printer offline | Print error | Auto-pause, user fixes, resume |
|
||||
|
||||
## Future Enhancements (Possible)
|
||||
|
||||
- Printer status monitoring (paper level, online/offline)
|
||||
- Print queue for multiple orders
|
||||
- Estimated time remaining
|
||||
- Sound notifications on completion
|
||||
- Email/SMS alerts for long jobs
|
||||
- Print history logging to database
|
||||
- Batch printing multiple orders
|
||||
- Automatic printer reconnection
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Normal Job**: Print 5-10 labels, verify all complete
|
||||
2. **Paper Depletion**: Remove paper mid-job, verify auto-pause and recovery
|
||||
3. **Pause/Resume**: Manually pause mid-job, wait, resume
|
||||
4. **Reprint**: Print job, reprint last label multiple times
|
||||
5. **Cancel**: Start job, cancel after 2-3 labels
|
||||
6. **Error Recovery**: Simulate error, verify automatic retry
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
- Chrome/Edge: ✅ Fully supported
|
||||
- Firefox: ✅ Fully supported
|
||||
- Safari: ✅ Fully supported
|
||||
- Mobile browsers: ⚠️ Desktop recommended for QZ Tray
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Check browser console for detailed error messages
|
||||
- Verify QZ Tray is running and connected
|
||||
- Check printer is online and has paper
|
||||
- Review print status log in modal
|
||||
- Restart QZ Tray if connection issues persist
|
||||
133
py_app/MOBILE_LOGIN_GUIDE.md
Normal file
133
py_app/MOBILE_LOGIN_GUIDE.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Mobile-Responsive Login Page
|
||||
|
||||
## Overview
|
||||
The login page has been enhanced with comprehensive mobile-responsive CSS to provide an optimal user experience across all device types and screen sizes.
|
||||
|
||||
## Mobile-Responsive Features Added
|
||||
|
||||
### 1. **Responsive Breakpoints**
|
||||
- **Tablet (≤768px)**: Column layout, optimized logo and form sizing
|
||||
- **Mobile (≤480px)**: Enhanced touch targets, better spacing
|
||||
- **Small Mobile (≤320px)**: Minimal padding, compact design
|
||||
- **Landscape (height ≤500px)**: Horizontal layout for landscape phones
|
||||
|
||||
### 2. **Layout Adaptations**
|
||||
|
||||
#### Desktop (>768px)
|
||||
- Side-by-side logo and form layout
|
||||
- Large logo (90vh height)
|
||||
- Fixed form width (600px)
|
||||
|
||||
#### Tablet (≤768px)
|
||||
- Vertical stacked layout
|
||||
- Logo height reduced to 30vh
|
||||
- Form width becomes responsive (100%, max 400px)
|
||||
|
||||
#### Mobile (≤480px)
|
||||
- Optimized touch targets (44px minimum)
|
||||
- Increased padding and margins
|
||||
- Better visual hierarchy
|
||||
- Enhanced shadows and border radius
|
||||
|
||||
#### Small Mobile (≤320px)
|
||||
- Minimal padding to maximize space
|
||||
- Compact logo (20vh height)
|
||||
- Reduced font sizes where appropriate
|
||||
|
||||
### 3. **Touch Optimizations**
|
||||
|
||||
#### iOS/Safari Specific
|
||||
- `font-size: 16px` on inputs prevents automatic zoom
|
||||
- Proper touch target sizing (44px minimum)
|
||||
|
||||
#### Touch Device Enhancements
|
||||
- Active states for button presses
|
||||
- Optimized image rendering for high DPI screens
|
||||
- Hover effects disabled on touch devices
|
||||
|
||||
### 4. **Accessibility Improvements**
|
||||
- Proper contrast ratios maintained
|
||||
- Touch targets meet accessibility guidelines
|
||||
- Readable font sizes across all devices
|
||||
- Smooth transitions and animations
|
||||
|
||||
### 5. **Performance Considerations**
|
||||
- CSS-only responsive design (no JavaScript required)
|
||||
- Efficient media queries
|
||||
- Optimized image rendering for retina displays
|
||||
|
||||
## Key CSS Features
|
||||
|
||||
### Flexible Layout
|
||||
```css
|
||||
.login-page {
|
||||
display: flex;
|
||||
flex-direction: column; /* Mobile */
|
||||
justify-content: center;
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Images
|
||||
```css
|
||||
.login-logo {
|
||||
max-height: 25vh; /* Mobile */
|
||||
max-width: 85vw;
|
||||
}
|
||||
```
|
||||
|
||||
### Touch-Friendly Inputs
|
||||
```css
|
||||
.form-container input {
|
||||
padding: 12px;
|
||||
font-size: 16px; /* Prevents iOS zoom */
|
||||
min-height: 44px; /* Touch target size */
|
||||
}
|
||||
```
|
||||
|
||||
### Landscape Optimization
|
||||
```css
|
||||
@media screen and (max-height: 500px) and (orientation: landscape) {
|
||||
.login-page {
|
||||
flex-direction: row; /* Back to horizontal */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
### Device Testing
|
||||
- [ ] iPhone (various sizes)
|
||||
- [ ] Android phones (various sizes)
|
||||
- [ ] iPad/Android tablets
|
||||
- [ ] Desktop browsers with responsive mode
|
||||
|
||||
### Orientation Testing
|
||||
- [ ] Portrait mode on all devices
|
||||
- [ ] Landscape mode on phones
|
||||
- [ ] Landscape mode on tablets
|
||||
|
||||
### Browser Testing
|
||||
- [ ] Safari (iOS)
|
||||
- [ ] Chrome (Android/iOS)
|
||||
- [ ] Firefox Mobile
|
||||
- [ ] Samsung Internet
|
||||
- [ ] Desktop browsers (Chrome, Firefox, Safari, Edge)
|
||||
|
||||
## Browser Support
|
||||
- Modern browsers (ES6+ support)
|
||||
- iOS Safari 12+
|
||||
- Android Chrome 70+
|
||||
- Desktop browsers (last 2 versions)
|
||||
|
||||
## Performance Impact
|
||||
- **CSS Size**: Increased by ~2KB (compressed)
|
||||
- **Load Time**: No impact (CSS only)
|
||||
- **Rendering**: Optimized for mobile GPUs
|
||||
- **Memory**: Minimal additional usage
|
||||
|
||||
## Future Enhancements
|
||||
1. **Dark mode mobile optimizations**
|
||||
2. **Progressive Web App (PWA) features**
|
||||
3. **Biometric authentication UI**
|
||||
4. **Loading states and animations**
|
||||
5. **Error message responsive design**
|
||||
125
py_app/PRINT_PROGRESS_FEATURE.md
Executable file
125
py_app/PRINT_PROGRESS_FEATURE.md
Executable file
@@ -0,0 +1,125 @@
|
||||
# Print Progress Modal Feature
|
||||
|
||||
## Overview
|
||||
Added a visual progress modal that displays during label printing operations via QZ Tray. The modal shows real-time progress, updates the database upon completion, and refreshes the table view automatically.
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### 1. Progress Modal UI
|
||||
- **Modal Overlay**: Full-screen semi-transparent overlay to focus user attention
|
||||
- **Progress Bar**: Animated progress bar showing percentage completion
|
||||
- **Status Messages**: Real-time status updates during printing
|
||||
- **Label Counter**: Shows "X / Y" format for current progress (e.g., "5 / 10")
|
||||
|
||||
### 2. Print Flow Improvements
|
||||
The printing process now follows these steps:
|
||||
|
||||
1. **Validation**: Check QZ Tray connection and printer selection
|
||||
2. **Modal Display**: Show progress modal immediately
|
||||
3. **Sequential Printing**: Print each label one by one with progress updates
|
||||
- Update progress bar after each successful print
|
||||
- Show current label number being printed
|
||||
- 500ms delay between labels for printer processing
|
||||
4. **Database Update**: Call `/update_printed_status/<order_id>` endpoint
|
||||
- Marks the order as printed in the database
|
||||
- Handles errors gracefully (prints still succeed even if DB update fails)
|
||||
5. **Table Refresh**: Automatically click "Load Orders" button to refresh the view
|
||||
6. **Modal Close**: Hide modal after completion
|
||||
7. **Notification**: Show success notification to user
|
||||
|
||||
### 3. Progress Updates
|
||||
The modal displays different status messages:
|
||||
- "Preparing to print..." (initial)
|
||||
- "Printing label X of Y..." (during printing)
|
||||
- "✅ All labels printed! Updating database..." (after prints complete)
|
||||
- "✅ Complete! Refreshing table..." (after DB update)
|
||||
- "⚠️ Labels printed but database update failed" (on DB error)
|
||||
|
||||
### 4. Error Handling
|
||||
- Modal automatically closes on any error
|
||||
- Error notifications shown to user
|
||||
- Database update failures don't prevent successful printing
|
||||
- Graceful degradation if DB update fails
|
||||
|
||||
## Technical Details
|
||||
|
||||
### CSS Styling
|
||||
- **Modal**: Fixed position, z-index 9999, centered layout
|
||||
- **Content Card**: White background, rounded corners, shadow
|
||||
- **Progress Bar**: Linear gradient blue, smooth transitions
|
||||
- **Responsive**: Min-width 400px, max-width 500px
|
||||
|
||||
### JavaScript Functions Modified
|
||||
|
||||
#### `handleQZTrayPrint(selectedRow)`
|
||||
**Changes:**
|
||||
- Added modal element references
|
||||
- Show modal before printing starts
|
||||
- Update progress bar and counter in loop
|
||||
- Call database update endpoint after printing
|
||||
- Handle database update errors
|
||||
- Refresh table automatically
|
||||
- Close modal on completion or error
|
||||
|
||||
### Backend Integration
|
||||
|
||||
#### Endpoint Used: `/update_printed_status/<int:order_id>`
|
||||
- **Method**: POST
|
||||
- **Purpose**: Mark order as printed in database
|
||||
- **Authentication**: Requires superadmin, warehouse_manager, or etichete role
|
||||
- **Response**: JSON with success/error message
|
||||
|
||||
## User Experience Flow
|
||||
|
||||
1. User selects an order row in the table
|
||||
2. User clicks "Print Label (QZ Tray)" button
|
||||
3. Modal appears showing "Preparing to print..."
|
||||
4. Progress bar fills as each label prints
|
||||
5. Counter shows current progress (e.g., "7 / 10")
|
||||
6. After all labels print: "✅ All labels printed! Updating database..."
|
||||
7. Database updates with printed status
|
||||
8. Modal shows "✅ Complete! Refreshing table..."
|
||||
9. Modal closes automatically
|
||||
10. Success notification appears
|
||||
11. Table refreshes showing updated order status
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Visual Feedback**: Users see real-time progress instead of a frozen UI
|
||||
✅ **Status Clarity**: Clear messages about what's happening
|
||||
✅ **Automatic Updates**: Database and UI update without manual intervention
|
||||
✅ **Error Recovery**: Graceful handling of database update failures
|
||||
✅ **Professional UX**: Modern, polished user interface
|
||||
✅ **Non-Blocking**: Progress modal doesn't interfere with printing operation
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **print_module.html**
|
||||
- Added modal HTML structure
|
||||
- Added modal CSS styles
|
||||
- Updated `handleQZTrayPrint()` function
|
||||
- Added database update API call
|
||||
- Added automatic table refresh
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Modal appears when printing starts
|
||||
- [ ] Progress bar animates smoothly
|
||||
- [ ] Counter updates correctly (1/10, 2/10, etc.)
|
||||
- [ ] All labels print successfully
|
||||
- [ ] Database updates after printing
|
||||
- [ ] Table refreshes automatically
|
||||
- [ ] Modal closes after completion
|
||||
- [ ] Success notification appears
|
||||
- [ ] Error handling works (if DB update fails)
|
||||
- [ ] Modal closes on printing errors
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
- Add "Cancel" button to stop printing mid-process
|
||||
- Show estimated time remaining
|
||||
- Add sound notification on completion
|
||||
- Log printing history with timestamps
|
||||
- Add printer status monitoring
|
||||
- Show print queue if multiple orders selected
|
||||
@@ -1,4 +0,0 @@
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: on
|
||||
Address already in use
|
||||
Port 8781 is in use by another program. Either identify and stop that program, or start the server with a different port.
|
||||
@@ -13,10 +13,8 @@ def create_app():
|
||||
db.init_app(app)
|
||||
|
||||
from app.routes import bp as main_bp, warehouse_bp
|
||||
from app.daily_mirror import daily_mirror_bp
|
||||
app.register_blueprint(main_bp, url_prefix='/')
|
||||
app.register_blueprint(warehouse_bp)
|
||||
app.register_blueprint(daily_mirror_bp)
|
||||
|
||||
# Add 'now' function to Jinja2 globals
|
||||
app.jinja_env.globals['now'] = datetime.now
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,98 +0,0 @@
|
||||
"""
|
||||
Simple access control decorators for the 4-tier system
|
||||
"""
|
||||
from functools import wraps
|
||||
from flask import session, redirect, url_for, flash, request
|
||||
from .permissions_simple import check_access, ROLES
|
||||
|
||||
def requires_role(min_role_level=None, required_modules=None, page=None):
|
||||
"""
|
||||
Simple role-based access decorator
|
||||
|
||||
Args:
|
||||
min_role_level (int): Minimum role level required (50, 70, 90, 100)
|
||||
required_modules (list): Required modules for access
|
||||
page (str): Page name for automatic access checking
|
||||
"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
# Check if user is logged in
|
||||
if 'user' not in session:
|
||||
flash('Please log in to access this page.')
|
||||
return redirect(url_for('main.login'))
|
||||
|
||||
user_role = session.get('role')
|
||||
user_modules = session.get('modules', [])
|
||||
|
||||
# If page is specified, use automatic access checking
|
||||
if page:
|
||||
if not check_access(user_role, user_modules, page):
|
||||
flash('Access denied: You do not have permission to access this page.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return f(*args, **kwargs)
|
||||
|
||||
# Manual role level checking
|
||||
if min_role_level:
|
||||
user_level = ROLES.get(user_role, {}).get('level', 0)
|
||||
if user_level < min_role_level:
|
||||
flash('Access denied: Insufficient privileges.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Module requirement checking
|
||||
if required_modules:
|
||||
if user_role in ['superadmin', 'admin']:
|
||||
# Superadmin and admin have access to all modules
|
||||
pass
|
||||
else:
|
||||
if not any(module in user_modules for module in required_modules):
|
||||
flash('Access denied: You do not have access to this module.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
def superadmin_only(f):
|
||||
"""Decorator for superadmin-only pages"""
|
||||
return requires_role(min_role_level=100)(f)
|
||||
|
||||
def admin_plus(f):
|
||||
"""Decorator for admin and superadmin access"""
|
||||
return requires_role(min_role_level=90)(f)
|
||||
|
||||
def manager_plus(f):
|
||||
"""Decorator for manager, admin, and superadmin access"""
|
||||
return requires_role(min_role_level=70)(f)
|
||||
|
||||
def requires_quality_module(f):
|
||||
"""Decorator for quality module access"""
|
||||
return requires_role(required_modules=['quality'])(f)
|
||||
|
||||
def requires_warehouse_module(f):
|
||||
"""Decorator for warehouse module access"""
|
||||
return requires_role(required_modules=['warehouse'])(f)
|
||||
|
||||
def requires_labels_module(f):
|
||||
"""Decorator for labels module access"""
|
||||
return requires_role(required_modules=['labels'])(f)
|
||||
|
||||
def requires_daily_mirror_module(f):
|
||||
"""Decorator for daily mirror module access"""
|
||||
return requires_role(required_modules=['daily_mirror'])(f)
|
||||
|
||||
def quality_manager_plus(f):
|
||||
"""Decorator for quality module manager+ access"""
|
||||
return requires_role(min_role_level=70, required_modules=['quality'])(f)
|
||||
|
||||
def warehouse_manager_plus(f):
|
||||
"""Decorator for warehouse module manager+ access"""
|
||||
return requires_role(min_role_level=70, required_modules=['warehouse'])(f)
|
||||
|
||||
def labels_manager_plus(f):
|
||||
"""Decorator for labels module manager+ access"""
|
||||
return requires_role(min_role_level=70, required_modules=['labels'])(f)
|
||||
|
||||
def daily_mirror_manager_plus(f):
|
||||
"""Decorator for daily mirror module manager+ access"""
|
||||
return requires_role(min_role_level=70, required_modules=['daily_mirror'])(f)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,320 +0,0 @@
|
||||
-- Daily Mirror Database Schema
|
||||
-- Quality Recticel Production Tracking System
|
||||
-- Created: October 24, 2025
|
||||
|
||||
-- =============================================
|
||||
-- ORDERS DATA TABLES
|
||||
-- =============================================
|
||||
|
||||
-- Main Orders Table (from Vizual. Artic. Comenzi Deschise)
|
||||
CREATE TABLE IF NOT EXISTS dm_orders (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id VARCHAR(50) UNIQUE NOT NULL,
|
||||
customer_code VARCHAR(50),
|
||||
customer_name VARCHAR(255),
|
||||
client_order VARCHAR(100),
|
||||
article_code VARCHAR(50),
|
||||
article_description TEXT,
|
||||
quantity_requested INT,
|
||||
delivery_date DATE,
|
||||
order_status VARCHAR(50),
|
||||
priority VARCHAR(20),
|
||||
product_group VARCHAR(100),
|
||||
order_date DATE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_customer (customer_code),
|
||||
INDEX idx_article (article_code),
|
||||
INDEX idx_delivery_date (delivery_date),
|
||||
INDEX idx_order_date (order_date),
|
||||
INDEX idx_status (order_status)
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- PRODUCTION DATA TABLES
|
||||
-- =============================================
|
||||
|
||||
-- Production Orders Table (from Comenzi Productie)
|
||||
CREATE TABLE IF NOT EXISTS dm_production_orders (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
production_order VARCHAR(50) UNIQUE NOT NULL,
|
||||
order_id VARCHAR(50),
|
||||
customer_code VARCHAR(50),
|
||||
customer_name VARCHAR(255),
|
||||
client_order VARCHAR(100),
|
||||
article_code VARCHAR(50),
|
||||
article_description TEXT,
|
||||
quantity_requested INT,
|
||||
delivery_date DATE,
|
||||
production_status VARCHAR(50),
|
||||
|
||||
-- Production Timeline
|
||||
end_of_quilting DATETIME,
|
||||
end_of_sewing DATETIME,
|
||||
data_deschiderii DATE,
|
||||
data_planificare DATE,
|
||||
|
||||
-- Quality Control Stages
|
||||
t1_status DECIMAL(3,1),
|
||||
t1_registration_date DATETIME,
|
||||
t1_operator_name VARCHAR(100),
|
||||
t2_status DECIMAL(3,1),
|
||||
t2_registration_date DATETIME,
|
||||
t2_operator_name VARCHAR(100),
|
||||
t3_status DECIMAL(3,1),
|
||||
t3_registration_date DATETIME,
|
||||
t3_operator_name VARCHAR(100),
|
||||
|
||||
-- Machine and Production Details
|
||||
machine_code VARCHAR(50),
|
||||
machine_type VARCHAR(50),
|
||||
machine_number VARCHAR(20),
|
||||
classification VARCHAR(100),
|
||||
design_number INT,
|
||||
needle_position INT,
|
||||
total_norm_time DECIMAL(8,2),
|
||||
model_lb2 VARCHAR(255),
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_production_order (production_order),
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_customer (customer_code),
|
||||
INDEX idx_article (article_code),
|
||||
INDEX idx_delivery_date (delivery_date),
|
||||
INDEX idx_status (production_status),
|
||||
INDEX idx_machine (machine_code),
|
||||
INDEX idx_quilting_date (end_of_quilting),
|
||||
INDEX idx_sewing_date (end_of_sewing)
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- DELIVERY DATA TABLES
|
||||
-- =============================================
|
||||
|
||||
-- Delivery/Shipment Table (from Articole livrate)
|
||||
CREATE TABLE IF NOT EXISTS dm_deliveries (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
shipment_id VARCHAR(50) UNIQUE NOT NULL,
|
||||
order_id VARCHAR(50),
|
||||
production_order VARCHAR(50),
|
||||
customer_code VARCHAR(50),
|
||||
customer_name VARCHAR(255),
|
||||
article_code VARCHAR(50),
|
||||
article_description TEXT,
|
||||
quantity_delivered INT,
|
||||
quantity_returned INT DEFAULT 0,
|
||||
|
||||
-- Delivery Timeline
|
||||
shipment_date DATE,
|
||||
delivery_date DATE,
|
||||
return_date DATE,
|
||||
|
||||
-- Delivery Status
|
||||
delivery_status VARCHAR(50), -- 'shipped', 'delivered', 'returned', 'partial'
|
||||
shipping_method VARCHAR(100),
|
||||
tracking_number VARCHAR(100),
|
||||
shipping_address TEXT,
|
||||
delivery_notes TEXT,
|
||||
|
||||
-- Financial
|
||||
unit_price DECIMAL(10,2),
|
||||
total_value DECIMAL(12,2),
|
||||
currency VARCHAR(3) DEFAULT 'RON',
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_shipment_id (shipment_id),
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_production_order (production_order),
|
||||
INDEX idx_customer (customer_code),
|
||||
INDEX idx_article (article_code),
|
||||
INDEX idx_shipment_date (shipment_date),
|
||||
INDEX idx_delivery_date (delivery_date),
|
||||
INDEX idx_status (delivery_status)
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- DAILY MIRROR AGGREGATION TABLES
|
||||
-- =============================================
|
||||
|
||||
-- Daily Summary Table (for fast reporting)
|
||||
CREATE TABLE IF NOT EXISTS dm_daily_summary (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
report_date DATE UNIQUE NOT NULL,
|
||||
|
||||
-- Orders Metrics
|
||||
orders_received INT DEFAULT 0,
|
||||
orders_quantity INT DEFAULT 0,
|
||||
orders_value DECIMAL(15,2) DEFAULT 0,
|
||||
unique_customers INT DEFAULT 0,
|
||||
|
||||
-- Production Metrics
|
||||
production_launched INT DEFAULT 0,
|
||||
production_finished INT DEFAULT 0,
|
||||
production_in_progress INT DEFAULT 0,
|
||||
quilting_completed INT DEFAULT 0,
|
||||
sewing_completed INT DEFAULT 0,
|
||||
|
||||
-- Delivery Metrics
|
||||
orders_shipped INT DEFAULT 0,
|
||||
orders_delivered INT DEFAULT 0,
|
||||
orders_returned INT DEFAULT 0,
|
||||
delivery_value DECIMAL(15,2) DEFAULT 0,
|
||||
|
||||
-- Efficiency Metrics
|
||||
on_time_deliveries INT DEFAULT 0,
|
||||
late_deliveries INT DEFAULT 0,
|
||||
active_operators INT DEFAULT 0,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_report_date (report_date)
|
||||
);
|
||||
|
||||
|
||||
-- =============================================
|
||||
-- CONFIGURATION AND LOOKUP TABLES
|
||||
-- =============================================
|
||||
|
||||
-- Customer Master
|
||||
CREATE TABLE IF NOT EXISTS dm_customers (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
customer_name VARCHAR(255) NOT NULL,
|
||||
customer_group VARCHAR(100),
|
||||
country VARCHAR(50),
|
||||
currency VARCHAR(3) DEFAULT 'RON',
|
||||
payment_terms VARCHAR(100),
|
||||
credit_limit DECIMAL(15,2),
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_customer_code (customer_code),
|
||||
INDEX idx_customer_name (customer_name),
|
||||
INDEX idx_customer_group (customer_group)
|
||||
);
|
||||
|
||||
-- Article Master
|
||||
CREATE TABLE IF NOT EXISTS dm_articles (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
article_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
article_description TEXT NOT NULL,
|
||||
product_group VARCHAR(100),
|
||||
classification VARCHAR(100),
|
||||
unit_of_measure VARCHAR(20),
|
||||
standard_price DECIMAL(10,2),
|
||||
standard_time DECIMAL(8,2),
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_article_code (article_code),
|
||||
INDEX idx_product_group (product_group),
|
||||
INDEX idx_classification (classification)
|
||||
);
|
||||
|
||||
-- Machine Master
|
||||
CREATE TABLE IF NOT EXISTS dm_machines (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
machine_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
machine_name VARCHAR(255),
|
||||
machine_type VARCHAR(50),
|
||||
machine_number VARCHAR(20),
|
||||
department VARCHAR(100),
|
||||
capacity_per_hour DECIMAL(8,2),
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
INDEX idx_machine_code (machine_code),
|
||||
INDEX idx_machine_type (machine_type),
|
||||
INDEX idx_department (department)
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- DATA IMPORT TRACKING
|
||||
-- =============================================
|
||||
|
||||
-- Track file uploads and data imports
|
||||
CREATE TABLE IF NOT EXISTS dm_import_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_type VARCHAR(50) NOT NULL, -- 'orders', 'production', 'delivery'
|
||||
upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
uploaded_by VARCHAR(100),
|
||||
records_processed INT DEFAULT 0,
|
||||
records_successful INT DEFAULT 0,
|
||||
records_failed INT DEFAULT 0,
|
||||
status VARCHAR(50) DEFAULT 'processing', -- 'processing', 'completed', 'failed'
|
||||
error_message TEXT,
|
||||
processing_time DECIMAL(8,2), -- seconds
|
||||
|
||||
INDEX idx_upload_date (upload_date),
|
||||
INDEX idx_file_type (file_type),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_uploaded_by (uploaded_by)
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- VIEWS FOR DAILY MIRROR REPORTING
|
||||
-- =============================================
|
||||
|
||||
-- View: Current Production Status
|
||||
CREATE OR REPLACE VIEW v_daily_production_status AS
|
||||
SELECT
|
||||
DATE(p.data_planificare) as production_date,
|
||||
COUNT(*) as total_orders,
|
||||
SUM(p.quantity_requested) as total_quantity,
|
||||
SUM(CASE WHEN p.production_status = 'Inchis' THEN 1 ELSE 0 END) as completed_orders,
|
||||
SUM(CASE WHEN p.production_status != 'Inchis' THEN 1 ELSE 0 END) as pending_orders,
|
||||
SUM(CASE WHEN p.end_of_quilting IS NOT NULL THEN 1 ELSE 0 END) as quilting_done,
|
||||
SUM(CASE WHEN p.end_of_sewing IS NOT NULL THEN 1 ELSE 0 END) as sewing_done,
|
||||
COUNT(DISTINCT p.customer_code) as unique_customers,
|
||||
COUNT(DISTINCT p.machine_code) as machines_used
|
||||
FROM dm_production_orders p
|
||||
WHERE p.data_planificare >= CURDATE() - INTERVAL 30 DAY
|
||||
GROUP BY DATE(p.data_planificare)
|
||||
ORDER BY production_date DESC;
|
||||
|
||||
-- View: Quality Performance Summary
|
||||
CREATE OR REPLACE VIEW v_daily_quality_summary AS
|
||||
SELECT
|
||||
DATE(p.t1_registration_date) as scan_date,
|
||||
COUNT(*) as total_t1_scans,
|
||||
SUM(CASE WHEN p.t1_status = 0 THEN 1 ELSE 0 END) as t1_approved,
|
||||
ROUND(SUM(CASE WHEN p.t1_status = 0 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) as t1_approval_rate,
|
||||
COUNT(CASE WHEN p.t2_registration_date IS NOT NULL THEN 1 END) as total_t2_scans,
|
||||
SUM(CASE WHEN p.t2_status = 0 THEN 1 ELSE 0 END) as t2_approved,
|
||||
ROUND(SUM(CASE WHEN p.t2_status = 0 THEN 1 ELSE 0 END) / COUNT(CASE WHEN p.t2_registration_date IS NOT NULL THEN 1 END) * 100, 2) as t2_approval_rate,
|
||||
COUNT(DISTINCT p.t1_operator_name) as active_operators
|
||||
FROM dm_production_orders p
|
||||
WHERE p.t1_registration_date >= CURDATE() - INTERVAL 30 DAY
|
||||
GROUP BY DATE(p.t1_registration_date)
|
||||
ORDER BY scan_date DESC;
|
||||
|
||||
-- View: Delivery Performance
|
||||
CREATE OR REPLACE VIEW v_daily_delivery_summary AS
|
||||
SELECT
|
||||
d.delivery_date,
|
||||
COUNT(*) as total_deliveries,
|
||||
SUM(d.quantity_delivered) as total_quantity_delivered,
|
||||
SUM(d.total_value) as total_delivery_value,
|
||||
SUM(CASE WHEN d.delivery_date <= o.delivery_date THEN 1 ELSE 0 END) as on_time_deliveries,
|
||||
SUM(CASE WHEN d.delivery_date > o.delivery_date THEN 1 ELSE 0 END) as late_deliveries,
|
||||
COUNT(DISTINCT d.customer_code) as unique_customers
|
||||
FROM dm_deliveries d
|
||||
LEFT JOIN dm_orders o ON d.order_id = o.order_id
|
||||
WHERE d.delivery_date >= CURDATE() - INTERVAL 30 DAY
|
||||
AND d.delivery_status = 'delivered'
|
||||
GROUP BY d.delivery_date
|
||||
ORDER BY d.delivery_date DESC;
|
||||
@@ -1,744 +0,0 @@
|
||||
"""
|
||||
Daily Mirror Database Setup and Management
|
||||
Quality Recticel Application
|
||||
|
||||
This script creates the database schema and provides utilities for
|
||||
data import and Daily Mirror reporting functionality.
|
||||
"""
|
||||
|
||||
import mariadb
|
||||
import pandas as pd
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DailyMirrorDatabase:
|
||||
def __init__(self, host='localhost', user='trasabilitate', password='Initial01!', database='trasabilitate'):
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.database = database
|
||||
self.connection = None
|
||||
|
||||
def connect(self):
|
||||
"""Establish database connection"""
|
||||
try:
|
||||
self.connection = mariadb.connect(
|
||||
host=self.host,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
database=self.database
|
||||
)
|
||||
logger.info("Database connection established")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Database connection failed: {e}")
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
"""Close database connection"""
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
logger.info("Database connection closed")
|
||||
|
||||
def create_database_schema(self):
|
||||
"""Create the Daily Mirror database schema"""
|
||||
try:
|
||||
cursor = self.connection.cursor()
|
||||
|
||||
# Read and execute the schema file
|
||||
schema_file = os.path.join(os.path.dirname(__file__), 'daily_mirror_database_schema.sql')
|
||||
|
||||
if not os.path.exists(schema_file):
|
||||
logger.error(f"Schema file not found: {schema_file}")
|
||||
return False
|
||||
|
||||
with open(schema_file, 'r') as file:
|
||||
schema_sql = file.read()
|
||||
|
||||
# Split by statements and execute each one
|
||||
statements = []
|
||||
current_statement = ""
|
||||
|
||||
for line in schema_sql.split('\n'):
|
||||
line = line.strip()
|
||||
if line and not line.startswith('--'):
|
||||
current_statement += line + " "
|
||||
if line.endswith(';'):
|
||||
statements.append(current_statement.strip())
|
||||
current_statement = ""
|
||||
|
||||
# Add any remaining statement
|
||||
if current_statement.strip():
|
||||
statements.append(current_statement.strip())
|
||||
|
||||
for statement in statements:
|
||||
if statement and any(statement.upper().startswith(cmd) for cmd in ['CREATE', 'ALTER', 'DROP', 'INSERT']):
|
||||
try:
|
||||
cursor.execute(statement)
|
||||
logger.info(f"Executed: {statement[:80]}...")
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
logger.warning(f"Error executing statement: {e}")
|
||||
|
||||
self.connection.commit()
|
||||
logger.info("Database schema created successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating database schema: {e}")
|
||||
return False
|
||||
|
||||
def import_production_data(self, file_path):
|
||||
"""Import production data from Excel file (Comenzi Productie format)"""
|
||||
try:
|
||||
# The correct data is in the first sheet (DataSheet)
|
||||
df = None
|
||||
sheet_used = None
|
||||
|
||||
# Get available sheets
|
||||
excel_file = pd.ExcelFile(file_path)
|
||||
logger.info(f"Available sheets: {excel_file.sheet_names}")
|
||||
|
||||
# Try DataSheet first (where the actual production data is), then fallback options
|
||||
sheet_attempts = [
|
||||
('DataSheet', 'openpyxl'),
|
||||
('DataSheet', 'xlrd'),
|
||||
(0, 'openpyxl'),
|
||||
(0, 'xlrd'),
|
||||
('Sheet1', 'openpyxl'), # fallback to Sheet1 if DataSheet fails
|
||||
(1, 'openpyxl')
|
||||
]
|
||||
|
||||
for sheet_name, engine in sheet_attempts:
|
||||
try:
|
||||
logger.info(f"Trying to read sheet '{sheet_name}' with engine '{engine}'")
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, engine=engine, header=0)
|
||||
sheet_used = f"{sheet_name} (engine: {engine})"
|
||||
logger.info(f"Successfully read from sheet: {sheet_used}")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read sheet {sheet_name} with {engine}: {e}")
|
||||
continue
|
||||
|
||||
# If all engines fail on DataSheet, try a different approach
|
||||
if df is None:
|
||||
try:
|
||||
logger.info("Trying alternative method: reading without specifying engine")
|
||||
df = pd.read_excel(file_path, sheet_name='DataSheet')
|
||||
sheet_used = "DataSheet (default engine)"
|
||||
logger.info("Successfully read with default engine")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed with default engine: {e}")
|
||||
raise Exception("Could not read the DataSheet from the Excel file. The file may be corrupted.")
|
||||
|
||||
logger.info(f"Loaded production data from {sheet_used}: {len(df)} rows, {len(df.columns)} columns")
|
||||
logger.info(f"Available columns: {list(df.columns)}")
|
||||
|
||||
cursor = self.connection.cursor()
|
||||
success_count = 0
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
error_count = 0
|
||||
|
||||
# Prepare insert statement
|
||||
insert_sql = """
|
||||
INSERT INTO dm_production_orders (
|
||||
production_order, customer_code, client_order, article_code,
|
||||
article_description, quantity_requested, delivery_date, production_status,
|
||||
end_of_quilting, end_of_sewing, t1_status, t1_registration_date, t1_operator_name,
|
||||
t2_status, t2_registration_date, t2_operator_name, t3_status, t3_registration_date,
|
||||
t3_operator_name, machine_code, machine_type, classification, total_norm_time,
|
||||
data_deschiderii, model_lb2, data_planificare, machine_number, design_number, needle_position
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
customer_code = VALUES(customer_code),
|
||||
client_order = VALUES(client_order),
|
||||
article_code = VALUES(article_code),
|
||||
article_description = VALUES(article_description),
|
||||
quantity_requested = VALUES(quantity_requested),
|
||||
delivery_date = VALUES(delivery_date),
|
||||
production_status = VALUES(production_status),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
"""
|
||||
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# Prepare data tuple
|
||||
data = (
|
||||
row.get('Comanda Productie', ''),
|
||||
row.get('Customer', ''),
|
||||
row.get('Comanda client', ''),
|
||||
row.get('Cod Articol', ''),
|
||||
row.get('Descriere', ''),
|
||||
row.get('Cantitate ceruta', 0),
|
||||
self._parse_date(row.get('Delivery date')),
|
||||
row.get('Status', ''),
|
||||
self._parse_datetime(row.get('End of Quilting')),
|
||||
self._parse_datetime(row.get('End of sewing')),
|
||||
row.get('T1', 0),
|
||||
self._parse_datetime(row.get('Data inregistrare T1')),
|
||||
row.get('Numele Complet T1', ''),
|
||||
row.get('T2', 0),
|
||||
self._parse_datetime(row.get('Data inregistrare T2')),
|
||||
row.get('Numele Complet T2', ''),
|
||||
row.get('T3', 0),
|
||||
self._parse_datetime(row.get('Data inregistrare T3')),
|
||||
row.get('Numele Complet T3', ''),
|
||||
row.get('Masina Cusut ', ''),
|
||||
row.get('Tip Masina', ''),
|
||||
row.get('Clasificare', ''),
|
||||
row.get('Timp normat total', 0),
|
||||
self._parse_date(row.get('Data Deschiderii')),
|
||||
row.get('Model Lb2', ''),
|
||||
self._parse_date(row.get('Data Planific.')),
|
||||
row.get('Numar masina', ''),
|
||||
row.get('Design nr', 0),
|
||||
row.get('Needle position', 0)
|
||||
)
|
||||
|
||||
cursor.execute(insert_sql, data)
|
||||
|
||||
# Check if row was inserted (created) or updated
|
||||
# In MySQL with ON DUPLICATE KEY UPDATE:
|
||||
# - rowcount = 1 means INSERT (new row created)
|
||||
# - rowcount = 2 means UPDATE (existing row updated)
|
||||
# - rowcount = 0 means no change
|
||||
if cursor.rowcount == 1:
|
||||
created_count += 1
|
||||
elif cursor.rowcount == 2:
|
||||
updated_count += 1
|
||||
|
||||
success_count += 1
|
||||
|
||||
except Exception as row_error:
|
||||
logger.warning(f"Error processing row {index}: {row_error}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Production data import completed: {success_count} successful, {error_count} failed")
|
||||
|
||||
return {
|
||||
'success_count': success_count,
|
||||
'created_count': created_count,
|
||||
'updated_count': updated_count,
|
||||
'error_count': error_count,
|
||||
'total_rows': len(df)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error importing production data: {e}")
|
||||
return None
|
||||
|
||||
def import_orders_data(self, file_path):
|
||||
"""Import orders data from Excel file with enhanced error handling"""
|
||||
try:
|
||||
# Ensure we have a database connection
|
||||
if not self.connection:
|
||||
self.connect()
|
||||
if not self.connection:
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': 'Could not establish database connection.'
|
||||
}
|
||||
|
||||
logger.info(f"Attempting to import orders data from: {file_path}")
|
||||
|
||||
# Check if file exists
|
||||
if not os.path.exists(file_path):
|
||||
logger.error(f"Orders file not found: {file_path}")
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': f'Orders file not found: {file_path}'
|
||||
}
|
||||
|
||||
# Try to get sheet names first
|
||||
try:
|
||||
excel_file = pd.ExcelFile(file_path)
|
||||
sheet_names = excel_file.sheet_names
|
||||
logger.info(f"Available sheets in orders file: {sheet_names}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get sheet names: {e}")
|
||||
sheet_names = ['DataSheet', 'Sheet1']
|
||||
|
||||
# Try multiple approaches to read the Excel file
|
||||
df = None
|
||||
sheet_used = None
|
||||
approaches = [
|
||||
('openpyxl', 0, 'read_only'),
|
||||
('openpyxl', 0, 'normal'),
|
||||
('openpyxl', 1, 'normal'),
|
||||
('xlrd', 0, 'normal') if file_path.endswith('.xls') else None,
|
||||
('default', 0, 'normal')
|
||||
]
|
||||
|
||||
for approach in approaches:
|
||||
if approach is None:
|
||||
continue
|
||||
|
||||
engine, sheet_name, mode = approach
|
||||
try:
|
||||
logger.info(f"Trying to read orders with engine: {engine}, sheet: {sheet_name}, mode: {mode}")
|
||||
|
||||
if engine == 'default':
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, header=0)
|
||||
elif mode == 'read_only':
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, engine=engine, header=0)
|
||||
else:
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, engine=engine, header=0)
|
||||
|
||||
sheet_used = f"{engine} (sheet: {sheet_name}, mode: {mode})"
|
||||
logger.info(f"Successfully read orders data with: {sheet_used}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed with {engine}, sheet {sheet_name}, mode {mode}: {e}")
|
||||
continue
|
||||
|
||||
if df is None:
|
||||
logger.error("Could not read the orders file with any method")
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': 'Could not read the orders Excel file. The file may have formatting issues or be corrupted.'
|
||||
}
|
||||
|
||||
logger.info(f"Loaded orders data from {sheet_used}: {len(df)} rows, {len(df.columns)} columns")
|
||||
logger.info(f"Available columns: {list(df.columns)[:10]}...")
|
||||
|
||||
cursor = self.connection.cursor()
|
||||
success_count = 0
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
error_count = 0
|
||||
|
||||
# Prepare insert statement for orders
|
||||
insert_sql = """
|
||||
INSERT INTO dm_orders (
|
||||
order_id, customer_code, customer_name, client_order,
|
||||
article_code, article_description, quantity_requested, delivery_date,
|
||||
order_status, product_group, order_date
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
customer_code = VALUES(customer_code),
|
||||
customer_name = VALUES(customer_name),
|
||||
client_order = VALUES(client_order),
|
||||
article_code = VALUES(article_code),
|
||||
article_description = VALUES(article_description),
|
||||
quantity_requested = VALUES(quantity_requested),
|
||||
delivery_date = VALUES(delivery_date),
|
||||
order_status = VALUES(order_status),
|
||||
product_group = VALUES(product_group),
|
||||
order_date = VALUES(order_date),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
"""
|
||||
|
||||
# Process each row with the actual column mapping and better null handling
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# Helper function to safely get values and handle NaN
|
||||
def safe_get(row, column, default=''):
|
||||
value = row.get(column, default)
|
||||
if pd.isna(value) or value == 'nan':
|
||||
return default
|
||||
return str(value).strip() if isinstance(value, str) else value
|
||||
|
||||
def safe_get_int(row, column, default=0):
|
||||
value = row.get(column, default)
|
||||
if pd.isna(value) or value == 'nan':
|
||||
return default
|
||||
try:
|
||||
return int(float(value)) if value != '' else default
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
# Map columns based on the actual Vizual. Artic. Comenzi Deschise format
|
||||
data = (
|
||||
safe_get(row, 'Comanda', f'ORD_{index:06d}'), # Order ID
|
||||
safe_get(row, 'Cod. Client'), # Customer Code
|
||||
safe_get(row, 'Customer Name'), # Customer Name
|
||||
safe_get(row, 'Com. Achiz. Client'), # Client Order
|
||||
safe_get(row, 'Cod Articol'), # Article Code
|
||||
safe_get(row, 'Part Description', safe_get(row, 'Descr. Articol')), # Article Description
|
||||
safe_get_int(row, 'Cantitate'), # Quantity
|
||||
self._parse_date(row.get('Data livrare')), # Delivery Date
|
||||
safe_get(row, 'Statut Comanda', 'PENDING'), # Order Status
|
||||
safe_get(row, 'Model'), # Product Group
|
||||
self._parse_date(row.get('Data Comenzii')) # Order Date
|
||||
)
|
||||
|
||||
cursor.execute(insert_sql, data)
|
||||
|
||||
# Track created vs updated
|
||||
if cursor.rowcount == 1:
|
||||
created_count += 1
|
||||
elif cursor.rowcount == 2:
|
||||
updated_count += 1
|
||||
|
||||
success_count += 1
|
||||
|
||||
except Exception as row_error:
|
||||
logger.warning(f"Error processing row {index}: {row_error}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Orders import completed: {success_count} successful, {error_count} errors")
|
||||
|
||||
return {
|
||||
'success_count': success_count,
|
||||
'created_count': created_count,
|
||||
'updated_count': updated_count,
|
||||
'error_count': error_count,
|
||||
'total_rows': len(df),
|
||||
'error_message': None if error_count == 0 else f'{error_count} rows failed to import'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error importing orders data: {e}")
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': str(e)
|
||||
}
|
||||
|
||||
def import_delivery_data(self, file_path):
|
||||
"""Import delivery data from Excel file with enhanced error handling"""
|
||||
try:
|
||||
# Ensure we have a database connection
|
||||
if not self.connection:
|
||||
self.connect()
|
||||
if not self.connection:
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': 'Could not establish database connection.'
|
||||
}
|
||||
|
||||
logger.info(f"Attempting to import delivery data from: {file_path}")
|
||||
|
||||
# Check if file exists
|
||||
if not os.path.exists(file_path):
|
||||
logger.error(f"Delivery file not found: {file_path}")
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': f'Delivery file not found: {file_path}'
|
||||
}
|
||||
|
||||
# Try to get sheet names first
|
||||
try:
|
||||
excel_file = pd.ExcelFile(file_path)
|
||||
sheet_names = excel_file.sheet_names
|
||||
logger.info(f"Available sheets in delivery file: {sheet_names}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get sheet names: {e}")
|
||||
sheet_names = ['DataSheet', 'Sheet1']
|
||||
|
||||
# Try multiple approaches to read the Excel file
|
||||
df = None
|
||||
sheet_used = None
|
||||
approaches = [
|
||||
('openpyxl', 0, 'read_only'),
|
||||
('openpyxl', 0, 'normal'),
|
||||
('openpyxl', 1, 'normal'),
|
||||
('xlrd', 0, 'normal') if file_path.endswith('.xls') else None,
|
||||
('default', 0, 'normal')
|
||||
]
|
||||
|
||||
for approach in approaches:
|
||||
if approach is None:
|
||||
continue
|
||||
|
||||
engine, sheet_name, mode = approach
|
||||
try:
|
||||
logger.info(f"Trying to read delivery data with engine: {engine}, sheet: {sheet_name}, mode: {mode}")
|
||||
|
||||
if engine == 'default':
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, header=0)
|
||||
elif mode == 'read_only':
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, engine=engine, header=0)
|
||||
else:
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, engine=engine, header=0)
|
||||
|
||||
sheet_used = f"{engine} (sheet: {sheet_name}, mode: {mode})"
|
||||
logger.info(f"Successfully read delivery data with: {sheet_used}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed with {engine}, sheet {sheet_name}, mode {mode}: {e}")
|
||||
continue
|
||||
|
||||
if df is None:
|
||||
logger.error("Could not read the delivery file with any method")
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': 'Could not read the delivery Excel file. The file may have formatting issues or be corrupted.'
|
||||
}
|
||||
|
||||
logger.info(f"Loaded delivery data from {sheet_used}: {len(df)} rows, {len(df.columns)} columns")
|
||||
logger.info(f"Available columns: {list(df.columns)[:10]}...")
|
||||
|
||||
cursor = self.connection.cursor()
|
||||
success_count = 0
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
error_count = 0
|
||||
|
||||
# Prepare insert statement for deliveries
|
||||
insert_sql = """
|
||||
INSERT INTO dm_deliveries (
|
||||
shipment_id, order_id, customer_code, customer_name,
|
||||
article_code, article_description, quantity_delivered,
|
||||
shipment_date, delivery_date, delivery_status, total_value
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
customer_code = VALUES(customer_code),
|
||||
customer_name = VALUES(customer_name),
|
||||
article_code = VALUES(article_code),
|
||||
article_description = VALUES(article_description),
|
||||
quantity_delivered = VALUES(quantity_delivered),
|
||||
shipment_date = VALUES(shipment_date),
|
||||
delivery_date = VALUES(delivery_date),
|
||||
delivery_status = VALUES(delivery_status),
|
||||
total_value = VALUES(total_value),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
"""
|
||||
|
||||
# Process each row with the actual column mapping and better null handling
|
||||
for index, row in df.iterrows():
|
||||
try:
|
||||
# Helper function to safely get values and handle NaN
|
||||
def safe_get(row, column, default=''):
|
||||
value = row.get(column, default)
|
||||
if pd.isna(value) or value == 'nan':
|
||||
return default
|
||||
return str(value).strip() if isinstance(value, str) else value
|
||||
|
||||
def safe_get_float(row, column, default=0.0):
|
||||
value = row.get(column, default)
|
||||
if pd.isna(value) or value == 'nan':
|
||||
return default
|
||||
try:
|
||||
return float(value) if value != '' else default
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
def safe_get_int(row, column, default=0):
|
||||
value = row.get(column, default)
|
||||
if pd.isna(value) or value == 'nan':
|
||||
return default
|
||||
try:
|
||||
return int(float(value)) if value != '' else default
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
# Map columns based on the actual Articole livrate_returnate format
|
||||
data = (
|
||||
safe_get(row, 'Document Number', f'SH_{index:06d}'), # Shipment ID
|
||||
safe_get(row, 'Comanda'), # Order ID
|
||||
safe_get(row, 'Cod. Client'), # Customer Code
|
||||
safe_get(row, 'Nume client'), # Customer Name
|
||||
safe_get(row, 'Cod Articol'), # Article Code
|
||||
safe_get(row, 'Part Description'), # Article Description
|
||||
safe_get_int(row, 'Cantitate'), # Quantity Delivered
|
||||
self._parse_date(row.get('Data')), # Shipment Date
|
||||
self._parse_date(row.get('Data')), # Delivery Date (same as shipment for now)
|
||||
safe_get(row, 'Stare', 'DELIVERED'), # Delivery Status
|
||||
safe_get_float(row, 'Total Price') # Total Value
|
||||
)
|
||||
|
||||
cursor.execute(insert_sql, data)
|
||||
|
||||
# Track created vs updated
|
||||
if cursor.rowcount == 1:
|
||||
created_count += 1
|
||||
elif cursor.rowcount == 2:
|
||||
updated_count += 1
|
||||
|
||||
success_count += 1
|
||||
|
||||
except Exception as row_error:
|
||||
logger.warning(f"Error processing delivery row {index}: {row_error}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Delivery import completed: {success_count} successful, {error_count} errors")
|
||||
|
||||
return {
|
||||
'success_count': success_count,
|
||||
'created_count': created_count,
|
||||
'updated_count': updated_count,
|
||||
'error_count': error_count,
|
||||
'total_rows': len(df),
|
||||
'error_message': None if error_count == 0 else f'{error_count} rows failed to import'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error importing delivery data: {e}")
|
||||
return {
|
||||
'success_count': 0,
|
||||
'error_count': 1,
|
||||
'total_rows': 0,
|
||||
'error_message': str(e)
|
||||
}
|
||||
|
||||
def generate_daily_summary(self, report_date=None):
|
||||
"""Generate daily summary for Daily Mirror reporting"""
|
||||
if not report_date:
|
||||
report_date = datetime.now().date()
|
||||
|
||||
try:
|
||||
cursor = self.connection.cursor()
|
||||
|
||||
# Check if summary already exists for this date
|
||||
cursor.execute("SELECT id FROM dm_daily_summary WHERE report_date = ?", (report_date,))
|
||||
existing = cursor.fetchone()
|
||||
|
||||
# Get production metrics
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total_orders,
|
||||
SUM(quantity_requested) as total_quantity,
|
||||
SUM(CASE WHEN production_status = 'Inchis' THEN 1 ELSE 0 END) as completed_orders,
|
||||
SUM(CASE WHEN end_of_quilting IS NOT NULL THEN 1 ELSE 0 END) as quilting_done,
|
||||
SUM(CASE WHEN end_of_sewing IS NOT NULL THEN 1 ELSE 0 END) as sewing_done,
|
||||
COUNT(DISTINCT customer_code) as unique_customers
|
||||
FROM dm_production_orders
|
||||
WHERE DATE(data_planificare) = ?
|
||||
""", (report_date,))
|
||||
|
||||
production_metrics = cursor.fetchone()
|
||||
|
||||
# Get active operators count
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT CASE
|
||||
WHEN t1_operator_name IS NOT NULL THEN t1_operator_name
|
||||
WHEN t2_operator_name IS NOT NULL THEN t2_operator_name
|
||||
WHEN t3_operator_name IS NOT NULL THEN t3_operator_name
|
||||
END) as active_operators
|
||||
FROM dm_production_orders
|
||||
WHERE DATE(data_planificare) = ?
|
||||
""", (report_date,))
|
||||
|
||||
operator_metrics = cursor.fetchone()
|
||||
active_operators = operator_metrics[0] or 0
|
||||
|
||||
if existing:
|
||||
# Update existing summary
|
||||
update_sql = """
|
||||
UPDATE dm_daily_summary SET
|
||||
orders_quantity = ?, production_launched = ?, production_finished = ?,
|
||||
quilting_completed = ?, sewing_completed = ?, unique_customers = ?,
|
||||
active_operators = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE report_date = ?
|
||||
"""
|
||||
cursor.execute(update_sql, (
|
||||
production_metrics[1] or 0, production_metrics[0] or 0, production_metrics[2] or 0,
|
||||
production_metrics[3] or 0, production_metrics[4] or 0, production_metrics[5] or 0,
|
||||
active_operators, report_date
|
||||
))
|
||||
else:
|
||||
# Insert new summary
|
||||
insert_sql = """
|
||||
INSERT INTO dm_daily_summary (
|
||||
report_date, orders_quantity, production_launched, production_finished,
|
||||
quilting_completed, sewing_completed, unique_customers, active_operators
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
cursor.execute(insert_sql, (
|
||||
report_date, production_metrics[1] or 0, production_metrics[0] or 0, production_metrics[2] or 0,
|
||||
production_metrics[3] or 0, production_metrics[4] or 0, production_metrics[5] or 0,
|
||||
active_operators
|
||||
))
|
||||
|
||||
self.connection.commit()
|
||||
logger.info(f"Daily summary generated for {report_date}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating daily summary: {e}")
|
||||
return False
|
||||
|
||||
def clear_production_orders(self):
|
||||
"""Delete all rows from the Daily Mirror production orders table"""
|
||||
try:
|
||||
cursor = self.connection.cursor()
|
||||
cursor.execute("DELETE FROM dm_production_orders")
|
||||
self.connection.commit()
|
||||
logger.info("All production orders deleted from dm_production_orders table.")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting production orders: {e}")
|
||||
return False
|
||||
|
||||
def _parse_date(self, date_value):
|
||||
"""Parse date with better null handling"""
|
||||
if pd.isna(date_value) or date_value == 'nan' or date_value is None or date_value == '':
|
||||
return None
|
||||
|
||||
try:
|
||||
if isinstance(date_value, str):
|
||||
# Handle various date formats
|
||||
for fmt in ['%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%d.%m.%Y']:
|
||||
try:
|
||||
return datetime.strptime(date_value, fmt).date()
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
elif hasattr(date_value, 'date'):
|
||||
return date_value.date()
|
||||
elif isinstance(date_value, datetime):
|
||||
return date_value.date()
|
||||
|
||||
return None # If all parsing attempts fail
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error parsing date {date_value}: {e}")
|
||||
return None
|
||||
|
||||
def _parse_datetime(self, datetime_value):
|
||||
"""Parse datetime value from Excel"""
|
||||
if pd.isna(datetime_value):
|
||||
return None
|
||||
if isinstance(datetime_value, str) and datetime_value == '00:00:00':
|
||||
return None
|
||||
return datetime_value
|
||||
|
||||
def setup_daily_mirror_database():
|
||||
"""Setup the Daily Mirror database schema"""
|
||||
db = DailyMirrorDatabase()
|
||||
|
||||
if not db.connect():
|
||||
return False
|
||||
|
||||
try:
|
||||
success = db.create_database_schema()
|
||||
if success:
|
||||
print("✅ Daily Mirror database schema created successfully!")
|
||||
|
||||
# Generate sample daily summary for today
|
||||
db.generate_daily_summary()
|
||||
|
||||
return success
|
||||
finally:
|
||||
db.disconnect()
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup_daily_mirror_database()
|
||||
151
py_app/app/db_create_scripts/add_printed_labels_column.py
Executable file
151
py_app/app/db_create_scripts/add_printed_labels_column.py
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Database script to add the printed_labels column to the order_for_labels table
|
||||
This column will track whether labels have been printed for each order (boolean: 0=false, 1=true)
|
||||
Default value: 0 (false)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import mariadb
|
||||
from flask import Flask
|
||||
|
||||
# Add the app directory to the path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
def get_db_connection():
|
||||
"""Get database connection using settings from external_server.conf"""
|
||||
# Go up two levels from this script to reach py_app directory, then to instance
|
||||
app_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
settings_file = os.path.join(app_root, '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']
|
||||
)
|
||||
|
||||
def add_printed_labels_column():
|
||||
"""
|
||||
Adds the printed_labels column to the order_for_labels table after the line_number column
|
||||
Column type: TINYINT(1) (boolean: 0=false, 1=true)
|
||||
Default value: 0 (false)
|
||||
"""
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if table exists
|
||||
cursor.execute("SHOW TABLES LIKE 'order_for_labels'")
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
print("❌ Table 'order_for_labels' does not exist. Please create it first.")
|
||||
return False
|
||||
|
||||
# Check if column already exists
|
||||
cursor.execute("SHOW COLUMNS FROM order_for_labels LIKE 'printed_labels'")
|
||||
column_exists = cursor.fetchone()
|
||||
|
||||
if column_exists:
|
||||
print("ℹ️ Column 'printed_labels' already exists.")
|
||||
# Show current structure
|
||||
cursor.execute("DESCRIBE order_for_labels")
|
||||
columns = cursor.fetchall()
|
||||
print("\n📋 Current table structure:")
|
||||
for col in columns:
|
||||
null_info = 'NULL' if col[2] == 'YES' else 'NOT NULL'
|
||||
default_info = f" DEFAULT {col[4]}" if col[4] else ""
|
||||
print(f" 📌 {col[0]:<25} {col[1]:<20} {null_info}{default_info}")
|
||||
else:
|
||||
# Add the column after line_number
|
||||
alter_table_sql = """
|
||||
ALTER TABLE order_for_labels
|
||||
ADD COLUMN printed_labels TINYINT(1) NOT NULL DEFAULT 0
|
||||
COMMENT 'Boolean flag: 0=labels not printed, 1=labels printed'
|
||||
AFTER line_number
|
||||
"""
|
||||
|
||||
cursor.execute(alter_table_sql)
|
||||
conn.commit()
|
||||
print("✅ Column 'printed_labels' added successfully!")
|
||||
|
||||
# Show the updated structure
|
||||
cursor.execute("DESCRIBE order_for_labels")
|
||||
columns = cursor.fetchall()
|
||||
print("\n📋 Updated table structure:")
|
||||
for col in columns:
|
||||
null_info = 'NULL' if col[2] == 'YES' else 'NOT NULL'
|
||||
default_info = f" DEFAULT {col[4]}" if col[4] else ""
|
||||
highlight = "🆕 " if col[0] == 'printed_labels' else " "
|
||||
print(f"{highlight}{col[0]:<25} {col[1]:<20} {null_info}{default_info}")
|
||||
|
||||
# Show count of existing records that will have printed_labels = 0
|
||||
cursor.execute("SELECT COUNT(*) FROM order_for_labels")
|
||||
count = cursor.fetchone()[0]
|
||||
if count > 0:
|
||||
print(f"\n📊 {count} existing records now have printed_labels = 0 (false)")
|
||||
|
||||
conn.close()
|
||||
|
||||
except mariadb.Error as e:
|
||||
print(f"❌ Database error: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def verify_column():
|
||||
"""Verify the column was added correctly"""
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Test the column functionality
|
||||
cursor.execute("SELECT COUNT(*) as total, SUM(printed_labels) as printed FROM order_for_labels")
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
total, printed = result
|
||||
print(f"\n🔍 Verification:")
|
||||
print(f" 📦 Total orders: {total}")
|
||||
print(f" 🖨️ Printed orders: {printed or 0}")
|
||||
print(f" 📄 Unprinted orders: {total - (printed or 0)}")
|
||||
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Verification failed: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🔧 Adding printed_labels column to order_for_labels table...")
|
||||
print("="*60)
|
||||
|
||||
success = add_printed_labels_column()
|
||||
|
||||
if success:
|
||||
print("\n🔍 Verifying column addition...")
|
||||
verify_column()
|
||||
print("\n✅ Database modification completed successfully!")
|
||||
print("\n📝 Column Details:")
|
||||
print(" • Name: printed_labels")
|
||||
print(" • Type: TINYINT(1) (boolean)")
|
||||
print(" • Default: 0 (false - labels not printed)")
|
||||
print(" • Values: 0 = not printed, 1 = printed")
|
||||
print(" • Position: After line_number column")
|
||||
else:
|
||||
print("\n❌ Database modification failed!")
|
||||
|
||||
print("="*60)
|
||||
@@ -5,7 +5,7 @@ db_config = {
|
||||
"user": "trasabilitate",
|
||||
"password": "Initial01!",
|
||||
"host": "localhost",
|
||||
"database": "trasabilitate"
|
||||
"database": "trasabilitate_database"
|
||||
}
|
||||
|
||||
# Connect to the database
|
||||
@@ -6,7 +6,7 @@ db_config = {
|
||||
"user": "trasabilitate",
|
||||
"password": "Initial01!",
|
||||
"host": "localhost",
|
||||
"database": "trasabilitate"
|
||||
"database": "trasabilitate_database"
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -5,7 +5,7 @@ db_config = {
|
||||
"user": "trasabilitate",
|
||||
"password": "Initial01!",
|
||||
"host": "localhost",
|
||||
"database": "trasabilitate"
|
||||
"database": "trasabilitate_database"
|
||||
}
|
||||
|
||||
# Connect to the database
|
||||
@@ -5,7 +5,7 @@ db_config = {
|
||||
"user": "trasabilitate",
|
||||
"password": "Initial01!",
|
||||
"host": "localhost",
|
||||
"database": "trasabilitate"
|
||||
"database": "trasabilitate_database"
|
||||
}
|
||||
|
||||
# Connect to the database
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Docker-compatible Database Setup Script
|
||||
Reads configuration from environment variables or config file
|
||||
"""
|
||||
|
||||
import mariadb
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
def get_db_config():
|
||||
"""Get database configuration from environment or config file"""
|
||||
# Try environment variables first (Docker)
|
||||
if os.getenv('DB_HOST'):
|
||||
return {
|
||||
"user": os.getenv('DB_USER', 'trasabilitate'),
|
||||
"password": os.getenv('DB_PASSWORD', 'Initial01!'),
|
||||
"host": os.getenv('DB_HOST', 'localhost'),
|
||||
"port": int(os.getenv('DB_PORT', '3306')),
|
||||
"database": os.getenv('DB_NAME', 'trasabilitate')
|
||||
}
|
||||
|
||||
# Fallback to config file (traditional deployment)
|
||||
config_file = os.path.join(os.path.dirname(__file__), '../../instance/external_server.conf')
|
||||
if os.path.exists(config_file):
|
||||
settings = {}
|
||||
with open(config_file, 'r') as f:
|
||||
for line in f:
|
||||
if '=' in line:
|
||||
key, value = line.strip().split('=', 1)
|
||||
settings[key] = value
|
||||
|
||||
return {
|
||||
"user": settings.get('username', 'trasabilitate'),
|
||||
"password": settings.get('password', 'Initial01!'),
|
||||
"host": settings.get('server_domain', 'localhost'),
|
||||
"port": int(settings.get('port', '3306')),
|
||||
"database": settings.get('database_name', 'trasabilitate')
|
||||
}
|
||||
|
||||
# Default configuration
|
||||
return {
|
||||
"user": "trasabilitate",
|
||||
"password": "Initial01!",
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"database": "trasabilitate"
|
||||
}
|
||||
|
||||
def print_step(step_num, description):
|
||||
"""Print formatted step information"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Step {step_num}: {description}")
|
||||
print('='*60)
|
||||
|
||||
def print_success(message):
|
||||
"""Print success message"""
|
||||
print(f"✅ {message}")
|
||||
|
||||
def print_error(message):
|
||||
"""Print error message"""
|
||||
print(f"❌ {message}")
|
||||
|
||||
# Get configuration
|
||||
DB_CONFIG = get_db_config()
|
||||
|
||||
print(f"Using database configuration: {DB_CONFIG['user']}@{DB_CONFIG['host']}:{DB_CONFIG['port']}/{DB_CONFIG['database']}")
|
||||
|
||||
# Import the rest from the original setup script
|
||||
@@ -1,722 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Complete Database Setup Script for Trasabilitate Application
|
||||
This script creates all necessary database tables, triggers, and initial data
|
||||
for quick deployment of the application.
|
||||
|
||||
Usage: python3 setup_complete_database.py
|
||||
Supports both traditional and Docker deployments via environment variables.
|
||||
"""
|
||||
|
||||
import mariadb
|
||||
import sqlite3
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Database configuration - supports environment variables (Docker) or defaults
|
||||
DB_CONFIG = {
|
||||
"user": os.getenv("DB_USER", "trasabilitate"),
|
||||
"password": os.getenv("DB_PASSWORD", "Initial01!"),
|
||||
"host": os.getenv("DB_HOST", "localhost"),
|
||||
"port": int(os.getenv("DB_PORT", "3306")),
|
||||
"database": os.getenv("DB_NAME", "trasabilitate")
|
||||
}
|
||||
|
||||
def print_step(step_num, description):
|
||||
"""Print formatted step information"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Step {step_num}: {description}")
|
||||
print('='*60)
|
||||
|
||||
def print_success(message):
|
||||
"""Print success message"""
|
||||
print(f"✅ {message}")
|
||||
|
||||
def print_error(message):
|
||||
"""Print error message"""
|
||||
print(f"❌ {message}")
|
||||
|
||||
def test_database_connection():
|
||||
"""Test if we can connect to the database"""
|
||||
print_step(1, "Testing Database Connection")
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
print_success("Successfully connected to MariaDB database 'trasabilitate'")
|
||||
conn.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
print_error(f"Failed to connect to database: {e}")
|
||||
print("\nPlease ensure:")
|
||||
print("1. MariaDB is running")
|
||||
print("2. Database 'trasabilitate' exists")
|
||||
print("3. User 'trasabilitate' has been created with password 'Initial01!'")
|
||||
print("4. User has all privileges on the database")
|
||||
return False
|
||||
|
||||
def create_scan_tables():
|
||||
"""Create scan1_orders and scanfg_orders tables"""
|
||||
print_step(2, "Creating Scan Tables (scan1_orders & scanfg_orders)")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create scan1_orders table
|
||||
scan1_table_query = """
|
||||
CREATE TABLE IF NOT EXISTS scan1_orders (
|
||||
Id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
operator_code VARCHAR(4) NOT NULL,
|
||||
CP_full_code VARCHAR(15) NOT NULL UNIQUE,
|
||||
OC1_code VARCHAR(4) NOT NULL,
|
||||
OC2_code VARCHAR(4) NOT NULL,
|
||||
CP_base_code VARCHAR(10) GENERATED ALWAYS AS (LEFT(CP_full_code, 10)) STORED,
|
||||
quality_code INT(3) NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
time TIME NOT NULL,
|
||||
approved_quantity INT DEFAULT 0,
|
||||
rejected_quantity INT DEFAULT 0
|
||||
);
|
||||
"""
|
||||
cursor.execute(scan1_table_query)
|
||||
print_success("Table 'scan1_orders' created successfully")
|
||||
|
||||
# Create scanfg_orders table
|
||||
scanfg_table_query = """
|
||||
CREATE TABLE IF NOT EXISTS scanfg_orders (
|
||||
Id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
operator_code VARCHAR(4) NOT NULL,
|
||||
CP_full_code VARCHAR(15) NOT NULL UNIQUE,
|
||||
OC1_code VARCHAR(4) NOT NULL,
|
||||
OC2_code VARCHAR(4) NOT NULL,
|
||||
CP_base_code VARCHAR(10) GENERATED ALWAYS AS (LEFT(CP_full_code, 10)) STORED,
|
||||
quality_code INT(3) NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
time TIME NOT NULL,
|
||||
approved_quantity INT DEFAULT 0,
|
||||
rejected_quantity INT DEFAULT 0
|
||||
);
|
||||
"""
|
||||
cursor.execute(scanfg_table_query)
|
||||
print_success("Table 'scanfg_orders' created successfully")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create scan tables: {e}")
|
||||
return False
|
||||
|
||||
def create_order_for_labels_table():
|
||||
"""Create order_for_labels table
|
||||
|
||||
This table stores production orders for label generation.
|
||||
Includes columns added for print module functionality:
|
||||
- printed_labels: Track if labels have been printed (0=no, 1=yes)
|
||||
- data_livrare: Delivery date from CSV uploads
|
||||
- dimensiune: Product dimensions from CSV uploads
|
||||
"""
|
||||
print_step(3, "Creating Order for Labels Table")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
order_labels_query = """
|
||||
CREATE TABLE IF NOT EXISTS order_for_labels (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
comanda_productie VARCHAR(15) NOT NULL,
|
||||
cod_articol VARCHAR(15) NULL,
|
||||
descr_com_prod VARCHAR(50) NOT NULL,
|
||||
cantitate INT(3) NOT NULL,
|
||||
com_achiz_client VARCHAR(25) NULL,
|
||||
nr_linie_com_client INT(3) NULL,
|
||||
customer_name VARCHAR(50) NULL,
|
||||
customer_article_number VARCHAR(25) NULL,
|
||||
open_for_order VARCHAR(25) NULL,
|
||||
line_number INT(3) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
printed_labels INT(1) DEFAULT 0,
|
||||
data_livrare DATE NULL,
|
||||
dimensiune VARCHAR(20) NULL
|
||||
);
|
||||
"""
|
||||
cursor.execute(order_labels_query)
|
||||
print_success("Table 'order_for_labels' created successfully")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create order_for_labels table: {e}")
|
||||
return False
|
||||
|
||||
def create_warehouse_locations_table():
|
||||
"""Create warehouse_locations table"""
|
||||
print_step(4, "Creating Warehouse Locations Table")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
warehouse_query = """
|
||||
CREATE TABLE IF NOT EXISTS warehouse_locations (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
location_code VARCHAR(12) NOT NULL UNIQUE,
|
||||
size INT,
|
||||
description VARCHAR(250)
|
||||
);
|
||||
"""
|
||||
cursor.execute(warehouse_query)
|
||||
print_success("Table 'warehouse_locations' created successfully")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create warehouse_locations table: {e}")
|
||||
return False
|
||||
|
||||
def create_permissions_tables():
|
||||
"""Create permission management tables"""
|
||||
print_step(5, "Creating Permission Management Tables")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create permissions table
|
||||
permissions_query = """
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
permission_key VARCHAR(255) UNIQUE NOT NULL,
|
||||
page VARCHAR(100) NOT NULL,
|
||||
page_name VARCHAR(255) NOT NULL,
|
||||
section VARCHAR(100) NOT NULL,
|
||||
section_name VARCHAR(255) NOT NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
action_name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
cursor.execute(permissions_query)
|
||||
print_success("Table 'permissions' created successfully")
|
||||
|
||||
# Create role_permissions table
|
||||
role_permissions_query = """
|
||||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) NOT NULL,
|
||||
permission_id INT NOT NULL,
|
||||
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
granted_by VARCHAR(100),
|
||||
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_role_permission (role_name, permission_id)
|
||||
);
|
||||
"""
|
||||
cursor.execute(role_permissions_query)
|
||||
print_success("Table 'role_permissions' created successfully")
|
||||
|
||||
# Create role_hierarchy table
|
||||
role_hierarchy_query = """
|
||||
CREATE TABLE IF NOT EXISTS role_hierarchy (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
role_name VARCHAR(100) UNIQUE NOT NULL,
|
||||
role_display_name VARCHAR(255) NOT NULL,
|
||||
level INT NOT NULL,
|
||||
parent_role VARCHAR(100),
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
cursor.execute(role_hierarchy_query)
|
||||
print_success("Table 'role_hierarchy' created successfully")
|
||||
|
||||
# Create permission_audit_log table
|
||||
audit_log_query = """
|
||||
CREATE TABLE IF NOT EXISTS permission_audit_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
role_name VARCHAR(100),
|
||||
permission_key VARCHAR(255),
|
||||
user_id VARCHAR(100),
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
details TEXT,
|
||||
ip_address VARCHAR(45)
|
||||
);
|
||||
"""
|
||||
cursor.execute(audit_log_query)
|
||||
print_success("Table 'permission_audit_log' created successfully")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create permissions tables: {e}")
|
||||
return False
|
||||
|
||||
def create_users_table_mariadb():
|
||||
"""Create users and roles tables in MariaDB and seed superadmin"""
|
||||
print_step(6, "Creating MariaDB Users and Roles Tables")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create users table in MariaDB
|
||||
users_table_query = """
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
cursor.execute(users_table_query)
|
||||
print_success("Table 'users' created successfully")
|
||||
|
||||
# Create roles table in MariaDB
|
||||
roles_table_query = """
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
access_level VARCHAR(50) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
cursor.execute(roles_table_query)
|
||||
print_success("Table 'roles' created successfully")
|
||||
|
||||
# Insert superadmin role if not exists
|
||||
cursor.execute("SELECT COUNT(*) FROM roles WHERE name = %s", ('superadmin',))
|
||||
if cursor.fetchone()[0] == 0:
|
||||
cursor.execute("""
|
||||
INSERT INTO roles (name, access_level, description)
|
||||
VALUES (%s, %s, %s)
|
||||
""", ('superadmin', 'full', 'Full access to all app areas and functions'))
|
||||
print_success("Superadmin role created")
|
||||
|
||||
# Insert superadmin user if not exists
|
||||
cursor.execute("SELECT COUNT(*) FROM users WHERE username = %s", ('superadmin',))
|
||||
if cursor.fetchone()[0] == 0:
|
||||
cursor.execute("""
|
||||
INSERT INTO users (username, password, role)
|
||||
VALUES (%s, %s, %s)
|
||||
""", ('superadmin', 'superadmin123', 'superadmin'))
|
||||
print_success("Superadmin user created (username: superadmin, password: superadmin123)")
|
||||
else:
|
||||
print_success("Superadmin user already exists")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create users tables in MariaDB: {e}")
|
||||
return False
|
||||
|
||||
def create_sqlite_tables():
|
||||
"""Create SQLite tables for users and roles (legacy/backup)"""
|
||||
print_step(7, "Creating SQLite User and Role Tables (Backup)")
|
||||
|
||||
try:
|
||||
# Create instance folder if it doesn't exist
|
||||
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
|
||||
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'))
|
||||
|
||||
# Create roles table
|
||||
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()
|
||||
|
||||
print_success("SQLite tables created and superadmin user initialized")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create SQLite tables: {e}")
|
||||
return False
|
||||
|
||||
def create_database_triggers():
|
||||
"""Create database triggers for automatic quantity calculations"""
|
||||
print_step(8, "Creating Database Triggers")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Drop existing triggers if they exist
|
||||
trigger_drops = [
|
||||
"DROP TRIGGER IF EXISTS increment_approved_quantity;",
|
||||
"DROP TRIGGER IF EXISTS increment_rejected_quantity;",
|
||||
"DROP TRIGGER IF EXISTS increment_approved_quantity_fg;",
|
||||
"DROP TRIGGER IF EXISTS increment_rejected_quantity_fg;"
|
||||
]
|
||||
|
||||
for drop_query in trigger_drops:
|
||||
cursor.execute(drop_query)
|
||||
|
||||
# Create trigger for scan1_orders approved quantity
|
||||
scan1_approved_trigger = """
|
||||
CREATE TRIGGER increment_approved_quantity
|
||||
AFTER INSERT ON scan1_orders
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF NEW.quality_code = 000 THEN
|
||||
UPDATE scan1_orders
|
||||
SET approved_quantity = approved_quantity + 1
|
||||
WHERE CP_base_code = NEW.CP_base_code;
|
||||
ELSE
|
||||
UPDATE scan1_orders
|
||||
SET rejected_quantity = rejected_quantity + 1
|
||||
WHERE CP_base_code = NEW.CP_base_code;
|
||||
END IF;
|
||||
END;
|
||||
"""
|
||||
cursor.execute(scan1_approved_trigger)
|
||||
print_success("Trigger 'increment_approved_quantity' created for scan1_orders")
|
||||
|
||||
# Create trigger for scanfg_orders approved quantity
|
||||
scanfg_approved_trigger = """
|
||||
CREATE TRIGGER increment_approved_quantity_fg
|
||||
AFTER INSERT ON scanfg_orders
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF NEW.quality_code = 000 THEN
|
||||
UPDATE scanfg_orders
|
||||
SET approved_quantity = approved_quantity + 1
|
||||
WHERE CP_base_code = NEW.CP_base_code;
|
||||
ELSE
|
||||
UPDATE scanfg_orders
|
||||
SET rejected_quantity = rejected_quantity + 1
|
||||
WHERE CP_base_code = NEW.CP_base_code;
|
||||
END IF;
|
||||
END;
|
||||
"""
|
||||
cursor.execute(scanfg_approved_trigger)
|
||||
print_success("Trigger 'increment_approved_quantity_fg' created for scanfg_orders")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to create database triggers: {e}")
|
||||
return False
|
||||
|
||||
def populate_permissions_data():
|
||||
"""Populate permissions and roles with default data"""
|
||||
print_step(9, "Populating Permissions and Roles Data")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Define all permissions
|
||||
permissions_data = [
|
||||
# Home page permissions
|
||||
('home.view', 'home', 'Home Page', 'navigation', 'Navigation', 'view', 'View Home Page', 'Access to home page'),
|
||||
|
||||
# Scan1 permissions
|
||||
('scan1.view', 'scan1', 'Scan1 Page', 'scanning', 'Scanning Operations', 'view', 'View Scan1', 'Access to scan1 page'),
|
||||
('scan1.scan', 'scan1', 'Scan1 Page', 'scanning', 'Scanning Operations', 'scan', 'Perform Scan1', 'Ability to perform scan1 operations'),
|
||||
('scan1.history', 'scan1', 'Scan1 Page', 'scanning', 'Scanning Operations', 'history', 'View Scan1 History', 'View scan1 operation history'),
|
||||
|
||||
# ScanFG permissions
|
||||
('scanfg.view', 'scanfg', 'ScanFG Page', 'scanning', 'Scanning Operations', 'view', 'View ScanFG', 'Access to scanfg page'),
|
||||
('scanfg.scan', 'scanfg', 'ScanFG Page', 'scanning', 'Scanning Operations', 'scan', 'Perform ScanFG', 'Ability to perform scanfg operations'),
|
||||
('scanfg.history', 'scanfg', 'ScanFG Page', 'scanning', 'Scanning Operations', 'history', 'View ScanFG History', 'View scanfg operation history'),
|
||||
|
||||
# Warehouse permissions
|
||||
('warehouse.view', 'warehouse', 'Warehouse Management', 'warehouse', 'Warehouse Operations', 'view', 'View Warehouse', 'Access to warehouse page'),
|
||||
('warehouse.manage_locations', 'warehouse', 'Warehouse Management', 'warehouse', 'Warehouse Operations', 'manage', 'Manage Locations', 'Add, edit, delete warehouse locations'),
|
||||
('warehouse.view_locations', 'warehouse', 'Warehouse Management', 'warehouse', 'Warehouse Operations', 'view_locations', 'View Locations', 'View warehouse locations'),
|
||||
|
||||
# Labels permissions
|
||||
('labels.view', 'labels', 'Label Management', 'labels', 'Label Operations', 'view', 'View Labels', 'Access to labels page'),
|
||||
('labels.print', 'labels', 'Label Management', 'labels', 'Label Operations', 'print', 'Print Labels', 'Print labels'),
|
||||
('labels.manage_orders', 'labels', 'Label Management', 'labels', 'Label Operations', 'manage', 'Manage Label Orders', 'Manage label orders'),
|
||||
|
||||
# Print Module permissions
|
||||
('print.view', 'print', 'Print Module', 'printing', 'Printing Operations', 'view', 'View Print Module', 'Access to print module'),
|
||||
('print.execute', 'print', 'Print Module', 'printing', 'Printing Operations', 'execute', 'Execute Print', 'Execute print operations'),
|
||||
('print.manage_queue', 'print', 'Print Module', 'printing', 'Printing Operations', 'manage_queue', 'Manage Print Queue', 'Manage print queue'),
|
||||
|
||||
# Settings permissions
|
||||
('settings.view', 'settings', 'Settings', 'system', 'System Management', 'view', 'View Settings', 'Access to settings page'),
|
||||
('settings.edit', 'settings', 'Settings', 'system', 'System Management', 'edit', 'Edit Settings', 'Modify application settings'),
|
||||
('settings.database', 'settings', 'Settings', 'system', 'System Management', 'database', 'Database Settings', 'Manage database settings'),
|
||||
|
||||
# User Management permissions
|
||||
('users.view', 'users', 'User Management', 'admin', 'Administration', 'view', 'View Users', 'View user list'),
|
||||
('users.create', 'users', 'User Management', 'admin', 'Administration', 'create', 'Create Users', 'Create new users'),
|
||||
('users.edit', 'users', 'User Management', 'admin', 'Administration', 'edit', 'Edit Users', 'Edit existing users'),
|
||||
('users.delete', 'users', 'User Management', 'admin', 'Administration', 'delete', 'Delete Users', 'Delete users'),
|
||||
|
||||
# Permission Management permissions
|
||||
('permissions.view', 'permissions', 'Permission Management', 'admin', 'Administration', 'view', 'View Permissions', 'View permissions'),
|
||||
('permissions.assign', 'permissions', 'Permission Management', 'admin', 'Administration', 'assign', 'Assign Permissions', 'Assign permissions to roles'),
|
||||
('permissions.audit', 'permissions', 'Permission Management', 'admin', 'Administration', 'audit', 'View Audit Log', 'View permission audit log'),
|
||||
]
|
||||
|
||||
# Insert permissions
|
||||
permission_insert_query = """
|
||||
INSERT IGNORE INTO permissions
|
||||
(permission_key, page, page_name, section, section_name, action, action_name, description)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
cursor.executemany(permission_insert_query, permissions_data)
|
||||
print_success(f"Inserted {len(permissions_data)} permissions")
|
||||
|
||||
# Define role hierarchy
|
||||
roles_data = [
|
||||
('superadmin', 'Super Administrator', 1, None, 'Full system access with all permissions'),
|
||||
('admin', 'Administrator', 2, 'superadmin', 'Administrative access with most permissions'),
|
||||
('manager', 'Manager', 3, 'admin', 'Management level access'),
|
||||
('quality_manager', 'Quality Manager', 4, 'manager', 'Quality control and scanning operations'),
|
||||
('warehouse_manager', 'Warehouse Manager', 4, 'manager', 'Warehouse operations and management'),
|
||||
('quality_worker', 'Quality Worker', 5, 'quality_manager', 'Basic quality scanning operations'),
|
||||
('warehouse_worker', 'Warehouse Worker', 5, 'warehouse_manager', 'Basic warehouse operations'),
|
||||
]
|
||||
|
||||
# Insert roles
|
||||
role_insert_query = """
|
||||
INSERT IGNORE INTO role_hierarchy
|
||||
(role_name, role_display_name, level, parent_role, description)
|
||||
VALUES (%s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
cursor.executemany(role_insert_query, roles_data)
|
||||
print_success(f"Inserted {len(roles_data)} roles")
|
||||
|
||||
# Assign permissions to roles
|
||||
# Get all permission IDs
|
||||
cursor.execute("SELECT id, permission_key FROM permissions")
|
||||
permissions = {key: id for id, key in cursor.fetchall()}
|
||||
|
||||
# Define role-permission mappings
|
||||
role_permissions = {
|
||||
'superadmin': list(permissions.values()), # All permissions
|
||||
'admin': [pid for key, pid in permissions.items() if not key.startswith('permissions.audit')], # All except audit
|
||||
'manager': [permissions[key] for key in permissions.keys() if any(key.startswith(prefix) for prefix in ['home.', 'settings.view', 'users.view'])],
|
||||
'quality_manager': [permissions[key] for key in permissions.keys() if any(key.startswith(prefix) for prefix in ['home.', 'scan1.', 'scanfg.', 'labels.', 'print.'])],
|
||||
'warehouse_manager': [permissions[key] for key in permissions.keys() if any(key.startswith(prefix) for prefix in ['home.', 'warehouse.', 'labels.'])],
|
||||
'quality_worker': [permissions[key] for key in permissions.keys() if any(key.startswith(prefix) for prefix in ['home.', 'scan1.view', 'scan1.scan', 'scanfg.view', 'scanfg.scan'])],
|
||||
'warehouse_worker': [permissions[key] for key in permissions.keys() if any(key.startswith(prefix) for prefix in ['home.', 'warehouse.view', 'warehouse.view_locations'])],
|
||||
}
|
||||
|
||||
# Insert role permissions
|
||||
for role, permission_ids in role_permissions.items():
|
||||
for permission_id in permission_ids:
|
||||
cursor.execute("""
|
||||
INSERT IGNORE INTO role_permissions (role_name, permission_id)
|
||||
VALUES (%s, %s)
|
||||
""", (role, permission_id))
|
||||
|
||||
print_success("Role permissions assigned successfully")
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to populate permissions data: {e}")
|
||||
return False
|
||||
|
||||
def update_external_config():
|
||||
"""Update external_server.conf with correct database settings"""
|
||||
print_step(10, "Updating External Server Configuration")
|
||||
|
||||
try:
|
||||
config_path = os.path.join(os.path.dirname(__file__), '../../instance/external_server.conf')
|
||||
|
||||
# Use environment variables if available (Docker), otherwise use defaults
|
||||
db_host = os.getenv('DB_HOST', 'localhost')
|
||||
db_port = os.getenv('DB_PORT', '3306')
|
||||
db_name = os.getenv('DB_NAME', 'trasabilitate')
|
||||
db_user = os.getenv('DB_USER', 'trasabilitate')
|
||||
db_password = os.getenv('DB_PASSWORD', 'Initial01!')
|
||||
|
||||
config_content = f"""server_domain={db_host}
|
||||
port={db_port}
|
||||
database_name={db_name}
|
||||
username={db_user}
|
||||
password={db_password}
|
||||
"""
|
||||
|
||||
# Create instance directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
f.write(config_content)
|
||||
|
||||
print_success(f"External server configuration updated (host: {db_host})")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to update external config: {e}")
|
||||
return False
|
||||
|
||||
def verify_database_setup():
|
||||
"""Verify that all tables were created successfully"""
|
||||
print_step(11, "Verifying Database Setup")
|
||||
|
||||
try:
|
||||
conn = mariadb.connect(**DB_CONFIG)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check MariaDB tables
|
||||
cursor.execute("SHOW TABLES")
|
||||
tables = [table[0] for table in cursor.fetchall()]
|
||||
|
||||
expected_tables = [
|
||||
'scan1_orders',
|
||||
'scanfg_orders',
|
||||
'order_for_labels',
|
||||
'warehouse_locations',
|
||||
'permissions',
|
||||
'role_permissions',
|
||||
'role_hierarchy',
|
||||
'permission_audit_log',
|
||||
'users',
|
||||
'roles'
|
||||
]
|
||||
|
||||
print("\n📊 MariaDB Tables Status:")
|
||||
for table in expected_tables:
|
||||
if table in tables:
|
||||
print_success(f"Table '{table}' exists")
|
||||
else:
|
||||
print_error(f"Table '{table}' missing")
|
||||
|
||||
# Check triggers
|
||||
cursor.execute("SHOW TRIGGERS")
|
||||
triggers = [trigger[0] for trigger in cursor.fetchall()]
|
||||
|
||||
expected_triggers = [
|
||||
'increment_approved_quantity',
|
||||
'increment_approved_quantity_fg'
|
||||
]
|
||||
|
||||
print("\n🔧 Database Triggers Status:")
|
||||
for trigger in expected_triggers:
|
||||
if trigger in triggers:
|
||||
print_success(f"Trigger '{trigger}' exists")
|
||||
else:
|
||||
print_error(f"Trigger '{trigger}' missing")
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
# Check SQLite database
|
||||
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
|
||||
sqlite_path = os.path.join(instance_folder, 'users.db')
|
||||
|
||||
if os.path.exists(sqlite_path):
|
||||
print_success("SQLite database 'users.db' exists")
|
||||
else:
|
||||
print_error("SQLite database 'users.db' missing")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Failed to verify database setup: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to orchestrate the complete database setup"""
|
||||
print("🚀 Trasabilitate Application - Complete Database Setup")
|
||||
print(f"Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
steps = [
|
||||
test_database_connection,
|
||||
create_scan_tables,
|
||||
create_order_for_labels_table,
|
||||
create_warehouse_locations_table,
|
||||
create_permissions_tables,
|
||||
create_users_table_mariadb,
|
||||
create_sqlite_tables,
|
||||
create_database_triggers,
|
||||
populate_permissions_data,
|
||||
update_external_config,
|
||||
verify_database_setup
|
||||
]
|
||||
|
||||
success_count = 0
|
||||
|
||||
for step in steps:
|
||||
if step():
|
||||
success_count += 1
|
||||
else:
|
||||
print(f"\n❌ Setup failed at step: {step.__name__}")
|
||||
print("Please check the error messages above and resolve the issues.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print("🎉 DATABASE SETUP COMPLETED SUCCESSFULLY!")
|
||||
print(f"{'='*60}")
|
||||
print(f"✅ All {success_count} steps completed successfully")
|
||||
print(f"📅 Completed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print("\n📋 Setup Summary:")
|
||||
print(" • MariaDB tables created with triggers")
|
||||
print(" • SQLite user database initialized")
|
||||
print(" • Permissions system fully configured")
|
||||
print(" • Default superadmin user created (username: superadmin, password: superadmin123)")
|
||||
print(" • Configuration files updated")
|
||||
print("\n🚀 Your application is ready to run!")
|
||||
print(" Run: python3 run.py")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -4,47 +4,7 @@ class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
password = db.Column(db.String(120), nullable=False)
|
||||
role = db.Column(db.String(20), nullable=False) # Role: superadmin, admin, manager, worker
|
||||
modules = db.Column(db.Text, nullable=True) # JSON string of assigned modules: ["quality", "warehouse"]
|
||||
role = db.Column(db.String(20), nullable=False) # Role: superadmin, administrator, quality, warehouse, scan
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
def get_modules(self):
|
||||
"""Get user's assigned modules as a list"""
|
||||
if not self.modules:
|
||||
return []
|
||||
try:
|
||||
import json
|
||||
return json.loads(self.modules)
|
||||
except:
|
||||
return []
|
||||
|
||||
def set_modules(self, module_list):
|
||||
"""Set user's assigned modules from a list"""
|
||||
if not module_list:
|
||||
self.modules = None
|
||||
else:
|
||||
import json
|
||||
self.modules = json.dumps(module_list)
|
||||
|
||||
def add_module(self, module):
|
||||
"""Add a module to user's assignments"""
|
||||
current_modules = self.get_modules()
|
||||
if module not in current_modules:
|
||||
current_modules.append(module)
|
||||
self.set_modules(current_modules)
|
||||
|
||||
def remove_module(self, module):
|
||||
"""Remove a module from user's assignments"""
|
||||
current_modules = self.get_modules()
|
||||
if module in current_modules:
|
||||
current_modules.remove(module)
|
||||
self.set_modules(current_modules)
|
||||
|
||||
def has_module(self, module):
|
||||
"""Check if user has access to a specific module"""
|
||||
# Superadmin and admin have access to all modules
|
||||
if self.role in ['superadmin', 'admin']:
|
||||
return True
|
||||
return module in self.get_modules()
|
||||
return f'<User {self.username}>'
|
||||
@@ -1,240 +0,0 @@
|
||||
"""
|
||||
Simplified 4-Tier Role-Based Access Control System
|
||||
Clear hierarchy: Superadmin → Admin → Manager → Worker
|
||||
Module-based permissions: Quality, Labels, Warehouse
|
||||
"""
|
||||
|
||||
# APPLICATION MODULES
|
||||
MODULES = {
|
||||
'quality': {
|
||||
'name': 'Quality Control',
|
||||
'scan_pages': ['quality', 'fg_quality'],
|
||||
'management_pages': ['quality_reports', 'quality_settings'],
|
||||
'worker_access': ['scan_only'] # Workers can only scan, no reports
|
||||
},
|
||||
'labels': {
|
||||
'name': 'Label Management',
|
||||
'scan_pages': ['label_scan'],
|
||||
'management_pages': ['label_creation', 'label_reports'],
|
||||
'worker_access': ['scan_only']
|
||||
},
|
||||
'warehouse': {
|
||||
'name': 'Warehouse Management',
|
||||
'scan_pages': ['move_orders'],
|
||||
'management_pages': ['create_locations', 'warehouse_reports', 'inventory_management'],
|
||||
'worker_access': ['move_orders_only'] # Workers can move orders but not create locations
|
||||
},
|
||||
'daily_mirror': {
|
||||
'name': 'Daily Mirror',
|
||||
'scan_pages': [], # No scanning, purely reporting/analytics
|
||||
'management_pages': ['daily_mirror_main', 'daily_mirror_report', 'daily_mirror_history', 'daily_mirror_analytics'],
|
||||
'worker_access': ['view_only'], # Workers can view daily reports but cannot generate or export
|
||||
'description': 'Business Intelligence and Production Reporting Module'
|
||||
}
|
||||
}
|
||||
|
||||
# 4-TIER ROLE STRUCTURE
|
||||
ROLES = {
|
||||
'superadmin': {
|
||||
'name': 'Super Administrator',
|
||||
'level': 100,
|
||||
'description': 'Full system access - complete control over all modules and system settings',
|
||||
'access': {
|
||||
'all_modules': True,
|
||||
'all_pages': True,
|
||||
'restricted_pages': [] # No restrictions
|
||||
}
|
||||
},
|
||||
'admin': {
|
||||
'name': 'Administrator',
|
||||
'level': 90,
|
||||
'description': 'Full app access except role permissions and extension download',
|
||||
'access': {
|
||||
'all_modules': True,
|
||||
'all_pages': True,
|
||||
'restricted_pages': ['role_permissions', 'download_extension']
|
||||
}
|
||||
},
|
||||
'manager': {
|
||||
'name': 'Manager',
|
||||
'level': 70,
|
||||
'description': 'Complete module access - can manage one or more modules (quality/labels/warehouse)',
|
||||
'access': {
|
||||
'all_modules': False, # Only assigned modules
|
||||
'module_access': 'full', # Full access to assigned modules
|
||||
'can_cumulate': True, # Can have multiple modules
|
||||
'restricted_pages': ['role_permissions', 'download_extension', 'system_settings']
|
||||
}
|
||||
},
|
||||
'worker': {
|
||||
'name': 'Worker',
|
||||
'level': 50,
|
||||
'description': 'Limited module access - can perform basic operations in assigned modules',
|
||||
'access': {
|
||||
'all_modules': False, # Only assigned modules
|
||||
'module_access': 'limited', # Limited access (scan pages only)
|
||||
'can_cumulate': True, # Can have multiple modules
|
||||
'restricted_pages': ['role_permissions', 'download_extension', 'system_settings', 'reports']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# PAGE ACCESS RULES
|
||||
PAGE_ACCESS = {
|
||||
# System pages accessible by role level
|
||||
'dashboard': {'min_level': 50, 'modules': []},
|
||||
'settings': {'min_level': 90, 'modules': []},
|
||||
'role_permissions': {'min_level': 100, 'modules': []}, # Superadmin only
|
||||
'download_extension': {'min_level': 100, 'modules': []}, # Superadmin only
|
||||
|
||||
# Quality module pages
|
||||
'quality': {'min_level': 50, 'modules': ['quality']},
|
||||
'fg_quality': {'min_level': 50, 'modules': ['quality']},
|
||||
'quality_reports': {'min_level': 70, 'modules': ['quality']}, # Manager+ only
|
||||
'reports': {'min_level': 70, 'modules': ['quality']}, # Manager+ only for quality reports
|
||||
|
||||
# Warehouse module pages
|
||||
'warehouse': {'min_level': 50, 'modules': ['warehouse']},
|
||||
'move_orders': {'min_level': 50, 'modules': ['warehouse']},
|
||||
'create_locations': {'min_level': 70, 'modules': ['warehouse']}, # Manager+ only
|
||||
'warehouse_reports': {'min_level': 70, 'modules': ['warehouse']}, # Manager+ only
|
||||
|
||||
# Labels module pages
|
||||
'labels': {'min_level': 50, 'modules': ['labels']},
|
||||
'label_scan': {'min_level': 50, 'modules': ['labels']},
|
||||
'label_creation': {'min_level': 70, 'modules': ['labels']}, # Manager+ only
|
||||
'label_reports': {'min_level': 70, 'modules': ['labels']}, # Manager+ only
|
||||
|
||||
# Daily Mirror module pages
|
||||
'daily_mirror_main': {'min_level': 70, 'modules': ['daily_mirror']}, # Manager+ only
|
||||
'daily_mirror_report': {'min_level': 70, 'modules': ['daily_mirror']}, # Manager+ only
|
||||
'daily_mirror_history': {'min_level': 70, 'modules': ['daily_mirror']}, # Manager+ only
|
||||
'daily_mirror_analytics': {'min_level': 90, 'modules': ['daily_mirror']}, # Admin+ only for advanced analytics
|
||||
'daily_mirror': {'min_level': 70, 'modules': ['daily_mirror']} # Legacy route support
|
||||
}
|
||||
|
||||
def check_access(user_role, user_modules, page):
|
||||
"""
|
||||
Simple access check for the 4-tier system
|
||||
|
||||
Args:
|
||||
user_role (str): User's role (superadmin, admin, manager, worker)
|
||||
user_modules (list): User's assigned modules ['quality', 'warehouse']
|
||||
page (str): Page being accessed
|
||||
|
||||
Returns:
|
||||
bool: True if access granted, False otherwise
|
||||
"""
|
||||
if user_role not in ROLES:
|
||||
return False
|
||||
|
||||
user_level = ROLES[user_role]['level']
|
||||
|
||||
# Check if page exists in our access rules
|
||||
if page not in PAGE_ACCESS:
|
||||
return False
|
||||
|
||||
page_config = PAGE_ACCESS[page]
|
||||
|
||||
# Check minimum level requirement
|
||||
if user_level < page_config['min_level']:
|
||||
return False
|
||||
|
||||
# Check restricted pages for this role
|
||||
if page in ROLES[user_role]['access']['restricted_pages']:
|
||||
return False
|
||||
|
||||
# Check module requirements
|
||||
required_modules = page_config['modules']
|
||||
if required_modules:
|
||||
# Page requires specific modules
|
||||
# Superadmin and admin have access to all modules by default
|
||||
if ROLES[user_role]['access']['all_modules']:
|
||||
return True
|
||||
# Other roles need to have the required module assigned
|
||||
if not any(module in user_modules for module in required_modules):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_user_accessible_pages(user_role, user_modules):
|
||||
"""
|
||||
Get list of pages accessible to a user
|
||||
|
||||
Args:
|
||||
user_role (str): User's role
|
||||
user_modules (list): User's assigned modules
|
||||
|
||||
Returns:
|
||||
list: List of accessible page names
|
||||
"""
|
||||
accessible_pages = []
|
||||
|
||||
for page in PAGE_ACCESS.keys():
|
||||
if check_access(user_role, user_modules, page):
|
||||
accessible_pages.append(page)
|
||||
|
||||
return accessible_pages
|
||||
|
||||
def validate_user_modules(user_role, user_modules):
|
||||
"""
|
||||
Validate that user's module assignment is valid for their role
|
||||
|
||||
Args:
|
||||
user_role (str): User's role
|
||||
user_modules (list): User's assigned modules
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid, error_message)
|
||||
"""
|
||||
if user_role not in ROLES:
|
||||
return False, "Invalid role"
|
||||
|
||||
role_config = ROLES[user_role]
|
||||
|
||||
# Superadmin and admin have access to all modules by default
|
||||
if role_config['access']['all_modules']:
|
||||
return True, ""
|
||||
|
||||
# Manager can have multiple modules
|
||||
if user_role == 'manager':
|
||||
if not user_modules:
|
||||
return False, "Managers must have at least one module assigned"
|
||||
valid_modules = list(MODULES.keys())
|
||||
for module in user_modules:
|
||||
if module not in valid_modules:
|
||||
return False, f"Invalid module: {module}"
|
||||
return True, ""
|
||||
|
||||
# Worker can have multiple modules now
|
||||
if user_role == 'worker':
|
||||
if not user_modules:
|
||||
return False, "Workers must have at least one module assigned"
|
||||
valid_modules = list(MODULES.keys())
|
||||
for module in user_modules:
|
||||
if module not in valid_modules:
|
||||
return False, f"Invalid module: {module}"
|
||||
return True, ""
|
||||
|
||||
return True, ""
|
||||
|
||||
def get_role_description(role):
|
||||
"""Get human-readable role description"""
|
||||
return ROLES.get(role, {}).get('description', 'Unknown role')
|
||||
|
||||
def get_available_modules():
|
||||
"""Get list of available modules"""
|
||||
return list(MODULES.keys())
|
||||
|
||||
def can_access_reports(user_role, user_modules, module):
|
||||
"""
|
||||
Check if user can access reports for a specific module
|
||||
Worker level users cannot access reports
|
||||
"""
|
||||
if user_role == 'worker':
|
||||
return False
|
||||
|
||||
if module in user_modules or ROLES[user_role]['access']['all_modules']:
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -34,9 +34,9 @@ def get_unprinted_orders_data(limit=100):
|
||||
# Use printed_labels column
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
created_at, updated_at, printed_labels, data_livrare, dimensiune
|
||||
printed_labels, created_at, updated_at
|
||||
FROM order_for_labels
|
||||
WHERE printed_labels != 1
|
||||
ORDER BY created_at DESC
|
||||
@@ -46,7 +46,7 @@ def get_unprinted_orders_data(limit=100):
|
||||
# Fallback: get all orders if no printed_labels column
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
created_at, updated_at
|
||||
FROM order_for_labels
|
||||
@@ -63,17 +63,17 @@ def get_unprinted_orders_data(limit=100):
|
||||
'cod_articol': row[2],
|
||||
'descr_com_prod': row[3],
|
||||
'cantitate': row[4],
|
||||
'com_achiz_client': row[5],
|
||||
'nr_linie_com_client': row[6],
|
||||
'customer_name': row[7],
|
||||
'customer_article_number': row[8],
|
||||
'open_for_order': row[9],
|
||||
'line_number': row[10],
|
||||
'created_at': row[11],
|
||||
'updated_at': row[12],
|
||||
'data_livrare': row[5],
|
||||
'dimensiune': row[6],
|
||||
'com_achiz_client': row[7],
|
||||
'nr_linie_com_client': row[8],
|
||||
'customer_name': row[9],
|
||||
'customer_article_number': row[10],
|
||||
'open_for_order': row[11],
|
||||
'line_number': row[12],
|
||||
'printed_labels': row[13],
|
||||
'data_livrare': row[14] or '-',
|
||||
'dimensiune': row[15] or '-'
|
||||
'created_at': row[14],
|
||||
'updated_at': row[15]
|
||||
})
|
||||
else:
|
||||
orders.append({
|
||||
@@ -82,18 +82,17 @@ def get_unprinted_orders_data(limit=100):
|
||||
'cod_articol': row[2],
|
||||
'descr_com_prod': row[3],
|
||||
'cantitate': row[4],
|
||||
'com_achiz_client': row[5],
|
||||
'nr_linie_com_client': row[6],
|
||||
'customer_name': row[7],
|
||||
'customer_article_number': row[8],
|
||||
'open_for_order': row[9],
|
||||
'line_number': row[10],
|
||||
'created_at': row[11],
|
||||
'updated_at': row[12],
|
||||
# Add default values for missing columns
|
||||
'data_livrare': '-',
|
||||
'dimensiune': '-',
|
||||
'printed_labels': 0
|
||||
'data_livrare': row[5],
|
||||
'dimensiune': row[6],
|
||||
'com_achiz_client': row[7],
|
||||
'nr_linie_com_client': row[8],
|
||||
'customer_name': row[9],
|
||||
'customer_article_number': row[10],
|
||||
'open_for_order': row[11],
|
||||
'line_number': row[12],
|
||||
'printed_labels': 0, # Default to not printed
|
||||
'created_at': row[13],
|
||||
'updated_at': row[14]
|
||||
})
|
||||
|
||||
conn.close()
|
||||
|
||||
1239
py_app/app/routes.py
1239
py_app/app/routes.py
File diff suppressed because it is too large
Load Diff
@@ -172,41 +172,37 @@ def settings_handler():
|
||||
flash('Access denied: Superadmin only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Get users from external MariaDB database with modules (same logic as user_management_simple)
|
||||
# Get users from external MariaDB database
|
||||
users = []
|
||||
try:
|
||||
conn = get_external_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if users table exists and get users with modules
|
||||
cursor.execute("SHOW TABLES LIKE 'users'")
|
||||
if cursor.fetchone():
|
||||
cursor.execute("SELECT id, username, role, modules FROM users")
|
||||
for row in cursor.fetchall():
|
||||
user_data = {
|
||||
'id': row[0],
|
||||
'username': row[1],
|
||||
'role': row[2],
|
||||
'modules': row[3] if len(row) > 3 else None
|
||||
}
|
||||
# Create a mock user object with get_modules method
|
||||
class MockUser:
|
||||
def __init__(self, data):
|
||||
self.id = data['id']
|
||||
self.username = data['username']
|
||||
self.role = data['role']
|
||||
self.modules = data['modules']
|
||||
|
||||
def get_modules(self):
|
||||
if not self.modules:
|
||||
return []
|
||||
try:
|
||||
import json
|
||||
return json.loads(self.modules)
|
||||
except:
|
||||
return []
|
||||
|
||||
users.append(MockUser(user_data))
|
||||
# Create users table if it doesn't exist
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL,
|
||||
email VARCHAR(255)
|
||||
)
|
||||
''')
|
||||
|
||||
# Get all users from external database
|
||||
cursor.execute("SELECT id, username, password, role, email FROM users")
|
||||
users_data = cursor.fetchall()
|
||||
|
||||
# Convert to list of dictionaries for template compatibility
|
||||
users = []
|
||||
for user_data in users_data:
|
||||
users.append({
|
||||
'id': user_data[0],
|
||||
'username': user_data[1],
|
||||
'password': user_data[2],
|
||||
'role': user_data[3],
|
||||
'email': user_data[4] if len(user_data) > 4 else None
|
||||
})
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
/* Daily Mirror Tune Pages - Modal Styles */
|
||||
/* Fixes for editable modals across tune/production, tune/orders, and tune/delivery pages */
|
||||
|
||||
/* Force Bootstrap modal to have proper z-index */
|
||||
#editModal.modal {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
#editModal .modal-backdrop {
|
||||
z-index: 9998 !important;
|
||||
}
|
||||
|
||||
/* Ensure modal dialog is interactive */
|
||||
#editModal .modal-dialog {
|
||||
pointer-events: auto !important;
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
|
||||
#editModal .modal-content {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/* Make all inputs in the modal fully interactive */
|
||||
#editModal .form-control:not([readonly]),
|
||||
#editModal .form-select:not([readonly]),
|
||||
#editModal input:not([readonly]):not([type="hidden"]),
|
||||
#editModal select:not([readonly]),
|
||||
#editModal textarea:not([readonly]) {
|
||||
pointer-events: auto !important;
|
||||
user-select: text !important;
|
||||
cursor: text !important;
|
||||
background-color: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
opacity: 1 !important;
|
||||
-webkit-user-select: text !important;
|
||||
-moz-user-select: text !important;
|
||||
-ms-user-select: text !important;
|
||||
}
|
||||
|
||||
#editModal .form-control:focus:not([readonly]),
|
||||
#editModal input:focus:not([readonly]),
|
||||
#editModal select:focus:not([readonly]),
|
||||
#editModal textarea:focus:not([readonly]) {
|
||||
background-color: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
border-color: #007bff !important;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
/* Dark mode specific overrides for modal inputs */
|
||||
body.dark-mode #editModal .form-control:not([readonly]),
|
||||
body.dark-mode #editModal input:not([readonly]):not([type="hidden"]),
|
||||
body.dark-mode #editModal select:not([readonly]),
|
||||
body.dark-mode #editModal textarea:not([readonly]) {
|
||||
background-color: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
body.dark-mode #editModal .form-control:focus:not([readonly]),
|
||||
body.dark-mode #editModal input:focus:not([readonly]),
|
||||
body.dark-mode #editModal select:focus:not([readonly]),
|
||||
body.dark-mode #editModal textarea:focus:not([readonly]) {
|
||||
background-color: #ffffff !important;
|
||||
color: #000000 !important;
|
||||
border-color: #007bff !important;
|
||||
}
|
||||
|
||||
/* Readonly fields should still look readonly */
|
||||
#editModal .form-control[readonly],
|
||||
#editModal input[readonly] {
|
||||
background-color: #e9ecef !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
body.dark-mode #editModal .form-control[readonly],
|
||||
body.dark-mode #editModal input[readonly] {
|
||||
background-color: #6c757d !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
/* Dark mode styles for cards and tables */
|
||||
body.dark-mode .card {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .card-header {
|
||||
background-color: #4a5568;
|
||||
border-bottom: 1px solid #6b7280;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .form-control {
|
||||
background-color: #4a5568;
|
||||
border-color: #6b7280;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .form-control:focus {
|
||||
background-color: #4a5568;
|
||||
border-color: #007bff;
|
||||
color: #e2e8f0;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
body.dark-mode .table {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
body.dark-mode .table-hover tbody tr:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body.dark-mode .modal-content {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-header {
|
||||
border-bottom: 1px solid #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-footer {
|
||||
border-top: 1px solid #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-secondary {
|
||||
background-color: #4a5568;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-secondary:hover {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-close {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
/* Table and button styling */
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.1rem;
|
||||
}
|
||||
|
||||
/* Editable field highlighting */
|
||||
.editable {
|
||||
background-color: #fff3cd;
|
||||
border: 1px dashed #ffc107;
|
||||
}
|
||||
|
||||
body.dark-mode .editable {
|
||||
background-color: #2d2d00;
|
||||
border: 1px dashed #ffc107;
|
||||
}
|
||||
|
||||
/* Compact table styling */
|
||||
.table-sm th,
|
||||
.table-sm td {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Action button styling */
|
||||
.btn-sm {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Pagination styling */
|
||||
.pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.dark-mode .pagination .page-link {
|
||||
background-color: #4a5568;
|
||||
border: 1px solid #6b7280;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .pagination .page-link:hover {
|
||||
background-color: #374151;
|
||||
border-color: #6b7280;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .pagination .page-item.active .page-link {
|
||||
background-color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Additional dark mode styles */
|
||||
body.dark-mode .container-fluid {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .table-dark th {
|
||||
background-color: #1a202c;
|
||||
color: #e2e8f0;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .table-striped > tbody > tr:nth-of-type(odd) > td {
|
||||
background-color: #374151;
|
||||
}
|
||||
|
||||
body.dark-mode .table-hover > tbody > tr:hover > td {
|
||||
background-color: #4a5568;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
}
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
// FG Quality specific JavaScript - Standalone version
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prevent conflicts with main script.js by removing existing listeners
|
||||
console.log('FG Quality JavaScript loaded');
|
||||
|
||||
const reportButtons = document.querySelectorAll('.report-btn');
|
||||
const reportTable = document.getElementById('report-table');
|
||||
const reportTitle = document.getElementById('report-title');
|
||||
const exportCsvButton = document.getElementById('export-csv');
|
||||
|
||||
// Calendar elements
|
||||
const calendarModal = document.getElementById('calendar-modal');
|
||||
const dateRangeModal = document.getElementById('date-range-modal');
|
||||
const selectDayReport = document.getElementById('select-day-report');
|
||||
const selectDayDefectsReport = document.getElementById('select-day-defects-report');
|
||||
const dateRangeReport = document.getElementById('date-range-report');
|
||||
const dateRangeDefectsReport = document.getElementById('date-range-defects-report');
|
||||
|
||||
let currentReportType = null;
|
||||
let currentDate = new Date();
|
||||
let selectedDate = null;
|
||||
|
||||
// Clear any existing event listeners by cloning elements
|
||||
function clearExistingListeners() {
|
||||
if (selectDayReport) {
|
||||
const newSelectDayReport = selectDayReport.cloneNode(true);
|
||||
selectDayReport.parentNode.replaceChild(newSelectDayReport, selectDayReport);
|
||||
}
|
||||
if (selectDayDefectsReport) {
|
||||
const newSelectDayDefectsReport = selectDayDefectsReport.cloneNode(true);
|
||||
selectDayDefectsReport.parentNode.replaceChild(newSelectDayDefectsReport, selectDayDefectsReport);
|
||||
}
|
||||
if (dateRangeReport) {
|
||||
const newDateRangeReport = dateRangeReport.cloneNode(true);
|
||||
dateRangeReport.parentNode.replaceChild(newDateRangeReport, dateRangeReport);
|
||||
}
|
||||
if (dateRangeDefectsReport) {
|
||||
const newDateRangeDefectsReport = dateRangeDefectsReport.cloneNode(true);
|
||||
dateRangeDefectsReport.parentNode.replaceChild(newDateRangeDefectsReport, dateRangeDefectsReport);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear existing listeners first
|
||||
clearExistingListeners();
|
||||
|
||||
// Re-get elements after cloning
|
||||
const newSelectDayReport = document.getElementById('select-day-report');
|
||||
const newSelectDayDefectsReport = document.getElementById('select-day-defects-report');
|
||||
const newDateRangeReport = document.getElementById('date-range-report');
|
||||
const newDateRangeDefectsReport = document.getElementById('date-range-defects-report');
|
||||
|
||||
// Add event listeners to report buttons
|
||||
reportButtons.forEach(button => {
|
||||
const reportType = button.getAttribute('data-report');
|
||||
if (reportType) {
|
||||
// Clone to remove existing listeners
|
||||
const newButton = button.cloneNode(true);
|
||||
button.parentNode.replaceChild(newButton, button);
|
||||
|
||||
newButton.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Report button clicked:', reportType);
|
||||
fetchFGReportData(reportType);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calendar-based report buttons with FG-specific handlers
|
||||
if (newSelectDayReport) {
|
||||
newSelectDayReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Select Day Report clicked');
|
||||
currentReportType = '6';
|
||||
showCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (newSelectDayDefectsReport) {
|
||||
newSelectDayDefectsReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Select Day Defects Report clicked');
|
||||
currentReportType = '8';
|
||||
showCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (newDateRangeReport) {
|
||||
newDateRangeReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Date Range Report clicked');
|
||||
currentReportType = '7';
|
||||
showDateRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (newDateRangeDefectsReport) {
|
||||
newDateRangeDefectsReport.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Date Range Defects Report clicked');
|
||||
currentReportType = '9';
|
||||
showDateRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
// Function to fetch FG report data
|
||||
function fetchFGReportData(reportType) {
|
||||
const url = `/get_fg_report_data?report=${reportType}`;
|
||||
console.log('Fetching FG data from:', url);
|
||||
reportTitle.textContent = 'Loading FG data...';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('FG Report data received:', data);
|
||||
if (data.error) {
|
||||
reportTitle.textContent = data.error;
|
||||
return;
|
||||
}
|
||||
|
||||
populateFGTable(data);
|
||||
updateReportTitle(reportType);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching FG report data:', error);
|
||||
reportTitle.textContent = 'Error loading FG data.';
|
||||
});
|
||||
}
|
||||
|
||||
// Function to fetch FG report data for specific dates
|
||||
function fetchFGDateReportData(reportType, date, startDate = null, endDate = null) {
|
||||
let url = `/generate_fg_report?report=${reportType}`;
|
||||
if (date) {
|
||||
url += `&date=${date}`;
|
||||
}
|
||||
if (startDate && endDate) {
|
||||
url += `&start_date=${startDate}&end_date=${endDate}`;
|
||||
}
|
||||
|
||||
console.log('Fetching FG date report from:', url);
|
||||
reportTitle.textContent = 'Loading FG data...';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('FG Date report data received:', data);
|
||||
if (data.error) {
|
||||
reportTitle.textContent = data.error;
|
||||
return;
|
||||
}
|
||||
|
||||
populateFGTable(data);
|
||||
updateDateReportTitle(reportType, date, startDate, endDate);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching FG date report data:', error);
|
||||
reportTitle.textContent = 'Error loading FG data.';
|
||||
});
|
||||
}
|
||||
|
||||
// Function to populate the table with FG data
|
||||
function populateFGTable(data) {
|
||||
const thead = reportTable.querySelector('thead tr');
|
||||
const tbody = reportTable.querySelector('tbody');
|
||||
|
||||
// Clear existing content
|
||||
thead.innerHTML = '';
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// Add headers
|
||||
if (data.headers && data.headers.length > 0) {
|
||||
data.headers.forEach(header => {
|
||||
const th = document.createElement('th');
|
||||
th.textContent = header;
|
||||
thead.appendChild(th);
|
||||
});
|
||||
}
|
||||
|
||||
// Add rows
|
||||
if (data.rows && data.rows.length > 0) {
|
||||
data.rows.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
row.forEach(cell => {
|
||||
const td = document.createElement('td');
|
||||
td.textContent = cell || '';
|
||||
tr.appendChild(td);
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} else {
|
||||
// Show no data message
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = data.headers ? data.headers.length : 1;
|
||||
td.textContent = data.message || 'No FG data found for the selected criteria.';
|
||||
td.style.textAlign = 'center';
|
||||
td.style.fontStyle = 'italic';
|
||||
td.style.padding = '20px';
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update report title based on type
|
||||
function updateReportTitle(reportType) {
|
||||
const titles = {
|
||||
'1': 'Daily Complete FG Orders Report',
|
||||
'2': '5-Day Complete FG Orders Report',
|
||||
'3': 'FG Items with Defects for Current Day',
|
||||
'4': 'FG Items with Defects for Last 5 Days',
|
||||
'5': 'Complete FG Database Report'
|
||||
};
|
||||
|
||||
reportTitle.textContent = titles[reportType] || 'FG Quality Report';
|
||||
}
|
||||
|
||||
// Function to update report title for date-based reports
|
||||
function updateDateReportTitle(reportType, date, startDate, endDate) {
|
||||
const titles = {
|
||||
'6': `FG Daily Report for ${date}`,
|
||||
'7': `FG Date Range Report (${startDate} to ${endDate})`,
|
||||
'8': `FG Quality Defects Report for ${date}`,
|
||||
'9': `FG Quality Defects Range Report (${startDate} to ${endDate})`
|
||||
};
|
||||
|
||||
reportTitle.textContent = titles[reportType] || 'FG Quality Report';
|
||||
}
|
||||
|
||||
// Calendar functionality
|
||||
function showCalendarModal() {
|
||||
if (calendarModal) {
|
||||
calendarModal.style.display = 'block';
|
||||
generateCalendar();
|
||||
}
|
||||
}
|
||||
|
||||
function hideCalendarModal() {
|
||||
if (calendarModal) {
|
||||
calendarModal.style.display = 'none';
|
||||
selectedDate = null;
|
||||
updateConfirmButton();
|
||||
}
|
||||
}
|
||||
|
||||
function showDateRangeModal() {
|
||||
if (dateRangeModal) {
|
||||
dateRangeModal.style.display = 'block';
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('start-date').value = today;
|
||||
document.getElementById('end-date').value = today;
|
||||
}
|
||||
}
|
||||
|
||||
function hideDataRangeModal() {
|
||||
if (dateRangeModal) {
|
||||
dateRangeModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function generateCalendar() {
|
||||
const calendarDays = document.getElementById('calendar-days');
|
||||
const monthYear = document.getElementById('calendar-month-year');
|
||||
|
||||
if (!calendarDays || !monthYear) return;
|
||||
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
|
||||
monthYear.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${year}`;
|
||||
|
||||
// Clear previous days
|
||||
calendarDays.innerHTML = '';
|
||||
|
||||
// Get first day of month and number of days
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
// Add empty cells for previous month
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
const emptyDay = document.createElement('div');
|
||||
emptyDay.className = 'calendar-day empty';
|
||||
calendarDays.appendChild(emptyDay);
|
||||
}
|
||||
|
||||
// Add days of current month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dayElement = document.createElement('div');
|
||||
dayElement.className = 'calendar-day';
|
||||
dayElement.textContent = day;
|
||||
|
||||
// Check if it's today
|
||||
const today = new Date();
|
||||
if (year === today.getFullYear() && month === today.getMonth() && day === today.getDate()) {
|
||||
dayElement.classList.add('today');
|
||||
}
|
||||
|
||||
dayElement.addEventListener('click', () => {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll('.calendar-day.selected').forEach(el => {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to clicked day
|
||||
dayElement.classList.add('selected');
|
||||
|
||||
// Set selected date
|
||||
selectedDate = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
console.log('FG Calendar date selected:', selectedDate);
|
||||
updateConfirmButton();
|
||||
});
|
||||
|
||||
calendarDays.appendChild(dayElement);
|
||||
}
|
||||
}
|
||||
|
||||
function updateConfirmButton() {
|
||||
const confirmButton = document.getElementById('confirm-date');
|
||||
if (confirmButton) {
|
||||
confirmButton.disabled = !selectedDate;
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar navigation
|
||||
const prevMonthBtn = document.getElementById('prev-month');
|
||||
const nextMonthBtn = document.getElementById('next-month');
|
||||
|
||||
if (prevMonthBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newPrevBtn = prevMonthBtn.cloneNode(true);
|
||||
prevMonthBtn.parentNode.replaceChild(newPrevBtn, prevMonthBtn);
|
||||
|
||||
newPrevBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
currentDate.setMonth(currentDate.getMonth() - 1);
|
||||
generateCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
if (nextMonthBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newNextBtn = nextMonthBtn.cloneNode(true);
|
||||
nextMonthBtn.parentNode.replaceChild(newNextBtn, nextMonthBtn);
|
||||
|
||||
newNextBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||||
generateCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
// Calendar modal buttons
|
||||
const cancelDateBtn = document.getElementById('cancel-date');
|
||||
const confirmDateBtn = document.getElementById('confirm-date');
|
||||
|
||||
if (cancelDateBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newCancelBtn = cancelDateBtn.cloneNode(true);
|
||||
cancelDateBtn.parentNode.replaceChild(newCancelBtn, cancelDateBtn);
|
||||
|
||||
newCancelBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
hideCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (confirmDateBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newConfirmBtn = confirmDateBtn.cloneNode(true);
|
||||
confirmDateBtn.parentNode.replaceChild(newConfirmBtn, confirmDateBtn);
|
||||
|
||||
newConfirmBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('FG Calendar confirm clicked with date:', selectedDate, 'report type:', currentReportType);
|
||||
if (selectedDate && currentReportType) {
|
||||
fetchFGDateReportData(currentReportType, selectedDate);
|
||||
hideCalendarModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Date range modal buttons
|
||||
const cancelDateRangeBtn = document.getElementById('cancel-date-range');
|
||||
const confirmDateRangeBtn = document.getElementById('confirm-date-range');
|
||||
|
||||
if (cancelDateRangeBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newCancelRangeBtn = cancelDateRangeBtn.cloneNode(true);
|
||||
cancelDateRangeBtn.parentNode.replaceChild(newCancelRangeBtn, cancelDateRangeBtn);
|
||||
|
||||
newCancelRangeBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
hideDataRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (confirmDateRangeBtn) {
|
||||
// Clone to remove existing listeners
|
||||
const newConfirmRangeBtn = confirmDateRangeBtn.cloneNode(true);
|
||||
confirmDateRangeBtn.parentNode.replaceChild(newConfirmRangeBtn, confirmDateRangeBtn);
|
||||
|
||||
newConfirmRangeBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const startDate = document.getElementById('start-date').value;
|
||||
const endDate = document.getElementById('end-date').value;
|
||||
|
||||
console.log('FG Date range confirm clicked:', startDate, 'to', endDate, 'report type:', currentReportType);
|
||||
if (startDate && endDate && currentReportType) {
|
||||
fetchFGDateReportData(currentReportType, null, startDate, endDate);
|
||||
hideDataRangeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enable/disable date range confirm button
|
||||
const startDateInput = document.getElementById('start-date');
|
||||
const endDateInput = document.getElementById('end-date');
|
||||
|
||||
function updateDateRangeConfirmButton() {
|
||||
const confirmBtn = document.getElementById('confirm-date-range');
|
||||
if (confirmBtn && startDateInput && endDateInput) {
|
||||
confirmBtn.disabled = !startDateInput.value || !endDateInput.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (startDateInput) {
|
||||
startDateInput.addEventListener('change', updateDateRangeConfirmButton);
|
||||
}
|
||||
|
||||
if (endDateInput) {
|
||||
endDateInput.addEventListener('change', updateDateRangeConfirmButton);
|
||||
}
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === calendarModal) {
|
||||
hideCalendarModal();
|
||||
}
|
||||
if (event.target === dateRangeModal) {
|
||||
hideDataRangeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modals with X button
|
||||
document.querySelectorAll('.close-modal').forEach(closeBtn => {
|
||||
// Clone to remove existing listeners
|
||||
const newCloseBtn = closeBtn.cloneNode(true);
|
||||
closeBtn.parentNode.replaceChild(newCloseBtn, closeBtn);
|
||||
|
||||
newCloseBtn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const modal = event.target.closest('.modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Export functionality
|
||||
if (exportCsvButton) {
|
||||
exportCsvButton.addEventListener('click', () => {
|
||||
const rows = reportTable.querySelectorAll('tr');
|
||||
if (rows.length === 0) {
|
||||
alert('No FG data available to export.');
|
||||
return;
|
||||
}
|
||||
const reportTitleText = reportTitle.textContent.trim();
|
||||
const filename = `${reportTitleText.replace(/\s+/g, '_')}.csv`;
|
||||
exportTableToCSV(filename);
|
||||
});
|
||||
}
|
||||
|
||||
// Export to CSV function
|
||||
function exportTableToCSV(filename) {
|
||||
const table = reportTable;
|
||||
const rows = Array.from(table.querySelectorAll('tr'));
|
||||
|
||||
const csvContent = rows.map(row => {
|
||||
const cells = Array.from(row.querySelectorAll('th, td'));
|
||||
return cells.map(cell => {
|
||||
let text = cell.textContent.trim();
|
||||
// Escape quotes and wrap in quotes if necessary
|
||||
if (text.includes(',') || text.includes('"') || text.includes('\n')) {
|
||||
text = '"' + text.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return text;
|
||||
}).join(',');
|
||||
}).join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// Test Database Button
|
||||
const testDatabaseBtn = document.getElementById('test-database');
|
||||
if (testDatabaseBtn) {
|
||||
testDatabaseBtn.addEventListener('click', () => {
|
||||
console.log('Testing FG database connection...');
|
||||
reportTitle.textContent = 'Testing FG Database Connection...';
|
||||
fetch('/test_fg_database')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('FG Database test results:', data);
|
||||
if (data.success) {
|
||||
reportTitle.textContent = `FG Database Test Results - ${data.total_records} records found`;
|
||||
// Show alert with summary
|
||||
alert(`FG Database Test Complete!\n\nConnection: ${data.database_connection}\nTable exists: ${data.table_exists}\nTotal records: ${data.total_records}\nMessage: ${data.message}`);
|
||||
} else {
|
||||
reportTitle.textContent = 'FG Database Test Failed';
|
||||
alert(`FG Database test failed: ${data.message}`);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('FG Database test error:', error);
|
||||
reportTitle.textContent = 'Error testing FG database.';
|
||||
alert('Error testing FG database connection.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log('FG Quality JavaScript setup complete');
|
||||
});
|
||||
@@ -806,20 +806,6 @@ body.dark-mode .export-description {
|
||||
background-color: #218838; /* Darker green on hover */
|
||||
}
|
||||
|
||||
.go-to-main-reports-btn {
|
||||
background-color: #007bff; /* Blue background */
|
||||
color: #fff; /* White text */
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
margin-left: 10px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.go-to-main-reports-btn:hover {
|
||||
background-color: #0056b3; /* Darker blue on hover */
|
||||
}
|
||||
|
||||
.draggable-field {
|
||||
cursor: move;
|
||||
padding: 5px;
|
||||
|
||||
@@ -4,16 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Flask App{% endblock %}</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome for icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<!-- Base CSS for common styles -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
|
||||
<!-- Legacy CSS for backward compatibility (temporarily) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<!-- Page-specific CSS -->
|
||||
{% block extra_css %}{% endblock %}
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="light-mode">
|
||||
@@ -26,12 +21,8 @@
|
||||
<span class="page-title">Welcome to Dashboard</span>
|
||||
{% elif request.endpoint == 'main.settings' %}
|
||||
<span class="page-title">Settings</span>
|
||||
{% elif request.endpoint == 'main.reports' %}
|
||||
<span class="page-title">Reports Module</span>
|
||||
{% elif request.endpoint == 'main.quality' %}
|
||||
<span class="page-title">Quality Module</span>
|
||||
{% elif request.endpoint == 'main.fg_quality' %}
|
||||
<span class="page-title">FG Quality Module</span>
|
||||
{% elif request.endpoint == 'main.warehouse' %}
|
||||
<span class="page-title">Warehouse Module</span>
|
||||
{% elif request.endpoint == 'main.scan' %}
|
||||
@@ -39,15 +30,15 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="right-header">
|
||||
<button id="theme-toggle" class="theme-toggle">Change to dark theme</button>
|
||||
{% if request.endpoint.startswith('daily_mirror') %}
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_main_route') }}" class="btn btn-info btn-sm ms-2"> <i class="fas fa-home"></i> Daily Mirror Main</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn go-to-dashboard-btn ms-2">Go to Dashboard</a>
|
||||
{% if 'user' in session %}
|
||||
<span class="user-info ms-2">You are logged in as {{ session['user'] }}</span>
|
||||
<a href="{{ url_for('main.logout') }}" class="logout-button ms-2">Logout</a>
|
||||
{% endif %}
|
||||
<button id="theme-toggle" class="theme-toggle">Change to dark theme</button>
|
||||
{% if request.endpoint in ['main.upload_data', 'main.upload_orders', 'main.print_module', 'main.label_templates', 'main.create_template', 'main.print_lost_labels', 'main.view_orders'] %}
|
||||
<a href="{{ url_for('main.etichete') }}" class="btn go-to-main-etichete-btn">Main Page Etichete</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn go-to-dashboard-btn">Go to Dashboard</a>
|
||||
{% if 'user' in session %}
|
||||
<span class="user-info">You are logged in as {{ session['user'] }}</span>
|
||||
<a href="{{ url_for('main.logout') }}" class="logout-button">Logout</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -55,11 +46,6 @@
|
||||
<div class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
{% if request.endpoint != 'main.fg_quality' %}
|
||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
<!-- Bootstrap JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,6 @@
|
||||
{% block title %}Create Warehouse Locations{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/warehouse.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
@@ -401,72 +400,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Edit button functionality
|
||||
editButton.addEventListener('click', function() {
|
||||
console.log('Edit button clicked', selectedLocation);
|
||||
if (selectedLocation) {
|
||||
openEditModal(selectedLocation);
|
||||
} else {
|
||||
showNotification('❌ No location selected', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// Delete button functionality
|
||||
deleteButton.addEventListener('click', function() {
|
||||
console.log('Delete button clicked', selectedLocation);
|
||||
if (selectedLocation) {
|
||||
openDeleteModal(selectedLocation);
|
||||
} else {
|
||||
showNotification('❌ No location selected', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize QZ Tray
|
||||
initializeQZTray();
|
||||
|
||||
// Handle edit form submission
|
||||
document.getElementById('edit-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
const data = {
|
||||
location_id: parseInt(formData.get('location_id')),
|
||||
location_code: formData.get('location_code'),
|
||||
size: formData.get('size') ? parseInt(formData.get('size')) : null,
|
||||
description: formData.get('description') || null
|
||||
};
|
||||
|
||||
console.log('Attempting to update location:', data);
|
||||
|
||||
// Send update request
|
||||
fetch("{{ url_for('warehouse.update_location') }}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Update response status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
console.log('Update result:', result);
|
||||
if (result.success) {
|
||||
showNotification('✅ Location updated successfully!', 'success');
|
||||
closeEditModal();
|
||||
// Reload page to show changes
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
} else {
|
||||
showNotification('❌ Error updating location: ' + result.error, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('❌ Error updating location: ' + error.message, 'error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Print barcode function with enhanced QZ Tray support
|
||||
@@ -490,7 +437,7 @@ async function printLocationBarcode() {
|
||||
printStatus.textContent = 'Generating label...';
|
||||
|
||||
// Generate PDF for the 4x8cm label
|
||||
const response = await fetch("{{ url_for('warehouse.generate_location_label_pdf') }}", {
|
||||
const response = await fetch('/generate_location_label_pdf', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -622,31 +569,22 @@ document.getElementById('edit-form').addEventListener('submit', function(e) {
|
||||
|
||||
const formData = new FormData(this);
|
||||
const data = {
|
||||
location_id: parseInt(formData.get('location_id')),
|
||||
location_id: formData.get('location_id'),
|
||||
location_code: formData.get('location_code'),
|
||||
size: formData.get('size') ? parseInt(formData.get('size')) : null,
|
||||
description: formData.get('description') || null
|
||||
size: formData.get('size'),
|
||||
description: formData.get('description')
|
||||
};
|
||||
|
||||
console.log('Attempting to update location:', data);
|
||||
|
||||
// Send update request
|
||||
fetch("{{ url_for('warehouse.update_location') }}", {
|
||||
fetch('/update_location', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Update response status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
console.log('Update result:', result);
|
||||
if (result.success) {
|
||||
showNotification('✅ Location updated successfully!', 'success');
|
||||
closeEditModal();
|
||||
@@ -662,46 +600,6 @@ document.getElementById('edit-form').addEventListener('submit', function(e) {
|
||||
});
|
||||
});
|
||||
|
||||
// Handle delete confirmation
|
||||
function confirmDelete() {
|
||||
const locationId = document.getElementById('delete-confirm-id').textContent;
|
||||
|
||||
console.log('Attempting to delete location ID:', locationId);
|
||||
|
||||
// Send delete request
|
||||
fetch("{{ url_for('warehouse.delete_location') }}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
location_id: parseInt(locationId)
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Delete response status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
console.log('Delete result:', result);
|
||||
if (result.success) {
|
||||
showNotification('✅ Location deleted successfully!', 'success');
|
||||
closeDeleteModal();
|
||||
// Reload page to show changes
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
} else {
|
||||
showNotification('❌ Error deleting location: ' + result.error, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showNotification('❌ Error deleting location: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle delete confirmation
|
||||
function confirmDelete() {
|
||||
const locationId = document.getElementById('delete-confirm-id').textContent;
|
||||
@@ -746,271 +644,4 @@ window.addEventListener('click', function(event) {
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Location Modal -->
|
||||
<div id="edit-modal" class="modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Edit Location</h3>
|
||||
<span class="close" onclick="closeEditModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="edit-form">
|
||||
<input type="hidden" id="edit-location-id" name="location_id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-location-code">Location Code:</label>
|
||||
<input type="text" id="edit-location-code" name="location_code" maxlength="12" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-size">Size:</label>
|
||||
<input type="number" id="edit-size" name="size">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-description">Description:</label>
|
||||
<input type="text" id="edit-description" name="description" maxlength="250">
|
||||
</div>
|
||||
|
||||
<div class="modal-buttons">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeEditModal()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Update Location</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Location Modal -->
|
||||
<div id="delete-modal" class="modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Delete Location</h3>
|
||||
<span class="close" onclick="closeDeleteModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete this location?</p>
|
||||
<div class="delete-info">
|
||||
<strong>ID:</strong> <span id="delete-confirm-id"></span><br>
|
||||
<strong>Code:</strong> <span id="delete-confirm-code"></span><br>
|
||||
<strong>Size:</strong> <span id="delete-confirm-size"></span><br>
|
||||
<strong>Description:</strong> <span id="delete-confirm-description"></span>
|
||||
</div>
|
||||
<p style="color: #d32f2f; font-weight: bold;">This action cannot be undone!</p>
|
||||
</div>
|
||||
<div class="modal-buttons">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeDeleteModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" onclick="confirmDelete()">Delete Location</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--app-overlay-bg, rgba(0, 0, 0, 0.5));
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--app-card-bg, #f8fafc);
|
||||
color: var(--app-card-text, #1e293b);
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
animation: modalFadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 25px;
|
||||
border-bottom: 1px solid #cbd5e1;
|
||||
background-color: var(--app-card-bg, #f8fafc);
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: var(--app-card-text, #1e293b);
|
||||
}
|
||||
|
||||
.close {
|
||||
color: var(--app-card-text, #1e293b);
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #e11d48;
|
||||
background-color: #f1f5f9;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 25px;
|
||||
background-color: var(--app-card-bg, #f8fafc);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--app-label-text, #334155);
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 6px;
|
||||
background-color: var(--app-input-bg, #e2e8f0);
|
||||
color: var(--app-input-text, #1e293b);
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
padding: 20px 25px;
|
||||
border-top: 1px solid #cbd5e1;
|
||||
background-color: var(--app-card-bg, #f8fafc);
|
||||
}
|
||||
|
||||
.modal-buttons .btn {
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
min-width: 100px;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-buttons .btn-primary {
|
||||
background: #1e293b;
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.modal-buttons .btn-primary:hover {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
.modal-buttons .btn-secondary {
|
||||
background: #e11d48;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.modal-buttons .btn-secondary:hover {
|
||||
background: #be185d;
|
||||
}
|
||||
|
||||
.delete-info {
|
||||
background-color: #fef3c7;
|
||||
border: 1px solid #f59e0b;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
border-left: 4px solid #f59e0b;
|
||||
color: var(--app-card-text, #1e293b);
|
||||
}
|
||||
|
||||
.delete-info strong {
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary-color, #007bff);
|
||||
color: white;
|
||||
border: 2px solid var(--primary-color, #007bff);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--primary-hover-color, #0056b3);
|
||||
border-color: var(--primary-hover-color, #0056b3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--secondary-color, #6c757d);
|
||||
color: white;
|
||||
border: 2px solid var(--secondary-color, #6c757d);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--secondary-hover-color, #545b62);
|
||||
border-color: var(--secondary-hover-color, #545b62);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--danger-color, #dc3545);
|
||||
color: white;
|
||||
border: 2px solid var(--danger-color, #dc3545);
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: var(--danger-hover-color, #c82333);
|
||||
border-color: var(--danger-hover-color, #c82333);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,447 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Daily Mirror - Quality Recticel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">📈 Daily Mirror</h1>
|
||||
<p class="text-muted">Generate comprehensive daily production reports</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_history_route') }}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-history"></i> View History
|
||||
</a>
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Selection Card -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-calendar-alt"></i> Select Report Date
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="reportDate" class="form-label">Report Date:</label>
|
||||
<input type="date" class="form-control" id="reportDate" value="{{ today }}">
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-primary" onclick="generateDailyReport()">
|
||||
<i class="fas fa-chart-line"></i> Generate Report
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-success" onclick="setTodayDate()">
|
||||
<i class="fas fa-calendar-day"></i> Today's Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Indicator -->
|
||||
<div id="loadingIndicator" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 mb-0">Generating daily report...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Report Results -->
|
||||
<div id="reportResults" style="display: none;">
|
||||
<!-- Key Metrics Overview -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-tachometer-alt"></i> Daily Production Overview
|
||||
<span id="reportDateDisplay" class="badge bg-primary ms-2"></span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card orders-quantity">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="ordersQuantity">-</h3>
|
||||
<p>Orders Quantity</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card production-launched">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-play-circle"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="productionLaunched">-</h3>
|
||||
<p>Production Launched</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card production-finished">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="productionFinished">-</h3>
|
||||
<p>Production Finished</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="metric-card orders-delivered">
|
||||
<div class="metric-icon">
|
||||
<i class="fas fa-truck"></i>
|
||||
</div>
|
||||
<div class="metric-content">
|
||||
<h3 id="ordersDelivered">-</h3>
|
||||
<p>Orders Delivered</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quality Control Metrics -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-search"></i> Quality Control Scans
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="quality-stats">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="stat-item">
|
||||
<h4 id="qualityTotalScans">-</h4>
|
||||
<p>Total Scans</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item approved">
|
||||
<h4 id="qualityApprovedScans">-</h4>
|
||||
<p>Approved</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item rejected">
|
||||
<h4 id="qualityRejectedScans">-</h4>
|
||||
<p>Rejected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="progress">
|
||||
<div id="qualityApprovalBar" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-center mt-2 mb-0">
|
||||
Approval Rate: <span id="qualityApprovalRate" class="fw-bold">0%</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-clipboard-check"></i> Finish Goods Quality
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="quality-stats">
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="stat-item">
|
||||
<h4 id="fgQualityTotalScans">-</h4>
|
||||
<p>Total Scans</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item approved">
|
||||
<h4 id="fgQualityApprovedScans">-</h4>
|
||||
<p>Approved</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="stat-item rejected">
|
||||
<h4 id="fgQualityRejectedScans">-</h4>
|
||||
<p>Rejected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<div class="progress">
|
||||
<div id="fgQualityApprovalBar" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-center mt-2 mb-0">
|
||||
Approval Rate: <span id="fgQualityApprovalRate" class="fw-bold">0%</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Export and Actions -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-download"></i> Export Options
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-success" onclick="exportReportPDF()">
|
||||
<i class="fas fa-file-pdf"></i> Export PDF
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" onclick="exportReportExcel()">
|
||||
<i class="fas fa-file-excel"></i> Export Excel
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info" onclick="printReport()">
|
||||
<i class="fas fa-print"></i> Print Report
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="shareReport()">
|
||||
<i class="fas fa-share"></i> Share Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div id="errorMessage" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Error:</strong> <span id="errorText"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.metric-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1rem;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.metric-card.orders-quantity {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
}
|
||||
|
||||
.metric-card.production-launched {
|
||||
background: linear-gradient(135deg, #f3e5f5 0%, #ce93d8 100%);
|
||||
}
|
||||
|
||||
.metric-card.production-finished {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #a5d6a7 100%);
|
||||
}
|
||||
|
||||
.metric-card.orders-delivered {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffcc02 100%);
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-right: 1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.metric-content h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.metric-content p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item h4 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item.approved h4 {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item.rejected h4 {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.quality-stats .stat-item p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.metric-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function setTodayDate() {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('reportDate').value = today;
|
||||
generateDailyReport();
|
||||
}
|
||||
|
||||
function generateDailyReport() {
|
||||
const reportDate = document.getElementById('reportDate').value;
|
||||
|
||||
if (!reportDate) {
|
||||
showError('Please select a report date');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('reportResults').style.display = 'none';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
|
||||
// Make API call to get daily data
|
||||
fetch(`/daily_mirror/api/data?date=${reportDate}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update display with data
|
||||
updateDailyReport(data);
|
||||
|
||||
// Hide loading and show results
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('reportResults').style.display = 'block';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error generating daily report:', error);
|
||||
showError('Failed to generate daily report. Please try again.');
|
||||
});
|
||||
}
|
||||
|
||||
function updateDailyReport(data) {
|
||||
// Update date display
|
||||
document.getElementById('reportDateDisplay').textContent = data.date;
|
||||
|
||||
// Update key metrics
|
||||
document.getElementById('ordersQuantity').textContent = data.orders_quantity.toLocaleString();
|
||||
document.getElementById('productionLaunched').textContent = data.production_launched.toLocaleString();
|
||||
document.getElementById('productionFinished').textContent = data.production_finished.toLocaleString();
|
||||
document.getElementById('ordersDelivered').textContent = data.orders_delivered.toLocaleString();
|
||||
|
||||
// Update quality control data
|
||||
document.getElementById('qualityTotalScans').textContent = data.quality_scans.total_scans.toLocaleString();
|
||||
document.getElementById('qualityApprovedScans').textContent = data.quality_scans.approved_scans.toLocaleString();
|
||||
document.getElementById('qualityRejectedScans').textContent = data.quality_scans.rejected_scans.toLocaleString();
|
||||
document.getElementById('qualityApprovalRate').textContent = data.quality_scans.approval_rate + '%';
|
||||
document.getElementById('qualityApprovalBar').style.width = data.quality_scans.approval_rate + '%';
|
||||
|
||||
// Update FG quality data
|
||||
document.getElementById('fgQualityTotalScans').textContent = data.fg_quality_scans.total_scans.toLocaleString();
|
||||
document.getElementById('fgQualityApprovedScans').textContent = data.fg_quality_scans.approved_scans.toLocaleString();
|
||||
document.getElementById('fgQualityRejectedScans').textContent = data.fg_quality_scans.rejected_scans.toLocaleString();
|
||||
document.getElementById('fgQualityApprovalRate').textContent = data.fg_quality_scans.approval_rate + '%';
|
||||
document.getElementById('fgQualityApprovalBar').style.width = data.fg_quality_scans.approval_rate + '%';
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('errorText').textContent = message;
|
||||
document.getElementById('errorMessage').style.display = 'block';
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('reportResults').style.display = 'none';
|
||||
}
|
||||
|
||||
function exportReportPDF() {
|
||||
alert('PDF export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function exportReportExcel() {
|
||||
alert('Excel export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function printReport() {
|
||||
window.print();
|
||||
}
|
||||
|
||||
function shareReport() {
|
||||
alert('Share functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
// Auto-generate today's report on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
generateDailyReport();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,719 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Build Database - Daily Mirror{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🔨 Build Database</h1>
|
||||
<p class="text-muted">Upload Excel files to populate Daily Mirror database tables</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 mb-4">
|
||||
<!-- Card 1: Upload Excel File -->
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-upload"></i> Upload Excel File
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data" id="uploadForm">
|
||||
<!-- Table Selection -->
|
||||
<div class="form-group mb-4">
|
||||
<label for="target_table" class="form-label">
|
||||
<strong>Select Target Table:</strong>
|
||||
</label>
|
||||
<select class="form-control" name="target_table" id="target_table" required>
|
||||
<option value="">-- Choose a table --</option>
|
||||
{% for table in available_tables %}
|
||||
<option value="{{ table.name }}" data-description="{{ table.description }}">
|
||||
{{ table.display }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small id="tableDescription" class="form-text text-muted mt-2">
|
||||
Select a table to see its description.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- File Upload -->
|
||||
<div class="form-group mb-4">
|
||||
<label for="excel_file" class="form-label">
|
||||
<strong>Select Excel File:</strong>
|
||||
</label>
|
||||
<input type="file" class="form-control" name="excel_file" id="excel_file"
|
||||
accept=".xlsx,.xls" required>
|
||||
<small class="form-text text-muted">
|
||||
Accepted formats: .xlsx, .xls (Maximum file size: 10MB)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Upload Button -->
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-primary btn-lg" id="uploadBtn">
|
||||
<i class="fas fa-cloud-upload-alt"></i> Upload and Process File
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 mb-4">
|
||||
<!-- Card 2: Excel File Format Instructions -->
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-info-circle"></i> Excel File Format Instructions
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="accordion" id="formatAccordion">
|
||||
<!-- Production Data Format -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#productionCollapse" aria-expanded="false">
|
||||
🏭 Production Data Format
|
||||
</button>
|
||||
</h2>
|
||||
<div id="productionCollapse" class="accordion-collapse collapse" data-bs-parent="#formatAccordion">
|
||||
<div class="accordion-body">
|
||||
<p><strong>Expected columns for Production Data:</strong></p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Production Order ID</code> <span class="text-muted">Unique identifier</span></li>
|
||||
<li><code>Customer Code</code> <span class="text-muted">Customer code</span></li>
|
||||
<li><code>Customer Name</code> <span class="text-muted">Customer name</span></li>
|
||||
<li><code>Article Code</code> <span class="text-muted">Article code</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Article Description</code> <span class="text-muted">Description</span></li>
|
||||
<li><code>Quantity</code> <span class="text-muted">To produce</span></li>
|
||||
<li><code>Production Date</code> <span class="text-muted">Date</span></li>
|
||||
<li><code>Status</code> <span class="text-muted">Production status</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Orders Data Format -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#ordersCollapse" aria-expanded="false">
|
||||
🛒 Orders Data Format
|
||||
</button>
|
||||
</h2>
|
||||
<div id="ordersCollapse" class="accordion-collapse collapse" data-bs-parent="#formatAccordion">
|
||||
<div class="accordion-body">
|
||||
<p><strong>Expected columns for Orders Data:</strong></p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Order ID</code> <span class="text-muted">Unique identifier</span></li>
|
||||
<li><code>Customer Code</code> <span class="text-muted">Customer code</span></li>
|
||||
<li><code>Customer Name</code> <span class="text-muted">Customer name</span></li>
|
||||
<li><code>Article Code</code> <span class="text-muted">Article code</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Article Description</code> <span class="text-muted">Description</span></li>
|
||||
<li><code>Quantity Ordered</code> <span class="text-muted">Ordered</span></li>
|
||||
<li><code>Order Date</code> <span class="text-muted">Date</span></li>
|
||||
<li><code>Status</code> <span class="text-muted">Order status</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delivery Data Format -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#deliveryCollapse" aria-expanded="false">
|
||||
🚚 Delivery Data Format (Articole livrate)
|
||||
</button>
|
||||
</h2>
|
||||
<div id="deliveryCollapse" class="accordion-collapse collapse" data-bs-parent="#formatAccordion">
|
||||
<div class="accordion-body">
|
||||
<p><strong>Expected columns for Delivery Data:</strong></p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Shipment ID</code> <span class="text-muted">Unique shipment identifier</span></li>
|
||||
<li><code>Order ID</code> <span class="text-muted">Related order</span></li>
|
||||
<li><code>Customer</code> <span class="text-muted">Customer info</span></li>
|
||||
<li><code>Article</code> <span class="text-muted">Code/description</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li><code>Quantity Delivered</code> <span class="text-muted">Delivered quantity</span></li>
|
||||
<li><code>Delivery Date</code> <span class="text-muted">Date</span></li>
|
||||
<li><code>Status</code> <span class="text-muted">Delivery status</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Result Modal (Better Solution) -->
|
||||
<div class="modal fade" id="uploadResultModal" tabindex="-1" aria-labelledby="uploadResultModalLabel" aria-hidden="true" data-bs-backdrop="true" data-bs-keyboard="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" id="modalHeader">
|
||||
<h5 class="modal-title" id="uploadResultModalLabel">
|
||||
<i class="fas fa-check-circle"></i> Upload Result
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="modalCloseBtn"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="uploadResultContent" class="text-center py-3">
|
||||
<!-- Result content will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="modalOkBtn">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.accordion-button:not(.collapsed) {
|
||||
background-color: #e7f3ff;
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Result stats styling */
|
||||
.upload-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
min-width: 100px;
|
||||
margin: 5px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.stat-box.success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.stat-box.warning {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
}
|
||||
|
||||
.stat-box.error {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Reduce font size for Excel Format Instructions card rows */
|
||||
.col-lg-6:nth-child(2) .card-body {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Make accordion button labels smaller */
|
||||
.accordion-button {
|
||||
font-size: 1rem;
|
||||
padding-top: 0.4rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
/* Override h2 size in accordion headers */
|
||||
.accordion-header {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.accordion-header h2 {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Modal summary list styling */
|
||||
#uploadResultContent ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin: 10px auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
#uploadResultContent ul li {
|
||||
padding: 5px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#uploadResultContent ul li::before {
|
||||
content: '✓ ';
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Make "Expected columns" text smaller in accordion bodies */
|
||||
.accordion-body p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.accordion-body strong {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
body.dark-mode .card {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
box-shadow: 0 4px 6px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body.dark-mode .card-header {
|
||||
background-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
border-bottom: 1px solid rgba(226, 232, 240, 0.2);
|
||||
}
|
||||
|
||||
body.dark-mode .form-control {
|
||||
background-color: #4a5568;
|
||||
border-color: #6b7280;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .form-control:focus {
|
||||
background-color: #4a5568;
|
||||
border-color: #007bff;
|
||||
color: #e2e8f0;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
body.dark-mode .accordion-button {
|
||||
background-color: #374151;
|
||||
color: #e2e8f0;
|
||||
border-color: #6b7280;
|
||||
}
|
||||
|
||||
body.dark-mode .accordion-button:not(.collapsed) {
|
||||
background-color: #1e3a8a;
|
||||
color: #bfdbfe;
|
||||
}
|
||||
|
||||
body.dark-mode .accordion-body {
|
||||
background-color: #374151;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode code {
|
||||
background-color: #374151;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid #6b7280;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-content {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-header {
|
||||
border-bottom-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .modal-footer {
|
||||
border-top-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-box.success {
|
||||
background-color: #1e4620;
|
||||
border-color: #2d5a2e;
|
||||
color: #a3d9a5;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-box.warning {
|
||||
background-color: #5a4a1e;
|
||||
border-color: #6b5a2d;
|
||||
color: #f4d88f;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-box.error {
|
||||
background-color: #5a1e1e;
|
||||
border-color: #6b2d2d;
|
||||
color: #f8a3a8;
|
||||
}
|
||||
|
||||
body.dark-mode .stat-label {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
body.dark-mode .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme on page load
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const body = document.body;
|
||||
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
}
|
||||
|
||||
const tableSelect = document.getElementById('target_table');
|
||||
const tableDescription = document.getElementById('tableDescription');
|
||||
const uploadBtn = document.getElementById('uploadBtn');
|
||||
const uploadForm = document.getElementById('uploadForm');
|
||||
const fileInput = document.getElementById('excel_file');
|
||||
|
||||
// Update table description when selection changes
|
||||
tableSelect.addEventListener('change', function() {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
if (selectedOption.value) {
|
||||
const description = selectedOption.getAttribute('data-description');
|
||||
tableDescription.innerHTML = `<strong>Selected:</strong> ${description}`;
|
||||
tableDescription.className = 'form-text text-info mt-2';
|
||||
} else {
|
||||
tableDescription.innerHTML = 'Select a table to see its description.';
|
||||
tableDescription.className = 'form-text text-muted mt-2';
|
||||
}
|
||||
});
|
||||
|
||||
// File input change handler to show file info
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const fileName = file.name;
|
||||
const fileSize = (file.size / 1024 / 1024).toFixed(2);
|
||||
console.log(`Selected file: ${fileName} (${fileSize} MB)`);
|
||||
}
|
||||
});
|
||||
|
||||
// Upload button click handler (AJAX submission)
|
||||
uploadBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate file selection
|
||||
if (!fileInput.files.length) {
|
||||
alert('Please select an Excel file to upload.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate table selection
|
||||
if (!tableSelect.value) {
|
||||
alert('Please select a target table.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file size (10MB limit)
|
||||
const file = fileInput.files[0];
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert('File size must be less than 10MB.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare form data
|
||||
const formData = new FormData(uploadForm);
|
||||
|
||||
// Show loading state
|
||||
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
|
||||
uploadBtn.disabled = true;
|
||||
|
||||
// Submit via AJAX
|
||||
fetch('/daily_mirror/build_database', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(err => Promise.reject(err));
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
// Reset button
|
||||
uploadBtn.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Upload and Process File';
|
||||
uploadBtn.disabled = false;
|
||||
|
||||
// Show result in modal
|
||||
showUploadResult(result);
|
||||
|
||||
// Reset form
|
||||
uploadForm.reset();
|
||||
tableDescription.innerHTML = 'Select a table to see its description.';
|
||||
tableDescription.className = 'form-text text-muted mt-2';
|
||||
})
|
||||
.catch(error => {
|
||||
// Reset button
|
||||
uploadBtn.innerHTML = '<i class="fas fa-cloud-upload-alt"></i> Upload and Process File';
|
||||
uploadBtn.disabled = false;
|
||||
|
||||
// Show error in modal
|
||||
showUploadError(error);
|
||||
});
|
||||
});
|
||||
|
||||
function showUploadResult(result) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('uploadResultModal'));
|
||||
const modalHeader = document.getElementById('modalHeader');
|
||||
const modalTitle = document.getElementById('uploadResultModalLabel');
|
||||
const content = document.getElementById('uploadResultContent');
|
||||
|
||||
// Determine overall status
|
||||
const hasErrors = result.error_count && result.error_count > 0;
|
||||
const hasSuccess = result.created_rows > 0 || result.updated_rows > 0;
|
||||
|
||||
// Update modal header color
|
||||
if (hasErrors && !hasSuccess) {
|
||||
modalHeader.className = 'modal-header bg-danger text-white';
|
||||
modalTitle.innerHTML = '<i class="fas fa-times-circle"></i> Upload Failed';
|
||||
} else if (hasErrors && hasSuccess) {
|
||||
modalHeader.className = 'modal-header bg-warning text-dark';
|
||||
modalTitle.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Upload Completed with Warnings';
|
||||
} else {
|
||||
modalHeader.className = 'modal-header bg-success text-white';
|
||||
modalTitle.innerHTML = '<i class="fas fa-check-circle"></i> Upload Successful';
|
||||
}
|
||||
|
||||
// Build result content with stats
|
||||
let html = '<div class="upload-stats">';
|
||||
|
||||
// Total rows processed from Excel
|
||||
html += `
|
||||
<div class="stat-box ${hasErrors ? 'warning' : 'success'}">
|
||||
<span class="stat-value">${result.total_rows || 0}</span>
|
||||
<span class="stat-label">Rows Processed</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Created rows (new in database)
|
||||
if (result.created_rows > 0) {
|
||||
html += `
|
||||
<div class="stat-box success">
|
||||
<span class="stat-value">${result.created_rows}</span>
|
||||
<span class="stat-label">New Rows Created</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Updated rows (existing in database)
|
||||
if (result.updated_rows > 0) {
|
||||
html += `
|
||||
<div class="stat-box success">
|
||||
<span class="stat-value">${result.updated_rows}</span>
|
||||
<span class="stat-label">Rows Updated</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Errors
|
||||
if (hasErrors) {
|
||||
html += `
|
||||
<div class="stat-box error">
|
||||
<span class="stat-value">${result.error_count}</span>
|
||||
<span class="stat-label">Errors</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Add detailed summary message
|
||||
const successCount = (result.created_rows || 0) + (result.updated_rows || 0);
|
||||
if (successCount > 0) {
|
||||
let msg = `<p class="mt-3 mb-0"><strong>Successfully processed ${result.total_rows} rows from Excel:</strong></p>`;
|
||||
msg += '<ul class="text-start">';
|
||||
if (result.created_rows > 0) {
|
||||
msg += `<li>${result.created_rows} new ${result.created_rows === 1 ? 'record' : 'records'} created in database</li>`;
|
||||
}
|
||||
if (result.updated_rows > 0) {
|
||||
msg += `<li>${result.updated_rows} existing ${result.updated_rows === 1 ? 'record' : 'records'} updated</li>`;
|
||||
}
|
||||
msg += '</ul>';
|
||||
html += msg;
|
||||
}
|
||||
if (hasErrors) {
|
||||
html += `<p class="text-danger mb-0"><strong>⚠️ ${result.error_count} ${result.error_count === 1 ? 'row' : 'rows'} could not be processed due to errors.</strong></p>`;
|
||||
}
|
||||
|
||||
// Add auto-close countdown for successful uploads without errors
|
||||
if (!hasErrors && successCount > 0) {
|
||||
html += `<p class="text-muted mt-2 mb-0" id="autoCloseCountdown"><small>This window will close automatically in <span id="countdown">3</span> seconds...</small></p>`;
|
||||
}
|
||||
|
||||
content.innerHTML = html;
|
||||
modal.show();
|
||||
|
||||
// Get modal element
|
||||
const modalElement = document.getElementById('uploadResultModal');
|
||||
|
||||
// Add explicit close handlers
|
||||
const okBtn = document.getElementById('modalOkBtn');
|
||||
const closeBtn = document.getElementById('modalCloseBtn');
|
||||
|
||||
if (okBtn) {
|
||||
okBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-close after 3 seconds for successful uploads without errors
|
||||
if (!hasErrors && successCount > 0) {
|
||||
let countdown = 3;
|
||||
const countdownInterval = setInterval(function() {
|
||||
countdown--;
|
||||
const countdownSpan = document.getElementById('countdown');
|
||||
if (countdownSpan) {
|
||||
countdownSpan.textContent = countdown;
|
||||
}
|
||||
if (countdown <= 0) {
|
||||
clearInterval(countdownInterval);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function() {
|
||||
clearInterval(countdownInterval);
|
||||
modal.hide();
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function showUploadError(error) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('uploadResultModal'));
|
||||
const modalHeader = document.getElementById('modalHeader');
|
||||
const modalTitle = document.getElementById('uploadResultModalLabel');
|
||||
const content = document.getElementById('uploadResultContent');
|
||||
|
||||
// Update modal header
|
||||
modalHeader.className = 'modal-header bg-danger text-white';
|
||||
modalTitle.innerHTML = '<i class="fas fa-times-circle"></i> Upload Error';
|
||||
|
||||
// Show error message
|
||||
const errorMsg = error.error || error.message || 'An unexpected error occurred during upload.';
|
||||
content.innerHTML = `
|
||||
<div class="alert alert-danger mb-0">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Error:</strong> ${errorMsg}
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
|
||||
// Get modal element
|
||||
const modalElement = document.getElementById('uploadResultModal');
|
||||
|
||||
// Add explicit close handlers
|
||||
const okBtn = document.getElementById('modalOkBtn');
|
||||
const closeBtn = document.getElementById('modalCloseBtn');
|
||||
|
||||
if (okBtn) {
|
||||
okBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.onclick = function() {
|
||||
modal.hide();
|
||||
// Also trigger Bootstrap's native close
|
||||
modalElement.classList.remove('show');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.paddingRight = '';
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,449 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Daily Mirror History - Quality Recticel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">📋 Daily Mirror History</h1>
|
||||
<p class="text-muted">Analyze historical daily production reports and trends</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_route') }}" class="btn btn-outline-success">
|
||||
<i class="fas fa-chart-line"></i> Create New Report
|
||||
</a>
|
||||
<a href="{{ url_for('main.dashboard') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Selection -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-calendar-week"></i> Select Date Range
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label for="startDate" class="form-label">Start Date:</label>
|
||||
<input type="date" class="form-control" id="startDate" value="{{ start_date }}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="endDate" class="form-label">End Date:</label>
|
||||
<input type="date" class="form-control" id="endDate" value="{{ end_date }}">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-primary" onclick="loadHistoryData()">
|
||||
<i class="fas fa-search"></i> Load History
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="setDateRange(7)">Last 7 days</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="setDateRange(30)">Last 30 days</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Indicator -->
|
||||
<div id="loadingIndicator" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 mb-0">Loading historical data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Statistics -->
|
||||
<div id="summaryStats" style="display: none;">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-chart-bar"></i> Period Summary
|
||||
<span id="periodRange" class="badge bg-secondary ms-2"></span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalOrdersQuantity">-</h4>
|
||||
<p>Total Orders Quantity</p>
|
||||
<small id="avgOrdersQuantity" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalProductionLaunched">-</h4>
|
||||
<p>Total Production Launched</p>
|
||||
<small id="avgProductionLaunched" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalProductionFinished">-</h4>
|
||||
<p>Total Production Finished</p>
|
||||
<small id="avgProductionFinished" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="summary-metric">
|
||||
<h4 id="totalOrdersDelivered">-</h4>
|
||||
<p>Total Orders Delivered</p>
|
||||
<small id="avgOrdersDelivered" class="text-muted">Avg: -</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Historical Data Table -->
|
||||
<div id="historyTable" style="display: none;">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Historical Daily Reports
|
||||
</h5>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="exportHistoryCSV()">
|
||||
<i class="fas fa-file-csv"></i> CSV
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-success" onclick="exportHistoryExcel()">
|
||||
<i class="fas fa-file-excel"></i> Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="historyDataTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Orders Quantity</th>
|
||||
<th>Production Launched</th>
|
||||
<th>Production Finished</th>
|
||||
<th>Orders Delivered</th>
|
||||
<th>Quality Approval Rate</th>
|
||||
<th>FG Quality Approval Rate</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="historyTableBody">
|
||||
<!-- Data will be populated here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<nav aria-label="History pagination" id="historyPagination" style="display: none;">
|
||||
<ul class="pagination pagination-sm justify-content-center">
|
||||
<!-- Pagination will be populated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chart Visualization -->
|
||||
<div id="chartVisualization" style="display: none;">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-chart-line"></i> Trend Analysis
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="trendChart" height="100"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div id="errorMessage" class="row mb-4" style="display: none;">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Error:</strong> <span id="errorText"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.summary-metric {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.summary-metric h4 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.summary-metric p {
|
||||
margin: 0.5rem 0;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.approval-rate {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.approval-rate.high {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.approval-rate.medium {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.approval-rate.low {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let historyData = [];
|
||||
let currentPage = 1;
|
||||
const itemsPerPage = 20;
|
||||
|
||||
function setDateRange(days) {
|
||||
const endDate = new Date();
|
||||
const startDate = new Date();
|
||||
startDate.setDate(endDate.getDate() - days);
|
||||
|
||||
document.getElementById('endDate').value = endDate.toISOString().split('T')[0];
|
||||
document.getElementById('startDate').value = startDate.toISOString().split('T')[0];
|
||||
|
||||
loadHistoryData();
|
||||
}
|
||||
|
||||
function loadHistoryData() {
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
|
||||
if (!startDate || !endDate) {
|
||||
showError('Please select both start and end dates');
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date(startDate) > new Date(endDate)) {
|
||||
showError('Start date cannot be after end date');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('summaryStats').style.display = 'none';
|
||||
document.getElementById('historyTable').style.display = 'none';
|
||||
document.getElementById('chartVisualization').style.display = 'none';
|
||||
document.getElementById('errorMessage').style.display = 'none';
|
||||
|
||||
// Make API call to get historical data
|
||||
fetch(`/daily_mirror/api/history_data?start_date=${startDate}&end_date=${endDate}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
historyData = data.history;
|
||||
|
||||
// Update displays
|
||||
updateSummaryStats(data);
|
||||
updateHistoryTable();
|
||||
updateTrendChart();
|
||||
|
||||
// Hide loading and show results
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('summaryStats').style.display = 'block';
|
||||
document.getElementById('historyTable').style.display = 'block';
|
||||
document.getElementById('chartVisualization').style.display = 'block';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading history data:', error);
|
||||
showError('Failed to load historical data. Please try again.');
|
||||
});
|
||||
}
|
||||
|
||||
function updateSummaryStats(data) {
|
||||
const history = data.history;
|
||||
|
||||
if (history.length === 0) {
|
||||
document.getElementById('periodRange').textContent = 'No Data';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('periodRange').textContent = `${data.start_date} to ${data.end_date}`;
|
||||
|
||||
// Calculate totals and averages
|
||||
const totals = history.reduce((acc, day) => {
|
||||
acc.ordersQuantity += day.orders_quantity;
|
||||
acc.productionLaunched += day.production_launched;
|
||||
acc.productionFinished += day.production_finished;
|
||||
acc.ordersDelivered += day.orders_delivered;
|
||||
return acc;
|
||||
}, { ordersQuantity: 0, productionLaunched: 0, productionFinished: 0, ordersDelivered: 0 });
|
||||
|
||||
const avgDivisor = history.length;
|
||||
|
||||
document.getElementById('totalOrdersQuantity').textContent = totals.ordersQuantity.toLocaleString();
|
||||
document.getElementById('avgOrdersQuantity').textContent = `Avg: ${Math.round(totals.ordersQuantity / avgDivisor).toLocaleString()}`;
|
||||
|
||||
document.getElementById('totalProductionLaunched').textContent = totals.productionLaunched.toLocaleString();
|
||||
document.getElementById('avgProductionLaunched').textContent = `Avg: ${Math.round(totals.productionLaunched / avgDivisor).toLocaleString()}`;
|
||||
|
||||
document.getElementById('totalProductionFinished').textContent = totals.productionFinished.toLocaleString();
|
||||
document.getElementById('avgProductionFinished').textContent = `Avg: ${Math.round(totals.productionFinished / avgDivisor).toLocaleString()}`;
|
||||
|
||||
document.getElementById('totalOrdersDelivered').textContent = totals.ordersDelivered.toLocaleString();
|
||||
document.getElementById('avgOrdersDelivered').textContent = `Avg: ${Math.round(totals.ordersDelivered / avgDivisor).toLocaleString()}`;
|
||||
}
|
||||
|
||||
function updateHistoryTable() {
|
||||
const tbody = document.getElementById('historyTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = startIndex + itemsPerPage;
|
||||
const pageData = historyData.slice(startIndex, endIndex);
|
||||
|
||||
pageData.forEach(day => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const qualityRate = day.quality_scans.approval_rate;
|
||||
const fgQualityRate = day.fg_quality_scans.approval_rate;
|
||||
|
||||
row.innerHTML = `
|
||||
<td><strong>${day.date}</strong></td>
|
||||
<td>${day.orders_quantity.toLocaleString()}</td>
|
||||
<td>${day.production_launched.toLocaleString()}</td>
|
||||
<td>${day.production_finished.toLocaleString()}</td>
|
||||
<td>${day.orders_delivered.toLocaleString()}</td>
|
||||
<td><span class="approval-rate ${getApprovalRateClass(qualityRate)}">${qualityRate}%</span></td>
|
||||
<td><span class="approval-rate ${getApprovalRateClass(fgQualityRate)}">${fgQualityRate}%</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="viewDayDetails('${day.date}')">
|
||||
<i class="fas fa-eye"></i> View
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
updatePagination();
|
||||
}
|
||||
|
||||
function getApprovalRateClass(rate) {
|
||||
if (rate >= 95) return 'high';
|
||||
if (rate >= 85) return 'medium';
|
||||
return 'low';
|
||||
}
|
||||
|
||||
function updatePagination() {
|
||||
const totalPages = Math.ceil(historyData.length / itemsPerPage);
|
||||
|
||||
if (totalPages <= 1) {
|
||||
document.getElementById('historyPagination').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('historyPagination').style.display = 'block';
|
||||
// Pagination implementation can be added here
|
||||
}
|
||||
|
||||
function updateTrendChart() {
|
||||
// Chart implementation using Chart.js can be added here
|
||||
// For now, we'll show a placeholder
|
||||
const canvas = document.getElementById('trendChart');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw placeholder text
|
||||
ctx.font = '16px Arial';
|
||||
ctx.fillStyle = '#6c757d';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('Trend chart visualization will be implemented here', canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
|
||||
function viewDayDetails(date) {
|
||||
// Navigate to daily mirror with specific date
|
||||
window.open(`{{ url_for('daily_mirror.daily_mirror_route') }}?date=${date}`, '_blank');
|
||||
}
|
||||
|
||||
function exportHistoryCSV() {
|
||||
alert('CSV export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function exportHistoryExcel() {
|
||||
alert('Excel export functionality will be implemented soon.');
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('errorText').textContent = message;
|
||||
document.getElementById('errorMessage').style.display = 'block';
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
document.getElementById('summaryStats').style.display = 'none';
|
||||
document.getElementById('historyTable').style.display = 'none';
|
||||
document.getElementById('chartVisualization').style.display = 'none';
|
||||
}
|
||||
|
||||
// Auto-load data on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadHistoryData();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,262 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Daily Mirror - Quality Recticel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">📊 Daily Mirror</h1>
|
||||
<p class="text-muted">Business Intelligence and Production Reporting</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Daily Mirror Cards -->
|
||||
<div class="row">
|
||||
<!-- Card 1: Build Database -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-primary">
|
||||
<i class="fas fa-database"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Build Database</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
Upload Excel files to create and populate tables.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_build_database') }}" class="btn btn-primary btn-block w-100">
|
||||
<i class="fas fa-hammer"></i> Build Database
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: Tune Database -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-warning">
|
||||
<i class="fas fa-edit"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Tune Database</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
Edit and update records after import.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.tune_production_data') }}" class="btn btn-warning btn-block w-100 btn-sm mb-2">
|
||||
<i class="fas fa-industry"></i> Production Orders
|
||||
</a>
|
||||
<a href="{{ url_for('daily_mirror.tune_orders_data') }}" class="btn btn-warning btn-block w-100 btn-sm mb-2">
|
||||
<i class="fas fa-shopping-cart"></i> Customer Orders
|
||||
</a>
|
||||
<a href="{{ url_for('daily_mirror.tune_delivery_data') }}" class="btn btn-warning btn-block w-100 btn-sm">
|
||||
<i class="fas fa-truck"></i> Delivery Records
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Daily Mirror -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-success">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Daily Mirror</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
Generate daily production reports.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_route') }}" class="btn btn-success btn-block w-100">
|
||||
<i class="fas fa-plus-circle"></i> Create Daily Report
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 4: Daily Mirror History -->
|
||||
<div class="col-lg-6 col-md-6 mb-4">
|
||||
<div class="card h-100 daily-mirror-card">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="text-center mb-3">
|
||||
<div class="feature-icon bg-info">
|
||||
<i class="fas fa-history"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title text-center">Daily Mirror History</h5>
|
||||
<p class="card-text flex-grow-1 text-center">
|
||||
View historical production reports.
|
||||
</p>
|
||||
<div class="mt-auto">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_history_route') }}" class="btn btn-info btn-block w-100">
|
||||
<i class="fas fa-chart-bar"></i> View History
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.daily-mirror-card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
background-color: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.daily-mirror-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
/* Light mode styles */
|
||||
body:not(.dark-mode) .daily-mirror-card {
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
body:not(.dark-mode) .card-text {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
body:not(.dark-mode) .text-muted {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
body.dark-mode .daily-mirror-card {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
border: 1px solid #4a5568;
|
||||
box-shadow: 0 2px 4px rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
body.dark-mode .daily-mirror-card:hover {
|
||||
box-shadow: 0 4px 15px rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
body.dark-mode .card-text {
|
||||
color: #cbd5e0;
|
||||
}
|
||||
|
||||
body.dark-mode .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .h3 {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .container-fluid {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* Ensure buttons maintain their intended colors in both themes */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%);
|
||||
border: none;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #6c757d 0%, #545b62 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.feature-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}</style>
|
||||
|
||||
<script>
|
||||
// Initialize theme on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Apply saved theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const body = document.body;
|
||||
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-mode');
|
||||
} else {
|
||||
body.classList.remove('dark-mode');
|
||||
}
|
||||
|
||||
// Update theme toggle button text if it exists
|
||||
const themeToggleButton = document.getElementById('theme-toggle');
|
||||
if (themeToggleButton) {
|
||||
if (body.classList.contains('dark-mode')) {
|
||||
themeToggleButton.textContent = 'Change to Light Mode';
|
||||
} else {
|
||||
themeToggleButton.textContent = 'Change to Dark Mode';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function showComingSoon(feature) {
|
||||
alert(`${feature} functionality will be available in a future update!\n\nThis feature is currently under development and will include advanced capabilities for enhanced Daily Mirror operations.`);
|
||||
}
|
||||
|
||||
// Auto-refresh quick stats every 5 minutes
|
||||
setInterval(function() {
|
||||
// This could be implemented to refresh the quick stats
|
||||
console.log('Auto-refresh daily stats (not implemented yet)');
|
||||
}, 300000); // 5 minutes
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,503 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tune Delivery Data - Daily Mirror{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/daily_mirror_tune.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🚚 Tune Delivery Data</h1>
|
||||
<p class="text-muted">Edit and update delivery records information</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-filter"></i> Filters and Search
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="searchInput"
|
||||
placeholder="Search by shipment, customer, or article...">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="statusFilter" class="form-label">Delivery Status</label>
|
||||
<select class="form-control" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="customerFilter" class="form-label">Customer</label>
|
||||
<select class="form-control" id="customerFilter">
|
||||
<option value="">All Customers</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="recordsPerPage" class="form-label">Records per page</label>
|
||||
<select class="form-control" id="recordsPerPage">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" onclick="loadDeliveryData()">
|
||||
<i class="fas fa-search"></i> Apply Filters
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Delivery Records Data
|
||||
</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="recordsInfo" class="text-muted me-3"></span>
|
||||
<button class="btn btn-success btn-sm" onclick="saveAllChanges()">
|
||||
<i class="fas fa-save"></i> Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="deliveryTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Shipment ID</th>
|
||||
<th>Customer</th>
|
||||
<th>Order ID</th>
|
||||
<th>Article Code</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th>Shipment Date</th>
|
||||
<th>Delivery Date</th>
|
||||
<th>Status</th>
|
||||
<th>Total Value</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="deliveryTableBody">
|
||||
<!-- Data will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loadingIndicator" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Loading data...</p>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div id="noDataMessage" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-info-circle fa-2x text-muted"></i>
|
||||
<p class="mt-2 text-muted">No delivery records found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="card-footer">
|
||||
<nav aria-label="Delivery data pagination">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0" id="pagination">
|
||||
<!-- Pagination will be generated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Delivery Record</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editRecordId">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editShipmentId" class="form-label">Shipment ID</label>
|
||||
<input type="text" class="form-control" id="editShipmentId" readonly>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editOrderId" class="form-label">Order ID</label>
|
||||
<input type="text" class="form-control" id="editOrderId">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerCode" class="form-label">Customer Code</label>
|
||||
<input type="text" class="form-control" id="editCustomerCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerName" class="form-label">Customer Name</label>
|
||||
<input type="text" class="form-control" id="editCustomerName">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editArticleCode" class="form-label">Article Code</label>
|
||||
<input type="text" class="form-control" id="editArticleCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editQuantity" class="form-label">Quantity Delivered</label>
|
||||
<input type="number" class="form-control" id="editQuantity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">Article Description</label>
|
||||
<textarea class="form-control" id="editDescription" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editShipmentDate" class="form-label">Shipment Date</label>
|
||||
<input type="date" class="form-control" id="editShipmentDate">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryDate" class="form-label">Delivery Date</label>
|
||||
<input type="date" class="form-control" id="editDeliveryDate">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryStatus" class="form-label">Delivery Status</label>
|
||||
<select class="form-control" id="editDeliveryStatus">
|
||||
<option value="Finalizat">Finalizat</option>
|
||||
<option value="Proiect">Proiect</option>
|
||||
<option value="SHIPPED">Shipped</option>
|
||||
<option value="DELIVERED">Delivered</option>
|
||||
<option value="RETURNED">Returned</option>
|
||||
<option value="PARTIAL">Partial</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editTotalValue" class="form-label">Total Value (€)</label>
|
||||
<input type="number" step="0.01" class="form-control" id="editTotalValue">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRecord()">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentPerPage = 50;
|
||||
let currentSearch = '';
|
||||
let currentStatusFilter = '';
|
||||
let currentCustomerFilter = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
loadDeliveryData();
|
||||
|
||||
// Setup search on enter key
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
loadDeliveryData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadDeliveryData(page = 1) {
|
||||
currentPage = page;
|
||||
currentPerPage = document.getElementById('recordsPerPage').value;
|
||||
currentSearch = document.getElementById('searchInput').value;
|
||||
currentStatusFilter = document.getElementById('statusFilter').value;
|
||||
currentCustomerFilter = document.getElementById('customerFilter').value;
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('deliveryTableBody').style.display = 'none';
|
||||
document.getElementById('noDataMessage').style.display = 'none';
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage,
|
||||
per_page: currentPerPage,
|
||||
search: currentSearch,
|
||||
status: currentStatusFilter,
|
||||
customer: currentCustomerFilter
|
||||
});
|
||||
|
||||
fetch(`/daily_mirror/api/tune/delivery_data?${params}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
|
||||
if (data.success) {
|
||||
if (data.data.length === 0) {
|
||||
document.getElementById('noDataMessage').style.display = 'block';
|
||||
} else {
|
||||
displayDeliveryData(data.data);
|
||||
updatePagination(data);
|
||||
updateRecordsInfo(data);
|
||||
|
||||
// Populate filter dropdowns on first load
|
||||
if (currentPage === 1) {
|
||||
populateCustomerFilter(data.customers);
|
||||
populateStatusFilter(data.statuses);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading data:', data.error);
|
||||
alert('Error loading delivery data: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
console.error('Error:', error);
|
||||
alert('Error loading delivery data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function displayDeliveryData(data) {
|
||||
const tbody = document.getElementById('deliveryTableBody');
|
||||
tbody.innerHTML = '';
|
||||
tbody.style.display = 'table-row-group';
|
||||
|
||||
data.forEach(record => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><strong>${record.shipment_id}</strong></td>
|
||||
<td>
|
||||
<small class="text-muted d-block">${record.customer_code}</small>
|
||||
${record.customer_name}
|
||||
</td>
|
||||
<td>${record.order_id || '-'}</td>
|
||||
<td><code>${record.article_code}</code></td>
|
||||
<td><small>${record.article_description || '-'}</small></td>
|
||||
<td><span class="badge bg-info">${record.quantity_delivered}</span></td>
|
||||
<td>${record.shipment_date || '-'}</td>
|
||||
<td>${record.delivery_date || '-'}</td>
|
||||
<td><span class="badge bg-success">${record.delivery_status}</span></td>
|
||||
<td><strong>€${parseFloat(record.total_value || 0).toFixed(2)}</strong></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editRecord(${record.id})"
|
||||
title="Edit Delivery">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function populateCustomerFilter(customers) {
|
||||
const filter = document.getElementById('customerFilter');
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Customers</option>';
|
||||
|
||||
customers.forEach(customer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = customer.code;
|
||||
option.textContent = `${customer.code} - ${customer.name}`;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function populateStatusFilter(statuses) {
|
||||
const filter = document.getElementById('statusFilter');
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Statuses</option>';
|
||||
|
||||
statuses.forEach(status => {
|
||||
const option = document.createElement('option');
|
||||
option.value = status;
|
||||
option.textContent = status;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function updatePagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
|
||||
if (data.total_pages <= 1) return;
|
||||
|
||||
// Previous button
|
||||
const prevLi = document.createElement('li');
|
||||
prevLi.className = `page-item ${data.page === 1 ? 'disabled' : ''}`;
|
||||
prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadDeliveryData(${data.page - 1})">Previous</a>`;
|
||||
pagination.appendChild(prevLi);
|
||||
|
||||
// Page numbers
|
||||
const startPage = Math.max(1, data.page - 2);
|
||||
const endPage = Math.min(data.total_pages, data.page + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const li = document.createElement('li');
|
||||
li.className = `page-item ${i === data.page ? 'active' : ''}`;
|
||||
li.innerHTML = `<a class="page-link" href="#" onclick="loadDeliveryData(${i})">${i}</a>`;
|
||||
pagination.appendChild(li);
|
||||
}
|
||||
|
||||
// Next button
|
||||
const nextLi = document.createElement('li');
|
||||
nextLi.className = `page-item ${data.page === data.total_pages ? 'disabled' : ''}`;
|
||||
nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadDeliveryData(${data.page + 1})">Next</a>`;
|
||||
pagination.appendChild(nextLi);
|
||||
}
|
||||
|
||||
function updateRecordsInfo(data) {
|
||||
const start = (data.page - 1) * data.per_page + 1;
|
||||
const end = Math.min(data.page * data.per_page, data.total_records);
|
||||
document.getElementById('recordsInfo').textContent =
|
||||
`Showing ${start}-${end} of ${data.total_records} deliveries`;
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('statusFilter').value = '';
|
||||
document.getElementById('customerFilter').value = '';
|
||||
loadDeliveryData(1);
|
||||
}
|
||||
|
||||
function editRecord(recordId) {
|
||||
// Get data via API for editing
|
||||
fetch(`/daily_mirror/api/tune/delivery_data?page=${currentPage}&per_page=${currentPerPage}&search=${currentSearch}&status=${currentStatusFilter}&customer=${currentCustomerFilter}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const recordData = data.data.find(record => record.id === recordId);
|
||||
if (recordData) {
|
||||
populateEditModal(recordData);
|
||||
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
editModal.show();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error loading record data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function populateEditModal(record) {
|
||||
document.getElementById('editRecordId').value = record.id;
|
||||
document.getElementById('editShipmentId').value = record.shipment_id;
|
||||
document.getElementById('editOrderId').value = record.order_id || '';
|
||||
document.getElementById('editCustomerCode').value = record.customer_code;
|
||||
document.getElementById('editCustomerName').value = record.customer_name;
|
||||
document.getElementById('editArticleCode').value = record.article_code;
|
||||
document.getElementById('editDescription').value = record.article_description || '';
|
||||
document.getElementById('editQuantity').value = record.quantity_delivered;
|
||||
document.getElementById('editShipmentDate').value = record.shipment_date;
|
||||
document.getElementById('editDeliveryDate').value = record.delivery_date;
|
||||
document.getElementById('editDeliveryStatus').value = record.delivery_status;
|
||||
document.getElementById('editTotalValue').value = record.total_value;
|
||||
}
|
||||
|
||||
function saveRecord() {
|
||||
const recordId = document.getElementById('editRecordId').value;
|
||||
const data = {
|
||||
customer_code: document.getElementById('editCustomerCode').value,
|
||||
customer_name: document.getElementById('editCustomerName').value,
|
||||
order_id: document.getElementById('editOrderId').value,
|
||||
article_code: document.getElementById('editArticleCode').value,
|
||||
article_description: document.getElementById('editDescription').value,
|
||||
quantity_delivered: parseInt(document.getElementById('editQuantity').value) || 0,
|
||||
shipment_date: document.getElementById('editShipmentDate').value,
|
||||
delivery_date: document.getElementById('editDeliveryDate').value,
|
||||
delivery_status: document.getElementById('editDeliveryStatus').value,
|
||||
total_value: parseFloat(document.getElementById('editTotalValue').value) || 0
|
||||
};
|
||||
|
||||
fetch(`/daily_mirror/api/tune/delivery_data/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
// Close modal
|
||||
const editModal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
|
||||
editModal.hide();
|
||||
|
||||
// Reload data
|
||||
loadDeliveryData(currentPage);
|
||||
|
||||
// Show success message
|
||||
alert('Delivery record updated successfully!');
|
||||
} else {
|
||||
alert('Error updating delivery record: ' + result.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error updating delivery record: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function saveAllChanges() {
|
||||
alert('Save All Changes functionality will be implemented for bulk operations.');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,522 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tune Orders Data - Daily Mirror{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/daily_mirror_tune.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🛒 Tune Orders Data</h1>
|
||||
<p class="text-muted">Edit and update customer orders information</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-filter"></i> Filters and Search
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="searchInput"
|
||||
placeholder="Search by order, customer, or article...">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="statusFilter" class="form-label">Order Status</label>
|
||||
<select class="form-control" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="customerFilter" class="form-label">Customer</label>
|
||||
<select class="form-control" id="customerFilter">
|
||||
<option value="">All Customers</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="recordsPerPage" class="form-label">Records per page</label>
|
||||
<select class="form-control" id="recordsPerPage">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" onclick="loadOrdersData()">
|
||||
<i class="fas fa-search"></i> Apply Filters
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Customer Orders Data
|
||||
</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="recordsInfo" class="text-muted me-3"></span>
|
||||
<button class="btn btn-success btn-sm" onclick="saveAllChanges()">
|
||||
<i class="fas fa-save"></i> Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="ordersTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Customer</th>
|
||||
<th>Client Order</th>
|
||||
<th>Article Code</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th>Delivery Date</th>
|
||||
<th>Status</th>
|
||||
<th>Priority</th>
|
||||
<th>Product Group</th>
|
||||
<th>Order Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ordersTableBody">
|
||||
<!-- Data will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loadingIndicator" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Loading data...</p>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div id="noDataMessage" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-info-circle fa-2x text-muted"></i>
|
||||
<p class="mt-2 text-muted">No orders found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="card-footer">
|
||||
<nav aria-label="Orders data pagination">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0" id="pagination">
|
||||
<!-- Pagination will be generated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Customer Order</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editRecordId">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editOrderId" class="form-label">Order ID</label>
|
||||
<input type="text" class="form-control" id="editOrderId" readonly>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerCode" class="form-label">Customer Code</label>
|
||||
<input type="text" class="form-control" id="editCustomerCode">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerName" class="form-label">Customer Name</label>
|
||||
<input type="text" class="form-control" id="editCustomerName">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editClientOrder" class="form-label">Client Order</label>
|
||||
<input type="text" class="form-control" id="editClientOrder">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editArticleCode" class="form-label">Article Code</label>
|
||||
<input type="text" class="form-control" id="editArticleCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editQuantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="editQuantity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">Article Description</label>
|
||||
<textarea class="form-control" id="editDescription" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryDate" class="form-label">Delivery Date</label>
|
||||
<input type="date" class="form-control" id="editDeliveryDate">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editOrderStatus" class="form-label">Order Status</label>
|
||||
<select class="form-control" id="editOrderStatus">
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="CONFIRMED">Confirmed</option>
|
||||
<option value="Confirmat">Confirmat</option>
|
||||
<option value="IN_PROGRESS">In Progress</option>
|
||||
<option value="FINISHED">Finished</option>
|
||||
<option value="DELIVERED">Delivered</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editPriority" class="form-label">Priority</label>
|
||||
<select class="form-control" id="editPriority">
|
||||
<option value="LOW">Low</option>
|
||||
<option value="NORMAL">Normal</option>
|
||||
<option value="HIGH">High</option>
|
||||
<option value="URGENT">Urgent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editProductGroup" class="form-label">Product Group</label>
|
||||
<input type="text" class="form-control" id="editProductGroup">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editOrderDate" class="form-label">Order Date</label>
|
||||
<input type="date" class="form-control" id="editOrderDate">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRecord()">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentPerPage = 50;
|
||||
let currentSearch = '';
|
||||
let currentStatusFilter = '';
|
||||
let currentCustomerFilter = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
loadOrdersData();
|
||||
|
||||
// Setup search on enter key
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
loadOrdersData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadOrdersData(page = 1) {
|
||||
currentPage = page;
|
||||
currentPerPage = document.getElementById('recordsPerPage').value;
|
||||
currentSearch = document.getElementById('searchInput').value;
|
||||
currentStatusFilter = document.getElementById('statusFilter').value;
|
||||
currentCustomerFilter = document.getElementById('customerFilter').value;
|
||||
|
||||
// Show loading indicator
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('ordersTableBody').style.display = 'none';
|
||||
document.getElementById('noDataMessage').style.display = 'none';
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage,
|
||||
per_page: currentPerPage,
|
||||
search: currentSearch,
|
||||
status: currentStatusFilter,
|
||||
customer: currentCustomerFilter
|
||||
});
|
||||
|
||||
fetch(`/daily_mirror/api/tune/orders_data?${params}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
|
||||
if (data.success) {
|
||||
if (data.data.length === 0) {
|
||||
document.getElementById('noDataMessage').style.display = 'block';
|
||||
} else {
|
||||
displayOrdersData(data.data);
|
||||
updatePagination(data);
|
||||
updateRecordsInfo(data);
|
||||
|
||||
// Populate filter dropdowns on first load
|
||||
if (currentPage === 1) {
|
||||
populateCustomerFilter(data.customers);
|
||||
populateStatusFilter(data.statuses);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading data:', data.error);
|
||||
alert('Error loading orders data: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
console.error('Error:', error);
|
||||
alert('Error loading orders data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function displayOrdersData(data) {
|
||||
const tbody = document.getElementById('ordersTableBody');
|
||||
tbody.innerHTML = '';
|
||||
tbody.style.display = 'table-row-group';
|
||||
|
||||
data.forEach(record => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td><strong>${record.order_id}</strong></td>
|
||||
<td>
|
||||
<small class="text-muted d-block">${record.customer_code}</small>
|
||||
${record.customer_name}
|
||||
</td>
|
||||
<td>${record.client_order || '-'}</td>
|
||||
<td><code>${record.article_code}</code></td>
|
||||
<td><small>${record.article_description || '-'}</small></td>
|
||||
<td><span class="badge bg-info">${record.quantity_requested}</span></td>
|
||||
<td>${record.delivery_date || '-'}</td>
|
||||
<td><span class="badge bg-primary">${record.order_status}</span></td>
|
||||
<td><span class="badge bg-warning">${record.priority || 'NORMAL'}</span></td>
|
||||
<td><small>${record.product_group || '-'}</small></td>
|
||||
<td>${record.order_date || '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editRecord(${record.id})"
|
||||
title="Edit Order">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function populateCustomerFilter(customers) {
|
||||
const filter = document.getElementById('customerFilter');
|
||||
// Keep the "All Customers" option and add new ones
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Customers</option>';
|
||||
|
||||
customers.forEach(customer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = customer.code;
|
||||
option.textContent = `${customer.code} - ${customer.name}`;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function populateStatusFilter(statuses) {
|
||||
const filter = document.getElementById('statusFilter');
|
||||
const currentValue = filter.value;
|
||||
filter.innerHTML = '<option value="">All Statuses</option>';
|
||||
|
||||
statuses.forEach(status => {
|
||||
const option = document.createElement('option');
|
||||
option.value = status;
|
||||
option.textContent = status;
|
||||
filter.appendChild(option);
|
||||
});
|
||||
|
||||
filter.value = currentValue;
|
||||
}
|
||||
|
||||
function updatePagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
pagination.innerHTML = '';
|
||||
|
||||
if (data.total_pages <= 1) return;
|
||||
|
||||
// Previous button
|
||||
const prevLi = document.createElement('li');
|
||||
prevLi.className = `page-item ${data.page === 1 ? 'disabled' : ''}`;
|
||||
prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadOrdersData(${data.page - 1})">Previous</a>`;
|
||||
pagination.appendChild(prevLi);
|
||||
|
||||
// Page numbers
|
||||
const startPage = Math.max(1, data.page - 2);
|
||||
const endPage = Math.min(data.total_pages, data.page + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const li = document.createElement('li');
|
||||
li.className = `page-item ${i === data.page ? 'active' : ''}`;
|
||||
li.innerHTML = `<a class="page-link" href="#" onclick="loadOrdersData(${i})">${i}</a>`;
|
||||
pagination.appendChild(li);
|
||||
}
|
||||
|
||||
// Next button
|
||||
const nextLi = document.createElement('li');
|
||||
nextLi.className = `page-item ${data.page === data.total_pages ? 'disabled' : ''}`;
|
||||
nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadOrdersData(${data.page + 1})">Next</a>`;
|
||||
pagination.appendChild(nextLi);
|
||||
}
|
||||
|
||||
function updateRecordsInfo(data) {
|
||||
const start = (data.page - 1) * data.per_page + 1;
|
||||
const end = Math.min(data.page * data.per_page, data.total_records);
|
||||
document.getElementById('recordsInfo').textContent =
|
||||
`Showing ${start}-${end} of ${data.total_records} orders`;
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('statusFilter').value = '';
|
||||
document.getElementById('customerFilter').value = '';
|
||||
loadOrdersData(1);
|
||||
}
|
||||
|
||||
function editRecord(recordId) {
|
||||
// Find the record data from the current display
|
||||
const rows = document.querySelectorAll('#ordersTableBody tr');
|
||||
let recordData = null;
|
||||
|
||||
// Get data via API for editing
|
||||
fetch(`/daily_mirror/api/tune/orders_data?page=${currentPage}&per_page=${currentPerPage}&search=${currentSearch}&status=${currentStatusFilter}&customer=${currentCustomerFilter}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
recordData = data.data.find(record => record.id === recordId);
|
||||
if (recordData) {
|
||||
populateEditModal(recordData);
|
||||
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
editModal.show();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error loading record data: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function populateEditModal(record) {
|
||||
document.getElementById('editRecordId').value = record.id;
|
||||
document.getElementById('editOrderId').value = record.order_id;
|
||||
document.getElementById('editCustomerCode').value = record.customer_code;
|
||||
document.getElementById('editCustomerName').value = record.customer_name;
|
||||
document.getElementById('editClientOrder').value = record.client_order || '';
|
||||
document.getElementById('editArticleCode').value = record.article_code;
|
||||
document.getElementById('editDescription').value = record.article_description || '';
|
||||
document.getElementById('editQuantity').value = record.quantity_requested;
|
||||
document.getElementById('editDeliveryDate').value = record.delivery_date;
|
||||
document.getElementById('editOrderStatus').value = record.order_status;
|
||||
document.getElementById('editPriority').value = record.priority || 'NORMAL';
|
||||
document.getElementById('editProductGroup').value = record.product_group || '';
|
||||
document.getElementById('editOrderDate').value = record.order_date;
|
||||
}
|
||||
|
||||
function saveRecord() {
|
||||
const recordId = document.getElementById('editRecordId').value;
|
||||
const data = {
|
||||
customer_code: document.getElementById('editCustomerCode').value,
|
||||
customer_name: document.getElementById('editCustomerName').value,
|
||||
client_order: document.getElementById('editClientOrder').value,
|
||||
article_code: document.getElementById('editArticleCode').value,
|
||||
article_description: document.getElementById('editDescription').value,
|
||||
quantity_requested: parseInt(document.getElementById('editQuantity').value) || 0,
|
||||
delivery_date: document.getElementById('editDeliveryDate').value,
|
||||
order_status: document.getElementById('editOrderStatus').value,
|
||||
priority: document.getElementById('editPriority').value,
|
||||
product_group: document.getElementById('editProductGroup').value,
|
||||
order_date: document.getElementById('editOrderDate').value
|
||||
};
|
||||
|
||||
fetch(`/daily_mirror/api/tune/orders_data/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
// Close modal
|
||||
const editModal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
|
||||
editModal.hide();
|
||||
|
||||
// Reload data
|
||||
loadOrdersData(currentPage);
|
||||
|
||||
// Show success message
|
||||
alert('Order updated successfully!');
|
||||
} else {
|
||||
alert('Error updating order: ' + result.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error updating order: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function saveAllChanges() {
|
||||
alert('Save All Changes functionality will be implemented for bulk operations.');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,516 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tune Production Data - Daily Mirror{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/daily_mirror_tune.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-0">🏭 Tune Production Data</h1>
|
||||
<p class="text-muted">Edit and update production orders information</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Buttons removed; now present in top header -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-filter"></i> Filters and Search
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="searchInput" class="form-label">Search</label>
|
||||
<input type="text" class="form-control" id="searchInput"
|
||||
placeholder="Search by order, customer, or article...">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="statusFilter" class="form-label">Production Status</label>
|
||||
<select class="form-control" id="statusFilter">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="IN_PROGRESS">In Progress</option>
|
||||
<option value="FINISHED">Finished</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="customerFilter" class="form-label">Customer</label>
|
||||
<select class="form-control" id="customerFilter">
|
||||
<option value="">All Customers</option>
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="recordsPerPage" class="form-label">Records per page</label>
|
||||
<select class="form-control" id="recordsPerPage">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" onclick="loadProductionData()">
|
||||
<i class="fas fa-search"></i> Apply Filters
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-table"></i> Production Orders Data
|
||||
</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="recordsInfo" class="text-muted me-3"></span>
|
||||
<button class="btn btn-success btn-sm" onclick="saveAllChanges()">
|
||||
<i class="fas fa-save"></i> Save All Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover" id="productionTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Production Order</th>
|
||||
<th>Customer</th>
|
||||
<th>Client Order</th>
|
||||
<th>Article Code</th>
|
||||
<th>Description</th>
|
||||
<th>Quantity</th>
|
||||
<th>Delivery Date</th>
|
||||
<th>Status</th>
|
||||
<th>Machine</th>
|
||||
<th>Planning Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="productionTableBody">
|
||||
<!-- Data will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="loadingIndicator" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Loading data...</p>
|
||||
</div>
|
||||
|
||||
<!-- No data message -->
|
||||
<div id="noDataMessage" class="text-center py-4" style="display: none;">
|
||||
<i class="fas fa-info-circle fa-2x text-muted"></i>
|
||||
<p class="mt-2 text-muted">No production orders found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="card-footer">
|
||||
<nav aria-label="Production data pagination">
|
||||
<ul class="pagination pagination-sm justify-content-center mb-0" id="pagination">
|
||||
<!-- Pagination will be generated here -->
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true" data-bs-backdrop="true" data-bs-keyboard="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Production Order</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" id="modalCloseBtn"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editRecordId">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editProductionOrder" class="form-label">Production Order</label>
|
||||
<input type="text" class="form-control" id="editProductionOrder" readonly>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerCode" class="form-label">Customer Code</label>
|
||||
<input type="text" class="form-control" id="editCustomerCode">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editCustomerName" class="form-label">Customer Name</label>
|
||||
<input type="text" class="form-control" id="editCustomerName">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editClientOrder" class="form-label">Client Order</label>
|
||||
<input type="text" class="form-control" id="editClientOrder">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editArticleCode" class="form-label">Article Code</label>
|
||||
<input type="text" class="form-control" id="editArticleCode">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editQuantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="editQuantity">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">Article Description</label>
|
||||
<textarea class="form-control" id="editDescription" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editDeliveryDate" class="form-label">Delivery Date</label>
|
||||
<input type="date" class="form-control" id="editDeliveryDate">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="editStatus" class="form-label">Production Status</label>
|
||||
<select class="form-control" id="editStatus">
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="IN_PROGRESS">In Progress</option>
|
||||
<option value="FINISHED">Finished</option>
|
||||
<option value="CANCELLED">Cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="editMachine" class="form-label">Machine Code</label>
|
||||
<input type="text" class="form-control" id="editMachine">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRecord()">
|
||||
<i class="fas fa-save"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentData = [];
|
||||
let hasChanges = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
loadProductionData();
|
||||
});
|
||||
|
||||
function loadProductionData(page = 1) {
|
||||
currentPage = page;
|
||||
|
||||
// Show loading
|
||||
document.getElementById('loadingIndicator').style.display = 'block';
|
||||
document.getElementById('productionTableBody').innerHTML = '';
|
||||
document.getElementById('noDataMessage').style.display = 'none';
|
||||
|
||||
// Get filter values
|
||||
const search = document.getElementById('searchInput').value;
|
||||
const status = document.getElementById('statusFilter').value;
|
||||
const customer = document.getElementById('customerFilter').value;
|
||||
const perPage = document.getElementById('recordsPerPage').value;
|
||||
|
||||
// Build query parameters
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (search) params.append('search', search);
|
||||
if (status) params.append('status', status);
|
||||
if (customer) params.append('customer', customer);
|
||||
|
||||
// Fetch data
|
||||
fetch(`/daily_mirror/api/tune/production_data?${params}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
currentData = data.records;
|
||||
renderTable(data);
|
||||
renderPagination(data);
|
||||
updateRecordsInfo(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading production data:', error);
|
||||
alert('Error loading data: ' + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
document.getElementById('loadingIndicator').style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(data) {
|
||||
const tbody = document.getElementById('productionTableBody');
|
||||
|
||||
if (data.records.length === 0) {
|
||||
document.getElementById('noDataMessage').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.records.map((record, index) => `
|
||||
<tr id="row-${record.id}">
|
||||
<td><strong>${record.production_order}</strong></td>
|
||||
<td>${record.customer_code}<br><small class="text-muted">${record.customer_name || ''}</small></td>
|
||||
<td>${record.client_order || ''}</td>
|
||||
<td>${record.article_code || ''}</td>
|
||||
<td><small>${record.article_description || ''}</small></td>
|
||||
<td>${record.quantity_requested || ''}</td>
|
||||
<td>${record.delivery_date || ''}</td>
|
||||
<td><span class="badge bg-${getStatusColor(record.production_status)}">${record.production_status || ''}</span></td>
|
||||
<td>${record.machine_code || ''}</td>
|
||||
<td>${record.data_planificare || ''}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary btn-action btn-sm" onclick="editRecord(${record.id})" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
switch(status) {
|
||||
case 'PENDING': return 'warning';
|
||||
case 'IN_PROGRESS': return 'info';
|
||||
case 'FINISHED': return 'success';
|
||||
case 'CANCELLED': return 'danger';
|
||||
default: return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const pagination = document.getElementById('pagination');
|
||||
|
||||
if (data.total_pages <= 1) {
|
||||
pagination.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let paginationHTML = '';
|
||||
|
||||
// Previous button
|
||||
if (data.page > 1) {
|
||||
paginationHTML += `<li class="page-item"><a class="page-link" href="#" onclick="loadProductionData(${data.page - 1})">Previous</a></li>`;
|
||||
}
|
||||
|
||||
// Page numbers
|
||||
for (let i = Math.max(1, data.page - 2); i <= Math.min(data.total_pages, data.page + 2); i++) {
|
||||
const active = i === data.page ? 'active' : '';
|
||||
paginationHTML += `<li class="page-item ${active}"><a class="page-link" href="#" onclick="loadProductionData(${i})">${i}</a></li>`;
|
||||
}
|
||||
|
||||
// Next button
|
||||
if (data.page < data.total_pages) {
|
||||
paginationHTML += `<li class="page-item"><a class="page-link" href="#" onclick="loadProductionData(${data.page + 1})">Next</a></li>`;
|
||||
}
|
||||
|
||||
pagination.innerHTML = paginationHTML;
|
||||
}
|
||||
|
||||
function updateRecordsInfo(data) {
|
||||
const info = document.getElementById('recordsInfo');
|
||||
const start = (data.page - 1) * data.per_page + 1;
|
||||
const end = Math.min(data.page * data.per_page, data.total);
|
||||
info.textContent = `Showing ${start}-${end} of ${data.total} records`;
|
||||
}
|
||||
|
||||
function editRecord(recordId) {
|
||||
const record = currentData.find(r => r.id === recordId);
|
||||
if (!record) return;
|
||||
|
||||
// Populate the edit form
|
||||
document.getElementById('editRecordId').value = record.id;
|
||||
document.getElementById('editProductionOrder').value = record.production_order;
|
||||
document.getElementById('editCustomerCode').value = record.customer_code || '';
|
||||
document.getElementById('editCustomerName').value = record.customer_name || '';
|
||||
document.getElementById('editClientOrder').value = record.client_order || '';
|
||||
document.getElementById('editArticleCode').value = record.article_code || '';
|
||||
document.getElementById('editDescription').value = record.article_description || '';
|
||||
document.getElementById('editQuantity').value = record.quantity_requested || '';
|
||||
document.getElementById('editDeliveryDate').value = record.delivery_date || '';
|
||||
document.getElementById('editStatus').value = record.production_status || '';
|
||||
document.getElementById('editMachine').value = record.machine_code || '';
|
||||
|
||||
// Explicitly enable all editable fields
|
||||
const editableFields = ['editCustomerCode', 'editCustomerName', 'editClientOrder',
|
||||
'editArticleCode', 'editDescription', 'editQuantity',
|
||||
'editDeliveryDate', 'editStatus', 'editMachine'];
|
||||
editableFields.forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.disabled = false;
|
||||
field.removeAttribute('disabled');
|
||||
field.removeAttribute('readonly');
|
||||
field.style.backgroundColor = '#ffffff';
|
||||
field.style.color = '#000000';
|
||||
field.style.opacity = '1';
|
||||
field.style.pointerEvents = 'auto';
|
||||
field.style.cursor = 'text';
|
||||
field.style.userSelect = 'text';
|
||||
field.tabIndex = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Show the modal with proper configuration
|
||||
const modalElement = document.getElementById('editModal');
|
||||
|
||||
// Remove any existing modal instances to prevent conflicts
|
||||
const existingModal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (existingModal) {
|
||||
existingModal.dispose();
|
||||
}
|
||||
|
||||
const modal = new bootstrap.Modal(modalElement, {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
focus: true
|
||||
});
|
||||
|
||||
modal.show();
|
||||
|
||||
// Ensure form inputs are focusable and interactive after modal is shown
|
||||
modalElement.addEventListener('shown.bs.modal', function () {
|
||||
// Re-enable all fields after modal animation completes
|
||||
editableFields.forEach(fieldId => {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.disabled = false;
|
||||
field.removeAttribute('disabled');
|
||||
field.style.pointerEvents = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
// Focus on the first editable field
|
||||
const firstField = document.getElementById('editCustomerCode');
|
||||
if (firstField) {
|
||||
setTimeout(() => {
|
||||
firstField.focus();
|
||||
firstField.select();
|
||||
}, 100);
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
function saveRecord() {
|
||||
const recordId = document.getElementById('editRecordId').value;
|
||||
|
||||
const formData = {
|
||||
customer_code: document.getElementById('editCustomerCode').value,
|
||||
customer_name: document.getElementById('editCustomerName').value,
|
||||
client_order: document.getElementById('editClientOrder').value,
|
||||
article_code: document.getElementById('editArticleCode').value,
|
||||
article_description: document.getElementById('editDescription').value,
|
||||
quantity_requested: parseInt(document.getElementById('editQuantity').value) || 0,
|
||||
delivery_date: document.getElementById('editDeliveryDate').value,
|
||||
production_status: document.getElementById('editStatus').value,
|
||||
machine_code: document.getElementById('editMachine').value
|
||||
};
|
||||
|
||||
fetch(`/daily_mirror/api/tune/production_data/${recordId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
// Close modal and reload data
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('editModal'));
|
||||
modal.hide();
|
||||
|
||||
alert('Record updated successfully!');
|
||||
loadProductionData(currentPage);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving record:', error);
|
||||
alert('Error saving record: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.getElementById('statusFilter').value = '';
|
||||
document.getElementById('customerFilter').value = '';
|
||||
loadProductionData(1);
|
||||
}
|
||||
|
||||
function saveAllChanges() {
|
||||
alert('Bulk save functionality will be implemented in a future update!');
|
||||
}
|
||||
|
||||
// Add event listeners for real-time filtering
|
||||
document.getElementById('searchInput').addEventListener('input', function() {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
loadProductionData(1);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
document.getElementById('statusFilter').addEventListener('change', function() {
|
||||
loadProductionData(1);
|
||||
});
|
||||
|
||||
document.getElementById('customerFilter').addEventListener('change', function() {
|
||||
loadProductionData(1);
|
||||
});
|
||||
|
||||
document.getElementById('recordsPerPage').addEventListener('change', function() {
|
||||
loadProductionData(1);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -7,12 +7,15 @@
|
||||
|
||||
<!-- Row of evenly distributed cards -->
|
||||
<div class="dashboard-card">
|
||||
<h3>Quality Module</h3>
|
||||
<p>Final scanning module for production orders and quality reports access.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.main_scan') }}" class="btn">Launch Scanning Module</a>
|
||||
<a href="{{ url_for('main.reports') }}" class="btn">Access to Quality reports</a>
|
||||
</div>
|
||||
<h3>Access Scanning Module</h3>
|
||||
<p>Final scanning module for production orders</p>
|
||||
<a href="{{ url_for('main.main_scan') }}" class="btn">Launch Scanning Module</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h3>Access Reports Module</h3>
|
||||
<p>Module for verification and quality settings configuration.</p>
|
||||
<a href="{{ url_for('main.quality') }}" class="btn">Launch Reports Module</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -34,17 +37,5 @@
|
||||
<a href="{{ url_for('main.settings') }}" class="btn">Access Settings Page</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h3>📊 Daily Mirror</h3>
|
||||
<p>Business Intelligence and Production Reporting - Generate comprehensive daily reports including order quantities, production status, and delivery tracking.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('daily_mirror.daily_mirror_main_route') }}" class="btn">📊 Daily Mirror Hub</a>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
||||
<strong>Tracks:</strong> Orders quantity • Production launched • Production finished • Orders delivered
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,440 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}FG Quality Module{% endblock %}
|
||||
{% block content %}
|
||||
<div class="scan-container">
|
||||
<!-- Reports Card -->
|
||||
<div class="card report-form-card">
|
||||
<h3>FG Quality Reports</h3>
|
||||
|
||||
<!-- Reports List with Label-Button Layout -->
|
||||
<div class="reports-grid">
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Daily report will export all FG orders scanned at quality scanning points</label>
|
||||
<button class="btn report-btn" data-report="1">Daily Complete FG Orders Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select day for daily FG report will export all orders scanned at quality scanning points</label>
|
||||
<button class="btn report-btn" id="select-day-report">Select Day Daily FG Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select date range for custom FG report - from start date 00:00 to end date 23:59</label>
|
||||
<button class="btn report-btn" id="date-range-report">Date Range FG Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">5-day report will export all FG orders scanned at quality scanning points</label>
|
||||
<button class="btn report-btn" data-report="2">5-Day Complete FG Orders Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Report on FG items with quality issues for the current day</label>
|
||||
<button class="btn report-btn" data-report="3">Report on FG Items with Defects for the Current Day</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select specific day for FG quality defects report - items with quality issues</label>
|
||||
<button class="btn report-btn" id="select-day-defects-report">Select Day FG Quality Defects Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Select date range for FG quality defects report - items with quality issues between two dates</label>
|
||||
<button class="btn report-btn" id="date-range-defects-report">Date Range FG Quality Defects Report</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Report on FG items with quality issues for the last 5 days</label>
|
||||
<button class="btn report-btn" data-report="4">Report on FG Items with Defects for the Last 5 Days</button>
|
||||
</div>
|
||||
<div class="form-centered">
|
||||
<label class="report-description">Report all FG entries from the database</label>
|
||||
<button class="btn report-btn" data-report="5">Report FG Database</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Separator -->
|
||||
<div class="report-separator"></div>
|
||||
|
||||
<!-- Export Section -->
|
||||
<div class="export-section">
|
||||
<div class="form-centered last-buttons">
|
||||
<label class="export-description">Export current report as:</label>
|
||||
<div class="button-row">
|
||||
<button class="btn export-btn" id="export-csv">Export CSV</button>
|
||||
<!-- <button class="btn export-btn" id="export-excel">Export excell</button> -->
|
||||
{% if session.get('role') == 'superadmin' %}
|
||||
<button class="btn export-btn test-db-btn" id="test-database">Test FG Database</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Display Card -->
|
||||
<div class="card report-table-card">
|
||||
<h3 id="report-title">No data to display, please select a report.</h3>
|
||||
<div class="report-table-container">
|
||||
<table class="scan-table" id="report-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- Table headers will be dynamically populated -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Table data will be dynamically populated -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Popup Modal -->
|
||||
<div id="calendar-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Select Date for Daily FG Report</h4>
|
||||
<span class="close-modal">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<button id="prev-month" class="calendar-nav"><</button>
|
||||
<h3 id="calendar-month-year"></h3>
|
||||
<button id="next-month" class="calendar-nav">></button>
|
||||
</div>
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-weekdays">
|
||||
<div>Sun</div>
|
||||
<div>Mon</div>
|
||||
<div>Tue</div>
|
||||
<div>Wed</div>
|
||||
<div>Thu</div>
|
||||
<div>Fri</div>
|
||||
<div>Sat</div>
|
||||
</div>
|
||||
<div class="calendar-days" id="calendar-days">
|
||||
<!-- Days will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancel-date">Cancel</button>
|
||||
<button class="btn btn-primary" id="confirm-date" disabled>Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Popup Modal -->
|
||||
<div id="date-range-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Select Date Range for FG Report</h4>
|
||||
<span class="close-modal" id="close-date-range">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="date-range-container">
|
||||
<div class="date-input-group">
|
||||
<label for="start-date">Start Date:</label>
|
||||
<input type="date" id="start-date" class="date-input" />
|
||||
<small class="date-help">Report will include data from 00:00:00</small>
|
||||
</div>
|
||||
<div class="date-input-group">
|
||||
<label for="end-date">End Date:</label>
|
||||
<input type="date" id="end-date" class="date-input" />
|
||||
<small class="date-help">Report will include data until 23:59:59</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="cancel-date-range">Cancel</button>
|
||||
<button class="btn btn-primary" id="confirm-date-range" disabled>Generate Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<script>
|
||||
// Debug check - this will run immediately when the page loads
|
||||
console.log('FG Quality page loaded - checking script conflicts');
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('FG Quality DOM loaded');
|
||||
// Add a visible indicator that our script loaded
|
||||
setTimeout(() => {
|
||||
const titleElement = document.getElementById('report-title');
|
||||
if (titleElement && titleElement.textContent === 'No data to display, please select a report.') {
|
||||
titleElement.textContent = '🟢 FG Quality Module Ready - Select a report';
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='fg_quality.js') }}"></script>
|
||||
<script>
|
||||
// Theme functionality for FG Quality page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const body = document.body;
|
||||
const themeToggleButton = document.getElementById('theme-toggle');
|
||||
|
||||
// Helper function to update the theme toggle button text
|
||||
function updateThemeToggleButtonText() {
|
||||
if (body.classList.contains('dark-mode')) {
|
||||
themeToggleButton.textContent = 'Change to Light Mode';
|
||||
} else {
|
||||
themeToggleButton.textContent = 'Change to Dark Mode';
|
||||
}
|
||||
}
|
||||
|
||||
// Check and apply the saved theme from localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme) {
|
||||
body.classList.toggle('dark-mode', savedTheme === 'dark');
|
||||
}
|
||||
|
||||
// Update the button text based on the current theme
|
||||
if (themeToggleButton) {
|
||||
updateThemeToggleButtonText();
|
||||
|
||||
// Toggle the theme on button click
|
||||
themeToggleButton.addEventListener('click', () => {
|
||||
const isDarkMode = body.classList.toggle('dark-mode');
|
||||
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
|
||||
updateThemeToggleButtonText(); // Update the button text after toggling
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Override script - runs after both script.js and fg_quality.js
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// Wait a bit to ensure all scripts have loaded
|
||||
setTimeout(() => {
|
||||
console.log('🔧 FG Quality Override Script - Fixing event handlers');
|
||||
|
||||
// Override the problematic calendar functionality
|
||||
const selectDayReport = document.getElementById('select-day-report');
|
||||
const selectDayDefectsReport = document.getElementById('select-day-defects-report');
|
||||
const dateRangeReport = document.getElementById('date-range-report');
|
||||
const dateRangeDefectsReport = document.getElementById('date-range-defects-report');
|
||||
|
||||
let currentFGReportType = null;
|
||||
let selectedFGDate = null;
|
||||
|
||||
// Clear and re-add event listeners with FG-specific functionality
|
||||
if (selectDayReport) {
|
||||
// Remove all existing listeners by cloning
|
||||
const newSelectDayReport = selectDayReport.cloneNode(true);
|
||||
selectDayReport.parentNode.replaceChild(newSelectDayReport, selectDayReport);
|
||||
|
||||
newSelectDayReport.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('🎯 FG Select Day Report clicked');
|
||||
currentFGReportType = '6';
|
||||
showFGCalendarModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (dateRangeReport) {
|
||||
const newDateRangeReport = dateRangeReport.cloneNode(true);
|
||||
dateRangeReport.parentNode.replaceChild(newDateRangeReport, dateRangeReport);
|
||||
|
||||
newDateRangeReport.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('🎯 FG Date Range Report clicked');
|
||||
currentFGReportType = '7';
|
||||
showFGDateRangeModal();
|
||||
});
|
||||
}
|
||||
|
||||
// FG-specific calendar modal functionality
|
||||
function showFGCalendarModal() {
|
||||
const calendarModal = document.getElementById('calendar-modal');
|
||||
if (calendarModal) {
|
||||
calendarModal.style.display = 'block';
|
||||
generateFGCalendar();
|
||||
}
|
||||
}
|
||||
|
||||
function showFGDateRangeModal() {
|
||||
const dateRangeModal = document.getElementById('date-range-modal');
|
||||
if (dateRangeModal) {
|
||||
dateRangeModal.style.display = 'block';
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('start-date').value = today;
|
||||
document.getElementById('end-date').value = today;
|
||||
}
|
||||
}
|
||||
|
||||
function generateFGCalendar() {
|
||||
const calendarDays = document.getElementById('calendar-days');
|
||||
const monthYear = document.getElementById('calendar-month-year');
|
||||
|
||||
if (!calendarDays || !monthYear) return;
|
||||
|
||||
const currentDate = new Date();
|
||||
const year = currentDate.getFullYear();
|
||||
const month = currentDate.getMonth();
|
||||
|
||||
monthYear.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${year}`;
|
||||
|
||||
// Clear previous days
|
||||
calendarDays.innerHTML = '';
|
||||
|
||||
// Get first day of month and number of days
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
// Add empty cells for previous month
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
const emptyDay = document.createElement('div');
|
||||
emptyDay.className = 'calendar-day empty';
|
||||
calendarDays.appendChild(emptyDay);
|
||||
}
|
||||
|
||||
// Add days of current month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dayElement = document.createElement('div');
|
||||
dayElement.className = 'calendar-day';
|
||||
dayElement.textContent = day;
|
||||
|
||||
// Highlight October 15 as it has FG data
|
||||
if (month === 9 && day === 15) { // October is month 9
|
||||
dayElement.style.backgroundColor = '#e3f2fd';
|
||||
dayElement.style.border = '2px solid #2196f3';
|
||||
dayElement.title = 'FG data available';
|
||||
}
|
||||
|
||||
dayElement.addEventListener('click', () => {
|
||||
// Remove previous selection
|
||||
document.querySelectorAll('.calendar-day.selected').forEach(el => {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to clicked day
|
||||
dayElement.classList.add('selected');
|
||||
|
||||
// Set selected date
|
||||
selectedFGDate = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
console.log('🗓️ FG Calendar date selected:', selectedFGDate);
|
||||
|
||||
// Enable confirm button
|
||||
const confirmButton = document.getElementById('confirm-date');
|
||||
if (confirmButton) {
|
||||
confirmButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
calendarDays.appendChild(dayElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Override confirm button
|
||||
const confirmDateBtn = document.getElementById('confirm-date');
|
||||
if (confirmDateBtn) {
|
||||
const newConfirmBtn = confirmDateBtn.cloneNode(true);
|
||||
confirmDateBtn.parentNode.replaceChild(newConfirmBtn, confirmDateBtn);
|
||||
|
||||
newConfirmBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log('✅ FG Calendar confirm clicked:', selectedFGDate, 'Report type:', currentFGReportType);
|
||||
|
||||
if (selectedFGDate && currentFGReportType) {
|
||||
const url = `/generate_fg_report?report=${currentFGReportType}&date=${selectedFGDate}`;
|
||||
console.log('🚀 Calling FG endpoint:', url);
|
||||
|
||||
document.getElementById('report-title').textContent = 'Loading FG data...';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('📊 FG Data received:', data);
|
||||
|
||||
const table = document.getElementById('report-table');
|
||||
const thead = table.querySelector('thead tr');
|
||||
const tbody = table.querySelector('tbody');
|
||||
|
||||
// Clear existing content
|
||||
thead.innerHTML = '';
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// Add headers
|
||||
if (data.headers) {
|
||||
data.headers.forEach(header => {
|
||||
const th = document.createElement('th');
|
||||
th.textContent = header;
|
||||
thead.appendChild(th);
|
||||
});
|
||||
}
|
||||
|
||||
// Add rows
|
||||
if (data.rows && data.rows.length > 0) {
|
||||
data.rows.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
row.forEach(cell => {
|
||||
const td = document.createElement('td');
|
||||
td.textContent = cell || '';
|
||||
tr.appendChild(td);
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
document.getElementById('report-title').textContent = `FG Daily Report for ${selectedFGDate} (${data.rows.length} records)`;
|
||||
} else {
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = data.headers ? data.headers.length : 1;
|
||||
td.textContent = data.message || 'No FG data found';
|
||||
td.style.textAlign = 'center';
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
document.getElementById('report-title').textContent = `FG Daily Report for ${selectedFGDate} - No data`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Error fetching FG data:', error);
|
||||
document.getElementById('report-title').textContent = 'Error loading FG data';
|
||||
});
|
||||
|
||||
// Hide modal
|
||||
document.getElementById('calendar-modal').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Override date range confirm button
|
||||
const confirmDateRangeBtn = document.getElementById('confirm-date-range');
|
||||
if (confirmDateRangeBtn) {
|
||||
const newConfirmRangeBtn = confirmDateRangeBtn.cloneNode(true);
|
||||
confirmDateRangeBtn.parentNode.replaceChild(newConfirmRangeBtn, confirmDateRangeBtn);
|
||||
|
||||
newConfirmRangeBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const startDate = document.getElementById('start-date').value;
|
||||
const endDate = document.getElementById('end-date').value;
|
||||
|
||||
console.log('📅 FG Date range confirm:', startDate, 'to', endDate);
|
||||
|
||||
if (startDate && endDate && currentFGReportType) {
|
||||
const url = `/generate_fg_report?report=${currentFGReportType}&start_date=${startDate}&end_date=${endDate}`;
|
||||
console.log('🚀 Calling FG range endpoint:', url);
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('📊 FG Range data received:', data);
|
||||
// Handle response similar to above
|
||||
document.getElementById('report-title').textContent = `FG Date Range Report (${startDate} to ${endDate})`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Error fetching FG range data:', error);
|
||||
});
|
||||
|
||||
// Hide modal
|
||||
document.getElementById('date-range-modal').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ FG Quality Override Script - Event handlers fixed');
|
||||
}, 500); // Wait 500ms to ensure all other scripts are loaded
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -23,80 +23,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const defectCodeInput = document.getElementById('defect_code');
|
||||
const form = document.getElementById('fg-scan-form');
|
||||
|
||||
// Restore saved operator code from localStorage (only Quality Operator Code)
|
||||
const savedOperatorCode = localStorage.getItem('fg_scan_operator_code');
|
||||
|
||||
if (savedOperatorCode) {
|
||||
operatorCodeInput.value = savedOperatorCode;
|
||||
}
|
||||
|
||||
// Check if we need to clear fields after a successful submission
|
||||
const shouldClearAfterSubmit = localStorage.getItem('fg_scan_clear_after_submit');
|
||||
if (shouldClearAfterSubmit === 'true') {
|
||||
// Clear the flag
|
||||
localStorage.removeItem('fg_scan_clear_after_submit');
|
||||
localStorage.removeItem('fg_scan_last_cp');
|
||||
localStorage.removeItem('fg_scan_last_defect');
|
||||
|
||||
// Clear CP code, OC1, OC2, and defect code for next scan
|
||||
cpCodeInput.value = '';
|
||||
oc1CodeInput.value = '';
|
||||
oc2CodeInput.value = '';
|
||||
defectCodeInput.value = '';
|
||||
|
||||
// Show success indicator
|
||||
setTimeout(function() {
|
||||
// Focus on CP code field for next scan
|
||||
cpCodeInput.focus();
|
||||
|
||||
// Add visual feedback
|
||||
const successIndicator = document.createElement('div');
|
||||
successIndicator.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
font-weight: bold;
|
||||
`;
|
||||
successIndicator.textContent = '✅ Scan recorded! Ready for next scan';
|
||||
document.body.appendChild(successIndicator);
|
||||
|
||||
// Remove success indicator after 3 seconds
|
||||
setTimeout(function() {
|
||||
if (successIndicator.parentNode) {
|
||||
successIndicator.parentNode.removeChild(successIndicator);
|
||||
}
|
||||
}, 3000);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Focus on the first empty required field (only if not clearing after submit)
|
||||
if (shouldClearAfterSubmit !== 'true') {
|
||||
if (!operatorCodeInput.value) {
|
||||
operatorCodeInput.focus();
|
||||
} else if (!oc1CodeInput.value) {
|
||||
oc1CodeInput.focus();
|
||||
} else if (!oc2CodeInput.value) {
|
||||
oc2CodeInput.focus();
|
||||
} else if (!cpCodeInput.value) {
|
||||
cpCodeInput.focus();
|
||||
} else {
|
||||
defectCodeInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Save operator codes to localStorage when they change (only Quality Operator Code)
|
||||
operatorCodeInput.addEventListener('input', function() {
|
||||
if (this.value.startsWith('OP') && this.value.length >= 3) {
|
||||
localStorage.setItem('fg_scan_operator_code', this.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Create error message element for operator code
|
||||
const operatorErrorMessage = document.createElement('div');
|
||||
operatorErrorMessage.className = 'error-message';
|
||||
@@ -412,11 +338,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
timeInput.value = `${hours}:${minutes}:${seconds}`;
|
||||
|
||||
// Save current CP code and defect code to localStorage for clearing after reload
|
||||
localStorage.setItem('fg_scan_clear_after_submit', 'true');
|
||||
localStorage.setItem('fg_scan_last_cp', cpCodeInput.value);
|
||||
localStorage.setItem('fg_scan_last_defect', defectCodeInput.value);
|
||||
|
||||
// Submit the form
|
||||
form.submit();
|
||||
}
|
||||
@@ -478,27 +399,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add functionality for clear saved codes button
|
||||
const clearSavedBtn = document.getElementById('clear-saved-btn');
|
||||
clearSavedBtn.addEventListener('click', function() {
|
||||
if (confirm('Clear saved Quality Operator code? You will need to re-enter it.')) {
|
||||
// Clear localStorage (only Quality Operator Code)
|
||||
localStorage.removeItem('fg_scan_operator_code');
|
||||
localStorage.removeItem('fg_scan_clear_after_submit');
|
||||
localStorage.removeItem('fg_scan_last_cp');
|
||||
localStorage.removeItem('fg_scan_last_defect');
|
||||
|
||||
// Clear Quality Operator Code field only
|
||||
operatorCodeInput.value = '';
|
||||
|
||||
// Focus on operator code field
|
||||
operatorCodeInput.focus();
|
||||
|
||||
// Show confirmation
|
||||
alert('✅ Saved Quality Operator code cleared! Please re-enter your operator code.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -530,7 +430,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<input type="text" id="time" name="time" value="{{ now().strftime('%H:%M:%S') }}" readonly>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
<button type="button" class="btn" id="clear-saved-btn" style="background-color: #ff6b6b; margin-left: 10px;">Clear Quality Operator</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<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_data') }}" class="btn">Upload Orders</a>
|
||||
<a href="{{ url_for('main.view_orders') }}" class="btn">View Orders</a>
|
||||
<a href="{{ url_for('main.get_unprinted_orders') }}" class="btn">View Orders</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Reports Module{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="reports-container">
|
||||
<h1>Reports Module</h1>
|
||||
<p>Access different quality and production reporting modules for data analysis and verification.</p>
|
||||
|
||||
<!-- Row of evenly distributed cards -->
|
||||
<div class="dashboard-container">
|
||||
<!-- Card 1: Quality Reports -->
|
||||
<div class="dashboard-card">
|
||||
<h3>Quality Reports</h3>
|
||||
<p>Access quality scanning reports and analysis for production process verification.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.quality') }}" class="btn">Launch Quality Reports</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: FG Quality Reports -->
|
||||
<div class="dashboard-card">
|
||||
<h3>FG Quality Reports</h3>
|
||||
<p>Finished Goods quality reports and analysis for final product verification.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.fg_quality') }}" class="btn">Launch FG Quality Reports</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 3: Additional Reports (Placeholder for future expansion) -->
|
||||
<div class="dashboard-card">
|
||||
<h3>Additional Reports</h3>
|
||||
<p>Access additional reporting modules and data analysis tools.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button class="btn" disabled style="opacity: 0.5;">Coming Soon</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -10,9 +10,8 @@
|
||||
|
||||
/* Enhanced table styling */
|
||||
.card.scan-table-card table.print-module-table.scan-table thead th {
|
||||
border-bottom: 2px solid var(--app-border-color, #dee2e6) !important;
|
||||
background-color: var(--app-table-header-bg, #2a3441) !important;
|
||||
color: var(--app-text-color, #ffffff) !important;
|
||||
border-bottom: 2px solid #dee2e6 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
padding: 0.25rem 0.4rem !important;
|
||||
text-align: left !important;
|
||||
font-weight: 600 !important;
|
||||
@@ -23,21 +22,13 @@
|
||||
.card.scan-table-card table.print-module-table.scan-table {
|
||||
width: 100% !important;
|
||||
border-collapse: collapse !important;
|
||||
background-color: var(--app-card-bg, #2a3441) !important;
|
||||
}
|
||||
|
||||
.card.scan-table-card table.print-module-table.scan-table tbody tr:hover td {
|
||||
background-color: var(--app-hover-bg, #3a4451) !important;
|
||||
background-color: #f8f9fa !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.card.scan-table-card table.print-module-table.scan-table tbody td {
|
||||
background-color: var(--app-card-bg, #2a3441) !important;
|
||||
color: var(--app-text-color, #ffffff) !important;
|
||||
border: 1px solid var(--app-border-color, #495057) !important;
|
||||
padding: 0.25rem 0.4rem !important;
|
||||
}
|
||||
|
||||
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected td {
|
||||
background-color: #007bff !important;
|
||||
color: white !important;
|
||||
@@ -149,13 +140,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Barcode Frame - positioned 10px below rectangle, centered, constrained to label width -->
|
||||
<div id="barcode-frame" style="position: absolute; top: 395px; left: 50%; transform: translateX(-50%); width: 220px; max-width: 220px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden;">
|
||||
<!-- Barcode Frame - positioned 10px below rectangle, centered, 90% of label width -->
|
||||
<div id="barcode-frame" style="position: absolute; top: 395px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 270px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<!-- Code 128 Barcode representation -->
|
||||
<svg id="barcode-display" style="width: 100%; height: 40px; max-width: 220px;"></svg>
|
||||
<svg id="barcode-display" style="width: 100%; height: 40px;"></svg>
|
||||
|
||||
<!-- Barcode text below the bars (hidden in preview) -->
|
||||
<div id="barcode-text" style="font-size: 8px; font-family: 'Courier New', monospace; margin-top: 2px; text-align: center; font-weight: bold; display: none;">
|
||||
<!-- Barcode text below the bars -->
|
||||
<div id="barcode-text" style="font-size: 8px; font-family: 'Courier New', monospace; margin-top: 2px; text-align: center; font-weight: bold;">
|
||||
<!-- Barcode text will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,8 +156,8 @@
|
||||
<!-- Vertical Code 128 Barcode representation -->
|
||||
<svg id="vertical-barcode-display" style="width: 100%; height: 35px;"></svg>
|
||||
|
||||
<!-- Vertical barcode text (hidden in preview) -->
|
||||
<div id="vertical-barcode-text" style="position: absolute; bottom: -15px; font-size: 7px; font-family: 'Courier New', monospace; text-align: center; font-weight: bold; width: 100%; display: none;">
|
||||
<!-- Vertical barcode text -->
|
||||
<div id="vertical-barcode-text" style="position: absolute; bottom: -15px; font-size: 7px; font-family: 'Courier New', monospace; text-align: center; font-weight: bold; width: 100%;">
|
||||
<!-- Vertical barcode text will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -484,12 +475,10 @@ function updateLabelPreview(order) {
|
||||
|
||||
JsBarcode("#barcode-display", horizontalBarcodeData, {
|
||||
format: "CODE128",
|
||||
width: 1.2,
|
||||
width: 2,
|
||||
height: 40,
|
||||
displayValue: false,
|
||||
margin: 0,
|
||||
fontSize: 0,
|
||||
textMargin: 0
|
||||
margin: 2
|
||||
});
|
||||
console.log('✅ Horizontal barcode generated successfully');
|
||||
} catch (e) {
|
||||
|
||||
@@ -22,74 +22,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const defectCodeInput = document.getElementById('defect_code');
|
||||
const form = document.querySelector('.form-centered');
|
||||
|
||||
// Load saved operator codes from localStorage (only Quality Operator Code)
|
||||
function loadSavedCodes() {
|
||||
const savedOperatorCode = localStorage.getItem('scan_operator_code');
|
||||
|
||||
if (savedOperatorCode) {
|
||||
operatorCodeInput.value = savedOperatorCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Save operator codes to localStorage (only Quality Operator Code)
|
||||
function saveCodes() {
|
||||
if (operatorCodeInput.value.startsWith('OP')) {
|
||||
localStorage.setItem('scan_operator_code', operatorCodeInput.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear saved codes from localStorage (only Quality Operator Code)
|
||||
function clearSavedCodes() {
|
||||
localStorage.removeItem('scan_operator_code');
|
||||
operatorCodeInput.value = '';
|
||||
showSuccessMessage('Quality Operator code cleared!');
|
||||
operatorCodeInput.focus();
|
||||
}
|
||||
|
||||
// Show success message
|
||||
function showSuccessMessage(message) {
|
||||
const successDiv = document.createElement('div');
|
||||
successDiv.className = 'success-message';
|
||||
successDiv.textContent = message;
|
||||
successDiv.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
z-index: 1000;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
`;
|
||||
document.body.appendChild(successDiv);
|
||||
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(successDiv)) {
|
||||
document.body.removeChild(successDiv);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Load saved codes on page load
|
||||
loadSavedCodes();
|
||||
|
||||
// Focus on the first empty field
|
||||
setTimeout(() => {
|
||||
if (!operatorCodeInput.value) {
|
||||
operatorCodeInput.focus();
|
||||
} else if (!cpCodeInput.value) {
|
||||
cpCodeInput.focus();
|
||||
} else if (!oc1CodeInput.value) {
|
||||
oc1CodeInput.focus();
|
||||
} else if (!oc2CodeInput.value) {
|
||||
oc2CodeInput.focus();
|
||||
} else {
|
||||
defectCodeInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Create error message element for operator code
|
||||
const operatorErrorMessage = document.createElement('div');
|
||||
operatorErrorMessage.className = 'error-message';
|
||||
@@ -401,21 +333,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
timeInput.value = `${hours}:${minutes}:${seconds}`;
|
||||
|
||||
// Save operator codes before submitting
|
||||
saveCodes();
|
||||
|
||||
// Submit the form
|
||||
form.submit();
|
||||
|
||||
// Clear CP, OC1, OC2, and defect code fields after successful submission
|
||||
setTimeout(() => {
|
||||
cpCodeInput.value = '';
|
||||
oc1CodeInput.value = '';
|
||||
oc2CodeInput.value = '';
|
||||
defectCodeInput.value = '';
|
||||
showSuccessMessage('Scan submitted successfully!');
|
||||
cpCodeInput.focus();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -506,7 +425,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<input type="text" id="time" name="time" value="{{ now().strftime('%H:%M:%S') }}" readonly>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearSavedCodes()" style="margin-top: 10px; background-color: #6c757d;">Clear Quality Operator</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,318 +3,129 @@
|
||||
{% block title %}Settings{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="settings-layout">
|
||||
<!-- First Column - Users Card -->
|
||||
<div class="settings-column-1">
|
||||
<div class="card">
|
||||
<h3>👥 Current Users</h3>
|
||||
<div class="users-grid">
|
||||
<div class="card-container">
|
||||
<div class="card">
|
||||
<h3>Manage Users</h3>
|
||||
<ul class="user-list">
|
||||
{% for user in users %}
|
||||
<div class="user-card">
|
||||
<div class="user-header">
|
||||
<div class="user-avatar">
|
||||
{% if user.role == 'superadmin' %}
|
||||
👑
|
||||
{% elif user.role == 'admin' %}
|
||||
🛡️
|
||||
{% elif user.role == 'manager' %}
|
||||
🎯
|
||||
{% else %}
|
||||
👤
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="user-name">{{ user.username }}</div>
|
||||
<div class="user-role-badge role-{{ user.role }}">
|
||||
{{ user.role.title() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-modules">
|
||||
<div class="modules-section">
|
||||
<strong>Modules:</strong>
|
||||
{% if user.role in ['superadmin', 'admin'] %}
|
||||
<div class="module-tags">
|
||||
<span class="module-tag all-modules">All Modules</span>
|
||||
</div>
|
||||
{% elif user.modules %}
|
||||
<div class="module-tags">
|
||||
{% for module in user.get_modules() %}
|
||||
<span class="module-tag module-{{ module }}">{{ module.title() }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="module-tags">
|
||||
<span class="module-tag no-modules">No modules assigned</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="access-level-section">
|
||||
<strong>Access Level:</strong>
|
||||
<div class="access-description">
|
||||
{% if user.role == 'superadmin' %}
|
||||
Full system access
|
||||
{% elif user.role == 'admin' %}
|
||||
Full app access
|
||||
{% elif user.role == 'manager' %}
|
||||
Full module access + reports
|
||||
{% elif user.role == 'worker' %}
|
||||
Basic operations only (no reports) - Can have multiple modules
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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-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-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}">Delete User</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</ul>
|
||||
<button id="create-user-btn" class="btn create-btn">Create User</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.users-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: var(--card-bg, #fff);
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.user-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--avatar-bg, #f0f0f0);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: var(--text-color, #333);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-role-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.role-superadmin {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-admin {
|
||||
background-color: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-manager {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.role-worker {
|
||||
background-color: #198754;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user-modules {
|
||||
font-size: 14px;
|
||||
color: var(--text-color, #555);
|
||||
}
|
||||
|
||||
.modules-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.access-level-section {
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--border-color, #eee);
|
||||
}
|
||||
|
||||
.access-description {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--muted-text, #666);
|
||||
font-style: italic;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.module-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.module-tag {
|
||||
padding: 3px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.all-modules {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.module-quality {
|
||||
background-color: #0dcaf0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.module-warehouse {
|
||||
background-color: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.module-labels {
|
||||
background-color: #20c997;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.no-modules {
|
||||
background-color: #e9ecef;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
body.dark-mode .user-card {
|
||||
background: #2a2a2a;
|
||||
border-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body.dark-mode .user-name {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body.dark-mode .user-modules {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
body.dark-mode .access-level-section {
|
||||
border-top-color: #555;
|
||||
}
|
||||
|
||||
body.dark-mode .access-description {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
body.dark-mode .user-avatar {
|
||||
background: #404040;
|
||||
}
|
||||
|
||||
body.dark-mode .user-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.users-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
/* Layout Styles for 2-column structure */
|
||||
.settings-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-column-1 {
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
.settings-column-2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 1024px) {
|
||||
.settings-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="card">
|
||||
<h3>External Server Settings</h3>
|
||||
<form method="POST" action="{{ url_for('main.save_external_db') }}" class="form-centered">
|
||||
<label for="server_domain">Server Domain/IP Address:</label>
|
||||
<input type="text" id="server_domain" name="server_domain" value="{{ external_settings.get('server_domain', '') }}" required>
|
||||
<label for="port">Port:</label>
|
||||
<input type="number" id="port" name="port" value="{{ external_settings.get('port', '') }}" required>
|
||||
<label for="database_name">Database Name:</label>
|
||||
<input type="text" id="database_name" name="database_name" value="{{ external_settings.get('database_name', '') }}" required>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="{{ external_settings.get('username', '') }}" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" value="{{ external_settings.get('password', '') }}" required>
|
||||
<button type="submit" class="btn">Save/Update External Database Info Settings</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Second Column - Management and Settings Cards -->
|
||||
<div class="settings-column-2">
|
||||
<div class="card">
|
||||
<h3>🎯 User & Permissions Management</h3>
|
||||
<p><strong>Simplified 4-Tier System:</strong> Superadmin → Admin → Manager → Worker</p>
|
||||
<p>Streamlined interface with module-based permissions (Quality, Warehouse, Labels)</p>
|
||||
<div style="margin-top: 15px;">
|
||||
<a href="{{ url_for('main.user_management_simple') }}" class="btn" style="background-color: #2196f3; color: white; margin-right: 10px;">
|
||||
🎯 Manage Users (Simplified)
|
||||
</a>
|
||||
</div>
|
||||
<small style="display: block; margin-top: 10px; color: #666;">
|
||||
Recommended: Use the simplified user management for easier administration
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>External Server Settings</h3>
|
||||
<form method="POST" action="{{ url_for('main.save_external_db') }}" class="form-centered">
|
||||
<label for="server_domain">Server Domain/IP Address:</label>
|
||||
<input type="text" id="server_domain" name="server_domain" value="{{ external_settings.get('server_domain', '') }}" required>
|
||||
<label for="port">Port:</label>
|
||||
<input type="number" id="port" name="port" value="{{ external_settings.get('port', '') }}" required>
|
||||
<label for="database_name">Database Name:</label>
|
||||
<input type="text" id="database_name" name="database_name" value="{{ external_settings.get('database_name', '') }}" required>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="{{ external_settings.get('username', '') }}" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" value="{{ external_settings.get('password', '') }}" required>
|
||||
<button type="submit" class="btn">Save/Update External Database Info Settings</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card" style="margin-top: 32px;">
|
||||
<h3>Role & Permissions Management</h3>
|
||||
<p>Configure granular permissions for each role in the system with expandable sections and detailed access control.</p>
|
||||
<a href="{{ url_for('main.role_permissions') }}" class="btn">Manage Role Permissions</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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') }}">
|
||||
<input type="hidden" id="user-id" name="user_id">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
<label for="email">Email (Optional):</label>
|
||||
<input type="email" id="email" name="email">
|
||||
<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="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">Save</button>
|
||||
<button type="button" id="close-user-popup-btn" class="btn cancel-btn">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popup for confirming user deletion -->
|
||||
<div id="delete-user-popup" class="popup">
|
||||
<div class="popup-content">
|
||||
<h3>Do you really want to delete the user <span id="delete-username"></span>?</h3>
|
||||
<form id="delete-user-form" method="POST" action="{{ url_for('main.delete_user') }}">
|
||||
<input type="hidden" id="delete-user-id" name="user_id">
|
||||
<button type="submit" class="btn delete-confirm-btn">Yes</button>
|
||||
<button type="button" id="close-delete-popup-btn" class="btn cancel-btn">No</button>
|
||||
</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('user-id').value = '';
|
||||
document.getElementById('password').required = true;
|
||||
document.getElementById('password').placeholder = '';
|
||||
document.getElementById('username').readOnly = false;
|
||||
};
|
||||
|
||||
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('user-id').value = btn.getAttribute('data-user-id');
|
||||
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('password').required = false;
|
||||
document.getElementById('password').placeholder = 'Leave blank to keep current password';
|
||||
document.getElementById('username').readOnly = true;
|
||||
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.edit_user") }}');
|
||||
};
|
||||
});
|
||||
|
||||
// Delete User button logic
|
||||
Array.from(document.getElementsByClassName('delete-user-btn')).forEach(function(btn) {
|
||||
btn.onclick = function() {
|
||||
document.getElementById('delete-user-popup').style.display = 'flex';
|
||||
document.getElementById('delete-username').innerText = btn.getAttribute('data-username');
|
||||
document.getElementById('delete-user-id').value = btn.getAttribute('data-user-id');
|
||||
};
|
||||
});
|
||||
|
||||
document.getElementById('close-delete-popup-btn').onclick = function() {
|
||||
document.getElementById('delete-user-popup').style.display = 'none';
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -82,35 +82,15 @@ table.view-orders-table.scan-table tbody tr:hover td {
|
||||
<h3>Upload Order Data for Labels</h3>
|
||||
{% endif %}
|
||||
<form method="POST" enctype="multipart/form-data" class="form-centered" id="csv-upload-form">
|
||||
{% if show_preview %}
|
||||
<!-- Show preview controls -->
|
||||
<input type="hidden" name="action" value="save">
|
||||
<label style="font-weight: bold;">Preview of: {{ filename }}</label><br>
|
||||
<p style="color: #666; font-size: 14px; margin: 10px 0;">
|
||||
Showing first 10 rows. Review the data below and click "Save to Database" to confirm.
|
||||
</p>
|
||||
<button type="submit" class="btn" style="background-color: #28a745;">Save to Database</button>
|
||||
<a href="{{ url_for('main.upload_data') }}" class="btn" style="background-color: #6c757d; margin-left: 10px;">Cancel</a>
|
||||
<label for="csv_file">Choose CSV file:</label>
|
||||
{% if leftover_description %}
|
||||
<button type="submit" class="btn btn-danger" name="clear_table" value="1">Clear Table</button>
|
||||
{% elif not orders %}
|
||||
<input type="file" name="csv_file" accept=".csv" required><br>
|
||||
<button type="submit" class="btn">Upload & Review</button>
|
||||
{% else %}
|
||||
<!-- Show file upload -->
|
||||
<input type="hidden" name="action" value="preview">
|
||||
<label for="file">Choose CSV file:</label>
|
||||
<input type="file" name="file" accept=".csv" required><br>
|
||||
<button type="submit" class="btn">Upload & Preview</button>
|
||||
|
||||
<!-- CSV Format Information -->
|
||||
<div style="margin-top: 20px; padding: 15px; background-color: var(--app-card-bg, #2a3441); border-radius: 5px; border-left: 4px solid var(--app-accent-color, #007bff); color: var(--app-text-color, #ffffff);">
|
||||
<h5 style="margin-top: 0; color: var(--app-accent-color, #007bff);">Expected CSV Format</h5>
|
||||
<p style="margin-bottom: 10px; color: var(--app-text-color, #ffffff);">Your CSV file should contain columns such as:</p>
|
||||
<ul style="margin-bottom: 10px; color: var(--app-text-color, #ffffff);">
|
||||
<li><strong>order_number</strong> - The order/production number</li>
|
||||
<li><strong>quantity</strong> - Number of items</li>
|
||||
<li><strong>warehouse_location</strong> - Storage location</li>
|
||||
</ul>
|
||||
<p style="color: var(--app-secondary-text, #b8c5d1); font-size: 14px; margin-bottom: 0;">
|
||||
Column names are case-insensitive and can have variations like "Order Number", "Quantity", "Location", etc.
|
||||
</p>
|
||||
</div>
|
||||
<label style="font-weight: bold;">Selected file: {{ session['csv_filename'] if session['csv_filename'] else 'Unknown' }}</label><br>
|
||||
<button type="button" class="btn" onclick="showPopupAndSubmit()">Upload to Database</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
@@ -144,45 +124,108 @@ table.view-orders-table.scan-table tbody tr:hover td {
|
||||
</div>
|
||||
|
||||
<!-- Preview Table Card (expandable height, scrollable) -->
|
||||
<div class="card scan-table-card" style="margin-bottom: 24px; max-height: 480px; overflow-y: auto;">
|
||||
{% if show_preview %}
|
||||
<h3>CSV Data Preview - {{ filename }}</h3>
|
||||
<table class="scan-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<th>{{ header }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if preview_data %}
|
||||
{% for row in preview_data %}
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<td>{{ row.get(header, '') }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td colspan="{{ headers|length }}" style="text-align:center;">No data to preview</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card scan-table-card{% if leftover_description %} leftover-table-card{% endif %}" style="margin-bottom: 24px; max-height: 480px; overflow-y: auto;">
|
||||
{% if leftover_description %}
|
||||
<h3>Left over orders</h3>
|
||||
{% else %}
|
||||
<h3>CSV Data Preview</h3>
|
||||
<table class="scan-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Upload a CSV file to see preview</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td style="text-align:center; padding: 40px;">No CSV file uploaded yet. Use the form above to upload and preview your data.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Preview Table</h3>
|
||||
{% endif %}
|
||||
<table class="scan-table view-orders-table{% if leftover_description %} leftover-table{% endif %}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Comanda<br>Productie</th>
|
||||
<th>Cod<br>Articol</th>
|
||||
<th>Descr. Com.<br>Prod</th>
|
||||
<th>Cantitate</th>
|
||||
<th>Data<br>Livrare</th>
|
||||
<th>Dimensiune</th>
|
||||
<th>Com.Achiz.<br>Client</th>
|
||||
<th>Nr.<br>Linie</th>
|
||||
<th>Customer<br>Name</th>
|
||||
<th>Customer<br>Art. Nr.</th>
|
||||
<th>Open<br>Order</th>
|
||||
<th>Line</th>
|
||||
<th>Printed</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if orders %}
|
||||
{% for order in orders %}
|
||||
{% if order and (order.get('comanda_productie', '') or order.get('descr_com_prod', '')) %}
|
||||
<tr>
|
||||
<td>{{ order.get('id', '') }}</td>
|
||||
<td><strong>{{ order.get('comanda_productie', '') }}</strong></td>
|
||||
<td>{{ order.get('cod_articol', '-') }}</td>
|
||||
<td>{{ order.get('descr_com_prod', '') }}</td>
|
||||
<td style="text-align: right; font-weight: 600;">{{ order.get('cantitate', '') }}</td>
|
||||
<td style="text-align: center;">{{ order.get('data_livrare', '') }}</td>
|
||||
<td style="text-align: center;">{{ order.get('dimensiune', '-') }}</td>
|
||||
<td>{{ order.get('com_achiz_client', '-') }}</td>
|
||||
<td style="text-align: right;">{{ order.get('nr_linie_com_client', '-') }}</td>
|
||||
<td>{{ order.get('customer_name', '-') }}</td>
|
||||
<td>{{ order.get('customer_article_number', '-') }}</td>
|
||||
<td>{{ order.get('open_for_order', '-') }}</td>
|
||||
<td style="text-align: right;">{{ order.get('line_number', '-') }}</td>
|
||||
<td style="text-align: center;">
|
||||
{% if order.get('printed_labels', 0) == 1 %}
|
||||
<span style="color: #28a745; font-weight: bold;">✓ Yes</span>
|
||||
{% else %}
|
||||
<span style="color: #dc3545;">✗ No</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="font-size: 11px; color: #6c757d;">{{ order.get('created_at', '-') }}</td>
|
||||
</tr>
|
||||
{% if order.error_message %}
|
||||
<tr>
|
||||
<td colspan="15" style="color: #dc3545; font-size: 12px; background: #fff3f3;">
|
||||
<strong>Error:</strong> {{ order.error_message }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td colspan="15" style="text-align:center;">No CSV file uploaded yet.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if validation_errors or validation_warnings %}
|
||||
{% if not leftover_description %}
|
||||
<div class="card" style="margin-bottom: 24px;">
|
||||
<h4>Validation Results</h4>
|
||||
{% if validation_errors %}
|
||||
<div style="color: #dc3545; margin-bottom: 16px;">
|
||||
<strong>Errors found:</strong>
|
||||
<ul>
|
||||
{% for error in validation_errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if validation_warnings %}
|
||||
<div style="color: #ffc107;">
|
||||
<strong>Warnings:</strong>
|
||||
<ul>
|
||||
{% for warning in validation_warnings %}
|
||||
<li>{{ warning }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if report %}
|
||||
<div class="card" style="margin-bottom: 24px;">
|
||||
<h4>Import Report</h4>
|
||||
<p>{{ report }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,902 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}User Management - Simplified{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* Theme-aware card styles */
|
||||
.user-management-page .card {
|
||||
text-align: left !important;
|
||||
flex: none !important;
|
||||
max-width: 100% !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 5px !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Light mode card styles */
|
||||
body.light-mode .user-management-page .card {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
border: 1px solid #ddd !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .card-body {
|
||||
background: #fff !important;
|
||||
color: #000 !important;
|
||||
padding: 1.25rem !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .card-header {
|
||||
background-color: #f8f9fa !important;
|
||||
color: #333 !important;
|
||||
border-bottom: 1px solid #ddd !important;
|
||||
padding: 0.75rem 1.25rem !important;
|
||||
border-top-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Dark mode card styles */
|
||||
body.dark-mode .user-management-page .card {
|
||||
background: #1e1e1e !important;
|
||||
color: #fff !important;
|
||||
border: 1px solid #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .card-body {
|
||||
background: #1e1e1e !important;
|
||||
color: #fff !important;
|
||||
padding: 1.25rem !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .card-header {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #ccc !important;
|
||||
border-bottom: 1px solid #444 !important;
|
||||
padding: 0.75rem 1.25rem !important;
|
||||
border-top-left-radius: 4px !important;
|
||||
border-top-right-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Theme-aware header text */
|
||||
.user-management-page .card-header h5 {
|
||||
margin: 0 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .card-header h5 {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .card-header h5 {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
/* Theme-aware role badges */
|
||||
.user-role-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.role-superadmin { background-color: #dc3545; color: white; }
|
||||
.role-admin { background-color: #fd7e14; color: white; }
|
||||
.role-manager { background-color: #007bff; color: white; }
|
||||
.role-worker { background-color: #28a745; color: white; }
|
||||
|
||||
/* Dark mode badge adjustments */
|
||||
body.dark-mode .role-manager { background-color: #0056b3; }
|
||||
body.dark-mode .role-worker { background-color: #1e7e34; }
|
||||
|
||||
/* Theme-aware module badges */
|
||||
.module-badges .badge {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
body.light-mode .module-badges .badge {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body.dark-mode .module-badges .badge {
|
||||
background-color: #0056b3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.module-checkboxes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.module-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Theme-aware info panels */
|
||||
.access-level-info {
|
||||
border-left: 4px solid #007bff;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
body.light-mode .access-level-info {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
body.dark-mode .access-level-info {
|
||||
background-color: #2a2a2a;
|
||||
color: #ccc;
|
||||
border-left-color: #0056b3;
|
||||
}
|
||||
|
||||
.table-warning {
|
||||
background-color: #fff3cd !important;
|
||||
}
|
||||
|
||||
body.dark-mode .table-warning {
|
||||
background-color: #664d03 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* Colored card headers - respect theme but use accent colors */
|
||||
.card-header.bg-primary {
|
||||
background-color: #007bff !important;
|
||||
color: white !important;
|
||||
border-color: #007bff !important;
|
||||
}
|
||||
|
||||
.card-header.bg-primary h5 {
|
||||
color: white !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.card-header.bg-success {
|
||||
background-color: #28a745 !important;
|
||||
color: white !important;
|
||||
border-color: #28a745 !important;
|
||||
}
|
||||
|
||||
.card-header.bg-success h5 {
|
||||
color: white !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Dark mode adjustments for colored headers */
|
||||
body.dark-mode .card-header.bg-primary {
|
||||
background-color: #0056b3 !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .card-header.bg-success {
|
||||
background-color: #1e7e34 !important;
|
||||
border-color: #1e7e34 !important;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#quickModuleEdit {
|
||||
border: 2px solid #198754;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
background-color: #f8fff8;
|
||||
}
|
||||
|
||||
/* Theme-aware table styling */
|
||||
.thead-dark {
|
||||
background-color: #343a40 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
body.dark-mode .thead-dark {
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
.user-management-page .table {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .table {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.user-management-page .table th,
|
||||
.user-management-page .table td {
|
||||
border-top: 1px solid #dee2e6 !important;
|
||||
padding: 0.75rem !important;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table th,
|
||||
body.dark-mode .user-management-page .table td {
|
||||
border-top: 1px solid #444 !important;
|
||||
}
|
||||
|
||||
.user-management-page .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
|
||||
.user-management-page .table-hover tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.075) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .table-hover tbody tr:hover {
|
||||
background-color: rgba(255, 255, 255, 0.075) !important;
|
||||
}
|
||||
|
||||
/* Theme-aware form elements */
|
||||
.user-management-page .form-control {
|
||||
border: 1px solid #ced4da !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .form-control {
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
border-color: #ced4da !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .form-control {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #fff !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
.user-management-page .form-control:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .form-control:focus {
|
||||
border-color: #80bdff !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .form-control:focus {
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
.user-management-page label {
|
||||
font-weight: 500 !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page label {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page label {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
/* Theme-aware buttons */
|
||||
.user-management-page .btn-primary {
|
||||
background-color: #007bff !important;
|
||||
border-color: #007bff !important;
|
||||
}
|
||||
|
||||
.user-management-page .btn-primary:hover {
|
||||
background-color: #0056b3 !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .btn-primary {
|
||||
background-color: #0056b3 !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .btn-primary:hover {
|
||||
background-color: #004085 !important;
|
||||
border-color: #004085 !important;
|
||||
}
|
||||
|
||||
/* Additional theme-aware elements */
|
||||
#quickModuleEdit {
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border: 2px solid #28a745;
|
||||
}
|
||||
|
||||
body.light-mode #quickModuleEdit {
|
||||
background-color: #f8fff8;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
body.dark-mode #quickModuleEdit {
|
||||
background-color: #1a2f1a;
|
||||
color: #ccc;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
/* Theme-aware text colors */
|
||||
body.light-mode .user-management-page h2,
|
||||
body.light-mode .user-management-page p {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page h2,
|
||||
body.dark-mode .user-management-page p {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
body.light-mode .user-management-page .text-muted {
|
||||
color: #6c757d !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page .text-muted {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
/* Theme-aware select dropdown */
|
||||
body.light-mode .user-management-page select.form-control {
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .user-management-page select.form-control {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4 user-management-page">
|
||||
<h2>Simplified User Management</h2>
|
||||
<p class="text-muted">Manage users with the new 4-tier permission system: Superadmin → Admin → Manager → Worker</p>
|
||||
|
||||
<div class="row">
|
||||
<!-- Create User Card -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">👤 Create New User</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="addUserForm" method="POST" action="/create_user_simple">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="role">Role:</label>
|
||||
<select id="role" name="role" class="form-control" required onchange="updateModuleSelection()">
|
||||
<option value="">Select a role...</option>
|
||||
<option value="superadmin">Superadmin - Full system access</option>
|
||||
<option value="admin">Admin - Full app access (except role permissions)</option>
|
||||
<option value="manager">Manager - Module-based access (can have multiple modules)</option>
|
||||
<option value="worker">Worker - Limited module access (can have multiple modules)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="moduleSelection" style="display: none;">
|
||||
<label>Modules:</label>
|
||||
<div class="module-checkboxes">
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_quality" name="modules" value="quality">
|
||||
<label for="module_quality">Quality Control</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_warehouse" name="modules" value="warehouse">
|
||||
<label for="module_warehouse">Warehouse Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_labels" name="modules" value="labels">
|
||||
<label for="module_labels">Label Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="module_daily_mirror" name="modules" value="daily_mirror">
|
||||
<label for="module_daily_mirror">Daily Mirror (Business Intelligence)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="accessLevelInfo" class="access-level-info" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">Create User</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Rights Card -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">⚙️ Edit User Rights</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="userRightsPanel">
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-user-edit fa-3x mb-3"></i>
|
||||
<p>Select a user from the table below to edit their rights and module access.</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Module Management for Selected User -->
|
||||
<div id="quickModuleEdit" style="display: none;">
|
||||
<h6>Quick Module Management</h6>
|
||||
<div class="mb-3">
|
||||
<strong>User:</strong> <span id="selectedUserName"></span>
|
||||
<br><small class="text-muted">Role: <span id="selectedUserRole"></span></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Current Modules:</label>
|
||||
<div id="currentModules" class="module-checkboxes">
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_quality" name="quick_modules" value="quality">
|
||||
<label for="quick_module_quality">Quality Control</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_warehouse" name="quick_modules" value="warehouse">
|
||||
<label for="quick_module_warehouse">Warehouse Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_labels" name="quick_modules" value="labels">
|
||||
<label for="quick_module_labels">Label Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="quick_module_daily_mirror" name="quick_modules" value="daily_mirror">
|
||||
<label for="quick_module_daily_mirror">Daily Mirror</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm w-100">
|
||||
<button type="button" class="btn btn-success" onclick="updateUserModules()">
|
||||
💾 Save Module Changes
|
||||
</button>
|
||||
<button type="button" class="btn btn-info" onclick="showFullEditModal()">
|
||||
✏️ Full Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteSelectedUser()">
|
||||
🗑️ Delete User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Users Table -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>👥 Current Users</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Role</th>
|
||||
<th>Modules</th>
|
||||
<th>Access Level</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr class="user-row" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-role="{{ user.role }}" data-modules="{{ (user.get_modules() or []) | tojson }}">
|
||||
<td>
|
||||
<strong>{{ user.username }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="user-role-badge role-{{ user.role }}">
|
||||
{{ user.role.title() }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="module-badges">
|
||||
{% if user.role in ['superadmin', 'admin'] %}
|
||||
<span class="badge bg-secondary">All Modules</span>
|
||||
{% elif user.modules %}
|
||||
{% for module in user.get_modules() %}
|
||||
<span class="badge bg-info">{{ module.title() }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="text-muted">No modules assigned</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">
|
||||
{% if user.role == 'superadmin' %}
|
||||
Full system access
|
||||
{% elif user.role == 'admin' %}
|
||||
Full app access
|
||||
{% elif user.role == 'manager' %}
|
||||
Full module access + reports
|
||||
{% elif user.role == 'worker' %}
|
||||
Basic operations only (no reports) - Can have multiple modules
|
||||
{% endif %}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary select-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
data-role="{{ user.role }}"
|
||||
data-modules="{{ (user.get_modules() or []) | tojson }}"
|
||||
title="Select for quick edit">
|
||||
📝 Select
|
||||
</button>
|
||||
<button class="btn btn-outline-info edit-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
data-role="{{ user.role }}"
|
||||
data-modules="{{ (user.get_modules() or []) | tojson }}"
|
||||
title="Full edit">
|
||||
⚙️ Edit
|
||||
</button>
|
||||
{% if user.username != session.get('user') %}
|
||||
<button class="btn btn-outline-danger delete-user-btn"
|
||||
data-user-id="{{ user.id }}"
|
||||
data-username="{{ user.username }}"
|
||||
title="Delete user">
|
||||
🗑️
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-users fa-3x mb-3"></i>
|
||||
<p>No users found. Create your first user above!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="editUserForm" method="POST" action="/edit_user_simple">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="edit_user_id" name="user_id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit_username">Username:</label>
|
||||
<input type="text" id="edit_username" name="username" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit_password">Password (leave blank to keep current):</label>
|
||||
<input type="password" id="edit_password" name="password" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit_role">Role:</label>
|
||||
<select id="edit_role" name="role" class="form-control" required onchange="updateEditModuleSelection()">
|
||||
<option value="">Select a role...</option>
|
||||
<option value="superadmin">Superadmin - Full system access</option>
|
||||
<option value="admin">Admin - Full app access (except role permissions)</option>
|
||||
<option value="manager">Manager - Module-based access (can have multiple modules)</option>
|
||||
<option value="worker">Worker - Limited module access (can have multiple modules)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="editModuleSelection" style="display: none;">
|
||||
<label>Modules:</label>
|
||||
<div class="module-checkboxes">
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_quality" name="modules" value="quality">
|
||||
<label for="edit_module_quality">Quality Control</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_warehouse" name="modules" value="warehouse">
|
||||
<label for="edit_module_warehouse">Warehouse Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_labels" name="modules" value="labels">
|
||||
<label for="edit_module_labels">Label Management</label>
|
||||
</div>
|
||||
<div class="module-checkbox">
|
||||
<input type="checkbox" id="edit_module_daily_mirror" name="modules" value="daily_mirror">
|
||||
<label for="edit_module_daily_mirror">Daily Mirror</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editAccessLevelInfo" class="access-level-info" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateModuleSelection() {
|
||||
const role = document.getElementById('role').value;
|
||||
const moduleSelection = document.getElementById('moduleSelection');
|
||||
const accessLevelInfo = document.getElementById('accessLevelInfo');
|
||||
const checkboxes = document.querySelectorAll('#moduleSelection input[type="checkbox"]');
|
||||
|
||||
if (role === 'superadmin' || role === 'admin') {
|
||||
moduleSelection.style.display = 'none';
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
accessLevelInfo.style.display = 'none';
|
||||
} else if (role === 'manager' || role === 'worker') {
|
||||
moduleSelection.style.display = 'block';
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
|
||||
if (role === 'manager') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Manager Access:</strong> Can have multiple modules. Full access to assigned modules including reports and management functions.';
|
||||
} else if (role === 'worker') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Worker Access:</strong> Can have multiple modules. Limited to basic operations only (no reports or management functions). <br><small>Examples: Quality worker can scan, Warehouse worker can move orders, etc.</small>';
|
||||
}
|
||||
} else {
|
||||
moduleSelection.style.display = 'none';
|
||||
accessLevelInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function updateEditModuleSelection() {
|
||||
const role = document.getElementById('edit_role').value;
|
||||
const moduleSelection = document.getElementById('editModuleSelection');
|
||||
const accessLevelInfo = document.getElementById('editAccessLevelInfo');
|
||||
const checkboxes = document.querySelectorAll('#editModuleSelection input[type="checkbox"]');
|
||||
|
||||
if (role === 'superadmin' || role === 'admin') {
|
||||
moduleSelection.style.display = 'none';
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
accessLevelInfo.style.display = 'none';
|
||||
} else if (role === 'manager' || role === 'worker') {
|
||||
moduleSelection.style.display = 'block';
|
||||
|
||||
if (role === 'manager') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Manager Access:</strong> Can have multiple modules. Full access to assigned modules including reports and management functions.';
|
||||
} else if (role === 'worker') {
|
||||
accessLevelInfo.style.display = 'block';
|
||||
accessLevelInfo.innerHTML = '<strong>Worker Access:</strong> Can have multiple modules. Limited to basic operations only (no reports or management functions). <br><small>Examples: Quality worker can scan, Warehouse worker can move orders, etc.</small>';
|
||||
}
|
||||
} else {
|
||||
moduleSelection.style.display = 'none';
|
||||
accessLevelInfo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Global variable to store selected user
|
||||
let selectedUser = null;
|
||||
|
||||
function selectUserForQuickEdit(userId, username, role, modules) {
|
||||
selectedUser = {id: userId, username: username, role: role, modules: modules};
|
||||
|
||||
// Update the quick edit panel
|
||||
document.getElementById('selectedUserName').textContent = username;
|
||||
document.getElementById('selectedUserRole').textContent = role;
|
||||
|
||||
// Clear all module checkboxes first
|
||||
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]');
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
|
||||
// Check the appropriate modules
|
||||
if (modules && Array.isArray(modules)) {
|
||||
modules.forEach(module => {
|
||||
const checkbox = document.getElementById('quick_module_' + module);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Show the quick edit panel
|
||||
document.getElementById('quickModuleEdit').style.display = 'block';
|
||||
document.querySelector('#userRightsPanel .text-center').style.display = 'none';
|
||||
|
||||
// Highlight selected row
|
||||
document.querySelectorAll('.user-row').forEach(row => {
|
||||
row.classList.remove('table-warning');
|
||||
});
|
||||
document.querySelector(`[data-user-id="${userId}"]`).classList.add('table-warning');
|
||||
}
|
||||
|
||||
function updateUserModules() {
|
||||
if (!selectedUser) {
|
||||
alert('No user selected');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selected modules
|
||||
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]:checked');
|
||||
const modules = Array.from(checkboxes).map(cb => cb.value);
|
||||
|
||||
// Validate modules for the role
|
||||
if ((selectedUser.role === 'manager' || selectedUser.role === 'worker') && modules.length === 0) {
|
||||
alert(`${selectedUser.role}s must have at least one module assigned.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and submit form
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/quick_update_modules';
|
||||
|
||||
// Add user ID
|
||||
const userIdInput = document.createElement('input');
|
||||
userIdInput.type = 'hidden';
|
||||
userIdInput.name = 'user_id';
|
||||
userIdInput.value = selectedUser.id;
|
||||
form.appendChild(userIdInput);
|
||||
|
||||
// Add modules
|
||||
modules.forEach(module => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'modules';
|
||||
input.value = module;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function showFullEditModal() {
|
||||
if (!selectedUser) {
|
||||
alert('No user selected');
|
||||
return;
|
||||
}
|
||||
|
||||
editUser(selectedUser.id, selectedUser.username, selectedUser.role, selectedUser.modules);
|
||||
}
|
||||
|
||||
function deleteSelectedUser() {
|
||||
if (!selectedUser) {
|
||||
alert('No user selected');
|
||||
return;
|
||||
}
|
||||
|
||||
deleteUser(selectedUser.id, selectedUser.username);
|
||||
}
|
||||
|
||||
function editUser(userId, username, role, modules) {
|
||||
document.getElementById('edit_user_id').value = userId;
|
||||
document.getElementById('edit_username').value = username;
|
||||
document.getElementById('edit_role').value = role;
|
||||
document.getElementById('edit_password').value = '';
|
||||
|
||||
// Clear all module checkboxes first
|
||||
const checkboxes = document.querySelectorAll('#editModuleSelection input[type="checkbox"]');
|
||||
checkboxes.forEach(cb => cb.checked = false);
|
||||
|
||||
// Check the appropriate modules
|
||||
if (modules && Array.isArray(modules)) {
|
||||
modules.forEach(module => {
|
||||
const checkbox = document.getElementById('edit_module_' + module);
|
||||
if (checkbox) checkbox.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
updateEditModuleSelection();
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function deleteUser(userId, username) {
|
||||
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/delete_user_simple';
|
||||
|
||||
const userIdInput = document.createElement('input');
|
||||
userIdInput.type = 'hidden';
|
||||
userIdInput.name = 'user_id';
|
||||
userIdInput.value = userId;
|
||||
|
||||
form.appendChild(userIdInput);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUser(userId, username) {
|
||||
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/delete_user_simple';
|
||||
|
||||
const userIdInput = document.createElement('input');
|
||||
userIdInput.type = 'hidden';
|
||||
userIdInput.name = 'user_id';
|
||||
userIdInput.value = userId;
|
||||
|
||||
form.appendChild(userIdInput);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateModuleSelection();
|
||||
|
||||
// Add event listeners for select user buttons
|
||||
document.querySelectorAll('.select-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
const role = this.dataset.role;
|
||||
let modules = [];
|
||||
try {
|
||||
modules = JSON.parse(this.dataset.modules) || [];
|
||||
} catch (e) {
|
||||
console.log('Error parsing modules for user:', username, e);
|
||||
modules = [];
|
||||
}
|
||||
selectUserForQuickEdit(userId, username, role, modules);
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for edit buttons
|
||||
document.querySelectorAll('.edit-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
const role = this.dataset.role;
|
||||
let modules = [];
|
||||
try {
|
||||
modules = JSON.parse(this.dataset.modules) || [];
|
||||
} catch (e) {
|
||||
console.log('Error parsing modules for user:', username, e);
|
||||
modules = [];
|
||||
}
|
||||
editUser(userId, username, role, modules);
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listeners for delete buttons
|
||||
document.querySelectorAll('.delete-user-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const userId = this.dataset.userId;
|
||||
const username = this.dataset.username;
|
||||
deleteUser(userId, username);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -25,14 +25,13 @@ table.view-orders-table.scan-table thead th {
|
||||
line-height: 1.3 !important;
|
||||
padding: 6px 3px !important;
|
||||
font-size: 11px !important;
|
||||
background-color: var(--header-bg-color) !important;
|
||||
color: var(--header-text-color) !important;
|
||||
background-color: #e9ecef !important;
|
||||
font-weight: bold !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0 !important;
|
||||
overflow: visible !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
border: 1px solid #ddd !important;
|
||||
text-overflow: clip !important;
|
||||
position: relative !important;
|
||||
}
|
||||
@@ -42,9 +41,7 @@ table.view-orders-table.scan-table tbody td {
|
||||
padding: 4px 2px !important;
|
||||
font-size: 10px !important;
|
||||
text-align: center !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
background-color: var(--card-bg-color) !important;
|
||||
color: var(--text-color) !important;
|
||||
border: 1px solid #ddd !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
@@ -71,7 +68,7 @@ table.view-orders-table.scan-table tbody td {
|
||||
|
||||
/* HOVER EFFECTS */
|
||||
table.view-orders-table.scan-table tbody tr:hover td {
|
||||
background-color: var(--hover-color) !important;
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
/* COLUMN WIDTH SPECIFICATIONS */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user