devshort

private self-hosted shortlink service
git clone https://git.clttr.info/devshort.git
Log (Feed) | Files | Refs (Tags) | README | LICENSE

commit acdb42ff2b06b9e642851cdb06f35d2859fd5d46
parent 69961f87c37514d5954d212f20f2c50f3e767103
Author: Florian <flokX@users.noreply.github.com>
Date:   Sat, 26 Jan 2019 11:38:16 +0100

Merge pull request #2 from flokX/new-admin-panel

New admin panel
Diffstat:
MREADME.md | 4++--
Rsecure/config.json -> admin/config.json | 0
Aadmin/index.php | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aadmin/main.js | 28++++++++++++++++++++++++++++
Aadmin/stats.json | 1+
Massets/main.css | 7+++++++
Massets/main.min.css | 3+--
Minstaller.php | 6+++---
Mredirect.php | 12++++++------
Dsecure/main.js | 61-------------------------------------------------------------
Dsecure/main.min.js | 2--
Dsecure/stats.json | 2--
12 files changed, 123 insertions(+), 78 deletions(-)

diff --git a/README.md b/README.md @@ -20,13 +20,13 @@ What is a URL shortener? Visit our [wiki article](https://github.com/flokX/devSh 1. With devShort you will never again have problems regarding legal questions because we only track the number of requests the shortlinks gain. 2. It's quite easy to install, upgrade and maintain because there are only 16 files with a size of 1,04 MB only to upload. -3. The appearence of the public pages can be changed easily with the `config.json` file. +3. The appearence of the public pages can be changed easily with the `admin/config.json` file. ## 1-2-3 Click installation 1. Copy the files into the directory -2. Insert a admin password in the `config.json` +2. Insert a admin password in the `admin/config.json` 3. Run the `installer.php` That's it! If you want to know what devShort can do visit our [wiki](https://github.com/flokX/devShort/wiki). diff --git a/secure/config.json b/admin/config.json diff --git a/admin/index.php b/admin/index.php @@ -0,0 +1,75 @@ +<?php + +// All relevant changes can be made in the data file. Please read the docs: https://github.com/flokX/devShort/wiki + +$name = htmlspecialchars($_POST["delete"]); + +$base_path = __DIR__; +$data = json_decode(file_get_contents($base_path . DIRECTORY_SEPARATOR . "config.json"), true); + +if (isset($_POST["delete"])) { + $filename = $base_path . DIRECTORY_SEPARATOR . "config.json"; + $content = json_decode(file_get_contents($filename), true); + unset($content["shortlinks"][$name]); + file_put_contents($filename, json_encode($content, JSON_PRETTY_PRINT)); + + $filename = $base_path . DIRECTORY_SEPARATOR . "stats.json"; + $content = json_decode(file_get_contents($filename), true); + unset($content[$name]); + file_put_contents($filename, json_encode($content, JSON_PRETTY_PRINT)); + + echo "{\"status\": \"successful\"}"; + exit; +} + +// Generator for page customization +$links_string = ""; +if ($data["settings"]["custom_links"]) { + foreach ($data["settings"]["custom_links"] as $name => $url) { + $links_string = $links_string . "<a href=\"$url\" class=\"badgebadge-secondary\">$name</a> "; + } + $links_string = substr($links_string, 0, -1); +} + +?> + +<!doctype html> +<html lang="en" class="h-100"> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta name="robots" content="noindex, nofollow"> + <meta name="author" content="<?php echo $data["settings"]["author"]; ?> and the devShort team"> + <link rel="icon" href="../<?php echo $data["settings"]["favicon"]; ?>"> + <title>Admin console | <?php echo $data["settings"]["name"]; ?></title> + <link href="../assets/vendor/bootstrap/bootstrap.min.css" rel="stylesheet"> + <link href="../assets/main.min.css" rel="stylesheet"> +</head> + +<body class="d-flex flex-column h-100"> + + <main role="main" class="flex-shrink-0"> + <div class="container"> + <h1 class="mt-5 mb-4 text-center"><?php echo $data["settings"]["name"]; ?> admin console</h1> + <div id="charts"></div> + <p class="text-center mt-4">powered by <a href="https://github.com/flokX/devShort">devShort</a> v1.1.0 (Latest: <a href="https://github.com/flokX/devShort/releases"><img src="https://img.shields.io/github/release/flokX/devShort.svg?style=flat" alt="Latest release"></a>)</p> + </div> + </main> + + <footer class="footer mt-auto py-3"> + <div class="container"> + <div class="d-flex justify-content-between align-items-center"> + <span class="text-muted">&copy; <?php echo date("Y") . " " . $data["settings"]["author"]; ?> and <a href="https://github.com/flokX/devShort">devShort</a></span> + <?php if ($links_string) { echo "<span class=\"text-muted\">$links_string</span>"; } ?> + </div> + </div> + </footer> + + <script src="../assets/vendor/frappe-charts/frappe-charts.min.iife.js"></script> + <script src="../assets/vendor/jquery/jquery.min.js"></script> + <script src="main.js"></script> + +</body> + +</html> diff --git a/admin/main.js b/admin/main.js @@ -0,0 +1,28 @@ +var currentDate = new Date(); +var startDate = new Date(new Date().setFullYear(currentDate.getFullYear() - 1)); + +$.getJSON('stats.json', function (json) { + 'use strict'; + $.each(json, function (name, data) { + $('div#charts').append('<div class="card mb-3"><div class="card-body"><div id="heatmap-' + name + '" class="heatmap"></div></div><div class="card-footer text-center text-muted"><a id="export-' + name + '" href="#download" class="card-link">Download chart</a><a id="delete-' + name + '" href="#delete" class="card-link">Delete shortlink and dataset</a></div></div>'); + let heatmap = new frappe.Chart('div#heatmap-' + name, { + type: 'heatmap', + title: 'Access statistics for ' + name, + data: { + dataPoints: data, + start: startDate, + end: currentDate + }, + countLabel: 'Access(es)' + }); + $('a#export-' + name).click(function () { + heatmap.export(); + }); + $('a#delete-' + name).click(function () { + $.post('index.php', { + delete: name + }); + location.reload(); + }); + }); +}); diff --git a/admin/stats.json b/admin/stats.json @@ -0,0 +1 @@ +{} diff --git a/assets/main.css b/assets/main.css @@ -11,3 +11,10 @@ .footer { background-color: #f5f5f5; } + +/* CSS by devShort +-------------------------------------------------- */ + +.heatmap { + overflow-x: auto; +} diff --git a/assets/main.min.css b/assets/main.min.css @@ -1 +1 @@ -.container{width:auto;max-width:680px;padding:0 15px}.footer{background-color:#f5f5f5} -\ No newline at end of file +.container{width:auto;max-width:680px;padding:0 15px}.footer{background-color:#f5f5f5}.heatmap{overflow-x:auto} diff --git a/installer.php b/installer.php @@ -3,7 +3,7 @@ // All relevant changes can be made in the data file. Please read the docs: https://github.com/flokX/devShort/wiki $success = false; -$data_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "secure", "config.json")); +$data_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "admin", "config.json")); $data = json_decode(file_get_contents($data_path), true); if ($data["installer"]["password"]) { @@ -23,7 +23,7 @@ RewriteRule ^(.*)$ {$installation_path}redirect.php?short=$1 [R=301,L]"; file_put_contents(__DIR__ . DIRECTORY_SEPARATOR . ".htaccess", $root_htaccess, FILE_APPEND); // Create the .htpasswd for the secure directory. If already a hashed password is there, copy it. - $htpasswd_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "secure", ".htpasswd")); + $htpasswd_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "admin", ".htpasswd")); $data_password = $data["installer"]["password"]; if (password_get_info($data_password)["algo"] === 0) { $hash = password_hash($data_password, PASSWORD_DEFAULT); @@ -38,7 +38,7 @@ AuthType Basic AuthName \"devShort admin area\" AuthUserFile $htpasswd_path require valid-user"; - file_put_contents(implode(DIRECTORY_SEPARATOR, array(__DIR__, "secure", ".htaccess")), $secure_htaccess); + file_put_contents(implode(DIRECTORY_SEPARATOR, array(__DIR__, "admin", ".htaccess")), $secure_htaccess); // Change password entry to the hash and remove installer file. $data["installer"]["password"] = $hash; diff --git a/redirect.php b/redirect.php @@ -10,7 +10,7 @@ if ($short === "robots.txt") { echo "User-agent: *\n"; echo "Disallow: /\n"; exit; -} else if ($short === 'favicon.ico') { +} else if ($short === "favicon.ico") { header("HTTP/1.1 404 Not Found"); exit; } @@ -19,12 +19,12 @@ if ($short === "robots.txt") { function count_access($base_path, $name) { $filename = $base_path . DIRECTORY_SEPARATOR . "stats.json"; $stats = json_decode(file_get_contents($filename), true); - $stats[$name][date("Y-m-d")] += 1; - file_put_contents($filename, json_encode($stats)); + $stats[$name][mktime(0, 0, 0)] += 1; + file_put_contents($filename, json_encode($stats, JSON_PRETTY_PRINT)); } -$base_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "secure")); -$data = json_decode(file_get_contents($base_path . DIRECTORY_SEPARATOR . "configjson"), true); +$base_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "admin")); +$data = json_decode(file_get_contents($base_path . DIRECTORY_SEPARATOR . "config.json"), true); if (array_key_exists($short, $data["shortlinks"])) { header("Location: " . $data["shortlinks"][$short], $http_response_code=303); @@ -32,7 +32,7 @@ if (array_key_exists($short, $data["shortlinks"])) { exit; } else { header("HTTP/1.1 404 Not Found"); - count_access($base_path, "404 request"); + count_access($base_path, "404-request"); // Generator for page customization $links_string = ""; diff --git a/secure/main.js b/secure/main.js @@ -1,61 +0,0 @@ -// Function based on https://stackoverflow.com/questions/1484506/random-color-generator/1484514#1484514 -function getRandomColor() { - 'use strict'; - var letters = '0123456789ABCDEF'; - var color = '#'; - for (var i = 0; i < 6; i++) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; -} - -var ctx = document.getElementById('chart').getContext('2d'); - -$.getJSON('stats.json', function (json) { - 'use strict'; - var datasets = []; - $.each(json, function (key, value) { - var data = []; - var color = getRandomColor(); - $.each(value, function (key, value) { - data.push({ - x: key, - y: value - }); - }); - datasets.push({ - label: key, - backgroundColor: color, - borderColor: color, - data: data, - fill: false - }); - }); - var chart = new Chart(ctx, { - type: 'line', - data: { - datasets: datasets - }, - options: { - responsive: true, - scales: { - xAxes: [{ - type: 'time', - scaleLabel: { - display: true, - labelString: 'Date' - } - }], - yAxes: [{ - ticks: { - min: 0 - }, - scaleLabel: { - display: true, - labelString: 'Request number' - } - }] - } - } - }); -}); diff --git a/secure/main.min.js b/secure/main.min.js @@ -1 +0,0 @@ -function getRandomColor(){"use strict";for(var t="#",e=0;e<6;e++)t+="0123456789ABCDEF"[Math.floor(16*Math.random())];return t}var ctx=document.getElementById("chart").getContext("2d");$.getJSON("stats.json",function(t){"use strict";var e=[];$.each(t,function(t,a){var o=[],n=getRandomColor();$.each(a,function(t,e){o.push({x:t,y:e})}),e.push({label:t,backgroundColor:n,borderColor:n,data:o,fill:!1})});new Chart(ctx,{type:"line",data:{datasets:e},options:{responsive:!0,scales:{xAxes:[{type:"time",scaleLabel:{display:!0,labelString:"Date"}}],yAxes:[{ticks:{min:0},scaleLabel:{display:!0,labelString:"Request number"}}]}}})}); -\ No newline at end of file diff --git a/secure/stats.json b/secure/stats.json @@ -1 +0,0 @@ -{} -\ No newline at end of file