This commit is contained in:
rohit 2025-08-03 23:14:42 +05:30
parent ae4842eb75
commit 9f4258d97f
25 changed files with 22748 additions and 1 deletions

@ -1 +0,0 @@
Subproject commit fdb7a83ad9fbffbccd2ef31c9864191a0aa6ea40

23
iot-dashboard/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

46
iot-dashboard/README.md Normal file
View File

@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

17684
iot-dashboard/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
{
"name": "iot-dashboard",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/chart.js": "^2.9.41",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.126",
"@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7",
"@types/react-router-dom": "^5.3.3",
"chart.js": "^4.5.0",
"react": "^19.1.1",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.1.1",
"react-router-dom": "^6.30.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

1248
iot-dashboard/reference.html Normal file

File diff suppressed because it is too large Load Diff

1162
iot-dashboard/src/App.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

65
iot-dashboard/src/App.tsx Normal file
View File

@ -0,0 +1,65 @@
import React, { useState } from 'react';
import './App.css';
import Header from './components/Header';
import Sidebar from './components/Sidebar';
import Dashboard from './components/Dashboard';
import Alerts from './components/Alerts';
import Suggestions from './components/Suggestions';
import Healing from './components/Healing';
import Devices from './components/Devices';
import Analytics from './components/Analytics';
import Settings from './components/Settings';
function App() {
const [currentPage, setCurrentPage] = useState('dashboard');
const [sidebarOpen, setSidebarOpen] = useState(false);
const handleMenuToggle = () => {
setSidebarOpen(!sidebarOpen);
};
const handlePageChange = (page: string) => {
setCurrentPage(page);
// Close sidebar on mobile when page changes
if (window.innerWidth <= 768) {
setSidebarOpen(false);
}
};
const renderPage = () => {
switch (currentPage) {
case 'dashboard':
return <Dashboard />;
case 'alerts':
return <Alerts />;
case 'suggestions':
return <Suggestions />;
case 'healing':
return <Healing />;
case 'devices':
return <Devices />;
case 'analytics':
return <Analytics />;
case 'settings':
return <Settings />;
default:
return <Dashboard />;
}
};
return (
<div className="dashboard-container">
<Header onMenuToggle={handleMenuToggle} />
<Sidebar
currentPage={currentPage}
setCurrentPage={handlePageChange}
isOpen={sidebarOpen}
/>
<main className="main-content">
{renderPage()}
</main>
</div>
);
}
export default App;

View File

@ -0,0 +1,76 @@
import React from 'react';
const Alerts: React.FC = () => {
return (
<div>
<h1 className="page-title">Real-time Alerts Management</h1>
<div className="dashboard-grid">
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Active Alerts</h3>
<div className="card-icon alerts">
<i className="fas fa-exclamation-triangle"></i>
</div>
</div>
<div>
<div className="alert-item critical">
<div className="alert-icon">
<i className="fas fa-thermometer-three-quarters"></i>
</div>
<div className="alert-content">
<div className="alert-title">Critical Temperature Alert</div>
<div className="alert-time">Boiler Unit #3 - Temperature: 85°C (Threshold: 75°C) - 2 min ago</div>
</div>
<button className="action-btn danger">Acknowledge</button>
</div>
<div className="alert-item warning">
<div className="alert-icon" style={{ background: '#ffa502' }}>
<i className="fas fa-bolt"></i>
</div>
<div className="alert-content">
<div className="alert-title">Power Fluctuation Warning</div>
<div className="alert-time">Generator A - Voltage: 215V (Normal: 220V±5V) - 5 min ago</div>
</div>
<button className="action-btn">Investigate</button>
</div>
<div className="alert-item info">
<div className="alert-icon" style={{ background: '#00d4ff' }}>
<i className="fas fa-wifi"></i>
</div>
<div className="alert-content">
<div className="alert-title">Connection Restored</div>
<div className="alert-time">Sensor Array B - Connection re-established - 8 min ago</div>
</div>
<button className="action-btn success">Clear</button>
</div>
</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Alert Statistics</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #a55eea, #26de81)' }}>
<i className="fas fa-chart-pie"></i>
</div>
</div>
<div style={{ marginTop: '20px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
<span>Critical</span>
<span style={{ color: '#ff4757' }}>12</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
<span>Warning</span>
<span style={{ color: '#ffa502' }}>28</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
<span>Info</span>
<span style={{ color: '#00d4ff' }}>45</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default Alerts;

View File

@ -0,0 +1,95 @@
import React, { useEffect, useRef } from 'react';
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
const Analytics: React.FC = () => {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null);
useEffect(() => {
if (chartRef.current) {
const ctx = chartRef.current.getContext('2d');
if (ctx) {
// Destroy existing chart if it exists
if (chartInstance.current) {
chartInstance.current.destroy();
}
const analyticsData = {
labels: ['Sensor A1', 'Monitor B2', 'Gauge C3', 'Detector D4', 'Air Quality E5'],
datasets: [{
label: 'Data Packets (k)',
data: [120, 95, 60, 150, 110],
backgroundColor: [
'rgba(0,212,255,0.7)',
'rgba(255,165,2,0.7)',
'rgba(255,71,87,0.7)',
'rgba(46,213,115,0.7)',
'rgba(165,94,234,0.7)'
],
borderRadius: 8,
barPercentage: 0.6,
categoryPercentage: 0.6
}]
};
chartInstance.current = new Chart(ctx, {
type: 'bar',
data: analyticsData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: {
beginAtZero: true,
ticks: { color: '#fff' },
grid: { color: '#333' }
},
x: {
ticks: { color: '#fff' },
grid: { color: '#333' }
}
}
}
});
}
}
// Cleanup function
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}, []);
return (
<div>
<h1 className="page-title">Device Analytics</h1>
<div className="dashboard-grid">
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Device Data Analysis</h3>
<div className="card-icon analytics">
<i className="fas fa-chart-bar"></i>
</div>
</div>
<ul style={{ marginTop: '20px', color: '#fff' }}>
<li>Most Active Device: <span style={{ color: '#00d4ff' }}>Sensor A1</span></li>
<li>Peak Load Time: <span style={{ color: '#ffa502' }}>14:00 - 15:00</span></li>
<li>Average Response Time: <span style={{ color: '#2ed573' }}>120ms</span></li>
<li>Data Packets Sent (24h): <span style={{ color: '#a55eea' }}>1,234,567</span></li>
</ul>
<div style={{ marginTop: '30px', height: '300px', position: 'relative' }}>
<canvas ref={chartRef}></canvas>
</div>
</div>
</div>
</div>
);
};
export default Analytics;

View File

@ -0,0 +1,143 @@
import React, { useEffect, useRef } from 'react';
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
const Dashboard: React.FC = () => {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null);
useEffect(() => {
if (chartRef.current) {
const ctx = chartRef.current.getContext('2d');
if (ctx) {
// Destroy existing chart if it exists
if (chartInstance.current) {
chartInstance.current.destroy();
}
const chartData = {
labels: Array.from({ length: 30 }, (_, i) => ''),
datasets: [
{
label: 'Temperature (°C)',
data: Array.from({ length: 30 }, () => Math.floor(Math.random() * 10) + 20),
borderColor: '#ff6384',
backgroundColor: 'rgba(255,99,132,0.08)',
tension: 0.4,
fill: true,
}
]
};
chartInstance.current = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: {
color: '#fff',
font: { size: 14 }
}
}
},
scales: {
y: {
min: 10,
max: 40,
ticks: { color: '#fff' },
grid: { color: '#333' }
},
x: { display: false }
}
}
});
// Update chart data every 1.5 seconds
const interval = setInterval(() => {
if (chartInstance.current) {
const newData = Math.floor(Math.random() * 10) + 20;
chartInstance.current.data.datasets[0].data.push(newData);
chartInstance.current.data.datasets[0].data.shift();
chartInstance.current.update();
}
}, 1500);
return () => {
clearInterval(interval);
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}
}
}, []);
return (
<div>
<h1 className="page-title">Industrial IoT Dashboard</h1>
<div className="dashboard-grid">
{/* System Overview Card */}
<div className="card">
<div className="card-header">
<h3 className="card-title">System Overview</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #00d4ff, #0078ff)' }}>
<i className="fas fa-chart-line"></i>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginTop: '20px' }}>
<div style={{ textAlign: 'center', padding: '15px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px' }}>
<div className="metric-value">98.7%</div>
<div className="metric-label">System Uptime</div>
</div>
<div style={{ textAlign: 'center', padding: '15px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px' }}>
<div className="metric-value">156</div>
<div className="metric-label">Active Devices</div>
</div>
</div>
</div>
{/* Recent Alerts Preview */}
<div className="card">
<div className="card-header">
<h3 className="card-title">Recent Alerts</h3>
<div className="card-icon alerts">
<i className="fas fa-bell"></i>
</div>
</div>
<div>
<div className="alert-item critical">
<div className="alert-icon">
<i className="fas fa-thermometer-three-quarters"></i>
</div>
<div className="alert-content">
<div className="alert-title">Critical Temperature</div>
<div className="alert-time">Boiler Unit #3 - 2 min ago</div>
</div>
</div>
</div>
</div>
{/* Real-time Performance Chart Card */}
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Real-time Performance Chart</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #2ed573, #1e90ff)' }}>
<i className="fas fa-chart-area"></i>
</div>
</div>
<div style={{ height: '300px', background: 'rgba(255,255,255,0.03)', borderRadius: '10px', marginTop: '20px', display: 'flex', alignItems: 'center', position: 'relative' }}>
<canvas ref={chartRef}></canvas>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;

