+ π CSV Recording Configuration
+
+
π Directory Management: Configure where CSV files are saved and manage file
+ rotation
+
π Automatic Cleanup: Set limits by size, days, or hours to automatically delete old
+ files
+
πΏ Disk Space: Monitor available space and estimated recording time remaining
+
+
+
+
+
+
+ π Records Directory:
+ Loading...
+
+
+ π Rotation Enabled:
+ Loading...
+
+
+ π Max Size:
+ Loading...
+
+
+ π
Max Days:
+ Loading...
+
+
+ β° Max Hours:
+ Loading...
+
+
+ π§Ή Cleanup Interval:
+ Loading...
+
+
+
+
+
+
+
+ π Directory Information
+
+
Loading directory information...
+
+
+
+
+
+
+ βοΈ Modify Configuration
+
+
+
π Application Events Log
@@ -863,7 +1127,24 @@
// Function to display messages
function showMessage(message, type = 'success') {
const messagesDiv = document.getElementById('messages');
- const alertClass = type === 'success' ? 'alert-success' : 'alert-error';
+ let alertClass;
+
+ switch (type) {
+ case 'success':
+ alertClass = 'alert-success';
+ break;
+ case 'warning':
+ alertClass = 'alert-warning';
+ break;
+ case 'info':
+ alertClass = 'alert-info';
+ break;
+ case 'error':
+ default:
+ alertClass = 'alert-error';
+ break;
+ }
+
messagesDiv.innerHTML = `${message}
`;
setTimeout(() => {
messagesDiv.innerHTML = '';
@@ -1128,6 +1409,350 @@
});
}
+ // Refresh variable values from PLC
+ function refreshVariableValues() {
+ if (!currentDatasetId) {
+ showMessage('No dataset selected', 'error');
+ return;
+ }
+
+ const refreshBtn = document.getElementById('refresh-values-btn');
+ const lastRefreshTime = document.getElementById('last-refresh-time');
+
+ // Disable button and show loading state
+ refreshBtn.disabled = true;
+ refreshBtn.innerHTML = 'β³ Reading...';
+
+ fetch(`/api/datasets/${currentDatasetId}/variables/values`)
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ // Update variable values in the table
+ Object.keys(data.values).forEach(varName => {
+ const valueCell = document.getElementById(`value-${varName}`);
+ if (valueCell) {
+ const value = data.values[varName];
+ valueCell.textContent = value;
+
+ // Color coding and tooltip based on value status
+ if (value === 'ERROR' || value === 'FORMAT_ERROR') {
+ valueCell.style.color = 'var(--pico-color-red-500)';
+
+ // Add tooltip with detailed error if available
+ const errorDetail = data.detailed_errors && data.detailed_errors[varName];
+ if (errorDetail) {
+ valueCell.title = `Error: ${errorDetail}`;
+ valueCell.style.cursor = 'help';
+ }
+ } else {
+ valueCell.style.color = 'var(--pico-color-green-600)';
+ valueCell.title = `Value: ${value}`;
+ valueCell.style.cursor = 'default';
+ }
+ }
+ });
+
+ // Update timestamp, stats, and source information
+ if (data.timestamp) {
+ const stats = data.stats;
+ const source = data.source || 'unknown';
+ const isCache = data.is_cached;
+
+ // Create source indicator
+ let sourceIcon = '';
+ let sourceText = '';
+ if (isCache) {
+ sourceIcon = 'π';
+ sourceText = 'from streaming cache';
+ } else if (source === 'plc_direct') {
+ sourceIcon = 'π';
+ sourceText = 'direct PLC read';
+ } else {
+ sourceIcon = 'β';
+ sourceText = 'unknown source';
+ }
+
+ if (stats && stats.failed > 0) {
+ lastRefreshTime.innerHTML = `
+ Last refresh: ${data.timestamp}
+
+ β οΈ ${stats.success}/${stats.total} variables read (${stats.failed} failed)
+
+
+ ${sourceIcon} ${sourceText}
+
+ `;
+ } else {
+ lastRefreshTime.innerHTML = `
+ Last refresh: ${data.timestamp}
+
+ β
All ${stats ? stats.success : 'N/A'} variables read successfully
+
+
+ ${sourceIcon} ${sourceText}
+
+ `;
+ }
+ }
+
+ // Show appropriate message
+ if (data.warning) {
+ showMessage(data.warning, 'warning');
+ // Show detailed error information in console for debugging
+ if (data.detailed_errors && Object.keys(data.detailed_errors).length > 0) {
+ console.warn('Variable read errors:', data.detailed_errors);
+ }
+ } else {
+ showMessage(data.message, 'success');
+ }
+
+ } else {
+ // Complete failure case
+ showMessage(data.message, 'error');
+ clearVariableValues('ERROR');
+
+ // Update timestamp with error info
+ const source = data.source || 'unknown';
+ const isCache = data.is_cached;
+
+ let sourceIcon = '';
+ let sourceText = '';
+ if (isCache) {
+ sourceIcon = 'π';
+ sourceText = 'from streaming cache';
+ } else if (source === 'plc_direct') {
+ sourceIcon = 'π';
+ sourceText = 'direct PLC read';
+ } else {
+ sourceIcon = 'β';
+ sourceText = 'unknown source';
+ }
+
+ lastRefreshTime.innerHTML = `
+ Last refresh attempt: ${data.timestamp}
+
+ β Failed to read any variables
+
+
+ ${sourceIcon} ${sourceText}
+
+ `;
+
+ // Show detailed error information if available
+ if (data.detailed_errors && Object.keys(data.detailed_errors).length > 0) {
+ console.error('Detailed variable errors:', data.detailed_errors);
+
+ // Create a detailed error message for the user
+ const errorSummary = Object.keys(data.detailed_errors).map(varName =>
+ `${varName}: ${data.detailed_errors[varName]}`
+ ).join('\n');
+
+ // Show in console and optionally as a detailed error dialog
+ console.error('Variable Error Details:\n' + errorSummary);
+ }
+
+ // Handle specific error types
+ if (data.error_type === 'connection_error') {
+ clearVariableValues('PLC OFFLINE');
+ } else if (data.error_type === 'all_failed') {
+ clearVariableValues('READ FAILED');
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error refreshing variable values:', error);
+ showMessage('Network error reading variable values from PLC', 'error');
+ clearVariableValues('COMM ERROR');
+
+ lastRefreshTime.innerHTML = `
+ Last refresh attempt: ${new Date().toLocaleString()}
+
+ β Network/Communication error
+
+ `;
+ })
+ .finally(() => {
+ // Re-enable button
+ refreshBtn.disabled = false;
+ refreshBtn.innerHTML = 'π Refresh Values';
+ });
+ }
+
+ // Clear all variable values and set status message
+ function clearVariableValues(statusMessage = '--') {
+ // Find all value cells and clear them
+ const valueCells = document.querySelectorAll('[id^="value-"]');
+ valueCells.forEach(cell => {
+ cell.textContent = statusMessage;
+ cell.style.color = 'var(--pico-muted-color)';
+ });
+ }
+
+ // Auto-refresh values when dataset changes (optional)
+ function autoRefreshOnDatasetChange() {
+ if (currentDatasetId) {
+ // Small delay to ensure table is loaded
+ setTimeout(() => {
+ refreshVariableValues();
+ }, 500);
+ }
+ }
+
+ // Diagnostic function for connection and variable issues
+ function diagnoseConnection() {
+ if (!currentDatasetId) {
+ showMessage('No dataset selected for diagnosis', 'error');
+ return;
+ }
+
+ const diagnoseBtn = document.getElementById('diagnose-btn');
+ const originalText = diagnoseBtn.innerHTML;
+
+ // Disable button and show diagnostic state
+ diagnoseBtn.disabled = true;
+ diagnoseBtn.innerHTML = 'π Diagnosing...';
+
+ // Create diagnostic report
+ let diagnosticReport = [];
+
+ diagnosticReport.push('=== PLC CONNECTION DIAGNOSTICS ===');
+ diagnosticReport.push(`Dataset: ${currentDatasetId}`);
+ diagnosticReport.push(`Timestamp: ${new Date().toLocaleString()}`);
+ diagnosticReport.push('');
+
+ // Step 1: Check PLC connection status
+ fetch('/api/status')
+ .then(response => response.json())
+ .then(statusData => {
+ diagnosticReport.push('1. PLC Connection Status:');
+ diagnosticReport.push(` Connected: ${statusData.plc_connected ? 'YES' : 'NO'}`);
+ diagnosticReport.push(` PLC IP: ${statusData.plc_config.ip}`);
+ diagnosticReport.push(` Rack: ${statusData.plc_config.rack}`);
+ diagnosticReport.push(` Slot: ${statusData.plc_config.slot}`);
+ diagnosticReport.push('');
+
+ if (!statusData.plc_connected) {
+ diagnosticReport.push(' β PLC is not connected. Please check:');
+ diagnosticReport.push(' - Network connectivity to PLC');
+ diagnosticReport.push(' - PLC IP address, rack, and slot configuration');
+ diagnosticReport.push(' - PLC is powered on and operational');
+ diagnosticReport.push('');
+ showDiagnosticResults(diagnosticReport);
+ return;
+ }
+
+ // Step 2: Get dataset information
+ return fetch('/api/datasets')
+ .then(response => response.json())
+ .then(datasetData => {
+ const dataset = datasetData.datasets[currentDatasetId];
+ if (!dataset) {
+ diagnosticReport.push('2. Dataset Status:');
+ diagnosticReport.push(' β Dataset not found');
+ showDiagnosticResults(diagnosticReport);
+ return;
+ }
+
+ diagnosticReport.push('2. Dataset Information:');
+ diagnosticReport.push(` Name: ${dataset.name}`);
+ diagnosticReport.push(` Variables: ${Object.keys(dataset.variables).length}`);
+ diagnosticReport.push(` Active: ${dataset.enabled ? 'YES' : 'NO'}`);
+ diagnosticReport.push('');
+
+ // Step 3: Test variable reading with diagnostics
+ diagnosticReport.push('3. Variable Reading Test:');
+ return fetch(`/api/datasets/${currentDatasetId}/variables/values`)
+ .then(response => response.json())
+ .then(valueData => {
+ if (valueData.success) {
+ const stats = valueData.stats || {};
+ diagnosticReport.push(` β
Success: ${stats.success || 0}/${stats.total || 0} variables read`);
+
+ if (stats.failed > 0) {
+ diagnosticReport.push(` β οΈ Failed: ${stats.failed} variables had errors`);
+ diagnosticReport.push('');
+ diagnosticReport.push('4. Variable-Specific Errors:');
+
+ if (valueData.detailed_errors) {
+ Object.keys(valueData.detailed_errors).forEach(varName => {
+ diagnosticReport.push(` ${varName}: ${valueData.detailed_errors[varName]}`);
+ });
+ }
+ } else {
+ diagnosticReport.push(' β
All variables read successfully');
+ }
+ } else {
+ diagnosticReport.push(` β Complete failure: ${valueData.message}`);
+ diagnosticReport.push('');
+ diagnosticReport.push('4. Detailed Error Information:');
+
+ if (valueData.detailed_errors) {
+ Object.keys(valueData.detailed_errors).forEach(varName => {
+ diagnosticReport.push(` ${varName}: ${valueData.detailed_errors[varName]}`);
+ });
+ }
+
+ diagnosticReport.push('');
+ diagnosticReport.push('5. Troubleshooting Suggestions:');
+ if (valueData.error_type === 'connection_error') {
+ diagnosticReport.push(' - Check PLC network connection');
+ diagnosticReport.push(' - Verify PLC is responding to network requests');
+ diagnosticReport.push(' - Check firewall settings');
+ } else if (valueData.error_type === 'all_failed') {
+ diagnosticReport.push(' - Verify variable memory addresses are correct');
+ diagnosticReport.push(' - Check if data blocks exist in PLC program');
+ diagnosticReport.push(' - Ensure variable types match PLC configuration');
+ }
+ }
+
+ showDiagnosticResults(diagnosticReport);
+ });
+ });
+ })
+ .catch(error => {
+ diagnosticReport.push('β Diagnostic failed with network error:');
+ diagnosticReport.push(` ${error.message}`);
+ diagnosticReport.push('');
+ diagnosticReport.push('Troubleshooting:');
+ diagnosticReport.push(' - Check web server connection');
+ diagnosticReport.push(' - Refresh the page and try again');
+ showDiagnosticResults(diagnosticReport);
+ })
+ .finally(() => {
+ // Re-enable button
+ diagnoseBtn.disabled = false;
+ diagnoseBtn.innerHTML = originalText;
+ });
+ }
+
+ // Show diagnostic results in console and as a message
+ function showDiagnosticResults(diagnosticReport) {
+ const reportText = diagnosticReport.join('\n');
+
+ // Log to console for detailed analysis
+ console.log(reportText);
+
+ // Show summary message to user
+ const errorCount = reportText.match(/β/g)?.length || 0;
+ const warningCount = reportText.match(/β οΈ/g)?.length || 0;
+ const successCount = reportText.match(/β
/g)?.length || 0;
+
+ let summaryMessage = 'Diagnosis completed. ';
+ if (errorCount > 0) {
+ summaryMessage += `${errorCount} errors found. `;
+ }
+ if (warningCount > 0) {
+ summaryMessage += `${warningCount} warnings found. `;
+ }
+ if (successCount > 0) {
+ summaryMessage += `${successCount} checks passed. `;
+ }
+ summaryMessage += 'Check browser console (F12) for detailed report.';
+
+ const messageType = errorCount > 0 ? 'error' : (warningCount > 0 ? 'warning' : 'success');
+ showMessage(summaryMessage, messageType);
+ }
+
// Load streaming variables status
function loadStreamingStatus() {
fetch('/api/variables/streaming')
@@ -1548,14 +2173,14 @@
// Update dataset information display
function updateDatasetInfo() {
- const infoSection = document.getElementById('dataset-info');
- const actionsSection = document.getElementById('dataset-actions');
- const variablesSection = document.getElementById('variables-section');
+ const statusBar = document.getElementById('dataset-status-bar');
+ const variablesManagement = document.getElementById('variables-management');
+ const noDatasetMessage = document.getElementById('no-dataset-message');
if (currentDatasetId && currentDatasets[currentDatasetId]) {
const dataset = currentDatasets[currentDatasetId];
- // Show dataset info
+ // Show dataset info in status bar
document.getElementById('dataset-name').textContent = dataset.name;
document.getElementById('dataset-prefix').textContent = dataset.prefix;
document.getElementById('dataset-sampling').textContent =
@@ -1574,19 +2199,16 @@
document.getElementById('deactivate-dataset-btn').style.display = isActive ? 'inline-block' : 'none';
// Show sections
- infoSection.style.display = 'block';
- actionsSection.style.display = 'block';
- variablesSection.style.display = 'block';
-
- // Update variables section title
- document.getElementById('current-dataset-title').textContent = dataset.name;
+ statusBar.style.display = 'block';
+ variablesManagement.style.display = 'block';
+ noDatasetMessage.style.display = 'none';
// Load variables for this dataset
loadDatasetVariables(currentDatasetId);
} else {
- infoSection.style.display = 'none';
- actionsSection.style.display = 'none';
- variablesSection.style.display = 'none';
+ statusBar.style.display = 'none';
+ variablesManagement.style.display = 'none';
+ noDatasetMessage.style.display = 'block';
}
}
@@ -1639,6 +2261,9 @@
${memoryAreaDisplay} |
${variable.offset} |
${variable.type.toUpperCase()} |
+
+ --
+ |
|