commit 0eec4e2a26a7ba5dabd2a839b52578f56fc8729a
parent 8062c4333b64fb133be9743a4aea9064617d19ce
Author: Florian <flokX@users.noreply.github.com>
Date: Thu, 26 Dec 2019 10:54:53 +0100
Relocate files
New directoy structure:
* admin panel in root
* user data in "data/"
* changed paths in admn.php
Diffstat:
9 files changed, 287 insertions(+), 297 deletions(-)
diff --git a/admin.php b/admin.php
@@ -0,0 +1,137 @@
+<?php
+
+// All relevant changes can be made in the data file. Please read the docs: https://github.com/flokX/devShort/wiki
+
+$config_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "data", "config.json"));
+$config_content = json_decode(file_get_contents($config_path), true);
+$stats_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "data", "stats.json"));
+$stats_content = json_decode(file_get_contents($stats_path), true);
+
+// Filter the names that the admin interface doesn't break
+function filter_name($nameRaw) {
+ $name = filter_var($nameRaw, FILTER_SANITIZE_STRING);
+ $name = str_replace(" ", "-", $name);
+ $name = preg_replace("/[^A-Za-z0-9-_]/", "", $name);
+ return $name;
+}
+
+// API functions to delete and add the shortlinks via the admin panel
+if (isset($_GET["delete"]) || isset($_GET["add"])) {
+ $data = json_decode(file_get_contents("php://input"), true);
+ if (isset($_GET["delete"])) {
+ unset($config_content["shortlinks"][$data["name"]]);
+ unset($stats_content[$data["name"]]);
+ } else if (isset($_GET["add"])) {
+ $filtered = array("name" => filter_name($data["name"]),
+ "url" => filter_var($data["url"], FILTER_SANITIZE_URL));
+ if (!filter_var($filtered["url"], FILTER_VALIDATE_URL)) {
+ echo "{\"status\": \"unvalid-url\"}";
+ exit;
+ }
+ $config_content["shortlinks"][$filtered["name"]] = $filtered["url"];
+ $stats_content[$filtered["name"]] = array();
+ }
+ file_put_contents($config_path, json_encode($config_content, JSON_PRETTY_PRINT));
+ file_put_contents($stats_path, json_encode($stats_content, JSON_PRETTY_PRINT));
+ header("Content-Type: application/json");
+ echo "{\"status\": \"successful\"}";
+ exit;
+}
+
+// Generator for page customization
+$links_string = "";
+if ($config_content["settings"]["custom_links"]) {
+ foreach ($config_content["settings"]["custom_links"] as $name => $url) {
+ $links_string = $links_string . "<a href=\"$url\" class=\"badge badge-secondary\">$name</a> ";
+ }
+ $links_string = substr($links_string, 0, -1);
+}
+
+?>
+
+<!doctype html>
+<html class="h-100" lang="en">
+
+<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 $config_content["settings"]["author"]; ?> and the devShort team">
+ <link href="<?php echo $config_content["settings"]["favicon"]; ?>" rel="icon">
+ <title>Admin panel | <?php echo $config_content["settings"]["name"]; ?></title>
+ <link href="assets/vendor/bootstrap/bootstrap.min.css" rel="stylesheet">
+ <link href="assets/main.css" rel="stylesheet">
+</head>
+
+<body class="d-flex flex-column h-100">
+
+ <main class="flex-shrink-0">
+ <div class="container">
+ <h1 class="mt-5 text-center"><?php echo $config_content["settings"]["name"]; ?></h1>
+ <h4 class="mb-4 text-center">admin panel</h4>
+ <div class="row">
+ <div class="col-md-4 col-lg-3">
+ <div class="card d-none d-md-block mb-3">
+ <div class="card-body">
+ <a class="card-link" id="refresh" href="#refresh">Refresh charts</a>
+ </div>
+ </div>
+ <div class="card mb-3">
+ <div class="card-body">
+ <h5 class="card-title">Add shortlink <small class="d-md-none"><a class="card-link" id="refresh" href="#refresh">Refresh charts</a></small></h5>
+ <form id="add-form">
+ <div class="form-group">
+ <label for="name">Name</label>
+ <input class="form-control mb-2 mb-sm-0 mr-sm-2" id="name" type="text" placeholder="Link1" required>
+ </div>
+ <div class="form-group">
+ <label for="url">URL (destination)</label>
+ <input class="form-control mb-2 mb-sm-0 mr-sm-2" id="url" type="url" placeholder="https://example.com" required>
+ </div>
+ <button class="btn btn-primary" type="submit">Add</button>
+ <div id="status"></div>
+ </form>
+ </div>
+ </div>
+ <div class="card mb-3">
+ <div class="card-body">
+ <h5 class="card-title">Search</h5>
+ <form>
+ <input class="form-control" id="search-bar" type="text">
+ </form>
+ </div>
+ </div>
+ <div class="card d-none d-md-block">
+ <div class="card-body">
+ <p class="mb-0">powered by <a href="https://github.com/flokX/devShort">devShort</a> v2.4.0</p>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-8 col-lg-9">
+ <div class="d-flex justify-content-center">
+ <div class="spinner-border text-primary mt-4" id="spinner" role="status">
+ <span class="sr-only">Loading...</span>
+ </div>
+ </div>
+ <div id="charts"></div>
+ </div>
+ </div>
+ <p class="text-center d-md-none mt-1 mb-5">powered by <a href="https://github.com/flokX/devShort">devShort</a> v2.4.0</p>
+ </div>
+ </main>
+
+ <footer class="footer mt-auto py-3 bg-light">
+ <div class="container">
+ <div class="d-flex justify-content-between align-items-center">
+ <span class="text-muted">© <?php echo date("Y") . " " . $config_content["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/main.js"></script>
+
+</body>
+
+</html>
diff --git a/admin/.htaccess b/admin/.htaccess
@@ -1,20 +0,0 @@
-# Lock the whole directory until the installer runned
-
-# Generated by Nextcloud
-# Nextcloud is licensed under the AGPL v3 license
-
-# line below if for Apache 2.4
-<ifModule mod_authz_core.c>
-Require all denied
-</ifModule>
-
-# line below if for Apache 2.2
-<ifModule !mod_authz_core.c>
-deny from all
-Satisfy All
-</ifModule>
-
-# section for Apache 2.2 and 2.4
-<ifModule mod_autoindex.c>
-IndexIgnore *
-</ifModule>
diff --git a/admin/config.json b/admin/config.json
@@ -1,20 +0,0 @@
-{
- "installer": {
- "username": "admin",
- "password": ""
- },
- "settings": {
- "name": "devShort shortlink service",
- "author": "The admin",
- "home_link": "https://github.com/flokX/devShort",
- "favicon": "assets/icon.png",
- "custom_links": {
- "devShort wiki": "https://github.com/flokX/devShort/wiki",
- "devShort author": "https://github.com/flokX"
- }
- },
- "shortlinks": {
- "repo": "https://github.com/flokX/devShort",
- "wiki": "https://github.com/flokX/devShort/wiki"
- }
-}
diff --git a/admin/index.php b/admin/index.php
@@ -1,137 +0,0 @@
-<?php
-
-// All relevant changes can be made in the data file. Please read the docs: https://github.com/flokX/devShort/wiki
-
-$config_path = __DIR__ . DIRECTORY_SEPARATOR . "config.json";
-$config_content = json_decode(file_get_contents($config_path), true);
-$stats_path = __DIR__ . DIRECTORY_SEPARATOR . "stats.json";
-$stats_content = json_decode(file_get_contents($stats_path), true);
-
-// Filter the names that the admin interface doesn't break
-function filter_name($nameRaw) {
- $name = filter_var($nameRaw, FILTER_SANITIZE_STRING);
- $name = str_replace(" ", "-", $name);
- $name = preg_replace("/[^A-Za-z0-9-_]/", "", $name);
- return $name;
-}
-
-// API functions to delete and add the shortlinks via the admin panel
-if (isset($_GET["delete"]) || isset($_GET["add"])) {
- $data = json_decode(file_get_contents("php://input"), true);
- if (isset($_GET["delete"])) {
- unset($config_content["shortlinks"][$data["name"]]);
- unset($stats_content[$data["name"]]);
- } else if (isset($_GET["add"])) {
- $filtered = array("name" => filter_name($data["name"]),
- "url" => filter_var($data["url"], FILTER_SANITIZE_URL));
- if (!filter_var($filtered["url"], FILTER_VALIDATE_URL)) {
- echo "{\"status\": \"unvalid-url\"}";
- exit;
- }
- $config_content["shortlinks"][$filtered["name"]] = $filtered["url"];
- $stats_content[$filtered["name"]] = array();
- }
- file_put_contents($config_path, json_encode($config_content, JSON_PRETTY_PRINT));
- file_put_contents($stats_path, json_encode($stats_content, JSON_PRETTY_PRINT));
- header("Content-Type: application/json");
- echo "{\"status\": \"successful\"}";
- exit;
-}
-
-// Generator for page customization
-$links_string = "";
-if ($config_content["settings"]["custom_links"]) {
- foreach ($config_content["settings"]["custom_links"] as $name => $url) {
- $links_string = $links_string . "<a href=\"$url\" class=\"badge badge-secondary\">$name</a> ";
- }
- $links_string = substr($links_string, 0, -1);
-}
-
-?>
-
-<!doctype html>
-<html class="h-100" lang="en">
-
-<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 $config_content["settings"]["author"]; ?> and the devShort team">
- <link href="../<?php echo $config_content["settings"]["favicon"]; ?>" rel="icon">
- <title>Admin panel | <?php echo $config_content["settings"]["name"]; ?></title>
- <link href="../assets/vendor/bootstrap/bootstrap.min.css" rel="stylesheet">
- <link href="../assets/main.css" rel="stylesheet">
-</head>
-
-<body class="d-flex flex-column h-100">
-
- <main class="flex-shrink-0">
- <div class="container">
- <h1 class="mt-5 text-center"><?php echo $config_content["settings"]["name"]; ?></h1>
- <h4 class="mb-4 text-center">admin panel</h4>
- <div class="row">
- <div class="col-md-4 col-lg-3">
- <div class="card d-none d-md-block mb-3">
- <div class="card-body">
- <a class="card-link" id="refresh" href="#refresh">Refresh charts</a>
- </div>
- </div>
- <div class="card mb-3">
- <div class="card-body">
- <h5 class="card-title">Add shortlink <small class="d-md-none"><a class="card-link" id="refresh" href="#refresh">Refresh charts</a></small></h5>
- <form id="add-form">
- <div class="form-group">
- <label for="name">Name</label>
- <input class="form-control mb-2 mb-sm-0 mr-sm-2" id="name" type="text" placeholder="Link1" required>
- </div>
- <div class="form-group">
- <label for="url">URL (destination)</label>
- <input class="form-control mb-2 mb-sm-0 mr-sm-2" id="url" type="url" placeholder="https://example.com" required>
- </div>
- <button class="btn btn-primary" type="submit">Add</button>
- <div id="status"></div>
- </form>
- </div>
- </div>
- <div class="card mb-3">
- <div class="card-body">
- <h5 class="card-title">Search</h5>
- <form>
- <input class="form-control" id="search-bar" type="text">
- </form>
- </div>
- </div>
- <div class="card d-none d-md-block">
- <div class="card-body">
- <p class="mb-0">powered by <a href="https://github.com/flokX/devShort">devShort</a> v2.4.0</p>
- </div>
- </div>
- </div>
- <div class="col-md-8 col-lg-9">
- <div class="d-flex justify-content-center">
- <div class="spinner-border text-primary mt-4" id="spinner" role="status">
- <span class="sr-only">Loading...</span>
- </div>
- </div>
- <div id="charts"></div>
- </div>
- </div>
- <p class="text-center d-md-none mt-1 mb-5">powered by <a href="https://github.com/flokX/devShort">devShort</a> v2.4.0</p>
- </div>
- </main>
-
- <footer class="footer mt-auto py-3 bg-light">
- <div class="container">
- <div class="d-flex justify-content-between align-items-center">
- <span class="text-muted">© <?php echo date("Y") . " " . $config_content["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="main.js"></script>
-
-</body>
-
-</html>
diff --git a/admin/main.js b/admin/main.js
@@ -1,120 +0,0 @@
-/* Register variables */
-const currentDate = new Date();
-const startDate = new Date(new Date().setFullYear(currentDate.getFullYear() - 1));
-const spinner = document.getElementById('spinner');
-const chartsDiv = document.getElementById('charts');
-const statusDiv = document.getElementById('status');
-
-/* Helper function to post to page api */
-function post(url, data) {
- 'use strict';
- return fetch(url, {
- method: 'POST',
- credentials: 'same-origin',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(data)
- }).then(function (response) {
- return response.json();
- });
-}
-
-/* Add a new shortlink */
-document.getElementById('add-form').addEventListener('submit', function (event) {
- 'use strict';
- event.preventDefault();
- spinner.style.display = '';
- post('index.php?add', {
- name: document.getElementById('name').value,
- url: document.getElementById('url').value
- }).then(function (data) {
- if (data.status === 'successful') {
- document.getElementById('name').value = '';
- document.getElementById('url').value = 'https://';
- if (statusDiv.firstChild) {
- statusDiv.firstChild.remove();
- }
- getCharts();
- } else if (data.status === 'unvalid-url') {
- statusDiv.insertAdjacentHTML('afterbegin', '<div class="alert alert-danger" role="alert">Unvalid URL. Please provide a valid URL.</div>');
- } else {
- statusDiv.insertAdjacentHTML('afterbegin', '<div class="alert alert-danger" role="alert">Error. Please try again.</div>');
- }
- });
- spinner.style.display = 'none';
-});
-
-/* Provide search functionality */
-var searchBox = document.getElementById('search-bar');
-searchBox.addEventListener('input', function (event) {
- 'use strict';
- for (let node of chartsDiv.childNodes) {
- let linkName = node.firstElementChild.innerHTML.toLowerCase();
- if (linkName.includes(searchBox.value.toLowerCase())) {
- node.style.display = 'block';
- } else {
- node.style.display = 'none';
- }
- }
-});
-
-/* Reload charts */
-document.getElementById('refresh').addEventListener('click', function (event) {
- 'use strict';
- event.preventDefault();
- getCharts();
-});
-
-/* Get charts and date (remove old when necessary) */
-function getCharts() {
- 'use strict';
- spinner.style.display = 'block';
- while (chartsDiv.firstChild) {
- chartsDiv.firstChild.remove();
- }
- fetch('stats.json', {
- cache: 'no-cache',
- credentials: 'same-origin'
- }).then(function (response) {
- return response.json();
- }).then(function (json) {
- for (let [name, data] of Object.entries(json)) {
- chartsDiv.insertAdjacentHTML('beforeend', `<div id="card-${name}" class="card text-center mb-3">
- <div class="card-header">${name}</div>
- <div class="card-body p-2">
- <div id="heatmap-${name}" class="overflow-auto"></div>
- </div>
- <div class="card-footer text-muted">
- <a id="export-${name}" href="#export" class="card-link">Export 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)',
- discreteDomains: 0
- });
- document.getElementById('export-' + name).addEventListener('click', function (event) {
- event.preventDefault();
- heatmap.export();
- });
- document.getElementById('delete-' + name).addEventListener('click', function (event) {
- event.preventDefault();
- post('index.php?delete', {
- name: name
- }).then(function () {
- document.getElementById('card-' + name).remove();
- });
- });
- }
- spinner.style.display = 'none';
- });
-}
-
-getCharts();
diff --git a/assets/main.js b/assets/main.js
@@ -0,0 +1,120 @@
+/* Register variables */
+const currentDate = new Date();
+const startDate = new Date(new Date().setFullYear(currentDate.getFullYear() - 1));
+const spinner = document.getElementById('spinner');
+const chartsDiv = document.getElementById('charts');
+const statusDiv = document.getElementById('status');
+
+/* Helper function to post to page api */
+function post(url, data) {
+ 'use strict';
+ return fetch(url, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data)
+ }).then(function (response) {
+ return response.json();
+ });
+}
+
+/* Add a new shortlink */
+document.getElementById('add-form').addEventListener('submit', function (event) {
+ 'use strict';
+ event.preventDefault();
+ spinner.style.display = '';
+ post('index.php?add', {
+ name: document.getElementById('name').value,
+ url: document.getElementById('url').value
+ }).then(function (data) {
+ if (data.status === 'successful') {
+ document.getElementById('name').value = '';
+ document.getElementById('url').value = 'https://';
+ if (statusDiv.firstChild) {
+ statusDiv.firstChild.remove();
+ }
+ getCharts();
+ } else if (data.status === 'unvalid-url') {
+ statusDiv.insertAdjacentHTML('afterbegin', '<div class="alert alert-danger" role="alert">Unvalid URL. Please provide a valid URL.</div>');
+ } else {
+ statusDiv.insertAdjacentHTML('afterbegin', '<div class="alert alert-danger" role="alert">Error. Please try again.</div>');
+ }
+ });
+ spinner.style.display = 'none';
+});
+
+/* Provide search functionality */
+var searchBox = document.getElementById('search-bar');
+searchBox.addEventListener('input', function (event) {
+ 'use strict';
+ for (let node of chartsDiv.childNodes) {
+ let linkName = node.firstElementChild.innerHTML.toLowerCase();
+ if (linkName.includes(searchBox.value.toLowerCase())) {
+ node.style.display = 'block';
+ } else {
+ node.style.display = 'none';
+ }
+ }
+});
+
+/* Reload charts */
+document.getElementById('refresh').addEventListener('click', function (event) {
+ 'use strict';
+ event.preventDefault();
+ getCharts();
+});
+
+/* Get charts and date (remove old when necessary) */
+function getCharts() {
+ 'use strict';
+ spinner.style.display = 'block';
+ while (chartsDiv.firstChild) {
+ chartsDiv.firstChild.remove();
+ }
+ fetch('../data/stats.json', {
+ cache: 'no-cache',
+ credentials: 'same-origin'
+ }).then(function (response) {
+ return response.json();
+ }).then(function (json) {
+ for (let [name, data] of Object.entries(json)) {
+ chartsDiv.insertAdjacentHTML('beforeend', `<div id="card-${name}" class="card text-center mb-3">
+ <div class="card-header">${name}</div>
+ <div class="card-body p-2">
+ <div id="heatmap-${name}" class="overflow-auto"></div>
+ </div>
+ <div class="card-footer text-muted">
+ <a id="export-${name}" href="#export" class="card-link">Export 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)',
+ discreteDomains: 0
+ });
+ document.getElementById('export-' + name).addEventListener('click', function (event) {
+ event.preventDefault();
+ heatmap.export();
+ });
+ document.getElementById('delete-' + name).addEventListener('click', function (event) {
+ event.preventDefault();
+ post('index.php?delete', {
+ name: name
+ }).then(function () {
+ document.getElementById('card-' + name).remove();
+ });
+ });
+ }
+ spinner.style.display = 'none';
+ });
+}
+
+getCharts();
diff --git a/data/.htaccess b/data/.htaccess
@@ -0,0 +1,10 @@
+# Lock the whole directory because the data should not be freely accessible
+# (aplication configuration data)
+
+<ifModule mod_authz_core.c>
+ Require all denied
+</ifModule>
+
+<ifModule mod_autoindex.c>
+ IndexIgnore *
+</ifModule>
diff --git a/data/config.json b/data/config.json
@@ -0,0 +1,20 @@
+{
+ "installer": {
+ "username": "admin",
+ "password": "123"
+ },
+ "settings": {
+ "name": "devShort shortlink service",
+ "author": "The admin",
+ "home_link": "https://github.com/flokX/devShort",
+ "favicon": "assets/icon.png",
+ "custom_links": {
+ "devShort wiki": "https://github.com/flokX/devShort/wiki",
+ "devShort author": "https://github.com/flokX"
+ }
+ },
+ "shortlinks": {
+ "repo": "https://github.com/flokX/devShort",
+ "wiki": "https://github.com/flokX/devShort/wiki"
+ }
+}
diff --git a/admin/stats.json b/data/stats.json