View File

@ -0,0 +1,527 @@
import React, { useState } from 'react';
interface Device {
id: number;
name: string;
type: string;
status: 'online' | 'offline' | 'maintenance' | 'error';
lastSeen: string;
ipAddress: string;
location: string;
firmware: string;
temperature?: number;
humidity?: number;
pressure?: number;
battery?: number;
signal?: number;
}
const Devices: React.FC = () => {
const [devices, setDevices] = useState<Device[]>([
{
id: 1,
name: 'Sensor A1',
type: 'Temperature',
status: 'online',
lastSeen: '2 min ago',
ipAddress: '192.168.1.101',
location: 'Boiler Room A',
firmware: 'v2.1.4',
temperature: 75.2,
battery: 87
},
{
id: 2,
name: 'Monitor B2',
type: 'Humidity',
status: 'maintenance',
lastSeen: '10 min ago',
ipAddress: '192.168.1.102',
location: 'Control Room B',
firmware: 'v1.9.2',
humidity: 45.8,
battery: 23
},
{
id: 3,
name: 'Gauge C3',
type: 'Pressure',
status: 'offline',
lastSeen: '1 hour ago',
ipAddress: '192.168.1.103',
location: 'Hydraulic System C',
firmware: 'v2.0.1',
pressure: 2.2,
battery: 0
},
{
id: 4,
name: 'Detector D4',
type: 'Gas',
status: 'online',
lastSeen: '30 sec ago',
ipAddress: '192.168.1.104',
location: 'Storage Area D',
firmware: 'v1.8.5',
battery: 95,
signal: 85
},
{
id: 5,
name: 'Air Quality E5',
type: 'Air Quality',
status: 'online',
lastSeen: '1 min ago',
ipAddress: '192.168.1.105',
location: 'Office Area E',
firmware: 'v2.2.0',
battery: 78,
signal: 92
}
]);
const [selectedDevice, setSelectedDevice] = useState<Device | null>(null);
const [showConfigModal, setShowConfigModal] = useState(false);
const [filter, setFilter] = useState<'all' | 'online' | 'offline' | 'maintenance'>('all');
const [searchTerm, setSearchTerm] = useState('');
const handleDeviceAction = (deviceId: number, action: 'restart' | 'remove' | 'config') => {
if (action === 'restart') {
setDevices(prev =>
prev.map(device =>
device.id === deviceId
? { ...device, status: 'maintenance', lastSeen: 'Just now' }
: device
)
);
// Simulate restart process
setTimeout(() => {
setDevices(prev =>
prev.map(device =>
device.id === deviceId
? { ...device, status: 'online', lastSeen: 'Just now' }
: device
)
);
}, 3000);
} else if (action === 'remove') {
setDevices(prev => prev.filter(device => device.id !== deviceId));
} else if (action === 'config') {
const device = devices.find(d => d.id === deviceId);
if (device) {
setSelectedDevice(device);
setShowConfigModal(true);
}
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'online':
return '#2ed573';
case 'offline':
return '#ff4757';
case 'maintenance':
return '#ffa502';
case 'error':
return '#ff3838';
default:
return '#888';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'online':
return 'fas fa-circle';
case 'offline':
return 'fas fa-circle';
case 'maintenance':
return 'fas fa-tools';
case 'error':
return 'fas fa-exclamation-triangle';
default:
return 'fas fa-question-circle';
}
};
const getTypeIcon = (type: string) => {
switch (type.toLowerCase()) {
case 'temperature':
return 'fas fa-thermometer-half';
case 'humidity':
return 'fas fa-tint';
case 'pressure':
return 'fas fa-tachometer-alt';
case 'gas':
return 'fas fa-wind';
case 'air quality':
return 'fas fa-cloud';
default:
return 'fas fa-microchip';
}
};
const filteredDevices = devices.filter(device => {
const matchesFilter = filter === 'all' || device.status === filter;
const matchesSearch = device.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
device.type.toLowerCase().includes(searchTerm.toLowerCase()) ||
device.location.toLowerCase().includes(searchTerm.toLowerCase());
return matchesFilter && matchesSearch;
});
const stats = {
total: devices.length,
online: devices.filter(d => d.status === 'online').length,
offline: devices.filter(d => d.status === 'offline').length,
maintenance: devices.filter(d => d.status === 'maintenance').length
};
return (
<div>
<h1 className="page-title">Device Configuration</h1>
{/* Stats Cards */}
<div className="dashboard-grid" style={{ marginBottom: '30px' }}>
<div className="card">
<div className="card-header">
<h3 className="card-title">Total Devices</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #00d4ff, #0078ff)' }}>
<i className="fas fa-microchip"></i>
</div>
</div>
<div className="metric-value">{stats.total}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Online</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #2ed573, #1e90ff)' }}>
<i className="fas fa-wifi"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#2ed573' }}>{stats.online}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Offline</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #ff4757, #ff3838)' }}>
<i className="fas fa-times-circle"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#ff4757' }}>{stats.offline}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Maintenance</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #ffa502, #ff6348)' }}>
<i className="fas fa-tools"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#ffa502' }}>{stats.maintenance}</div>
</div>
</div>
{/* Search and Filter */}
<div style={{ marginBottom: '30px' }}>
<div style={{ display: 'flex', gap: '15px', flexWrap: 'wrap', alignItems: 'center' }}>
<div style={{ flex: 1, minWidth: '250px' }}>
<input
type="text"
placeholder="Search devices..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{
width: '100%',
padding: '12px 16px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid #333',
borderRadius: '8px',
color: '#fff',
fontSize: '14px'
}}
/>
</div>
<div style={{ display: 'flex', gap: '10px' }}>
{[
{ key: 'all', label: 'All', count: stats.total },
{ key: 'online', label: 'Online', count: stats.online },
{ key: 'offline', label: 'Offline', count: stats.offline },
{ key: 'maintenance', label: 'Maintenance', count: stats.maintenance }
].map(tab => (
<button
key={tab.key}
className={`filter-tab ${filter === tab.key ? 'active' : ''}`}
onClick={() => setFilter(tab.key as any)}
>
{tab.label} ({tab.count})
</button>
))}
</div>
</div>
</div>
<div className="dashboard-grid">
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Device List & Status</h3>
<div className="card-icon devices">
<i className="fas fa-cogs"></i>
</div>
</div>
{filteredDevices.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#888' }}>
<i className="fas fa-search" style={{ fontSize: '48px', marginBottom: '15px', opacity: 0.5 }}></i>
<p>No devices found matching your criteria.</p>
</div>
) : (
<table>
<thead>
<tr style={{ background: 'rgba(0,212,255,0.08)' }}>
<th>Device</th>
<th>Type</th>
<th>Status</th>
<th>Location</th>
<th>Last Seen</th>
<th>Battery</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{filteredDevices.map(device => (
<tr key={device.id}>
<td>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<i
className={getTypeIcon(device.type)}
style={{ color: '#00eaff', fontSize: '16px' }}
></i>
<div>
<div style={{ fontWeight: '600', color: '#fff' }}>{device.name}</div>
<div style={{ fontSize: '12px', color: '#888' }}>{device.ipAddress}</div>
</div>
</div>
</td>
<td>
<span style={{ color: '#ccc' }}>{device.type}</span>
<div style={{ fontSize: '11px', color: '#666' }}>v{device.firmware}</div>
</td>
<td>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<i
className={getStatusIcon(device.status)}
style={{
color: getStatusColor(device.status),
fontSize: '12px',
animation: device.status === 'maintenance' ? 'pulse 2s infinite' : 'none'
}}
></i>
<span style={{
color: getStatusColor(device.status),
textTransform: 'capitalize',
fontWeight: '500'
}}>
{device.status}
</span>
</div>
</td>
<td>
<span style={{ color: '#ccc' }}>{device.location}</span>
</td>
<td>
<span style={{ color: '#888', fontSize: '13px' }}>{device.lastSeen}</span>
</td>
<td>
{device.battery !== undefined && (
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<div style={{
width: '40px',
height: '6px',
background: '#333',
borderRadius: '3px',
overflow: 'hidden'
}}>
<div style={{
width: `${device.battery}%`,
height: '100%',
background: device.battery > 20 ? '#2ed573' : '#ff4757',
transition: 'width 0.3s ease'
}}></div>
</div>
<span style={{
color: device.battery > 20 ? '#2ed573' : '#ff4757',
fontSize: '12px',
fontWeight: '500'
}}>
{device.battery}%
</span>
</div>
)}
</td>
<td>
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
<button
className="action-btn"
onClick={() => handleDeviceAction(device.id, 'config')}
title="Configure"
>
<i className="fas fa-cog"></i>
</button>
<button
className="action-btn success"
onClick={() => handleDeviceAction(device.id, 'restart')}
disabled={device.status === 'maintenance'}
title="Restart"
>
<i className="fas fa-sync-alt"></i>
</button>
<button
className="action-btn danger"
onClick={() => handleDeviceAction(device.id, 'remove')}
title="Remove"
>
<i className="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
{/* Device Configuration Modal */}
{showConfigModal && selectedDevice && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.8)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 3000
}}>
<div style={{
background: '#232837',
padding: '30px',
borderRadius: '12px',
width: '90%',
maxWidth: '600px',
maxHeight: '80vh',
overflowY: 'auto',
border: '1px solid #333'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
<h3 style={{ color: '#fff', margin: 0 }}>Configure {selectedDevice.name}</h3>
<button
className="action-btn"
onClick={() => setShowConfigModal(false)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginBottom: '20px' }}>
<div>
<label style={{ color: '#ccc', fontSize: '14px', marginBottom: '5px', display: 'block' }}>Device Name</label>
<input
type="text"
defaultValue={selectedDevice.name}
style={{
width: '100%',
padding: '10px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid #333',
borderRadius: '6px',
color: '#fff',
fontSize: '14px'
}}
/>
</div>
<div>
<label style={{ color: '#ccc', fontSize: '14px', marginBottom: '5px', display: 'block' }}>IP Address</label>
<input
type="text"
defaultValue={selectedDevice.ipAddress}
style={{
width: '100%',
padding: '10px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid #333',
borderRadius: '6px',
color: '#fff',
fontSize: '14px'
}}
/>
</div>
<div>
<label style={{ color: '#ccc', fontSize: '14px', marginBottom: '5px', display: 'block' }}>Location</label>
<input
type="text"
defaultValue={selectedDevice.location}
style={{
width: '100%',
padding: '10px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid #333',
borderRadius: '6px',
color: '#fff',
fontSize: '14px'
}}
/>
</div>
<div>
<label style={{ color: '#ccc', fontSize: '14px', marginBottom: '5px', display: 'block' }}>Firmware Version</label>
<input
type="text"
defaultValue={selectedDevice.firmware}
style={{
width: '100%',
padding: '10px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid #333',
borderRadius: '6px',
color: '#fff',
fontSize: '14px'
}}
/>
</div>
</div>
<div style={{ display: 'flex', gap: '10px', justifyContent: 'flex-end' }}>
<button
className="action-btn"
onClick={() => setShowConfigModal(false)}
>
Cancel
</button>
<button
className="btn-primary"
onClick={() => {
// TODO: Save configuration
setShowConfigModal(false);
}}
>
Save Configuration
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default Devices;

View File

@ -0,0 +1,232 @@
import React, { useState, useRef, useEffect } from 'react';
interface HeaderProps {
onMenuToggle?: () => void;
}
interface Notification {
id: number;
title: string;
message: string;
type: 'critical' | 'warning' | 'info';
time: string;
read: boolean;
}
const Header: React.FC<HeaderProps> = ({ onMenuToggle }) => {
const [showNotifications, setShowNotifications] = useState(false);
const [showUserDropdown, setShowUserDropdown] = useState(false);
const [notifications, setNotifications] = useState<Notification[]>([
{
id: 1,
title: 'Critical Temperature Alert',
message: 'Boiler Unit #3 temperature exceeded threshold (85°C)',
type: 'critical',
time: '2 min ago',
read: false
},
{
id: 2,
title: 'Power Fluctuation Warning',
message: 'Generator A voltage dropped to 215V (Normal: 220V±5V)',
type: 'warning',
time: '5 min ago',
read: false
},
{
id: 3,
title: 'Connection Restored',
message: 'Sensor Array B connection has been re-established',
type: 'info',
time: '8 min ago',
read: true
},
{
id: 4,
title: 'Maintenance Due',
message: 'Motor Unit #7 requires scheduled maintenance within 72 hours',
type: 'warning',
time: '15 min ago',
read: false
}
]);
const notificationRef = useRef<HTMLDivElement>(null);
const userDropdownRef = useRef<HTMLDivElement>(null);
// Close dropdowns when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (notificationRef.current && !notificationRef.current.contains(event.target as Node)) {
setShowNotifications(false);
}
if (userDropdownRef.current && !userDropdownRef.current.contains(event.target as Node)) {
setShowUserDropdown(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const unreadCount = notifications.filter(n => !n.read).length;
const handleNotificationClick = (notificationId: number) => {
setNotifications(prev =>
prev.map(n => n.id === notificationId ? { ...n, read: true } : n)
);
};
const handleLogout = () => {
// TODO: Implement logout functionality
console.log('Logout clicked');
setShowUserDropdown(false);
};
const getNotificationIcon = (type: string) => {
switch (type) {
case 'critical':
return 'fas fa-exclamation-triangle';
case 'warning':
return 'fas fa-exclamation-circle';
case 'info':
return 'fas fa-info-circle';
default:
return 'fas fa-bell';
}
};
const getNotificationColor = (type: string) => {
switch (type) {
case 'critical':
return '#ff4757';
case 'warning':
return '#ffa502';
case 'info':
return '#00d4ff';
default:
return '#00eaff';
}
};
return (
<header className="header">
<div className="logo">
<button
className="mobile-menu-toggle"
onClick={onMenuToggle}
aria-label="Toggle menu"
>
<i className="fas fa-bars"></i>
</button>
<i className="fas fa-microchip"></i> IoT Control Hub
</div>
<div className="header-right">
{/* Notifications */}
<div className="notification-container" ref={notificationRef}>
<div
className="notification-bell"
onClick={() => setShowNotifications(!showNotifications)}
>
<i className="fas fa-bell"></i>
{unreadCount > 0 && (
<span className="notification-badge">{unreadCount}</span>
)}
</div>
{/* Notifications Popup */}
{showNotifications && (
<div className="notifications-popup">
<div className="notifications-header">
<h3>Notifications</h3>
<button
className="close-notifications"
onClick={() => setShowNotifications(false)}
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="notifications-list">
{notifications.length === 0 ? (
<div className="no-notifications">
<i className="fas fa-bell-slash"></i>
<p>No notifications</p>
</div>
) : (
notifications.map(notification => (
<div
key={notification.id}
className={`notification-item ${notification.read ? 'read' : 'unread'}`}
onClick={() => handleNotificationClick(notification.id)}
>
<div
className="notification-icon"
style={{ backgroundColor: getNotificationColor(notification.type) }}
>
<i className={getNotificationIcon(notification.type)}></i>
</div>
<div className="notification-content">
<div className="notification-title">{notification.title}</div>
<div className="notification-message">{notification.message}</div>
<div className="notification-time">{notification.time}</div>
</div>
{!notification.read && <div className="unread-indicator"></div>}
</div>
))
)}
</div>
</div>
)}
</div>
{/* User Profile Dropdown */}
<div className="user-profile-container" ref={userDropdownRef}>
<div
className="user-profile"
onClick={() => setShowUserDropdown(!showUserDropdown)}
>
<div className="avatar">AG</div>
<span>Admin Giri</span>
<i className="fas fa-chevron-down"></i>
</div>
{/* User Dropdown */}
{showUserDropdown && (
<div className="user-dropdown">
<div className="user-info">
<div className="user-avatar">AG</div>
<div className="user-details">
<div className="user-name">Admin Giri</div>
<div className="user-email">admin@iotcontrol.com</div>
<div className="user-role">System Administrator</div>
</div>
</div>
<div className="dropdown-divider"></div>
<div className="dropdown-menu">
<button className="dropdown-item">
<i className="fas fa-user"></i>
Profile Settings
</button>
<button className="dropdown-item">
<i className="fas fa-cog"></i>
Account Settings
</button>
<button className="dropdown-item">
<i className="fas fa-shield-alt"></i>
Security
</button>
<div className="dropdown-divider"></div>
<button className="dropdown-item logout" onClick={handleLogout}>
<i className="fas fa-sign-out-alt"></i>
Logout
</button>
</div>
</div>
)}
</div>
</div>
</header>
);
};
export default Header;

View File

@ -0,0 +1,402 @@
import React, { useState, useEffect } from 'react';
interface HealingEvent {
id: number;
type: 'auto' | 'manual';
description: string;
status: 'success' | 'failed' | 'in-progress';
timestamp: string;
duration?: string;
affectedDevices?: string[];
}
interface SystemStatus {
autoRecovery: boolean;
lastHealEvent: string;
issuesResolved: number;
pendingIntervention: number;
systemHealth: number;
activeHealing: boolean;
}
const Healing: React.FC = () => {
const [systemStatus, setSystemStatus] = useState<SystemStatus>({
autoRecovery: true,
lastHealEvent: '3 hours ago',
issuesResolved: 12,
pendingIntervention: 1,
systemHealth: 94,
activeHealing: false
});
const [healingEvents, setHealingEvents] = useState<HealingEvent[]>([
{
id: 1,
type: 'auto',
description: 'Restored connection to Sensor Array B',
status: 'success',
timestamp: '3 hours ago',
duration: '2 minutes',
affectedDevices: ['Sensor Array B']
},
{
id: 2,
type: 'auto',
description: 'Fixed temperature sensor calibration drift',
status: 'success',
timestamp: '5 hours ago',
duration: '5 minutes',
affectedDevices: ['Temperature Sensor A1', 'Temperature Sensor C3']
},
{
id: 3,
type: 'manual',
description: 'Rebooted offline pressure gauge',
status: 'success',
timestamp: '1 day ago',
duration: '3 minutes',
affectedDevices: ['Pressure Gauge D2']
},
{
id: 4,
type: 'auto',
description: 'Attempted to fix communication protocol mismatch',
status: 'failed',
timestamp: '2 days ago',
duration: '8 minutes',
affectedDevices: ['Communication Module E5']
}
]);
const [showTriggerModal, setShowTriggerModal] = useState(false);
const [triggerReason, setTriggerReason] = useState('');
const handleTriggerHealing = () => {
if (!triggerReason.trim()) return;
const newEvent: HealingEvent = {
id: Date.now(),
type: 'manual',
description: triggerReason,
status: 'in-progress',
timestamp: 'Just now',
affectedDevices: ['System-wide']
};
setHealingEvents(prev => [newEvent, ...prev]);
setSystemStatus(prev => ({
...prev,
activeHealing: true,
pendingIntervention: prev.pendingIntervention + 1
}));
// Simulate healing process
setTimeout(() => {
setHealingEvents(prev =>
prev.map(event =>
event.id === newEvent.id
? { ...event, status: 'success', duration: '3 minutes' }
: event
)
);
setSystemStatus(prev => ({
...prev,
activeHealing: false,
issuesResolved: prev.issuesResolved + 1,
pendingIntervention: Math.max(0, prev.pendingIntervention - 1),
lastHealEvent: 'Just now'
}));
}, 3000);
setShowTriggerModal(false);
setTriggerReason('');
};
const getStatusColor = (status: string) => {
switch (status) {
case 'success':
return '#2ed573';
case 'failed':
return '#ff4757';
case 'in-progress':
return '#00d4ff';
default:
return '#888';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'success':
return 'fas fa-check-circle';
case 'failed':
return 'fas fa-times-circle';
case 'in-progress':
return 'fas fa-spinner fa-spin';
default:
return 'fas fa-question-circle';
}
};
const getTypeIcon = (type: string) => {
return type === 'auto' ? 'fas fa-robot' : 'fas fa-user-cog';
};
return (
<div>
<h1 className="page-title">Self Healing System</h1>
{/* System Status Cards */}
<div className="dashboard-grid" style={{ marginBottom: '30px' }}>
<div className="card">
<div className="card-header">
<h3 className="card-title">System Health</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #2ed573, #1e90ff)' }}>
<i className="fas fa-heartbeat"></i>
</div>
</div>
<div className="metric-value" style={{ color: systemStatus.systemHealth > 90 ? '#2ed573' : systemStatus.systemHealth > 70 ? '#ffa502' : '#ff4757' }}>
{systemStatus.systemHealth}%
</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Auto-Recovery</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #00d4ff, #0078ff)' }}>
<i className="fas fa-shield-alt"></i>
</div>
</div>
<div className="metric-value" style={{ color: systemStatus.autoRecovery ? '#2ed573' : '#ff4757' }}>
{systemStatus.autoRecovery ? 'Enabled' : 'Disabled'}
</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Issues Resolved</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #a55eea, #26de81)' }}>
<i className="fas fa-check-double"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#2ed573' }}>{systemStatus.issuesResolved}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Pending Intervention</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #ffa502, #ff6348)' }}>
<i className="fas fa-exclamation-triangle"></i>
</div>
</div>
<div className="metric-value" style={{ color: systemStatus.pendingIntervention > 0 ? '#ff4757' : '#2ed573' }}>
{systemStatus.pendingIntervention}
</div>
</div>
</div>
<div className="dashboard-grid">
{/* Self Healing Status */}
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Self Healing Status</h3>
<div className="card-icon healing">
<i className="fas fa-magic"></i>
</div>
</div>
<div style={{ marginTop: '20px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginBottom: '30px' }}>
<div style={{ padding: '20px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px' }}>
<h4 style={{ color: '#00eaff', marginBottom: '10px' }}>System Status</h4>
<ul style={{ color: '#fff', listStyle: 'none', padding: 0 }}>
<li style={{ marginBottom: '8px' }}>
<i className="fas fa-circle" style={{ color: systemStatus.autoRecovery ? '#2ed573' : '#ff4757', fontSize: '8px', marginRight: '8px' }}></i>
Auto-Recovery: <span style={{ color: systemStatus.autoRecovery ? '#2ed573' : '#ff4757' }}>
{systemStatus.autoRecovery ? 'Enabled' : 'Disabled'}
</span>
</li>
<li style={{ marginBottom: '8px' }}>
<i className="fas fa-clock" style={{ color: '#ffa502', marginRight: '8px' }}></i>
Last Heal Event: <span style={{ color: '#ffa502' }}>{systemStatus.lastHealEvent}</span>
</li>
<li style={{ marginBottom: '8px' }}>
<i className="fas fa-check-circle" style={{ color: '#2ed573', marginRight: '8px' }}></i>
Issues Resolved: <span style={{ color: '#2ed573' }}>{systemStatus.issuesResolved}</span>
</li>
<li style={{ marginBottom: '8px' }}>
<i className="fas fa-exclamation-triangle" style={{ color: '#ff4757', marginRight: '8px' }}></i>
Pending Intervention: <span style={{ color: '#ff4757' }}>{systemStatus.pendingIntervention}</span>
</li>
</ul>
</div>
<div style={{ padding: '20px', background: 'rgba(255,255,255,0.05)', borderRadius: '10px' }}>
<h4 style={{ color: '#00eaff', marginBottom: '10px' }}>Quick Actions</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<button
className="btn-primary"
onClick={() => setShowTriggerModal(true)}
disabled={systemStatus.activeHealing}
>
<i className="fas fa-magic"></i>
Trigger Self-Heal
</button>
<button
className="action-btn"
onClick={() => setSystemStatus(prev => ({ ...prev, autoRecovery: !prev.autoRecovery }))}
>
<i className="fas fa-toggle-on"></i>
{systemStatus.autoRecovery ? 'Disable' : 'Enable'} Auto-Recovery
</button>
<button className="action-btn">
<i className="fas fa-sync-alt"></i>
Refresh Status
</button>
</div>
</div>
</div>
</div>
</div>
{/* Healing Events History */}
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Healing Events History</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #a55eea, #26de81)' }}>
<i className="fas fa-history"></i>
</div>
</div>
<div style={{ marginTop: '20px' }}>
{healingEvents.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#888' }}>
<i className="fas fa-inbox" style={{ fontSize: '48px', marginBottom: '15px', opacity: 0.5 }}></i>
<p>No healing events recorded.</p>
</div>
) : (
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
{healingEvents.map(event => (
<div
key={event.id}
style={{
padding: '15px',
margin: '10px 0',
borderRadius: '8px',
background: 'rgba(255,255,255,0.05)',
border: `1px solid ${getStatusColor(event.status)}33`,
position: 'relative'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '10px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<i
className={getTypeIcon(event.type)}
style={{ color: event.type === 'auto' ? '#00d4ff' : '#ffa502' }}
></i>
<span style={{ color: '#fff', fontWeight: '600' }}>{event.description}</span>
<span
style={{
padding: '4px 8px',
borderRadius: '12px',
fontSize: '10px',
fontWeight: 'bold',
textTransform: 'uppercase',
backgroundColor: getStatusColor(event.status),
color: 'white'
}}
>
{event.status}
</span>
</div>
<span style={{ color: '#888', fontSize: '12px' }}>{event.timestamp}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
<i
className={getStatusIcon(event.status)}
style={{ color: getStatusColor(event.status) }}
></i>
{event.duration && (
<span style={{ color: '#ccc', fontSize: '12px' }}>
Duration: {event.duration}
</span>
)}
{event.affectedDevices && (
<span style={{ color: '#ccc', fontSize: '12px' }}>
Devices: {event.affectedDevices.join(', ')}
</span>
)}
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
{/* Trigger Healing Modal */}
{showTriggerModal && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.8)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 3000
}}>
<div style={{
background: '#232837',
padding: '30px',
borderRadius: '12px',
width: '90%',
maxWidth: '500px',
border: '1px solid #333'
}}>
<h3 style={{ color: '#fff', marginBottom: '20px' }}>Trigger Self-Healing</h3>
<textarea
value={triggerReason}
onChange={(e) => setTriggerReason(e.target.value)}
placeholder="Describe the issue or reason for manual healing..."
style={{
width: '100%',
minHeight: '100px',
padding: '12px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid #333',
borderRadius: '8px',
color: '#fff',
fontSize: '14px',
resize: 'vertical'
}}
/>
<div style={{ display: 'flex', gap: '10px', marginTop: '20px', justifyContent: 'flex-end' }}>
<button
className="action-btn"
onClick={() => setShowTriggerModal(false)}
>
Cancel
</button>
<button
className="btn-primary"
onClick={handleTriggerHealing}
disabled={!triggerReason.trim()}
>
Trigger Healing
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default Healing;

View File

@ -0,0 +1,516 @@
import React, { useState, useEffect } from 'react';
interface UserProfile {
fullName: string;
email: string;
role: string;
avatar: string;
phone: string;
department: string;
timezone: string;
language: string;
}
interface AccountSettings {
emailNotifications: boolean;
pushNotifications: boolean;
smsAlerts: boolean;
autoLogout: boolean;
sessionTimeout: number;
language: string;
}
interface SecuritySettings {
twoFactorAuth: boolean;
passwordExpiry: number;
loginAttempts: number;
ipWhitelist: string[];
sessionManagement: boolean;
auditLogs: boolean;
}
const Settings: React.FC = () => {
const [activeTab, setActiveTab] = useState<'profile' | 'account' | 'security' | 'notifications'>('profile');
const [userProfile, setUserProfile] = useState<UserProfile>({
fullName: 'Admin Giri',
email: 'admin@iotcontrol.com',
role: 'System Administrator',
avatar: 'AG',
phone: '+1 (555) 123-4567',
department: 'IT Operations',
timezone: 'UTC-5 (Eastern Time)',
language: 'English'
});
const [accountSettings, setAccountSettings] = useState<AccountSettings>({
emailNotifications: true,
pushNotifications: true,
smsAlerts: false,
autoLogout: true,
sessionTimeout: 30,
language: 'en'
});
const [securitySettings, setSecuritySettings] = useState<SecuritySettings>({
twoFactorAuth: true,
passwordExpiry: 90,
loginAttempts: 5,
ipWhitelist: ['192.168.1.0/24', '10.0.0.0/8'],
sessionManagement: true,
auditLogs: true
});
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [currentPassword, setCurrentPassword] = useState('');
const handleProfileUpdate = (field: keyof UserProfile, value: string) => {
setUserProfile(prev => ({ ...prev, [field]: value }));
};
const handleAccountUpdate = (field: keyof AccountSettings, value: any) => {
setAccountSettings(prev => ({ ...prev, [field]: value }));
};
const handleSecurityUpdate = (field: keyof SecuritySettings, value: any) => {
setSecuritySettings(prev => ({ ...prev, [field]: value }));
};
const handlePasswordChange = () => {
if (newPassword !== confirmPassword) {
alert('Passwords do not match!');
return;
}
if (newPassword.length < 8) {
alert('Password must be at least 8 characters long!');
return;
}
// TODO: Implement password change
alert('Password updated successfully!');
setNewPassword('');
setConfirmPassword('');
setCurrentPassword('');
};
const tabs = [
{ id: 'profile', label: 'Profile Settings', icon: 'fas fa-user' },
{ id: 'account', label: 'Account Settings', icon: 'fas fa-cog' },
{ id: 'security', label: 'Security', icon: 'fas fa-shield-alt' },
{ id: 'notifications', label: 'Notifications', icon: 'fas fa-bell' }
];
const renderProfileSettings = () => (
<div className="settings-section">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '30px' }}>
<div>
<div style={{ textAlign: 'center', marginBottom: '30px' }}>
<div
style={{
width: '120px',
height: '120px',
borderRadius: '50%',
background: 'linear-gradient(45deg, #00eaff, #0078ff)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: '48px',
fontWeight: 'bold',
margin: '0 auto 15px'
}}
>
{userProfile.avatar}
</div>
<button className="action-btn">
<i className="fas fa-camera"></i> Change Avatar
</button>
</div>
</div>
<div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div className="form-group">
<label className="form-label">Full Name</label>
<input
type="text"
value={userProfile.fullName}
onChange={(e) => handleProfileUpdate('fullName', e.target.value)}
className="form-input"
/>
</div>
<div className="form-group">
<label className="form-label">Email</label>
<input
type="email"
value={userProfile.email}
onChange={(e) => handleProfileUpdate('email', e.target.value)}
className="form-input"
/>
</div>
<div className="form-group">
<label className="form-label">Phone</label>
<input
type="tel"
value={userProfile.phone}
onChange={(e) => handleProfileUpdate('phone', e.target.value)}
className="form-input"
/>
</div>
<div className="form-group">
<label className="form-label">Department</label>
<input
type="text"
value={userProfile.department}
onChange={(e) => handleProfileUpdate('department', e.target.value)}
className="form-input"
/>
</div>
<div className="form-group">
<label className="form-label">Role</label>
<input
type="text"
value={userProfile.role}
disabled
className="form-input"
style={{ opacity: 0.6 }}
/>
</div>
<div className="form-group">
<label className="form-label">Timezone</label>
<select
value={userProfile.timezone}
onChange={(e) => handleProfileUpdate('timezone', e.target.value)}
className="form-input"
>
<option value="UTC-5 (Eastern Time)">UTC-5 (Eastern Time)</option>
<option value="UTC-6 (Central Time)">UTC-6 (Central Time)</option>
<option value="UTC-7 (Mountain Time)">UTC-7 (Mountain Time)</option>
<option value="UTC-8 (Pacific Time)">UTC-8 (Pacific Time)</option>
<option value="UTC+0 (GMT)">UTC+0 (GMT)</option>
<option value="UTC+1 (CET)">UTC+1 (CET)</option>
</select>
</div>
</div>
<div style={{ marginTop: '30px' }}>
<button className="btn-primary">
<i className="fas fa-save"></i> Save Profile Changes
</button>
</div>
</div>
</div>
</div>
);
const renderAccountSettings = () => (
<div className="settings-section">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '30px' }}>
<div>
<h4 style={{ color: '#00eaff', marginBottom: '20px' }}>Session Management</h4>
<div className="form-group">
<label className="form-label">Auto Logout</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={accountSettings.autoLogout}
onChange={(e) => handleAccountUpdate('autoLogout', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Enable automatic logout</span>
</div>
</div>
<div className="form-group">
<label className="form-label">Session Timeout (minutes)</label>
<input
type="number"
value={accountSettings.sessionTimeout}
onChange={(e) => handleAccountUpdate('sessionTimeout', parseInt(e.target.value))}
className="form-input"
min="5"
max="120"
/>
</div>
<div className="form-group">
<label className="form-label">Language</label>
<select
value={accountSettings.language}
onChange={(e) => handleAccountUpdate('language', e.target.value)}
className="form-input"
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="zh">Chinese</option>
</select>
</div>
</div>
<div>
<h4 style={{ color: '#00eaff', marginBottom: '20px' }}>Password Change</h4>
<div className="form-group">
<label className="form-label">Current Password</label>
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="form-input"
placeholder="Enter current password"
/>
</div>
<div className="form-group">
<label className="form-label">New Password</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="form-input"
placeholder="Enter new password"
/>
</div>
<div className="form-group">
<label className="form-label">Confirm New Password</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="form-input"
placeholder="Confirm new password"
/>
</div>
<button
className="btn-primary"
onClick={handlePasswordChange}
disabled={!currentPassword || !newPassword || !confirmPassword}
>
<i className="fas fa-key"></i> Change Password
</button>
</div>
</div>
</div>
);
const renderSecuritySettings = () => (
<div className="settings-section">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '30px' }}>
<div>
<h4 style={{ color: '#00eaff', marginBottom: '20px' }}>Authentication</h4>
<div className="form-group">
<label className="form-label">Two-Factor Authentication</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={securitySettings.twoFactorAuth}
onChange={(e) => handleSecurityUpdate('twoFactorAuth', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Enable 2FA</span>
</div>
</div>
<div className="form-group">
<label className="form-label">Password Expiry (days)</label>
<input
type="number"
value={securitySettings.passwordExpiry}
onChange={(e) => handleSecurityUpdate('passwordExpiry', parseInt(e.target.value))}
className="form-input"
min="30"
max="365"
/>
</div>
<div className="form-group">
<label className="form-label">Max Login Attempts</label>
<input
type="number"
value={securitySettings.loginAttempts}
onChange={(e) => handleSecurityUpdate('loginAttempts', parseInt(e.target.value))}
className="form-input"
min="3"
max="10"
/>
</div>
</div>
<div>
<h4 style={{ color: '#00eaff', marginBottom: '20px' }}>Access Control</h4>
<div className="form-group">
<label className="form-label">Session Management</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={securitySettings.sessionManagement}
onChange={(e) => handleSecurityUpdate('sessionManagement', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Allow multiple sessions</span>
</div>
</div>
<div className="form-group">
<label className="form-label">Audit Logs</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={securitySettings.auditLogs}
onChange={(e) => handleSecurityUpdate('auditLogs', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Enable audit logging</span>
</div>
</div>
<div className="form-group">
<label className="form-label">IP Whitelist</label>
<textarea
value={securitySettings.ipWhitelist.join('\n')}
onChange={(e) => handleSecurityUpdate('ipWhitelist', e.target.value.split('\n').filter(ip => ip.trim()))}
className="form-input"
placeholder="Enter IP addresses (one per line)"
rows={4}
/>
</div>
</div>
</div>
</div>
);
const renderNotificationSettings = () => (
<div className="settings-section">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '30px' }}>
<div>
<h4 style={{ color: '#00eaff', marginBottom: '20px' }}>Notification Preferences</h4>
<div className="form-group">
<label className="form-label">Email Notifications</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={accountSettings.emailNotifications}
onChange={(e) => handleAccountUpdate('emailNotifications', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Receive email notifications</span>
</div>
</div>
<div className="form-group">
<label className="form-label">Push Notifications</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={accountSettings.pushNotifications}
onChange={(e) => handleAccountUpdate('pushNotifications', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Enable push notifications</span>
</div>
</div>
<div className="form-group">
<label className="form-label">SMS Alerts</label>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
checked={accountSettings.smsAlerts}
onChange={(e) => handleAccountUpdate('smsAlerts', e.target.checked)}
style={{ width: '20px', height: '20px' }}
/>
<span style={{ color: '#ccc' }}>Receive SMS alerts</span>
</div>
</div>
</div>
<div>
<h4 style={{ color: '#00eaff', marginBottom: '20px' }}>Alert Types</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input type="checkbox" defaultChecked style={{ width: '18px', height: '18px' }} />
<span style={{ color: '#ccc' }}>Critical System Alerts</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input type="checkbox" defaultChecked style={{ width: '18px', height: '18px' }} />
<span style={{ color: '#ccc' }}>Device Status Changes</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input type="checkbox" defaultChecked style={{ width: '18px', height: '18px' }} />
<span style={{ color: '#ccc' }}>Performance Warnings</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input type="checkbox" style={{ width: '18px', height: '18px' }} />
<span style={{ color: '#ccc' }}>Maintenance Reminders</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input type="checkbox" style={{ width: '18px', height: '18px' }} />
<span style={{ color: '#ccc' }}>Security Events</span>
</div>
</div>
</div>
</div>
</div>
);
return (
<div>
<h1 className="page-title">Settings</h1>
<div className="dashboard-grid">
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">System Settings</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #00d4ff, #0078ff)' }}>
<i className="fas fa-cog"></i>
</div>
</div>
{/* Settings Tabs */}
<div style={{ marginTop: '20px' }}>
<div style={{ display: 'flex', gap: '5px', marginBottom: '30px', flexWrap: 'wrap' }}>
{tabs.map(tab => (
<button
key={tab.id}
className={`filter-tab ${activeTab === tab.id ? 'active' : ''}`}
onClick={() => setActiveTab(tab.id as any)}
>
<i className={tab.icon} style={{ marginRight: '8px' }}></i>
{tab.label}
</button>
))}
</div>
{/* Tab Content */}
<div style={{ minHeight: '400px' }}>
{activeTab === 'profile' && renderProfileSettings()}
{activeTab === 'account' && renderAccountSettings()}
{activeTab === 'security' && renderSecuritySettings()}
{activeTab === 'notifications' && renderNotificationSettings()}
</div>
</div>
</div>
</div>
</div>
);
};
export default Settings;

View File

@ -0,0 +1,39 @@
import React from 'react';
interface SidebarProps {
currentPage: string;
setCurrentPage: (page: string) => void;
isOpen?: boolean;
}
const Sidebar: React.FC<SidebarProps> = ({ currentPage, setCurrentPage, isOpen = false }) => {
const navItems = [
{ id: 'dashboard', label: 'Dashboard', icon: 'fas fa-tachometer-alt' },
{ id: 'alerts', label: 'Real-time Alerts', icon: 'fas fa-exclamation-triangle' },
{ id: 'suggestions', label: 'AI Suggestions', icon: 'fas fa-lightbulb' },
{ id: 'healing', label: 'Self Healing', icon: 'fas fa-magic' },
{ id: 'devices', label: 'Device Config', icon: 'fas fa-cogs' },
{ id: 'analytics', label: 'Analytics', icon: 'fas fa-chart-bar' },
{ id: 'settings', label: 'Settings', icon: 'fas fa-cog' },
];
return (
<nav className={`sidebar ${isOpen ? 'active' : ''}`}>
<ul className="nav-menu">
{navItems.map((item) => (
<li key={item.id} className="nav-item">
<div
className={`nav-link ${currentPage === item.id ? 'active' : ''}`}
onClick={() => setCurrentPage(item.id)}
>
<i className={item.icon}></i>
{item.label}
</div>
</li>
))}
</ul>
</nav>
);
};
export default Sidebar;

View File

@ -0,0 +1,352 @@
import React, { useState } from 'react';
interface Suggestion {
id: number;
title: string;
description: string;
type: 'energy' | 'maintenance' | 'performance' | 'security';
priority: 'high' | 'medium' | 'low';
status: 'pending' | 'implemented' | 'scheduled' | 'rejected';
estimatedSavings?: string;
implementationTime?: string;
createdAt: string;
}
const Suggestions: React.FC = () => {
const [suggestions, setSuggestions] = useState<Suggestion[]>([
{
id: 1,
title: 'Energy Optimization',
description: 'Based on usage patterns, you can reduce energy consumption by 23% by implementing smart scheduling during off-peak hours (2 AM - 6 AM).',
type: 'energy',
priority: 'high',
status: 'pending',
estimatedSavings: '23% energy reduction',
implementationTime: '2-3 hours',
createdAt: '2 hours ago'
},
{
id: 2,
title: 'Predictive Maintenance Alert',
description: 'Motor Unit #7 showing abnormal vibration patterns. Schedule maintenance within 72 hours to prevent failure.',
type: 'maintenance',
priority: 'high',
status: 'scheduled',
estimatedSavings: 'Prevent $50K downtime',
implementationTime: '4-6 hours',
createdAt: '1 hour ago'
},
{
id: 3,
title: 'Performance Enhancement',
description: 'Adjust pressure settings in Hydraulic System C to improve efficiency by 15%. Optimal pressure: 1.8 bar (current: 2.2 bar).',
type: 'performance',
priority: 'medium',
status: 'pending',
estimatedSavings: '15% efficiency gain',
implementationTime: '1-2 hours',
createdAt: '30 min ago'
},
{
id: 4,
title: 'Security Protocol Update',
description: 'Implement enhanced authentication for remote access points to prevent unauthorized access.',
type: 'security',
priority: 'medium',
status: 'pending',
estimatedSavings: 'Enhanced security',
implementationTime: '3-4 hours',
createdAt: '45 min ago'
}
]);
const [filter, setFilter] = useState<'all' | 'pending' | 'implemented' | 'scheduled'>('all');
const handleAction = (suggestionId: number, action: 'implement' | 'schedule' | 'reject') => {
setSuggestions(prev =>
prev.map(suggestion => {
if (suggestion.id === suggestionId) {
let newStatus: Suggestion['status'];
switch (action) {
case 'implement':
newStatus = 'implemented';
break;
case 'schedule':
newStatus = 'scheduled';
break;
case 'reject':
newStatus = 'rejected';
break;
default:
newStatus = suggestion.status;
}
return { ...suggestion, status: newStatus };
}
return suggestion;
})
);
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'high':
return '#ff4757';
case 'medium':
return '#ffa502';
case 'low':
return '#2ed573';
default:
return '#00eaff';
}
};
const getTypeIcon = (type: string) => {
switch (type) {
case 'energy':
return 'fas fa-bolt';
case 'maintenance':
return 'fas fa-tools';
case 'performance':
return 'fas fa-chart-line';
case 'security':
return 'fas fa-shield-alt';
default:
return 'fas fa-lightbulb';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'pending':
return '#ffa502';
case 'implemented':
return '#2ed573';
case 'scheduled':
return '#00d4ff';
case 'rejected':
return '#ff4757';
default:
return '#888';
}
};
const filteredSuggestions = suggestions.filter(suggestion =>
filter === 'all' || suggestion.status === filter
);
const stats = {
total: suggestions.length,
pending: suggestions.filter(s => s.status === 'pending').length,
implemented: suggestions.filter(s => s.status === 'implemented').length,
scheduled: suggestions.filter(s => s.status === 'scheduled').length
};
return (
<div>
<h1 className="page-title">AI-Powered Suggestions</h1>
{/* Stats Cards */}
<div className="dashboard-grid" style={{ marginBottom: '30px' }}>
<div className="card">
<div className="card-header">
<h3 className="card-title">Total Suggestions</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #00d4ff, #0078ff)' }}>
<i className="fas fa-lightbulb"></i>
</div>
</div>
<div className="metric-value">{stats.total}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Pending</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #ffa502, #ff6348)' }}>
<i className="fas fa-clock"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#ffa502' }}>{stats.pending}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Implemented</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #2ed573, #1e90ff)' }}>
<i className="fas fa-check-circle"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#2ed573' }}>{stats.implemented}</div>
</div>
<div className="card">
<div className="card-header">
<h3 className="card-title">Scheduled</h3>
<div className="card-icon" style={{ background: 'linear-gradient(45deg, #a55eea, #26de81)' }}>
<i className="fas fa-calendar-alt"></i>
</div>
</div>
<div className="metric-value" style={{ color: '#a55eea' }}>{stats.scheduled}</div>
</div>
</div>
{/* Filter Tabs */}
<div style={{ marginBottom: '30px' }}>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
{[
{ key: 'all', label: 'All', count: stats.total },
{ key: 'pending', label: 'Pending', count: stats.pending },
{ key: 'implemented', label: 'Implemented', count: stats.implemented },
{ key: 'scheduled', label: 'Scheduled', count: stats.scheduled }
].map(tab => (
<button
key={tab.key}
className={`filter-tab ${filter === tab.key ? 'active' : ''}`}
onClick={() => setFilter(tab.key as any)}
>
{tab.label} ({tab.count})
</button>
))}
</div>
</div>
{/* Suggestions List */}
<div className="dashboard-grid">
<div className="card large-card">
<div className="card-header">
<h3 className="card-title">Optimization Recommendations</h3>
<div className="card-icon suggestions">
<i className="fas fa-lightbulb"></i>
</div>
</div>
<div style={{ marginTop: '20px' }}>
{filteredSuggestions.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#888' }}>
<i className="fas fa-inbox" style={{ fontSize: '48px', marginBottom: '15px', opacity: 0.5 }}></i>
<p>No suggestions found for the selected filter.</p>
</div>
) : (
filteredSuggestions.map(suggestion => (
<div
key={suggestion.id}
style={{
padding: '20px',
margin: '15px 0',
borderRadius: '10px',
background: 'linear-gradient(45deg, rgba(255, 165, 2, 0.1), rgba(255, 99, 72, 0.1))',
border: '1px solid rgba(255, 165, 2, 0.3)',
position: 'relative'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '15px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<i
className={getTypeIcon(suggestion.type)}
style={{
color: getPriorityColor(suggestion.priority),
fontSize: '20px'
}}
></i>
<h4 style={{ color: '#ffa502', margin: 0 }}>{suggestion.title}</h4>
<span
style={{
padding: '4px 8px',
borderRadius: '12px',
fontSize: '10px',
fontWeight: 'bold',
textTransform: 'uppercase',
backgroundColor: getPriorityColor(suggestion.priority),
color: 'white'
}}
>
{suggestion.priority}
</span>
<span
style={{
padding: '4px 8px',
borderRadius: '12px',
fontSize: '10px',
fontWeight: 'bold',
textTransform: 'uppercase',
backgroundColor: getStatusColor(suggestion.status),
color: 'white'
}}
>
{suggestion.status}
</span>
</div>
<span style={{ color: '#888', fontSize: '12px' }}>{suggestion.createdAt}</span>
</div>
<p style={{ color: '#ccc', marginBottom: '15px', lineHeight: '1.5' }}>
{suggestion.description}
</p>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<div style={{ display: 'flex', gap: '20px' }}>
{suggestion.estimatedSavings && (
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<i className="fas fa-chart-line" style={{ color: '#2ed573' }}></i>
<span style={{ color: '#ccc', fontSize: '13px' }}>{suggestion.estimatedSavings}</span>
</div>
)}
{suggestion.implementationTime && (
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<i className="fas fa-clock" style={{ color: '#00d4ff' }}></i>
<span style={{ color: '#ccc', fontSize: '13px' }}>{suggestion.implementationTime}</span>
</div>
)}
</div>
</div>
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
{suggestion.status === 'pending' && (
<>
<button
className="btn-primary"
onClick={() => handleAction(suggestion.id, 'implement')}
>
Implement Now
</button>
<button
className="action-btn"
onClick={() => handleAction(suggestion.id, 'schedule')}
>
Schedule Later
</button>
<button
className="action-btn danger"
onClick={() => handleAction(suggestion.id, 'reject')}
>
Reject
</button>
</>
)}
{suggestion.status === 'scheduled' && (
<button
className="btn-primary"
onClick={() => handleAction(suggestion.id, 'implement')}
>
Implement Now
</button>
)}
{suggestion.status === 'implemented' && (
<span style={{ color: '#2ed573', fontWeight: 'bold' }}>
<i className="fas fa-check-circle"></i> Implemented
</span>
)}
{suggestion.status === 'rejected' && (
<span style={{ color: '#ff4757', fontWeight: 'bold' }}>
<i className="fas fa-times-circle"></i> Rejected
</span>
)}
</div>
</div>
))
)}
</div>
</div>
</div>
</div>
);
};
export default Suggestions;

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

1
iot-dashboard/src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}