diff --git a/public/script.js b/public/script.js
index 04af788..9d0db02 100644
--- a/public/script.js
+++ b/public/script.js
@@ -42,6 +42,25 @@ class FrontendApiClient {
throw new Error(`Request failed: ${error.message}`);
}
}
+
+ async getTasksStatus() {
+ try {
+ const response = await fetch('/api/tasks/status', {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ throw new Error(`Request failed: ${error.message}`);
+ }
+ }
}
// Global client instance
@@ -101,37 +120,167 @@ function displayNodeDetails(container, nodeStatus) {
console.log('Node status data:', nodeStatus);
container.innerHTML = `
-
-
Available API Endpoints:
- ${nodeStatus.api ? nodeStatus.api.map(endpoint =>
- `
${endpoint.method === 1 ? 'GET' : 'POST'} ${endpoint.uri}
`
- ).join('') : '
No API endpoints available
'}
+
+
+
+
+
+ Free Heap:
+ ${Math.round(nodeStatus.freeHeap / 1024)}KB
+
+
+ Chip ID:
+ ${nodeStatus.chipId}
+
+
+ SDK Version:
+ ${nodeStatus.sdkVersion}
+
+
+ CPU Frequency:
+ ${nodeStatus.cpuFreqMHz}MHz
+
+
+ Flash Size:
+ ${Math.round(nodeStatus.flashChipSize / 1024)}KB
+
+
+
+
+
Available API Endpoints:
+ ${nodeStatus.api ? nodeStatus.api.map(endpoint =>
+ `
${endpoint.method === 1 ? 'GET' : 'POST'} ${endpoint.uri}
`
+ ).join('') : '
No API endpoints available
'}
+
+
+
+
+
+
+
Firmware Update
+
+
+
+
Select a .bin or .hex file to upload
+
+
+
`;
+ // Set up tab switching
+ setupTabs(container);
+
+ // Load tasks data for the tasks tab
+ loadTasksData(container, nodeStatus);
+
console.log('Node details HTML set successfully');
}
+// Function to set up tab switching
+function setupTabs(container) {
+ const tabButtons = container.querySelectorAll('.tab-button');
+ const tabContents = container.querySelectorAll('.tab-content');
+
+ tabButtons.forEach(button => {
+ button.addEventListener('click', (e) => {
+ // Prevent the click event from bubbling up to the card
+ e.stopPropagation();
+
+ const targetTab = button.dataset.tab;
+
+ // Remove active class from all buttons and contents
+ tabButtons.forEach(btn => btn.classList.remove('active'));
+ tabContents.forEach(content => content.classList.remove('active'));
+
+ // Add active class to clicked button and corresponding content
+ button.classList.add('active');
+ const targetContent = container.querySelector(`#${targetTab}-tab`);
+ if (targetContent) {
+ targetContent.classList.add('active');
+ }
+ });
+ });
+
+ // Also prevent event propagation on tab content areas
+ tabContents.forEach(content => {
+ content.addEventListener('click', (e) => {
+ e.stopPropagation();
+ });
+ });
+
+ // Set up firmware upload button
+ const uploadBtn = container.querySelector('.upload-btn[data-action="select-file"]');
+ if (uploadBtn) {
+ uploadBtn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ const fileInput = container.querySelector('#firmware-file');
+ if (fileInput) {
+ fileInput.click();
+ }
+ });
+ }
+}
+
+// Function to load tasks data
+async function loadTasksData(container, nodeStatus) {
+ const tasksTab = container.querySelector('#tasks-tab');
+ if (!tasksTab) return;
+
+ try {
+ const response = await client.getTasksStatus();
+ console.log('Tasks data received:', response);
+
+ if (response && response.length > 0) {
+ const tasksHTML = response.map(task => `
+
+
+
+ Interval: ${task.interval}ms
+ ${task.enabled ? '🟢 Enabled' : '🔴 Disabled'}
+
+
+ `).join('');
+
+ tasksTab.innerHTML = `
+
Active Tasks
+ ${tasksHTML}
+ `;
+ } else {
+ tasksTab.innerHTML = `
+
+
📋 No active tasks found
+
+ This node has no running tasks
+
+
+ `;
+ }
+ } catch (error) {
+ console.error('Failed to load tasks:', error);
+ tasksTab.innerHTML = `
+
+ Error loading tasks:
+ ${error.message}
+
+ `;
+ }
+}
+
// Function to display cluster members
function displayClusterMembers(members) {
const container = document.getElementById('cluster-members-container');
diff --git a/public/styles.css b/public/styles.css
index 7c3614b..bc26dbe 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -283,6 +283,154 @@ p {
border: 1px solid rgba(255, 255, 255, 0.1);
}
+/* Tab Styles */
+.tabs-container {
+ margin-top: 1rem;
+}
+
+.tabs-header {
+ display: flex;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ margin-bottom: 1rem;
+ gap: 0.5rem;
+}
+
+.tab-button {
+ background: rgba(0, 0, 0, 0.3);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ color: rgba(255, 255, 255, 0.7);
+ padding: 0.5rem 1rem;
+ border-radius: 8px 8px 0 0;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: all 0.3s ease;
+ border-bottom: none;
+}
+
+.tab-button:hover {
+ background: rgba(0, 0, 0, 0.5);
+ color: rgba(255, 255, 255, 0.9);
+}
+
+.tab-button.active {
+ background: rgba(255, 255, 255, 0.1);
+ color: #ecf0f1;
+ border-color: rgba(255, 255, 255, 0.2);
+}
+
+.tab-content {
+ display: none;
+ padding: 1rem 0;
+}
+
+.tab-content.active {
+ display: block;
+}
+
+/* Task Styles */
+.task-item {
+ background: rgba(0, 0, 0, 0.2);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+ padding: 1rem;
+ margin-bottom: 0.75rem;
+}
+
+.task-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 0.5rem;
+}
+
+.task-name {
+ font-weight: 600;
+ color: #ecf0f1;
+}
+
+.task-status {
+ font-size: 0.85rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-weight: 500;
+}
+
+.task-status.running {
+ background: rgba(76, 175, 80, 0.2);
+ color: #4caf50;
+ border: 1px solid rgba(76, 175, 80, 0.3);
+}
+
+.task-status.stopped {
+ background: rgba(244, 67, 54, 0.2);
+ color: #f44336;
+ border: 1px solid rgba(244, 67, 54, 0.3);
+}
+
+.task-details {
+ display: flex;
+ gap: 1rem;
+ font-size: 0.8rem;
+ opacity: 0.8;
+}
+
+.task-interval, .task-enabled {
+ background: rgba(0, 0, 0, 0.2);
+ padding: 0.2rem 0.5rem;
+ border-radius: 4px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+/* Firmware Upload Styles */
+.firmware-upload h4 {
+ margin-bottom: 1rem;
+ color: #ecf0f1;
+}
+
+.upload-area {
+ text-align: center;
+ padding: 2rem;
+ border: 2px dashed rgba(255, 255, 255, 0.2);
+ border-radius: 12px;
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.upload-btn {
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ color: #ecf0f1;
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 1rem;
+ transition: all 0.3s ease;
+ margin-bottom: 1rem;
+}
+
+.upload-btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: translateY(-2px);
+}
+
+.upload-info {
+ font-size: 0.9rem;
+ opacity: 0.7;
+ color: rgba(255, 255, 255, 0.8);
+}
+
+.no-tasks {
+ text-align: center;
+ padding: 2rem;
+ opacity: 0.7;
+}
+
+.loading-tasks {
+ text-align: center;
+ padding: 1rem;
+ opacity: 0.7;
+ font-style: italic;
+}
+
.loading {
text-align: center;
padding: 2rem;