mirror of
https://github.com/michalcz10/USB-RAID-Array.git
synced 2025-12-10 03:22:19 +00:00
WebApp production ver
This commit is contained in:
35
Web/betatest/content/ftp/config.php
Normal file
35
Web/betatest/content/ftp/config.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require '../../vendor/autoload.php';
|
||||
use phpseclib3\Net\SFTP;
|
||||
|
||||
$defPath = $_SESSION['defPath'] ?? '/';
|
||||
|
||||
// SFTP Configuration
|
||||
$host = 'localhost';
|
||||
$username = 'UNAME';
|
||||
$password = 'PSWD';
|
||||
$defaultPath = $defPath;
|
||||
|
||||
function initializeSFTP($host, $username, $password) {
|
||||
$sftp = new SFTP($host);
|
||||
if (!$sftp->login($username, $password)) {
|
||||
die('Login Failed');
|
||||
}
|
||||
return $sftp;
|
||||
}
|
||||
|
||||
function normalizePath($path) {
|
||||
$parts = array_filter(explode('/', $path), fn($part) => $part !== '' && $part !== '.');
|
||||
$stack = [];
|
||||
foreach ($parts as $part) {
|
||||
if ($part === '..') {
|
||||
array_pop($stack);
|
||||
} else {
|
||||
$stack[] = $part;
|
||||
}
|
||||
}
|
||||
return '/' . implode('/', $stack);
|
||||
}
|
||||
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
$currentPath = normalizePath($defaultPath);
|
||||
41
Web/betatest/content/ftp/createdir.php
Normal file
41
Web/betatest/content/ftp/createdir.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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';
|
||||
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'createDir') {
|
||||
$currentPath = isset($_POST['currentPath']) ? normalizePath($_POST['currentPath']) : $defaultPath;
|
||||
|
||||
if (!isset($_POST['dirName'])) {
|
||||
http_response_code(400);
|
||||
echo "Directory name is required";
|
||||
exit;
|
||||
}
|
||||
|
||||
$dirName = $_POST['dirName'];
|
||||
|
||||
$dirName = preg_replace('/[^\w\-\.]/', '_', $dirName);
|
||||
|
||||
if (strpos($currentPath, $defaultPath) !== 0) {
|
||||
$currentPath = $defaultPath;
|
||||
}
|
||||
|
||||
$newDirPath = $currentPath . '/' . $dirName;
|
||||
|
||||
if ($sftp->mkdir($newDirPath)) {
|
||||
echo "Directory created successfully!";
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo "Failed to create directory.";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
echo "Invalid request";
|
||||
exit;
|
||||
68
Web/betatest/content/ftp/createfile.php
Normal file
68
Web/betatest/content/ftp/createfile.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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(!isset($_SESSION['uname'])){
|
||||
header("location: ../index.html");
|
||||
session_destroy();
|
||||
exit;
|
||||
}
|
||||
|
||||
if(!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) {
|
||||
http_response_code(403);
|
||||
echo "You don't have permission to create files.";
|
||||
exit;
|
||||
}
|
||||
|
||||
require 'config.php';
|
||||
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'createFile') {
|
||||
$currentPath = isset($_POST['currentPath']) ? normalizePath($_POST['currentPath']) : $defaultPath;
|
||||
|
||||
if (!isset($_POST['fileName'])) {
|
||||
http_response_code(400);
|
||||
echo "File name is required";
|
||||
exit;
|
||||
}
|
||||
|
||||
$fileName = $_POST['fileName'];
|
||||
|
||||
if (strpos($fileName, '.') === false) {
|
||||
http_response_code(400);
|
||||
echo "File name must include an extension (e.g., .txt, .html, .php)";
|
||||
exit;
|
||||
}
|
||||
|
||||
$fileName = preg_replace('/[^\w\-\.]/', '_', $fileName);
|
||||
|
||||
if (strpos($currentPath, $defaultPath) !== 0) {
|
||||
$currentPath = $defaultPath;
|
||||
}
|
||||
|
||||
$newFilePath = $currentPath . '/' . $fileName;
|
||||
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'new_file_');
|
||||
file_put_contents($tempFile, '');
|
||||
|
||||
if ($sftp->put($newFilePath, $tempFile)) {
|
||||
@unlink($tempFile);
|
||||
|
||||
echo json_encode(['success' => true, 'filePath' => $newFilePath]);
|
||||
} else {
|
||||
@unlink($tempFile);
|
||||
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Failed to create file.']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
echo "Invalid request";
|
||||
exit;
|
||||
128
Web/betatest/content/ftp/css/index.css
Normal file
128
Web/betatest/content/ftp/css/index.css
Normal file
@@ -0,0 +1,128 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.custom-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
section.row {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
article.col-8 {
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
@media (max-width: 1800px) {
|
||||
article.col-8 {
|
||||
flex: 0 0 auto;
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.custom-container {
|
||||
height: auto;
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
section.row {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body {
|
||||
height: auto;
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
article {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
border: 2px dashed #007bff;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dropzone.dragover {
|
||||
background-color: #e0f7fa;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-buttons button {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: auto;
|
||||
padding: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
width: 100%;
|
||||
}
|
||||
54
Web/betatest/content/ftp/css/open.css
Normal file
54
Web/betatest/content/ftp/css/open.css
Normal file
@@ -0,0 +1,54 @@
|
||||
.editor-container {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#editor {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
padding-left: 55px; /* Make room for line numbers */
|
||||
}
|
||||
|
||||
.line-numbers {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 45px;
|
||||
text-align: right;
|
||||
padding: 10px 5px 10px 0;
|
||||
border-right: 1px solid #ccc;
|
||||
color: #999;
|
||||
user-select: none;
|
||||
font-family: monospace;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
background-color: #f8f9fa; /* Light mode default */
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .line-numbers {
|
||||
background-color: #212529; /* Dark mode background */
|
||||
border-right-color: #495057; /* Darker border for dark mode */
|
||||
color: #6c757d; /* Lighter text for dark mode */
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.readonly-notice {
|
||||
color: #dc3545;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
}
|
||||
253
Web/betatest/content/ftp/css/pdf.css
Normal file
253
Web/betatest/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/betatest/content/ftp/css/serverstat.css
Normal file
124
Web/betatest/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;
|
||||
}
|
||||
}
|
||||
86
Web/betatest/content/ftp/css/view.css
Normal file
86
Web/betatest/content/ftp/css/view.css
Normal file
@@ -0,0 +1,86 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.custom-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.media-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
margin: 0 auto;
|
||||
overflow: visible;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.media-container img {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.media-container video {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.media-container audio {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.file-info table {
|
||||
width: 95% !important;
|
||||
}
|
||||
.file-info th {
|
||||
width: 40%;
|
||||
}
|
||||
.file-info td {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
39
Web/betatest/content/ftp/delete.php
Normal file
39
Web/betatest/content/ftp/delete.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
require 'config.php';
|
||||
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete'])) {
|
||||
$itemToDelete = $_POST['delete'];
|
||||
$parentDir = dirname($itemToDelete);
|
||||
|
||||
if ($parentDir === '/' || $parentDir === '.') {
|
||||
$parentDir = $defaultPath;
|
||||
}
|
||||
|
||||
if ($sftp->is_dir($itemToDelete)) {
|
||||
function deleteFolder($sftp, $folderPath) {
|
||||
$items = $sftp->nlist($folderPath);
|
||||
foreach ($items as $item) {
|
||||
if ($item === '.' || $item === '..') continue;
|
||||
$itemPath = $folderPath . '/' . $item;
|
||||
if ($sftp->is_dir($itemPath)) {
|
||||
deleteFolder($sftp, $itemPath);
|
||||
} else {
|
||||
$sftp->delete($itemPath);
|
||||
}
|
||||
}
|
||||
return $sftp->rmdir($folderPath);
|
||||
}
|
||||
|
||||
$success = deleteFolder($sftp, $itemToDelete);
|
||||
} else {
|
||||
$success = $sftp->delete($itemToDelete);
|
||||
}
|
||||
|
||||
header("Location: index.php?path=" . urlencode($parentDir));
|
||||
exit;
|
||||
}
|
||||
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
217
Web/betatest/content/ftp/download.php
Normal file
217
Web/betatest/content/ftp/download.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
set_time_limit(900); // 15 minutes max execution time
|
||||
|
||||
// Include this at the top to see potential errors
|
||||
// Comment out in production
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(0);
|
||||
|
||||
ob_start();
|
||||
|
||||
try {
|
||||
require 'config.php';
|
||||
|
||||
$logFile = __DIR__ . '/download_log.txt';
|
||||
file_put_contents($logFile, "Download started: " . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
|
||||
|
||||
$sftp = null;
|
||||
try {
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
file_put_contents($logFile, "SFTP connection established\n", FILE_APPEND);
|
||||
} catch (Exception $e) {
|
||||
file_put_contents($logFile, "SFTP connection failed: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||
throw new Exception("Failed to connect to SFTP server: " . $e->getMessage());
|
||||
}
|
||||
|
||||
function zipFolderRecursive($sftp, $remoteBasePath, $currentPath, $zip, $logFile, &$tempFiles) {
|
||||
file_put_contents($logFile, "Processing directory: $currentPath\n", FILE_APPEND);
|
||||
|
||||
$files = $sftp->nlist($currentPath);
|
||||
|
||||
if ($files === false) {
|
||||
file_put_contents($logFile, "Failed to list directory contents for: $currentPath\n", FILE_APPEND);
|
||||
throw new Exception("Failed to list directory contents for: $currentPath");
|
||||
}
|
||||
|
||||
file_put_contents($logFile, "Found " . count($files) . " items in $currentPath\n", FILE_APPEND);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file == '.' || $file == '..') continue;
|
||||
|
||||
$fullRemotePath = rtrim($currentPath, '/') . '/' . $file;
|
||||
|
||||
$baseDirName = basename($remoteBasePath);
|
||||
$relPathFromBase = substr($fullRemotePath, strlen(dirname($remoteBasePath)) + 1);
|
||||
file_put_contents($logFile, "Processing: $fullRemotePath (relative: $relPathFromBase)\n", FILE_APPEND);
|
||||
|
||||
$isDir = $sftp->is_dir($fullRemotePath);
|
||||
|
||||
if ($isDir) {
|
||||
file_put_contents($logFile, "Found subdirectory: $fullRemotePath\n", FILE_APPEND);
|
||||
$zip->addEmptyDir($relPathFromBase);
|
||||
|
||||
zipFolderRecursive($sftp, $remoteBasePath, $fullRemotePath, $zip, $logFile, $tempFiles);
|
||||
} else {
|
||||
$localTempFile = tempnam(sys_get_temp_dir(), 'sftp');
|
||||
$tempFiles[] = $localTempFile;
|
||||
file_put_contents($logFile, "Downloading to temp file: $localTempFile\n", FILE_APPEND);
|
||||
|
||||
$downloadStart = time();
|
||||
$downloadSuccess = $sftp->get($fullRemotePath, $localTempFile);
|
||||
$downloadTime = time() - $downloadStart;
|
||||
|
||||
if ($downloadSuccess) {
|
||||
$fileSize = filesize($localTempFile);
|
||||
file_put_contents($logFile, "Download successful ($downloadTime seconds), size: $fileSize bytes\n", FILE_APPEND);
|
||||
|
||||
$zip->addFile($localTempFile, $relPathFromBase);
|
||||
} else {
|
||||
file_put_contents($logFile, "Failed to download file after $downloadTime seconds\n", FILE_APPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function zipFolder($sftp, $folderPath, $zipFilePath, $logFile) {
|
||||
file_put_contents($logFile, "Starting to zip folder recursively: $folderPath\n", FILE_APPEND);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
|
||||
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
|
||||
file_put_contents($logFile, "Failed to create zip archive\n", FILE_APPEND);
|
||||
throw new Exception("Unable to create the zip file.");
|
||||
}
|
||||
|
||||
|
||||
$tempFiles = [];
|
||||
|
||||
try {
|
||||
zipFolderRecursive($sftp, $folderPath, $folderPath, $zip, $logFile, $tempFiles);
|
||||
|
||||
file_put_contents($logFile, "Closing zip file\n", FILE_APPEND);
|
||||
$zipSuccess = $zip->close();
|
||||
|
||||
foreach ($tempFiles as $tempFile) {
|
||||
if (file_exists($tempFile)) {
|
||||
@unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
if ($zipSuccess) {
|
||||
file_put_contents($logFile, "Zip created successfully, size: " . filesize($zipFilePath) . " bytes\n", FILE_APPEND);
|
||||
return true;
|
||||
} else {
|
||||
file_put_contents($logFile, "Failed to create zip\n", FILE_APPEND);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
foreach ($tempFiles as $tempFile) {
|
||||
if (file_exists($tempFile)) {
|
||||
@unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($logFile, "Error during zip creation: " . $e->getMessage() . "\n", FILE_APPEND);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['file'])) ||
|
||||
($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['file']))) {
|
||||
|
||||
$file = $_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST['file'] : $_GET['file'];
|
||||
file_put_contents($logFile, "Requested file: $file\n", FILE_APPEND);
|
||||
|
||||
$fileExists = $sftp->file_exists($file);
|
||||
$isDir = $sftp->is_dir($file);
|
||||
|
||||
file_put_contents($logFile, "File exists: " . ($fileExists ? "Yes" : "No") . "\n", FILE_APPEND);
|
||||
file_put_contents($logFile, "Is directory: " . ($isDir ? "Yes" : "No") . "\n", FILE_APPEND);
|
||||
|
||||
if (!$fileExists && !$isDir) {
|
||||
throw new Exception("File not found: $file");
|
||||
}
|
||||
|
||||
if ($isDir) {
|
||||
$zipFilePath = tempnam(sys_get_temp_dir(), 'folder_') . '.zip';
|
||||
file_put_contents($logFile, "Creating zip at: $zipFilePath\n", FILE_APPEND);
|
||||
|
||||
if (zipFolder($sftp, $file, $zipFilePath, $logFile)) {
|
||||
if (file_exists($zipFilePath) && filesize($zipFilePath) > 0) {
|
||||
file_put_contents($logFile, "Sending zip file to browser, size: " . filesize($zipFilePath) . " bytes\n", FILE_APPEND);
|
||||
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: application/zip');
|
||||
header('Content-Disposition: attachment; filename="' . basename($file) . '.zip"');
|
||||
header('Content-Length: ' . filesize($zipFilePath));
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
readfile($zipFilePath);
|
||||
file_put_contents($logFile, "Download completed\n", FILE_APPEND);
|
||||
@unlink($zipFilePath);
|
||||
exit;
|
||||
} else {
|
||||
throw new Exception("Failed to create zip file or zip file is empty");
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Failed to create zip archive");
|
||||
}
|
||||
} else {
|
||||
$localFile = basename($file);
|
||||
file_put_contents($logFile, "Downloading single file: $localFile\n", FILE_APPEND);
|
||||
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'file_');
|
||||
|
||||
$downloadStart = time();
|
||||
$downloadSuccess = $sftp->get($file, $tempFile);
|
||||
$downloadTime = time() - $downloadStart;
|
||||
|
||||
file_put_contents($logFile, "Download " . ($downloadSuccess ? "successful" : "failed") . " ($downloadTime seconds)\n", FILE_APPEND);
|
||||
|
||||
if ($downloadSuccess) {
|
||||
$fileSize = filesize($tempFile);
|
||||
file_put_contents($logFile, "Downloaded file size: $fileSize bytes\n", FILE_APPEND);
|
||||
|
||||
if ($fileSize > 0) {
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename="' . $localFile . '"');
|
||||
header('Content-Length: ' . $fileSize);
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
readfile($tempFile);
|
||||
file_put_contents($logFile, "Download completed\n", FILE_APPEND);
|
||||
@unlink($tempFile);
|
||||
exit;
|
||||
} else {
|
||||
@unlink($tempFile);
|
||||
throw new Exception("Downloaded file is empty");
|
||||
}
|
||||
} else {
|
||||
@unlink($tempFile);
|
||||
throw new Exception("Failed to download file from SFTP server");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Invalid request method or missing file parameter");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errorMessage = "Error: " . $e->getMessage();
|
||||
file_put_contents($logFile, $errorMessage . "\n", FILE_APPEND);
|
||||
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header("HTTP/1.1 500 Internal Server Error");
|
||||
echo $errorMessage;
|
||||
}
|
||||
?>
|
||||
244
Web/betatest/content/ftp/extract.php
Normal file
244
Web/betatest/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);
|
||||
}
|
||||
?>
|
||||
338
Web/betatest/content/ftp/index.php
Normal file
338
Web/betatest/content/ftp/index.php
Normal file
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if(!isset($_SESSION['uname'])){
|
||||
header("location: ../../index.php");
|
||||
session_destroy();
|
||||
exit;
|
||||
}
|
||||
|
||||
// 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';
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
$currentPath = isset($_GET['path']) ? normalizePath($_GET['path']) : $defaultPath;
|
||||
if (strpos($currentPath, $defaultPath) !== 0) {
|
||||
$currentPath = $defaultPath;
|
||||
}
|
||||
|
||||
if (!$sftp->chdir($currentPath)) {
|
||||
die("Failed to navigate to the selected folder: $currentPath");
|
||||
}
|
||||
|
||||
$items = $sftp->nlist();
|
||||
|
||||
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
|
||||
$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">
|
||||
<head>
|
||||
<title>FTP</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/index.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>USB RAID Array</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>
|
||||
<?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>
|
||||
|
||||
<section class="row">
|
||||
|
||||
<article class="col-8 border border-2 border-primary rounded p-2">
|
||||
<div class="col">
|
||||
<h4>Current Path: <?= htmlspecialchars($currentPath) ?></h4>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) { ?>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createFileModal">Create File</button>
|
||||
<button type="button" class="btn btn-success" onclick="document.getElementById('fileInput').click()">Upload Files</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createDirModal">Create Directory</button>
|
||||
<button type="button" class="btn btn-info" onclick="document.getElementById('dirInput').click()">Upload Directory</button>
|
||||
<input type="file" id="fileInput" multiple style="display: none;" onchange="handleFileSelect(event)">
|
||||
<input type="file" id="dirInput" webkitdirectory directory multiple style="display: none;" onchange="handleFileSelect(event)">
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="createDirModalLabel">Create New Directory</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="dirName" class="form-label">Directory Name</label>
|
||||
<input type="text" class="form-control" id="dirName" placeholder="Enter directory name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="createDirectory()">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create File Dialog -->
|
||||
<div class="modal fade" id="createFileModal" tabindex="-1" aria-labelledby="createFileModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="createFileModalLabel">Create New File</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="fileName" class="form-label">File Name (with extension)</label>
|
||||
<input type="text" class="form-control" id="fileName" placeholder="example.txt">
|
||||
<div class="form-text">Supported extensions: .txt, .html, .css, .js, .php, etc.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="createFile()">Create</button>
|
||||
</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) { ?>
|
||||
<div class="dropzone" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" ondrop="handleDrop(event)">
|
||||
Drop files or folders here to upload
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$directories = [];
|
||||
$files = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item === '.' || $item === '..') continue;
|
||||
|
||||
if ($sftp->is_dir($item)) {
|
||||
$directories[] = $item;
|
||||
} else {
|
||||
$files[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort directories and files alphabetically
|
||||
sort($directories, SORT_STRING | SORT_FLAG_CASE);
|
||||
sort($files, SORT_STRING | SORT_FLAG_CASE);
|
||||
|
||||
if ($currentPath !== $defaultPath) : ?>
|
||||
<tr>
|
||||
<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 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 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 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;
|
||||
|
||||
foreach ($files as $file) : ?>
|
||||
<?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>
|
||||
|
||||
<?php if ($isMedia): ?>
|
||||
<a class="text-info-emphasis" href="view.php?file=<?= urlencode($currentPath . '/' . $file) ?>">
|
||||
<?= htmlspecialchars($file) ?>
|
||||
</a>
|
||||
<?php if ($isImage): ?>
|
||||
<span class="badge bg-success rounded-pill">Image</span>
|
||||
<?php elseif ($isVideo): ?>
|
||||
<span class="badge bg-primary rounded-pill">Video</span>
|
||||
<?php elseif ($isAudio): ?>
|
||||
<span class="badge bg-info rounded-pill">Audio</span>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($isEditable): ?>
|
||||
<a class="text-warning-emphasis" href="open.php?file=<?= urlencode($currentPath . '/' . $file) ?>">
|
||||
<?= 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 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 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>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
// Pass PHP variables to JavaScript
|
||||
var currentPath = "<?php echo $currentPath; ?>";
|
||||
</script>
|
||||
<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];
|
||||
}
|
||||
?>
|
||||
288
Web/betatest/content/ftp/js/index.js
Normal file
288
Web/betatest/content/ftp/js/index.js
Normal file
@@ -0,0 +1,288 @@
|
||||
function handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.add('dragover');
|
||||
}
|
||||
|
||||
function handleDragLeave(event) {
|
||||
event.currentTarget.classList.remove('dragover');
|
||||
}
|
||||
|
||||
function handleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove('dragover');
|
||||
|
||||
if (event.dataTransfer.items) {
|
||||
const items = event.dataTransfer.items;
|
||||
processDroppedItems(items);
|
||||
} else {
|
||||
const files = event.dataTransfer.files;
|
||||
uploadFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
function processDroppedItems(items) {
|
||||
const allFiles = [];
|
||||
let pendingItems = 0;
|
||||
|
||||
function handleEntry(entry, path = '') {
|
||||
if (entry.isFile) {
|
||||
pendingItems++;
|
||||
entry.file(file => {
|
||||
Object.defineProperty(file, 'webkitRelativePath', {
|
||||
value: path + file.name
|
||||
});
|
||||
|
||||
allFiles.push(file);
|
||||
pendingItems--;
|
||||
|
||||
if (pendingItems === 0) {
|
||||
uploadFiles(allFiles);
|
||||
}
|
||||
});
|
||||
} else if (entry.isDirectory) {
|
||||
const reader = entry.createReader();
|
||||
readEntries(reader, path + entry.name + '/');
|
||||
}
|
||||
}
|
||||
|
||||
function readEntries(reader, path) {
|
||||
pendingItems++;
|
||||
reader.readEntries(entries => {
|
||||
if (entries.length > 0) {
|
||||
for (const entry of entries) {
|
||||
handleEntry(entry, path);
|
||||
}
|
||||
readEntries(reader, path);
|
||||
}
|
||||
pendingItems--;
|
||||
|
||||
if (pendingItems === 0 && allFiles.length > 0) {
|
||||
uploadFiles(allFiles);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (item.kind !== 'file') continue;
|
||||
|
||||
const entry = item.webkitGetAsEntry ? item.webkitGetAsEntry() : item.getAsEntry();
|
||||
if (entry) {
|
||||
handleEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingItems === 0 && allFiles.length === 0) {
|
||||
alert('No valid files or directories found.');
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(event) {
|
||||
const files = event.target.files;
|
||||
uploadFiles(files);
|
||||
}
|
||||
|
||||
function uploadFiles(files) {
|
||||
if (!files || files.length === 0) {
|
||||
alert('No files selected for upload.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('currentPath', currentPath);
|
||||
|
||||
let filesAdded = 0;
|
||||
let directories = new Set();
|
||||
|
||||
for (const file of files) {
|
||||
const relativePath = file.webkitRelativePath || '';
|
||||
|
||||
if (relativePath) {
|
||||
const parts = relativePath.split('/');
|
||||
let currentPath = '';
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
currentPath += (i > 0 ? '/' : '') + parts[i];
|
||||
if (currentPath) {
|
||||
directories.add(currentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formData.append('files[]', file);
|
||||
formData.append('paths[]', relativePath);
|
||||
filesAdded++;
|
||||
}
|
||||
|
||||
if (directories.size > 0) {
|
||||
formData.append('create_dirs', JSON.stringify(Array.from(directories)));
|
||||
}
|
||||
|
||||
if (filesAdded === 0) {
|
||||
alert('No valid files selected for upload.');
|
||||
return;
|
||||
}
|
||||
|
||||
const uploadStatus = document.createElement('div');
|
||||
uploadStatus.className = 'alert alert-info';
|
||||
uploadStatus.textContent = 'Uploading files, please wait...';
|
||||
document.querySelector('.action-buttons').after(uploadStatus);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'upload.php', true);
|
||||
|
||||
xhr.onerror = () => {
|
||||
uploadStatus.className = 'alert alert-danger';
|
||||
uploadStatus.textContent = 'Network error occurred during upload.';
|
||||
setTimeout(() => uploadStatus.remove(), 5000);
|
||||
};
|
||||
|
||||
xhr.timeout = 300000; // 5 minutes
|
||||
xhr.ontimeout = () => {
|
||||
uploadStatus.className = 'alert alert-danger';
|
||||
uploadStatus.textContent = 'Upload timed out. Try with smaller files or fewer files.';
|
||||
setTimeout(() => uploadStatus.remove(), 5000);
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
uploadStatus.className = 'alert alert-success';
|
||||
uploadStatus.textContent = 'Upload successful!';
|
||||
setTimeout(() => {
|
||||
uploadStatus.remove();
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
uploadStatus.className = 'alert alert-danger';
|
||||
uploadStatus.textContent = 'Upload failed: ' + (xhr.responseText || xhr.statusText);
|
||||
setTimeout(() => uploadStatus.remove(), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
function createDirectory() {
|
||||
const dirName = document.getElementById('dirName').value.trim();
|
||||
if (!dirName) {
|
||||
alert('Please enter a directory name.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('currentPath', currentPath);
|
||||
formData.append('dirName', dirName);
|
||||
formData.append('action', 'createDir');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'createdir.php', true);
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
alert('Directory created successfully!');
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('createDirModal'));
|
||||
if (modal) modal.hide();
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Failed to create directory: ' + xhr.responseText || xhr.statusText);
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
function confirmDelete(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (confirm("Do you really want to delete this file?")) {
|
||||
event.target.form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
function createFile() {
|
||||
const fileName = document.getElementById('fileName').value.trim();
|
||||
if (!fileName) {
|
||||
alert('Please enter a file name.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileName.indexOf('.') === -1) {
|
||||
alert('Please include a file extension (e.g., .txt, .html, .php)');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('currentPath', currentPath);
|
||||
formData.append('fileName', fileName);
|
||||
formData.append('action', 'createFile');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'createfile.php', true);
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
alert('File created successfully!');
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('createFileModal'));
|
||||
if (modal) modal.hide();
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Failed to create file: ' + (response.message || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error processing response: ' + xhr.responseText);
|
||||
}
|
||||
} else {
|
||||
alert('Failed to create file: ' + xhr.responseText || xhr.statusText);
|
||||
}
|
||||
};
|
||||
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;
|
||||
const themeText = document.getElementById('themeText');
|
||||
const themeIcon = themeToggle.querySelector('.bi');
|
||||
|
||||
function setTheme(theme) {
|
||||
html.setAttribute('data-bs-theme', theme);
|
||||
document.body.classList.remove('theme-light', 'theme-dark');
|
||||
document.body.classList.add('theme-' + theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
if (theme === 'dark') {
|
||||
themeText.textContent = 'Light Mode';
|
||||
themeIcon.className = 'bi bi-sun';
|
||||
themeToggle.classList.remove('btn-dark');
|
||||
themeToggle.classList.add('btn-light');
|
||||
} else {
|
||||
themeText.textContent = 'Dark Mode';
|
||||
themeIcon.className = 'bi bi-moon';
|
||||
themeToggle.classList.remove('btn-light');
|
||||
themeToggle.classList.add('btn-dark');
|
||||
}
|
||||
}
|
||||
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if (savedTheme) {
|
||||
setTheme(savedTheme);
|
||||
} else {
|
||||
setTheme(prefersDark ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('click', function() {
|
||||
const currentTheme = html.getAttribute('data-bs-theme');
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
setTheme(newTheme);
|
||||
});
|
||||
});
|
||||
204
Web/betatest/content/ftp/open.php
Normal file
204
Web/betatest/content/ftp/open.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?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(!isset($_SESSION['uname'])){
|
||||
header("location: ../../index.php");
|
||||
session_destroy();
|
||||
exit;
|
||||
}
|
||||
|
||||
require 'config.php';
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
$filePath = isset($_GET['file']) ? $_GET['file'] : '';
|
||||
|
||||
$content = '';
|
||||
$fileName = basename($filePath);
|
||||
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$editable = false;
|
||||
|
||||
$editableExtensions = ['txt', 'html', 'css', 'js', 'php', 'xml', 'json', 'md', 'csv', 'log', 'ini', 'conf', 'sh', 'bat', 'py', 'rb', 'java', 'c', 'cpp', 'h', 'hpp'];
|
||||
|
||||
if (!empty($filePath) && $sftp->file_exists($filePath) && !$sftp->is_dir($filePath)) {
|
||||
if (in_array(strtolower($extension), $editableExtensions)) {
|
||||
$editable = true;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true) {
|
||||
$newContent = $_POST['content'];
|
||||
|
||||
if ($sftp->put($filePath, $newContent)) {
|
||||
$saveSuccess = true;
|
||||
} else {
|
||||
$saveError = "Failed to save changes. Check permissions.";
|
||||
}
|
||||
}
|
||||
|
||||
$content = $sftp->get($filePath);
|
||||
if ($content === false) {
|
||||
$error = "Failed to read file contents.";
|
||||
}
|
||||
} else {
|
||||
$error = "This file type is not supported for editing.";
|
||||
}
|
||||
} else {
|
||||
$error = "File not found or is a directory.";
|
||||
}
|
||||
|
||||
$showLineNumbers = in_array(strtolower($extension), ['php', 'js', 'html', 'css', 'py', 'java', 'c', 'cpp', 'h', 'hpp', 'rb', 'sh', 'xml', 'json']);
|
||||
|
||||
$parentDir = dirname($filePath);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Edit File - <?= htmlspecialchars($fileName) ?></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/open.css">
|
||||
<script src="../../js/bootstrap.bundle.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="container mt-4">
|
||||
<div class="header-container">
|
||||
<h1>Edit File: <?= htmlspecialchars($fileName) ?></h1>
|
||||
<a href="index.php?path=<?= urlencode($parentDir) ?>" class="btn btn-secondary">Back to File List</a>
|
||||
</div>
|
||||
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="alert alert-danger"><?= $error ?></div>
|
||||
<?php elseif ($editable): ?>
|
||||
<?php if (isset($saveSuccess)): ?>
|
||||
<div class="alert alert-success">File saved successfully!</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($saveError)): ?>
|
||||
<div class="alert alert-danger"><?= $saveError ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" id="editorForm">
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label for="editor" class="form-label">File Content</label>
|
||||
<?php if (!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true): ?>
|
||||
<span class="readonly-notice">Read-only mode (you don't have upload permissions)</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="editor-container">
|
||||
<?php if ($showLineNumbers): ?>
|
||||
<div id="lineNumbers" class="line-numbers"></div>
|
||||
<?php endif; ?>
|
||||
<textarea id="editor" name="content" class="form-control" <?= (!isset($_SESSION["upPer"]) || $_SESSION["upPer"] != true) ? 'readonly' : '' ?>><?= htmlspecialchars($content) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_SESSION["upPer"]) && $_SESSION["upPer"] == true): ?>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<script src="../../js/theme.js"></script>
|
||||
<script>
|
||||
function updateLineNumbers() {
|
||||
const editor = document.getElementById('editor');
|
||||
const lineNumbers = document.getElementById('lineNumbers');
|
||||
|
||||
if (!lineNumbers) return;
|
||||
|
||||
const lines = editor.value.split('\n');
|
||||
const lineCount = lines.length;
|
||||
|
||||
let html = '';
|
||||
for (let i = 1; i <= lineCount; i++) {
|
||||
html += i + '<br>';
|
||||
}
|
||||
|
||||
lineNumbers.innerHTML = html;
|
||||
|
||||
syncLineNumbersHeight();
|
||||
lineNumbers.scrollTop = editor.scrollTop;
|
||||
}
|
||||
|
||||
function syncLineNumbersHeight() {
|
||||
const editor = document.getElementById('editor');
|
||||
const lineNumbers = document.getElementById('lineNumbers');
|
||||
|
||||
if (!lineNumbers || !editor) return;
|
||||
|
||||
lineNumbers.style.height = editor.clientHeight + 'px';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const editor = document.getElementById('editor');
|
||||
const lineNumbers = document.getElementById('lineNumbers');
|
||||
|
||||
if (editor && lineNumbers) {
|
||||
setTimeout(() => {
|
||||
updateLineNumbers();
|
||||
syncLineNumbersHeight();
|
||||
}, 0);
|
||||
|
||||
editor.addEventListener('input', updateLineNumbers);
|
||||
|
||||
editor.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
|
||||
const start = this.selectionStart;
|
||||
const end = this.selectionEnd;
|
||||
|
||||
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
||||
|
||||
this.selectionStart = this.selectionEnd = start + 4;
|
||||
|
||||
updateLineNumbers();
|
||||
}
|
||||
});
|
||||
|
||||
editor.addEventListener('scroll', function() {
|
||||
if (lineNumbers) {
|
||||
lineNumbers.scrollTop = this.scrollTop;
|
||||
}
|
||||
});
|
||||
|
||||
editor.addEventListener('mouseup', syncLineNumbersHeight);
|
||||
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
syncLineNumbersHeight();
|
||||
});
|
||||
|
||||
observer.observe(editor, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style']
|
||||
});
|
||||
|
||||
if (typeof ResizeObserver === 'function') {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
syncLineNumbersHeight();
|
||||
});
|
||||
resizeObserver.observe(editor);
|
||||
} else {
|
||||
|
||||
window.addEventListener('resize', syncLineNumbersHeight);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
331
Web/betatest/content/ftp/pdf.php
Normal file
331
Web/betatest/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-pole" 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/betatest/content/ftp/rename.php
Normal file
48
Web/betatest/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/betatest/content/ftp/serverstat.php
Normal file
560
Web/betatest/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-pole" 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/betatest/content/ftp/shared.php
Normal file
340
Web/betatest/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-pole" 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>
|
||||
130
Web/betatest/content/ftp/upload.php
Normal file
130
Web/betatest/content/ftp/upload.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?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';
|
||||
use phpseclib3\Net\SFTP;
|
||||
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
$currentPath = isset($_POST['currentPath']) ? normalizePath($_POST['currentPath']) : $defaultPath;
|
||||
|
||||
if (strpos($currentPath, $defaultPath) !== 0) {
|
||||
$currentPath = $defaultPath;
|
||||
}
|
||||
|
||||
function createDirectoryRecursive($sftp, $path) {
|
||||
if ($sftp->is_dir($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$parent = dirname($path);
|
||||
if ($parent != '/' && !$sftp->is_dir($parent)) {
|
||||
createDirectoryRecursive($sftp, $parent);
|
||||
}
|
||||
|
||||
return $sftp->mkdir($path, 0755);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['create_dirs'])) {
|
||||
$directories = json_decode($_POST['create_dirs'], true);
|
||||
|
||||
if (is_array($directories)) {
|
||||
foreach ($directories as $dir) {
|
||||
$remoteDirPath = $currentPath . '/' . $dir;
|
||||
createDirectoryRecursive($sftp, $remoteDirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($_FILES['files']) || empty($_FILES['files']['name'][0])) {
|
||||
echo "No files received or file size exceeds PHP limits.";
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_FILES['files']['error'][0] !== 0) {
|
||||
$error = $_FILES['files']['error'][0];
|
||||
$errorMessage = "Upload error code: $error";
|
||||
|
||||
switch ($error) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
$errorMessage = "File exceeds upload_max_filesize directive in php.ini";
|
||||
break;
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$errorMessage = "File exceeds MAX_FILE_SIZE directive in the HTML form";
|
||||
break;
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$errorMessage = "File was only partially uploaded";
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$errorMessage = "No file was uploaded";
|
||||
break;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
$errorMessage = "Missing a temporary folder";
|
||||
break;
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
$errorMessage = "Failed to write file to disk";
|
||||
break;
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
$errorMessage = "A PHP extension stopped the file upload";
|
||||
break;
|
||||
}
|
||||
|
||||
echo $errorMessage;
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$uploadStatus = array();
|
||||
$anySuccess = false;
|
||||
|
||||
foreach ($_FILES['files']['tmp_name'] as $index => $tmpName) {
|
||||
if (empty($tmpName)) continue;
|
||||
|
||||
$fileName = $_FILES['files']['name'][$index];
|
||||
$relativePath = isset($_POST['paths']) && isset($_POST['paths'][$index]) ? $_POST['paths'][$index] : '';
|
||||
|
||||
if (!empty($relativePath)) {
|
||||
$fileName = basename($relativePath);
|
||||
|
||||
$dirPart = dirname($relativePath);
|
||||
if ($dirPart !== '.' && $dirPart !== '') {
|
||||
$remoteDirPath = $currentPath . '/' . $dirPart;
|
||||
|
||||
if (!$sftp->is_dir($remoteDirPath)) {
|
||||
createDirectoryRecursive($sftp, $remoteDirPath);
|
||||
}
|
||||
|
||||
$remotePath = $remoteDirPath . '/' . $fileName;
|
||||
} else {
|
||||
$remotePath = $currentPath . '/' . $fileName;
|
||||
}
|
||||
} else {
|
||||
$remotePath = $currentPath . '/' . $fileName;
|
||||
}
|
||||
|
||||
if ($sftp->put($remotePath, $tmpName, SFTP::SOURCE_LOCAL_FILE)) {
|
||||
$uploadStatus[] = "Uploaded: " . ($relativePath ? $relativePath : $fileName);
|
||||
$anySuccess = true;
|
||||
} else {
|
||||
$uploadStatus[] = "Failed to upload: " . ($relativePath ? $relativePath : $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
if ($anySuccess) {
|
||||
echo implode("\n", $uploadStatus);
|
||||
exit;
|
||||
} else {
|
||||
echo "Failed to upload any files. Please check SFTP connection and permissions.";
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
echo "No files received or invalid request.";
|
||||
http_response_code(400);
|
||||
exit;
|
||||
473
Web/betatest/content/ftp/view.php
Normal file
473
Web/betatest/content/ftp/view.php
Normal file
@@ -0,0 +1,473 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if(!isset($_SESSION['uname'])){
|
||||
header("location: ../../index.php");
|
||||
session_destroy();
|
||||
exit;
|
||||
}
|
||||
|
||||
// Include this at the top to see potential errors
|
||||
// Comment out in production
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
ini_set('memory_limit', '512M');
|
||||
|
||||
require 'config.php';
|
||||
$sftp = initializeSFTP($host, $username, $password);
|
||||
|
||||
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));
|
||||
|
||||
if (!$sftp->stat($filePath)) {
|
||||
die("File not found: $filePath");
|
||||
}
|
||||
|
||||
$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");
|
||||
}
|
||||
|
||||
$fileSize = $sftp->stat($filePath)['size'];
|
||||
|
||||
$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 (isset($_GET['stream'])) {
|
||||
// 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 = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Calculate adaptive chunk size
|
||||
$readSize = min($chunkSize, $bytesRemaining);
|
||||
|
||||
// Get chunk from SFTP
|
||||
$chunkData = $sftp->get($filePath, false, $currentPosition, $readSize);
|
||||
|
||||
if ($chunkData !== false) {
|
||||
$bytesSent = strlen($chunkData);
|
||||
|
||||
// Output chunk
|
||||
echo $chunkData;
|
||||
$bytesRemaining -= $bytesSent;
|
||||
$currentPosition += $bytesSent;
|
||||
|
||||
// Flush buffers
|
||||
if (ob_get_level()) {
|
||||
ob_flush();
|
||||
}
|
||||
flush();
|
||||
|
||||
// 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
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Streaming error: " . $e->getMessage());
|
||||
if (isset($_GET['debug'])) {
|
||||
header("X-Stream-Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
<title>Media Viewer - <?= 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>Media Viewer</h1>
|
||||
<div class="mb-3 p-3">
|
||||
<a href="index.php?path=<?= urlencode(dirname($filePath)) ?>" class="btn btn-primary">Back to Files</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="row">
|
||||
<article class="col-12">
|
||||
<div class="media-container position-relative">
|
||||
<?php if ($isImage): ?>
|
||||
<img src="view.php?file=<?= urlencode($filePath) ?>&stream=1" alt="<?= htmlspecialchars($fileName) ?>" class="img-fluid">
|
||||
<?php elseif ($isVideo): ?>
|
||||
<video id="videoPlayer" controls autoplay playsinline>
|
||||
<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 ?>">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
<?php endif; ?>
|
||||
|
||||
<?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">
|
||||
<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>MIME Type</th>
|
||||
<td class="text-break"><?= htmlspecialchars($mimeType) ?></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-pole" 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<?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];
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user