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>
<div class="page">
<div class="header">
<div class="header" onclick="window.location.href = '/'">
void.cat
</div>
<div id="page-upload">
@ -109,12 +109,44 @@
</div>
</div>
<div id="page-stats">
</div>
<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 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"
data-ad-slot="9187315106" data-ad-format="auto" data-full-width-responsive="true"></ins>
<script>

View File

@ -18,6 +18,10 @@ html, body {
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 {
width: $page-width;
margin-left: auto;
@ -32,7 +36,7 @@ html, body {
#page-view {
display:none;
padding: $page-padding;
padding: 10px 10px 0px 10px;
overflow: hidden;
}
@ -54,7 +58,7 @@ html, body {
#page-upload {
display: none;
padding: $page-padding;
padding: 10px 10px 0px 10px;
overflow: hidden;
}
@ -141,10 +145,61 @@ html, body {
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 {
display: grid;
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 {

View File

@ -2,13 +2,12 @@
* @constant {Object}
*/
const App = {
get Version() { return AppVersion },
Elements: {
get Dropzone() { return $('#dropzone') },
get Uploads() { return $('#uploads') },
get PageView() { return $('#page-view') },
get PageUpload() { return $('#page-upload') }
get PageUpload() { return $('#page-upload') },
get PageFaq() { return $('#page-faq') }
},
Templates: {
@ -35,15 +34,34 @@ const App = {
/**
* Sets up the page
*/
Init: function () {
Init: async function () {
if (location.hash !== "") {
App.Elements.PageUpload.style.display = "none";
App.Elements.PageView.style.display = "block";
new ViewManager();
if (location.hash == "#faq") {
let faq_headers = document.querySelectorAll('#page-faq .faq-header');
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 {
App.Elements.PageUpload.style.display = "block";
App.Elements.PageView.style.display = "none";
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);
},
GetSiteInfo: async function (id) {
return await Api.DoRequest({
cmd: 'site_info'
});
},
GetFileInfo: async function (id) {
return await Api.DoRequest({
cmd: 'file_info',

View File

@ -9,7 +9,7 @@
class Api implements RequestHandler {
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);
}
@ -23,6 +23,14 @@
$fs = new FileStore(Config::$Instance->upload_folder);
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": {
$rsp->ok = true;
$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();
$abuse->CheckDownload($id);
$tracking->TrackDownload($id);
$tracking->TrackDownload($this->Fs, $id);
//allow embeded header from preflight check
if($_SERVER["REQUEST_METHOD"] === "OPTIONS"){

View File

@ -1,9 +1,11 @@
<?php
class FileStore {
private $UploadFolder;
private $DocumentRoot;
public function __construct($path) {
$this->UploadFolder = $path;
public function __construct($dir, $root = null) {
$this->UploadFolder = $dir;
$this->DocumentRoot = $root === null ? $_SERVER["DOCUMENT_ROOT"] : $root;
}
public function SetFileStats($info) : void {
@ -27,12 +29,16 @@
);
}
public function GetUploadDirAbsolute() : string {
return "$this->DocumentRoot/$this->UploadFolder";
}
public function GetRelativeFilePath($id) : string {
return "$this->UploadFolder/$id";
}
public function GetAbsoluteFilePath($id) : string {
return "$_SERVER[DOCUMENT_ROOT]/$this->UploadFolder/$id";
return "$this->GetUploadDirAbsolute()/$this->UploadFolder/$id";
}
public function GetFileInfo($id) : ?FileInfo {
@ -67,5 +73,13 @@
fclose($fout);
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
define('REDIS_CONFIG', 'redis-host');
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';
});
include_once("init.php");
//Startup
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 {
if(isset($_SERVER["HTTP_X_FILE_ID"])) {
$id = $_SERVER["HTTP_X_FILE_ID"];
$fs = new FileStore(Config::$Instance->upload_folder);
@ -14,7 +13,7 @@
//resolve the hostnames to ips
$redis = StaticRedis::$Instance;
$sync_hosts = $redis->sMembers(REDIS_PREFIX . 'sync-hosts');
$sync_hosts_ips = array();
foreach($sync_hosts as $host) {
$sync_hosts_ips[] = gethostbyname($host);
@ -23,16 +22,19 @@
//check the ip of the host submitting the file for sync
if(in_array(USER_IP, $sync_hosts_ips)) {
$fs->StoreFile("php://input", $id);
http_response_code(201);
} else {
http_response_code(401);
}
} else {
http_response_code(200);
}
} else {
http_response_code(400);
}
}
public static function SyncFile($id, $filename, $host) : void {
public static function SyncFile($id, $filename, $host) : int {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://$host/sync");
curl_setopt($ch, CURLOPT_POST, 1);
@ -42,7 +44,10 @@
"X-File-Id: " . $id
));
curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close ($ch);
return intval($status);
}
}

View File

@ -1,27 +1,45 @@
<?php
class Tracking {
public function TrackDownload($id) : void {
public function TrackDownload($fs, $id) : void {
$redis = StaticRedis::$Instance;
$file_key = REDIS_PREFIX . $id;
$file_size = $fs->GetFileSize($id);
if(!$this->IsRangeRequest()) {
$redis->hIncrBy($file_key, 'views', 1);
$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();
}
function IsRangeRequest() : bool {
function GetRequestRange($len) : ?object {
if(isset($_SERVER['HTTP_RANGE'])) {
$rby = explode('=', $_SERVER['HTTP_RANGE']);
$rbv = explode('-', $rby[1]);
if($rbv[0] != '0'){
return True;
}
return (object)array(
"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 {

View File

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