Pushed betatest to production

This commit is contained in:
2025-05-20 20:51:04 +02:00
parent d60d524a6a
commit abf232bffa
15 changed files with 2286 additions and 47 deletions

View File

@@ -34,6 +34,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($uname) || empty($pswd)) {
$_SESSION['message'] = 'Error: Username and password are required!';
$_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 {
$sql_check = "SELECT * FROM users WHERE uname = ?";
$stmt_check = $conn->prepare($sql_check);
@@ -117,6 +120,19 @@ $message = $_SESSION['message'] ?? '';
$message_type = $_SESSION['message_type'] ?? '';
unset($_SESSION['message']);
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>
@@ -151,6 +167,7 @@ unset($_SESSION['message_type']);
<a href="logout.php" class="btn btn-danger">Logout</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/serverstat.php" class="btn btn-primary">Server Status</a>
</div>
</header>
<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">
<span class="text-muted">Developed by Michal Sedlák</span>
<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_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
</a>

View File

@@ -21,7 +21,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$confirm_password = htmlspecialchars($_POST['confirm_password']);
$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!";
$messageType = "danger";
} else {
@@ -74,6 +77,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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>
@@ -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">
<span class="text-muted">Developed by Michal Sedlák</span>
<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_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
</a>

253
Web/content/ftp/css/pdf.css Normal file
View 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;
}

View 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
View 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);
}
?>

View File

@@ -30,6 +30,7 @@
$videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
$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'];
$archiveExtensions = ['zip', 'rar', 'tar', 'gz', '7z', 'bz2', 'xz', 'tar.gz', 'tar.bz2', 'tar.xz'];
?>
<!DOCTYPE html>
<html lang="en">
@@ -58,6 +59,7 @@
<a href="../changepassword.php" class="btn btn-warning">Change Password</a>
<?php if (isset($_SESSION["admin"]) && $_SESSION["admin"] == true) { ?>
<a href="../adminpanel.php" class="btn btn-primary">Admin Panel</a>
<a href="serverstat.php" class="btn btn-primary">Server Status</a>
<?php } ?>
</div>
</header>
@@ -82,7 +84,8 @@
<?php } ?>
</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-dialog">
<div class="modal-content">
@@ -127,6 +130,37 @@
</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) { ?>
<div class="dropzone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)">
Drop files or folders here to upload
@@ -137,6 +171,7 @@
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Actions</th>
</tr>
</thead>
@@ -161,29 +196,32 @@
if ($currentPath !== $defaultPath) : ?>
<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>
<?php endif;
foreach ($directories as $directory) : ?>
<tr>
<td>
<td colspan="2">
<a href="?path=<?= urlencode($currentPath . '/' . $directory) ?>"><?= htmlspecialchars($directory) ?>/</a>
</td>
<td>
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?>
<form method="POST" action="delete.php" style="display:inline;">
<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>
<?php endif; ?>
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
<form method="POST" action="download.php" style="display:inline;">
<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>
<?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>
</tr>
<?php endforeach;
@@ -192,11 +230,16 @@
<?php
$fileExtension = pathinfo($file, PATHINFO_EXTENSION);
$isPDF = strtolower($fileExtension) === 'pdf';
$isImage = in_array($fileExtension, $imageExtensions);
$isVideo = in_array($fileExtension, $videoExtensions);
$isAudio = in_array($fileExtension, $audioExtensions);
$isMedia = $isImage || $isVideo || $isAudio;
$isEditable = in_array($fileExtension, $editableExtensions);
$isArchive = in_array($fileExtension, $archiveExtensions);
if (!$isArchive && strpos($file, '.tar.') !== false) {
$isArchive = true;
}
?>
<tr>
<td>
@@ -217,23 +260,40 @@
<?= htmlspecialchars($file) ?>
</a>
<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: ?>
<?= htmlspecialchars($file) ?>
<span class="badge bg-light text-dark rounded-pill"><?= htmlspecialchars($fileExtension) ?></span>
<?php endif; ?>
</td>
<td>
<span class="text"><?= formatBytes($sftp->stat($currentPath . '/' . $file)['size']) ?></span>
</td>
<td>
<?php if (isset($_SESSION["delPer"]) && $_SESSION["delPer"] == true) : ?>
<form method="POST" action="delete.php" style="display:inline;">
<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>
<?php endif; ?>
<?php if (isset($_SESSION["downPer"]) && $_SESSION["downPer"] == true) : ?>
<form method="POST" action="download.php" style="display:inline;">
<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>
<?php endif; ?>
</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">
<span class="text-muted">Developed by Michal Sedlák</span>
<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_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
</a>
@@ -265,3 +325,14 @@
<script src="js/index.js"></script>
</body>
</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];
}
?>

