Docker deployment improvements: fixed backup/restore, sticky headers, quality code display
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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, '""') + '"';
|
||||
|
||||
@@ -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(','));
|
||||
});
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user