add stats and faq

This commit is contained in:
Kieran
2018-11-22 17:49:16 +08:00
parent e964422096
commit b3c2f72146
16 changed files with 272 additions and 43 deletions

0
examples/embed/README.md Normal file
View File

View File

@@ -82,7 +82,7 @@
<body> <body>
<div class="page"> <div class="page">
<div class="header"> <div class="header" onclick="window.location.href = '/'">
void.cat void.cat
</div> </div>
<div id="page-upload"> <div id="page-upload">
@@ -109,12 +109,44 @@
</div> </div>
</div> </div>
<div id="page-stats"> <div id="page-stats">
</div> </div>
<div id="page-faq"> <div id="page-faq">
<div class="faq-section">
<div class="faq-header">
Are there any restrictions on what can be uploaded
</div>
<div class="faq-content">
No.
</div>
</div>
<div class="faq-section">
<div class="faq-header">
How can I embed content in my site
</div>
<div class="faq-content">
I have created a service worker which you can host on your site which will decrypt the links automatically, find
more info <a href="https://github.com/v0l/void.cat/blob/v3-b2b/examples/embed" target="_blank">here on github.</a>
</div>
</div>
<div class="faq-section">
<div class="faq-header">
I have more questions
</div>
<div class="faq-content">
Feel free to join <a href="https://discord.gg/8BkxTGs" target="_blank">Discord</a> or send a mail to <a href="mailto:admin@void.cat">admin@void.cat</a>
</div>
</div>
</div> </div>
<div id="footer-stats">
<div><b>Files:</b><span>0</span></div>
<div><b>Total Size:</b><span>0 B</span></div>
<div><b>24Hr Transfer:</b><span>0 B</span></div>
</div>
<div id="footer">
<a href="javascript:(function() { window.location.href='/#faq'; App.Init(); })()">FAQ</a> | <a href="http://rv6omygg3ksi3dys.onion/" target="_blank">Tor</a>
<br>
</div>
<ins class="adsbygoogle" style="display:block; margin-left: auto; margin-right: auto;" data-ad-client="ca-pub-3289062345896209" <ins class="adsbygoogle" style="display:block; margin-left: auto; margin-right: auto;" data-ad-client="ca-pub-3289062345896209"
data-ad-slot="9187315106" data-ad-format="auto" data-full-width-responsive="true"></ins> data-ad-slot="9187315106" data-ad-format="auto" data-full-width-responsive="true"></ins>
<script> <script>

View File

