Docker deployment improvements: fixed backup/restore, sticky headers, quality code display

This commit is contained in:
ske087
2025-11-13 02:40:36 +02:00
parent 3b69161f1e
commit 2ce918e1b3
13 changed files with 1034 additions and 117 deletions

View File

@@ -104,11 +104,12 @@ class DatabaseBackupManager:
# Build mysqldump command
# Note: --skip-lock-tables and --force help with views that have permission issues
cmd = [
'mysqldump',
'mariadb-dump',
f"--host={self.config['host']}",
f"--port={self.config['port']}",
f"--user={self.config['user']}",
f"--password={self.config['password']}",
'--skip-ssl',
'--single-transaction',
'--skip-lock-tables',
'--force',
@@ -315,7 +316,7 @@ class DatabaseBackupManager:
# --complete-insert: Include column names in INSERT (more reliable)
# --extended-insert: Use multi-row INSERT for efficiency
cmd = [
'mysqldump',
'mariadb-dump',
f"--host={self.config['host']}",
f"--port={self.config['port']}",
f"--user={self.config['user']}",
@@ -396,36 +397,43 @@ class DatabaseBackupManager:
'message': 'Backup file not found'
}
# Build mysql restore command
cmd = [
'mysql',
f"--host={self.config['host']}",
f"--port={self.config['port']}",
f"--user={self.config['user']}",
f"--password={self.config['password']}"
]
# Read SQL file and execute using Python mariadb library
import mariadb
# Execute mysql restore
with open(file_path, 'r') as f:
result = subprocess.run(
cmd,
stdin=f,
stderr=subprocess.PIPE,
text=True
)
sql_content = f.read()
if result.returncode == 0:
return {
'success': True,
'message': f'Database restored successfully from {filename}'
}
else:
error_msg = result.stderr
print(f"Restore error: {error_msg}")
return {
'success': False,
'message': f'Restore failed: {error_msg}'
}
# Connect to database
conn = mariadb.connect(
user=self.config['user'],
password=self.config['password'],
host=self.config['host'],
port=int(self.config['port']),
database=self.config['database']
)
cursor = conn.cursor()
# Split SQL into statements and execute
statements = sql_content.split(';')
executed = 0
for statement in statements:
statement = statement.strip()
if statement:
try:
cursor.execute(statement)
executed += 1
except Exception as stmt_error:
print(f"Warning executing statement: {stmt_error}")
conn.commit()
conn.close()
return {
'success': True,
'message': f'Database restored successfully from {filename} ({executed} statements executed)'
}
except Exception as e:
print(f"Exception during restore: {e}")
@@ -499,24 +507,34 @@ class DatabaseBackupManager:
print(f"Warning during table truncation: {e}")
# Continue anyway - the restore might still work
# Build mysql restore command for data
cmd = [
'mysql',
f"--host={self.config['host']}",
f"--port={self.config['port']}",
f"--user={self.config['user']}",
f"--password={self.config['password']}",
self.config['database']
]
# Execute mysql restore
# Read and execute SQL file using Python mariadb library
with open(file_path, 'r') as f:
result = subprocess.run(
cmd,
stdin=f,
stderr=subprocess.PIPE,
text=True
)
sql_content = f.read()
conn = mariadb.connect(
user=self.config['user'],
password=self.config['password'],
host=self.config['host'],
port=int(self.config['port']),
database=self.config['database']
)
cursor = conn.cursor()
statements = sql_content.split(';')
executed = 0
for statement in statements:
statement = statement.strip()
if statement:
try:
cursor.execute(statement)
executed += 1
except Exception as stmt_error:
print(f"Warning executing statement: {stmt_error}")
conn.commit()
result_success = True
result_returncode = 0
# Re-enable foreign key checks
try:
@@ -535,17 +553,15 @@ class DatabaseBackupManager:
except Exception as e:
print(f"Warning: Could not re-enable foreign key checks: {e}")
if result.returncode == 0:
if result_success:
return {
'success': True,
'message': f'Data restored successfully from {filename}'
}
else:
error_msg = result.stderr
print(f"Data restore error: {error_msg}")
return {
'success': False,
'message': f'Data restore failed: {error_msg}'
'message': f'Data restore failed'
}
except Exception as e:

View File

@@ -253,4 +253,55 @@ body.dark-mode .floating-back-btn {
body.dark-mode .floating-back-btn:hover {
background: linear-gradient(135deg, #343a40, #212529);
}
/* ==========================================================================
STICKY TABLE HEADERS - Keep first row fixed when scrolling
========================================================================== */
.report-table-container {
max-height: 600px;
overflow-y: auto;
overflow-x: auto;
position: relative;
border: 1px solid #ddd;
border-radius: 4px;
}
.report-table-container table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
.report-table-container thead th {
position: sticky;
top: 0;
background-color: #f8f9fa;
z-index: 10;
border-bottom: 2px solid #dee2e6;
padding: 12px 8px;
font-weight: 600;
text-align: left;
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
}
.report-table-container tbody td {
padding: 8px;
border-bottom: 1px solid #dee2e6;
}
/* Dark mode support for sticky headers */
body.dark-mode .report-table-container {
border-color: #495057;
}
body.dark-mode .report-table-container thead th {
background-color: #343a40;
border-bottom-color: #495057;
color: #f8f9fa;
}
body.dark-mode .report-table-container tbody td {
border-bottom-color: #495057;
}

View File

@@ -171,12 +171,20 @@ document.addEventListener('DOMContentLoaded', function() {
thead.innerHTML = '';
tbody.innerHTML = '';
// Find the index of the "Defect Code" column
let defectCodeIndex = -1;
// Add headers
if (data.headers && data.headers.length > 0) {
data.headers.forEach(header => {
data.headers.forEach((header, index) => {
const th = document.createElement('th');
th.textContent = header;
thead.appendChild(th);
// Track the defect code column (quality_code)
if (header === 'Defect Code' || header === 'Quality Code') {
defectCodeIndex = index;
}
});
}
@@ -184,9 +192,20 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.rows && data.rows.length > 0) {
data.rows.forEach(row => {
const tr = document.createElement('tr');
row.forEach(cell => {
row.forEach((cell, index) => {
const td = document.createElement('td');
td.textContent = cell || '';
// Special handling for defect code column
if (index === defectCodeIndex && (cell === 0 || cell === '0' || cell === '' || cell === null)) {
td.textContent = 'OK';
td.style.color = '#28a745'; // Green color for OK
td.style.fontWeight = '600';
td.setAttribute('data-csv-value', '0'); // Store original value for CSV
} else {
td.textContent = cell || '';
td.setAttribute('data-csv-value', cell || ''); // Store original value
}
tr.appendChild(td);
});
tbody.appendChild(tr);
@@ -488,7 +507,8 @@ document.addEventListener('DOMContentLoaded', function() {
const csvContent = rows.map(row => {
const cells = Array.from(row.querySelectorAll('th, td'));
return cells.map(cell => {
let text = cell.textContent.trim();
// Use data-csv-value attribute if available (for defect codes), otherwise use text content
let text = cell.hasAttribute('data-csv-value') ? cell.getAttribute('data-csv-value') : cell.textContent.trim();
// Escape quotes and wrap in quotes if necessary
if (text.includes(',') || text.includes('"') || text.includes('\n')) {
text = '"' + text.replace(/"/g, '""') + '"';

View File

@@ -42,13 +42,21 @@ document.addEventListener('DOMContentLoaded', () => {
// Clear existing table content
tableHead.innerHTML = '';
tableBody.innerHTML = '';
// Find the index of the "Defect Code" column
let defectCodeIndex = -1;
if (data.headers && data.rows && data.rows.length > 0) {
// Populate table headers
data.headers.forEach((header) => {
data.headers.forEach((header, index) => {
const th = document.createElement('th');
th.textContent = header;
tableHead.appendChild(th);
// Track the defect code column (quality_code)
if (header === 'Defect Code' || header === 'Quality Code') {
defectCodeIndex = index;
}
});
// Populate table rows
@@ -57,8 +65,17 @@ document.addEventListener('DOMContentLoaded', () => {
row.forEach((cell, index) => {
const td = document.createElement('td');
// Use the cell data as-is since backend now handles formatting
td.textContent = cell;
// Special handling for defect code column
if (index === defectCodeIndex && (cell === 0 || cell === '0' || cell === '' || cell === null)) {
td.textContent = 'OK';
td.style.color = '#28a745'; // Green color for OK
td.style.fontWeight = '600';
td.setAttribute('data-csv-value', '0'); // Store original value for CSV
} else {
// Use the cell data as-is since backend now handles formatting
td.textContent = cell;
td.setAttribute('data-csv-value', cell || ''); // Store original value
}
tr.appendChild(td);
});
@@ -96,7 +113,11 @@ document.addEventListener('DOMContentLoaded', () => {
// Loop through each row in the table
rows.forEach((row) => {
const cells = row.querySelectorAll('th, td');
const rowData = Array.from(cells).map((cell) => `"${cell.textContent.trim()}"`);
const rowData = Array.from(cells).map((cell) => {
// Use data-csv-value attribute if available (for defect codes), otherwise use text content
const value = cell.hasAttribute('data-csv-value') ? cell.getAttribute('data-csv-value') : cell.textContent.trim();
return `"${value}"`;
});
csv.push(rowData.join(','));
});

View File

@@ -537,38 +537,40 @@ document.addEventListener('DOMContentLoaded', function() {
<!-- Latest Scans Card -->
<div class="card scan-table-card">
<h3>Latest Scans</h3>
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1 Code</th>
<th>OC2 Code</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Apr. Quantity</th>
<th>Rejec. Quantity</th>
</tr>
</thead>
<tbody>
{% for row in scan_data %}
<tr>
<td>{{ row[0] }}</td> <!-- Id -->
<td>{{ row[1] }}</td> <!-- operator_code -->
<td>{{ row[2] }}</td> <!-- CP_full_code -->
<td>{{ row[3] }}</td> <!-- OC1_code -->
<td>{{ row[4] }}</td> <!-- OC2_code -->
<td>{{ row[5] }}</td> <!-- quality_code -->
<td>{{ row[6] }}</td> <!-- date -->
<td>{{ row[7] }}</td> <!-- time -->
<td>{{ row[8] }}</td> <!-- approved quantity -->
<td>{{ row[9] }}</td> <!-- rejected quantity -->
</tr>
{% endfor %}
</tbody>
</table>
<div class="report-table-container">
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1 Code</th>
<th>OC2 Code</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Apr. Quantity</th>
<th>Rejec. Quantity</th>
</tr>
</thead>
<tbody>
{% for row in scan_data %}
<tr>
<td>{{ row[0] }}</td> <!-- Id -->
<td>{{ row[1] }}</td> <!-- operator_code -->
<td>{{ row[2] }}</td> <!-- CP_full_code -->
<td>{{ row[3] }}</td> <!-- OC1_code -->
<td>{{ row[4] }}</td> <!-- OC2_code -->
<td>{{ row[5] }}</td> <!-- quality_code -->
<td>{{ row[6] }}</td> <!-- date -->
<td>{{ row[7] }}</td> <!-- time -->
<td>{{ row[8] }}</td> <!-- approved quantity -->
<td>{{ row[9] }}</td> <!-- rejected quantity -->
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -513,22 +513,23 @@ document.addEventListener('DOMContentLoaded', function() {
<!-- Latest Scans Card -->
<div class="card scan-table-card">
<h3>Latest Scans</h3>
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1 Code</th>
<th>OC2 Code</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Apr. Quantity</th>
<th>Rejec. Quantity</th>
</tr>
</thead>
<tbody>
<div class="report-table-container">
<table class="scan-table">
<thead>
<tr>
<th>ID</th>
<th>Op Code</th>
<th>CP Code</th>
<th>OC1 Code</th>
<th>OC2 Code</th>
<th>Defect Code</th>
<th>Date</th>
<th>Time</th>
<th>Apr. Quantity</th>
<th>Rejec. Quantity</th>
</tr>
</thead>
<tbody>
{% for row in scan_data %}
<tr>
<td>{{ row[0] }}</td> <!-- Id -->
@@ -545,6 +546,7 @@ document.addEventListener('DOMContentLoaded', function() {
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}