mirror of
https://github.com/michalcz10/USB-RAID-Array.git
synced 2025-12-10 03:22:19 +00:00
Pushed betatest to production
This commit is contained in:
@@ -34,6 +34,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
if (empty($uname) || empty($pswd)) {
|
if (empty($uname) || empty($pswd)) {
|
||||||
$_SESSION['message'] = 'Error: Username and password are required!';
|
$_SESSION['message'] = 'Error: Username and password are required!';
|
||||||
$_SESSION['message_type'] = 'error';
|
$_SESSION['message_type'] = 'error';
|
||||||
|
} else if (!CheckPassword($pswd)) {
|
||||||
|
$_SESSION['message'] = 'Error: Password must be at least 8 characters long, contain at least one number and one uppercase letter!';
|
||||||
|
$_SESSION['message_type'] = 'error';
|
||||||
} else {
|
} else {
|
||||||
$sql_check = "SELECT * FROM users WHERE uname = ?";
|
$sql_check = "SELECT * FROM users WHERE uname = ?";
|
||||||
$stmt_check = $conn->prepare($sql_check);
|
$stmt_check = $conn->prepare($sql_check);
|
||||||
@@ -117,6 +120,19 @@ $message = $_SESSION['message'] ?? '';
|
|||||||
$message_type = $_SESSION['message_type'] ?? '';
|
$message_type = $_SESSION['message_type'] ?? '';
|
||||||
unset($_SESSION['message']);
|
unset($_SESSION['message']);
|
||||||
unset($_SESSION['message_type']);
|
unset($_SESSION['message_type']);
|
||||||
|
|
||||||
|
function CheckPassword($password) {
|
||||||
|
if (strlen($password) < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!preg_match('/[0-9]/', $password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!preg_match('/[A-Z]/', $password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -151,6 +167,7 @@ unset($_SESSION['message_type']);
|
|||||||
<a href="logout.php" class="btn btn-danger">Logout</a>
|
<a href="logout.php" class="btn btn-danger">Logout</a>
|
||||||
<a href="changepassword.php" class="btn btn-warning">Change Password</a>
|
<a href="changepassword.php" class="btn btn-warning">Change Password</a>
|
||||||
<a href="ftp/index.php" class="btn btn-primary">SFTP</a>
|
<a href="ftp/index.php" class="btn btn-primary">SFTP</a>
|
||||||
|
<a href="ftp/serverstat.php" class="btn btn-primary">Server Status</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section class="row">
|
<section class="row">
|
||||||
@@ -228,7 +245,7 @@ unset($_SESSION['message_type']);
|
|||||||
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
||||||
<span class="text-muted">Developed by Michal Sedlák</span>
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="https://github.com/michalcz10/USB-RAID-pole" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
<img src="../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
<img src="../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
<img src="../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
<img src="../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$confirm_password = htmlspecialchars($_POST['confirm_password']);
|
$confirm_password = htmlspecialchars($_POST['confirm_password']);
|
||||||
$uname = $_SESSION['uname'];
|
$uname = $_SESSION['uname'];
|
||||||
|
|
||||||
if ($new_password !== $confirm_password) {
|
if(!CheckPassword($new_password)) {
|
||||||
|
$message = "New password must be at least 8 characters long, contain at least one number and one uppercase letter!";
|
||||||
|
$messageType = "danger";
|
||||||
|
} else if ($new_password !== $confirm_password) {
|
||||||
$message = "New passwords do not match!";
|
$message = "New passwords do not match!";
|
||||||
$messageType = "danger";
|
$messageType = "danger";
|
||||||
} else {
|
} else {
|
||||||
@@ -74,6 +77,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$messageType = "danger";
|
$messageType = "danger";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CheckPassword($password) {
|
||||||
|
if (strlen($password) < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!preg_match('/[0-9]/', $password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!preg_match('/[A-Z]/', $password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -140,7 +156,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
||||||
<span class="text-muted">Developed by Michal Sedlák</span>
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
<div class="d-flex gap-3 flex-wrap justify-content-center">
|
<div class="d-flex gap-3 flex-wrap justify-content-center">
|
||||||
<a href="https://github.com/michalcz10/USB-RAID-pole" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
<img src="../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
<img src="../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
<img src="../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
<img src="../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
253
Web/content/ftp/css/pdf.css
Normal file
253
Web/content/ftp/css/pdf.css
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#pdfContainer {
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
#loadingMessage {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] #loadingMessage {
|
||||||
|
background-color: rgba(33, 37, 41, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] #loadingMessage {
|
||||||
|
background-color: rgba(248, 249, 250, 0.9);
|
||||||
|
}
|
||||||
|
.page-container {
|
||||||
|
background-color: white;
|
||||||
|
margin: 10px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-effect {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-effect:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .dark-logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .light-logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.file-info table {
|
||||||
|
table-layout: fixed;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.actionButton {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode header,
|
||||||
|
.fullscreen-mode footer,
|
||||||
|
.fullscreen-mode .theme-toggle,
|
||||||
|
.fullscreen-mode .actionButton {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode #pdfContainer {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .controls {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: rgba(var(--bs-body-bg-rgb), 0.3);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0.3;
|
||||||
|
z-index: 1000;
|
||||||
|
border-radius: .375rem;
|
||||||
|
width: auto;
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .controls:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgba(var(--bs-body-bg-rgb), 0.9);
|
||||||
|
box-shadow: 0 0 15px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode[data-bs-theme="light"] .btn-light {
|
||||||
|
background-color: rgba(233, 236, 239, 0.7);
|
||||||
|
border-color: rgba(222, 226, 230, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode[data-bs-theme="dark"] .btn-light {
|
||||||
|
background-color: rgba(52, 58, 64, 0.7);
|
||||||
|
border-color: rgba(73, 80, 87, 0.7);
|
||||||
|
}
|
||||||
|
.fullscreen-mode .btn-light:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .btn-light.disabled {
|
||||||
|
background-color: rgba(var(--bs-body-bg-rgb), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .page-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
margin: 0;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.fullscreen-mode .btn-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .btn-group .btn {
|
||||||
|
min-width: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .btn-group .btn.disabled {
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
/* Mobile-specific adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.fullscreen-mode .controls {
|
||||||
|
bottom: 5px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
width: auto;
|
||||||
|
max-width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .btn-group .btn {
|
||||||
|
min-width: 36px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .btn-group .btn.disabled {
|
||||||
|
min-width: 110px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode #pageNum,
|
||||||
|
.fullscreen-mode #pageCount {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-mode .btn-group {
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add spacing between page numbers and "of" text */
|
||||||
|
.fullscreen-mode .btn-light.disabled span {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme-specific button styles */
|
||||||
|
[data-bs-theme="light"] .btn-light {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-color: #dee2e6;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .btn-light:hover {
|
||||||
|
background-color: #dde2e6;
|
||||||
|
border-color: #ced4da;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .btn-light {
|
||||||
|
background-color: #343a40;
|
||||||
|
border-color: #495057;
|
||||||
|
color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .btn-light:hover {
|
||||||
|
background-color: #495057;
|
||||||
|
border-color: #6c757d;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button group specific styles */
|
||||||
|
.btn-group .btn-light {
|
||||||
|
margin: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group .btn-light.disabled {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
124
Web/content/ftp/css/serverstat.css
Normal file
124
Web/content/ftp/css/serverstat.css
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/* Layout */
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid var(--bs-border-color);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monitor Cards */
|
||||||
|
.monitor-card {
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-card .card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-header {
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
border-bottom: 1px solid var(--bs-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
background-color: var(--bs-tertiary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CPU Gauge */
|
||||||
|
.cpu-gauge {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu-gauge canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu-gauge .position-relative {
|
||||||
|
position: absolute !important;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -55%);
|
||||||
|
z-index: 2;
|
||||||
|
width: auto;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu-gauge .h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu-gauge .small {
|
||||||
|
opacity: 0.75;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme & Logo */
|
||||||
|
.hover-effect {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-effect:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .dark-logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .light-logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid Spacing */
|
||||||
|
.row.g-4 {
|
||||||
|
--bs-gutter-y: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h4 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-card .card-body {
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.g-4 > [class*="col-"] {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
244
Web/content/ftp/extract.php
Normal file
244
Web/content/ftp/extract.php
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if(!isset($_SESSION['uname'])){
|
||||||
|
header("location: ../../index.php");
|
||||||
|
session_destroy();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has permission to upload/modify
|
||||||
|
if(!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) {
|
||||||
|
die("You don't have permission to extract archives.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include error reporting for debugging
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require 'config.php';
|
||||||
|
use phpseclib3\Net\SFTP;
|
||||||
|
|
||||||
|
$sftp = initializeSFTP($host, $username, $password);
|
||||||
|
|
||||||
|
// Process extraction request
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['archive'])) {
|
||||||
|
$archivePath = $_POST['archive'];
|
||||||
|
$dirPath = dirname($archivePath);
|
||||||
|
$archiveName = basename($archivePath);
|
||||||
|
$extension = pathinfo($archiveName, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
// Create a temporary local directory for extraction
|
||||||
|
$tempDir = sys_get_temp_dir() . '/sftp_extract_' . time();
|
||||||
|
if (!mkdir($tempDir, 0777, true)) {
|
||||||
|
die("Failed to create temporary directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the archive to the temp directory
|
||||||
|
$localArchivePath = $tempDir . '/' . $archiveName;
|
||||||
|
if (!$sftp->get($archivePath, $localArchivePath)) {
|
||||||
|
rmdir($tempDir);
|
||||||
|
die("Failed to download the archive");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect archive type and extract
|
||||||
|
$extractionSuccess = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine the extraction method based on file extension
|
||||||
|
switch (strtolower($extension)) {
|
||||||
|
case 'zip':
|
||||||
|
$extractionSuccess = extractZip($localArchivePath, $tempDir);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rar':
|
||||||
|
$extractionSuccess = extractRar($localArchivePath, $tempDir);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tar':
|
||||||
|
case 'gz':
|
||||||
|
case 'bz2':
|
||||||
|
case 'xz':
|
||||||
|
case '7z':
|
||||||
|
$extractionSuccess = extractArchive($localArchivePath, $tempDir);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
die("Unsupported archive format");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($extractionSuccess) {
|
||||||
|
// Upload extracted files back to the server
|
||||||
|
uploadExtractedFiles($sftp, $tempDir, $dirPath);
|
||||||
|
|
||||||
|
// Clean up temporary directory
|
||||||
|
deleteDirectory($tempDir);
|
||||||
|
|
||||||
|
// Redirect back to the file listing
|
||||||
|
header("Location: index.php?path=" . urlencode($dirPath));
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
die("Failed to extract the archive");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
deleteDirectory($tempDir);
|
||||||
|
die("Error during extraction: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ZIP archives
|
||||||
|
function extractZip($archivePath, $destination) {
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
|
||||||
|
if ($zip->open($archivePath) === TRUE) {
|
||||||
|
$zip->extractTo($destination);
|
||||||
|
$zip->close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract RAR archives (requires rar extension or unrar command)
|
||||||
|
function extractRar($archivePath, $destination) {
|
||||||
|
// Try PHP Rar extension first
|
||||||
|
if (extension_loaded('rar')) {
|
||||||
|
$rar = RarArchive::open($archivePath);
|
||||||
|
if ($rar) {
|
||||||
|
$entries = $rar->getEntries();
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$entry->extract($destination);
|
||||||
|
}
|
||||||
|
$rar->close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to command line unrar if available
|
||||||
|
if (shell_exec("which unrar") || file_exists('C:\\Program Files\\WinRAR\\UnRAR.exe')) {
|
||||||
|
$command = '';
|
||||||
|
|
||||||
|
if (PHP_OS_FAMILY === 'Windows') {
|
||||||
|
$command = '"C:\\Program Files\\WinRAR\\UnRAR.exe" x -o+ ' . escapeshellarg($archivePath) . ' ' . escapeshellarg($destination);
|
||||||
|
} else {
|
||||||
|
$command = 'unrar x -o+ ' . escapeshellarg($archivePath) . ' ' . escapeshellarg($destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
exec($command, $output, $returnCode);
|
||||||
|
return $returnCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("RAR extraction is not available. Please install PHP RAR extension or UnRAR command line tool.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract other archives using system commands
|
||||||
|
function extractArchive($archivePath, $destination) {
|
||||||
|
$extension = pathinfo($archivePath, PATHINFO_EXTENSION);
|
||||||
|
$command = '';
|
||||||
|
|
||||||
|
// Change to the destination directory
|
||||||
|
$currentDir = getcwd();
|
||||||
|
chdir($destination);
|
||||||
|
|
||||||
|
if (PHP_OS_FAMILY === 'Windows') {
|
||||||
|
// For Windows, you'll need to have 7-Zip installed
|
||||||
|
$sevenZipPath = 'C:\\Program Files\\7-Zip\\7z.exe';
|
||||||
|
if (file_exists($sevenZipPath)) {
|
||||||
|
$command = '"' . $sevenZipPath . '" x ' . escapeshellarg($archivePath);
|
||||||
|
} else {
|
||||||
|
throw new Exception("7-Zip is not installed or not found at the expected location.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For Linux/Unix systems
|
||||||
|
switch (strtolower($extension)) {
|
||||||
|
case 'tar':
|
||||||
|
$command = 'tar -xf ' . escapeshellarg($archivePath);
|
||||||
|
break;
|
||||||
|
case 'gz':
|
||||||
|
if (strpos($archivePath, '.tar.gz') !== false) {
|
||||||
|
$command = 'tar -xzf ' . escapeshellarg($archivePath);
|
||||||
|
} else {
|
||||||
|
$command = 'gzip -d ' . escapeshellarg($archivePath);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'bz2':
|
||||||
|
if (strpos($archivePath, '.tar.bz2') !== false) {
|
||||||
|
$command = 'tar -xjf ' . escapeshellarg($archivePath);
|
||||||
|
} else {
|
||||||
|
$command = 'bzip2 -d ' . escapeshellarg($archivePath);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'xz':
|
||||||
|
if (strpos($archivePath, '.tar.xz') !== false) {
|
||||||
|
$command = 'tar -xJf ' . escapeshellarg($archivePath);
|
||||||
|
} else {
|
||||||
|
$command = 'xz -d ' . escapeshellarg($archivePath);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '7z':
|
||||||
|
$command = '7z x ' . escapeshellarg($archivePath);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unsupported archive format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec($command, $output, $returnCode);
|
||||||
|
|
||||||
|
// Change back to the original directory
|
||||||
|
chdir($currentDir);
|
||||||
|
|
||||||
|
return $returnCode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload extracted files back to the SFTP server
|
||||||
|
function uploadExtractedFiles($sftp, $localDir, $remoteDir) {
|
||||||
|
$items = scandir($localDir);
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($item === '.' || $item === '..') continue;
|
||||||
|
|
||||||
|
$localPath = $localDir . '/' . $item;
|
||||||
|
$remotePath = $remoteDir . '/' . $item;
|
||||||
|
|
||||||
|
if (is_dir($localPath)) {
|
||||||
|
// Create the directory on the remote server
|
||||||
|
if (!$sftp->is_dir($remotePath)) {
|
||||||
|
$sftp->mkdir($remotePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload the contents of the directory
|
||||||
|
uploadExtractedFiles($sftp, $localPath, $remotePath);
|
||||||
|
} else {
|
||||||
|
// Upload the file - use the proper phpseclib3 method
|
||||||
|
$sftp->put($remotePath, $localPath, SFTP::SOURCE_LOCAL_FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively delete a directory
|
||||||
|
function deleteDirectory($dir) {
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = scandir($dir);
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($item === '.' || $item === '..') continue;
|
||||||
|
|
||||||
|
$path = $dir . '/' . $item;
|
||||||
|
|
||||||
|
if (is_dir($path)) {
|
||||||
|
deleteDirectory($path);
|
||||||
|
} else {
|
||||||
|
unlink($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmdir($dir);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
$videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
|
$videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
|
||||||
$audioExtensions = ['mp3', 'wav', 'm4a', 'flac', 'aac'];
|
$audioExtensions = ['mp3', 'wav', 'm4a', 'flac', 'aac'];
|
||||||
$editableExtensions = ['txt', 'html', 'css', 'js', 'php', 'xml', 'json', 'md', 'csv', 'log', 'ini', 'conf', 'sh', 'bat', 'py', 'rb', 'java', 'c', 'cpp', 'h', 'hpp'];
|
$editableExtensions = ['txt', 'html', 'css', 'js', 'php', 'xml', 'json', 'md', 'csv', 'log', 'ini', 'conf', 'sh', 'bat', 'py', 'rb', 'java', 'c', 'cpp', 'h', 'hpp'];
|
||||||
|
$archiveExtensions = ['zip', 'rar', 'tar', 'gz', '7z', 'bz2', 'xz', 'tar.gz', 'tar.bz2', 'tar.xz'];
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -58,6 +59,7 @@
|
|||||||
<a href="../changepassword.php" class="btn btn-warning">Change Password</a>
|
<a href="../changepassword.php" class="btn btn-warning">Change Password</a>
|
||||||
<?php if (isset($_SESSION["admin"]) && $_SESSION["admin"] == true) { ?>
|
<?php if (isset($_SESSION["admin"]) && $_SESSION["admin"] == true) { ?>
|
||||||
<a href="../adminpanel.php" class="btn btn-primary">Admin Panel</a>
|
<a href="../adminpanel.php" class="btn btn-primary">Admin Panel</a>
|
||||||
|
<a href="serverstat.php" class="btn btn-primary">Server Status</a>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -82,7 +84,8 @@
|
|||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create Directory Dialog -->
|
<!-- Modal Forms -->
|
||||||
|
<!-- Create Directory Dialog -->
|
||||||
<div class="modal fade" id="createDirModal" tabindex="-1" aria-labelledby="createDirModalLabel" aria-hidden="true">
|
<div class="modal fade" id="createDirModal" tabindex="-1" aria-labelledby="createDirModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -127,6 +130,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Rename Confirmation Dialog -->
|
||||||
|
<div class="modal fade" id="renameModal" tabindex="-1" aria-labelledby="renameModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="renameModalLabel">Rename Item</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="rename.php">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="originalName" class="form-label">Current Name:</label>
|
||||||
|
<input type="text" class="form-control" id="originalName" disabled>
|
||||||
|
<input type="hidden" id="fullPath" name="path">
|
||||||
|
<input type="hidden" id="isDirectory" name="isDirectory">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="newName" class="form-label">New Name:</label>
|
||||||
|
<input type="text" class="form-control" id="newName" name="newName" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Rename</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End of Modal Forms -->
|
||||||
|
|
||||||
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) { ?>
|
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) { ?>
|
||||||
<div class="dropzone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)">
|
<div class="dropzone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)">
|
||||||
Drop files or folders here to upload
|
Drop files or folders here to upload
|
||||||
@@ -137,6 +171,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
<th>Size</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -161,29 +196,32 @@
|
|||||||
|
|
||||||
if ($currentPath !== $defaultPath) : ?>
|
if ($currentPath !== $defaultPath) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><a class="text-danger" href="?path=<?= urlencode(dirname($currentPath)) ?>"><b>.. (Go Back)</b></a></td>
|
<td colspan="3"><a class="text-danger" href="?path=<?= urlencode(dirname($currentPath)) ?>"><b>.. (Go Back)</b></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endif;
|
<?php endif;
|
||||||
|
|
||||||
foreach ($directories as $directory) : ?>
|
foreach ($directories as $directory) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td colspan="2">
|
||||||
<a href="?path=<?= urlencode($currentPath . '/' . $directory) ?>"><?= htmlspecialchars($directory) ?>/</a>
|
<a href="?path=<?= urlencode($currentPath . '/' . $directory) ?>"><?= htmlspecialchars($directory) ?>/</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?>
|
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?>
|
||||||
<form method="POST" action="delete.php" style="display:inline;">
|
<form method="POST" action="delete.php" style="display:inline;">
|
||||||
<input type="hidden" name="delete" value="<?= htmlspecialchars($currentPath . '/' . $directory) ?>">
|
<input type="hidden" name="delete" value="<?= htmlspecialchars($currentPath . '/' . $directory) ?>">
|
||||||
<button type="submit" class="btn btn-danger" onclick="confirmDelete(event)">Delete</button>
|
<button type="submit" class="btn btn-danger mt-1 mb-1" onclick="confirmDelete(event)">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
||||||
<form method="POST" action="download.php" style="display:inline;">
|
<form method="POST" action="download.php" style="display:inline;">
|
||||||
<input type="hidden" name="file" value="<?= htmlspecialchars($currentPath . '/' . $directory) ?>">
|
<input type="hidden" name="file" value="<?= htmlspecialchars($currentPath . '/' . $directory) ?>">
|
||||||
<button type="submit" class="btn btn-success">Download</button>
|
<button type="submit" class="btn btn-success mt-1 mb-1">Download</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) : ?>
|
||||||
|
<button type="button" class="btn btn-warning mt-1 mb-1" onclick="openRenameModal('<?= htmlspecialchars(addslashes($directory)) ?>', '<?= htmlspecialchars(addslashes($currentPath . '/' . $directory)) ?>')">Rename</button>
|
||||||
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach;
|
<?php endforeach;
|
||||||
@@ -192,11 +230,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$fileExtension = pathinfo($file, PATHINFO_EXTENSION);
|
$fileExtension = pathinfo($file, PATHINFO_EXTENSION);
|
||||||
|
$isPDF = strtolower($fileExtension) === 'pdf';
|
||||||
$isImage = in_array($fileExtension, $imageExtensions);
|
$isImage = in_array($fileExtension, $imageExtensions);
|
||||||
$isVideo = in_array($fileExtension, $videoExtensions);
|
$isVideo = in_array($fileExtension, $videoExtensions);
|
||||||
$isAudio = in_array($fileExtension, $audioExtensions);
|
$isAudio = in_array($fileExtension, $audioExtensions);
|
||||||
$isMedia = $isImage || $isVideo || $isAudio;
|
$isMedia = $isImage || $isVideo || $isAudio;
|
||||||
$isEditable = in_array($fileExtension, $editableExtensions);
|
$isEditable = in_array($fileExtension, $editableExtensions);
|
||||||
|
$isArchive = in_array($fileExtension, $archiveExtensions);
|
||||||
|
if (!$isArchive && strpos($file, '.tar.') !== false) {
|
||||||
|
$isArchive = true;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -217,23 +260,40 @@
|
|||||||
<?= htmlspecialchars($file) ?>
|
<?= htmlspecialchars($file) ?>
|
||||||
</a>
|
</a>
|
||||||
<span class="badge bg-secondary rounded-pill"><?= htmlspecialchars($fileExtension) ?></span>
|
<span class="badge bg-secondary rounded-pill"><?= htmlspecialchars($fileExtension) ?></span>
|
||||||
|
<?php elseif ($isPDF): ?>
|
||||||
|
<a class="text-info-emphasis" href="pdf.php?file=<?= urlencode($currentPath . '/' . $file) ?>&type=pdf">
|
||||||
|
<?= htmlspecialchars($file) ?>
|
||||||
|
</a>
|
||||||
|
<span class="badge bg-danger rounded-pill">PDF</span>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?= htmlspecialchars($file) ?>
|
<?= htmlspecialchars($file) ?>
|
||||||
<span class="badge bg-light text-dark rounded-pill"><?= htmlspecialchars($fileExtension) ?></span>
|
<span class="badge bg-light text-dark rounded-pill"><?= htmlspecialchars($fileExtension) ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="text"><?= formatBytes($sftp->stat($currentPath . '/' . $file)['size']) ?></span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?>
|
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?>
|
||||||
<form method="POST" action="delete.php" style="display:inline;">
|
<form method="POST" action="delete.php" style="display:inline;">
|
||||||
<input type="hidden" name="delete" value="<?= htmlspecialchars($currentPath . '/' . $file) ?>">
|
<input type="hidden" name="delete" value="<?= htmlspecialchars($currentPath . '/' . $file) ?>">
|
||||||
<button type="submit" class="btn btn-danger" onclick="confirmDelete(event)">Delete</button>
|
<button type="submit" class="btn btn-danger mt-1 mb-1" onclick="confirmDelete(event)">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
||||||
<form method="POST" action="download.php" style="display:inline;">
|
<form method="POST" action="download.php" style="display:inline;">
|
||||||
<input type="hidden" name="file" value="<?= htmlspecialchars($currentPath . '/' . $file) ?>">
|
<input type="hidden" name="file" value="<?= htmlspecialchars($currentPath . '/' . $file) ?>">
|
||||||
<button type="submit" class="btn btn-success">Download</button>
|
<button type="submit" class="btn btn-success mt-1 mb-1">Download</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) : ?>
|
||||||
|
<button type="button" class="btn btn-warning mt-1 mb-1" onclick="openRenameModal('<?= htmlspecialchars(addslashes($file)) ?>', '<?= htmlspecialchars(addslashes($currentPath . '/' . $file)) ?>')">Rename</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($isArchive && isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) : ?>
|
||||||
|
<form method="POST" action="extract.php" style="display:inline;">
|
||||||
|
<input type="hidden" name="archive" value="<?= htmlspecialchars($currentPath . '/' . $file) ?>">
|
||||||
|
<button type="submit" class="btn btn-info mt-1 mb-1">Extract</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
@@ -247,7 +307,7 @@
|
|||||||
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
||||||
<span class="text-muted">Developed by Michal Sedlák</span>
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="https://github.com/michalcz10/USB-RAID-pole" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
</a>
|
</a>
|
||||||
@@ -265,3 +325,14 @@
|
|||||||
<script src="js/index.js"></script>
|
<script src="js/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
function formatBytes($bytes, $precision = 2) {
|
||||||
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
$bytes = max($bytes, 0);
|
||||||
|
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||||
|
$pow = min($pow, count($units) - 1);
|
||||||
|
$bytes /= (1 << (10 * $pow));
|
||||||
|
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -236,6 +236,16 @@ function createFile() {
|
|||||||
};
|
};
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
}
|
}
|
||||||
|
function openRenameModal(name, path) {
|
||||||
|
document.getElementById('originalName').value = name;
|
||||||
|
document.getElementById('fullPath').value = path;
|
||||||
|
document.getElementById('isDirectory').value = name.endsWith('/') ? '1' : '0';
|
||||||
|
document.getElementById('newName').value = name.endsWith('/') ? name.slice(0, -1) : name;
|
||||||
|
|
||||||
|
const renameModal = new bootstrap.Modal(document.getElementById('renameModal'));
|
||||||
|
renameModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const themeToggle = document.getElementById('themeToggle');
|
const themeToggle = document.getElementById('themeToggle');
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
|
|||||||
331
Web/content/ftp/pdf.php
Normal file
331
Web/content/ftp/pdf.php
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if(!isset($_SESSION['uname'])){
|
||||||
|
header("location: ../../index.php");
|
||||||
|
session_destroy();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require 'config.php';
|
||||||
|
$sftp = initializeSFTP($host, $username, $password);
|
||||||
|
|
||||||
|
if (!isset($_GET['file'])) {
|
||||||
|
die("No file specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = $_GET['file'];
|
||||||
|
$fileName = basename($filePath);
|
||||||
|
$fileSize = $sftp->stat($filePath)['size'];
|
||||||
|
|
||||||
|
if (isset($_GET['stream'])) {
|
||||||
|
session_write_close();
|
||||||
|
|
||||||
|
while (ob_get_level()) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ini_get('zlib.output_compression')) {
|
||||||
|
ini_set('zlib.output_compression', 'Off');
|
||||||
|
}
|
||||||
|
|
||||||
|
$start = 0;
|
||||||
|
$end = $fileSize - 1;
|
||||||
|
$length = $fileSize;
|
||||||
|
|
||||||
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
|
$rangeHeader = $_SERVER['HTTP_RANGE'];
|
||||||
|
$matches = [];
|
||||||
|
if (preg_match('/bytes=(\d+)-(\d*)/', $rangeHeader, $matches)) {
|
||||||
|
$start = intval($matches[1]);
|
||||||
|
|
||||||
|
if (!empty($matches[2])) {
|
||||||
|
$end = intval($matches[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = $end - $start + 1;
|
||||||
|
|
||||||
|
header('HTTP/1.1 206 Partial Content');
|
||||||
|
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/pdf');
|
||||||
|
header('Content-Disposition: inline; filename="' . basename($filePath) . '"');
|
||||||
|
header("Accept-Ranges: bytes");
|
||||||
|
header("Content-Length: $length");
|
||||||
|
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: 0");
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
$minChunkSize = 64 * 1024; // 64KB minimum
|
||||||
|
$maxChunkSize = 2 * 1024 * 1024; // 2MB maximum
|
||||||
|
$chunkSize = 256 * 1024; // Start with 256KB
|
||||||
|
|
||||||
|
$currentPosition = $start;
|
||||||
|
$bytesRemaining = $length;
|
||||||
|
$lastChunkTime = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ($bytesRemaining > 0) {
|
||||||
|
if (connection_aborted()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$readSize = min($chunkSize, $bytesRemaining);
|
||||||
|
|
||||||
|
$chunkData = $sftp->get($filePath, false, $currentPosition, $readSize);
|
||||||
|
|
||||||
|
if ($chunkData !== false) {
|
||||||
|
$bytesSent = strlen($chunkData);
|
||||||
|
echo $chunkData;
|
||||||
|
$bytesRemaining -= $bytesSent;
|
||||||
|
$currentPosition += $bytesSent;
|
||||||
|
|
||||||
|
if (ob_get_level()) {
|
||||||
|
ob_flush();
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
|
||||||
|
$currentTime = microtime(true);
|
||||||
|
$timeDiff = $currentTime - $lastChunkTime;
|
||||||
|
$lastChunkTime = $currentTime;
|
||||||
|
|
||||||
|
if ($timeDiff > 0) {
|
||||||
|
$speed = $bytesSent / $timeDiff;
|
||||||
|
$chunkSize = min(
|
||||||
|
max($minChunkSize, $chunkSize * (($speed > 512 * 1024) ? 1.5 : 0.8)),
|
||||||
|
$maxChunkSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Streaming error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PDF Viewer</title>
|
||||||
|
<link rel="icon" href="../../img/favicon.ico" type="image/x-icon">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="../../css/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="css/pdf.css">
|
||||||
|
<script src="../../js/bootstrap.bundle.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="d-flex justify-content-end p-3">
|
||||||
|
<button id="themeToggle" class="btn btn-sm theme-toggle">
|
||||||
|
<i class="bi"></i>
|
||||||
|
<span id="themeText"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="custom-container">
|
||||||
|
<header class="text-center border-bottom m-5">
|
||||||
|
<h1 class="mb-4">PDF Viewer</h1>
|
||||||
|
<div class="mb-3">
|
||||||
|
<a href="index.php?path=<?= urlencode(dirname($filePath)) ?>" class="btn btn-primary">Back to Files</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="pdfContainer" class="container-fluid position-relative">
|
||||||
|
<div id="loadingMessage" class="d-flex align-items-center justify-content-center w-100 h-100">
|
||||||
|
<div class="bg-dark bg-opacity-75 text-white p-3 rounded">
|
||||||
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||||
|
Loading PDF...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="btn-group" role="group" aria-label="PDF Navigation">
|
||||||
|
<button id="prev" class="btn btn-light border">
|
||||||
|
<i class="bi bi-chevron-left"></i> Previous
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-light border disabled">
|
||||||
|
Page <span id="pageNum"></span> of <span id="pageCount"></span>
|
||||||
|
</button>
|
||||||
|
<button id="next" class="btn btn-light border">
|
||||||
|
Next <i class="bi bi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
<button id="fullscreen" class="btn btn-light border">
|
||||||
|
<i class="bi bi-fullscreen"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actionButton">
|
||||||
|
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
||||||
|
<a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
||||||
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
|
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
|
</a>
|
||||||
|
<a href="https://app.freelo.io/public/shared-link-view/?a=81efbcb4df761b3f29cdc80855b41e6d&b=4519c717f0729cc8e953af661e9dc981" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="../../img/freelo-logo-rgb.png" alt="Freelo Logo" class="img-fluid hover-effect light-logo" style="height: 24px;">
|
||||||
|
<img src="../../img/freelo-logo-rgb-on-dark.png" alt="Freelo Logo" class="img-fluid hover-effect dark-logo" style="height: 24px;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||||
|
|
||||||
|
const url = 'pdf.php?file=<?= urlencode($filePath) ?>&stream=1';
|
||||||
|
const pdfFileName = '<?= htmlspecialchars($fileName) ?>';
|
||||||
|
let currentPage = 1;
|
||||||
|
let pdfDoc = null;
|
||||||
|
|
||||||
|
const PdfPageStorage = {
|
||||||
|
getStorageKey() {
|
||||||
|
return `pdf_page_${pdfFileName}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
savePage(pageNum) {
|
||||||
|
localStorage.setItem(this.getStorageKey(), pageNum.toString());
|
||||||
|
},
|
||||||
|
|
||||||
|
loadPage() {
|
||||||
|
return parseInt(localStorage.getItem(this.getStorageKey())) || 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadPDF() {
|
||||||
|
try {
|
||||||
|
pdfDoc = await pdfjsLib.getDocument(url).promise;
|
||||||
|
document.getElementById('pageCount').textContent = pdfDoc.numPages;
|
||||||
|
|
||||||
|
currentPage = Math.min(PdfPageStorage.loadPage(), pdfDoc.numPages);
|
||||||
|
|
||||||
|
renderPage(currentPage);
|
||||||
|
document.getElementById('loadingMessage').classList.add('d-none');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading PDF:', error);
|
||||||
|
const loadingMessage = document.getElementById('loadingMessage');
|
||||||
|
loadingMessage.querySelector('div').classList.add('bg-danger');
|
||||||
|
loadingMessage.querySelector('div').innerHTML = '<i class="bi bi-exclamation-triangle"></i> Error loading PDF';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPage(pageNumber) {
|
||||||
|
const page = await pdfDoc.getPage(pageNumber);
|
||||||
|
const pageContainer = document.createElement('div');
|
||||||
|
pageContainer.className = 'page-container';
|
||||||
|
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
const viewport = page.getViewport({ scale: 1.0 });
|
||||||
|
|
||||||
|
const pixelRatio = window.devicePixelRatio || 1;
|
||||||
|
const widthScale = (windowWidth / viewport.width);
|
||||||
|
const heightScale = (windowHeight / viewport.height);
|
||||||
|
|
||||||
|
let scale;
|
||||||
|
if (isFullscreen) {
|
||||||
|
scale = Math.min(widthScale, heightScale) * 0.95;
|
||||||
|
} else if (windowWidth < 768) {
|
||||||
|
scale = widthScale * 0.95;
|
||||||
|
} else {
|
||||||
|
scale = Math.min(widthScale, heightScale, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scaledViewport = page.getViewport({ scale: scale * pixelRatio });
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
canvas.height = scaledViewport.height;
|
||||||
|
canvas.width = scaledViewport.width;
|
||||||
|
|
||||||
|
canvas.style.width = `${scaledViewport.width / pixelRatio}px`;
|
||||||
|
canvas.style.height = `${scaledViewport.height / pixelRatio}px`;
|
||||||
|
|
||||||
|
pageContainer.appendChild(canvas);
|
||||||
|
document.getElementById('pdfContainer').innerHTML = '';
|
||||||
|
document.getElementById('pdfContainer').appendChild(pageContainer);
|
||||||
|
document.getElementById('pageNum').textContent = pageNumber;
|
||||||
|
|
||||||
|
await page.render({
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: scaledViewport // Use scaledViewport instead of viewport
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
PdfPageStorage.savePage(pageNumber);
|
||||||
|
}
|
||||||
|
document.getElementById('prev').addEventListener('click', () => {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderPage(currentPage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('next').addEventListener('click', () => {
|
||||||
|
if (currentPage < pdfDoc.numPages) {
|
||||||
|
currentPage++;
|
||||||
|
renderPage(currentPage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && isFullscreen) {
|
||||||
|
document.getElementById('fullscreen').click();
|
||||||
|
} else if (e.key === 'ArrowLeft' && currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderPage(currentPage);
|
||||||
|
} else if (e.key === 'ArrowRight' && currentPage < pdfDoc.numPages) {
|
||||||
|
currentPage++;
|
||||||
|
renderPage(currentPage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (currentPage) {
|
||||||
|
renderPage(currentPage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let isFullscreen = false;
|
||||||
|
|
||||||
|
document.getElementById('fullscreen').addEventListener('click', () => {
|
||||||
|
const container = document.querySelector('.custom-container');
|
||||||
|
const fullscreenBtn = document.getElementById('fullscreen');
|
||||||
|
const fullscreenIcon = fullscreenBtn.querySelector('i');
|
||||||
|
|
||||||
|
if (!isFullscreen) {
|
||||||
|
container.classList.add('fullscreen-mode');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
fullscreenIcon.classList.remove('bi-fullscreen');
|
||||||
|
fullscreenIcon.classList.add('bi-fullscreen-exit');
|
||||||
|
} else {
|
||||||
|
container.classList.remove('fullscreen-mode');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
fullscreenIcon.classList.remove('bi-fullscreen-exit');
|
||||||
|
fullscreenIcon.classList.add('bi-fullscreen');
|
||||||
|
}
|
||||||
|
|
||||||
|
isFullscreen = !isFullscreen;
|
||||||
|
renderPage(currentPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPDF();
|
||||||
|
</script>
|
||||||
|
<script src="../../js/theme.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
Web/content/ftp/rename.php
Normal file
48
Web/content/ftp/rename.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if(!isset($_SESSION['uname'])){
|
||||||
|
header("location: ../../index.php");
|
||||||
|
session_destroy();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) {
|
||||||
|
die("You don't have permission to rename files or directories.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require 'config.php';
|
||||||
|
$sftp = initializeSFTP($host, $username, $password);
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$oldPath = isset($_POST['path']) ? $_POST['path'] : '';
|
||||||
|
$newName = isset($_POST['newName']) ? trim($_POST['newName']) : '';
|
||||||
|
$isDirectory = isset($_POST['isDirectory']) && $_POST['isDirectory'] == '1';
|
||||||
|
|
||||||
|
if (empty($oldPath) || empty($newName)) {
|
||||||
|
die("Missing required information for renaming.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$dirPath = dirname($oldPath);
|
||||||
|
$oldName = basename($oldPath);
|
||||||
|
|
||||||
|
$newPath = $dirPath . '/' . $newName;
|
||||||
|
|
||||||
|
if ($sftp->file_exists($newPath) || $sftp->is_dir($newPath)) {
|
||||||
|
die("Error: A file or directory with this name already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sftp->rename($oldPath, $newPath)) {
|
||||||
|
header("Location: index.php?path=" . urlencode($dirPath));
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
die("Failed to rename the item. Please try again.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
560
Web/content/ftp/serverstat.php
Normal file
560
Web/content/ftp/serverstat.php
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
<?php
|
||||||
|
// Include this at the top to see potential errors
|
||||||
|
// Comment out in production
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (empty($_SESSION['admin']) || $_SESSION['admin'] !== true) {
|
||||||
|
session_destroy();
|
||||||
|
header("location: ../../index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
require '../../vendor/autoload.php';
|
||||||
|
|
||||||
|
use phpseclib3\Net\SSH2;
|
||||||
|
|
||||||
|
$ssh_host = 'localhost';
|
||||||
|
$ssh_username = 'uname';
|
||||||
|
$ssh_password = 'pswd';
|
||||||
|
|
||||||
|
$ssh = new SSH2($ssh_host);
|
||||||
|
if (!$ssh->login($ssh_username, $ssh_password)) {
|
||||||
|
die('SSH login failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiskData($ssh, $device) {
|
||||||
|
return $ssh->exec("df -h $device");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystemData($ssh) {
|
||||||
|
// Get memory info using awk to clean up the output
|
||||||
|
$meminfo = $ssh->exec("free -m | awk 'NR==2{printf \"%s,%s,%s,\", $2,$3,$7} NR==3{printf \"%s,%s,%s\", $2,$3,$4}'");
|
||||||
|
|
||||||
|
// Use vmstat to get CPU idle percentage, more reliable and commonly available
|
||||||
|
$cpuIdle = $ssh->exec("vmstat 1 2 | tail -1 | awk '{print $15}'");
|
||||||
|
|
||||||
|
return [
|
||||||
|
'storage' => [
|
||||||
|
'system' => parseDiskData($ssh->exec('df -h /dev/ubuntu-vg/ubuntu-lv')),
|
||||||
|
'data' => parseDiskData($ssh->exec('df -h /dev/sdb1'))
|
||||||
|
],
|
||||||
|
'raid' => parseRaidStatus($ssh->exec('cat /proc/mdstat')),
|
||||||
|
'resources' => parseSystemStats($cpuIdle, $meminfo),
|
||||||
|
'lastUpdate' => date('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$systemData = getSystemData($ssh);
|
||||||
|
|
||||||
|
$monitors = [
|
||||||
|
[
|
||||||
|
'id' => 'SystemDisk',
|
||||||
|
'title' => 'System Disk (sda)',
|
||||||
|
'icon' => 'hdd',
|
||||||
|
'color' => 'primary',
|
||||||
|
'type' => 'storage',
|
||||||
|
'source' => 'system'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'DataDisk',
|
||||||
|
'title' => 'Data Disk (sdb)',
|
||||||
|
'icon' => 'hdd',
|
||||||
|
'color' => 'primary',
|
||||||
|
'type' => 'storage',
|
||||||
|
'source' => 'data'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'RAID',
|
||||||
|
'title' => 'RAID Status',
|
||||||
|
'icon' => 'shield-check',
|
||||||
|
'color' => 'warning',
|
||||||
|
'type' => 'raid'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'CPU',
|
||||||
|
'title' => 'CPU Usage',
|
||||||
|
'icon' => 'cpu',
|
||||||
|
'color' => 'info',
|
||||||
|
'type' => 'resources',
|
||||||
|
'source' => 'cpu'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'Memory',
|
||||||
|
'title' => 'Memory Usage',
|
||||||
|
'icon' => 'memory',
|
||||||
|
'color' => 'success',
|
||||||
|
'type' => 'resources',
|
||||||
|
'source' => 'memory'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
function parseDiskData($output) {
|
||||||
|
$lines = explode("\n", trim($output));
|
||||||
|
if (count($lines) < 2) return null;
|
||||||
|
|
||||||
|
$values = preg_split('/\s+/', trim($lines[1]));
|
||||||
|
return [
|
||||||
|
'size' => $values[1] ?? 'N/A',
|
||||||
|
'used' => $values[2] ?? 'N/A',
|
||||||
|
'available' => $values[3] ?? 'N/A',
|
||||||
|
'usage' => $values[4] ?? '0%'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRaidStatus($output) {
|
||||||
|
if (empty($output)) {
|
||||||
|
return [
|
||||||
|
'active' => false,
|
||||||
|
'status' => 'unknown',
|
||||||
|
'type' => 'N/A'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = [
|
||||||
|
'active' => false,
|
||||||
|
'status' => 'unknown',
|
||||||
|
'type' => 'N/A'
|
||||||
|
];
|
||||||
|
|
||||||
|
$lines = explode("\n", trim($output));
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (preg_match('/active\s+(\w+)/', $line, $matches)) {
|
||||||
|
$status['active'] = true;
|
||||||
|
$status['type'] = $matches[1] ?? 'N/A';
|
||||||
|
}
|
||||||
|
if (strpos($line, '[UU]') !== false) {
|
||||||
|
$status['status'] = 'healthy';
|
||||||
|
} elseif (strpos($line, '_') !== false) {
|
||||||
|
$status['status'] = 'degraded';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSystemStats($cpuOutput, $memOutput) {
|
||||||
|
// Parse CPU stats - convert idle percentage to usage percentage
|
||||||
|
$idlePercent = floatval(trim(str_replace(',', '.', $cpuOutput)));
|
||||||
|
$cpuUsage = 100 - $idlePercent; // Convert idle to usage percentage
|
||||||
|
if (is_nan($cpuUsage) || $cpuUsage < 0 || $cpuUsage > 100) {
|
||||||
|
$cpuUsage = 0; // Default to 0% if parsing fails
|
||||||
|
}
|
||||||
|
$cpu = ['usage' => number_format($cpuUsage, 1)];
|
||||||
|
|
||||||
|
// Parse memory stats
|
||||||
|
$parts = explode(',', $memOutput);
|
||||||
|
if (count($parts) >= 6) {
|
||||||
|
$memTotal = intval($parts[0]);
|
||||||
|
$memUsed = intval($parts[1]);
|
||||||
|
$memAvail = intval($parts[2]);
|
||||||
|
$swapTotal = intval($parts[3]);
|
||||||
|
$swapUsed = intval($parts[4]);
|
||||||
|
$swapFree = intval($parts[5]);
|
||||||
|
|
||||||
|
$memory = [
|
||||||
|
'total' => number_format($memTotal / 1024, 2) . 'G',
|
||||||
|
'used' => number_format($memUsed / 1024, 2) . 'G',
|
||||||
|
'available' => number_format($memAvail / 1024, 2) . 'G',
|
||||||
|
'usage' => ($memTotal > 0 ? round(($memUsed / $memTotal) * 100, 1) : 0) . '%',
|
||||||
|
'swap_total' => number_format($swapTotal / 1024, 2) . 'G',
|
||||||
|
'swap_used' => number_format($swapUsed / 1024, 2) . 'G',
|
||||||
|
'swap_free' => number_format($swapFree / 1024, 2) . 'G',
|
||||||
|
'swap_usage' => ($swapTotal > 0 ? round(($swapUsed / $swapTotal) * 100, 1) : 0) . '%'
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$memory = [
|
||||||
|
'total' => '0G',
|
||||||
|
'used' => '0G',
|
||||||
|
'available' => '0G',
|
||||||
|
'usage' => '0%',
|
||||||
|
'swap_total' => '0G',
|
||||||
|
'swap_used' => '0G',
|
||||||
|
'swap_free' => '0G',
|
||||||
|
'swap_usage' => '0%'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['cpu' => $cpu, 'memory' => $memory];
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToBytes($size) {
|
||||||
|
if (preg_match('/^([\d.]+)([KMGT]?)i?B?$/', trim($size), $matches)) {
|
||||||
|
$value = floatval($matches[1]);
|
||||||
|
$unit = strtoupper($matches[2]);
|
||||||
|
|
||||||
|
switch ($unit) {
|
||||||
|
case 'P': $value *= 1024;
|
||||||
|
case 'T': $value *= 1024;
|
||||||
|
case 'G': $value *= 1024;
|
||||||
|
case 'M': $value *= 1024;
|
||||||
|
case 'K': $value *= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes($bytes, $forceUnit = '') {
|
||||||
|
$units = ['B', 'K', 'M', 'G', 'T', 'P'];
|
||||||
|
$bytes = max($bytes, 0);
|
||||||
|
|
||||||
|
if ($forceUnit) {
|
||||||
|
$unitIndex = array_search($forceUnit, $units);
|
||||||
|
if ($unitIndex !== false) {
|
||||||
|
$bytes /= pow(1024, $unitIndex);
|
||||||
|
return round($bytes, 2) . $forceUnit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||||
|
$pow = min($pow, count($units) - 1);
|
||||||
|
$bytes /= pow(1024, $pow);
|
||||||
|
|
||||||
|
return round($bytes, 2) . $units[$pow];
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Server Status</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="icon" href="../../img/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="../../css/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="css/serverstat.css">
|
||||||
|
<script src="../../js/bootstrap.bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="container-fluid text-center">
|
||||||
|
<div class="d-flex justify-content-end p-3">
|
||||||
|
<button id="themeToggle" class="btn btn-sm theme-toggle">
|
||||||
|
<i class="bi"></i>
|
||||||
|
<span id="themeText"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="row border-bottom m-5">
|
||||||
|
<h1>Server Status</h1>
|
||||||
|
<div class="mb-3 p-3">
|
||||||
|
<a href="../logout.php" class="btn btn-danger">Logout</a>
|
||||||
|
<a href="../changepassword.php" class="btn btn-warning">Change Password</a>
|
||||||
|
<a href="../adminpanel.php" class="btn btn-primary">Admin Panel</a>
|
||||||
|
<a href="index.php" class="btn btn-primary">SFTP</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-10">
|
||||||
|
<div class="card shadow-sm bg-body-tertiary">
|
||||||
|
<div class="card-header monitor-header">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h1 class="h4 mb-0">
|
||||||
|
<i class="bi bi-server text-primary me-2"></i>
|
||||||
|
Server Disk Status
|
||||||
|
</h1>
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
<i class="bi bi-clock me-1"></i>
|
||||||
|
<?= htmlspecialchars($systemData['lastUpdate']) ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row g-4">
|
||||||
|
<?php foreach ($monitors as $monitor): ?>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="monitor-card card h-100 border-<?= $monitor['color'] ?>">
|
||||||
|
<div class="card-header bg-<?= $monitor['color'] ?> bg-opacity-10">
|
||||||
|
<h3 class="h5 mb-0">
|
||||||
|
<i class="bi bi-<?= $monitor['icon'] ?> text-<?= $monitor['color'] ?> me-2"></i>
|
||||||
|
<?= $monitor['title'] ?>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($monitor['type'] === 'storage'):
|
||||||
|
$diskData = $systemData['storage'][$monitor['source']];
|
||||||
|
$usagePercent = intval(rtrim($diskData['usage'], '%'));
|
||||||
|
?>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span>Storage Usage</span>
|
||||||
|
<span><?= $diskData['usage'] ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 10px">
|
||||||
|
<div class="progress-bar bg-<?= $usagePercent > 90 ? 'danger' : ($usagePercent > 75 ? 'warning' : 'success') ?>"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: <?= $usagePercent ?>%"
|
||||||
|
aria-valuenow="<?= $usagePercent ?>"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row text-center g-2">
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Total</div>
|
||||||
|
<div class="fw-bold"><?= $diskData['size'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Used</div>
|
||||||
|
<div class="fw-bold"><?= $diskData['used'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Free</div>
|
||||||
|
<div class="fw-bold"><?= $diskData['available'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($monitor['type'] === 'raid'): ?>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="display-4 mb-2">
|
||||||
|
<i class="bi bi-<?= $systemData['raid']['active'] ? 'check-circle-fill text-success' : 'x-circle-fill text-danger' ?>"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="h6"><?= $systemData['raid']['type'] ?> Array</h4>
|
||||||
|
<span class="badge bg-<?= $systemData['raid']['status'] === 'healthy' ? 'success' : 'warning' ?>">
|
||||||
|
<?= ucfirst($systemData['raid']['status']) ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($monitor['type'] === 'resources' && $monitor['source'] === 'cpu'): ?>
|
||||||
|
<div class="cpu-gauge">
|
||||||
|
<canvas id="cpuGauge" width="150" height="150" data-value="<?= $systemData['resources']['cpu']['usage'] ?>"></canvas>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($monitor['type'] === 'resources' && $monitor['source'] === 'memory'):
|
||||||
|
$memData = $systemData['resources']['memory'];
|
||||||
|
$usagePercent = floatval(rtrim($memData['usage'], '%'));
|
||||||
|
$swapPercent = floatval(rtrim($memData['swap_usage'], '%'));
|
||||||
|
?>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span>Memory Usage</span>
|
||||||
|
<span><?= $memData['usage'] ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 10px">
|
||||||
|
<div class="progress-bar bg-<?= $usagePercent > 90 ? 'danger' : ($usagePercent > 75 ? 'warning' : 'success') ?>"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: <?= $usagePercent ?>%"
|
||||||
|
aria-valuenow="<?= $usagePercent ?>"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row text-center g-2 mb-3">
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Total</div>
|
||||||
|
<div class="fw-bold"><?= $memData['total'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Used</div>
|
||||||
|
<div class="fw-bold"><?= $memData['used'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Available</div>
|
||||||
|
<div class="fw-bold"><?= $memData['available'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span>Swap Usage</span>
|
||||||
|
<span><?= $memData['swap_usage'] ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 10px">
|
||||||
|
<div class="progress-bar bg-<?= $swapPercent > 90 ? 'danger' : ($swapPercent > 75 ? 'warning' : 'success') ?>"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: <?= $swapPercent ?>%"
|
||||||
|
aria-valuenow="<?= $swapPercent ?>"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row text-center g-2">
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Total</div>
|
||||||
|
<div class="fw-bold"><?= $memData['swap_total'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Used</div>
|
||||||
|
<div class="fw-bold"><?= $memData['swap_used'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="p-2 border rounded">
|
||||||
|
<div class="small text-muted">Free</div>
|
||||||
|
<div class="fw-bold"><?= $memData['swap_free'] ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3">
|
||||||
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
|
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
|
</a>
|
||||||
|
<a href="https://app.freelo.io/public/shared-link-view/?a=81efbcb4df761b3f29cdc80855b41e6d&b=4519c717f0729cc8e953af661e9dc981" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="../../img/freelo-logo-rgb.png" alt="Freelo Logo" class="img-fluid hover-effect light-logo" style="height: 24px;">
|
||||||
|
<img src="../../img/freelo-logo-rgb-on-dark.png" alt="Freelo Logo" class="img-fluid hover-effect dark-logo" style="height: 24px;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script src="../../js/theme.js"></script>
|
||||||
|
<script>
|
||||||
|
let refreshInterval;
|
||||||
|
let isRefreshing = false;
|
||||||
|
|
||||||
|
function updateData(html) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(html, 'text/html');
|
||||||
|
|
||||||
|
// Update monitor cards
|
||||||
|
document.querySelectorAll('.monitor-card').forEach((card, index) => {
|
||||||
|
const newCard = doc.querySelectorAll('.monitor-card')[index];
|
||||||
|
if (newCard && card.querySelector('.card-body')) {
|
||||||
|
card.querySelector('.card-body').innerHTML = newCard.querySelector('.card-body').innerHTML;
|
||||||
|
|
||||||
|
// Redraw CPU gauge if this is the CPU card
|
||||||
|
if (card.querySelector('.cpu-gauge')) {
|
||||||
|
const cpuValue = parseFloat(newCard.querySelector('canvas').getAttribute('data-value') || '0');
|
||||||
|
drawCpuGauge(cpuValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update last update time
|
||||||
|
const newLastUpdate = doc.querySelector('.badge.bg-light.text-dark')?.innerHTML;
|
||||||
|
const currentLastUpdate = document.querySelector('.badge.bg-light.text-dark');
|
||||||
|
if (newLastUpdate && currentLastUpdate && newLastUpdate !== currentLastUpdate.innerHTML) {
|
||||||
|
currentLastUpdate.innerHTML = newLastUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update CPU gauge if needed
|
||||||
|
const cpuValue = document.querySelector('.cpu-gauge .h4')?.textContent;
|
||||||
|
if (cpuValue) {
|
||||||
|
drawCpuGauge(parseFloat(cpuValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRefresh() {
|
||||||
|
if (!refreshInterval) {
|
||||||
|
refreshInterval = setInterval(() => {
|
||||||
|
if (!isRefreshing) {
|
||||||
|
isRefreshing = true;
|
||||||
|
fetch(window.location.href)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(html => {
|
||||||
|
updateData(html);
|
||||||
|
isRefreshing = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
isRefreshing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRefresh() {
|
||||||
|
if (refreshInterval) {
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
refreshInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start refresh when page is visible
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
stopRefresh();
|
||||||
|
} else {
|
||||||
|
startRefresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
startRefresh();
|
||||||
|
drawCpuGauge(parseFloat(document.querySelector('.cpu-gauge .h4')?.textContent || '0'));
|
||||||
|
|
||||||
|
function drawCpuGauge(value) {
|
||||||
|
const canvas = document.getElementById('cpuGauge');
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
// Ensure value is a valid number between 0 and 100
|
||||||
|
value = parseFloat(value) || 0;
|
||||||
|
value = Math.max(0, Math.min(100, value));
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const centerX = canvas.width / 2;
|
||||||
|
const centerY = canvas.height / 2;
|
||||||
|
const radius = Math.min(centerX, centerY) - 10;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Draw background circle
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
||||||
|
ctx.strokeStyle = getComputedStyle(document.body).getPropertyValue('--bs-border-color');
|
||||||
|
ctx.lineWidth = 10;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw value arc
|
||||||
|
const startAngle = -Math.PI / 2;
|
||||||
|
const endAngle = startAngle + (Math.PI * 2 * value / 100);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
|
||||||
|
ctx.strokeStyle = value > 90 ? '#dc3545' : value > 75 ? '#ffc107' : '#198754';
|
||||||
|
ctx.lineWidth = 10;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Draw percentage text inside the circle
|
||||||
|
ctx.font = 'bold 20px Arial';
|
||||||
|
ctx.fillStyle = getComputedStyle(document.body).getPropertyValue('--bs-body-color');
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(`${value.toFixed(1)}%`, centerX, centerY - 10);
|
||||||
|
|
||||||
|
// Draw 'CPU USAGE' text below the percentage
|
||||||
|
ctx.font = '12px Arial';
|
||||||
|
ctx.fillText('CPU USAGE', centerX, centerY + 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial CPU gauge draw
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
const cpuValue = parseFloat(document.querySelector('.cpu-gauge canvas')?.getAttribute('data-value') || '0');
|
||||||
|
drawCpuGauge(cpuValue);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
340
Web/content/ftp/shared.php
Normal file
340
Web/content/ftp/shared.php
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
<?php
|
||||||
|
// Include this at the top to see potential errors
|
||||||
|
// Comment out in production
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require 'config.php';
|
||||||
|
|
||||||
|
function getDatabaseConnection() {
|
||||||
|
$servername = "localhost:3306";
|
||||||
|
$username = "UNAME";
|
||||||
|
$password = "PSWD";
|
||||||
|
$db = "usbraidlogin";
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $db);
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Connection failed: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_GET['token'])) {
|
||||||
|
die("Invalid request. No token provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $_GET['token'];
|
||||||
|
$conn = getDatabaseConnection();
|
||||||
|
|
||||||
|
// Get share information
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM share WHERE token = ? AND expiration > NOW()");
|
||||||
|
$stmt->bind_param("s", $token);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
die("Invalid or expired share link.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$share = $result->fetch_assoc();
|
||||||
|
$filePath = $share['file_path'];
|
||||||
|
$downloadAllowed = $share['download_allowed'] == 1;
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// Initialize SFTP
|
||||||
|
$sftp = initializeSFTP($host, $username, $password);
|
||||||
|
|
||||||
|
if (!$sftp->stat($filePath)) {
|
||||||
|
die("File not found: $filePath");
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = basename($filePath);
|
||||||
|
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
||||||
|
$fileSize = $sftp->stat($filePath)['size'];
|
||||||
|
|
||||||
|
$imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
|
||||||
|
$videoTypes = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
|
||||||
|
$audioTypes = ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac'];
|
||||||
|
|
||||||
|
$isImage = in_array($fileExtension, $imageTypes);
|
||||||
|
$isVideo = in_array($fileExtension, $videoTypes);
|
||||||
|
$isAudio = in_array($fileExtension, $audioTypes);
|
||||||
|
|
||||||
|
if (!$isImage && !$isVideo && !$isAudio) {
|
||||||
|
die("Unsupported file type");
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimeMap = [
|
||||||
|
'mp4' => 'video/mp4',
|
||||||
|
'webm' => 'video/mp4',
|
||||||
|
'ogg' => 'video/mp4',
|
||||||
|
'mov' => 'video/mp4',
|
||||||
|
'avi' => 'video/mp4',
|
||||||
|
'mkv' => 'video/mp4',
|
||||||
|
|
||||||
|
'jpg' => 'image/jpeg',
|
||||||
|
'jpeg' => 'image/jpeg',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'bmp' => 'image/bmp',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
'svg' => 'image/svg+xml',
|
||||||
|
|
||||||
|
'mp3' => 'audio/mpeg',
|
||||||
|
'wav' => 'audio/wav',
|
||||||
|
'm4a' => 'audio/mp4',
|
||||||
|
'flac' => 'audio/flac',
|
||||||
|
'aac' => 'audio/aac',
|
||||||
|
];
|
||||||
|
|
||||||
|
$mimeType = isset($mimeMap[$fileExtension]) ? $mimeMap[$fileExtension] : 'application/octet-stream';
|
||||||
|
|
||||||
|
// If streaming requested, stream file
|
||||||
|
if (isset($_GET['stream'])) {
|
||||||
|
// Similar streaming code as in view.php
|
||||||
|
session_write_close();
|
||||||
|
while (ob_get_level()) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
if (ini_get('zlib.output_compression')) {
|
||||||
|
ini_set('zlib.output_compression', 'Off');
|
||||||
|
}
|
||||||
|
|
||||||
|
$start = 0;
|
||||||
|
$end = $fileSize - 1;
|
||||||
|
$length = $fileSize;
|
||||||
|
|
||||||
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
|
$rangeHeader = $_SERVER['HTTP_RANGE'];
|
||||||
|
$matches = [];
|
||||||
|
if (preg_match('/bytes=(\d+)-(\d*)/', $rangeHeader, $matches)) {
|
||||||
|
$start = intval($matches[1]);
|
||||||
|
|
||||||
|
if (!empty($matches[2])) {
|
||||||
|
$end = intval($matches[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = $end - $start + 1;
|
||||||
|
|
||||||
|
header('HTTP/1.1 206 Partial Content');
|
||||||
|
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Content-Type: $mimeType");
|
||||||
|
header("Accept-Ranges: bytes");
|
||||||
|
header("Content-Length: $length");
|
||||||
|
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: 0");
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
$minChunkSize = 64 * 1024;
|
||||||
|
$maxChunkSize = 2 * 1024 * 1024;
|
||||||
|
$chunkSize = 256 * 1024;
|
||||||
|
|
||||||
|
$currentPosition = $start;
|
||||||
|
$bytesRemaining = $length;
|
||||||
|
$lastChunkTime = microtime(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ($bytesRemaining > 0) {
|
||||||
|
if (connection_aborted() || connection_status() !== CONNECTION_NORMAL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$readSize = min($chunkSize, $bytesRemaining);
|
||||||
|
$chunkData = $sftp->get($filePath, false, $currentPosition, $readSize);
|
||||||
|
|
||||||
|
if ($chunkData !== false) {
|
||||||
|
$bytesSent = strlen($chunkData);
|
||||||
|
echo $chunkData;
|
||||||
|
$bytesRemaining -= $bytesSent;
|
||||||
|
$currentPosition += $bytesSent;
|
||||||
|
|
||||||
|
if (ob_get_level()) {
|
||||||
|
ob_flush();
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
|
||||||
|
$currentTime = microtime(true);
|
||||||
|
$timeDiff = $currentTime - $lastChunkTime;
|
||||||
|
$lastChunkTime = $currentTime;
|
||||||
|
|
||||||
|
if ($timeDiff > 0) {
|
||||||
|
$speed = $bytesSent / $timeDiff;
|
||||||
|
$chunkSize = min(
|
||||||
|
max($minChunkSize, $chunkSize * (($speed > 512 * 1024) ? 1.5 : 0.8)),
|
||||||
|
$maxChunkSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Shared streaming error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create download handler
|
||||||
|
if (isset($_GET['download'])) {
|
||||||
|
if (!$downloadAllowed) {
|
||||||
|
die("Download not permitted for this share.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download file code similar to download.php
|
||||||
|
header("Content-Type: application/octet-stream");
|
||||||
|
header("Content-Disposition: attachment; filename=\"$fileName\"");
|
||||||
|
header("Content-Length: $fileSize");
|
||||||
|
|
||||||
|
$minChunkSize = 64 * 1024;
|
||||||
|
$maxChunkSize = 2 * 1024 * 1024;
|
||||||
|
$chunkSize = 256 * 1024;
|
||||||
|
|
||||||
|
$currentPosition = 0;
|
||||||
|
$bytesRemaining = $fileSize;
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
while ($bytesRemaining > 0) {
|
||||||
|
$readSize = min($chunkSize, $bytesRemaining);
|
||||||
|
$chunkData = $sftp->get($filePath, false, $currentPosition, $readSize);
|
||||||
|
|
||||||
|
if ($chunkData !== false) {
|
||||||
|
$bytesRead = strlen($chunkData);
|
||||||
|
echo $chunkData;
|
||||||
|
$bytesRemaining -= $bytesRead;
|
||||||
|
$currentPosition += $bytesRead;
|
||||||
|
flush();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBytes($bytes, $precision = 2) {
|
||||||
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
$bytes = max($bytes, 0);
|
||||||
|
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||||
|
$pow = min($pow, count($units) - 1);
|
||||||
|
$bytes /= (1 << (10 * $pow));
|
||||||
|
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-bs-theme="<?= isset($_COOKIE['theme']) ? $_COOKIE['theme'] : 'light' ?>">
|
||||||
|
<head>
|
||||||
|
<title>Shared Media - <?= htmlspecialchars($fileName) ?></title>
|
||||||
|
<link rel="icon" href="../../img/favicon.ico" type="image/x-icon">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="../../css/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="css/view.css">
|
||||||
|
<script src="../../js/bootstrap.bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="text-center">
|
||||||
|
<div class="d-flex justify-content-end p-3">
|
||||||
|
<button id="themeToggle" class="btn btn-sm theme-toggle">
|
||||||
|
<i class="bi"></i>
|
||||||
|
<span id="themeText"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="custom-container">
|
||||||
|
<header class="row border-bottom m-5">
|
||||||
|
<h1>Shared Media</h1>
|
||||||
|
<div class="mb-3">
|
||||||
|
<span class="badge bg-info">Shared Link</span>
|
||||||
|
<p class="text-muted small">This link will expire on <?= date('F j, Y, g:i a', strtotime($share['expiration'])) ?></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="row">
|
||||||
|
<article class="col-12">
|
||||||
|
<div class="media-container position-relative">
|
||||||
|
<?php if ($isImage): ?>
|
||||||
|
<img src="shared.php?token=<?= urlencode($token) ?>&stream=1" alt="<?= htmlspecialchars($fileName) ?>" class="img-fluid">
|
||||||
|
<?php elseif ($isVideo): ?>
|
||||||
|
<video id="videoPlayer" controls autoplay playsinline>
|
||||||
|
<source src="shared.php?token=<?= urlencode($token) ?>&stream=1" type="<?= $mimeType ?>">
|
||||||
|
Your browser does not support this video format.
|
||||||
|
</video>
|
||||||
|
<?php if($fileExtension !== 'mp4'): ?>
|
||||||
|
<div class="alert alert-warning mt-2">
|
||||||
|
Note: For best results, use MP4 format (H.264 codec). Other formats may not play correctly in all browsers.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php elseif ($isAudio): ?>
|
||||||
|
<audio controls autoplay style="width: 80%;">
|
||||||
|
<source src="shared.php?token=<?= urlencode($token) ?>&stream=1" type="<?= $mimeType ?>">
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($downloadAllowed) : ?>
|
||||||
|
<a href="shared.php?token=<?= urlencode($token) ?>&download=1" class="btn btn-success m-3">Download</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-info mt-4">
|
||||||
|
<h4>File Information</h4>
|
||||||
|
<table class="table table-bordered w-auto mx-auto text-wrap">
|
||||||
|
<tr>
|
||||||
|
<th>File Name</th>
|
||||||
|
<td class="text-break"><?= htmlspecialchars($fileName) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>File Type</th>
|
||||||
|
<td><?= htmlspecialchars(strtoupper($fileExtension)) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>File Size</th>
|
||||||
|
<td><?= formatBytes($fileSize) ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
||||||
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
|
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
|
</a>
|
||||||
|
<a href="https://app.freelo.io/public/shared-link-view/?a=81efbcb4df761b3f29cdc80855b41e6d&b=4519c717f0729cc8e953af661e9dc981" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img src="../../img/freelo-logo-rgb.png" alt="Freelo Logo" class="img-fluid hover-effect light-logo" style="height: 24px;">
|
||||||
|
<img src="../../img/freelo-logo-rgb-on-dark.png" alt="Freelo Logo" class="img-fluid hover-effect dark-logo" style="height: 24px;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../../js/theme.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const mediaElement = document.querySelector('video, audio');
|
||||||
|
|
||||||
|
if (mediaElement) {
|
||||||
|
const savedVolume = localStorage.getItem('mediaVolume');
|
||||||
|
if (savedVolume !== null) {
|
||||||
|
mediaElement.volume = parseFloat(savedVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaElement.addEventListener('volumechange', () => {
|
||||||
|
localStorage.setItem('mediaVolume', mediaElement.volume);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -20,6 +20,69 @@ if (!isset($_GET['file'])) {
|
|||||||
die("No file specified");
|
die("No file specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($_POST['createShare'])) {
|
||||||
|
$duration = intval($_POST['duration']);
|
||||||
|
$downloadAllowed = isset($_POST['downloadAllowed']) ? 1 : 0;
|
||||||
|
$filePath = $_POST['filePath'];
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
date_default_timezone_set('Europe/Prague');
|
||||||
|
|
||||||
|
$expiration = date('Y-m-d H:i:s', strtotime("+$duration hours"));
|
||||||
|
|
||||||
|
$conn = getDatabaseConnection();
|
||||||
|
$stmt = $conn->prepare("INSERT INTO share (token, file_path, expiration, download_allowed) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->bind_param("sssi", $token, $filePath, $expiration, $downloadAllowed);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
$shareUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" .
|
||||||
|
dirname($_SERVER['PHP_SELF']) . "/shared.php?token=$token";
|
||||||
|
|
||||||
|
$_SESSION['shareCreated'] = true;
|
||||||
|
$_SESSION['shareUrl'] = $shareUrl;
|
||||||
|
$_SESSION['shareExpiration'] = $expiration;
|
||||||
|
|
||||||
|
header("Location: view.php?file=" . urlencode($filePath) . "&shared=1");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$_SESSION['shareError'] = "Failed to create share: " . $conn->error;
|
||||||
|
header("Location: view.php?file=" . urlencode($filePath) . "&shared=0");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$shareCreated = false;
|
||||||
|
$shareUrl = '';
|
||||||
|
$shareExpiration = '';
|
||||||
|
$shareError = '';
|
||||||
|
|
||||||
|
if (isset($_GET['shared'])) {
|
||||||
|
if ($_GET['shared'] == '1' && isset($_SESSION['shareCreated']) && $_SESSION['shareCreated']) {
|
||||||
|
$shareCreated = true;
|
||||||
|
$shareUrl = $_SESSION['shareUrl'];
|
||||||
|
$shareExpiration = $_SESSION['shareExpiration'];
|
||||||
|
} elseif ($_GET['shared'] == '0' && isset($_SESSION['shareError'])) {
|
||||||
|
$shareError = $_SESSION['shareError'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatabaseConnection() {
|
||||||
|
$servername = "localhost:3306";
|
||||||
|
$username = "UNAME";
|
||||||
|
$password = "PSWD";
|
||||||
|
$db = "usbraidlogin";
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $db);
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Connection failed: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
$filePath = $_GET['file'];
|
$filePath = $_GET['file'];
|
||||||
$fileName = basename($filePath);
|
$fileName = basename($filePath);
|
||||||
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
||||||
@@ -62,18 +125,31 @@ $mimeMap = [
|
|||||||
'wav' => 'audio/wav',
|
'wav' => 'audio/wav',
|
||||||
'm4a' => 'audio/mp4',
|
'm4a' => 'audio/mp4',
|
||||||
'flac' => 'audio/flac',
|
'flac' => 'audio/flac',
|
||||||
'aac' => 'audio/aac'
|
'aac' => 'audio/aac',
|
||||||
];
|
];
|
||||||
|
|
||||||
$mimeType = isset($mimeMap[$fileExtension]) ? $mimeMap[$fileExtension] : 'application/octet-stream';
|
$mimeType = isset($mimeMap[$fileExtension]) ? $mimeMap[$fileExtension] : 'application/octet-stream';
|
||||||
|
|
||||||
if (isset($_GET['stream'])) {
|
if (isset($_GET['stream'])) {
|
||||||
$tempFile = tempnam(sys_get_temp_dir(), 'media_');
|
// Close session to allow other scripts to run
|
||||||
|
session_write_close();
|
||||||
|
|
||||||
|
// Prevent output buffering
|
||||||
|
while (ob_get_level()) {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable output compression
|
||||||
|
if (ini_get('zlib.output_compression')) {
|
||||||
|
ini_set('zlib.output_compression', 'Off');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize range variables
|
||||||
$start = 0;
|
$start = 0;
|
||||||
$end = $fileSize - 1;
|
$end = $fileSize - 1;
|
||||||
$length = $fileSize;
|
$length = $fileSize;
|
||||||
|
|
||||||
|
// Handle range requests
|
||||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||||
$rangeHeader = $_SERVER['HTTP_RANGE'];
|
$rangeHeader = $_SERVER['HTTP_RANGE'];
|
||||||
$matches = [];
|
$matches = [];
|
||||||
@@ -91,56 +167,104 @@ if (isset($_GET['stream'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set headers for streaming
|
||||||
header("Content-Type: $mimeType");
|
header("Content-Type: $mimeType");
|
||||||
header("Accept-Ranges: bytes");
|
header("Accept-Ranges: bytes");
|
||||||
header("Content-Length: $length");
|
header("Content-Length: $length");
|
||||||
|
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: 0");
|
||||||
|
|
||||||
while (ob_get_level()) {
|
// Debug headers
|
||||||
ob_end_clean();
|
if (isset($_GET['debug'])) {
|
||||||
|
header("X-Stream-Info: Chunked SFTP Streaming");
|
||||||
|
header("X-File-Path: " . basename($filePath));
|
||||||
|
header("X-File-Size: $fileSize");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set timeout to 0 to prevent script termination
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
$chunkSize = 8 * 1024 * 1024; // 8MB chunks for better performance
|
// Chunk settings
|
||||||
|
$minChunkSize = 64 * 1024; // 64KB minimum
|
||||||
|
$maxChunkSize = 2 * 1024 * 1024; // 2MB maximum
|
||||||
|
$chunkSize = 256 * 1024; // Start with 256KB
|
||||||
|
|
||||||
$currentPosition = $start;
|
$currentPosition = $start;
|
||||||
$bytesRemaining = $length;
|
$bytesRemaining = $length;
|
||||||
|
$lastChunkTime = microtime(true);
|
||||||
|
|
||||||
$tempHandle = fopen($tempFile, 'w+');
|
try {
|
||||||
|
while ($bytesRemaining > 0) {
|
||||||
|
// Check client connection and server status
|
||||||
|
if (connection_aborted() || connection_status() !== CONNECTION_NORMAL) {
|
||||||
|
if (isset($_GET['debug'])) {
|
||||||
|
error_log("Client disconnected at position $currentPosition");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
while ($bytesRemaining > 0) {
|
// Calculate adaptive chunk size
|
||||||
$readSize = min($chunkSize, $bytesRemaining);
|
$readSize = min($chunkSize, $bytesRemaining);
|
||||||
|
|
||||||
$chunkTemp = tempnam(sys_get_temp_dir(), 'chunk_');
|
// Get chunk from SFTP
|
||||||
|
$chunkData = $sftp->get($filePath, false, $currentPosition, $readSize);
|
||||||
|
|
||||||
if ($sftp->get($filePath, $chunkTemp, $currentPosition, $readSize)) {
|
if ($chunkData !== false) {
|
||||||
$chunkData = file_get_contents($chunkTemp);
|
$bytesSent = strlen($chunkData);
|
||||||
echo $chunkData;
|
|
||||||
|
|
||||||
unlink($chunkTemp);
|
// Output chunk
|
||||||
|
echo $chunkData;
|
||||||
|
$bytesRemaining -= $bytesSent;
|
||||||
|
$currentPosition += $bytesSent;
|
||||||
|
|
||||||
$bytesRemaining -= strlen($chunkData);
|
// Flush buffers
|
||||||
$currentPosition += strlen($chunkData);
|
if (ob_get_level()) {
|
||||||
|
ob_flush();
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
|
||||||
flush();
|
// Adaptive chunk sizing based on transfer speed
|
||||||
} else {
|
$currentTime = microtime(true);
|
||||||
error_log("SFTP reading error at position $currentPosition");
|
$timeDiff = $currentTime - $lastChunkTime;
|
||||||
break;
|
$lastChunkTime = $currentTime;
|
||||||
|
|
||||||
|
if ($timeDiff > 0) {
|
||||||
|
$speed = $bytesSent / $timeDiff; // bytes/second
|
||||||
|
$chunkSize = min(
|
||||||
|
max($minChunkSize, $chunkSize * (($speed > 512 * 1024) ? 1.5 : 0.8)),
|
||||||
|
$maxChunkSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['debug'])) {
|
||||||
|
header("X-Chunk-Size: $bytesSent");
|
||||||
|
header("X-Position: $currentPosition");
|
||||||
|
header("X-Remaining: $bytesRemaining");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error_log("SFTP read error at position $currentPosition");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throttle to prevent CPU overload
|
||||||
|
usleep(100000); // 100ms
|
||||||
}
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
if (connection_status() != CONNECTION_NORMAL) {
|
error_log("Streaming error: " . $e->getMessage());
|
||||||
break;
|
if (isset($_GET['debug'])) {
|
||||||
|
header("X-Stream-Error: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose($tempHandle);
|
if (isset($_GET['debug'])) {
|
||||||
if (file_exists($tempFile)) {
|
error_log("Streaming completed. Sent $currentPosition bytes of $fileSize");
|
||||||
unlink($tempFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-bs-theme="<?= isset($_COOKIE['theme']) ? $_COOKIE['theme'] : 'light' ?>">
|
<html lang="en" data-bs-theme="<?= isset($_COOKIE['theme']) ? $_COOKIE['theme'] : 'light' ?>">
|
||||||
<head>
|
<head>
|
||||||
@@ -178,9 +302,11 @@ if (isset($_GET['stream'])) {
|
|||||||
<source src="view.php?file=<?= urlencode($filePath) ?>&stream=1" type="<?= $mimeType ?>">
|
<source src="view.php?file=<?= urlencode($filePath) ?>&stream=1" type="<?= $mimeType ?>">
|
||||||
Your browser does not support this video format. Try downloading the file instead.
|
Your browser does not support this video format. Try downloading the file instead.
|
||||||
</video>
|
</video>
|
||||||
|
<?php if($fileExtension !== 'mp4'): ?>
|
||||||
<div class="alert alert-warning mt-2">
|
<div class="alert alert-warning mt-2">
|
||||||
Note: For best results, use MP4 format (H.264 codec). Other formats may not play correctly in all browsers.
|
Note: For best results, use MP4 format (H.264 codec). Other formats may not play correctly in all browsers.
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php elseif ($isAudio): ?>
|
<?php elseif ($isAudio): ?>
|
||||||
<audio controls autoplay style="width: 80%;">
|
<audio controls autoplay style="width: 80%;">
|
||||||
<source src="view.php?file=<?= urlencode($filePath) ?>&stream=1" type="<?= $mimeType ?>">
|
<source src="view.php?file=<?= urlencode($filePath) ?>&stream=1" type="<?= $mimeType ?>">
|
||||||
@@ -191,8 +317,33 @@ if (isset($_GET['stream'])) {
|
|||||||
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
|
||||||
<a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a>
|
<a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Add Share Button -->
|
||||||
|
<button type="button" class="btn btn-info m-3" data-bs-toggle="modal" data-bs-target="#shareModal">
|
||||||
|
<i class="bi bi-share"></i> Share
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Share success message -->
|
||||||
|
<?php if ($shareCreated): ?>
|
||||||
|
<div class="alert alert-success mt-3">
|
||||||
|
<p>Share link created successfully!</p>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input type="text" class="form-control" id="shareUrl" value="<?= htmlspecialchars($shareUrl) ?>" readonly>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="copyShareUrl()">
|
||||||
|
<i class="bi bi-clipboard"></i> Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="small text-muted">This link will expire on <?= date('F j, Y, g:i a', strtotime($shareExpiration)) ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($shareError): ?>
|
||||||
|
<div class="alert alert-danger mt-3">
|
||||||
|
<?= htmlspecialchars($shareError) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="file-info mt-4">
|
<div class="file-info mt-4">
|
||||||
<h4>File Information</h4>
|
<h4>File Information</h4>
|
||||||
<table class="table table-bordered w-auto mx-auto text-wrap">
|
<table class="table table-bordered w-auto mx-auto text-wrap">
|
||||||
@@ -220,7 +371,7 @@ if (isset($_GET['stream'])) {
|
|||||||
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3 m-3">
|
||||||
<span class="text-muted">Developed by Michal Sedlák</span>
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="https://github.com/michalcz10/USB-RAID-pole" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
<img src="../../img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
<img src="../../img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
</a>
|
</a>
|
||||||
@@ -232,7 +383,81 @@ if (isset($_GET['stream'])) {
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Share Modal -->
|
||||||
|
<div class="modal fade" id="shareModal" tabindex="-1" aria-labelledby="shareModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="shareModalLabel">Share "<?= htmlspecialchars($fileName) ?>"</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="post" action="">
|
||||||
|
<input type="hidden" name="filePath" value="<?= htmlspecialchars($filePath) ?>">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="duration" class="form-label">Link expires after</label>
|
||||||
|
<select class="form-select" id="duration" name="duration">
|
||||||
|
<option value="1">1 hour</option>
|
||||||
|
<option value="6">6 hours</option>
|
||||||
|
<option value="24" selected>1 day</option>
|
||||||
|
<option value="168">1 week</option>
|
||||||
|
<option value="720">30 days</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="downloadAllowed" name="downloadAllowed">
|
||||||
|
<label class="form-check-label" for="downloadAllowed">Allow downloading</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-end">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" name="createShare" class="btn btn-primary">Create Share Link</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyShareUrl() {
|
||||||
|
const shareUrl = document.getElementById('shareUrl');
|
||||||
|
shareUrl.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
|
||||||
|
// Show feedback
|
||||||
|
const button = shareUrl.nextElementSibling;
|
||||||
|
const originalText = button.innerHTML;
|
||||||
|
button.innerHTML = '<i class="bi bi-check"></i> Copied!';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = originalText;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script src="../../js/theme.js"></script>
|
<script src="../../js/theme.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const mediaElement = document.querySelector('video, audio');
|
||||||
|
|
||||||
|
if (mediaElement) {
|
||||||
|
// Restore volume from localStorage
|
||||||
|
const savedVolume = localStorage.getItem('mediaVolume');
|
||||||
|
if (savedVolume !== null) {
|
||||||
|
mediaElement.volume = parseFloat(savedVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save volume to localStorage when it changes
|
||||||
|
mediaElement.addEventListener('volumechange', () => {
|
||||||
|
localStorage.setItem('mediaVolume', mediaElement.volume);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ if (empty($_SESSION)) {
|
|||||||
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3">
|
<footer class="d-flex flex-column justify-content-center align-items-center p-3 border-top gap-3">
|
||||||
<span class="text-muted">Developed by Michal Sedlák</span>
|
<span class="text-muted">Developed by Michal Sedlák</span>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
<a href="https://github.com/michalcz10/USB-RAID-pole" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/michalcz10/USB-RAID-Array" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
|
||||||
<img src="img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
<img src="img/GitHub_Logo.png" alt="GitHub Logo" class="img-fluid hover-effect light-logo" style="height: 32px;">
|
||||||
<img src="img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
<img src="img/GitHub_Logo_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user