View File

@@ -236,6 +236,16 @@ function createFile() {
};
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() {
const themeToggle = document.getElementById('themeToggle');
const html = document.documentElement;

331
Web/content/ftp/pdf.php Normal file
View 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>

View 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;
}
?>

View 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
View 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>

View File

@@ -20,6 +20,69 @@ if (!isset($_GET['file'])) {
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'];
$fileName = basename($filePath);
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
@@ -62,18 +125,31 @@ $mimeMap = [
'wav' => 'audio/wav',
'm4a' => 'audio/mp4',
'flac' => 'audio/flac',
'aac' => 'audio/aac'
'aac' => 'audio/aac',
];
$mimeType = isset($mimeMap[$fileExtension]) ? $mimeMap[$fileExtension] : 'application/octet-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;
$end = $fileSize - 1;
$length = $fileSize;
// Handle range requests
if (isset($_SERVER['HTTP_RANGE'])) {
$rangeHeader = $_SERVER['HTTP_RANGE'];
$matches = [];
@@ -91,56 +167,104 @@ if (isset($_GET['stream'])) {
}
}
// Set headers for streaming
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");
while (ob_get_level()) {
ob_end_clean();
// Debug headers
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;
$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) {
$readSize = min($chunkSize, $bytesRemaining);
// Calculate adaptive chunk size
$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)) {
$chunkData = file_get_contents($chunkTemp);
echo $chunkData;
if ($chunkData !== false) {
$bytesSent = strlen($chunkData);
unlink($chunkTemp);
// Output chunk
echo $chunkData;
$bytesRemaining -= $bytesSent;
$currentPosition += $bytesSent;
$bytesRemaining -= strlen($chunkData);
$currentPosition += strlen($chunkData);
// Flush buffers
if (ob_get_level()) {
ob_flush();
}
flush();
flush();
} else {
error_log("SFTP reading error at position $currentPosition");
break;
// Adaptive chunk sizing based on transfer speed
$currentTime = microtime(true);
$timeDiff = $currentTime - $lastChunkTime;
$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
}
if (connection_status() != CONNECTION_NORMAL) {
break;
} catch (Exception $e) {
error_log("Streaming error: " . $e->getMessage());
if (isset($_GET['debug'])) {
header("X-Stream-Error: " . $e->getMessage());
}
}
fclose($tempHandle);
if (file_exists($tempFile)) {
unlink($tempFile);
if (isset($_GET['debug'])) {
error_log("Streaming completed. Sent $currentPosition bytes of $fileSize");
}
exit;
}
?>
<!DOCTYPE html>
<html lang="en" data-bs-theme="<?= isset($_COOKIE['theme']) ? $_COOKIE['theme'] : 'light' ?>">
<head>
@@ -178,9 +302,11 @@ if (isset($_GET['stream'])) {
<source src="view.php?file=<?= urlencode($filePath) ?>&stream=1" type="<?= $mimeType ?>">
Your browser does not support this video format. Try downloading the file instead.
</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="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) : ?>
<a href="download.php?file=<?= urlencode($filePath) ?>" class="btn btn-success m-3">Download</a>
<?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>
<!-- 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">
<h4>File Information</h4>
<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">
<span class="text-muted">Developed by Michal Sedlák</span>
<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_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
</a>
@@ -232,7 +383,81 @@ if (isset($_GET['stream'])) {
</footer>
</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>
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>
</html>

View File

@@ -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">
<span class="text-muted">Developed by Michal Sedlák</span>
<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_White.png" alt="GitHub Logo" class="img-fluid hover-effect dark-logo" style="height: 32px;">
</a>