devshort

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

admin.php (10510B)


      1 <?php
      2 
      3 // This file is part of the devShort project under the MIT License. Visit https://sr.ht/~rwa/devshort for more information.
      4 
      5 $config_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "data", "config.json"));
      6 $config_content = json_decode(file_get_contents($config_path), true);
      7 $stats_path = implode(DIRECTORY_SEPARATOR, array(__DIR__, "data", "stats.json"));
      8 $stats_content = json_decode(file_get_contents($stats_path), true);
      9 
     10 // remove outdated stats to keep stats.json small
     11 $converted = 0;
     12 foreach ( $stats_content as &$stats_entry ) {
     13 	foreach ($stats_entry as $key => $value) {
     14 		if ($key < date('Y-m-d', strtotime('-1 year'))) {
     15 			$converted = 1;
     16 			unset($stats_entry[$key]);
     17 		}
     18 	}
     19 }
     20 if ( $converted ) { file_put_contents($stats_path, json_encode($stats_content, JSON_PRETTY_PRINT)); }
     21 
     22 // Check if authentication is valid
     23 session_start();
     24 if (!isset($_SESSION["user_authenticated"])) {
     25     header("Location: admin-auth.php?login");
     26     exit;
     27 }
     28 
     29 // Deliver stats.json content for the program (make AJAX calls and charts reloading possible)
     30 if (isset($_GET["get_data"])) {
     31     header("Content-Type: application/json");
     32     $content = array("shortlinks" => $config_content["shortlinks"], "stats" => $stats_content);
     33     echo json_encode($content);
     34     exit;
     35 }
     36 
     37 // API functions to delete and add the shortlinks via the admin panel
     38 if (isset($_GET["delete"]) || isset($_GET["add"])) {
     39     $data = json_decode(file_get_contents("php://input"), true);
     40     if (isset($_GET["delete"])) {
     41         unset($config_content["shortlinks"][$data["name"]]);
     42         unset($stats_content[$data["name"]]);
     43     } else if (isset($_GET["add"])) {
     44         $filtered = array("name" => strtolower(filter_var($data["name"], FILTER_SANITIZE_STRING)),
     45                           "url" => filter_var($data["url"], FILTER_SANITIZE_URL));
     46         if (!filter_var($filtered["url"], FILTER_VALIDATE_URL)) {
     47             echo "{\"status\": \"unvalid-url\"}";
     48             exit;
     49         }
     50         if (array_key_exists($filtered["name"], $config_content["shortlinks"])) {
     51             echo "{\"status\": \"url-already-exists\"}";
     52             exit;
     53         }
     54         $config_content["shortlinks"][$filtered["name"]] = $filtered["url"];
     55         $stats_content[$filtered["name"]] = array();
     56     }
     57     file_put_contents($config_path, json_encode($config_content, JSON_PRETTY_PRINT), LOCK_EX);
     58     file_put_contents($stats_path, json_encode($stats_content, JSON_PRETTY_PRINT), LOCK_EX);
     59     header("Content-Type: application/json");
     60     echo "{\"status\": \"successful\"}";
     61     exit;
     62 }
     63 
     64 // Generate custom buttons for the footer
     65 $links_string = "";
     66 if ($config_content["settings"]["custom_links"]) {
     67     foreach ($config_content["settings"]["custom_links"] as $name => $url) {
     68         $links_string = $links_string . "<a href=\"$url\" class=\"badge badge-primary\" target=\"_blank\">$name</a> ";
     69     }
     70     $links_string = substr($links_string, 0, -1);
     71 }
     72 
     73 $author_string = "";
     74 if ($config_content["settings"]["author_link"]) {
     75     $author_string = "<a rel=\"me\" target=\"_blank\" href=\"". $config_content["settings"]["author_link"] ."\">".$config_content["settings"]["author"]."</a>";
     76 } else {
     77     $author_string = $config_content["settings"]["author"];
     78 }
     79 ?>
     80 
     81 <!doctype html>
     82 <html class="h-100" lang="en">
     83 
     84 <head>
     85     <meta charset="utf-8">
     86     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     87     <meta name="robots" content="noindex, nofollow">
     88     <meta name="author" content="<?php echo $config_content["settings"]["author"]; ?> and the devShort team">
     89     <link href="<?php echo $config_content["settings"]["favicon"]; ?>" rel="icon">
     90     <title>admin panel | <?php echo $config_content["settings"]["name"]; ?></title>
     91     <link href="assets/vendor/bootstrap/bootstrap.min.css" rel="stylesheet">
     92 </head>
     93 
     94 <body class="d-flex flex-column h-100">
     95 
     96     <main class="flex-shrink-0">
     97         <div class="container">
     98             <nav class="mt-3" aria-label="breadcrumb">
     99                 <ol class="breadcrumb shadow-sm">
    100                     <li class="breadcrumb-item"><a href="<?php echo $config_content["settings"]["home_link"]; ?>">Home</a></li>
    101                     <li class="breadcrumb-item" aria-current="page"><a href="/"><?php echo $config_content["settings"]["name"]; ?></a></li>
    102 					<li class="breadcrumb-item active">admin panel</li>
    103                 </ol>
    104             </nav>
    105             <div class="row" id="app">
    106                 <div class="col-md-4 col-lg-3">
    107                     <div class="card d-none d-md-block mb-3">
    108                         <div class="card-body">
    109                             <h5 class="card-title">Tools</h5>
    110                             <a class="card-link" href="#reload" v-on:click="loadData">Reload charts</a>
    111                             <a class="card-link" href="admin-auth.php?logout">Logout</a>
    112                         </div>
    113                     </div>
    114                     <div class="card mb-3">
    115                         <div class="card-body">
    116                             <h5 class="card-title">Add shortlink</h5>
    117                             <form id="add-form">
    118                                 <div class="form-group">
    119                                     <label for="name">Name</label>
    120                                     <input class="form-control mb-2 mb-sm-0 mr-sm-2" id="name" type="text" placeholder="Link1" required>
    121                                 </div>
    122                                 <div class="form-group">
    123                                     <label for="url">URL (destination)</label>
    124                                     <input class="form-control mb-2 mb-sm-0 mr-sm-2" id="url" type="url" placeholder="https://example.com" required>
    125                                 </div>
    126                                 <button class="btn btn-primary" type="submit">Add</button>
    127                                 <div id="status"></div>
    128                             </form>
    129                         </div>
    130                     </div>
    131                     <div class="card mb-3">
    132                         <div class="card-body">
    133                             <h5 class="card-title">Search</h5>
    134                             <form>
    135                                 <input class="form-control" type="text" v-model="search">
    136                             </form>
    137                         </div>
    138                     </div>
    139                     <div class="card d-none d-md-block mb-3">
    140                         <div class="card-body">
    141                             <p class="mb-0" id="version-1">powered by <a href="https://sr.ht/~rwa/devshort" target="_blank">devShort</a></p>
    142                         </div>
    143                     </div>
    144                     <div class="card d-md-none mb-3">
    145                         <div class="card-body text-center">
    146                             <a class="card-link" href="index.php">Index</a>
    147                             <a class="card-link" href="#reload" v-on:click="loadData">Reload</a>
    148                             <a class="card-link" href="admin-auth.php?logout">Logout</a>
    149                         </div>
    150                     </div>
    151                 </div>
    152                 <div class="col-md-8 col-lg-9">
    153                     <div class="d-flex justify-content-center" v-if="!loaded">
    154                         <div class="spinner-border text-primary my-4" role="status">
    155                             <span class="sr-only">Loading...</span>
    156                         </div>
    157                     </div>
    158                     <chart v-for="(stats, name) in dataObject.stats" v-bind:key="name" :style="displayStyle(name)" v-bind:name="name" v-bind:stats="stats" v-else></chart>
    159                 </div>
    160             </div>
    161         </div>
    162     </main>
    163 
    164     <footer class="footer mt-auto py-3">
    165         <div class="container">
    166             <div class="d-flex justify-content-between align-items-center breadcrumb shadow-sm">
    167                 <span class="text-dark">&copy; 2020-2023 <?php echo $author_string; ?></a> and <a href="https://sr.ht/~rwa/devshort" target="_blank">devShort</a></span>
    168                 <?php if ($links_string) { echo "<span class=\"text-muted\">$links_string</span>"; } ?>
    169             </div>
    170         </div>
    171     </footer>
    172 
    173     <template id="chart-template">
    174         <div class="card mb-3">
    175             <div class="card-body">
    176                 <div class="row">
    177                     <div class="col-lg-6 d-flex align-items-center">
    178                         <h3 class="card-title mb-0">{{ name }}</h3>
    179                     </div>
    180                     <div class="col-lg-6 d-flex align-items-center">
    181                         <span>{{ accessCount.sevenDays }} <small class="text-muted">Accesses last 7 days</small> | {{ accessCount.total }} <small class="text-muted">Accesses in total</small></span>
    182                     </div>
    183                 </div>
    184                 <hr>
    185                 <div class="overflow-auto" :id="chartId"></div>
    186                 <hr>
    187                 <p class="text-center text-muted mb-0" v-if="this.name === 'index'">Index is an internal entry. It counts the number of front page accesses.</p>
    188                 <p class="text-center text-muted mb-0" v-else-if="this.name === '404-request'">404-request is an internal entry. It counts the number of accesses to non-existent shortlinks.</p>
    189                 <div class="row" v-else>
    190                     <div class="col-lg-9">
    191                         <label class="sr-only" :for="'destination-' + this.identifier">URL (destination)</label>
    192                         <div class="input-group">
    193                             <input class="form-control" :id="'destination-' + this.identifier" type="url" :value="shortlinkUrl" readonly>
    194                             <div class="input-group-append">
    195                                 <div class="input-group-text">
    196                                     <a :href="shortlinkUrl" target="_blank" rel="noopener">open</a>
    197                                 </div>
    198                             </div>
    199                         </div>
    200                     </div>
    201                     <div class="col-lg-3 mt-2 mt-lg-0 text-center">
    202                         <button type="button" class="btn btn-outline-danger" v-on:click="remove">Delete shortlink</button>
    203                     </div>
    204                 </div>
    205             </div>
    206         </div>
    207     </template>
    208 
    209     <script src="assets/vendor/frappe-charts/frappe-charts.min.umd.js"></script>
    210     <script src="assets/vendor/vue/vue.min.js"></script>
    211     <script src="assets/main.js"></script>
    212 
    213 </body>
    214 
    215 </html>