14/02/2025
This commit is contained in:
parent
760aec0638
commit
a58c9b35a4
552
src/App.css
552
src/App.css
@ -2,8 +2,6 @@
|
|||||||
body {
|
body {
|
||||||
font-family: 'Poppins', sans-serif !important;
|
font-family: 'Poppins', sans-serif !important;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: background-color 0.3s, color 0.3s;
|
||||||
padding-top: 70px; /* Sesuaikan dengan tinggi Navbar */
|
|
||||||
padding-left: 250px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.light {
|
body.light {
|
||||||
@ -16,31 +14,169 @@ body.dark {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-transition {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeIn 0.5s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.3s, transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light Mode */
|
||||||
|
.light .theme-toggle-btn {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
color: #1d1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark Mode */
|
||||||
|
.dark .theme-toggle-btn {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover Effect */
|
||||||
|
.theme-toggle-btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar-app {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
margin-left: 0; /* Pastikan kontennya full width */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.menu-toggle-btn, .sidebar-toggle-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
margin-top: 75px;
|
||||||
|
margin-left: 250px; /* Pastikan kontennya full width */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling modal */
|
||||||
|
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.menu-toggle-btn,
|
||||||
|
.sidebar-toggle-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling modal menyesuaikan dengan tema */
|
||||||
|
.sidebar-modal .modal-content {
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .sidebar-modal .modal-content {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .sidebar-modal .modal-content {
|
||||||
|
background-color: #161616;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-modal .modal-header {
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .sidebar-modal .modal-header {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-modal .modal-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-modal .modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-modal .nav-link {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .sidebar-modal .nav-link {
|
||||||
|
color: #1d1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .sidebar-modal .nav-link {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .sidebar-modal .nav-link:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .sidebar-modal .nav-link:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.navbar-app {
|
.navbar-app {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
transition: background-color 0.3s, color 0.3s, box-shadow 0.3s;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Default shadow */
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.navbar-app {
|
/* Light Mode Navbar */
|
||||||
|
.light .navbar-app {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Soft shadow */
|
||||||
}
|
}
|
||||||
|
|
||||||
.light.navbar-brand {
|
.light .navbar-brand {
|
||||||
color: #161616;
|
color: #161616;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.navbar-app {
|
/* Dark Mode Navbar */
|
||||||
|
.dark .navbar-app {
|
||||||
background-color: #161616;
|
background-color: #161616;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1); /* Lebih jelas di dark mode */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark.navbar-brand {
|
.dark .navbar-app:hover {
|
||||||
transition: background-color 0.3s, color 0.3s;
|
box-shadow: 0 6px 18px rgba(255, 255, 255, 0.2); /* Efek hover agar terlihat lebih elegan */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .navbar-brand {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,13 +206,30 @@ button:hover {
|
|||||||
/* border-right: 1px solid #414141; */
|
/* border-right: 1px solid #414141; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-app.closed {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-dropdown-toggle {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Styling Default Link */
|
/* Styling Default Link */
|
||||||
.sidebar-app .nav-link {
|
.sidebar-app .nav-link {
|
||||||
margin: 5px 15px;
|
margin: 5px 15px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
transition: background 0.3s, color 0.3s;
|
|
||||||
text-decoration: none; /* Hilangkan underline */
|
text-decoration: none; /* Hilangkan underline */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,3 +244,380 @@ button:hover {
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LIGHT MODE - Sidebar */
|
||||||
|
.light .sidebar-app {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #1d1d1d;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK MODE - Sidebar */
|
||||||
|
.dark .sidebar-app {
|
||||||
|
background-color: #161616;
|
||||||
|
color: #ffffff;
|
||||||
|
border-right: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIGHT MODE - Sidebar Link */
|
||||||
|
.light .nav-link {
|
||||||
|
color: #1d1d1d;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK MODE - Sidebar Link */
|
||||||
|
.dark .nav-link {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover Effect */
|
||||||
|
.light .nav-link:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .nav-link:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aktif Link */
|
||||||
|
.light .nav-link.active {
|
||||||
|
background-color:#d32f2f;
|
||||||
|
color:rgb(255, 255, 255) !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .nav-link.active {
|
||||||
|
background-color:#EF5350;
|
||||||
|
color: white !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================= */
|
||||||
|
/* CARD STYLING - START */
|
||||||
|
/* ======================= */
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .card-header {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #1d1d1d;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .card {
|
||||||
|
background-color: #252525;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .card-header {
|
||||||
|
background-color: #333333;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling untuk Card Header */
|
||||||
|
.card-header {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling untuk Card Body */
|
||||||
|
.card-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling untuk Card Title */
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover Effect */
|
||||||
|
.card:hover {
|
||||||
|
transform: scale(1.03);
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================= */
|
||||||
|
/* CARD STYLING - END */
|
||||||
|
/* ======================= */
|
||||||
|
|
||||||
|
/* ======================= */
|
||||||
|
/* METRICS GRID TABLE */
|
||||||
|
/* ======================= */
|
||||||
|
|
||||||
|
.metrics-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-header, .metrics-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 1fr 1fr 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styling */
|
||||||
|
.metrics-header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIGHT MODE */
|
||||||
|
.light .metrics-container {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .metrics-header {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .metrics-body .metrics-row {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .metrics-body .metrics-row:nth-child(even) {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK MODE */
|
||||||
|
.dark .metrics-container {
|
||||||
|
background-color: #161616;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .metrics-header {
|
||||||
|
background-color: #333333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .metrics-body .metrics-row {
|
||||||
|
background-color: #222222;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .metrics-body .metrics-row:nth-child(even) {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link Styling */
|
||||||
|
.metrics-row a {
|
||||||
|
color: #4CAF50;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metrics-row a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.metrics-header, .metrics-row {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr; /* Kurangi jumlah kolom agar lebih rapi di mobile */
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================= */
|
||||||
|
/* DATA TABLE GRID STYLING */
|
||||||
|
/* ======================= */
|
||||||
|
|
||||||
|
.data-table-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table-header, .data-table-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 2fr 2fr 2fr 2fr 3fr 3fr;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HEADER */
|
||||||
|
.data-table-header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIGHT MODE */
|
||||||
|
.light .data-table-container {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .data-table-header {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .data-table-body .data-table-row {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .data-table-body .data-table-row:nth-child(even) {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK MODE */
|
||||||
|
.dark .data-table-container {
|
||||||
|
background-color: #161616;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .data-table-header {
|
||||||
|
background-color: #333333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .data-table-body .data-table-row {
|
||||||
|
background-color: #222222;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .data-table-body .data-table-row:nth-child(even) {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.data-table-header, .data-table-row {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr; /* Kurangi jumlah kolom di mobile */
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================= */
|
||||||
|
/* DEVICE TABLE GRID STYLE */
|
||||||
|
/* ======================= */
|
||||||
|
|
||||||
|
.device-table-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-header, .device-table-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3fr 3fr 2fr;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HEADER */
|
||||||
|
.device-table-header {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIGHT MODE */
|
||||||
|
.light .device-table-container {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .device-table-header {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .device-table-body .device-table-row {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1d1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .device-table-body .device-table-row:nth-child(even) {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DARK MODE */
|
||||||
|
.dark .device-table-container {
|
||||||
|
background-color: #161616;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .device-table-header {
|
||||||
|
background-color: #333333;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .device-table-body .device-table-row {
|
||||||
|
background-color: #222222;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .device-table-body .device-table-row:nth-child(even) {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Styling */
|
||||||
|
.status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.device-table-header, .device-table-row {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/App.jsx
24
src/App.jsx
@ -1,7 +1,7 @@
|
|||||||
import "bootstrap/dist/css/bootstrap.min.css"
|
import "bootstrap/dist/css/bootstrap.min.css"
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import Sidebar from './components/layout/Sidebar';
|
import Sidebar from './components/layout/Sidebar';
|
||||||
import Navbar from './components/layout/Navbar';
|
import AppNavbar from './components/layout/Navbar';
|
||||||
import Dashboard from './pages/Dashboard';
|
import Dashboard from './pages/Dashboard';
|
||||||
import DevicesPage from './pages/DevicesPage';
|
import DevicesPage from './pages/DevicesPage';
|
||||||
import ReportPage from "./pages/ReportPage";
|
import ReportPage from "./pages/ReportPage";
|
||||||
@ -10,8 +10,8 @@ import { useEffect, useState } from "react";
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
const [theme, setTheme] = useState(localStorage.getItem("theme") || "light");
|
const [theme, setTheme] = useState(localStorage.getItem("theme") || "light");
|
||||||
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.className = theme;
|
document.body.className = theme;
|
||||||
@ -22,18 +22,22 @@ function App() {
|
|||||||
setTheme(prevTheme => (prevTheme === "light" ? "dark" : "light"));
|
setTheme(prevTheme => (prevTheme === "light" ? "dark" : "light"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleSidebar = () => {
|
||||||
|
setIsSidebarOpen(!isSidebarOpen);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<div className={`app-container ${theme}`}>
|
<div className={`app-container ${theme} theme-transition`}>
|
||||||
<Navbar toggleTheme={toggleTheme} theme={theme} className={`${theme}`}/>
|
<AppNavbar toggleTheme={toggleTheme} theme={theme} toggleSidebar={toggleSidebar} />
|
||||||
<Sidebar />
|
<Sidebar theme={theme} isOpen={isSidebarOpen} />
|
||||||
<div className={`main-content ${theme}`}>
|
<div className={`main-content ${theme}`}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/dashboard" />}/>
|
<Route path="/" element={<Navigate to="/dashboard"/>} />
|
||||||
<Route path="/dashboard" element={<Dashboard />}/>
|
<Route path="/dashboard" element={<Dashboard theme={theme}/>}/>
|
||||||
<Route path="/devices" element={<DevicesPage />}/>
|
<Route path="/devices" element={<DevicesPage theme={theme}/>} />
|
||||||
<Route path="/reports" element={<ReportPage />}/>
|
<Route path="/reports" element={<ReportPage theme={theme}/>} />
|
||||||
<Route path="/settings" element={<SettingsPage />}/>
|
<Route path="/settings" element={<SettingsPage theme={theme}/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import ReactApexChart from 'react-apexcharts';
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
const DownloadChart = ({ data }) => {
|
const DownloadChart = ({ data, theme }) => {
|
||||||
|
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
const chartData = {
|
const chartData = {
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
@ -15,39 +18,39 @@ const DownloadChart = ({ data }) => {
|
|||||||
chart: {
|
chart: {
|
||||||
height: 250,
|
height: 250,
|
||||||
type: 'area',
|
type: 'area',
|
||||||
background: '#1e1e1e',
|
background: isDark ? '#1e1e1e' : '#ffffff',
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
},
|
},
|
||||||
dataLabels: { enabled: false },
|
dataLabels: { enabled: false },
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'smooth',
|
curve: 'smooth',
|
||||||
colors: ['#4CAF50'],
|
colors: [isDark ? '#4CAF50' : '#28a745'],
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: '#ffffff' } },
|
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: isDark ? '#ffffff' : '#333333'} },
|
||||||
axisBorder: { color: '#777777' },
|
axisBorder: { color: isDark ? '#777777' : '#cccccc' },
|
||||||
axisTicks: { color: '#777777' },
|
axisTicks: { color: isDark ? '#777777' : '#cccccc' },
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: '#ffffff' } },
|
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: isDark ? '#ffffff' : '#333333' } },
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: '#777777',
|
borderColor: '#777777',
|
||||||
strokeDashArray: 4,
|
strokeDashArray: 4,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
theme: 'dark',
|
theme: isDark ? 'dark' : 'light',
|
||||||
x: { format: 'dd/MM/yy HH:mm' },
|
x: { format: 'dd/MM/yy HH:mm' },
|
||||||
y: { formatter: (val) => `${val} Mbps` },
|
y: { formatter: (val) => `${val} Mbps` },
|
||||||
},
|
},
|
||||||
colors: ['#4CAF50'],
|
colors: [ isDark ? '#4CAF50' : '#28a745'],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
shade: 'dark',
|
shade: isDark ? 'dark' : 'light',
|
||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
gradientToColors: ['#28a745'],
|
gradientToColors: [ isDark ? '#28a745' : '#4CAF50'],
|
||||||
stops: [0, 100],
|
stops: [0, 100],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import ReactApexChart from 'react-apexcharts';
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
const PingChart = ({ data }) => {
|
const PingChart = ({ data, theme }) => {
|
||||||
|
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
const chartData = {
|
const chartData = {
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
@ -16,39 +19,39 @@ const PingChart = ({ data }) => {
|
|||||||
chart: {
|
chart: {
|
||||||
height: 250,
|
height: 250,
|
||||||
type: 'area',
|
type: 'area',
|
||||||
background: '#1e1e1e',
|
background: isDark ? '#1e1e1e' : '#ffffff',
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
},
|
},
|
||||||
dataLabels: { enabled: false },
|
dataLabels: { enabled: false },
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'smooth',
|
curve: 'smooth',
|
||||||
colors: ['#ffc107'],
|
colors: [ isDark ? '#ffc107' : '#d4a307'],
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: '#ffffff' } },
|
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: isDark ? '#ffffff' : '#333333' } },
|
||||||
axisBorder: { color: '#777777' },
|
axisBorder: { color: isDark ? '#777777' : '#cccccc' },
|
||||||
axisTicks: { color: '#777777' },
|
axisTicks: { color: isDark ? '#777777' : '#cccccc' },
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: '#ffffff' } },
|
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: isDark ? '#ffffff' : '#333333' } },
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: '#777777',
|
borderColor: isDark ? '#777777' : '#cccccc',
|
||||||
strokeDashArray: 4,
|
strokeDashArray: 4,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
theme: 'dark',
|
theme: isDark ? 'dark' : 'light',
|
||||||
x: { format: 'dd/MM/yy HH:mm' },
|
x: { format: 'dd/MM/yy HH:mm' },
|
||||||
y: { formatter: (val) => `${val} Mbps` },
|
y: { formatter: (val) => `${val} Mbps` },
|
||||||
},
|
},
|
||||||
colors: ['#ffc107'],
|
colors: [ isDark ? '#ffc107' : '#d4a307'],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
shade: 'dark',
|
shade: isDark ? 'dark' : 'light',
|
||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
gradientToColors: ['#ffc107'],
|
gradientToColors: [ isDark ? '#ffc107' : '#d4a307'],
|
||||||
stops: [0, 100],
|
stops: [0, 100],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import ReactApexChart from 'react-apexcharts';
|
import ReactApexChart from 'react-apexcharts';
|
||||||
|
|
||||||
const UploadChart = ({ data }) => {
|
const UploadChart = ({ data, theme }) => {
|
||||||
|
|
||||||
|
const isDark = theme === 'dark';
|
||||||
|
|
||||||
const chartData = {
|
const chartData = {
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
@ -15,39 +18,39 @@ const UploadChart = ({ data }) => {
|
|||||||
chart: {
|
chart: {
|
||||||
height: 250,
|
height: 250,
|
||||||
type: 'area',
|
type: 'area',
|
||||||
background: '#1e1e1e',
|
background: isDark ? '#1e1e1e' : '#ffffff',
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
},
|
},
|
||||||
dataLabels: { enabled: false },
|
dataLabels: { enabled: false },
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'smooth',
|
curve: 'smooth',
|
||||||
colors: ['#00bcd4'],
|
colors: [ isDark ? '#00bcd4' : '#0097a7'],
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: '#ffffff' } },
|
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: isDark ? '#ffffff' : '#333333' } },
|
||||||
axisBorder: { color: '#777777' },
|
axisBorder: { color: isDark ? '#777777' : '#cccccc' },
|
||||||
axisTicks: { color: '#777777' },
|
axisTicks: { color: isDark ? '#777777' : '#cccccc' },
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: '#ffffff' } },
|
labels: { style: { fontFamily: 'Poppins, sans-serif', colors: isDark ? '#ffffff' : '#333333' } },
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: '#777777',
|
borderColor: isDark ? '#777777' : '#cccccc',
|
||||||
strokeDashArray: 4,
|
strokeDashArray: 4,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
theme: 'dark',
|
theme: isDark ? 'dark' : 'light',
|
||||||
x: { format: 'dd/MM/yy HH:mm' },
|
x: { format: 'dd/MM/yy HH:mm' },
|
||||||
y: { formatter: (val) => `${val} Mbps` },
|
y: { formatter: (val) => `${val} Mbps` },
|
||||||
},
|
},
|
||||||
colors: ['#00bcd4'],
|
colors: [ isDark ? '#00bcd4' : '#0097a7'],
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
shade: 'dark',
|
shade: isDark ? 'dark' : 'light',
|
||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
gradientToColors: ['#007bff'],
|
gradientToColors: [ isDark ? '#0097a7' : '#00bcd4'],
|
||||||
stops: [0, 100],
|
stops: [0, 100],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,41 +1,43 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import { Table } from 'react-bootstrap';
|
|
||||||
import { FaCheckCircle, FaTimesCircle } from "react-icons/fa";
|
import { FaCheckCircle, FaTimesCircle } from "react-icons/fa";
|
||||||
|
|
||||||
const AppDevice = ({ devices }) => {
|
const AppDevice = ({ devices, theme }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={`device-table-container ${theme}`}>
|
||||||
<Table striped bordered hover responsive>
|
{/* Header */}
|
||||||
<thead>
|
<div className="device-table-header">
|
||||||
<tr>
|
<div className="header-item">IP Address</div>
|
||||||
<th>IP Address</th>
|
<div className="header-item">Device Name</div>
|
||||||
<th>Device Name</th>
|
<div className="header-item">Status</div>
|
||||||
<th>Status</th>
|
</div>
|
||||||
</tr>
|
|
||||||
</thead>
|
{/* Body */}
|
||||||
<tbody>
|
<div className="device-table-body">
|
||||||
{devices.map((device) => (
|
{devices.map((device) => (
|
||||||
<tr key={device.id}>
|
<div className="device-table-row" key={device.id}>
|
||||||
<td>{device.ip}</td>
|
<div className="row-item">{device.ip}</div>
|
||||||
<td>{device.name}</td>
|
<div className="row-item">{device.name}</div>
|
||||||
<td style={{ color: device.is_online === "Online" ? "green" : "red", display: "flex", alignItems: "center" }}>
|
<div
|
||||||
|
className={`row-item status ${
|
||||||
|
device.is_online === "Online" ? "online" : "offline"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{device.is_online === "Online" ? (
|
{device.is_online === "Online" ? (
|
||||||
<>
|
<>
|
||||||
<FaCheckCircle className="me-2" color="green" />
|
<FaCheckCircle className="me-2" />
|
||||||
Online
|
Online
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<FaTimesCircle className="me-2" color="red" />
|
<FaTimesCircle className="me-2" />
|
||||||
Offline
|
Offline
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</div>
|
||||||
</Table>
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import { Card, Col, Row } from 'react-bootstrap';
|
import { Card, Col, Row } from 'react-bootstrap';
|
||||||
import { FaDownload, FaUpload, FaTachometerAlt } from 'react-icons/fa';
|
import { FaDownload, FaUpload, FaTachometerAlt } from 'react-icons/fa';
|
||||||
|
|
||||||
const AverageData = ({ data }) => {
|
const AverageData = ({ data, theme }) => {
|
||||||
const average = {
|
const average = {
|
||||||
download: 0,
|
download: 0,
|
||||||
upload: 0,
|
upload: 0,
|
||||||
@ -24,7 +24,7 @@ const AverageData = ({ data }) => {
|
|||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Card className="mb-4 shadow-sm bg-dark text-light">
|
<Card className={`card ${theme} mb-4 shadow-sm`}>
|
||||||
<Card.Header as="h6">
|
<Card.Header as="h6">
|
||||||
<FaDownload style={{ marginRight: '10px', width: '15px', color: '#28a745' }} />
|
<FaDownload style={{ marginRight: '10px', width: '15px', color: '#28a745' }} />
|
||||||
Average Download
|
Average Download
|
||||||
@ -35,7 +35,7 @@ const AverageData = ({ data }) => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Card className="mb-4 shadow-sm bg-dark text-light">
|
<Card className={`card ${theme} mb-4 shadow-sm`}>
|
||||||
<Card.Header as="h6">
|
<Card.Header as="h6">
|
||||||
<FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
<FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
||||||
Average Upload
|
Average Upload
|
||||||
@ -46,7 +46,7 @@ const AverageData = ({ data }) => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={4}>
|
<Col md={4}>
|
||||||
<Card className="mb-4 shadow-sm bg-dark text-light">
|
<Card className={`card ${theme} mb-4 shadow-sm`}>
|
||||||
<Card.Header as="h6">
|
<Card.Header as="h6">
|
||||||
<FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
<FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
||||||
Average Ping
|
Average Ping
|
||||||
|
|||||||
@ -10,35 +10,35 @@ const Chart = ({ data, theme }) => {
|
|||||||
<>
|
<>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md="4">
|
<Col md="4">
|
||||||
<Card className={`mb-4 shadow-sm ${theme}`}>
|
<Card className="mb-4 shadow-sm">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<FaDownload style={{ marginRight: '10px', color: '#28a745'}} />
|
<FaDownload style={{ marginRight: '10px', color: '#28a745'}} />
|
||||||
Download
|
Download
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<DownloadChart data={data} title="Download Speed" type="download" />
|
<DownloadChart theme={theme} data={data} title="Download Speed" type="download" />
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md="4">
|
<Col md="4">
|
||||||
<Card className={`mb-4 shadow-sm ${theme}`}>
|
<Card className="mb-4 shadow-sm">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
<FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
||||||
Upload
|
Upload
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<UploadChart data={data} title="Upload Speed" type="upload" />
|
<UploadChart theme={theme} data={data} title="Upload Speed" type="upload" />
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md="4">
|
<Col md="4">
|
||||||
<Card className={`mb-4 shadow-sm ${theme}`}>
|
<Card className="mb-4 shadow-sm">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>
|
<Card.Title>
|
||||||
<FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
<FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
||||||
Ping
|
Ping
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<PingChart data={data} title="Ping" type="ping" />
|
<PingChart theme={theme} data={data} title="Ping" type="ping" />
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@ -1,36 +1,33 @@
|
|||||||
import Table from 'react-bootstrap/Table';
|
|
||||||
|
|
||||||
const MetricsTable = ({ metrics = [] }) => {
|
const MetricsTable = ({ metrics = [], theme }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ overflowX: "auto", maxHeight: "450px", overflowY: "auto", position: "relative", borderTop: "1px solid #444", backgroundColor: "#1e1e1e" }}>
|
<div className={`metrics-container ${theme}`}>
|
||||||
<Table striped bordered hover responsive variant='dark' style={{ fontFamily: 'Poppins, sans-serif', minWidth: "900px", backgroundColor: "#1e1e1e", color: "#ffffff" }}>
|
<div className="metrics-header">
|
||||||
<thead>
|
<div>Monitor Name</div>
|
||||||
<tr style={{ backgroundColor: "#333333", color: "#ffffff" }}>
|
<div>URL</div>
|
||||||
<th className="py-3 text-center align-middle">Monitor Name</th>
|
<div>Response Time (ms)</div>
|
||||||
<th className="py-3 text-center align-middle">URL</th>
|
<div>Status</div>
|
||||||
<th className="py-3 text-center align-middle">Response Time (ms)</th>
|
<div>Certificate Valid</div>
|
||||||
<th className="py-3 text-center align-middle">Status</th>
|
<div>Cert Days Remaining</div>
|
||||||
<th className="py-3 text-center align-middle">Certificate Valid</th>
|
</div>
|
||||||
<th className="py-3 text-center align-middle">Cert Days Remaining</th>
|
<div className="metrics-body">
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{metrics.map((item, index) => (
|
{metrics.map((item, index) => (
|
||||||
<tr key={index} style={{ backgroundColor: "#222222", color: "#ffffff" }}>
|
<div key={index} className="metrics-row">
|
||||||
<td>{item.monitor_name}</td>
|
<div>{item.monitor_name}</div>
|
||||||
<td><a href={item.monitor_url} target="_blank" rel="noopener noreferrer" style={{ color: "#4CAF50" }}>{item.monitor_url}</a></td>
|
<div>
|
||||||
<td>{item.response_time} ms</td>
|
<a href={item.monitor_url} target="_blank" rel="noopener noreferrer">
|
||||||
<td>
|
{item.monitor_url}
|
||||||
{item.status === 1 ? 'UP' :
|
</a>
|
||||||
item.status === 0 ? 'DOWN' :
|
</div>
|
||||||
item.status === 2 ? 'PENDING' : 'MAINTENANCE'}
|
<div>{item.response_time} ms</div>
|
||||||
</td>
|
<div>
|
||||||
<td>{item.cert_valid === 1 ? 'Yes' : 'No'}</td>
|
{item.status === 1 ? "UP" : item.status === 0 ? "DOWN" : item.status === 2 ? "PENDING" : "MAINTENANCE"}
|
||||||
<td>{item.cert_days_remaining}</td>
|
</div>
|
||||||
</tr>
|
<div>{item.cert_valid === 1 ? "Yes" : "No"}</div>
|
||||||
|
<div>{item.cert_days_remaining}</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</div>
|
||||||
</Table>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,49 +1,59 @@
|
|||||||
/* eslint-disable react/prop-types */
|
import { format } from "date-fns";
|
||||||
import { Table } from 'react-bootstrap';
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { FaDownload, FaUpload, FaTachometerAlt } from "react-icons/fa";
|
import { FaDownload, FaUpload, FaTachometerAlt } from "react-icons/fa";
|
||||||
|
|
||||||
const DataTable = ({ data = [], theme }) => {
|
const DataTable = ({ data = [], theme }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ overflowX: "auto", maxHeight: "400px", overflowY: "auto", position: "relative", marginBottom: "20px"}}>
|
<div className={`data-table-container ${theme}`}>
|
||||||
<Table striped bordered hover responsive style={{ fontFamily: 'Poppins, sans-serif', minWidth: "900px"}} className={`${theme}`}>
|
{/* Header */}
|
||||||
<thead className="bg-secondary text-center" style={{ position: "sticky", top: 0, zIndex: 10, backgroundColor: "#343a40" }}>
|
<div className="data-table-header">
|
||||||
<tr>
|
<div className="header-item">No</div>
|
||||||
<th className="py-3 text-center align-middle">No</th>
|
<div className="header-item">
|
||||||
<th className="py-3 text-center align-middle" style={{ color: '#28a745' }}>
|
|
||||||
<FaDownload className="me-2" /> Bytes
|
<FaDownload className="me-2" /> Bytes
|
||||||
</th>
|
</div>
|
||||||
<th className="py-3 text-center align-middle" style={{ color: '#28a745' }}>
|
<div className="header-item">
|
||||||
<FaDownload className="me-2" /> Elapsed
|
<FaDownload className="me-2" /> Elapsed
|
||||||
</th>
|
</div>
|
||||||
<th className="py-3 text-center align-middle" style={{ color: '#00bcd4' }}>
|
<div className="header-item">
|
||||||
<FaUpload className="me-2" /> Bytes
|
<FaUpload className="me-2" /> Bytes
|
||||||
</th>
|
</div>
|
||||||
<th className="py-3 text-center align-middle" style={{ color: '#00bcd4' }}>
|
<div className="header-item">
|
||||||
<FaUpload className="me-2" /> Elapsed
|
<FaUpload className="me-2" /> Elapsed
|
||||||
</th>
|
</div>
|
||||||
<th className="py-3 text-center align-middle" style={{ color: '#ffc107' }}>
|
<div className="header-item">
|
||||||
<FaTachometerAlt className="me-2" /> Jitter
|
<FaTachometerAlt className="me-2" /> Jitter
|
||||||
</th>
|
</div>
|
||||||
<th className="py-3 text-center align-middle">Time</th>
|
<div className="header-item">Time</div>
|
||||||
<th className="py-3 text-center align-middle">Server</th>
|
<div className="header-item">Server</div>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
|
||||||
<tbody>
|
{/* Body */}
|
||||||
|
<div className="data-table-body">
|
||||||
{data.map((item, index) => (
|
{data.map((item, index) => (
|
||||||
<tr key={item._id}>
|
<div className="data-table-row" key={item._id}>
|
||||||
<td className="py-3 text-center">{index + 1}</td>
|
<div className="row-item">{index + 1}</div>
|
||||||
<td className="py-3 text-center">{(item.data.download.bytes / 1048576).toFixed(1)} Mbps</td>
|
<div className="row-item">
|
||||||
<td className="py-3 text-center">{(item.data.download.elapsed / 1000).toFixed(2)}s</td>
|
{(item.data.download.bytes / 1048576).toFixed(1)} Mbps
|
||||||
<td className="py-3 text-center">{(item.data.upload.bytes / 1048576).toFixed(1)} Mbps</td>
|
</div>
|
||||||
<td className="py-3 text-center">{(item.data.upload.elapsed / 1000).toFixed(2)}s</td>
|
<div className="row-item">
|
||||||
<td className="py-3 text-center">{(item.data.ping.jitter).toFixed(1)}ms</td>
|
{(item.data.download.elapsed / 1000).toFixed(2)}s
|
||||||
<td className="py-3 text-center">{format(new Date(item.datetime), 'eeee, d MMMM yyyy HH:mm:ss')}</td>
|
</div>
|
||||||
<td className="py-3 text-center">{item.data.isp} ({item.data.server.name}, {item.data.server.location})</td>
|
<div className="row-item">
|
||||||
</tr>
|
{(item.data.upload.bytes / 1048576).toFixed(1)} Mbps
|
||||||
|
</div>
|
||||||
|
<div className="row-item">
|
||||||
|
{(item.data.upload.elapsed / 1000).toFixed(2)}s
|
||||||
|
</div>
|
||||||
|
<div className="row-item">{item.data.ping.jitter.toFixed(1)}ms</div>
|
||||||
|
<div className="row-item">
|
||||||
|
{format(new Date(item.datetime), "eeee, d MMMM yyyy HH:mm:ss")}
|
||||||
|
</div>
|
||||||
|
<div className="row-item">
|
||||||
|
{item.data.isp} ({item.data.server.name},{" "}
|
||||||
|
{item.data.server.location})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</div>
|
||||||
</Table>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,22 +1,52 @@
|
|||||||
import { Navbar, Nav } from 'react-bootstrap';
|
import { Navbar, Nav, Modal, Button } from 'react-bootstrap';
|
||||||
import { FaChartLine, FaSun, FaMoon } from 'react-icons/fa';
|
import { FaChartLine, FaSun, FaMoon, FaBars } from 'react-icons/fa';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const AppNavbar = ({ toggleTheme, theme }) => {
|
const AppNavbar = ({ toggleTheme, theme }) => {
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar
|
<>
|
||||||
className={`${theme} navbar-app fixed-top w-100 shadow-sm d-flex justify-content-between`}
|
<Navbar className={`${theme} navbar-app fixed-top w-100 shadow-sm d-flex justify-content-between`} style={{ zIndex: 1030, padding: '15px 20px' }}>
|
||||||
style={{ zIndex: 1030, padding: '15px 20px' }}
|
<div className="d-flex align-items-center">
|
||||||
>
|
<Navbar.Brand className={`${theme}`} href="#home" style={{ marginLeft: '10px' }}>
|
||||||
<Navbar.Brand className={`${theme}`} href="#home" style={{ marginLeft: '20px'}}>
|
<FaChartLine style={{ marginRight: '10px', color: '#b31e1e' }} /> Speedtest
|
||||||
<FaChartLine style={{ marginRight: '10px', color: '#b31e1e' }} />
|
|
||||||
Speedtest Tracker
|
|
||||||
</Navbar.Brand>
|
</Navbar.Brand>
|
||||||
<Nav className="ml-auto" style={{ marginRight: '20px' }}>
|
</div>
|
||||||
<button onClick={toggleTheme} className="theme-toggle-btn">
|
<Nav className="ml-auto d-flex align-items-center" style={{ marginRight: '20px' }}>
|
||||||
|
<button onClick={toggleTheme} className="theme-toggle-btn me-3">
|
||||||
{theme === 'light' ? <FaMoon size={20} /> : <FaSun size={20} />}
|
{theme === 'light' ? <FaMoon size={20} /> : <FaSun size={20} />}
|
||||||
</button>
|
</button>
|
||||||
|
<button className="sidebar-toggle-btn" onClick={() => setShowModal(true)}>
|
||||||
|
<FaBars size={20} />
|
||||||
|
</button>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
|
{/* Modal Sidebar */}
|
||||||
|
<Modal className={`${theme}`} show={showModal} onHide={() => setShowModal(false)} centered>
|
||||||
|
<Modal.Header className={`${theme}`} closeButton>
|
||||||
|
<Modal.Title className={`${theme}`}>Menu</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body className={`${theme}`}>
|
||||||
|
<Nav className={`${theme} flex-column`}>
|
||||||
|
<Nav.Link as={Link} to="/dashboard" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||||
|
Dashboard
|
||||||
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/devices" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||||
|
Devices
|
||||||
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/reports" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||||
|
Reports
|
||||||
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/settings" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||||
|
Settings
|
||||||
|
</Nav.Link>
|
||||||
|
</Nav>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,48 +1,29 @@
|
|||||||
import { Navbar, Nav } from 'react-bootstrap';
|
import { Navbar, Nav } from 'react-bootstrap';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { FaTachometerAlt, FaDesktop, FaChartBar, FaCog } from "react-icons/fa";
|
import { FaTachometerAlt, FaDesktop, FaChartBar, FaCog, FaBars } from "react-icons/fa";
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
const Sidebar = ({theme}) => {
|
|
||||||
|
|
||||||
|
const Sidebar = ({ theme }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar expand="lg" className={`sidebar-app flex-column ${theme}`}>
|
<div className={`sidebar-app ${theme}`}>
|
||||||
<Nav className="flex-column w-100">
|
<Nav className="flex-column w-100">
|
||||||
<Nav.Link
|
<Nav.Link as={Link} to="/dashboard" className={location.pathname === "/dashboard" ? "active" : ""}>
|
||||||
as={Link}
|
|
||||||
to="/dashboard"
|
|
||||||
className={location.pathname === "/dashboard" ? "active" : ""}
|
|
||||||
>
|
|
||||||
<FaTachometerAlt className="me-2" /> Dashboard
|
<FaTachometerAlt className="me-2" /> Dashboard
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/devices" className={location.pathname === "/devices" ? "active" : ""}>
|
||||||
<Nav.Link
|
|
||||||
as={Link}
|
|
||||||
to="/devices"
|
|
||||||
className={location.pathname === "/devices" ? "active" : ""}
|
|
||||||
>
|
|
||||||
<FaDesktop className="me-2" /> Devices
|
<FaDesktop className="me-2" /> Devices
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/reports" className={location.pathname === "/reports" ? "active" : ""}>
|
||||||
<Nav.Link
|
|
||||||
as={Link}
|
|
||||||
to="/reports"
|
|
||||||
className={location.pathname === "/reports" ? "active" : ""}
|
|
||||||
>
|
|
||||||
<FaChartBar className="me-2" /> Reports
|
<FaChartBar className="me-2" /> Reports
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/settings" className={location.pathname === "/settings" ? "active" : ""}>
|
||||||
<Nav.Link
|
|
||||||
as={Link}
|
|
||||||
to="/settings"
|
|
||||||
className={location.pathname === "/settings" ? "active" : ""}
|
|
||||||
>
|
|
||||||
<FaCog className="me-2" /> Settings
|
<FaCog className="me-2" /> Settings
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Container, Spinner, Alert, Row, Col } from 'react-bootstrap';
|
import { Container, Spinner, Alert, Row, Col } from 'react-bootstrap';
|
||||||
import { fetchSpeedTestData, fetchMetricsData } from '../services/api';
|
import { fetchSpeedTestData } from '../services/api';
|
||||||
import AverageData from '../components/dashboard/AverageData';
|
import AverageData from '../components/dashboard/AverageData';
|
||||||
import Chart from '../components/dashboard/ChartData';
|
import Chart from '../components/dashboard/ChartData';
|
||||||
import DataTable from '../components/dashboard/TableData';
|
import DataTable from '../components/dashboard/TableData';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = ({theme}) => {
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@ -42,18 +42,18 @@ const Dashboard = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container fluid className='flex'>
|
<Container fluid className={`flex dashboard-page ${theme}`}>
|
||||||
<Col className="p-3 ms-auto">
|
<Col className="p-3 ms-auto">
|
||||||
<h4 className='my-3'>Dashboard</h4>
|
<h4 className='my-3'>Dashboard</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<AverageData data={data} />
|
<AverageData data={data} theme={theme}/>
|
||||||
<Chart data={data} />
|
<Chart data={data} theme={theme}/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<DataTable data={data} />
|
<DataTable data={data} theme={theme}/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import MetricsTable from '../components/dashboard/MetricsTable';
|
|||||||
import AppDevice from '../components/dashboard/AppDevice';
|
import AppDevice from '../components/dashboard/AppDevice';
|
||||||
|
|
||||||
|
|
||||||
function DevicesPage() {
|
function DevicesPage({theme}) {
|
||||||
|
|
||||||
const [metrics, setMetrics] = useState([]);
|
const [metrics, setMetrics] = useState([]);
|
||||||
const [devices, setDevices] = useState([]);
|
const [devices, setDevices] = useState([]);
|
||||||
@ -36,7 +36,7 @@ function DevicesPage() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Container fluid className="text-center mt-5">
|
<Container fluid className="text-center mt-5">
|
||||||
<Spinner animation="border" variant="info"/>
|
<Spinner animation="border" variant="danger"/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -50,11 +50,11 @@ function DevicesPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container fluid className='d-flex' style={{ fontFamily: 'Poppins, sans-serif'}}>
|
<Container fluid className={`device-page flex {$theme}`} style={{ fontFamily: 'Poppins, sans-serif'}}>
|
||||||
<Col className="p-3 ms-auto">
|
<Col className="p-3 ms-auto">
|
||||||
<h4 className='my-3'>Devices Page</h4>
|
<h4 className='my-3'>Devices Page</h4>
|
||||||
<AppDevice devices={devices}/>
|
<AppDevice devices={devices} theme={theme}/>
|
||||||
<MetricsTable metrics={metrics}/>
|
<MetricsTable metrics={metrics} theme={theme}/>
|
||||||
</Col>
|
</Col>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user