@@ -18,6 +18,10 @@ html, body {
background-color: #444; background-color: #444;
} }
a { text-decoration: none; color: rgb(0, 9, 94); font-weight: bold; }
a:visited { text-decoration: none; }
a:hover { text-decoration: underline; }
.page { .page {
width: $page-width; width: $page-width;
margin-left: auto; margin-left: auto;
@@ -32,7 +36,7 @@ html, body {
#page-view { #page-view {
display:none; display:none;
padding: $page-padding; padding: 10px 10px 0px 10px;
overflow: hidden; overflow: hidden;
} }
@@ -54,7 +58,7 @@ html, body {
#page-upload { #page-upload {
display: none; display: none;
padding: $page-padding; padding: 10px 10px 0px 10px;
overflow: hidden; overflow: hidden;
} }
@@ -141,10 +145,61 @@ html, body {
background-color: #790e00; background-color: #790e00;
} }
#footer {
text-align: center;
text-transform: uppercase;
font-size: 14px;
}
#footer-stats {
display: grid;
grid-template-columns: 33.3% 33.3% 33.3%;
text-align: center;
border: 1px solid #aaa;
margin: 0px 10px 10px 10px;
padding: 5px;
}
#page-stats {
display: none;
}
#page-faq {
display: none;
}
#page-faq .faq-section {
margin: 10px;
}
#page-faq .faq-section .faq-header {
font-weight: bold;
margin-top: 10px;
padding: 10px;
overflow: hidden;
border: 1px solid #aaa;
background-color: #eee;
}
#page-faq .faq-section .faq-content {
display: none;
padding: 10px;
overflow: hidden;
border: 1px solid #eee;
background-color: #fff;
}
.show {
display: block !important;
}
#page-view .file-info { #page-view .file-info {
display: grid; display: grid;
grid-template-columns: 25% 25% 25% 25%; grid-template-columns: 25% 25% 25% 25%;
line-height: 30px; text-align: center;
border: 1px solid #aaa;
margin: 10px 0px 0px 0px;
padding: 5px;
} }
.header { .header {

View File

@@ -2,13 +2,12 @@
* @constant {Object} * @constant {Object}
*/ */
const App = { const App = {
get Version() { return AppVersion },
Elements: { Elements: {
get Dropzone() { return $('#dropzone') }, get Dropzone() { return $('#dropzone') },
get Uploads() { return $('#uploads') }, get Uploads() { return $('#uploads') },
get PageView() { return $('#page-view') }, get PageView() { return $('#page-view') },
get PageUpload() { return $('#page-upload') } get PageUpload() { return $('#page-upload') },
get PageFaq() { return $('#page-faq') }
}, },
Templates: { Templates: {
@@ -35,15 +34,34 @@ const App = {
/** /**
* Sets up the page * Sets up the page
*/ */
Init: function () { Init: async function () {
if (location.hash !== "") { if (location.hash !== "") {
App.Elements.PageUpload.style.display = "none"; if (location.hash == "#faq") {
App.Elements.PageView.style.display = "block"; let faq_headers = document.querySelectorAll('#page-faq .faq-header');
new ViewManager(); for (let x = 0; x < faq_headers.length; x++) {
faq_headers[x].addEventListener('click', function() {
this.nextElementSibling.classList.toggle("show");
}.bind(faq_headers[x]));
}
App.Elements.PageUpload.style.display = "none";
App.Elements.PageFaq.style.display = "block";
} else {
App.Elements.PageUpload.style.display = "none";
App.Elements.PageView.style.display = "block";
new ViewManager();
}
} else { } else {
App.Elements.PageUpload.style.display = "block"; App.Elements.PageUpload.style.display = "block";
App.Elements.PageView.style.display = "none"; App.Elements.PageView.style.display = "none";
new DropzoneManager(App.Elements.Dropzone); new DropzoneManager(App.Elements.Dropzone);
let stats = await Api.GetSiteInfo();
if(stats.ok){
let elms = document.querySelectorAll("#footer-stats div span");
elms[0].textContent = stats.data.basic_stats.Files;
elms[1].textContent = Utils.FormatBytes(stats.data.basic_stats.Size, 2);
elms[2].textContent = Utils.FormatBytes(stats.data.basic_stats.Transfer_24h, 2);
}
} }
} }
}; };

View File

@@ -81,6 +81,12 @@ const Api = {
return JSON.parse((await JsonXHR('POST', '/api', req)).response); return JSON.parse((await JsonXHR('POST', '/api', req)).response);
}, },
GetSiteInfo: async function (id) {
return await Api.DoRequest({
cmd: 'site_info'
});
},
GetFileInfo: async function (id) { GetFileInfo: async function (id) {
return await Api.DoRequest({ return await Api.DoRequest({
cmd: 'file_info', cmd: 'file_info',

View File

@@ -9,7 +9,7 @@
class Api implements RequestHandler { class Api implements RequestHandler {
public function __construct(){ public function __construct(){
Config::LoadConfig(array('upload_folder', 'recaptcha_site_key', 'recaptcha_secret')); Config::LoadConfig(array('max_upload_size', 'upload_folder', 'recaptcha_site_key', 'recaptcha_secret'));
ini_set('enable_post_data_reading', 0); ini_set('enable_post_data_reading', 0);
} }
@@ -23,6 +23,14 @@
$fs = new FileStore(Config::$Instance->upload_folder); $fs = new FileStore(Config::$Instance->upload_folder);
switch($cmd->cmd){ switch($cmd->cmd){
case "site_info": {
$rsp->ok = true;
$rsp->data = array(
"max_upload_size" => Config::$Instance->max_upload_size,
"basic_stats" => Stats::Get()
);
break;
}
case "file_info": { case "file_info": {
$rsp->ok = true; $rsp->ok = true;
$rsp->data = $fs->GetFileInfo($cmd->id); $rsp->data = $fs->GetFileInfo($cmd->id);

13
src/php/cron.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
include_once("init.php");
StaticRedis::Connect();
Config::LoadConfig(array("upload_folder"));
$fs = new FileStore(Config::$Instance->upload_folder, "/usr/local/nginx/html");
echo "Loading stats for: " . $fs->GetUploadDirAbsolute() . "\n";
//echo "\n\t" . implode("\n\t", $fs->ListFiles()) . "\n";
Stats::Collect($fs);
?>

View File

@@ -29,7 +29,7 @@
$tracking = new Tracking(); $tracking = new Tracking();
$abuse->CheckDownload($id); $abuse->CheckDownload($id);
$tracking->TrackDownload($id); $tracking->TrackDownload($this->Fs, $id);
//allow embeded header from preflight check //allow embeded header from preflight check
if($_SERVER["REQUEST_METHOD"] === "OPTIONS"){ if($_SERVER["REQUEST_METHOD"] === "OPTIONS"){

View File

@@ -1,9 +1,11 @@
<?php <?php
class FileStore { class FileStore {
private $UploadFolder; private $UploadFolder;
private $DocumentRoot;
public function __construct($path) { public function __construct($dir, $root = null) {
$this->UploadFolder = $path; $this->UploadFolder = $dir;
$this->DocumentRoot = $root === null ? $_SERVER["DOCUMENT_ROOT"] : $root;
} }
public function SetFileStats($info) : void { public function SetFileStats($info) : void {
@@ -27,12 +29,16 @@
); );
} }
public function GetUploadDirAbsolute() : string {
return "$this->DocumentRoot/$this->UploadFolder";
}
public function GetRelativeFilePath($id) : string { public function GetRelativeFilePath($id) : string {
return "$this->UploadFolder/$id"; return "$this->UploadFolder/$id";
} }
public function GetAbsoluteFilePath($id) : string { public function GetAbsoluteFilePath($id) : string {
return "$_SERVER[DOCUMENT_ROOT]/$this->UploadFolder/$id"; return "$this->GetUploadDirAbsolute()/$this->UploadFolder/$id";
} }
public function GetFileInfo($id) : ?FileInfo { public function GetFileInfo($id) : ?FileInfo {
@@ -67,5 +73,13 @@
fclose($fout); fclose($fout);
fclose($input); fclose($input);
} }
public function GetFileSize($id) : int {
return filesize($this->GetAbsoluteFilePath($id));
}
public function ListFiles() : array {
return glob($this->GetUploadDirAbsolute() . "/*");
}
} }
?> ?>

View File

@@ -1,15 +1,5 @@
<?php <?php
define('REDIS_CONFIG', 'redis-host'); include_once("init.php");
define('REDIS_PREFIX', 'vc:');
define('USER_IP', isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER['REMOTE_ADDR']);
if(!isset($_COOKIE["VC:UID"])) {
setcookie("VC:UID", uniqid());
}
spl_autoload_register(function ($class_name) {
include dirname(__FILE__) . '/' . strtolower($class_name) . '.php';
});
//Startup //Startup
if(StaticRedis::Connect() == True) { if(StaticRedis::Connect() == True) {

13
src/php/init.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
define('REDIS_CONFIG', 'redis-host');
define('REDIS_PREFIX', 'vc:');
define('USER_IP', isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : "127.0.0.1"));
if(!isset($_COOKIE["VC:UID"])) {
setcookie("VC:UID", uniqid());
}
spl_autoload_register(function ($class_name) {
include dirname(__FILE__) . '/' . strtolower($class_name) . '.php';
});
?>

53
src/php/stats.php Normal file
View File

@@ -0,0 +1,53 @@
<?php
class Stats {
public $Files;
public $Size;
public $Transfer_24h;
private static $TransferStatsKey = REDIS_PREFIX . "stats-transfer-all";
private static $GeneralStatsKey = REDIS_PREFIX . "stats-general";
public static function Get() : Stats {
$redis = StaticRedis::$Instance;
//calculate 24hr transfer stats
$tx_24h_array = $redis->zRange(self::$TransferStatsKey, 0, 24, true); //stats are 1hr interval
$tx_24h = 0;
foreach($tx_24h_array as $tx_key => $tx_bytes) {
$tx_24h += $tx_bytes;
}
//get general stats
$general = (object)$redis->hMGet(self::$GeneralStatsKey, array("files", "size"));
$ret = new Stats();
$ret->Transfer_24h = $tx_24h;
$ret->Files = intval($general->files !== False ? $general->files : 0);
$ret->Size = intval($general->size !== False ? $general->size : 0);
return $ret;
}
public static function TrackTransfer($id, $size) : void {
$redis = StaticRedis::$Instance;
$stat_member = date("YmdH");
$redis->zIncrBy(self::$TransferStatsKey, $size, $stat_member);
}
public static function Collect($fs) : void {
$redis = StaticRedis::$Instance;
$files = $fs->ListFiles();
$total_size = 0;
foreach($files as $file) {
$total_size += filesize($file);
}
$redis->hMSet(self::$GeneralStatsKey, array(
"files" => count($files),
"size" => $total_size
));
}
}
?>

View File

@@ -6,7 +6,6 @@
} }
public function HandleRequest() : void { public function HandleRequest() : void {
if(isset($_SERVER["HTTP_X_FILE_ID"])) { if(isset($_SERVER["HTTP_X_FILE_ID"])) {
$id = $_SERVER["HTTP_X_FILE_ID"]; $id = $_SERVER["HTTP_X_FILE_ID"];
$fs = new FileStore(Config::$Instance->upload_folder); $fs = new FileStore(Config::$Instance->upload_folder);
@@ -14,7 +13,7 @@
//resolve the hostnames to ips //resolve the hostnames to ips
$redis = StaticRedis::$Instance; $redis = StaticRedis::$Instance;
$sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts'); $sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts');
$sync_hosts_ips = array(); $sync_hosts_ips = array();
foreach($sync_hosts as $host) { foreach($sync_hosts as $host) {
$sync_hosts_ips[] = gethostbyname($host); $sync_hosts_ips[] = gethostbyname($host);
@@ -23,16 +22,19 @@
//check the ip of the host submitting the file for sync //check the ip of the host submitting the file for sync
if(in_array(USER_IP, $sync_hosts_ips)) { if(in_array(USER_IP, $sync_hosts_ips)) {
$fs->StoreFile("php://input", $id); $fs->StoreFile("php://input", $id);
http_response_code(201);
} else { } else {
http_response_code(401); http_response_code(401);
} }
} else {
http_response_code(200);
} }
} else { } else {
http_response_code(400); http_response_code(400);
} }
} }
public static function SyncFile($id, $filename, $host) : void { public static function SyncFile($id, $filename, $host) : int {
$ch = curl_init(); $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://$host/sync"); curl_setopt($ch, CURLOPT_URL, "https://$host/sync");
curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POST, 1);
@@ -42,7 +44,10 @@
"X-File-Id: " . $id "X-File-Id: " . $id
)); ));
curl_exec($ch); curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close ($ch); curl_close ($ch);
return intval($status);
} }
} }

View File

@@ -1,27 +1,45 @@
<?php <?php
class Tracking { class Tracking {
public function TrackDownload($id) : void { public function TrackDownload($fs, $id) : void {
$redis = StaticRedis::$Instance; $redis = StaticRedis::$Instance;
$file_key = REDIS_PREFIX . $id; $file_key = REDIS_PREFIX . $id;
$file_size = $fs->GetFileSize($id);
if(!$this->IsRangeRequest()) { if(!$this->IsRangeRequest()) {
$redis->hIncrBy($file_key, 'views', 1); $redis->hIncrBy($file_key, 'views', 1);
$redis->hSet($file_key, 'lastview', time()); $redis->hSet($file_key, 'lastview', time());
Stats::TrackTransfer($id, $file_size);
} else {
$range = $this->GetRequestRange($file_size);
Stats::TrackTransfer($id, $range->end - $range->start);
} }
$this->SendMatomoEvent(); $this->SendMatomoEvent();
} }
function IsRangeRequest() : bool { function GetRequestRange($len) : ?object {
if(isset($_SERVER['HTTP_RANGE'])) { if(isset($_SERVER['HTTP_RANGE'])) {
$rby = explode('=', $_SERVER['HTTP_RANGE']); $rby = explode('=', $_SERVER['HTTP_RANGE']);
$rbv = explode('-', $rby[1]); $rbv = explode('-', $rby[1]);
if($rbv[0] != '0'){ return (object)array(
return True; "start" => intval($rbv[0]),
} "end" => intval($rbv[1] == "" ? $len : $rbv[1])
);
} }
return False; return null;
}
function IsRangeRequest() : bool {
$range = $this->GetRequestRange(0);
if($range !== null){
if($range->start != 0){
return true;
}
}
return false;
} }
function SendMatomoEvent() : void { function SendMatomoEvent() : void {

View File

@@ -3,6 +3,7 @@
public $status = 0; public $status = 0;
public $msg; public $msg;
public $id; public $id;
public $sync;
} }
class Upload implements RequestHandler { class Upload implements RequestHandler {
@@ -39,8 +40,7 @@
$id = $this->SaveUpload($bf); $id = $this->SaveUpload($bf);
//sync to other servers //sync to other servers
$this->SyncFileUpload($id); $rsp->sync = $this->SyncFileUpload($id);
$rsp->status = 200; $rsp->status = 200;
$rsp->id = $id; $rsp->id = $id;
} else { } else {
@@ -52,16 +52,18 @@
echo json_encode($rsp); echo json_encode($rsp);
} }
function SyncFileUpload($id) : void { function SyncFileUpload($id) : array {
$redis = StaticRedis::$Instance; $redis = StaticRedis::$Instance;
$sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts'); $sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts');
if($sync_hosts !== False) { if($sync_hosts !== False) {
$fs = new FileStore(Config::$Instance->upload_folder); $fs = new FileStore(Config::$Instance->upload_folder);
$status_codes = [];
foreach($sync_hosts as $host) { foreach($sync_hosts as $host) {
Sync::SyncFile($id, $fs->GetAbsoluteFilePath($id), $host); $status_codes[] = Sync::SyncFile($id, $fs->GetAbsoluteFilePath($id), $host);
} }
return $status_codes;
/* /*
$sync_threads = array(); $sync_threads = array();
foreach($sync_hosts as $host) { foreach($sync_hosts as $host) {
@@ -77,6 +79,8 @@
$thread->join(); $thread->join();
}*/ }*/
} }
return array();
} }
function SaveUpload($bf) : string { function SaveUpload($bf) : string {