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">© 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>