Skip to content

Instantly share code, notes, and snippets.

@syncip
Created January 25, 2026 12:04
Show Gist options
  • Select an option

  • Save syncip/fd70ca69e685afdc572f83f0042c9612 to your computer and use it in GitHub Desktop.

Select an option

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.
<?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