Adding Alarm management
This commit is contained in:
parent
1f42ce3ad2
commit
2590675d9c
114
index.html
114
index.html
@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
|
||||||
<title>Machine Description Form</title>
|
<title>Machine Description Form</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@ -116,14 +117,99 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
.tab-nav {
|
||||||
|
border-bottom: 2px solid #D4D9F0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tab-nav button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #6c757d;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
}
|
||||||
|
.tab-nav button.active {
|
||||||
|
color: #2649B2;
|
||||||
|
border-bottom-color: #2649B2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dynamic-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.dynamic-table th, .dynamic-table td {
|
||||||
|
border: 1px solid #D4D9F0;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.dynamic-table th {
|
||||||
|
background-color: #f4f4f9;
|
||||||
|
}
|
||||||
|
.dynamic-table input, .dynamic-table textarea {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.dynamic-table .col-id {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
.dynamic-table .col-name {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
.dynamic-table .col-actions {
|
||||||
|
width: 15%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.add-row-btn {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #6C8BE0;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.add-row-btn:hover {
|
||||||
|
background-color: #5a7dc2;
|
||||||
|
}
|
||||||
|
.remove-row-btn {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #e06c6c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.remove-row-btn:hover {
|
||||||
|
background-color: #c55a5a;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="renderer.js"></script>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Machine Description</h2>
|
<h2>Machine Description</h2>
|
||||||
|
|
||||||
|
<nav class="tab-nav">
|
||||||
|
<button class="tab-btn active" data-tab="machine" id="machine-tab-btn">Machine</button>
|
||||||
|
<button class="tab-btn" data-tab="alarms" id="alarms-tab-btn">Alarms</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<form id="machine-form">
|
<form id="machine-form">
|
||||||
|
|
||||||
|
<div class="tab-content" id="machine-tab" active>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Machine Details</legend>
|
<legend>Machine Details</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -279,10 +365,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div id="alarms-tab" class="tab-content">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Alarms Exposed by the Machine</legend>
|
||||||
|
<p>Define all alarms for the machine. If the message is empty, the alarm will not be generated.</p>
|
||||||
|
<table class="dynamic-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th class="col-id">ID</th><th class="col-name">Name</th><th>Message</th><th class="col-actions">Actions</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="alarms-table-body"></tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="add-row-btn" id="add-alarm-btn">Add Alarm</button>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<input type="submit" value="Validate and Submit Description">
|
<input type="submit" value="Validate and Submit Description">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template id="alarm-row-template">
|
||||||
|
<tr>
|
||||||
|
<td><input type="number" name="alarm_id" min="1" placeholder="e.g., 1"></td>
|
||||||
|
<td><input type="text" name="alarm_name" placeholder="e.g., ALARM_1"></td>
|
||||||
|
<td><textarea name="alarm_message" rows="1" cols="50" placeholder="Message displayed on dashboard"></textarea></td>
|
||||||
|
<td class="col-actions"><button type="button" class="remove-row-btn">Remove</button></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="renderer.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
43
main.js
43
main.js
@ -29,44 +29,55 @@ app.whenReady().then(() => {
|
|||||||
const sp2 = ' ';
|
const sp2 = ' ';
|
||||||
const sp4 = ' ';
|
const sp4 = ' ';
|
||||||
const sp6 = ' ';
|
const sp6 = ' ';
|
||||||
|
const sp8 = ' ';
|
||||||
|
|
||||||
// Create an array to hold each line of the YAML file
|
// Create an array to hold each line of the YAML file
|
||||||
const yamlLines = [];
|
const yamlLines = [];
|
||||||
|
|
||||||
// 1. Build the 'machine' section line by line
|
// Build the 'machine' section line by line
|
||||||
yamlLines.push('machine:');
|
|
||||||
yamlLines.push(`${sp2}profile: ${data.machineType || 'Unspecified'}`);
|
|
||||||
yamlLines.push(`${sp2}identification:`);
|
|
||||||
|
|
||||||
// Build the 'identification' sub-section, using the [VendorName] placeholder literally
|
|
||||||
const machineType = data.machineType || 'Unspecified';
|
const machineType = data.machineType || 'Unspecified';
|
||||||
const machineModel = data.model || '';
|
const machineModel = data.model || '';
|
||||||
const serialNo = data.serialNo || '';
|
const serialNo = data.serialNo || '';
|
||||||
const VendorName = data.vendor || '';
|
const VendorName = data.vendor || '';
|
||||||
|
yamlLines.push('machine:');
|
||||||
|
yamlLines.push(`${sp2}profile: ${machineType}`);
|
||||||
|
yamlLines.push(`${sp2}identification:`);
|
||||||
yamlLines.push(`${sp4}namespace: http://${VendorName}/${machineType}/${machineModel}/${serialNo}`);
|
yamlLines.push(`${sp4}namespace: http://${VendorName}/${machineType}/${machineModel}/${serialNo}`);
|
||||||
yamlLines.push(`${sp4}browseName: ${data.browseName || ''}`);
|
yamlLines.push(`${sp4}browseName: ${data.browseName || ''}`);
|
||||||
yamlLines.push(`${sp4}manufacturer: "[${VendorName}]"`);
|
yamlLines.push(`${sp4}manufacturer: "[${VendorName}]"`);
|
||||||
|
|
||||||
// Append the user-provided vendor as a comment, if it exists
|
|
||||||
if (data.vendor) {
|
|
||||||
yamlLines.push(`${sp4}# User-provided Vendor: ${data.vendor}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlLines.push(`${sp4}model: "${machineModel}"`);
|
yamlLines.push(`${sp4}model: "${machineModel}"`);
|
||||||
yamlLines.push(`${sp4}serialNumber: "${serialNo}"`);
|
yamlLines.push(`${sp4}serialNumber: "${serialNo}"`);
|
||||||
yamlLines.push(`${sp4}yearofconstruction: "${data.buildYear || ''}"`);
|
yamlLines.push(`${sp4}yearofconstruction: "${data.buildYear || ''}"`);
|
||||||
|
|
||||||
// Add the final line for the 'machine' section
|
|
||||||
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
|
yamlLines.push(`${sp2}version: "${data.version || 'Unset'}"`);
|
||||||
|
|
||||||
// 2. Build and Append the 'optionals' section if needed
|
// 2. Build the 'alarms' section from the alarms data
|
||||||
|
if (data.alarms && data.alarms.length > 0) {
|
||||||
|
yamlLines.push(''); // Blank line for separation
|
||||||
|
yamlLines.push(`${sp2}alarms:`);
|
||||||
|
yamlLines.push(`${sp4}packing: OBJECTS`); // As per PowerShell script logic
|
||||||
|
yamlLines.push(`${sp4}behavior: ${data.alarmBehavior || 'STATIC'}`);
|
||||||
|
yamlLines.push(`${sp4}count: ${data.alarms.length}`);
|
||||||
|
yamlLines.push(`${sp4}alarms: `); // Note the trailing space
|
||||||
|
|
||||||
|
data.alarms.forEach(alarm => {
|
||||||
|
const id = alarm.id || 0;
|
||||||
|
const name = alarm.name || `Alarm_${id}`;
|
||||||
|
const message = alarm.message.replace(/:/g, ';'); // Sanitize message as in PowerShell
|
||||||
|
|
||||||
|
yamlLines.push(`${sp6}- ID: ${id}`);
|
||||||
|
yamlLines.push(`${sp8}name: ${name}`);
|
||||||
|
yamlLines.push(`${sp8}message: ${message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and Append the 'optionals' section if needed
|
||||||
if (data.stopReason === 'Yes') {
|
if (data.stopReason === 'Yes') {
|
||||||
yamlLines.push(''); // Add a blank line for spacing
|
yamlLines.push(''); // Add a blank line for spacing
|
||||||
yamlLines.push(`${sp2}optionals:`);
|
yamlLines.push(`${sp2}optionals:`);
|
||||||
yamlLines.push(`${sp4}- StopReason`);
|
yamlLines.push(`${sp4}- StopReason`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Build and Append the 'addins' section if needed
|
// Build and Append the 'addins' section if needed
|
||||||
const selectedAddins = data.addins || []; // Default to an empty array
|
const selectedAddins = data.addins || []; // Default to an empty array
|
||||||
|
|
||||||
if (selectedAddins.length > 0) {
|
if (selectedAddins.length > 0) {
|
||||||
|
|||||||
111
renderer.js
111
renderer.js
@ -4,59 +4,102 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
console.log('DOM fully loaded. renderer.js is now running.');
|
console.log('DOM fully loaded. renderer.js is now running.');
|
||||||
|
|
||||||
// --- Real-time Filename Generation ---
|
// --- 0. INITIALIZE FORM DEFAULTS ---
|
||||||
const browseNameInput = document.getElementById('BrowseName');
|
|
||||||
const filenameDisplay = document.getElementById('output-filename-display');
|
|
||||||
|
|
||||||
const updateFilename = () => {
|
|
||||||
const browseName = browseNameInput.value;
|
|
||||||
if (browseName) {
|
|
||||||
// Sanitize the name for the file system
|
|
||||||
const safeBrowseName = browseName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
||||||
filenameDisplay.textContent = `${safeBrowseName}.yml`;
|
|
||||||
} else {
|
|
||||||
filenameDisplay.textContent = ''; // Clear if BrowseName is empty
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (browseNameInput && filenameDisplay) {
|
|
||||||
// Listen for any input in the BrowseName field and update the display
|
|
||||||
browseNameInput.addEventListener('input', updateFilename);
|
|
||||||
// Initial call to set the name if the field is pre-filled
|
|
||||||
updateFilename();
|
|
||||||
}
|
|
||||||
|
|
||||||
const publicationDateInput = document.getElementById('publicationDate');
|
const publicationDateInput = document.getElementById('publicationDate');
|
||||||
if (publicationDateInput) {
|
if (publicationDateInput) {
|
||||||
publicationDateInput.value = new Date().toISOString().split('T')[0];
|
publicationDateInput.value = new Date().toISOString().split('T')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- 1. TAB SWITCHING LOGIC ---
|
||||||
|
const tabButtons = document.querySelectorAll('.tab-btn');
|
||||||
|
const tabContents = document.querySelectorAll('.tab-content');
|
||||||
|
|
||||||
|
tabButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||||
|
tabContents.forEach(content => content.classList.remove('active'));
|
||||||
|
button.classList.add('active');
|
||||||
|
document.getElementById(button.dataset.tab + '-tab').classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Set the initial active tab content
|
||||||
|
document.getElementById('machine-tab').classList.add('active');
|
||||||
|
|
||||||
|
// --- 2. DYNAMIC ALARM ROW LOGIC ---
|
||||||
|
const addAlarmBtn = document.getElementById('add-alarm-btn');
|
||||||
|
const alarmsTableBody = document.getElementById('alarms-table-body');
|
||||||
|
const alarmTemplate = document.getElementById('alarm-row-template');
|
||||||
|
|
||||||
|
addAlarmBtn.addEventListener('click', () => {
|
||||||
|
const currentIdInputs = alarmsTableBody.querySelectorAll('[name="alarm_id"]');
|
||||||
|
let maxId = 0;
|
||||||
|
currentIdInputs.forEach(input => {
|
||||||
|
const currentId = parseInt(input.value, 10);
|
||||||
|
if (!isNaN(currentId) && currentId > maxId) {
|
||||||
|
maxId = currentId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const nextId = maxId + 1;
|
||||||
|
const paddedId = nextId.toString().padStart(3, '0');
|
||||||
|
|
||||||
|
const clone = alarmTemplate.content.cloneNode(true);
|
||||||
|
clone.querySelector('[name="alarm_id"]').value = nextId;
|
||||||
|
clone.querySelector('[name="alarm_name"]').value = `ALARM_${paddedId}`;
|
||||||
|
const removeBtn = clone.querySelector('.remove-row-btn');
|
||||||
|
removeBtn.addEventListener('click', (e) => {
|
||||||
|
e.target.closest('tr').remove();
|
||||||
|
});
|
||||||
|
alarmsTableBody.appendChild(clone);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// --- 3. FORM SUBMISSION LOGIC ---
|
||||||
const form = document.getElementById('machine-form');
|
const form = document.getElementById('machine-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', async (event) => {
|
form.addEventListener('submit', async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
// A. Collect data from the main "Machine" tab using FormData
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
// Convert FormData to a plain object
|
||||||
// The FormData API handles checkboxes and dropdowns with the same name identically.
|
const mainData = {};
|
||||||
// We just need to make sure the data object is built correctly.
|
|
||||||
const data = {};
|
|
||||||
for (const [key, value] of formData.entries()) {
|
for (const [key, value] of formData.entries()) {
|
||||||
// This check prevents multi-value fields from overwriting themselves
|
// This check prevents multi-value fields from overwriting themselves
|
||||||
if (!data[key]) {
|
if (!mainData[key]) {
|
||||||
data[key] = value;
|
mainData[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Use .getAll('addins') to get an array of all CHECKED addins
|
mainData.addins = formData.getAll('addins');
|
||||||
data.addins = formData.getAll('addins');
|
|
||||||
|
|
||||||
console.log('Collected form data:', data);
|
// B. Collect all alarm rows from the table
|
||||||
|
const alarmRows = alarmsTableBody.querySelectorAll('tr');
|
||||||
|
const alarmsData = [];
|
||||||
|
alarmRows.forEach(row => {
|
||||||
|
const alarm = {
|
||||||
|
id: row.querySelector('[name="alarm_id"]').value,
|
||||||
|
name: row.querySelector('[name="alarm_name"]').value,
|
||||||
|
message: row.querySelector('[name="alarm_message"]').value,
|
||||||
|
};
|
||||||
|
if (alarm.message) {
|
||||||
|
alarmsData.push(alarm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// C. Combine all data into a single object
|
||||||
|
const finalData = { ...mainData, alarms: alarmsData };
|
||||||
|
|
||||||
|
console.log('Sending all collected data to main process:', finalData);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.electronAPI.submitForm(data);
|
const result = await window.electronAPI.submitForm(finalData);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
form.reset();
|
// // Reset the entire form to its initial state
|
||||||
// ... (reset logic remains the same)
|
// form.reset();
|
||||||
|
// // Re-apply the defaults that are not part of the standard reset
|
||||||
|
// document.getElementById('publicationDate').value = new Date().toISOString().split('T')[0];
|
||||||
|
// document.querySelector('#stopReasonYes').checked = true; // Set StopReason back to Yes
|
||||||
|
// updateFilename(); // Clear the filename preview
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during form submission process:', error);
|
console.error('Error during form submission process:', error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user