Created
January 25, 2026 12:04
-
-
Save syncip/fd70ca69e685afdc572f83f0042c9612 to your computer and use it in GitHub Desktop.
PHP WebDAV proxy for Hetzner Storage Box. Single-file UI with SQLite caching, temporary share links, auto folder sizes, media previews (img/vid/txt), deep search, password protection, and Dark Mode. Light, fast, and secure.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // ========================================== | |
| // CONFIGURATION | |
| // ========================================== | |
| $storage_url = 'https://<SUBUSERNAME>.your-storagebox.de'; | |
| $username = '<SUBUSERNAME>'; | |
| $password = '<SUBUSERPASSWORD>'; | |
| $log_file = 'access.log'; | |
| $db_file = 'stats.db'; | |
| $pw_delimiter = '.pw-'; | |
| $admin_password = 'AdminSecret123'; | |
| // ========================================== | |
| session_start(); | |
| set_time_limit(0); | |
| date_default_timezone_set('Europe/Berlin'); | |
| // --- 1. Database & Migrations --- | |
| try { | |
| $db = new PDO('sqlite:' . __DIR__ . '/' . $db_file); | |
| $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
| $db->exec("CREATE TABLE IF NOT EXISTS downloads (filepath TEXT PRIMARY KEY, count INTEGER DEFAULT 0, md5 TEXT, last_dl DATETIME, comment TEXT, size_bytes INTEGER DEFAULT 0, is_folder INTEGER DEFAULT 0)"); | |
| $db->exec("CREATE TABLE IF NOT EXISTS shares (token TEXT PRIMARY KEY, filepath TEXT, expiry DATETIME)"); | |
| $target_cols = ['md5' => 'TEXT', 'last_dl' => 'DATETIME', 'comment' => 'TEXT', 'size_bytes' => 'INTEGER DEFAULT 0', 'is_folder' => 'INTEGER DEFAULT 0']; | |
| foreach ($target_cols as $cN => $cT) { try { $db->exec("ALTER TABLE downloads ADD COLUMN $cN $cT"); } catch (Exception $e) {} } | |
| } catch (PDOException $e) { die("Database Error"); } | |
| // --- 2. Helpers --- | |
| function formatBytes($bytes) { | |
| if ($bytes <= 0) return '0 B'; | |
| $units = ['B', 'KB', 'MB', 'GB', 'TB']; | |
| $pow = floor(log($bytes) / log(1024)); | |
| return round($bytes / pow(1024, $pow), 2) . ' ' . $units[$pow]; | |
| } | |
| function getFileType($fn) { | |
| $ext = strtolower(pathinfo($fn, PATHINFO_EXTENSION)); | |
| $t = ['jpg'=>'Image','png'=>'Image','pdf'=>'PDF','zip'=>'Zip','mp4'=>'Video','txt'=>'Text','php'=>'Code','html'=>'Web']; | |
| return $t[$ext] ?? strtoupper($ext).' File'; | |
| } | |
| function isPreviewable($fn) { | |
| $ext = strtolower(pathinfo($fn, PATHINFO_EXTENSION)); | |
| if (in_array($ext, ['jpg','jpeg','png','gif','webp'])) return 'img'; | |
| if (in_array($ext, ['txt','php','html','css','js','json','md','log'])) return 'txt'; | |
| if (in_array($ext, ['mp4','webm'])) return 'vid'; | |
| return false; | |
| } | |
| function parseProtectedFilename($fn, $dlm) { | |
| if (strpos($fn, $dlm) !== false) { | |
| $p = explode($dlm, $fn); | |
| $pw = array_pop($p); | |
| return ['is_locked' => true, 'clean_name' => implode($dlm, $p), 'password' => $pw]; | |
| } | |
| return ['is_locked' => false, 'clean_name' => $fn, 'password' => null]; | |
| } | |
| // --- 3. Auth & Path Normalization --- | |
| $auth_header = "Authorization: Basic " . base64_encode("$username:$password"); | |
| $context = stream_context_create(["http" => ["method" => "GET", "header" => $auth_header, "ignore_errors" => true, "follow_location" => false]]); | |
| $pfad = isset($_GET['file']) ? $_GET['file'] : '/'; | |
| $pfad = '/' . trim(str_replace('..', '', $pfad), '/'); | |
| if ($pfad === '/') $pfad = ''; | |
| $remote_url = $storage_url . ($pfad === '' ? '/' : $pfad); | |
| $action = $_GET['action'] ?? 'view'; | |
| $base_share_url = (isset($_SERVER['HTTPS'])?'https':'http')."://".$_SERVER['HTTP_HOST'].explode('?', $_SERVER['REQUEST_URI'])[0]; | |
| // ========================================================= | |
| // 4. AJAX ACTIONS | |
| // ========================================================= | |
| if ($action === 'md5') { | |
| $fp = fopen($remote_url, 'rb', false, $context); | |
| if ($fp) { | |
| $h_ctx = hash_init('md5'); | |
| while (!feof($fp)) hash_update($h_ctx, fread($fp, 8192)); | |
| $final = strtoupper(hash_final($h_ctx)); fclose($fp); | |
| $db->prepare("UPDATE downloads SET md5 = ? WHERE filepath = ?")->execute([$final, $pfad]); | |
| echo $final; | |
| } exit; | |
| } | |
| if ($action === 'save_comment' && $_SERVER['REQUEST_METHOD'] === 'POST') { | |
| if (($_POST['admin_pw'] ?? '') !== $admin_password) die("Unauthorized"); | |
| $db->prepare("INSERT INTO downloads (filepath, comment) VALUES (?, ?) ON CONFLICT(filepath) DO UPDATE SET comment = ?")->execute([$_POST['target'], $_POST['comment'], $_POST['comment']]); | |
| echo "OK"; exit; | |
| } | |
| if ($action === 'create_share' && $_SERVER['REQUEST_METHOD'] === 'POST') { | |
| if (($_POST['admin_pw'] ?? '') !== $admin_password) die("Unauthorized"); | |
| $token = bin2hex(random_bytes(16)); | |
| $expiry = date('Y-m-d H:i:s', strtotime("+".$_POST['duration']." hours")); | |
| $db->prepare("INSERT INTO shares (token, filepath, expiry) VALUES (?, ?, ?)")->execute([$token, $_POST['target'], $expiry]); | |
| echo $base_share_url . "?share=" . $token; exit; | |
| } | |
| if ($action === 'delete_share' && $_SERVER['REQUEST_METHOD'] === 'POST') { | |
| if (($_POST['admin_pw'] ?? '') !== $admin_password) die("Unauthorized"); | |
| $db->prepare("DELETE FROM shares WHERE token = ?")->execute([$_POST['token']]); | |
| echo "OK"; exit; | |
| } | |
| if ($action === 'preview') { | |
| $fp = fopen($remote_url, 'rb', false, $context); | |
| if ($fp) fpassthru($fp); fclose($fp); exit; | |
| } | |
| // ========================================================= | |
| // 5. DOWNLOAD & LISTING | |
| // ========================================================= | |
| if (isset($_GET['share'])) { | |
| $stmt = $db->prepare("SELECT filepath FROM shares WHERE token = ? AND expiry > DATETIME('now')"); | |
| $stmt->execute([$_GET['share']]); | |
| if ($s = $stmt->fetchColumn()) { $pfad = $s; $remote_url = $storage_url . $s; $action = 'download'; } | |
| else die("Link expired."); | |
| } | |
| $is_html = false; | |
| $fp_head = @fopen($remote_url . ($pfad === '' ? '' : '/'), 'rb', false, stream_context_create(["http" => ["method" => "HEAD", "header" => $auth_header]])); | |
| if ($fp_head) { | |
| foreach (stream_get_meta_data($fp_head)['wrapper_data'] as $h) if (stripos($h, 'text/html') !== false) $is_html = true; | |
| fclose($fp_head); | |
| } else { $is_html = false; } | |
| if (!$is_html || $action === 'download') { | |
| $info = parseProtectedFilename(basename($pfad), $pw_delimiter); | |
| if ($info['is_locked'] && !isset($_GET['share'])) { | |
| if (($_POST['upw'] ?? '') !== $info['password']) { | |
| $err = isset($_POST['upw']) ? '<h2 style="color:#d9534f;margin-top:0;">β Wrong Password</h2>' : '<h2 style="margin-top:0;">π Protected</h2>'; | |
| // FIX: Pfad fΓΌr den Back-Button berechnen (ΓΌbergeordneter Ordner) | |
| $parent_dir = dirname($pfad); | |
| if ($parent_dir === '/' || $parent_dir === '.') $parent_dir = ''; | |
| $back_url = "?file=" . urlencode($parent_dir); | |
| die(' | |
| <body style="font-family:sans-serif;background:#f4f6f9;display:flex;justify-content:center;align-items:center;height:100vh;"> | |
| <form method="post" style="background:#fff;padding:40px;border-radius:12px;text-align:center;box-shadow:0 10px 40px rgba(0,0,0,0.1);width:320px;"> | |
| '.$err.' | |
| <p>File: <strong>'.htmlspecialchars($info['clean_name']).'</strong></p> | |
| <input type="password" name="upw" placeholder="Password" autofocus required style="width:100%;padding:12px;margin-bottom:15px;border:1px solid #ddd;border-radius:6px;box-sizing:border-box;"> | |
| <div style="display:flex; gap:10px;"> | |
| <a href="'.$back_url.'" style="flex:1;padding:12px;background:#6c757d;color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:bold;text-decoration:none;font-size:0.85em;line-height:1.2;">Back to Folder</a> | |
| <button type="submit" style="flex:1;padding:12px;background:#007bff;color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:bold;">Unlock</button> | |
| </div> | |
| </form> | |
| </body>'); | |
| } | |
| } | |
| $req_h = $auth_header . (isset($_SERVER['HTTP_RANGE']) ? "\r\nRange: " . $_SERVER['HTTP_RANGE'] : ""); | |
| $fp = fopen($remote_url, 'rb', false, stream_context_create(["http" => ["method" => "GET", "header" => $req_h]])); | |
| foreach (stream_get_meta_data($fp)['wrapper_data'] as $h) { | |
| foreach (['Content-Type','Content-Length','Content-Range','Accept-Ranges','Etag','HTTP/1.1 206'] as $a) if (stripos($h, $a) !== false) header($h); | |
| } | |
| header('Content-Disposition: attachment; filename="' . ($info['is_locked'] ? $info['clean_name'] : basename($pfad)) . '"'); | |
| if (!isset($_SERVER['HTTP_RANGE'])) { $db->prepare("INSERT INTO downloads (filepath, count, last_dl) VALUES (?, 1, DATETIME('now')) ON CONFLICT(filepath) DO UPDATE SET count = count + 1, last_dl = DATETIME('now')")->execute([$pfad]); } | |
| if (ob_get_level()) ob_end_clean(); fpassthru($fp); fclose($fp); exit; | |
| } | |
| // Directory Listing | |
| $html_listing = file_get_contents($remote_url . '/', false, $context); | |
| $db_stats = $db->query("SELECT * FROM downloads")->fetchAll(PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC); | |
| $all_shares = $db->query("SELECT * FROM shares WHERE expiry > DATETIME('now')")->fetchAll(PDO::FETCH_ASSOC); | |
| $globalFiles = $db->query("SELECT COUNT(*) FROM downloads WHERE is_folder = 0")->fetchColumn(); | |
| $globalFolders = $db->query("SELECT COUNT(*) FROM downloads WHERE is_folder = 1")->fetchColumn(); | |
| $globalSize = $db->query("SELECT SUM(size_bytes) FROM downloads WHERE is_folder = 0")->fetchColumn(); | |
| $dom = new DOMDocument(); @$dom->loadHTML($html_listing); | |
| $folders = []; $files = []; | |
| foreach ($dom->getElementsByTagName('tr') as $row) { | |
| $tds = $row->getElementsByTagName('td'); if ($tds->length < 3) continue; | |
| $a = $tds->item(1)->getElementsByTagName('a')->item(0) ?: $tds->item(0)->getElementsByTagName('a')->item(0); | |
| if (!$a || in_array(trim($a->nodeValue), ['Parent Directory', '..'])) continue; | |
| $href = $a->getAttribute('href'); | |
| $full = '/' . trim($pfad . '/' . trim($href, '/'), '/'); | |
| $is_f = (substr($href, -1) == '/'); | |
| $info = parseProtectedFilename(trim($a->nodeValue), $pw_delimiter); | |
| $sStr = trim($tds->item(3)->nodeValue); $sizeB = floatval($sStr); | |
| if(stripos($sStr, 'K')) $sizeB *= 1024; if(stripos($sStr, 'M')) $sizeB *= 1048576; if(stripos($sStr, 'G')) $sizeB *= 1073741824; | |
| if (!$is_f) { $db->prepare("INSERT INTO downloads (filepath, size_bytes, is_folder) VALUES (?, ?, 0) ON CONFLICT(filepath) DO UPDATE SET size_bytes = ?")->execute([$full, $sizeB, $sizeB]); } | |
| else { $db->prepare("INSERT INTO downloads (filepath, is_folder) VALUES (?, 1) ON CONFLICT(filepath) DO NOTHING")->execute([$full]); } | |
| $s = $db_stats[$full] ?? null; | |
| if ($is_f) { | |
| $calc = $db->prepare("SELECT SUM(size_bytes) FROM downloads WHERE filepath LIKE ? AND is_folder = 0"); | |
| $calc->execute([$full . '/%']); | |
| $totalBytes = $calc->fetchColumn() ?: 0; | |
| $displaySize = formatBytes($totalBytes); $sortBytes = $totalBytes; | |
| } else { $displaySize = $sStr; $sortBytes = $sizeB; } | |
| $item = [ | |
| 'name' => $info['clean_name'], 'real' => $full, 'is_f' => $is_f, 'locked' => $info['is_locked'], | |
| 'size' => $displaySize, 'sort_bytes' => $sortBytes, 'count' => $s['count'] ?? 0, 'last' => $s['last_dl'] ?? '-', | |
| 'last_ts' => strtotime($s['last_dl'] ?? '0'), 'comment' => $s['comment'] ?? '', 'md5' => $s['md5'] ?? '', | |
| 'type' => $is_f ? 'Folder' : getFileType($info['clean_name']), 'prev_type' => (!$is_f && !$info['is_locked']) ? isPreviewable($info['clean_name']) : false | |
| ]; | |
| if ($is_f) $folders[] = $item; else $files[] = $item; | |
| } | |
| ?> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Storage Explorer Pro</title> | |
| <style> | |
| :root { --bg:#f4f7fa; --card:#fff; --text:#2d3436; --primary:#0984e3; --border:#dfe6e9; --hover:#f1f2f6; --footer-bg: #f8f9fa; --text-sub: #636e72; } | |
| [data-theme="dark"] { --bg:#1e272e; --card:#2f3640; --text:#f5f6fa; --border:#57606f; --hover:#353b48; --footer-bg: #2d3436; --text-sub: #a4b0be; } | |
| body { font-family:'Segoe UI',sans-serif; background:var(--bg); color:var(--text); margin:10px; transition:0.3s; } | |
| .container { max-width:98%; margin:0 auto; background:var(--card); padding:20px 20px 0 20px; border-radius:12px; box-shadow:0 10px 30px rgba(0,0,0,0.1); } | |
| .header { display:flex; justify-content:space-between; align-items:center; border-bottom:2px solid var(--primary); padding-bottom:15px; margin-bottom:20px; } | |
| .breadcrumbs { display:flex; flex-wrap:wrap; align-items:center; font-weight:bold; } | |
| .breadcrumbs a { color:var(--primary); text-decoration:none; margin-right:5px; } | |
| table { width:100%; border-collapse:collapse; } | |
| th { text-align:left; padding:12px; background:rgba(0,0,0,0.02); cursor:pointer; color:var(--primary); font-size:0.85em; text-transform:uppercase; border-bottom: 2px solid var(--border); } | |
| td { padding:10px 12px; border-bottom:1px solid var(--border); } | |
| tr:hover { background:var(--hover); } | |
| .btn { padding:5px 8px; border-radius:4px; border:1px solid var(--border); background:var(--card); color:var(--text); cursor:pointer; font-size:0.85em; } | |
| .btn:hover { border-color:var(--primary); color:var(--primary); } | |
| .hash-txt { font-family:monospace; font-size:0.8em; color:#e84393; background:rgba(232, 67, 147, 0.1); padding:4px 8px; border-radius:4px; cursor:pointer; } | |
| .modal { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:1000; backdrop-filter:blur(3px); } | |
| .modal-content { background:var(--card); width:90%; max-width:650px; margin:8vh auto; padding:25px; border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,0.4); max-height:80vh; overflow-y:auto; } | |
| input, textarea, select { width:100%; padding:10px; margin:5px 0; border:1px solid var(--border); border-radius:6px; background:var(--bg); color:var(--text); box-sizing:border-box; outline:none; } | |
| .lock-badge { background:#fee; color:#c00; padding:2px 6px; border-radius:3px; font-size:0.75em; font-weight:bold; } | |
| .stats-summary { margin: 0 -20px; padding: 15px 25px; background: var(--footer-bg); display: flex; justify-content: space-between; align-items: center; font-size: 0.85em; color: var(--text-sub); border-top: 1px solid var(--border); border-bottom-left-radius: 12px; border-bottom-right-radius: 12px; } | |
| @media (max-width:900px) { .hide-m { display:none; } .stats-summary { flex-direction: column; gap: 8px; } } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <div class="breadcrumbs"> | |
| <a href="?file=/">π </a> | |
| <?php $curr_bc = ''; foreach(array_filter(explode('/',$pfad)) as $p){ $curr_bc .= '/' . $p; echo '<span>/</span><a href="?file='.urlencode($curr_bc).'">'.htmlspecialchars($p).'</a>'; } ?> | |
| </div> | |
| <div style="display:flex; gap:10px;"> | |
| <input type="text" id="filter" onkeyup="doSearch()" placeholder="Filter..." style="width:200px; margin:0; padding:8px; border-radius:20px;"> | |
| <button class="btn" onclick="openShareManager()">π‘οΈ Shares</button> | |
| <button class="btn" onclick="toggleTheme()">π</button> | |
| </div> | |
| </div> | |
| <table id="fTable"> | |
| <thead> | |
| <tr> | |
| <th onclick="sort(0)" width="35%">Name β¬</th> | |
| <th width="120px">Tools</th> | |
| <th onclick="sort(2)" class="hide-m" width="10%">Type β¬</th> | |
| <th onclick="sort(3)" width="70px">DLs β¬</th> | |
| <th onclick="sort(4)" class="hide-m" width="15%">Last DL β¬</th> | |
| <th onclick="sort(5)" width="120px">Size β¬</th> | |
| <th onclick="sort(6)" class="hide-m" width="150px">MD5 β¬</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <?php if(!empty($pfad)): | |
| $parent_path = dirname($pfad); | |
| if ($parent_path === '/' || $parent_path === '.') $parent_path = ''; | |
| ?> | |
| <tr data-s0="" data-s3="-1" data-s5="-1"><td colspan="7"><a href="?file=<?php echo urlencode($parent_path); ?>" style="text-decoration:none; font-weight:bold;">β¬οΈ .. Parent Directory</a></td></tr> | |
| <?php endif; ?> | |
| <?php foreach(array_merge($folders, $files) as $i): | |
| $searchData = strtolower($i['name'] . ' ' . $i['type'] . ' ' . $i['comment']); | |
| ?> | |
| <tr class="row" data-search="<?php echo htmlspecialchars($searchData); ?>" data-s0="<?php echo strtolower($i['name']); ?>" data-s2="<?php echo strtolower($i['type']); ?>" data-s3="<?php echo $i['count']; ?>" data-s4="<?php echo $i['last_ts']; ?>" data-s5="<?php echo $i['sort_bytes']; ?>" data-s6="<?php echo strtolower($i['md5']); ?>"> | |
| <td> | |
| <div style="display:flex; align-items:center;"> | |
| <a href="?file=<?php echo urlencode($i['real']); ?>" style="text-decoration:none; color:inherit; font-weight:500;"><?php echo $i['is_f']?'π':'π'; ?> <?php echo htmlspecialchars($i['name']); ?></a> | |
| <?php if($i['locked']) echo '<span class="lock-badge">Locked</span>'; ?> | |
| <?php if(!empty($i['comment'])): ?><span style="cursor:pointer; margin-left:8px;" onclick="showNote('<?php echo htmlspecialchars($i['comment'], ENT_QUOTES); ?>')">π</span><?php endif; ?> | |
| </div> | |
| </td> | |
| <td> | |
| <div style="display:flex; gap:5px;"> | |
| <?php if($i['prev_type']): ?><button class="btn" onclick="preview('<?php echo htmlspecialchars($i['name']); ?>','<?php echo urlencode($i['real']); ?>','<?php echo $i['prev_type']; ?>')">ποΈ</button><?php endif; ?> | |
| <button class="btn" onclick="openShare('<?php echo urlencode($i['real']); ?>')">π</button> | |
| <button class="btn" onclick="editNote('<?php echo urlencode($i['real']); ?>','<?php echo htmlspecialchars($i['name'], ENT_QUOTES); ?>','<?php echo addslashes($i['comment']); ?>')">βοΈ</button> | |
| </div> | |
| </td> | |
| <td class="hide-m" style="opacity:0.6; font-size:0.85em;"><?php echo $i['type']; ?></td> | |
| <td><span style="opacity:0.6"><?php echo $i['count']; ?></span></td> | |
| <td class="hide-m" style="font-size:0.8em; opacity:0.6;"><?php echo $i['last']; ?></td> | |
| <td style="font-weight:<?php echo $i['is_f']?'bold':'normal'; ?>; color:<?php echo $i['is_f']?'var(--primary)':'inherit'; ?>;"><?php echo $i['size']; ?></td> | |
| <td class="hide-m"><?php if(!$i['is_f'] && !$i['locked']): ?><?php if($i['md5']): ?><span class="hash-txt" onclick="copy('<?php echo $i['md5']; ?>',this)"><?php echo $i['md5']; ?></span><?php else: ?><button class="btn" onclick="calcMD5('<?php echo urlencode($i['real']); ?>',this)">MD5</button><?php endif; ?><?php endif; ?></td> | |
| </tr> | |
| <?php endforeach; ?> | |
| </tbody> | |
| </table> | |
| <div class="stats-summary"> | |
| <div>π <strong><?php echo $globalFolders; ?></strong> Folders | π <strong><?php echo $globalFiles; ?></strong> Files</div> | |
| <div>πΎ Known Usage: <strong><?php echo formatBytes($globalSize); ?></strong></div> | |
| </div> | |
| </div> | |
| <div id="modal" class="modal" onclick="if(event.target==this)this.style.display='none'"> | |
| <div class="modal-content" id="mBox"></div> | |
| </div> | |
| <script> | |
| if(localStorage.getItem('theme')==='dark') document.body.setAttribute('data-theme','dark'); | |
| function toggleTheme(){ var b=document.body; if(b.getAttribute('data-theme')==='dark'){ b.removeAttribute('data-theme'); localStorage.setItem('theme','light'); } else { b.setAttribute('data-theme','dark'); localStorage.setItem('theme','dark'); } } | |
| function doSearch(){ var v=document.getElementById('filter').value.toLowerCase(); var rs=document.getElementsByClassName('row'); for(var i=0; i<rs.length; i++){ var s = rs[i].getAttribute('data-search'); if(s) rs[i].style.display = s.indexOf(v) > -1 ? '' : 'none'; } } | |
| function copy(h, e){ navigator.clipboard.writeText(h).then(() => { var o=e.innerText; e.innerText='β '; setTimeout(()=>{e.innerText=o},1500); }); } | |
| let lastIdx = -1, dir = 1; | |
| function sort(n) { | |
| const tb = document.querySelector("#fTable tbody"); | |
| const rows = Array.from(tb.rows).filter(r => r.getAttribute('data-s0') !== ""); | |
| if(lastIdx === n) dir *= -1; else dir = 1; | |
| lastIdx = n; | |
| rows.sort((a, b) => { | |
| let v1 = a.getAttribute('data-s'+n), v2 = b.getAttribute('data-s'+n); | |
| if(!isNaN(v1) && v1 !== "" && !isNaN(v2) && v2 !== "") return (parseFloat(v1) - parseFloat(v2)) * dir; | |
| return (v1 || "").localeCompare(v2 || "") * dir; | |
| }); | |
| rows.forEach(r => tb.appendChild(r)); | |
| } | |
| function preview(name, path, type) { | |
| document.getElementById('modal').style.display='block'; | |
| let m = document.getElementById('mBox'); | |
| m.innerHTML = `<h3>Preview: ${name}</h3><div id="prevC" style="text-align:center;">β³ Loading...</div>`; | |
| if(type === 'img') document.getElementById('prevC').innerHTML = `<img src="?action=preview&file=${path}" style="max-width:100%; border-radius:8px;">`; | |
| else if(type === 'vid') document.getElementById('prevC').innerHTML = `<video controls autoplay style="width:100%; border-radius:8px;"><source src="?action=preview&file=${path}" type="video/mp4"></video>`; | |
| else fetch('?action=preview&file='+path).then(r=>r.text()).then(d=>{ document.getElementById('prevC').innerHTML = `<pre style="text-align:left; background:var(--bg); padding:15px; border-radius:8px; overflow:auto; max-height:50vh;">${d}</pre>`; }); | |
| } | |
| function openShareManager() { | |
| document.getElementById('modal').style.display='block'; | |
| let html = `<h3>Active Shares</h3><table style="width:100%; font-size:0.85em; border-collapse:collapse;"><tr><th>File</th><th>Actions</th></tr>`; | |
| <?php foreach($all_shares as $s): ?> | |
| html += `<tr><td><strong><a href="<?php echo $base_share_url . "?share=" . $s['token']; ?>" target="_blank"><?php echo basename($s['filepath']); ?></a></strong><br><small><?php echo $s['expiry']; ?></small></td><td><button class="btn" onclick="copy('<?php echo $base_share_url . "?share=" . $s['token']; ?>', this)">π Link</button> <button class="btn" onclick="deleteShare('<?php echo $s['token']; ?>')">ποΈ</button></td></tr>`; | |
| <?php endforeach; ?> | |
| html += `</table><br><input type="password" id="mgrPw" placeholder="Admin Pw"><button class="btn" style="width:100%" onclick="document.getElementById('modal').style.display='none'">Close</button>`; | |
| document.getElementById('mBox').innerHTML = html; | |
| } | |
| function deleteShare(t){ const pw=document.getElementById('mgrPw').value; fetch('?action=delete_share',{method:'POST',body:new URLSearchParams({admin_pw:pw,token:t})}).then(r=>r.text()).then(res=>{if(res=='OK')location.reload();else alert(res);}); } | |
| function calcMD5(p, b){ b.innerText='...'; fetch('?action=md5&file='+p).then(r=>r.text()).then(()=>{location.reload();}); } | |
| function openShare(p){ document.getElementById('modal').style.display='block'; document.getElementById('mBox').innerHTML = `<h3>Share</h3><select id="dur"><option value="1">1h</option><option value="24">24h</option><option value="168">7d</option></select><input type="password" id="apw" placeholder="Admin Pw"><button class="btn" style="background:#0984e3;color:#fff;width:100%" onclick="genL('${p}')">Link</button><div id="res" style="margin-top:10px;word-break:break-all;color:var(--primary);cursor:pointer" onclick="copy(this.innerText,this)"></div>`; } | |
| function genL(p){ fetch('?action=create_share',{method:'POST',body:new URLSearchParams({admin_pw:document.getElementById('apw').value,target:decodeURIComponent(p),duration:document.getElementById('dur').value})}).then(r=>r.text()).then(res=>{document.getElementById('res').innerText=res;}); } | |
| function editNote(p, n, c){ document.getElementById('modal').style.display='block'; document.getElementById('mBox').innerHTML = `<h3>Note</h3><p>${n}</p><textarea id="ntt" style="height:100px;">${c}</textarea><input type="password" id="apw" placeholder="Admin Pw"><button class="btn" style="background:#0984e3;color:#fff;width:100%" onclick="saveN('${p}')">Save</button>`; } | |
| function saveN(p){ fetch('?action=save_comment', {method:'POST', body:new URLSearchParams({admin_pw:document.getElementById('apw').value, target:decodeURIComponent(p), comment:document.getElementById('ntt').value})}).then(r=>r.text()).then(res=>{if(res=='OK')location.reload();else alert(res);}); } | |
| function showNote(c){ document.getElementById('modal').style.display='block'; document.getElementById('mBox').innerHTML=`<h3>Note</h3><div style="background:var(--bg);padding:20px;border-radius:8px;white-space:pre-wrap;">${c}</div>`; } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment