Compare commits
15 Commits
b6da8a5ad5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8410a50f26 | ||
|
|
053662c2e8 | ||
|
|
cc4e915cf5 | ||
|
|
030d72c202 | ||
|
|
a08c4eba3b | ||
|
|
a1aee4134c | ||
|
|
9424f1857f | ||
|
|
bcb7d7ea7d | ||
|
|
e9908bd65b | ||
|
|
8c9cb042ff | ||
|
|
d96fa63298 | ||
|
|
5a11305caf | ||
|
|
e31bd43eaa | ||
|
|
ca0007515f | ||
|
|
54007325c3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ etsyTokens.json
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules/
|
node_modules/
|
||||||
vendor/
|
vendor/
|
||||||
|
logs/
|
||||||
57
.htaccess
Normal file
57
.htaccess
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Fix for nginx proxy to avoid internal server errors
|
||||||
|
RewriteBase /
|
||||||
|
|
||||||
|
# Redirect all traffic to the public folder, but allow existing files/directories
|
||||||
|
RewriteCond %{REQUEST_URI} !^/public/
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f [OR]
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -d
|
||||||
|
RewriteRule ^(.*)$ /public/$1 [L,QSA]
|
||||||
|
|
||||||
|
# Handle cases where the file doesn't exist
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule . /public/index.php [L]
|
||||||
|
|
||||||
|
# Ensure directory listing is disabled
|
||||||
|
Options -Indexes
|
||||||
|
|
||||||
|
# Enable compression
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Leverage browser caching
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresByType text/html "access plus 1 month"
|
||||||
|
ExpiresByType image/gif "access plus 1 year"
|
||||||
|
ExpiresByType image/jpeg "access plus 1 year"
|
||||||
|
ExpiresByType image/png "access plus 1 year"
|
||||||
|
ExpiresByType text/css "access plus 1 month"
|
||||||
|
ExpiresByType text/javascript "access plus 1 month"
|
||||||
|
ExpiresByType application/javascript "access plus 1 month"
|
||||||
|
ExpiresByType application/x-shockwave-flash "access plus 1 month"
|
||||||
|
ExpiresByType application/pdf "access plus 1 month"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Basic security headers
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
Header set X-Content-Type-Options "nosniff"
|
||||||
|
Header set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header set X-XSS-Protection "1; mode=block"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Handle 404 errors
|
||||||
|
ErrorDocument 404 /public/404.html
|
||||||
|
|
||||||
|
# Handle PHP execution if needed
|
||||||
|
<FilesMatch "\.php$">
|
||||||
|
SetHandler application/x-httpd-php
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# Deny access to sensitive files
|
||||||
|
<FilesMatch "^\.(htaccess|htpasswd|env|ini|log|sh|sql|bak|config)$">
|
||||||
|
Require all denied
|
||||||
|
</FilesMatch>
|
||||||
252
addPrinter.php
252
addPrinter.php
@@ -1,252 +0,0 @@
|
|||||||
<?php include 'assets/php/session_check.php'; ?>
|
|
||||||
|
|
||||||
<html lang="en" data-bs-theme="light">
|
|
||||||
<head>
|
|
||||||
<!-- Required meta tags -->
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<!--favicon-->
|
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png">
|
|
||||||
<!--plugins-->
|
|
||||||
<link href="assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
|
||||||
<link href="assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
|
||||||
<link href="assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
|
||||||
<link href="assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
|
||||||
<!-- loader-->
|
|
||||||
<link href="assets/css/pace.min.css" rel="stylesheet"/>
|
|
||||||
<script src="assets/js/pace.min.js"></script>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="assets/css/bootstrap-extended.css" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<link href="assets/sass/app.css" rel="stylesheet">
|
|
||||||
<link href="assets/css/icons.css" rel="stylesheet">
|
|
||||||
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
|
||||||
<!-- Theme Style CSS -->
|
|
||||||
<link rel="stylesheet" href="assets/sass/dark-theme.css">
|
|
||||||
<link rel="stylesheet" href="assets/sass/semi-dark.css">
|
|
||||||
<link rel="stylesheet" href="assets/sass/bordered-theme.css">
|
|
||||||
|
|
||||||
<title>TOD Dashboard</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!--wrapper-->
|
|
||||||
<div class="wrapper">
|
|
||||||
<!--sidebar wrapper -->
|
|
||||||
<?php include 'assets/php/nav.php'; ?>
|
|
||||||
<!--end sidebar wrapper -->
|
|
||||||
<!--start header -->
|
|
||||||
<header>
|
|
||||||
<div class="topbar">
|
|
||||||
<nav class="navbar navbar-expand gap-2 align-items-center">
|
|
||||||
<div class="mobile-toggle-menu d-flex"><i class='bx bx-menu'></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-bar d-lg-block d-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
|
||||||
<a href="avascript:;" class="btn d-flex align-items-center"><i class="bx bx-search"></i>Search</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="top-menu ms-auto">
|
|
||||||
<ul class="navbar-nav align-items-center gap-1">
|
|
||||||
<li class="nav-item mobile-search-icon d-flex d-lg-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
|
||||||
<a class="nav-link" href="avascript:;"><i class='bx bx-search'></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item dark-mode d-none d-sm-flex">
|
|
||||||
<a class="nav-link dark-mode-icon" href="javascript:;"><i class='bx bx-moon'></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item dropdown dropdown-large">
|
|
||||||
<a class="nav-link dropdown-toggle dropdown-toggle-nocaret position-relative" href="#" data-bs-toggle="dropdown"><span class="alert-count">7</span>
|
|
||||||
<i class='bx bx-bell'></i>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
|
||||||
<a href="javascript:;">
|
|
||||||
<div class="msg-header">
|
|
||||||
<p class="msg-header-title">Notifications</p>
|
|
||||||
<p class="msg-header-badge">8 New</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="header-notifications-list">
|
|
||||||
<a class="dropdown-item" href="javascript:;">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="user-online">
|
|
||||||
<img src="assets/images/avatars/avatar-1.png" class="msg-avatar" alt="user avatar">
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h6 class="msg-name">Daisy Anderson<span class="msg-time float-end">5 sec
|
|
||||||
ago</span></h6>
|
|
||||||
<p class="msg-info">The standard chunk of lorem</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a href="javascript:;">
|
|
||||||
<div class="text-center msg-footer">
|
|
||||||
<button class="btn btn-primary w-100">View All Notifications</button>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="user-box dropdown px-3">
|
|
||||||
<a class="d-flex align-items-center nav-link dropdown-toggle gap-3 dropdown-toggle-nocaret" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<img src="assets/images/avatars/avatar-2.png" class="user-img" alt="user avatar">
|
|
||||||
<div class="user-info">
|
|
||||||
<p class="user-name mb-0"><?php echo isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : 'Guest'; ?></p>
|
|
||||||
<p class="designattion mb-0"><?php echo isset($_SESSION['role']) ? htmlspecialchars($_SESSION['role']) : 'N/A'; ?></p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-cog fs-5"></i><span>My Printers</span></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="dropdown-divider mb-0"></div>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="logout.php"><i class="bx bx-log-out-circle"></i><span>Logout</span></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<!--end header -->
|
|
||||||
|
|
||||||
<!--start page wrapper -->
|
|
||||||
<div class="page-wrapper">
|
|
||||||
<div class="page-content">
|
|
||||||
<!--start page content -->
|
|
||||||
|
|
||||||
<!--breadcrumb-->
|
|
||||||
<div class="page-breadcrumb d-none d-sm-flex align-items-center mb-3">
|
|
||||||
<div class="breadcrumb-title pe-3">User Settings</div>
|
|
||||||
<div class="ps-3">
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb mb-0 p-0">
|
|
||||||
<li class="breadcrumb-item"><a href="javascript:;"><i class="bx bx-home-alt"></i></a>
|
|
||||||
</li>
|
|
||||||
<li class="breadcrumb-item active" aria-current="page">Add Printer</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--end breadcrumb-->
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-8 mx-auto">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header px-4 py-3">
|
|
||||||
<h5 class="mb-0">Add New Printer</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-4">
|
|
||||||
<form id="printerForm" method="post" action="addPrinter.php">
|
|
||||||
<!-- Printer Name -->
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label for="printerName" class="col-sm-3 col-form-label">Printer Name</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" class="form-control" id="printerName" name="printerName" placeholder="Bambu X1 or P1P" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Printer IP -->
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label for="printerIp" class="col-sm-3 col-form-label">Printer IP</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" class="form-control" id="printerIp" name="printerIp" placeholder="e.g., 192.168.1.100" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Serial Number -->
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label for="serialNumber" class="col-sm-3 col-form-label">Serial Number</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" class="form-control" id="serialNumber" name="serialNumber" placeholder="e.g., X1SN123456" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Printer Access Code -->
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label for="accessCode" class="col-sm-3 col-form-label">Printer Access Code</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="password" class="form-control" id="accessCode" name="accessCode" placeholder="Enter Printer Access Code" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit and Reset Buttons -->
|
|
||||||
<div class="row">
|
|
||||||
<label class="col-sm-3 col-form-label"></label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="d-md-flex d-grid align-items-center gap-3">
|
|
||||||
<button type="submit" class="btn btn-primary px-4">Add Printer</button>
|
|
||||||
<button type="reset" class="btn btn-light px-4">Reset</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--end page content -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--end page wrapper -->
|
|
||||||
|
|
||||||
<!--start overlay-->
|
|
||||||
<div class="overlay mobile-toggle-icon"></div>
|
|
||||||
<!--end overlay-->
|
|
||||||
<!--Start Back To Top Button-->
|
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
|
||||||
<!--End Back To Top Button-->
|
|
||||||
<footer class="page-footer">
|
|
||||||
<p class="mb-0">Copyright © 2024. All right reserved.</p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<!--end wrapper-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- search modal -->
|
|
||||||
<div class="modal" id="SearchModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header gap-2">
|
|
||||||
<div class="position-relative popup-search w-100">
|
|
||||||
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
|
||||||
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- end search modal -->
|
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
|
||||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<!--plugins-->
|
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
|
||||||
<script src="assets/plugins/simplebar/js/simplebar.min.js"></script>
|
|
||||||
<script src="assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
|
||||||
<script src="assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
|
||||||
<script src="assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
|
||||||
<!--app JS-->
|
|
||||||
<script src="assets/js/app.js"></script>
|
|
||||||
|
|
||||||
<script src="assets/js/index.js"></script>
|
|
||||||
<script src="assets/plugins/peity/jquery.peity.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(".data-attributes span").peity("donut")
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -3780,7 +3780,7 @@ form select.error:focus {
|
|||||||
|
|
||||||
.btn-inverse-success {
|
.btn-inverse-success {
|
||||||
|
|
||||||
color: #15ca20;
|
color: #1aad24;
|
||||||
|
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
|
|
||||||
@@ -3792,7 +3792,7 @@ form select.error:focus {
|
|||||||
|
|
||||||
.btn-inverse-success:hover {
|
.btn-inverse-success:hover {
|
||||||
|
|
||||||
color: #15ca20;
|
color: #1aad24;
|
||||||
|
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
|
|
||||||
@@ -3802,7 +3802,7 @@ form select.error:focus {
|
|||||||
|
|
||||||
.btn-inverse-success:focus {
|
.btn-inverse-success:focus {
|
||||||
|
|
||||||
color: #15ca20;
|
color: #1aad24;
|
||||||
|
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
|
|
||||||
|
|||||||
50
assets/css/bootstrap-extended.css
vendored
50
assets/css/bootstrap-extended.css
vendored
@@ -23,33 +23,33 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.valid-feedback {
|
.valid-feedback {
|
||||||
color: #15ca20
|
color: #23ac2c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-control.is-valid, .was-validated .form-control:valid {
|
.form-control.is-valid, .was-validated .form-control:valid {
|
||||||
border-color: #15ca20;
|
border-color: #23ac2c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control.is-valid:focus, .was-validated .form-control:valid:focus {
|
.form-control.is-valid:focus, .was-validated .form-control:valid:focus {
|
||||||
border-color: #15ca20;
|
border-color: #23ac2c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select.is-valid, .was-validated .form-select:valid {
|
.form-select.is-valid, .was-validated .form-select:valid {
|
||||||
border-color: #15ca20;
|
border-color: #23ac2c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select.is-valid:focus, .was-validated .form-select:valid:focus {
|
.form-select.is-valid:focus, .was-validated .form-select:valid:focus {
|
||||||
border-color: #15ca20;
|
border-color: #23ac2c;
|
||||||
}
|
}
|
||||||
.form-check-input.is-valid, .was-validated .form-check-input:valid {
|
.form-check-input.is-valid, .was-validated .form-check-input:valid {
|
||||||
border-color: #15ca20
|
border-color: #23ac2c
|
||||||
}
|
}
|
||||||
.form-check-input.is-valid:checked, .was-validated .form-check-input:valid:checked {
|
.form-check-input.is-valid:checked, .was-validated .form-check-input:valid:checked {
|
||||||
background-color: #15ca20
|
background-color: #23ac2c
|
||||||
}
|
}
|
||||||
.form-check-input.is-valid~.form-check-label, .was-validated .form-check-input:valid~.form-check-label {
|
.form-check-input.is-valid~.form-check-label, .was-validated .form-check-input:valid~.form-check-label {
|
||||||
color: #15ca20
|
color: #23ac2c
|
||||||
}
|
}
|
||||||
|
|
||||||
.invalid-feedback {
|
.invalid-feedback {
|
||||||
@@ -120,8 +120,8 @@ a {
|
|||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #15ca20;
|
background-color: #2375ac;
|
||||||
border-color: #15ca20
|
border-color: #23ac2c
|
||||||
}
|
}
|
||||||
.btn-success:hover {
|
.btn-success:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -207,27 +207,27 @@ a {
|
|||||||
|
|
||||||
|
|
||||||
.btn-outline-success {
|
.btn-outline-success {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
border-color: #15ca20
|
border-color: #23ac2c
|
||||||
}
|
}
|
||||||
.btn-outline-success:hover {
|
.btn-outline-success:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #15ca20;
|
background-color: #23ac2c;
|
||||||
border-color: #15ca20
|
border-color: #23ac2c
|
||||||
}
|
}
|
||||||
.btn-check:focus+.btn-outline-success, .btn-outline-success:focus {
|
.btn-check:focus+.btn-outline-success, .btn-outline-success:focus {
|
||||||
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%)
|
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%)
|
||||||
}
|
}
|
||||||
.btn-check:active+.btn-outline-success, .btn-check:checked+.btn-outline-success, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show, .btn-outline-success:active {
|
.btn-check:active+.btn-outline-success, .btn-check:checked+.btn-outline-success, .btn-outline-success.active, .btn-outline-success.dropdown-toggle.show, .btn-outline-success:active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #15ca20;
|
background-color: #23ac2c;
|
||||||
border-color: #15ca20
|
border-color: #23ac2c
|
||||||
}
|
}
|
||||||
.btn-check:active+.btn-outline-success:focus, .btn-check:checked+.btn-outline-success:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus, .btn-outline-success:active:focus {
|
.btn-check:active+.btn-outline-success:focus, .btn-check:checked+.btn-outline-success:focus, .btn-outline-success.active:focus, .btn-outline-success.dropdown-toggle.show:focus, .btn-outline-success:active:focus {
|
||||||
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%)
|
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 52%)
|
||||||
}
|
}
|
||||||
.btn-outline-success.disabled, .btn-outline-success:disabled {
|
.btn-outline-success.disabled, .btn-outline-success:disabled {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: transparent
|
background-color: transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.border-success {
|
.border-success {
|
||||||
border-color: #15ca20!important
|
border-color: #23ac2c!important
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-danger {
|
.border-danger {
|
||||||
@@ -324,7 +324,7 @@ a {
|
|||||||
color: #6c757d!important
|
color: #6c757d!important
|
||||||
}
|
}
|
||||||
.text-success {
|
.text-success {
|
||||||
color: #15ca20!important
|
color: #23ac2c!important
|
||||||
}
|
}
|
||||||
.text-info {
|
.text-info {
|
||||||
color: #0dcaf0!important
|
color: #0dcaf0!important
|
||||||
@@ -364,7 +364,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bg-success {
|
.bg-success {
|
||||||
background-color: #15ca20 !important;
|
background-color: #23ac2c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-danger {
|
.bg-danger {
|
||||||
@@ -373,19 +373,19 @@ a {
|
|||||||
|
|
||||||
|
|
||||||
.form-check-success .form-check-input:checked {
|
.form-check-success .form-check-input:checked {
|
||||||
background-color: #15ca20;
|
background-color: #23ac2c;
|
||||||
border-color: #15ca20
|
border-color: #23ac2c
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check-success .form-check-input[type=checkbox]:indeterminate {
|
.form-check-success .form-check-input[type=checkbox]:indeterminate {
|
||||||
background-color: #15ca20;
|
background-color: #23ac2c;
|
||||||
border-color: #15ca20;
|
border-color: #23ac2c;
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-check-success .form-check-input:focus {
|
.form-check-success .form-check-input:focus {
|
||||||
border-color: #15ca20;
|
border-color: #23ac2c;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
box-shadow: 0 0 0 .25rem rgba(21, 202, 33, 0.25)
|
box-shadow: 0 0 0 .25rem rgba(21, 202, 33, 0.25)
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
assets/images/printer-icon.png
Normal file
BIN
assets/images/printer-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -1,161 +0,0 @@
|
|||||||
<!--sidebar wrapper -->
|
|
||||||
<div class="sidebar-wrapper" data-simplebar="true">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<div>
|
|
||||||
<img src="assets/images/logo-icon.png" class="logo-icon" alt="logo icon">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 class="logo-text">TechOdyssey</h4>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-toggle-icon ms-auto"><i class='bx bx-x'></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--navigation-->
|
|
||||||
<ul class="metismenu" id="menu">
|
|
||||||
<li>
|
|
||||||
<a href="javascript:;" class="has-arrow">
|
|
||||||
<div class="parent-icon"><i class='bx bx-home-alt'></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Dashboard</div>
|
|
||||||
</a>
|
|
||||||
<ul>
|
|
||||||
<li> <a href="index.html"><i class='bx bx-radio-circle'></i>Infographic</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="index2.html"><i class='bx bx-radio-circle'></i>eCommerce</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="index3.html"><i class='bx bx-radio-circle'></i>Analytics</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:;" class="has-arrow">
|
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Application</div>
|
|
||||||
</a>
|
|
||||||
<ul>
|
|
||||||
<li> <a href="app-emailbox.html"><i class='bx bx-radio-circle'></i>Email</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="app-chat-box.html"><i class='bx bx-radio-circle'></i>Chat Box</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="app-file-manager.html"><i class='bx bx-radio-circle'></i>File Manager</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="app-contact-list.html"><i class='bx bx-radio-circle'></i>Contatcs</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="app-to-do.html"><i class='bx bx-radio-circle'></i>Todo List</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="app-invoice.html"><i class='bx bx-radio-circle'></i>Invoice</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="app-fullcalender.html"><i class='bx bx-radio-circle'></i>Calendar</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="menu-label">UI Elements</li>
|
|
||||||
<li>
|
|
||||||
<a href="widgets.html">
|
|
||||||
<div class="parent-icon"><i class='bx bx-cookie'></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Widgets</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:;" class="has-arrow">
|
|
||||||
<div class="parent-icon"><i class='bx bx-cart'></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">eCommerce</div>
|
|
||||||
</a>
|
|
||||||
<ul>
|
|
||||||
<li> <a href="ecommerce-products.html"><i class='bx bx-radio-circle'></i>Products</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="ecommerce-products-details.html"><i class='bx bx-radio-circle'></i>Product Details</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="ecommerce-add-new-products.html"><i class='bx bx-radio-circle'></i>Add New Products</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="ecommerce-orders.html"><i class='bx bx-radio-circle'></i>Orders</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="has-arrow" href="javascript:;">
|
|
||||||
<div class="parent-icon"><i class='bx bx-bookmark-heart'></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Components</div>
|
|
||||||
</a>
|
|
||||||
<ul>
|
|
||||||
<li> <a href="component-alerts.html"><i class='bx bx-radio-circle'></i>Alerts</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-accordions.html"><i class='bx bx-radio-circle'></i>Accordions</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-badges.html"><i class='bx bx-radio-circle'></i>Badges</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-buttons.html"><i class='bx bx-radio-circle'></i>Buttons</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-cards.html"><i class='bx bx-radio-circle'></i>Cards</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-carousels.html"><i class='bx bx-radio-circle'></i>Carousels</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-list-groups.html"><i class='bx bx-radio-circle'></i>List Groups</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-media-object.html"><i class='bx bx-radio-circle'></i>Media Objects</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-modals.html"><i class='bx bx-radio-circle'></i>Modals</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-navs-tabs.html"><i class='bx bx-radio-circle'></i>Navs & Tabs</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-navbar.html"><i class='bx bx-radio-circle'></i>Navbar</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-paginations.html"><i class='bx bx-radio-circle'></i>Pagination</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-popovers-tooltips.html"><i class='bx bx-radio-circle'></i>Popovers & Tooltips</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-progress-bars.html"><i class='bx bx-radio-circle'></i>Progress</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-spinners.html"><i class='bx bx-radio-circle'></i>Spinners</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-notifications.html"><i class='bx bx-radio-circle'></i>Notifications</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="component-avtars-chips.html"><i class='bx bx-radio-circle'></i>Avatrs & Chips</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="has-arrow" href="javascript:;">
|
|
||||||
<div class="parent-icon"><i class="bx bx-repeat"></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Content</div>
|
|
||||||
</a>
|
|
||||||
<ul>
|
|
||||||
<li> <a href="content-grid-system.html"><i class='bx bx-radio-circle'></i>Grid System</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="content-typography.html"><i class='bx bx-radio-circle'></i>Typography</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="content-text-utilities.html"><i class='bx bx-radio-circle'></i>Text Utilities</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="has-arrow" href="javascript:;">
|
|
||||||
<div class="parent-icon"> <i class="bx bx-donate-blood"></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Icons</div>
|
|
||||||
</a>
|
|
||||||
<ul>
|
|
||||||
<li> <a href="icons-line-icons.html"><i class='bx bx-radio-circle'></i>Line Icons</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="icons-boxicons.html"><i class='bx bx-radio-circle'></i>Boxicons</a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="icons-feather-icons.html"><i class='bx bx-radio-circle'></i>Feather Icons</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="form-froala-editor.html">
|
|
||||||
<div class="parent-icon"><i class='bx bx-code-alt'></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Froala Editor</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<!--end navigation-->
|
|
||||||
</div>
|
|
||||||
<!--end sidebar wrapper -->
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
session_start();
|
|
||||||
if (!isset($_SESSION['user_id'])) {
|
|
||||||
header("Location: /login.php");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@@ -72,11 +72,11 @@ $(function() {
|
|||||||
label: 'Facebook',
|
label: 'Facebook',
|
||||||
data: [12, 30, 16, 23, 8, 14, 11],
|
data: [12, 30, 16, 23, 8, 14, 11],
|
||||||
backgroundColor: [
|
backgroundColor: [
|
||||||
'#15ca20'
|
'#23ac2c'
|
||||||
],
|
],
|
||||||
tension: 0,
|
tension: 0,
|
||||||
borderColor: [
|
borderColor: [
|
||||||
'#15ca20'
|
'#23ac2c'
|
||||||
],
|
],
|
||||||
borderWidth: 3
|
borderWidth: 3
|
||||||
}]
|
}]
|
||||||
@@ -113,7 +113,7 @@ $(function() {
|
|||||||
'#6f42c1',
|
'#6f42c1',
|
||||||
'#d63384',
|
'#d63384',
|
||||||
'#fd7e14',
|
'#fd7e14',
|
||||||
'#15ca20',
|
'#23ac2c',
|
||||||
'#0dcaf0'
|
'#0dcaf0'
|
||||||
],
|
],
|
||||||
borderWidth: 1.5
|
borderWidth: 1.5
|
||||||
@@ -146,7 +146,7 @@ $(function() {
|
|||||||
'#6f42c1',
|
'#6f42c1',
|
||||||
'#d63384',
|
'#d63384',
|
||||||
'#fd7e14',
|
'#fd7e14',
|
||||||
'#15ca20',
|
'#23ac2c',
|
||||||
'#0dcaf0'
|
'#0dcaf0'
|
||||||
],
|
],
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
@@ -242,7 +242,7 @@ $(function() {
|
|||||||
'#6f42c1',
|
'#6f42c1',
|
||||||
'#d63384',
|
'#d63384',
|
||||||
'#fd7e14',
|
'#fd7e14',
|
||||||
'#15ca20',
|
'#23ac2c',
|
||||||
'#0dcaf0'
|
'#0dcaf0'
|
||||||
],
|
],
|
||||||
}]
|
}]
|
||||||
@@ -372,7 +372,7 @@ var myChart = new Chart(ctx, {
|
|||||||
label: 'Facebook',
|
label: 'Facebook',
|
||||||
data: [5, 30, 16, 23, 8, 14, 2],
|
data: [5, 30, 16, 23, 8, 14, 2],
|
||||||
backgroundColor: [
|
backgroundColor: [
|
||||||
'#15ca20'
|
'#23ac2c'
|
||||||
],
|
],
|
||||||
fill: {
|
fill: {
|
||||||
target: 'origin',
|
target: 'origin',
|
||||||
@@ -381,7 +381,7 @@ var myChart = new Chart(ctx, {
|
|||||||
},
|
},
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
borderColor: [
|
borderColor: [
|
||||||
'#15ca20'
|
'#23ac2c'
|
||||||
],
|
],
|
||||||
borderWidth: 4
|
borderWidth: 4
|
||||||
}]
|
}]
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ Morris.Area({
|
|||||||
labels: ['iPhone', 'iPad'],
|
labels: ['iPhone', 'iPad'],
|
||||||
pointSize: 3,
|
pointSize: 3,
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
pointStrokeColors:['#008cff', '#15ca20'],
|
pointStrokeColors:['#008cff', '#23ac2c'],
|
||||||
behaveLikeLine: true,
|
behaveLikeLine: true,
|
||||||
gridLineColor: '#e0e0e0',
|
gridLineColor: '#e0e0e0',
|
||||||
lineWidth: 3,
|
lineWidth: 3,
|
||||||
hideHover: 'auto',
|
hideHover: 'auto',
|
||||||
lineColors: ['#008cff', '#15ca20'],
|
lineColors: ['#008cff', '#23ac2c'],
|
||||||
resize: true
|
resize: true
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -67,7 +67,7 @@ Morris.Area({
|
|||||||
value: 20
|
value: 20
|
||||||
}],
|
}],
|
||||||
resize: true,
|
resize: true,
|
||||||
colors:['#008cff', '#15ca20', '#fd3550']
|
colors:['#008cff', '#23ac2c', '#fd3550']
|
||||||
});
|
});
|
||||||
|
|
||||||
// Morris bar chart
|
// Morris bar chart
|
||||||
@@ -112,7 +112,7 @@ Morris.Area({
|
|||||||
xkey: 'y',
|
xkey: 'y',
|
||||||
ykeys: ['a', 'b', 'c'],
|
ykeys: ['a', 'b', 'c'],
|
||||||
labels: ['A', 'B', 'C'],
|
labels: ['A', 'B', 'C'],
|
||||||
barColors:['#008cff', '#15ca20', '#75808a'],
|
barColors:['#008cff', '#23ac2c', '#75808a'],
|
||||||
hideHover: 'auto',
|
hideHover: 'auto',
|
||||||
gridLineColor: '#eef0f2',
|
gridLineColor: '#eef0f2',
|
||||||
resize: true
|
resize: true
|
||||||
@@ -153,7 +153,7 @@ Morris.Area({
|
|||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
lineColors: ['#008cff', '#15ca20'],
|
lineColors: ['#008cff', '#23ac2c'],
|
||||||
xkey: 'period',
|
xkey: 'period',
|
||||||
ykeys: ['iphone', 'ipad'],
|
ykeys: ['iphone', 'ipad'],
|
||||||
labels: ['Site A', 'Site B'],
|
labels: ['Site A', 'Site B'],
|
||||||
|
|||||||
@@ -2130,17 +2130,17 @@ form select.error:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-inverse-success {
|
.btn-inverse-success {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
border-color: rgb(212, 246, 214);
|
border-color: rgb(212, 246, 214);
|
||||||
}
|
}
|
||||||
.btn-inverse-success:hover {
|
.btn-inverse-success:hover {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
border-color: rgba(21, 202, 32, 0.18);
|
border-color: rgba(21, 202, 32, 0.18);
|
||||||
}
|
}
|
||||||
.btn-inverse-success:focus {
|
.btn-inverse-success:focus {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
border-color: rgba(21, 202, 32, 0.18);
|
border-color: rgba(21, 202, 32, 0.18);
|
||||||
box-shadow: 0 0 0 0.25rem rgba(23, 160, 14, 0.32);
|
box-shadow: 0 0 0 0.25rem rgba(23, 160, 14, 0.32);
|
||||||
|
|||||||
@@ -2582,18 +2582,18 @@ form select.error:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-inverse-success {
|
.btn-inverse-success {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
border-color: rgb(212, 246, 214);
|
border-color: rgb(212, 246, 214);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
border-color: rgba(21, 202, 32, 0.18);
|
border-color: rgba(21, 202, 32, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
color: #15ca20;
|
color: #23ac2c;
|
||||||
background-color: rgba(21, 202, 32, 0.18);
|
background-color: rgba(21, 202, 32, 0.18);
|
||||||
border-color: rgba(21, 202, 32, 0.18);
|
border-color: rgba(21, 202, 32, 0.18);
|
||||||
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 32%);
|
box-shadow: 0 0 0 .25rem rgb(23 160 14 / 32%);
|
||||||
|
|||||||
9
composer.json
Normal file
9
composer.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"php-mqtt/client": "^2.2",
|
||||||
|
"fabpot/goutte": "^4.0",
|
||||||
|
"symfony/polyfill-ctype": "^1.31",
|
||||||
|
"phpmailer/phpmailer": "^6.9",
|
||||||
|
"twig/twig": "^3.18"
|
||||||
|
}
|
||||||
|
}
|
||||||
1532
composer.lock
generated
Normal file
1532
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
226
esp32Code/dryer/main.ino
Normal file
226
esp32Code/dryer/main.ino
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "Adafruit_AHTX0.h"
|
||||||
|
|
||||||
|
// WiFi Credentials
|
||||||
|
const char* ssid = "HickmanWiFi";
|
||||||
|
const char* password = "BlackBriar8787@@";
|
||||||
|
|
||||||
|
// Sensor and Server Instances
|
||||||
|
Adafruit_AHTX0 aht;
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
|
||||||
|
// Pins for Relays
|
||||||
|
#define FAN_RELAY_PIN 26
|
||||||
|
#define HEATER_RELAY_PIN 25 // GPIO 25 for heater relay
|
||||||
|
|
||||||
|
// Dryer State Variables
|
||||||
|
bool dryerOn = false;
|
||||||
|
bool fanRelayState = false;
|
||||||
|
bool heaterRelayState = false;
|
||||||
|
float currentTemperature = 0.0;
|
||||||
|
float currentHumidity = 0.0;
|
||||||
|
float targetTemperature = 0.0;
|
||||||
|
int dryingTime = 0; // Total drying time in minutes
|
||||||
|
int remainingTime = 0; // Remaining drying time in minutes
|
||||||
|
unsigned long startTime = 0; // Time when the dryer was started (millis)
|
||||||
|
|
||||||
|
// Historical Data
|
||||||
|
const int maxHistory = 180;
|
||||||
|
std::vector<float> tempHistory;
|
||||||
|
std::vector<float> humidityHistory;
|
||||||
|
|
||||||
|
// Initialize Relays and Sensor
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Connect to WiFi
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(1000);
|
||||||
|
Serial.println("Connecting to WiFi...");
|
||||||
|
}
|
||||||
|
Serial.println("Connected to WiFi");
|
||||||
|
|
||||||
|
// AHT10 Sensor Setup
|
||||||
|
if (!aht.begin()) {
|
||||||
|
Serial.println("Failed to find AHT10 sensor");
|
||||||
|
while (1) delay(10);
|
||||||
|
}
|
||||||
|
Serial.println("AHT10 Found");
|
||||||
|
|
||||||
|
// Setup Relay Pins
|
||||||
|
pinMode(FAN_RELAY_PIN, OUTPUT);
|
||||||
|
pinMode(HEATER_RELAY_PIN, OUTPUT);
|
||||||
|
digitalWrite(FAN_RELAY_PIN, LOW);
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, LOW);
|
||||||
|
|
||||||
|
// Web Server Endpoints
|
||||||
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
request->send(200, "text/plain", "Filament Dryer Control");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
sensors_event_t humidity, temp;
|
||||||
|
aht.getEvent(&humidity, &temp);
|
||||||
|
|
||||||
|
currentTemperature = temp.temperature;
|
||||||
|
currentHumidity = humidity.relative_humidity;
|
||||||
|
|
||||||
|
// Update remaining drying time
|
||||||
|
if (dryerOn) {
|
||||||
|
unsigned long elapsedTime = (millis() - startTime) / 60000; // Convert to minutes
|
||||||
|
remainingTime = dryingTime > elapsedTime ? dryingTime - elapsedTime : 0;
|
||||||
|
|
||||||
|
// Automatically turn off dryer when time is up
|
||||||
|
if (remainingTime == 0) {
|
||||||
|
dryerOn = false;
|
||||||
|
heaterRelayState = false;
|
||||||
|
fanRelayState = false;
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, LOW);
|
||||||
|
digitalWrite(FAN_RELAY_PIN, LOW);
|
||||||
|
Serial.println("Drying completed. Dryer turned off.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remainingTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store historical data
|
||||||
|
if (tempHistory.size() >= maxHistory) tempHistory.erase(tempHistory.begin());
|
||||||
|
if (humidityHistory.size() >= maxHistory) humidityHistory.erase(humidityHistory.begin());
|
||||||
|
tempHistory.push_back(currentTemperature);
|
||||||
|
humidityHistory.push_back(currentHumidity);
|
||||||
|
|
||||||
|
String response = "{";
|
||||||
|
response += "\"dryerOn\":" + String(dryerOn ? "true" : "false") + ",";
|
||||||
|
response += "\"fanRelayState\":" + String(fanRelayState ? "true" : "false") + ",";
|
||||||
|
response += "\"heaterRelayState\":" + String(heaterRelayState ? "true" : "false") + ",";
|
||||||
|
response += "\"temperature\":" + String(currentTemperature) + ",";
|
||||||
|
response += "\"humidity\":" + String(currentHumidity) + ",";
|
||||||
|
response += "\"targetTemperature\":" + String(targetTemperature) + ",";
|
||||||
|
response += "\"dryingTime\":" + String(dryingTime) + ","; // Total drying time
|
||||||
|
response += "\"remainingTime\":" + String(remainingTime) + ","; // Remaining time
|
||||||
|
|
||||||
|
// Serialize temperature and humidity history
|
||||||
|
response += "\"tempHistory\":[";
|
||||||
|
for (size_t i = 0; i < tempHistory.size(); i++) {
|
||||||
|
response += String(tempHistory[i]);
|
||||||
|
if (i < tempHistory.size() - 1) response += ",";
|
||||||
|
}
|
||||||
|
response += "],";
|
||||||
|
|
||||||
|
response += "\"humidityHistory\":[";
|
||||||
|
for (size_t i = 0; i < humidityHistory.size(); i++) {
|
||||||
|
response += String(humidityHistory[i]);
|
||||||
|
if (i < humidityHistory.size() - 1) response += ",";
|
||||||
|
}
|
||||||
|
response += "]";
|
||||||
|
|
||||||
|
|
||||||
|
response += "}";
|
||||||
|
|
||||||
|
request->send(200, "application/json", response);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/control", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||||
|
int params = request->params();
|
||||||
|
for (int i = 0; i < params; i++) {
|
||||||
|
AsyncWebParameter* p = request->getParam(i);
|
||||||
|
if (p->name() == "dryerOn") {
|
||||||
|
dryerOn = (p->value() == "true");
|
||||||
|
if (dryerOn) {
|
||||||
|
startTime = millis();
|
||||||
|
fanRelayState = true;
|
||||||
|
}
|
||||||
|
} else if (p->name() == "targetTemperature") {
|
||||||
|
targetTemperature = p->value().toFloat();
|
||||||
|
} else if (p->name() == "dryingTime") {
|
||||||
|
dryingTime = p->value().toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request->send(200, "application/json", "{\"status\":\"success\"}");
|
||||||
|
});
|
||||||
|
|
||||||
|
// CORS Handling
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*");
|
||||||
|
|
||||||
|
// Start Server
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor and Update
|
||||||
|
void loop() {
|
||||||
|
sensors_event_t humidity, temp;
|
||||||
|
aht.getEvent(&humidity, &temp);
|
||||||
|
currentTemperature = temp.temperature;
|
||||||
|
currentHumidity = humidity.relative_humidity;
|
||||||
|
|
||||||
|
// Safety Controls
|
||||||
|
if (currentTemperature > 26.0) {
|
||||||
|
fanRelayState = true;
|
||||||
|
digitalWrite(FAN_RELAY_PIN, HIGH); // Force fan on
|
||||||
|
Serial.println("Safety: Fan forced ON due to high temperature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dryer Control Logic
|
||||||
|
if (dryerOn) {
|
||||||
|
if (currentTemperature < targetTemperature) {
|
||||||
|
heaterRelayState = true;
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, HIGH);
|
||||||
|
} else {
|
||||||
|
heaterRelayState = false;
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the fan is always on when the heater is active
|
||||||
|
fanRelayState = true;
|
||||||
|
digitalWrite(FAN_RELAY_PIN, HIGH);
|
||||||
|
} else {
|
||||||
|
heaterRelayState = false;
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, LOW);
|
||||||
|
if (currentTemperature <= 26.0) {
|
||||||
|
fanRelayState = false;
|
||||||
|
digitalWrite(FAN_RELAY_PIN, LOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual Serial Command Handling
|
||||||
|
handleSerialCommands();
|
||||||
|
|
||||||
|
delay(5000); // Update sensor readings every 5 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Serial Commands to Control Relays
|
||||||
|
void handleSerialCommands() {
|
||||||
|
if (Serial.available() > 0) {
|
||||||
|
String command = Serial.readStringUntil('\n');
|
||||||
|
command.trim();
|
||||||
|
|
||||||
|
if (command == "fan on") {
|
||||||
|
fanRelayState = true;
|
||||||
|
digitalWrite(FAN_RELAY_PIN, HIGH);
|
||||||
|
Serial.println("Fan relay activated.");
|
||||||
|
} else if (command == "fan off") {
|
||||||
|
fanRelayState = false;
|
||||||
|
digitalWrite(FAN_RELAY_PIN, LOW);
|
||||||
|
Serial.println("Fan relay deactivated.");
|
||||||
|
} else if (command == "heater on") {
|
||||||
|
heaterRelayState = true;
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, HIGH);
|
||||||
|
fanRelayState = true;
|
||||||
|
digitalWrite(FAN_RELAY_PIN, HIGH);
|
||||||
|
Serial.println("Heater relay activated. Fan ON for safety.");
|
||||||
|
} else if (command == "heater off") {
|
||||||
|
heaterRelayState = false;
|
||||||
|
digitalWrite(HEATER_RELAY_PIN, LOW);
|
||||||
|
Serial.println("Heater relay deactivated.");
|
||||||
|
} else {
|
||||||
|
Serial.println("Unknown command. Use 'fan on', 'fan off', 'heater on', or 'heater off'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
196
public/addFilament.php
Normal file
196
public/addFilament.php
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<?php include '../src/session_check.php';
|
||||||
|
checkUserRole(['admin']);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<!--start page content -->
|
||||||
|
|
||||||
|
<!--breadcrumb-->
|
||||||
|
<div class="page-breadcrumb d-none d-sm-flex align-items-center mb-3">
|
||||||
|
<div class="breadcrumb-title pe-3">Filament Settings</div>
|
||||||
|
<div class="ps-3">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb mb-0 p-0">
|
||||||
|
<li class="breadcrumb-item"><a href="index.php"><i class="bx bx-home-alt"></i></a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Add Filament</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end breadcrumb-->
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 mx-auto">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header px-4 py-3">
|
||||||
|
<h5 class="mb-0">Add New Filament to Track</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<!-- Alert Banner (Hidden Initially) -->
|
||||||
|
<div id="alertBanner" class="alert d-none" role="alert"></div>
|
||||||
|
|
||||||
|
<form id="filamentForm">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="productName" class="col-sm-3 col-form-label">Filament Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="productName" name="productName" placeholder="Brand / Type of Filament" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="productUrl" class="col-sm-3 col-form-label">Amazon URL</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="url" class="form-control" id="productUrl" name="productUrl" placeholder="https://www.amazon.co.uk/example" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-sm-3 col-form-label"></label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="d-md-flex d-grid align-items-center gap-3">
|
||||||
|
<button type="submit" class="btn btn-primary px-4">Track Filament</button>
|
||||||
|
<button type="reset" class="btn btn-light px-4">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery & AJAX Script -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#filamentForm').on('submit', function(event) {
|
||||||
|
event.preventDefault(); // Prevent traditional form submission
|
||||||
|
|
||||||
|
// Collect form data
|
||||||
|
let formData = $(this).serialize();
|
||||||
|
|
||||||
|
// AJAX request to submit form
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '../src/filamentTracker/addFilament.php',
|
||||||
|
data: formData,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
let banner = $('#alertBanner');
|
||||||
|
if (response.status === 'success') {
|
||||||
|
banner.removeClass('d-none alert-danger')
|
||||||
|
.addClass('alert-success')
|
||||||
|
.text(response.message);
|
||||||
|
} else {
|
||||||
|
banner.removeClass('d-none alert-success')
|
||||||
|
.addClass('alert-danger')
|
||||||
|
.text(response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$('#alertBanner').removeClass('d-none alert-success')
|
||||||
|
.addClass('alert-danger')
|
||||||
|
.text('An error occurred while adding the filament.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--end page content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
<footer class="page-footer">
|
||||||
|
<p class="mb-0">Copyright © 2024. All right reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
618
public/filamentDryer.php
Normal file
618
public/filamentDryer.php
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
<?php include '../src/session_check.php';
|
||||||
|
checkUserRole(['admin']);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<!-- Start Page Content -->
|
||||||
|
|
||||||
|
<!-- Start Drying Modal -->
|
||||||
|
<div class="modal fade" id="startDryingModal" tabindex="-1" aria-labelledby="startDryingModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="startDryingModalLabel">Start Drying</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="dryerControlForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="filamentPreset" class="form-label">Filament Preset</label>
|
||||||
|
<select class="form-select" id="filamentPreset" name="filamentPreset" required>
|
||||||
|
<option value="">Select Filament Type</option>
|
||||||
|
<option value="PLA" data-temp="50">PLA</option>
|
||||||
|
<option value="ABS" data-temp="70">ABS</option>
|
||||||
|
<option value="PETG" data-temp="65">PETG</option>
|
||||||
|
<option value="Nylon" data-temp="80">Nylon</option>
|
||||||
|
<option value="TPU" data-temp="45">TPU</option>
|
||||||
|
<option value="Custom" data-temp="">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dryingTime" class="form-label">Drying Time (Hours)</label>
|
||||||
|
<input type="number" class="form-control" id="dryingTime" name="dryingTime" min="1" max="24" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="temperature" class="form-label">Drying Temperature (°C)</label>
|
||||||
|
<input type="number" class="form-control" id="temperature" name="temperature" min="30" max="100" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button type="submit" class="btn btn-success">Start Drying</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stop Drying Modal -->
|
||||||
|
<div class="modal fade" id="stopDryingModal" tabindex="-1" aria-labelledby="stopDryingModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="stopDryingModalLabel">Stop Drying</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to stop the drying process?</p>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmStopDrying">Yes, Stop Drying</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Page Content -->
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Dryer Status Cards -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card bg-danger radius-10 overflow-hidden">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0">Dryer Status</p>
|
||||||
|
<h5 class="mb-0">STATUS</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card bg-danger radius-10 overflow-hidden" id="heaterRelayStatus">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0">Heater Status</p>
|
||||||
|
<h5 class="mb-0">STATUS</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card bg-danger radius-10 overflow-hidden" id="fanRelayStatus">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0">Fan Status</p>
|
||||||
|
<h5 class="mb-0">STATUS</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Left Column - Charts -->
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Temperature Chart -->
|
||||||
|
<div class="col-lg-12 mb-0">
|
||||||
|
<div class="card radius-10 overflow-hidden">
|
||||||
|
<div class="card-body d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 filament-name">Temp:</p>
|
||||||
|
<h5 class="mb-0" id="temperatureValue">--°C</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tempChart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Humidity Chart -->
|
||||||
|
<div class="col-lg-12 mb-0">
|
||||||
|
<div class="card radius-10 overflow-hidden">
|
||||||
|
<div class="card-body d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<p class="mb-0 filament-name">Humidity:</p>
|
||||||
|
<h5 class="mb-0" id="humidityValue">--%</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="humidityChart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column - Dryer Control Card with Dropdown and Chart -->
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card rounded-4 mb-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start justify-content-between mb-0">
|
||||||
|
<div class="">
|
||||||
|
<h6 class="mb-0">Dryer Control</h6>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="javascript:;" class="dropdown-toggle-nocaret more-options dropdown-toggle" data-bs-toggle="dropdown">
|
||||||
|
<i class='bx bx-dots-vertical-rounded'></i>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="javascript:;" data-bs-toggle="modal" data-bs-target="#startDryingModal">Start Drying</a></li>
|
||||||
|
<li><a class="dropdown-item" href="javascript:;" data-bs-toggle="modal" data-bs-target="#stopDryingModal">Stop Drying</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container2">
|
||||||
|
<div id="chartProgress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="">
|
||||||
|
<h4 id="dryingTimeDisplay" class="mb-1">Loadig..</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- End Page Content -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const ESP32_IP = 'https://filamentdry.hickmeister.uk'; // Replace with your ESP32 IP
|
||||||
|
let tempChart, humidityChart;
|
||||||
|
let progressChart; // Declare a variable for the chart instance
|
||||||
|
const startDryingModal = new bootstrap.Modal(document.getElementById('startDryingModal'));
|
||||||
|
const stopDryingModal = new bootstrap.Modal(document.getElementById('stopDryingModal'));
|
||||||
|
|
||||||
|
// Fetch ESP32 Status Data
|
||||||
|
function fetchDryerStatus() {
|
||||||
|
fetch(`${ESP32_IP}/status`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.dryerOn !== undefined) {
|
||||||
|
updateTemperatureCard(data);
|
||||||
|
updateHumidityCard(data);
|
||||||
|
updateStatusCards(data);
|
||||||
|
updateChartProgress(data); // Update chart for drying progress
|
||||||
|
} else {
|
||||||
|
console.error("Dryer offline or no data available");
|
||||||
|
showOfflineStatus();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Failed to fetch dryer status:", error);
|
||||||
|
showOfflineStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control Dryer
|
||||||
|
function controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes) {
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append("dryerOn", dryerOn);
|
||||||
|
formData.append("targetTemperature", targetTemperature);
|
||||||
|
formData.append("dryingTime", dryingTimeInMinutes);
|
||||||
|
|
||||||
|
fetch(`${ESP32_IP}/control`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: formData.toString(),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Dryer control updated:', data);
|
||||||
|
fetchDryerStatus(); // Refresh status after control update
|
||||||
|
if (dryerOn) {
|
||||||
|
startDryingModal.hide();
|
||||||
|
} else {
|
||||||
|
stopDryingModal.hide();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Failed to update dryer control:', error);
|
||||||
|
alert('Failed to update dryer control. Check your connection.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Form Submission for Starting Drying
|
||||||
|
const dryerControlForm = document.getElementById("dryerControlForm");
|
||||||
|
dryerControlForm.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Collect form data
|
||||||
|
const formData = new FormData(dryerControlForm);
|
||||||
|
const dryerOn = true; // Always true when starting the dryer
|
||||||
|
const dryingTimeInHours = parseFloat(formData.get("dryingTime"));
|
||||||
|
const targetTemperature = parseFloat(formData.get("temperature"));
|
||||||
|
|
||||||
|
// Validate Input
|
||||||
|
if (isNaN(dryingTimeInHours) || isNaN(targetTemperature)) {
|
||||||
|
alert("Please complete all required fields.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert drying time to minutes
|
||||||
|
const dryingTimeInMinutes = Math.round(dryingTimeInHours * 60);
|
||||||
|
|
||||||
|
// Control Dryer
|
||||||
|
controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle Stop Dryer
|
||||||
|
document.getElementById("stopDryingModal").addEventListener("click", function () {
|
||||||
|
stopDryingModal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("confirmStopDrying").addEventListener("click", function() {
|
||||||
|
controlDryer(false, 0, 0); // Turn off the dryer
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-Fill Temperature Input Based on Filament Preset
|
||||||
|
const filamentPresetSelect = document.getElementById("filamentPreset");
|
||||||
|
const temperatureInput = document.getElementById("temperature");
|
||||||
|
|
||||||
|
filamentPresetSelect.addEventListener("change", function () {
|
||||||
|
const selectedOption = filamentPresetSelect.options[filamentPresetSelect.selectedIndex];
|
||||||
|
const presetTemperature = selectedOption.getAttribute("data-temp");
|
||||||
|
|
||||||
|
if (presetTemperature) {
|
||||||
|
temperatureInput.value = presetTemperature; // Auto-fill temperature input
|
||||||
|
} else {
|
||||||
|
temperatureInput.value = ""; // Clear the temperature input for "Custom"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update chart progress
|
||||||
|
function updateChartProgress(data) {
|
||||||
|
const chartProgressElement = document.getElementById("chartProgress");
|
||||||
|
|
||||||
|
const dryingTime = data.dryingTime || 0; // Total drying time in minutes
|
||||||
|
const remainingTime = data.remainingTime || 0; // Remaining time in minutes
|
||||||
|
|
||||||
|
// Convert remaining time and total drying time to h:mm format
|
||||||
|
const remainingTimeFormatted = convertToHoursAndMinutes(remainingTime);
|
||||||
|
const dryingTimeFormatted = convertToHoursAndMinutes(dryingTime);
|
||||||
|
|
||||||
|
const progressPercent = dryingTime > 0 ? ((dryingTime - remainingTime) / dryingTime) * 100 : 0; // Calculate the percentage of drying progress
|
||||||
|
|
||||||
|
// Initialize the chart if it doesn't exist yet
|
||||||
|
if (!progressChart) {
|
||||||
|
const options = {
|
||||||
|
series: [progressPercent],
|
||||||
|
chart: {
|
||||||
|
height: 385,
|
||||||
|
type: 'radialBar',
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
hollow: {
|
||||||
|
margin: 0,
|
||||||
|
size: '80%',
|
||||||
|
background: 'transparent',
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
background: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
strokeWidth: '67%',
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
show: true,
|
||||||
|
name: {
|
||||||
|
offsetY: -10,
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
offsetY: 10,
|
||||||
|
color: 'rgba(13, 160, 8, 0.82)',
|
||||||
|
fontSize: '20px',
|
||||||
|
show: true,
|
||||||
|
formatter: function () {
|
||||||
|
return remainingTimeFormatted; // Display remaining time in the center
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shade: 'dark',
|
||||||
|
type: 'horizontal',
|
||||||
|
shadeIntensity: 02,
|
||||||
|
gradientToColors: ['#44FF00'],
|
||||||
|
opacityFrom: 1,
|
||||||
|
opacityTo: 1,
|
||||||
|
stops: [0, 100]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ["#289600"],
|
||||||
|
stroke: {
|
||||||
|
lineCap: 'round'
|
||||||
|
},
|
||||||
|
labels: ['Drying Progress'],
|
||||||
|
};
|
||||||
|
|
||||||
|
progressChart = new ApexCharts(chartProgressElement, options);
|
||||||
|
progressChart.render();
|
||||||
|
} else {
|
||||||
|
progressChart.updateSeries([progressPercent]);
|
||||||
|
|
||||||
|
// Dynamically update the remaining time in the center
|
||||||
|
progressChart.updateOptions({
|
||||||
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
dataLabels: {
|
||||||
|
value: {
|
||||||
|
formatter: function () {
|
||||||
|
return remainingTimeFormatted; // Dynamically update the remaining time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display drying time below the chart
|
||||||
|
const timeSetElement = document.getElementById('dryingTimeDisplay');
|
||||||
|
if (timeSetElement) {
|
||||||
|
timeSetElement.innerText = `Time Set: ${dryingTimeFormatted}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert minutes to hours and minutes (e.g., 90 minutes -> 1h 30m)
|
||||||
|
function convertToHoursAndMinutes(minutes) {
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const mins = minutes % 60;
|
||||||
|
return `${hours}h ${mins}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Temperature Card and Chart
|
||||||
|
function updateTemperatureCard(data) {
|
||||||
|
const temperature = data.temperature.toFixed(1); // Limit temperature to 1 decimal place
|
||||||
|
const tempHistory = data.tempHistory.map(temp => parseFloat(temp.toFixed(1))); // Limit history data
|
||||||
|
const timestamps = Array.from({ length: tempHistory.length }, (_, i) => `${i + 1}`);
|
||||||
|
|
||||||
|
document.getElementById('temperatureValue').innerText = `${temperature}°C`;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
series: [{ name: "Temperature", data: tempHistory }],
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
color: '#1df00a',
|
||||||
|
height: 150,
|
||||||
|
toolbar: { show: false },
|
||||||
|
sparkline: { enabled: true },
|
||||||
|
id: 'tempChart',
|
||||||
|
},
|
||||||
|
stroke: { curve: 'smooth' },
|
||||||
|
xaxis: { categories: timestamps, labels: { show: false } },
|
||||||
|
tooltip: {
|
||||||
|
theme: 'dark',
|
||||||
|
x: { show: false },
|
||||||
|
y: { formatter: value => `${value.toFixed(1)}°C` },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!tempChart) {
|
||||||
|
tempChart = new ApexCharts(document.querySelector("#tempChart"), options);
|
||||||
|
tempChart.render();
|
||||||
|
} else {
|
||||||
|
tempChart.updateSeries([{ data: tempHistory }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Humidity Card and Chart
|
||||||
|
function updateHumidityCard(data) {
|
||||||
|
const humidity = data.humidity.toFixed(1);
|
||||||
|
const humidityHistory = data.humidityHistory || [];
|
||||||
|
const timestamps = Array.from({ length: humidityHistory.length }, (_, i) => `${i + 1}`);
|
||||||
|
|
||||||
|
document.getElementById('humidityValue').innerText = `${humidity}%`;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
series: [{ name: "Humidity", data: humidityHistory }],
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
height: 150,
|
||||||
|
toolbar: { show: false },
|
||||||
|
sparkline: { enabled: true },
|
||||||
|
id: 'humidityChart',
|
||||||
|
},
|
||||||
|
stroke: { curve: 'smooth' },
|
||||||
|
xaxis: { categories: timestamps, labels: { show: false } },
|
||||||
|
tooltip: {
|
||||||
|
theme: 'dark',
|
||||||
|
x: { show: false },
|
||||||
|
y: { formatter: value => `${value.toFixed(1)}%` },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!humidityChart) {
|
||||||
|
humidityChart = new ApexCharts(document.querySelector("#humidityChart"), options);
|
||||||
|
humidityChart.render();
|
||||||
|
} else {
|
||||||
|
humidityChart.updateSeries([{ data: humidityHistory }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Status Cards (Dryer, Heater, Fan)
|
||||||
|
function updateStatusCards(data) {
|
||||||
|
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
||||||
|
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
||||||
|
const fanStatusCard = document.getElementById('fanRelayStatus');
|
||||||
|
|
||||||
|
const dryerState = data.dryerOn ? "On" : "Off";
|
||||||
|
const fanRelayState = data.fanRelayState ? "On" : "Off";
|
||||||
|
const heaterRelayState = data.heaterRelayState ? "On" : "Off";
|
||||||
|
|
||||||
|
if (dryerStatusCard) {
|
||||||
|
dryerStatusCard.classList.toggle("bg-success", dryerState === "On");
|
||||||
|
dryerStatusCard.classList.toggle("bg-danger", dryerState === "Off");
|
||||||
|
dryerStatusCard.querySelector("h5").innerText = dryerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heaterStatusCard) {
|
||||||
|
heaterStatusCard.classList.toggle("bg-success", heaterRelayState === "On");
|
||||||
|
heaterStatusCard.classList.toggle("bg-danger", heaterRelayState === "Off");
|
||||||
|
heaterStatusCard.querySelector("h5").innerText = heaterRelayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fanStatusCard) {
|
||||||
|
fanStatusCard.classList.toggle("bg-success", fanRelayState === "On");
|
||||||
|
fanStatusCard.classList.toggle("bg-danger", fanRelayState === "Off");
|
||||||
|
fanStatusCard.querySelector("h5").innerText = fanRelayState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Offline Status
|
||||||
|
function showOfflineStatus() {
|
||||||
|
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
||||||
|
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
||||||
|
const fanStatusCard = document.getElementById('fanRelayStatus');
|
||||||
|
|
||||||
|
if (dryerStatusCard) {
|
||||||
|
dryerStatusCard.classList.remove("bg-success");
|
||||||
|
dryerStatusCard.classList.add("bg-danger");
|
||||||
|
dryerStatusCard.querySelector("h5").innerText = "Offline";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heaterStatusCard) {
|
||||||
|
heaterStatusCard.classList.remove("bg-success");
|
||||||
|
heaterStatusCard.classList.add("bg-danger");
|
||||||
|
heaterStatusCard.querySelector("h5").innerText = "Offline";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fanStatusCard) {
|
||||||
|
fanStatusCard.classList.remove("bg-success");
|
||||||
|
fanStatusCard.classList.add("bg-danger");
|
||||||
|
fanStatusCard.querySelector("h5").innerText = "Offline";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data every 10 seconds
|
||||||
|
fetchDryerStatus();
|
||||||
|
setInterval(fetchDryerStatus, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
<?php include 'assets/php/session_check.php'; ?>
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
<html lang="en" data-bs-theme="light">
|
<html lang="en" data-bs-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<!-- Required meta tags -->
|
<!-- Required meta tags -->
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!--favicon-->
|
<!--favicon-->
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
<!--plugins-->
|
<!--plugins-->
|
||||||
<link href="assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
<!-- loader-->
|
<!-- loader-->
|
||||||
<link href="assets/css/pace.min.css" rel="stylesheet"/>
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
<script src="assets/js/pace.min.js"></script>
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="assets/css/bootstrap-extended.css" rel="stylesheet">
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link href="assets/sass/app.css" rel="stylesheet">
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
<link href="assets/css/icons.css" rel="stylesheet">
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
<!-- Theme Style CSS -->
|
<!-- Theme Style CSS -->
|
||||||
<link rel="stylesheet" href="assets/sass/dark-theme.css">
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
<link rel="stylesheet" href="assets/sass/semi-dark.css">
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
<link rel="stylesheet" href="assets/sass/bordered-theme.css">
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
<title>TOD Dashboard</title>
|
<title>TOD Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
@@ -35,88 +35,10 @@
|
|||||||
<!--wrapper-->
|
<!--wrapper-->
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<!--sidebar wrapper -->
|
<!--sidebar wrapper -->
|
||||||
<?php include 'assets/php/nav.php'; ?>
|
<?php include '../src/nav.php'; ?>
|
||||||
<!--end sidebar wrapper -->
|
<!--end sidebar wrapper -->
|
||||||
<!--start header -->
|
<!--start header -->
|
||||||
<header>
|
<?php include '../src/header.php'; ?>
|
||||||
<div class="topbar">
|
|
||||||
<nav class="navbar navbar-expand gap-2 align-items-center">
|
|
||||||
<div class="mobile-toggle-menu d-flex"><i class='bx bx-menu'></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-bar d-lg-block d-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
|
||||||
<a href="avascript:;" class="btn d-flex align-items-center"><i class="bx bx-search"></i>Search</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="top-menu ms-auto">
|
|
||||||
<ul class="navbar-nav align-items-center gap-1">
|
|
||||||
<li class="nav-item mobile-search-icon d-flex d-lg-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
|
||||||
<a class="nav-link" href="avascript:;"><i class='bx bx-search'></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item dark-mode d-none d-sm-flex">
|
|
||||||
<a class="nav-link dark-mode-icon" href="javascript:;"><i class='bx bx-moon'></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item dropdown dropdown-large">
|
|
||||||
<a class="nav-link dropdown-toggle dropdown-toggle-nocaret position-relative" href="#" data-bs-toggle="dropdown"><span class="alert-count">7</span>
|
|
||||||
<i class='bx bx-bell'></i>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
|
||||||
<a href="javascript:;">
|
|
||||||
<div class="msg-header">
|
|
||||||
<p class="msg-header-title">Notifications</p>
|
|
||||||
<p class="msg-header-badge">8 New</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="header-notifications-list">
|
|
||||||
<a class="dropdown-item" href="javascript:;">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="user-online">
|
|
||||||
<img src="assets/images/avatars/avatar-1.png" class="msg-avatar" alt="user avatar">
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h6 class="msg-name">Daisy Anderson<span class="msg-time float-end">5 sec
|
|
||||||
ago</span></h6>
|
|
||||||
<p class="msg-info">The standard chunk of lorem</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a href="javascript:;">
|
|
||||||
<div class="text-center msg-footer">
|
|
||||||
<button class="btn btn-primary w-100">View All Notifications</button>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="user-box dropdown px-3">
|
|
||||||
<a class="d-flex align-items-center nav-link dropdown-toggle gap-3 dropdown-toggle-nocaret" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<img src="assets/images/avatars/avatar-2.png" class="user-img" alt="user avatar">
|
|
||||||
<div class="user-info">
|
|
||||||
<p class="user-name mb-0"><?php echo isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : 'Guest'; ?></p>
|
|
||||||
<p class="designattion mb-0"><?php echo isset($_SESSION['role']) ? htmlspecialchars($_SESSION['role']) : 'N/A'; ?></p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-cog fs-5"></i><span>My Printers</span></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="dropdown-divider mb-0"></div>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="logout.php"><i class="bx bx-log-out-circle"></i><span>Logout</span></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<!--end header -->
|
<!--end header -->
|
||||||
<!--start page wrapper -->
|
<!--start page wrapper -->
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
@@ -291,18 +213,18 @@
|
|||||||
<!-- end search modal -->
|
<!-- end search modal -->
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
<!--plugins-->
|
<!--plugins-->
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
<script src="assets/plugins/simplebar/js/simplebar.min.js"></script>
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
<script src="assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
<script src="assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
<script src="assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
<!--app JS-->
|
<!--app JS-->
|
||||||
<script src="assets/js/app.js"></script>
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
<script src="assets/js/index.js"></script>
|
<script src="../assets/js/index.js"></script>
|
||||||
<script src="assets/plugins/peity/jquery.peity.min.js"></script>
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(".data-attributes span").peity("donut")
|
$(".data-attributes span").peity("donut")
|
||||||
</script>
|
</script>
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-bs-theme="light">
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
<link href="assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
<link href="assets/css/pace.min.css" rel="stylesheet">
|
<link href="../assets/css/pace.min.css" rel="stylesheet">
|
||||||
<script src="assets/js/pace.min.js"></script>
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="assets/css/bootstrap-extended.css" rel="stylesheet">
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
<link href="assets/sass/app.css" rel="stylesheet">
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="assets/sass/dark-theme.css">
|
<!-- Theme Style CSS -->
|
||||||
<link href="assets/css/icons.css" rel="stylesheet">
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
<title>Syndron - Admin Dashboard</title>
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<title>TOD - Admin Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -26,7 +29,7 @@
|
|||||||
<div class="col-12 col-xl-7 col-xxl-8 auth-cover-left d-none d-xl-flex align-items-center justify-content-center">
|
<div class="col-12 col-xl-7 col-xxl-8 auth-cover-left d-none d-xl-flex align-items-center justify-content-center">
|
||||||
<div class="card shadow-none bg-transparent rounded-0">
|
<div class="card shadow-none bg-transparent rounded-0">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<img src="assets/images/login-images/login-cover.svg" class="img-fluid" width="650" alt=""/>
|
<img src="../assets/images/login-images/login-cover.svg" class="img-fluid" width="650" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,13 +37,13 @@
|
|||||||
<div class="card rounded-0 m-3 shadow-none bg-transparent">
|
<div class="card rounded-0 m-3 shadow-none bg-transparent">
|
||||||
<div class="card-body p-sm-5">
|
<div class="card-body p-sm-5">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
<img src="assets/images/logo-icon.png" width="60" alt="">
|
<img src="../assets/images/logo-icon.png" width="60" alt="">
|
||||||
<h5>TechOdyssey Designs Dashboard</h5>
|
<h5>TechOdyssey Designs Dashboard</h5>
|
||||||
<p>Please log in to your account</p>
|
<p>Please log in to your account</p>
|
||||||
</div>
|
</div>
|
||||||
<form id="loginForm" class="row g-3">
|
<form id="loginForm" class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label for="inputEmailAddress" class="form-label">Email</label>
|
<label for="inputEmailAddress" class="form-label">Username</label>
|
||||||
<input type="input" class="form-control" name="username" id="inputEmailAddress" required>
|
<input type="input" class="form-control" name="username" id="inputEmailAddress" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -62,11 +65,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
<script src="assets/plugins/simplebar/js/simplebar.min.js"></script>
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
<script src="assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
<script src="assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
@@ -75,7 +78,7 @@
|
|||||||
const formData = $(this).serialize();
|
const formData = $(this).serialize();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'assets/php/login.php',
|
url: '../src/login.php',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: formData,
|
data: formData,
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
@@ -90,6 +93,6 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="assets/js/app.js"></script>
|
<script src="../assets/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
session_start();
|
session_start();
|
||||||
session_unset();
|
session_unset();
|
||||||
session_destroy();
|
session_destroy();
|
||||||
header("Location: /login.php");
|
header("Location: login.php");
|
||||||
exit();
|
exit();
|
||||||
?>
|
?>
|
||||||
211
public/printerForm.php
Normal file
211
public/printerForm.php
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<!--start page content -->
|
||||||
|
|
||||||
|
<!--breadcrumb-->
|
||||||
|
<div class="page-breadcrumb d-none d-sm-flex align-items-center mb-3">
|
||||||
|
<div class="breadcrumb-title pe-3">User Settings</div>
|
||||||
|
<div class="ps-3">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb mb-0 p-0">
|
||||||
|
<li class="breadcrumb-item"><a href="index.php"><i class="bx bx-home-alt"></i></a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Add Printer</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end breadcrumb-->
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 mx-auto">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header px-4 py-3">
|
||||||
|
<h5 class="mb-0">Add New Printer</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<!-- Alert Banner (Hidden Initially) -->
|
||||||
|
<div id="alertBanner" class="alert d-none" role="alert"></div>
|
||||||
|
|
||||||
|
<form id="printerForm">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="printerName" class="col-sm-3 col-form-label">Printer Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="printerName" name="printerName" placeholder="Bambu X1 or P1P" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="printerIp" class="col-sm-3 col-form-label">Printer IP</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="printerIp" name="printerIp" placeholder="e.g., 192.168.1.100" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="serialNumber" class="col-sm-3 col-form-label">Serial Number</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="serialNumber" name="serialNumber" placeholder="e.g., X1SN123456" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="accessCode" class="col-sm-3 col-form-label">Printer Access Code</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="password" class="form-control" id="accessCode" name="accessCode" placeholder="Enter Printer Access Code" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-sm-3 col-form-label"></label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="d-md-flex d-grid align-items-center gap-3">
|
||||||
|
<button type="submit" class="btn btn-primary px-4">Add Printer</button>
|
||||||
|
<button type="reset" class="btn btn-light px-4">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jQuery & AJAX Script -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#printerForm').on('submit', function(event) {
|
||||||
|
event.preventDefault(); // Prevent traditional form submission
|
||||||
|
|
||||||
|
// Collect form data
|
||||||
|
let formData = $(this).serialize();
|
||||||
|
|
||||||
|
// AJAX request to submit form
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '../src/printers/addPrinter.php',
|
||||||
|
data: formData,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
let banner = $('#alertBanner');
|
||||||
|
if (response.status === 'success') {
|
||||||
|
banner.removeClass('d-none alert-danger')
|
||||||
|
.addClass('alert-success')
|
||||||
|
.text(response.message);
|
||||||
|
} else {
|
||||||
|
banner.removeClass('d-none alert-success')
|
||||||
|
.addClass('alert-danger')
|
||||||
|
.text(response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$('#alertBanner').removeClass('d-none alert-success')
|
||||||
|
.addClass('alert-danger')
|
||||||
|
.text('An error occurred while adding the printer.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--end page content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
<footer class="page-footer">
|
||||||
|
<p class="mb-0">Copyright © 2024. All right reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
265
public/shipping.php
Normal file
265
public/shipping.php
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<div class="">
|
||||||
|
|
||||||
|
<!--start page content -->
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Account Information Card -->
|
||||||
|
<div class="col-12 col-xl-6">
|
||||||
|
<div class="card rounded-4 mb-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<div class="widgets-icons bg-light-success text-success rounded-circle d-flex align-items-center justify-content-center p-3">
|
||||||
|
<i class='bx bxs-user fs-3'></i>
|
||||||
|
</div>
|
||||||
|
<div id="accountInfo" class="ms-3">
|
||||||
|
<h5 class="mb-0 fw-bold">Loading...</h5>
|
||||||
|
<p class="text-muted mb-0">Account Information</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Prepay Balance Card -->
|
||||||
|
<div class="col-12 col-xl-6">
|
||||||
|
<div class="card rounded-4 mb-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<div class="widgets-icons bg-light-info text-info rounded-circle d-flex align-items-center justify-content-center p-3">
|
||||||
|
<i class='bx bx-wallet fs-3'></i>
|
||||||
|
</div>
|
||||||
|
<div id="prepayBalance" class="ms-3">
|
||||||
|
<h5 class="mb-0 fw-bold">Loading...</h5>
|
||||||
|
<p class="text-muted mb-0">Prepay Balance</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Orders Table -->
|
||||||
|
<div class="col-12 col-xl-12">
|
||||||
|
<div class="card rounded-4 mb-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title fw-bold mb-3">Orders</h5>
|
||||||
|
<div id="ordersDetail" class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Order ID</th>
|
||||||
|
<th>Courier</th>
|
||||||
|
<th>Service</th>
|
||||||
|
<th>Tracking Status</th>
|
||||||
|
<th>Estimated Delivery</th>
|
||||||
|
<th>Recipient</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center text-muted">Loading orders...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page content -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Fetch account information via AJAX
|
||||||
|
function fetchAccountInfo() {
|
||||||
|
$.ajax({
|
||||||
|
url: '../src/shipping/get_account_info.php',
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
const data = response.data;
|
||||||
|
let html = `
|
||||||
|
<h5 class="mb-0 fw-bold">${data.Forename || 'N/A'} ${data.Surname || ''}</h5>
|
||||||
|
<p class="text-muted mb-0">Email: ${data.Email || 'N/A'}</p>
|
||||||
|
<p class="text-muted mb-0">Member Since: ${data.MemberSince || 'N/A'}</p>
|
||||||
|
`;
|
||||||
|
$('#accountInfo').html(html);
|
||||||
|
} else {
|
||||||
|
$('#accountInfo').html('<p class="text-danger">Error fetching account information.</p>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$('#accountInfo').html('<p class="text-danger">Error loading account information.</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch prepay balance via AJAX
|
||||||
|
function fetchPrepayBalance() {
|
||||||
|
$.ajax({
|
||||||
|
url: '../src/shipping/get_prepay_balance.php',
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
const data = response.data;
|
||||||
|
const balance = data.Balance || 0;
|
||||||
|
$('#prepayBalance').html(`
|
||||||
|
<h5 class="mb-0 fw-bold">£ ${balance}</h5>
|
||||||
|
<p class="text-muted mb-0">Prepay Balance</p>
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
$('#prepayBalance').html('<p class="text-danger">Error fetching balance.</p>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$('#prepayBalance').html('<p class="text-danger">Error loading balance.</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch order details via AJAX
|
||||||
|
function fetchOrdersDetail() {
|
||||||
|
$.ajax({
|
||||||
|
url: '../src/shipping/get_orders_detail.php',
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
const orders = response.data.Orders || [];
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if (orders.length === 0) {
|
||||||
|
html = '<tr><td colspan="6" class="text-center text-muted">No orders found.</td></tr>';
|
||||||
|
} else {
|
||||||
|
orders.forEach(order => {
|
||||||
|
const trackingStatus = order.Tracking?.Delivered ?
|
||||||
|
'<span class="badge bg-success">Delivered</span>' :
|
||||||
|
'<span class="badge bg-warning text-dark">In Transit</span>';
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td>${order.OrderLineId}</td>
|
||||||
|
<td>${order.Courier}</td>
|
||||||
|
<td>${order.Service}</td>
|
||||||
|
<td>${trackingStatus}</td>
|
||||||
|
<td>${order.EstimatedDeliveryDate ? new Date(order.EstimatedDeliveryDate).toLocaleString() : 'N/A'}</td>
|
||||||
|
<td>${order.DeliveryAddress?.ContactName || 'N/A'}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#ordersDetail tbody').html(html);
|
||||||
|
} else {
|
||||||
|
$('#ordersDetail tbody').html('<tr><td colspan="6" class="text-center text-danger">Error fetching orders.</td></tr>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$('#ordersDetail tbody').html('<tr><td colspan="6" class="text-center text-danger">Error loading orders.</td></tr>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger all fetches on page load
|
||||||
|
fetchAccountInfo();
|
||||||
|
fetchPrepayBalance();
|
||||||
|
fetchOrdersDetail();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
319
public/userManagement.php
Normal file
319
public/userManagement.php
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
<?php include '../src/session_check.php';
|
||||||
|
checkUserRole(['admin']);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<!--start page content -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2 class="text-center mb-4">User Management</h2>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mb-3">
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
||||||
|
<i class="fas fa-user-plus"></i> Create New User
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover table-bordered align-middle">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="userTableBody">
|
||||||
|
<!-- Rows dynamically populated -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create User Modal -->
|
||||||
|
<div class="modal fade" id="createUserModal" tabindex="-1" aria-labelledby="createUserModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="createUserModalLabel">Create New User</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="createUserForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" id="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="role" class="form-label">Role</label>
|
||||||
|
<select id="role" class="form-select" required>
|
||||||
|
<option value="">Select Role</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
<option value="user">User</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control" id="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Create User</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reset Password Modal -->
|
||||||
|
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="resetPasswordModalLabel">Reset Password</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="resetPasswordForm">
|
||||||
|
<p>Enter a new password for <span id="resetUserName"></span>:</p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="newPassword" class="form-label">New Password</label>
|
||||||
|
<input type="password" class="form-control" id="newPassword" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100">Reset Password</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--end page content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
<footer class="page-footer">
|
||||||
|
<p class="mb-0">Copyright © 2024. All right reserved.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
const apiUrl = '../src/userMananagementService.php';
|
||||||
|
|
||||||
|
// Load users on page load
|
||||||
|
fetchUsers();
|
||||||
|
|
||||||
|
// Create new user form submission
|
||||||
|
$('#createUserForm').submit(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const username = $('#username').val();
|
||||||
|
const email = $('#email').val();
|
||||||
|
const role = $('#role').val();
|
||||||
|
const password = $('#password').val();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: apiUrl,
|
||||||
|
method: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify({ username, email, role, password }),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function () {
|
||||||
|
alert('User created successfully!');
|
||||||
|
fetchUsers();
|
||||||
|
$('#createUserModal').modal('hide');
|
||||||
|
$('#createUserForm')[0].reset();
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
alert('Error creating user: ' + xhr.responseText);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset password form submission
|
||||||
|
$('#resetPasswordForm').submit(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const userId = $('#resetPasswordModal').data('userId');
|
||||||
|
const password = $('#newPassword').val();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: `${apiUrl}?reset-password=${userId}`,
|
||||||
|
method: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify({ password }),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function () {
|
||||||
|
alert('Password reset successfully!');
|
||||||
|
$('#resetPasswordModal').modal('hide');
|
||||||
|
$('#resetPasswordForm')[0].reset();
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
alert('Error resetting password: ' + xhr.responseText);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch users from the backend
|
||||||
|
function fetchUsers() {
|
||||||
|
const apiUrl = '../src/userMananagementService.php';
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: apiUrl,
|
||||||
|
method: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (users) {
|
||||||
|
const tableBody = $('#userTableBody');
|
||||||
|
tableBody.empty();
|
||||||
|
|
||||||
|
users.forEach(function (user) {
|
||||||
|
const row = `
|
||||||
|
<tr>
|
||||||
|
<td>${user.id}</td>
|
||||||
|
<td>${user.username}</td>
|
||||||
|
<td>${user.email}</td>
|
||||||
|
<td>${user.role}</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-warning btn-sm" onclick="openResetPasswordModal(${user.id}, '${user.username}')">
|
||||||
|
<i class="fas fa-key"></i> Reset Password
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger btn-sm" onclick="deleteUser(${user.id})">
|
||||||
|
<i class="fas fa-trash"></i> Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
tableBody.append(row);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
alert('Error fetching users: ' + xhr.responseText);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the reset password modal
|
||||||
|
function openResetPasswordModal(userId, username) {
|
||||||
|
$('#resetPasswordModal').data('userId', userId);
|
||||||
|
$('#resetUserName').text(username);
|
||||||
|
$('#resetPasswordModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
function deleteUser(userId) {
|
||||||
|
const apiUrl = '../src/userMananagementService.php';
|
||||||
|
|
||||||
|
if (confirm('Are you sure you want to delete this user?')) {
|
||||||
|
$.ajax({
|
||||||
|
url: `${apiUrl}?delete-user=${userId}`,
|
||||||
|
method: 'POST',
|
||||||
|
success: function () {
|
||||||
|
alert('User deleted successfully!');
|
||||||
|
fetchUsers();
|
||||||
|
},
|
||||||
|
error: function (xhr) {
|
||||||
|
alert('Error deleting user: ' + xhr.responseText);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
283
public/viewFilament.php
Normal file
283
public/viewFilament.php
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<!--start page content -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row" id="filamentCardContainer">
|
||||||
|
<!-- Cards will be inserted here dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--end page content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const container = document.getElementById('filamentCardContainer');
|
||||||
|
|
||||||
|
// Function to generate random colors
|
||||||
|
function getRandomColor() {
|
||||||
|
const letters = '0123456789ABCDEF';
|
||||||
|
let color = '#';
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
color += letters[Math.floor(Math.random() * 16)];
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skeleton Loader Template
|
||||||
|
function showSkeletonLoader() {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="col-lg-4 mb-1">
|
||||||
|
<div class="card radius-10 overflow-hidden skeleton-loader" style="height: 200px;">
|
||||||
|
<div class="card-body d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="skeleton-text mb-2" style="width: 70px; height: 20px;"></div>
|
||||||
|
<div class="skeleton-text" style="width: 50px; height: 30px;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-button" style="width: 40px; height: 40px;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skeleton-chart" style="height: 120px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('../src/filamentTracker/getFilamentPrices.php')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
container.innerHTML = ''; // Clear Skeleton
|
||||||
|
|
||||||
|
Object.keys(data.data).forEach(filament => {
|
||||||
|
const filamentData = data.data[filament];
|
||||||
|
const prices = filamentData.prices.map(entry => parseFloat(entry.price));
|
||||||
|
const timestamps = filamentData.prices.map(entry => entry.recordedAt);
|
||||||
|
let latestPrice = prices[prices.length - 1] || 0;
|
||||||
|
|
||||||
|
// Adjust price for voucher
|
||||||
|
const currentDiscount = filamentData.currentDiscount || {};
|
||||||
|
const voucher = currentDiscount.voucher || {};
|
||||||
|
if (voucher.value > 0) {
|
||||||
|
if (voucher.type === 'percentage') {
|
||||||
|
latestPrice -= (latestPrice * voucher.value) / 100; // Apply percentage voucher
|
||||||
|
} else if (voucher.type === 'fixed') {
|
||||||
|
latestPrice -= voucher.value; // Subtract fixed voucher value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure price doesn't go below zero
|
||||||
|
latestPrice = Math.max(latestPrice, 0);
|
||||||
|
|
||||||
|
// Find the last distinct price (where the price actually changed)
|
||||||
|
let lastDistinctPrice = latestPrice;
|
||||||
|
for (let i = prices.length - 2; i >= 0; i--) {
|
||||||
|
if (prices[i] !== latestPrice) {
|
||||||
|
lastDistinctPrice = prices[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceDifference = latestPrice - lastDistinctPrice;
|
||||||
|
|
||||||
|
// Price Change Indicator
|
||||||
|
let priceChangeIndicator;
|
||||||
|
if (priceDifference > 0) {
|
||||||
|
priceChangeIndicator = `<span class="text-danger ms-4">
|
||||||
|
<i class="bx bx-up-arrow-alt"></i> +£${priceDifference.toFixed(2)}</span>`;
|
||||||
|
} else if (priceDifference < 0) {
|
||||||
|
priceChangeIndicator = `<span class="text-success ms-4">
|
||||||
|
<i class="bx bx-down-arrow-alt"></i> -£${Math.abs(priceDifference).toFixed(2)}</span>`;
|
||||||
|
} else {
|
||||||
|
priceChangeIndicator = `<span class="text-muted ms-4">
|
||||||
|
<i class="bx bx-minus"></i> £0.00</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amazonUrl = filamentData.amazonUrl || '#';
|
||||||
|
|
||||||
|
// Extract discount details
|
||||||
|
const discount = currentDiscount.discount || {};
|
||||||
|
let discountText = '';
|
||||||
|
if (voucher.value > 0) {
|
||||||
|
discountText = voucher.type === 'percentage'
|
||||||
|
? `-${voucher.value}% Voucher`
|
||||||
|
: `-£${voucher.value} Voucher`;
|
||||||
|
} else if (discount.value > 0) {
|
||||||
|
discountText = discount.type === 'percentage'
|
||||||
|
? `-${discount.value}%`
|
||||||
|
: `-£${discount.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartId = `chart-${filament.replace(/\s+/g, '-')}`;
|
||||||
|
const chartColor = getRandomColor();
|
||||||
|
|
||||||
|
// Create Card HTML
|
||||||
|
const cardHTML = `
|
||||||
|
<div class="col-lg-4 mb-1">
|
||||||
|
<div class="card radius-10 overflow-hidden">
|
||||||
|
<div class="card-body d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<a href="${amazonUrl}" target="_blank" class="mb-0 filament-name">${filament}</a>
|
||||||
|
<h5 class="mb-0">
|
||||||
|
£${latestPrice.toFixed(2)} ${priceChangeIndicator}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${
|
||||||
|
discountText
|
||||||
|
? `<span class="text-success fw-bold" style="font-size: 1rem;">
|
||||||
|
${discountText}
|
||||||
|
</span>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="${chartId}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.insertAdjacentHTML('beforeend', cardHTML);
|
||||||
|
|
||||||
|
// Chart Rendering
|
||||||
|
const chartOptions = {
|
||||||
|
series: [{ name: "Price", data: prices }],
|
||||||
|
chart: {
|
||||||
|
type: "area",
|
||||||
|
height: 110,
|
||||||
|
toolbar: { show: false },
|
||||||
|
zoom: { enabled: false },
|
||||||
|
sparkline: { enabled: true },
|
||||||
|
},
|
||||||
|
markers: { size: 0 },
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
stroke: { show: true, width: 2.4, curve: "smooth" },
|
||||||
|
colors: [chartColor],
|
||||||
|
xaxis: { categories: timestamps, labels: { show: false } },
|
||||||
|
fill: { opacity: 1 },
|
||||||
|
tooltip: {
|
||||||
|
theme: "dark",
|
||||||
|
y: {
|
||||||
|
formatter: function (value, { dataPointIndex }) {
|
||||||
|
const date = new Date(timestamps[dataPointIndex]);
|
||||||
|
return `£${value} - ${date.toLocaleDateString()}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartElement = document.getElementById(chartId);
|
||||||
|
if (chartElement) {
|
||||||
|
const chart = new ApexCharts(chartElement, chartOptions);
|
||||||
|
chart.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
container.innerHTML = '<p class="text-center">No filament data available.</p>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error fetching filament data:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
showSkeletonLoader();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
249
public/viewPrinters.php
Normal file
249
public/viewPrinters.php
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!--favicon-->
|
||||||
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
|
<!--plugins-->
|
||||||
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
|
<!-- loader-->
|
||||||
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
|
<!-- Theme Style CSS -->
|
||||||
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
|
<title>TOD Dashboard</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!--wrapper-->
|
||||||
|
<div class="wrapper">
|
||||||
|
<!--sidebar wrapper -->
|
||||||
|
<?php include '../src/nav.php'; ?>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
|
<!--start header -->
|
||||||
|
<?php include '../src/header.php'; ?>
|
||||||
|
<!--end header -->
|
||||||
|
|
||||||
|
<!--start page wrapper -->
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<!--start page content -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="page-breadcrumb d-none d-sm-flex align-items-center mb-3">
|
||||||
|
<div class="breadcrumb-title pe-3">Printer Dashboard</div>
|
||||||
|
<div class="ps-3">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb mb-0 p-0">
|
||||||
|
<li class="breadcrumb-item"><a href="index.php"><i class="bx bx-home-alt"></i></a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Your Printers</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="ms-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
Refresh Interval: <span id="selectedIntervalText">60s</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" id="intervalDropdown">
|
||||||
|
<li><a class="dropdown-item set-interval" data-interval="5000" href="#">5 seconds</a></li>
|
||||||
|
<li><a class="dropdown-item set-interval" data-interval="10000" href="#">10 seconds</a></li>
|
||||||
|
<li><a class="dropdown-item set-interval" data-interval="30000" href="#">30 seconds</a></li>
|
||||||
|
<li><a class="dropdown-item set-interval" data-interval="60000" href="#">60 seconds</a></li>
|
||||||
|
<li><a class="dropdown-item set-interval" data-interval="off" href="#">Off</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- End Breadcrumb -->
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="main-body">
|
||||||
|
<div id="alertContainer" class="container mt-3">
|
||||||
|
<!-- Alerts will be dynamically inserted here -->
|
||||||
|
</div>
|
||||||
|
<div class="row" id="printerContainer">
|
||||||
|
<!-- Printer cards will be dynamically inserted here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!--end page content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--end page wrapper -->
|
||||||
|
|
||||||
|
<!--start overlay-->
|
||||||
|
<div class="overlay mobile-toggle-icon"></div>
|
||||||
|
<!--end overlay-->
|
||||||
|
<!--Start Back To Top Button-->
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<!--End Back To Top Button-->
|
||||||
|
</div>
|
||||||
|
<!--end wrapper-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- search modal -->
|
||||||
|
<div class="modal" id="SearchModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header gap-2">
|
||||||
|
<div class="position-relative popup-search w-100">
|
||||||
|
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
|
||||||
|
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end search modal -->
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!--plugins-->
|
||||||
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
|
<!--app JS-->
|
||||||
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="../assets/js/index.js"></script>
|
||||||
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(".data-attributes span").peity("donut")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
let refreshInterval = 60000; // Default interval of 10s
|
||||||
|
let intervalHandler;
|
||||||
|
|
||||||
|
// Load printers asynchronously
|
||||||
|
async function loadPrinters() {
|
||||||
|
try {
|
||||||
|
const response = await $.ajax({
|
||||||
|
url: '../src/printers/getPrinters.php',
|
||||||
|
method: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
cache: false
|
||||||
|
});
|
||||||
|
|
||||||
|
let container = $('#printerContainer');
|
||||||
|
|
||||||
|
if (response.status === 'success' && response.data.length > 0) {
|
||||||
|
response.data.forEach(function(printer) {
|
||||||
|
let existingCard = container.find(`.printer-card[data-serial="${printer.serialNumber}"]`);
|
||||||
|
|
||||||
|
let trayHTML = '';
|
||||||
|
printer.trays.forEach(tray => {
|
||||||
|
let trayColor = `#${tray.color.slice(0, 6)}`;
|
||||||
|
trayHTML += `
|
||||||
|
<div class="tray-slot text-center">
|
||||||
|
<div class="tray-circle" style="background-color: ${trayColor}; width: 40px; height: 40px; border-radius: 50%;"></div>
|
||||||
|
<p>Slot ${parseInt(tray.id) + 1}</p>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
let printerCard = `
|
||||||
|
<div class="col-lg-4 printer-card" data-serial="${printer.serialNumber}">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<img src="../assets/images/printer-icon.png" alt="Printer" class="rounded-circle p-1" width="110">
|
||||||
|
<div class="mt-3">
|
||||||
|
<h4>${printer.printerName}</h4>
|
||||||
|
<p class="text-secondary mb-1">Current Job: <span>${printer.jobName}</span></p>
|
||||||
|
<p class="text-muted font-size-sm">
|
||||||
|
Bed Temp: <span>${printer.bedTemp}°C</span> |
|
||||||
|
Nozzle Temp: <span>${printer.nozzleTemp}°C</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-center gap-4 mt-3">
|
||||||
|
${trayHTML}
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 w-100">
|
||||||
|
<label for="humidityBar${printer.serialNumber}" class="form-label">AMS Humidity</label>
|
||||||
|
<div class="progress" style="height: 20px;">
|
||||||
|
<div id="humidityBar${printer.serialNumber}" class="progress-bar" role="progressbar"
|
||||||
|
style="width: ${printer.humidity * 20}%;"
|
||||||
|
aria-valuemin="1"
|
||||||
|
aria-valuemax="5">
|
||||||
|
${printer.humidity}/5
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (existingCard.length) {
|
||||||
|
existingCard.replaceWith(printerCard);
|
||||||
|
} else {
|
||||||
|
container.append(printerCard);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to load printers:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polling function
|
||||||
|
function startPolling(interval) {
|
||||||
|
clearInterval(intervalHandler);
|
||||||
|
if (interval !== 'off') {
|
||||||
|
intervalHandler = setInterval(loadPrinters, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle refresh dropdown
|
||||||
|
$('.set-interval').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let selectedInterval = $(this).data('interval');
|
||||||
|
|
||||||
|
if (selectedInterval === 'off') {
|
||||||
|
clearInterval(intervalHandler);
|
||||||
|
console.log("Auto-refresh turned off.");
|
||||||
|
} else {
|
||||||
|
refreshInterval = parseInt(selectedInterval);
|
||||||
|
startPolling(refreshInterval);
|
||||||
|
console.log(`Refresh interval set to ${refreshInterval / 1000} seconds.`);
|
||||||
|
}
|
||||||
|
$('#selectedIntervalText').text(selectedInterval === 'off' ? 'Off' : `${selectedInterval / 1000}s`);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPrinters();
|
||||||
|
startPolling(refreshInterval); // Start polling immediately
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
$host = '172.16.18.3';
|
require_once 'envLoader.php';
|
||||||
$db = 'TechOdysseyDashboard';
|
loadEnv(__DIR__ . '/../.env');
|
||||||
$user = 'tod_admin';
|
|
||||||
$pass = 'QprczJwYor./_.T*';
|
$host = $_ENV['DB_HOST'];
|
||||||
|
$db = $_ENV['DB_NAME'];
|
||||||
|
$user = $_ENV['DB_USER'];
|
||||||
|
$pass = $_ENV['DB_PASS'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
|
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
|
||||||
@@ -10,4 +13,4 @@ try {
|
|||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
die("Database connection failed: " . $e->getMessage());
|
die("Database connection failed: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
162
src/emailService/cache/3e/3eb72c23c9363b64752d0504bdd61ac5.php
vendored
Normal file
162
src/emailService/cache/3e/3eb72c23c9363b64752d0504bdd61ac5.php
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Error\LoaderError;
|
||||||
|
use Twig\Error\RuntimeError;
|
||||||
|
use Twig\Extension\CoreExtension;
|
||||||
|
use Twig\Extension\SandboxExtension;
|
||||||
|
use Twig\Markup;
|
||||||
|
use Twig\Sandbox\SecurityError;
|
||||||
|
use Twig\Sandbox\SecurityNotAllowedTagError;
|
||||||
|
use Twig\Sandbox\SecurityNotAllowedFilterError;
|
||||||
|
use Twig\Sandbox\SecurityNotAllowedFunctionError;
|
||||||
|
use Twig\Source;
|
||||||
|
use Twig\Template;
|
||||||
|
use Twig\TemplateWrapper;
|
||||||
|
|
||||||
|
/* filament_price_summary.html.twig */
|
||||||
|
class __TwigTemplate_fb2b23b970fb7474e15dec31809a1a27 extends Template
|
||||||
|
{
|
||||||
|
private Source $source;
|
||||||
|
/**
|
||||||
|
* @var array<string, Template>
|
||||||
|
*/
|
||||||
|
private array $macros = [];
|
||||||
|
|
||||||
|
public function __construct(Environment $env)
|
||||||
|
{
|
||||||
|
parent::__construct($env);
|
||||||
|
|
||||||
|
$this->source = $this->getSourceContext();
|
||||||
|
|
||||||
|
$this->parent = false;
|
||||||
|
|
||||||
|
$this->blocks = [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doDisplay(array $context, array $blocks = []): iterable
|
||||||
|
{
|
||||||
|
$macros = $this->macros;
|
||||||
|
// line 1
|
||||||
|
yield "<!DOCTYPE html>
|
||||||
|
<html lang=\"en\">
|
||||||
|
<head>
|
||||||
|
<meta charset=\"UTF-8\">
|
||||||
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||||
|
<link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">
|
||||||
|
<title>Filament Price Change Summary</title>
|
||||||
|
<style>
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.price-drop {
|
||||||
|
color: #28a745; /* Green for price drop */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.price-rise {
|
||||||
|
color: #dc3545; /* Red for price rise */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class=\"container py-5\">
|
||||||
|
<h1 class=\"mb-4\">Filament Price Change Summary</h1>
|
||||||
|
<p class=\"lead\">Here's the latest update on filament prices from your tracker:</p>
|
||||||
|
<div class=\"table-container\">
|
||||||
|
<table class=\"table table-striped table-bordered\">
|
||||||
|
<thead class=\"thead-dark\">
|
||||||
|
<tr>
|
||||||
|
<th>Filament Name</th>
|
||||||
|
<th>Brand</th>
|
||||||
|
<th>New Price</th>
|
||||||
|
<th>Old Price</th>
|
||||||
|
<th>Change</th>
|
||||||
|
<th>Amazon Link</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
";
|
||||||
|
// line 39
|
||||||
|
$context['_parent'] = $context;
|
||||||
|
$context['_seq'] = CoreExtension::ensureTraversable(($context["filaments"] ?? null));
|
||||||
|
foreach ($context['_seq'] as $context["_key"] => $context["filament"]) {
|
||||||
|
// line 40
|
||||||
|
yield " <tr>
|
||||||
|
<td>";
|
||||||
|
// line 41
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "filamentName", [], "any", false, false, false, 41), "html", null, true);
|
||||||
|
yield "</td>
|
||||||
|
<td>";
|
||||||
|
// line 42
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "brand", [], "any", false, false, false, 42), "html", null, true);
|
||||||
|
yield "</td>
|
||||||
|
<td>";
|
||||||
|
// line 43
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "newPrice", [], "any", false, false, false, 43), "html", null, true);
|
||||||
|
yield "</td>
|
||||||
|
<td>";
|
||||||
|
// line 44
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "oldPrice", [], "any", false, false, false, 44), "html", null, true);
|
||||||
|
yield "</td>
|
||||||
|
<td class=\"price-drop\">";
|
||||||
|
// line 45
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "priceChange", [], "any", false, false, false, 45), "html", null, true);
|
||||||
|
yield "</td>
|
||||||
|
<td><a href=\"";
|
||||||
|
// line 46
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["filament"], "amazonUrl", [], "any", false, false, false, 46), "html", null, true);
|
||||||
|
yield "\" target=\"_blank\">View on Amazon</a></td>
|
||||||
|
</tr>
|
||||||
|
";
|
||||||
|
}
|
||||||
|
$_parent = $context['_parent'];
|
||||||
|
unset($context['_seq'], $context['_key'], $context['filament'], $context['_parent']);
|
||||||
|
$context = array_intersect_key($context, $_parent) + $_parent;
|
||||||
|
// line 49
|
||||||
|
yield " </tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p class=\"mt-4\">
|
||||||
|
Visit your <a href=\"";
|
||||||
|
// line 53
|
||||||
|
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["dashboardUrl"] ?? null), "html", null, true);
|
||||||
|
yield "\" target=\"_blank\">dashboard</a> for more details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
";
|
||||||
|
yield from [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function getTemplateName(): string
|
||||||
|
{
|
||||||
|
return "filament_price_summary.html.twig";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function isTraitable(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function getDebugInfo(): array
|
||||||
|
{
|
||||||
|
return array ( 124 => 53, 118 => 49, 109 => 46, 105 => 45, 101 => 44, 97 => 43, 93 => 42, 89 => 41, 86 => 40, 82 => 39, 42 => 1,);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSourceContext(): Source
|
||||||
|
{
|
||||||
|
return new Source("", "filament_price_summary.html.twig", "/mnt/www-live/TechOdyssey_Designs_Dashboard/src/emailService/templates/filament_price_summary.html.twig");
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/emailService/css/email_styles.css
Normal file
9
src/emailService/css/email_styles.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
54
src/emailService/emailService.php
Normal file
54
src/emailService/emailService.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv('../../.env');
|
||||||
|
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
|
||||||
|
function sendEmail($to, $subject, $template, $variables = []) {
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load the template
|
||||||
|
$templatePath = __DIR__ . "/templates/$template.html";
|
||||||
|
if (!file_exists($templatePath)) {
|
||||||
|
throw new Exception("Template not found: $template");
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = file_get_contents($templatePath);
|
||||||
|
|
||||||
|
// Replace variables
|
||||||
|
foreach ($variables as $key => $value) {
|
||||||
|
print_r($key);
|
||||||
|
$body = str_replace("{{" . $key . "}}", strval($value), $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add environment-based dynamic replacements
|
||||||
|
$body = str_replace("{{website_url}}", strval($_ENV['WEBSITE_URL']), $body);
|
||||||
|
$body = str_replace("{{business_name}}", strval($_ENV['BUSINESS_NAME']), $body);
|
||||||
|
|
||||||
|
// SMTP Configuration
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = $_ENV['SMTP_HOST'];
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->Username = $_ENV['SMTP_USER'];
|
||||||
|
$mail->Password = $_ENV['SMTP_PASS'];
|
||||||
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||||
|
$mail->Port = $_ENV['SMTP_PORT'];
|
||||||
|
|
||||||
|
// Email Settings
|
||||||
|
$mail->setFrom($_ENV['SMTP_USER'], $_ENV['BUSINESS_NAME']);
|
||||||
|
$mail->addAddress($to);
|
||||||
|
$mail->Subject = $subject;
|
||||||
|
$mail->isHTML(true);
|
||||||
|
$mail->Body = $body;
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
echo "Email sent successfully to $to";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: {$mail->ErrorInfo}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
60
src/emailService/sendPriceDropEmailService.php
Normal file
60
src/emailService/sendPriceDropEmailService.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use PHPMailer\PHPMailer\Exception;
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv('../../.env');
|
||||||
|
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
|
||||||
|
function sendEmail($to, $subject, $template, $variables = []) {
|
||||||
|
$mail = new PHPMailer(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Set up Twig
|
||||||
|
$loader = new FilesystemLoader(__DIR__ . '/templates');
|
||||||
|
$twig = new Environment($loader, [
|
||||||
|
'cache' => __DIR__ . '/cache', // Optional: Enable Twig cache
|
||||||
|
'auto_reload' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Render the Twig template
|
||||||
|
$body = $twig->render($template . '.html.twig', $variables);
|
||||||
|
|
||||||
|
// SMTP Configuration
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = $_ENV['SMTP_HOST'];
|
||||||
|
$mail->SMTPAuth = true;
|
||||||
|
$mail->Username = $_ENV['SMTP_USER'];
|
||||||
|
$mail->Password = $_ENV['SMTP_PASS'];
|
||||||
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||||
|
$mail->Port = $_ENV['SMTP_PORT'];
|
||||||
|
|
||||||
|
// Email Charset and Settings
|
||||||
|
$mail->CharSet = 'UTF-8'; // Ensure correct encoding
|
||||||
|
$mail->setFrom($_ENV['SMTP_USER'], $_ENV['BUSINESS_NAME']);
|
||||||
|
// Handle multiple recipients
|
||||||
|
if (is_array($to)) {
|
||||||
|
foreach ($to as $recipient) {
|
||||||
|
$mail->addAddress($recipient);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$mail->addAddress($to); // Single recipient
|
||||||
|
}
|
||||||
|
$mail->Subject = $subject;
|
||||||
|
$mail->isHTML(true);
|
||||||
|
$mail->Body = $body;
|
||||||
|
|
||||||
|
$mail->send();
|
||||||
|
foreach ($to as $recipient) {
|
||||||
|
echo "Email sent successfully to $recipient\n";
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: {$mail->ErrorInfo}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
19
src/emailService/templates/alert_error.html
Normal file
19
src/emailService/templates/alert_error.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<title>Error Alert</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h4 class="alert-heading">Error!</h4>
|
||||||
|
<p>{{message}}</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">For more details, visit our <a href="https://yourwebsite.com">dashboard</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
src/emailService/templates/alert_info.html
Normal file
19
src/emailService/templates/alert_info.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<title>Info Alert</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<h4 class="alert-heading">Information</h4>
|
||||||
|
<p>{{message}}</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">For more details, visit our <a href="https://yourwebsite.com">dashboard</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
src/emailService/templates/alert_success.html
Normal file
19
src/emailService/templates/alert_success.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<title>Success Alert</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<h4 class="alert-heading">Success!</h4>
|
||||||
|
<p>{{message}}</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">For more details, visit our <a href="https://yourwebsite.com">dashboard</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
57
src/emailService/templates/filament_price_summary.html.twig
Normal file
57
src/emailService/templates/filament_price_summary.html.twig
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<title>Filament Price Change Summary</title>
|
||||||
|
<style>
|
||||||
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.price-drop {
|
||||||
|
color: #28a745; /* Green for price drop */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.price-rise {
|
||||||
|
color: #dc3545; /* Red for price rise */
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-5">
|
||||||
|
<h1 class="mb-4">Filament Price Change Summary</h1>
|
||||||
|
<p class="lead">Here's the latest update on filament prices from your tracker:</p>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Filament Name</th>
|
||||||
|
<th>Brand</th>
|
||||||
|
<th>New Price</th>
|
||||||
|
<th>Old Price</th>
|
||||||
|
<th>Change</th>
|
||||||
|
<th>Amazon Link</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for filament in filaments %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ filament.filamentName }}</td>
|
||||||
|
<td>{{ filament.brand }}</td>
|
||||||
|
<td>{{ filament.newPrice }}</td>
|
||||||
|
<td>{{ filament.oldPrice }}</td>
|
||||||
|
<td class="price-drop">{{ filament.priceChange }}</td>
|
||||||
|
<td><a href="{{ filament.amazonUrl }}" target="_blank">View on Amazon</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p class="mt-4">
|
||||||
|
Visit your <a href="{{ dashboardUrl }}" target="_blank">dashboard</a> for more details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
include '/mnt/www-live/TechOdyssey_Designs_Dashboard/assets/php/session_check.php';
|
include '../session_check.php';
|
||||||
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/assets/php/envLoader.php'; // Load envLoader from php/
|
require '../envLoader.php'; // Load envLoader from php/
|
||||||
loadEnv(__DIR__ . '/../../../.env'); // Go up three levels to root for .env
|
loadEnv(__DIR__ . '/../../.env'); // Go up three levels to root for .env
|
||||||
|
|
||||||
$clientId = $_ENV['ETSY_KEYSTRING'];
|
$clientId = $_ENV['ETSY_KEYSTRING'];
|
||||||
$redirectUri = $_ENV['ETSY_REDIRECT_URI'];
|
$redirectUri = $_ENV['ETSY_REDIRECT_URI'];
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
include '/mnt/www-live/TechOdyssey_Designs_Dashboard/assets/php/session_check.php';
|
include '../session_check.php';
|
||||||
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/assets/php/envLoader.php'; // Load envLoader from php/
|
require '../envLoader.php'; // Load envLoader from php/
|
||||||
loadEnv(__DIR__ . '/../../../.env'); // Go up three levels to find .env
|
loadEnv(__DIR__ . '/../../../.env'); // Go up three levels to find .env
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
include '/mnt/www-live/TechOdyssey_Designs_Dashboard/assets/php/session_check.php';
|
include '../session_check.php';
|
||||||
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/assets/php/envLoader.php'; // Load envLoader from php/
|
require '../envLoader.php'; // Load envLoader from php/
|
||||||
loadEnv(__DIR__ . '/../../../.env'); // Go up three levels to find .env
|
loadEnv(__DIR__ . '/../../../.env'); // Go up three levels to find .env
|
||||||
|
|
||||||
function refreshAccessToken() {
|
function refreshAccessToken() {
|
||||||
122
src/filamentTracker/addFilament.php
Normal file
122
src/filamentTracker/addFilament.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
require 'scraper.php'; // Include the scraper file
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
include '../session_check.php';
|
||||||
|
checkUserRole(['admin']);
|
||||||
|
|
||||||
|
// Start session to get user ID
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the request method is POST
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$filamentName = $_POST['productName'] ?? '';
|
||||||
|
$amazonUrl = $_POST['productUrl'] ?? '';
|
||||||
|
$userId = $_SESSION['userId'];
|
||||||
|
|
||||||
|
// Validate the input
|
||||||
|
if (empty($filamentName) || empty($amazonUrl)) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Filament name and URL are required.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Scrape data from Amazon
|
||||||
|
$scrapedData = scrapeAmazonData($amazonUrl);
|
||||||
|
if (!$scrapedData) {
|
||||||
|
throw new Exception('Scraping failed for the provided URL.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$price = $scrapedData['price'];
|
||||||
|
$currentDiscount = json_encode($scrapedData['currentDiscount']); // Convert discount to JSON
|
||||||
|
$details = $scrapedData['details'] ?? [];
|
||||||
|
|
||||||
|
// Extract details or use defaults
|
||||||
|
$brand = $details['Brand'] ?? 'Unknown';
|
||||||
|
$material = $details['Material'] ?? 'Unknown';
|
||||||
|
$color = $details['Colour'] ?? 'Unknown';
|
||||||
|
$filamentWeight = isset($details['Item weight']) ? preg_replace('/[^0-9.]/', '', $details['Item weight']) : 1;
|
||||||
|
if (stripos($details['Item weight'] ?? '', 'kilograms') !== false || stripos($details['Item weight'] ?? '', 'kg') !== false) {
|
||||||
|
$filamentWeight *= 1000; // Convert KG to G
|
||||||
|
}
|
||||||
|
$itemDiameter = isset($details['Item diameter']) ? preg_replace('/[^0-9.]/', '', $details['Item diameter']) : 1.75;
|
||||||
|
|
||||||
|
// Begin transaction for database operations
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Check if the filament already exists in the database
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM filamentTracker WHERE amazonUrl = :amazonUrl");
|
||||||
|
$stmt->execute([':amazonUrl' => $amazonUrl]);
|
||||||
|
$existingFilament = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($existingFilament) {
|
||||||
|
// If the filament exists, update the price and discount
|
||||||
|
$filamentId = $existingFilament['id'];
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount)
|
||||||
|
VALUES (:filamentId, :price, :currentDiscount)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':filamentId' => $filamentId,
|
||||||
|
':price' => $price,
|
||||||
|
':currentDiscount' => $currentDiscount
|
||||||
|
]);
|
||||||
|
$message = 'Filament price and discount updated successfully.';
|
||||||
|
} else {
|
||||||
|
// If the filament does not exist, insert it into filamentTracker
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentTracker (userId, filamentName, amazonUrl, filamentWeight, brand, material, color, itemDiameter)
|
||||||
|
VALUES (:userId, :filamentName, :amazonUrl, :filamentWeight, :brand, :material, :color, :itemDiameter)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':userId' => $userId,
|
||||||
|
':filamentName' => $filamentName,
|
||||||
|
':amazonUrl' => $amazonUrl,
|
||||||
|
':filamentWeight' => $filamentWeight,
|
||||||
|
':brand' => $brand,
|
||||||
|
':material' => $material,
|
||||||
|
':color' => $color,
|
||||||
|
':itemDiameter' => $itemDiameter
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Get the ID of the newly inserted filament
|
||||||
|
$filamentId = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Insert the initial price and discount into filamentPriceHistory
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount)
|
||||||
|
VALUES (:filamentId, :price, :currentDiscount)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':filamentId' => $filamentId,
|
||||||
|
':price' => $price,
|
||||||
|
':currentDiscount' => $currentDiscount
|
||||||
|
]);
|
||||||
|
$message = "$filamentName added successfully and price with discount tracked.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the transaction
|
||||||
|
$pdo->commit();
|
||||||
|
echo json_encode(['status' => 'success', 'message' => $message]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Rollback transaction if any exception occurs
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
82
src/filamentTracker/getFilamentPrices.php
Normal file
82
src/filamentTracker/getFilamentPrices.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
include '../src/session_check.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Query 1: Fetch filament details and price history
|
||||||
|
$stmt1 = $pdo->query("
|
||||||
|
SELECT ft.id AS filamentId,
|
||||||
|
ft.filamentName,
|
||||||
|
ft.brand,
|
||||||
|
ft.material,
|
||||||
|
ft.color,
|
||||||
|
ft.amazonUrl,
|
||||||
|
fp.price,
|
||||||
|
fp.recordedAt
|
||||||
|
FROM filamentTracker ft
|
||||||
|
JOIN filamentPriceHistory fp ON ft.id = fp.filamentId
|
||||||
|
ORDER BY ft.filamentName ASC, fp.recordedAt ASC;
|
||||||
|
");
|
||||||
|
$filaments = $stmt1->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Query 2: Fetch the latest discount for each filament
|
||||||
|
$stmt2 = $pdo->query("
|
||||||
|
SELECT filamentId,
|
||||||
|
JSON_OBJECT(
|
||||||
|
'discount', JSON_OBJECT('value', MAX(JSON_EXTRACT(currentDiscount, '$.discount.value')), 'type', 'percentage'),
|
||||||
|
'voucher', JSON_OBJECT('value', MAX(JSON_EXTRACT(currentDiscount, '$.voucher.value')), 'type', MAX(JSON_UNQUOTE(JSON_EXTRACT(currentDiscount, '$.voucher.type'))))
|
||||||
|
) AS currentDiscount
|
||||||
|
FROM filamentPriceHistory
|
||||||
|
GROUP BY filamentId;
|
||||||
|
");
|
||||||
|
$discounts = $stmt2->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Create a mapping of filamentId to discounts
|
||||||
|
$discountMap = [];
|
||||||
|
foreach ($discounts as $discount) {
|
||||||
|
$discountMap[$discount['filamentId']] = json_decode($discount['currentDiscount'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format data for response
|
||||||
|
$result = [];
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
$name = $filament['filamentName'];
|
||||||
|
|
||||||
|
if (!isset($result[$name])) {
|
||||||
|
$result[$name] = [
|
||||||
|
'brand' => $filament['brand'],
|
||||||
|
'material' => $filament['material'],
|
||||||
|
'color' => $filament['color'],
|
||||||
|
'amazonUrl' => $filament['amazonUrl'],
|
||||||
|
'prices' => [],
|
||||||
|
'currentDiscount' => $discountMap[$filament['filamentId']] ?? [
|
||||||
|
'discount' => ['value' => 0, 'type' => 'none'],
|
||||||
|
'voucher' => ['value' => 0, 'type' => 'none']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[$name]['prices'][] = [
|
||||||
|
'price' => (float)$filament['price'],
|
||||||
|
'recordedAt' => $filament['recordedAt']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return as JSON
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $result
|
||||||
|
], JSON_PRETTY_PRINT);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Failed to fetch filament prices: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
132
src/filamentTracker/priceDropEmailCheck.php
Normal file
132
src/filamentTracker/priceDropEmailCheck.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv('../../.env');
|
||||||
|
|
||||||
|
require_once '../emailService/sendPriceDropEmailService.php';
|
||||||
|
|
||||||
|
// Fetch list of all filaments being tracked
|
||||||
|
function getFilaments() {
|
||||||
|
global $pdo;
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("SELECT id, filamentName, brand, amazonUrl FROM filamentTracker");
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Error fetching filaments: " . $e->getMessage();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the latest prices for each filament
|
||||||
|
function getCurrentPrices() {
|
||||||
|
global $pdo;
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("
|
||||||
|
SELECT fp.filamentId, fp.price, fp.recordedAt
|
||||||
|
FROM filamentPriceHistory fp
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT filamentId, MAX(recordedAt) AS latestRecordedAt
|
||||||
|
FROM filamentPriceHistory
|
||||||
|
GROUP BY filamentId
|
||||||
|
) latest ON fp.filamentId = latest.filamentId AND fp.recordedAt = latest.latestRecordedAt
|
||||||
|
");
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Error fetching current prices: " . $e->getMessage();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch prices from the last 24 hours for each filament
|
||||||
|
function getPriceHistoryForLast24Hours() {
|
||||||
|
global $pdo;
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("
|
||||||
|
SELECT filamentId, price, recordedAt
|
||||||
|
FROM filamentPriceHistory
|
||||||
|
WHERE recordedAt >= NOW() - INTERVAL 1 DAY
|
||||||
|
ORDER BY filamentId, recordedAt DESC
|
||||||
|
");
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Error fetching price history for the last 24 hours: " . $e->getMessage();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare prices and find drops
|
||||||
|
function checkPriceDropsAndNotify() {
|
||||||
|
$filaments = getFilaments();
|
||||||
|
$currentPrices = getCurrentPrices();
|
||||||
|
$priceHistory = getPriceHistoryForLast24Hours();
|
||||||
|
|
||||||
|
// Index prices by filamentId for quick lookup
|
||||||
|
$currentPricesMap = [];
|
||||||
|
foreach ($currentPrices as $current) {
|
||||||
|
$currentPricesMap[$current['filamentId']] = $current['price'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group price history by filamentId
|
||||||
|
$priceHistoryMap = [];
|
||||||
|
foreach ($priceHistory as $history) {
|
||||||
|
$priceHistoryMap[$history['filamentId']][] = $history['price'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$priceDrops = [];
|
||||||
|
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
$filamentId = $filament['id'];
|
||||||
|
$currentPrice = $currentPricesMap[$filamentId] ?? null;
|
||||||
|
|
||||||
|
if ($currentPrice !== null && isset($priceHistoryMap[$filamentId])) {
|
||||||
|
foreach ($priceHistoryMap[$filamentId] as $oldPrice) {
|
||||||
|
if ($currentPrice < (float)$oldPrice) {
|
||||||
|
$priceDrops[] = [
|
||||||
|
'filamentName' => $filament['filamentName'],
|
||||||
|
'brand' => $filament['brand'] ?: 'Unknown',
|
||||||
|
'newPrice' => "£" . number_format($currentPrice, 2),
|
||||||
|
'oldPrice' => "£" . number_format((float)$oldPrice, 2),
|
||||||
|
'priceChange' => "-£" . number_format((float)$oldPrice - $currentPrice, 2),
|
||||||
|
'amazonUrl' => $filament['amazonUrl']
|
||||||
|
];
|
||||||
|
break; // Stop checking once a price drop is detected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecipients() {
|
||||||
|
global $pdo;
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("SELECT `email` FROM `users` WHERE `alertEmails` = 1");
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Error fetching recipients: " . $e->getMessage();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email if there are price drops
|
||||||
|
if (!empty($priceDrops)) {
|
||||||
|
$recipient = getRecipients();
|
||||||
|
$subject = 'Filament Price Change Summary';
|
||||||
|
$data = [
|
||||||
|
'filaments' => $priceDrops,
|
||||||
|
'dashboardUrl' => $_ENV['WEBSITE_URL'] . '/dashboard'
|
||||||
|
];
|
||||||
|
|
||||||
|
sendEmail($recipient, $subject, 'filament_price_summary', $data);
|
||||||
|
|
||||||
|
echo "Email sent for the following price drops:\n";
|
||||||
|
foreach ($priceDrops as $drop) {
|
||||||
|
echo "- {$drop['filamentName']}: Price dropped from {$drop['oldPrice']} to {$drop['newPrice']}\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "No price drops detected.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPriceDropsAndNotify();
|
||||||
|
|
||||||
|
?>
|
||||||
67
src/filamentTracker/scraper.php
Normal file
67
src/filamentTracker/scraper.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php';
|
||||||
|
|
||||||
|
use Goutte\Client;
|
||||||
|
|
||||||
|
function scrapeAmazonData($url) {
|
||||||
|
$client = new Client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$crawler = $client->request('GET', $url);
|
||||||
|
|
||||||
|
// Filter by `centerCol`
|
||||||
|
$centerCol = $crawler->filter('#centerCol');
|
||||||
|
|
||||||
|
// Scrape price
|
||||||
|
$whole = $centerCol->filter('.a-price-whole')->count() ? $centerCol->filter('.a-price-whole')->text() : '0';
|
||||||
|
$fraction = $centerCol->filter('.a-price-fraction')->count() ? $centerCol->filter('.a-price-fraction')->text() : '00';
|
||||||
|
$whole = preg_replace('/[^0-9]/', '', $whole);
|
||||||
|
$fraction = preg_replace('/[^0-9]/', '', $fraction);
|
||||||
|
$fraction = strlen($fraction) === 1 ? $fraction . '0' : substr($fraction, 0, 2);
|
||||||
|
$totalPrice = floatval($whole . '.' . $fraction);
|
||||||
|
|
||||||
|
// Scrape discount
|
||||||
|
$discount = $centerCol->filter('.savingsPercentage')->count()
|
||||||
|
? preg_replace('/[^0-9]/', '', $centerCol->filter('.savingsPercentage')->text())
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Scrape voucher
|
||||||
|
$voucherText = '';
|
||||||
|
$centerCol->filter('.promoPriceBlockMessage')->each(function ($node) use (&$voucherText) {
|
||||||
|
$text = $node->text();
|
||||||
|
if (preg_match('/Apply (\d+%|£\d+) voucher/', $text, $voucherMatch)) {
|
||||||
|
$voucherText = $voucherMatch[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse voucher
|
||||||
|
$voucherValue = 0;
|
||||||
|
$voucherType = null;
|
||||||
|
if (!empty($voucherText)) {
|
||||||
|
if (strpos($voucherText, '%') !== false) {
|
||||||
|
$voucherValue = (int) preg_replace('/[^0-9]/', '', $voucherText); // Extract percentage
|
||||||
|
$voucherType = 'percentage';
|
||||||
|
} elseif (strpos($voucherText, '£') !== false) {
|
||||||
|
$voucherValue = (float) preg_replace('/[^0-9.]/', '', $voucherText); // Extract £ value
|
||||||
|
$voucherType = 'fixed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'price' => $totalPrice,
|
||||||
|
'currentDiscount' => [
|
||||||
|
'discount' => [
|
||||||
|
'value' => $discount,
|
||||||
|
'type' => 'percentage'
|
||||||
|
],
|
||||||
|
'voucher' => [
|
||||||
|
'value' => $voucherValue,
|
||||||
|
'type' => $voucherType
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'details' => [] // Placeholder for additional details if needed
|
||||||
|
];
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return null; // Return null if scraping fails
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/filamentTracker/updateFilamentPrices.php
Normal file
53
src/filamentTracker/updateFilamentPrices.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php';
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/db.php';
|
||||||
|
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||||
|
require 'scraper.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
// Fetch all filaments
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->query("SELECT * FROM filamentTracker");
|
||||||
|
$filaments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
$amazonUrl = $filament['amazonUrl'];
|
||||||
|
$filamentId = $filament['id'];
|
||||||
|
|
||||||
|
// Use the scraper function to fetch price and discount
|
||||||
|
$scrapedData = scrapeAmazonData($amazonUrl);
|
||||||
|
|
||||||
|
if ($scrapedData && $scrapedData['price'] > 0) {
|
||||||
|
// Prepare currentDiscount JSON structure with the correct format
|
||||||
|
$currentDiscount = json_encode([
|
||||||
|
'discount' => [
|
||||||
|
'value' => $scrapedData['currentDiscount']['discount']['value'] ?? 0,
|
||||||
|
'type' => $scrapedData['currentDiscount']['discount']['type'] ?? 'percentage',
|
||||||
|
],
|
||||||
|
'voucher' => [
|
||||||
|
'value' => $scrapedData['currentDiscount']['voucher']['value'] ?? 0,
|
||||||
|
'type' => $scrapedData['currentDiscount']['voucher']['type'] ?? null,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO filamentPriceHistory (filamentId, price, currentDiscount)
|
||||||
|
VALUES (:filamentId, :price, :currentDiscount)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':filamentId' => $filamentId,
|
||||||
|
':price' => $scrapedData['price'],
|
||||||
|
':currentDiscount' => $currentDiscount
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo "Updated price for {$filament['filamentName']}: £{$scrapedData['price']}, Current Discount: {$currentDiscount}\n";
|
||||||
|
} else {
|
||||||
|
echo "Failed to update {$filament['filamentName']} (no price found or £0).\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a small random delay between 1 to 5 seconds
|
||||||
|
sleep(rand(1, 5));
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Database error: " . $e->getMessage();
|
||||||
|
}
|
||||||
104
src/header.php
Normal file
104
src/header.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<header>
|
||||||
|
<div class="topbar">
|
||||||
|
<nav class="navbar navbar-expand gap-2 align-items-center">
|
||||||
|
<div class="mobile-toggle-menu d-flex"><i class='bx bx-menu'></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-bar d-lg-block d-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
||||||
|
<a href="avascript:;" class="btn d-flex align-items-center"><i class="bx bx-search"></i>Search</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="top-menu ms-auto">
|
||||||
|
<ul class="navbar-nav align-items-center gap-1">
|
||||||
|
<li class="nav-item mobile-search-icon d-flex d-lg-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
||||||
|
<a class="nav-link" href="avascript:;"><i class='bx bx-search'></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item dark-mode d-none d-sm-flex">
|
||||||
|
<a class="nav-link dark-mode-icon" href="javascript:;"><i class='bx bx-moon'></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item dropdown dropdown-large">
|
||||||
|
<a class="nav-link dropdown-toggle dropdown-toggle-nocaret position-relative" href="#" data-bs-toggle="dropdown"><span class="alert-count">7</span>
|
||||||
|
<i class='bx bx-bell'></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
<a href="javascript:;">
|
||||||
|
<div class="msg-header">
|
||||||
|
<p class="msg-header-title">Notifications</p>
|
||||||
|
<p class="msg-header-badge">8 New</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="header-notifications-list">
|
||||||
|
<a class="dropdown-item" href="javascript:;">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="user-online">
|
||||||
|
<img src="../assets/images/avatars/avatar-1.png" class="msg-avatar" alt="user avatar">
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h6 class="msg-name">Daisy Anderson<span class="msg-time float-end">5 sec
|
||||||
|
ago</span></h6>
|
||||||
|
<p class="msg-info">The standard chunk of lorem</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<a href="javascript:;">
|
||||||
|
<div class="text-center msg-footer">
|
||||||
|
<button class="btn btn-primary w-100">View All Notifications</button>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="user-box dropdown px-3">
|
||||||
|
<a class="d-flex align-items-center nav-link dropdown-toggle gap-3 dropdown-toggle-nocaret" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<img src="../assets/images/avatars/avatar-2.png" class="user-img" alt="user avatar">
|
||||||
|
<div class="user-info">
|
||||||
|
<p class="user-name mb-0"><?php echo isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : 'Guest'; ?></p>
|
||||||
|
<p class="designattion mb-0"><?php echo isset($_SESSION['role']) ? htmlspecialchars($_SESSION['role']) : 'N/A'; ?></p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
||||||
|
</li>
|
||||||
|
<li><a class="dropdown-item d-flex align-items-center" href="printerForm.php"><i class="bx bx-cog fs-5"></i><span>Add Printer</span></a>
|
||||||
|
</li>
|
||||||
|
<li><a class="dropdown-item d-flex align-items-center" href="userManagement.php"><i class="bx bx-cog fs-5"></i><span>user Management</span></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="dropdown-divider mb-0"></div>
|
||||||
|
</li>
|
||||||
|
<li><a class="dropdown-item d-flex align-items-center" href="../public/logout.php"><i class="bx bx-log-out-circle"></i><span>Logout</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<?php if (isset($_SESSION['errorMessage'])): ?>
|
||||||
|
<div id="sessionAlert" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<?php echo $_SESSION['errorMessage']; ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
let alertElement = document.getElementById('sessionAlert');
|
||||||
|
if (alertElement) {
|
||||||
|
alertElement.classList.remove('show');
|
||||||
|
alertElement.classList.add('fade');
|
||||||
|
alertElement.addEventListener('transitionend', function () {
|
||||||
|
alertElement.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
|
// Clear the session variable after rendering
|
||||||
|
<?php unset($_SESSION['errorMessage']); ?>
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
require 'db.php';
|
require_once 'db.php';
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
$username = htmlspecialchars($_POST['username']);
|
$username = htmlspecialchars($_POST['username']);
|
||||||
@@ -12,7 +12,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
|||||||
|
|
||||||
if ($user && password_verify($password, $user['password']) && !$user['disabled']) {
|
if ($user && password_verify($password, $user['password']) && !$user['disabled']) {
|
||||||
// Store user ID, username, and role in session
|
// Store user ID, username, and role in session
|
||||||
$_SESSION['user_id'] = $user['id'];
|
$_SESSION['userId'] = $user['id'];
|
||||||
$_SESSION['username'] = $user['username'];
|
$_SESSION['username'] = $user['username'];
|
||||||
$_SESSION['role'] = $user['role']; // Store user role
|
$_SESSION['role'] = $user['role']; // Store user role
|
||||||
echo 'success';
|
echo 'success';
|
||||||
80
src/nav.php
Normal file
80
src/nav.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<!--sidebar wrapper -->
|
||||||
|
<div class="sidebar-wrapper" data-simplebar="true">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<div>
|
||||||
|
<img src="../assets/images/logo-icon.png" class="logo-icon" alt="logo icon">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 class="logo-text">TechOdyssey</h4>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-toggle-icon ms-auto"><i class='bx bx-x'></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--navigation-->
|
||||||
|
<ul class="metismenu" id="menu">
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="index.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-home-alt'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Dashboard</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Shipping Section -->
|
||||||
|
<li class="menu-label">Shipping</li>
|
||||||
|
<li>
|
||||||
|
<a href="shipping.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-code-alt'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Shipping Dashboard</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Printing Section -->
|
||||||
|
<li class="menu-label">Printing Control</li>
|
||||||
|
<li>
|
||||||
|
<a href="viewPrinters.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-code-alt'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Printer Dashboard</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="filamentDryer.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-plus-circle'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Dryer Control</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Filament Tracking Section -->
|
||||||
|
<li class="menu-label">Filament Tracker</li>
|
||||||
|
<li>
|
||||||
|
<a href="viewFilament.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-layer'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Filament Overview</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="addFilament.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-plus-circle'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Add Filament</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Settings or Admin Section -->
|
||||||
|
<li class="menu-label">Settings</li>
|
||||||
|
<li>
|
||||||
|
<a href="settings.php">
|
||||||
|
<div class="parent-icon"><i class='bx bx-cog'></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">User Settings</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!--end navigation-->
|
||||||
|
</div>
|
||||||
|
<!--end sidebar wrapper -->
|
||||||
53
src/printers/addPrinter.php
Normal file
53
src/printers/addPrinter.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
session_start(); // Ensure session is started to get logged-in user ID
|
||||||
|
require '../db.php';
|
||||||
|
require_once '../envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Check if the user is logged in
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $_SESSION['userId']; // Retrieve user ID from session
|
||||||
|
|
||||||
|
// Handle POST Request
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// Collect and sanitize form data
|
||||||
|
$printerName = filter_input(INPUT_POST, 'printerName', FILTER_SANITIZE_STRING);
|
||||||
|
$printerIp = filter_input(INPUT_POST, 'printerIp', FILTER_VALIDATE_IP);
|
||||||
|
$serialNumber = filter_input(INPUT_POST, 'serialNumber', FILTER_SANITIZE_STRING);
|
||||||
|
$accessCode = filter_input(INPUT_POST, 'accessCode', FILTER_SANITIZE_STRING);
|
||||||
|
|
||||||
|
if (!$printerName || !$printerIp || !$serialNumber || !$accessCode) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'All fields are required and IP must be valid.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prepare SQL insert statement with user ID
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO bambuPrinters (printerName, printerIp, mqttPassword, serialNumber, userId)
|
||||||
|
VALUES (:printerName, :printerIp, :mqttPassword, :serialNumber, :userId)
|
||||||
|
");
|
||||||
|
|
||||||
|
// Execute the query with bound parameters
|
||||||
|
$stmt->execute([
|
||||||
|
':printerName' => $printerName,
|
||||||
|
':printerIp' => $printerIp,
|
||||||
|
':mqttPassword' => $accessCode,
|
||||||
|
':serialNumber' => $serialNumber,
|
||||||
|
':userId' => $userId
|
||||||
|
]);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'message' => 'Printer successfully added!']);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Error adding printer: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
79
src/printers/getPrinters.php
Normal file
79
src/printers/getPrinters.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require '../db.php';
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'User not authenticated.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $_SESSION['userId'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Subquery to fetch latest telemetry for each printer
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT p.*, t.telemetry, t.recordedAt
|
||||||
|
FROM bambuPrinters p
|
||||||
|
LEFT JOIN printerTelemetry t
|
||||||
|
ON t.id = (
|
||||||
|
SELECT id FROM printerTelemetry
|
||||||
|
WHERE printerId = p.id
|
||||||
|
ORDER BY recordedAt DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE p.userId = :userId
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([':userId' => $userId]);
|
||||||
|
$printers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($printers as $printer) {
|
||||||
|
$telemetry = json_decode($printer['telemetry'], true);
|
||||||
|
|
||||||
|
$bedTemp = isset($telemetry['print']['bed_temper']) ? $telemetry['print']['bed_temper'] : 'N/A';
|
||||||
|
$nozzleTemp = isset($telemetry['print']['device']['nozzle']['0']['temp'])
|
||||||
|
? $telemetry['print']['device']['nozzle']['0']['temp']
|
||||||
|
: 'N/A';
|
||||||
|
$jobName = isset($telemetry['subtask_name']) ? $telemetry['subtask_name'] : 'No job running';
|
||||||
|
$status = isset($telemetry['print']['gcode_state']) ? $telemetry['print']['gcode_state'] : 'Unknown';
|
||||||
|
|
||||||
|
// AMS Data
|
||||||
|
$humidity = isset($telemetry['print']['ams']['ams'][0]['humidity'])
|
||||||
|
? $telemetry['print']['ams']['ams'][0]['humidity']
|
||||||
|
: 0;
|
||||||
|
$trays = isset($telemetry['print']['ams']['ams'][0]['tray'])
|
||||||
|
? $telemetry['print']['ams']['ams'][0]['tray']
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Structure tray data for direct output
|
||||||
|
$trayData = [];
|
||||||
|
foreach ($trays as $tray) {
|
||||||
|
$trayData[] = [
|
||||||
|
'id' => $tray['id'],
|
||||||
|
'color' => isset($tray['tray_color']) ? $tray['tray_color'] : 'ccc'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'printerId' => $printer['id'],
|
||||||
|
'printerName' => $printer['printerName'],
|
||||||
|
'serialNumber' => $printer['serialNumber'],
|
||||||
|
'printerIp' => $printer['printerIp'],
|
||||||
|
'bedTemp' => $bedTemp,
|
||||||
|
'nozzleTemp' => $nozzleTemp,
|
||||||
|
'jobName' => $jobName,
|
||||||
|
'status' => $status,
|
||||||
|
'humidity' => $humidity,
|
||||||
|
'trays' => $trayData,
|
||||||
|
'recordedAt' => $printer['recordedAt'] ?? 'Never'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'success', 'data' => $result]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Failed to fetch printers: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
105
src/printers/mqttConnect.php
Normal file
105
src/printers/mqttConnect.php
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/vendor/autoload.php';
|
||||||
|
require '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/db.php';
|
||||||
|
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
use PhpMqtt\Client\MqttClient;
|
||||||
|
use PhpMqtt\Client\ConnectionSettings;
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Enable asynchronous signal handling
|
||||||
|
pcntl_async_signals(true);
|
||||||
|
|
||||||
|
// MQTT Configuration from .env
|
||||||
|
$mqttPort = $_ENV['MQTT_PORT'];
|
||||||
|
$mqttUsername = $_ENV['MQTT_USERNAME'];
|
||||||
|
$clientId = 'printer_monitor_' . uniqid();
|
||||||
|
|
||||||
|
// Fetch printers associated with the logged-in user
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM bambuPrinters");
|
||||||
|
$stmt->execute();
|
||||||
|
$printers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die(json_encode(['status' => 'error', 'message' => 'Failed to fetch printers.']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process 2 messages per printer
|
||||||
|
$maxMessagesPerPrinter = 2;
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($printers as $printer) {
|
||||||
|
echo "Connecting to " . $printer['printerName'] . " (" . $printer['serialNumber'] . ")...\n";
|
||||||
|
echo "IP Address: " . $printer['printerIp'] . "\n";
|
||||||
|
echo "MQTT Password: " . $printer['mqttPassword'] . "\n";
|
||||||
|
|
||||||
|
$mqttPassword = $printer['mqttPassword']; // Use mqttPassword from DB
|
||||||
|
$printerIp = $printer['printerIp']; // Printer IP from DB
|
||||||
|
$messageCount = 0; // Reset counter per printer
|
||||||
|
|
||||||
|
$mqtt = new MqttClient($printerIp, $mqttPort, $clientId);
|
||||||
|
|
||||||
|
// Handle SIGINT (CTRL + C) to gracefully exit
|
||||||
|
pcntl_signal(SIGINT, function () use ($mqtt) {
|
||||||
|
echo "Interrupt received. Disconnecting MQTT...\n";
|
||||||
|
$mqtt->interrupt();
|
||||||
|
});
|
||||||
|
|
||||||
|
$connectionSettings = (new ConnectionSettings())
|
||||||
|
->setUsername($mqttUsername)
|
||||||
|
->setPassword($mqttPassword)
|
||||||
|
->setKeepAliveInterval(60)
|
||||||
|
->setUseTls(true)
|
||||||
|
->setTlsVerifyPeer(false)
|
||||||
|
->setTlsVerifyPeerName(false);
|
||||||
|
|
||||||
|
// Establish Connection
|
||||||
|
$mqtt->connect($connectionSettings, true);
|
||||||
|
|
||||||
|
// Subscribe to the printer's report topic
|
||||||
|
$topic = "device/{$printer['serialNumber']}/report";
|
||||||
|
|
||||||
|
$mqtt->subscribe($topic, function (string $topic, string $message) use (
|
||||||
|
$printer, $pdo, &$messageCount, $maxMessagesPerPrinter, $mqtt
|
||||||
|
) {
|
||||||
|
echo "[" . $printer['printerName'] . "] [$topic] $message\n";
|
||||||
|
|
||||||
|
// Store full JSON telemetry report
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO printerTelemetry
|
||||||
|
(printerId, telemetry)
|
||||||
|
VALUES
|
||||||
|
(:printerId, :telemetry)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
':printerId' => $printer['id'],
|
||||||
|
':telemetry' => $message
|
||||||
|
]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("Failed to insert telemetry: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$messageCount++;
|
||||||
|
|
||||||
|
// Stop loop after 2 messages for this printer
|
||||||
|
if ($messageCount >= $maxMessagesPerPrinter) {
|
||||||
|
echo "Processed $maxMessagesPerPrinter messages for " . $printer['printerName'] . ". Disconnecting...\n";
|
||||||
|
$mqtt->interrupt();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Start Listening for Messages
|
||||||
|
$mqtt->loop(true); // Loop until interrupted after processing 2 messages
|
||||||
|
$mqtt->disconnect();
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die(json_encode(['status' => 'error', 'message' => 'MQTT connection failed. ' . $e->getMessage()]));
|
||||||
|
}
|
||||||
|
?>
|
||||||
39
src/session_check.php
Normal file
39
src/session_check.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Configuration for session timeout (in seconds)
|
||||||
|
define('SESSION_TIMEOUT', 1800); // 30 minutes
|
||||||
|
|
||||||
|
// Check if the user is logged in
|
||||||
|
if (!isset($_SESSION['userId'])) {
|
||||||
|
redirectToLogin("You must be logged in to access this page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session Timeout Check
|
||||||
|
if (isset($_SESSION['lastActivity']) && (time() - $_SESSION['lastActivity']) > SESSION_TIMEOUT) {
|
||||||
|
// Session expired
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
redirectToLogin("Session expired. Please log in again.");
|
||||||
|
} else {
|
||||||
|
$_SESSION['lastActivity'] = time(); // Update activity timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check user roles
|
||||||
|
function checkUserRole($allowedRoles = []) {
|
||||||
|
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], $allowedRoles)) {
|
||||||
|
$_SESSION['errorMessage'] = "Access denied: You do not have the required permissions.";
|
||||||
|
header("Location: dashboard.php"); // Redirect to dashboard or another page
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to redirect to login with optional message
|
||||||
|
function redirectToLogin($message = '') {
|
||||||
|
if (!empty($message)) {
|
||||||
|
$_SESSION['errorMessage'] = $message;
|
||||||
|
}
|
||||||
|
header("Location: login.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
?>
|
||||||
74
src/shipping/get_account_info.php
Normal file
74
src/shipping/get_account_info.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
// Get API credentials from the environment
|
||||||
|
$client_id = $_ENV['P2G_CLIENT_ID'];
|
||||||
|
$client_secret = $_ENV['P2G_CLIENT_SECRET'];
|
||||||
|
$auth_url = $_ENV['P2G_API_AUTH_URL'];
|
||||||
|
$account_info_url = 'https://www.parcel2go.com/api/me/detail';
|
||||||
|
|
||||||
|
// Function to get the API token
|
||||||
|
function getApiToken($client_id, $client_secret, $auth_url) {
|
||||||
|
$ch = curl_init($auth_url);
|
||||||
|
|
||||||
|
$data = http_build_query([
|
||||||
|
'grant_type' => 'client_credentials',
|
||||||
|
'scope' => 'public-api',
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_secret' => $client_secret,
|
||||||
|
]);
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/x-www-form-urlencoded',
|
||||||
|
'Accept: */*',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
return $result['access_token'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to fetch account information
|
||||||
|
function getAccountInfo($token, $account_info_url) {
|
||||||
|
$ch = curl_init($account_info_url);
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer $token",
|
||||||
|
'Accept: application/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic
|
||||||
|
$token = getApiToken($client_id, $client_secret, $auth_url);
|
||||||
|
$account_info = getAccountInfo($token, $account_info_url);
|
||||||
|
|
||||||
|
if ($token) {
|
||||||
|
$account_info = getAccountInfo($token, $account_info_url);
|
||||||
|
|
||||||
|
if ($account_info) {
|
||||||
|
echo json_encode(['success' => true, 'data' => $account_info]);
|
||||||
|
} else {
|
||||||
|
http_response_code(402);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to fetch account information.']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http_response_code(402);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
73
src/shipping/get_orders_detail.php
Normal file
73
src/shipping/get_orders_detail.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
// Get API credentials from the environment
|
||||||
|
$client_id = $_ENV['P2G_CLIENT_ID'];
|
||||||
|
$client_secret = $_ENV['P2G_CLIENT_SECRET'];
|
||||||
|
$auth_url = $_ENV['P2G_API_AUTH_URL'];
|
||||||
|
$orders_detail_url = 'https://www.parcel2go.com/api/me/orders/detail';
|
||||||
|
|
||||||
|
// Function to get the API token
|
||||||
|
function getApiToken($client_id, $client_secret, $auth_url) {
|
||||||
|
$ch = curl_init($auth_url);
|
||||||
|
|
||||||
|
$data = http_build_query([
|
||||||
|
'grant_type' => 'client_credentials',
|
||||||
|
'scope' => 'public-api',
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_secret' => $client_secret,
|
||||||
|
]);
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/x-www-form-urlencoded',
|
||||||
|
'Accept: */*',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
return $result['access_token'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to fetch order details
|
||||||
|
function getOrdersDetail($token, $orders_detail_url) {
|
||||||
|
$ch = curl_init($orders_detail_url);
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer $token",
|
||||||
|
'Accept: application/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic
|
||||||
|
$token = getApiToken($client_id, $client_secret, $auth_url);
|
||||||
|
|
||||||
|
if ($token) {
|
||||||
|
$orders_detail = getOrdersDetail($token, $orders_detail_url);
|
||||||
|
|
||||||
|
if ($orders_detail) {
|
||||||
|
echo json_encode(['success' => true, 'data' => $orders_detail]);
|
||||||
|
} else {
|
||||||
|
http_response_code(402);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to fetch order details.']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http_response_code(402);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
73
src/shipping/get_prepay_balance.php
Normal file
73
src/shipping/get_prepay_balance.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once '/mnt/www-live/TechOdyssey_Designs_Dashboard/src/envLoader.php';
|
||||||
|
loadEnv(__DIR__ . '/../../.env');
|
||||||
|
|
||||||
|
// Get API credentials from the environment
|
||||||
|
$client_id = $_ENV['P2G_CLIENT_ID'];
|
||||||
|
$client_secret = $_ENV['P2G_CLIENT_SECRET'];
|
||||||
|
$auth_url = $_ENV['P2G_API_AUTH_URL'];
|
||||||
|
$prepay_balance_url = 'https://www.parcel2go.com/api/me/prepay/balance';
|
||||||
|
|
||||||
|
// Function to get the API token
|
||||||
|
function getApiToken($client_id, $client_secret, $auth_url) {
|
||||||
|
$ch = curl_init($auth_url);
|
||||||
|
|
||||||
|
$data = http_build_query([
|
||||||
|
'grant_type' => 'client_credentials',
|
||||||
|
'scope' => 'public-api',
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_secret' => $client_secret,
|
||||||
|
]);
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
'Content-Type: application/x-www-form-urlencoded',
|
||||||
|
'Accept: */*',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
return $result['access_token'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to fetch prepay balance
|
||||||
|
function getPrepayBalance($token, $prepay_balance_url) {
|
||||||
|
$ch = curl_init($prepay_balance_url);
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||||
|
"Authorization: Bearer $token",
|
||||||
|
'Accept: application/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic
|
||||||
|
$token = getApiToken($client_id, $client_secret, $auth_url);
|
||||||
|
|
||||||
|
if ($token) {
|
||||||
|
$balance_info = getPrepayBalance($token, $prepay_balance_url);
|
||||||
|
|
||||||
|
if ($balance_info) {
|
||||||
|
echo json_encode(['success' => true, 'data' => $balance_info]);
|
||||||
|
} else {
|
||||||
|
http_response_code(402);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to fetch prepay balance.']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http_response_code(402);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Failed to authenticate.']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
111
src/userMananagementService.php
Normal file
111
src/userMananagementService.php
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once 'db.php';
|
||||||
|
require_once 'session_check.php';
|
||||||
|
checkUserRole(['admin']);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database connection failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle requests
|
||||||
|
$method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
|
||||||
|
if ($method === 'GET') {
|
||||||
|
fetchUsers();
|
||||||
|
} elseif ($method === 'POST') {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (isset($input['username']) && isset($input['email']) && isset($input['role']) && isset($input['password'])) {
|
||||||
|
createUser($input);
|
||||||
|
} elseif (isset($_GET['reset-password'])) {
|
||||||
|
$userId = intval($_GET['reset-password']);
|
||||||
|
resetPassword($userId, $input);
|
||||||
|
} elseif (isset($_GET['delete-user'])) {
|
||||||
|
$userId = intval($_GET['delete-user']);
|
||||||
|
deleteUser($userId);
|
||||||
|
} else {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(["error" => "Invalid request"]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(["error" => "Method not allowed"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchUsers()
|
||||||
|
{
|
||||||
|
global $pdo;
|
||||||
|
$stmt = $pdo->prepare("SELECT id, username, email, role, disabled, created_at FROM users");
|
||||||
|
$stmt->execute();
|
||||||
|
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
echo json_encode($users);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUser($input)
|
||||||
|
{
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
$hashedPassword = password_hash($input['password'], PASSWORD_BCRYPT);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (username, email, role, password, disabled, created_at) VALUES (:username, :email, :role, :password, 0, NOW())");
|
||||||
|
|
||||||
|
if ($stmt->execute([
|
||||||
|
':username' => $input['username'],
|
||||||
|
':email' => $input['email'],
|
||||||
|
':role' => $input['role'],
|
||||||
|
':password' => $hashedPassword
|
||||||
|
])) {
|
||||||
|
http_response_code(201);
|
||||||
|
echo json_encode(["success" => "User created successfully"]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => "Failed to create user"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPassword($userId, $input)
|
||||||
|
{
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
if (!isset($input['password'])) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(["error" => "Password not provided"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newPassword = password_hash($input['password'], PASSWORD_BCRYPT);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET password = :password WHERE id = :id");
|
||||||
|
|
||||||
|
if ($stmt->execute([
|
||||||
|
':password' => $newPassword,
|
||||||
|
':id' => $userId
|
||||||
|
])) {
|
||||||
|
echo json_encode(["success" => "Password reset successfully"]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => "Failed to reset password"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUser($userId)
|
||||||
|
{
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
|
||||||
|
|
||||||
|
if ($stmt->execute([':id' => $userId])) {
|
||||||
|
echo json_encode(["success" => "User deleted successfully"]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["error" => "Failed to delete user"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
template.php
134
template.php
@@ -1,32 +1,32 @@
|
|||||||
<?php include 'assets/php/session_check.php'; ?>
|
<?php include '../src/session_check.php'; ?>
|
||||||
|
|
||||||
<html lang="en" data-bs-theme="light">
|
<html lang="en" data-bs-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<!-- Required meta tags -->
|
<!-- Required meta tags -->
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!--favicon-->
|
<!--favicon-->
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png">
|
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
|
||||||
<!--plugins-->
|
<!--plugins-->
|
||||||
<link href="assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
|
||||||
<link href="assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
|
||||||
<!-- loader-->
|
<!-- loader-->
|
||||||
<link href="assets/css/pace.min.css" rel="stylesheet"/>
|
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
|
||||||
<script src="assets/js/pace.min.js"></script>
|
<script src="../assets/js/pace.min.js"></script>
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="assets/css/bootstrap.min.css" rel="stylesheet">
|
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="assets/css/bootstrap-extended.css" rel="stylesheet">
|
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link href="assets/sass/app.css" rel="stylesheet">
|
<link href="../assets/sass/app.css" rel="stylesheet">
|
||||||
<link href="assets/css/icons.css" rel="stylesheet">
|
<link href="../assets/css/icons.css" rel="stylesheet">
|
||||||
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
|
||||||
<!-- Theme Style CSS -->
|
<!-- Theme Style CSS -->
|
||||||
<link rel="stylesheet" href="assets/sass/dark-theme.css">
|
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
|
||||||
<link rel="stylesheet" href="assets/sass/semi-dark.css">
|
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
|
||||||
<link rel="stylesheet" href="assets/sass/bordered-theme.css">
|
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
|
||||||
|
|
||||||
<title>TOD Dashboard</title>
|
<title>TOD Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
@@ -35,88 +35,10 @@
|
|||||||
<!--wrapper-->
|
<!--wrapper-->
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<!--sidebar wrapper -->
|
<!--sidebar wrapper -->
|
||||||
<?php include 'assets/php/nav.php'; ?>
|
<?php include '../src/nav.php'; ?>
|
||||||
<!--end sidebar wrapper -->
|
<!--end sidebar wrapper -->
|
||||||
<!--start header -->
|
<!--start header -->
|
||||||
<header>
|
<?php include '../src/header.php'; ?>
|
||||||
<div class="topbar">
|
|
||||||
<nav class="navbar navbar-expand gap-2 align-items-center">
|
|
||||||
<div class="mobile-toggle-menu d-flex"><i class='bx bx-menu'></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-bar d-lg-block d-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
|
||||||
<a href="avascript:;" class="btn d-flex align-items-center"><i class="bx bx-search"></i>Search</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="top-menu ms-auto">
|
|
||||||
<ul class="navbar-nav align-items-center gap-1">
|
|
||||||
<li class="nav-item mobile-search-icon d-flex d-lg-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
|
|
||||||
<a class="nav-link" href="avascript:;"><i class='bx bx-search'></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item dark-mode d-none d-sm-flex">
|
|
||||||
<a class="nav-link dark-mode-icon" href="javascript:;"><i class='bx bx-moon'></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="nav-item dropdown dropdown-large">
|
|
||||||
<a class="nav-link dropdown-toggle dropdown-toggle-nocaret position-relative" href="#" data-bs-toggle="dropdown"><span class="alert-count">7</span>
|
|
||||||
<i class='bx bx-bell'></i>
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
|
||||||
<a href="javascript:;">
|
|
||||||
<div class="msg-header">
|
|
||||||
<p class="msg-header-title">Notifications</p>
|
|
||||||
<p class="msg-header-badge">8 New</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="header-notifications-list">
|
|
||||||
<a class="dropdown-item" href="javascript:;">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="user-online">
|
|
||||||
<img src="assets/images/avatars/avatar-1.png" class="msg-avatar" alt="user avatar">
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<h6 class="msg-name">Daisy Anderson<span class="msg-time float-end">5 sec
|
|
||||||
ago</span></h6>
|
|
||||||
<p class="msg-info">The standard chunk of lorem</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a href="javascript:;">
|
|
||||||
<div class="text-center msg-footer">
|
|
||||||
<button class="btn btn-primary w-100">View All Notifications</button>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="user-box dropdown px-3">
|
|
||||||
<a class="d-flex align-items-center nav-link dropdown-toggle gap-3 dropdown-toggle-nocaret" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<img src="assets/images/avatars/avatar-2.png" class="user-img" alt="user avatar">
|
|
||||||
<div class="user-info">
|
|
||||||
<p class="user-name mb-0"><?php echo isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : 'Guest'; ?></p>
|
|
||||||
<p class="designattion mb-0"><?php echo isset($_SESSION['role']) ? htmlspecialchars($_SESSION['role']) : 'N/A'; ?></p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-user fs-5"></i><span>Profile</span></a>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="javascript:;"><i class="bx bx-cog fs-5"></i><span>My Printers</span></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="dropdown-divider mb-0"></div>
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item d-flex align-items-center" href="logout.php"><i class="bx bx-log-out-circle"></i><span>Logout</span></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<!--end header -->
|
<!--end header -->
|
||||||
|
|
||||||
<!--start page wrapper -->
|
<!--start page wrapper -->
|
||||||
@@ -126,7 +48,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -165,18 +87,18 @@
|
|||||||
<!-- end search modal -->
|
<!-- end search modal -->
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script src="assets/js/bootstrap.bundle.min.js"></script>
|
<script src="../assets/js/bootstrap.bundle.min.js"></script>
|
||||||
<!--plugins-->
|
<!--plugins-->
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
<script src="../assets/js/jquery.min.js"></script>
|
||||||
<script src="assets/plugins/simplebar/js/simplebar.min.js"></script>
|
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
|
||||||
<script src="assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
|
||||||
<script src="assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
|
||||||
<script src="assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
|
||||||
<!--app JS-->
|
<!--app JS-->
|
||||||
<script src="assets/js/app.js"></script>
|
<script src="../assets/js/app.js"></script>
|
||||||
|
|
||||||
<script src="assets/js/index.js"></script>
|
<script src="../assets/js/index.js"></script>
|
||||||
<script src="assets/plugins/peity/jquery.peity.min.js"></script>
|
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(".data-attributes span").peity("donut")
|
$(".data-attributes span").peity("donut")
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user