Add site analytics (close #8)

master
Skylar Ittner 6 years ago
parent 88d7b539b9
commit a3391f5ab6

Binary file not shown.

@ -66,10 +66,23 @@ define("STRINGS", [
"page id" => "Page ID (slug)",
"add page" => "Add page",
"page settings" => "Page Settings",
"analytics" => "Analytics",
"today" => "Today",
"this week" => "This Week",
"visit" => "visit",
"visits" => "visits",
"page view" => "page view",
"page views" => "page views",
"site" => "Site",
"filter by site" => "Filter by site",
"all sites" => "All Sites",
"filter" => "Filter",
"start date" => "Start date",
"end date" => "End date",
"recent actions" => "Recent Actions",
"overview" => "Overview",
"views per visit" => "views per visit",
"visits over time" => "Visits Over Time",
"no data" => "No data.",
"visitor map" => "Visitor Map"
]);

@ -0,0 +1,264 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* An array of two-letter to three-letter country codes, from
* http://country.io/iso3.json
*/
$COUNTRY_CODES = array (
'BD' => 'BGD',
'BE' => 'BEL',
'BF' => 'BFA',
'BG' => 'BGR',
'BA' => 'BIH',
'BB' => 'BRB',
'WF' => 'WLF',
'BL' => 'BLM',
'BM' => 'BMU',
'BN' => 'BRN',
'BO' => 'BOL',
'BH' => 'BHR',
'BI' => 'BDI',
'BJ' => 'BEN',
'BT' => 'BTN',
'JM' => 'JAM',
'BV' => 'BVT',
'BW' => 'BWA',
'WS' => 'WSM',
'BQ' => 'BES',
'BR' => 'BRA',
'BS' => 'BHS',
'JE' => 'JEY',
'BY' => 'BLR',
'BZ' => 'BLZ',
'RU' => 'RUS',
'RW' => 'RWA',
'RS' => 'SRB',
'TL' => 'TLS',
'RE' => 'REU',
'TM' => 'TKM',
'TJ' => 'TJK',
'RO' => 'ROU',
'TK' => 'TKL',
'GW' => 'GNB',
'GU' => 'GUM',
'GT' => 'GTM',
'GS' => 'SGS',
'GR' => 'GRC',
'GQ' => 'GNQ',
'GP' => 'GLP',
'JP' => 'JPN',
'GY' => 'GUY',
'GG' => 'GGY',
'GF' => 'GUF',
'GE' => 'GEO',
'GD' => 'GRD',
'GB' => 'GBR',
'GA' => 'GAB',
'SV' => 'SLV',
'GN' => 'GIN',
'GM' => 'GMB',
'GL' => 'GRL',
'GI' => 'GIB',
'GH' => 'GHA',
'OM' => 'OMN',
'TN' => 'TUN',
'JO' => 'JOR',
'HR' => 'HRV',
'HT' => 'HTI',
'HU' => 'HUN',
'HK' => 'HKG',
'HN' => 'HND',
'HM' => 'HMD',
'VE' => 'VEN',
'PR' => 'PRI',
'PS' => 'PSE',
'PW' => 'PLW',
'PT' => 'PRT',
'SJ' => 'SJM',
'PY' => 'PRY',
'IQ' => 'IRQ',
'PA' => 'PAN',
'PF' => 'PYF',
'PG' => 'PNG',
'PE' => 'PER',
'PK' => 'PAK',
'PH' => 'PHL',
'PN' => 'PCN',
'PL' => 'POL',
'PM' => 'SPM',
'ZM' => 'ZMB',
'EH' => 'ESH',
'EE' => 'EST',
'EG' => 'EGY',
'ZA' => 'ZAF',
'EC' => 'ECU',
'IT' => 'ITA',
'VN' => 'VNM',
'SB' => 'SLB',
'ET' => 'ETH',
'SO' => 'SOM',
'ZW' => 'ZWE',
'SA' => 'SAU',
'ES' => 'ESP',
'ER' => 'ERI',
'ME' => 'MNE',
'MD' => 'MDA',
'MG' => 'MDG',
'MF' => 'MAF',
'MA' => 'MAR',
'MC' => 'MCO',
'UZ' => 'UZB',
'MM' => 'MMR',
'ML' => 'MLI',
'MO' => 'MAC',
'MN' => 'MNG',
'MH' => 'MHL',
'MK' => 'MKD',
'MU' => 'MUS',
'MT' => 'MLT',
'MW' => 'MWI',
'MV' => 'MDV',
'MQ' => 'MTQ',
'MP' => 'MNP',
'MS' => 'MSR',
'MR' => 'MRT',
'IM' => 'IMN',
'UG' => 'UGA',
'TZ' => 'TZA',
'MY' => 'MYS',
'MX' => 'MEX',
'IL' => 'ISR',
'FR' => 'FRA',
'IO' => 'IOT',
'SH' => 'SHN',
'FI' => 'FIN',
'FJ' => 'FJI',
'FK' => 'FLK',
'FM' => 'FSM',
'FO' => 'FRO',
'NI' => 'NIC',
'NL' => 'NLD',
'NO' => 'NOR',
'NA' => 'NAM',
'VU' => 'VUT',
'NC' => 'NCL',
'NE' => 'NER',
'NF' => 'NFK',
'NG' => 'NGA',
'NZ' => 'NZL',
'NP' => 'NPL',
'NR' => 'NRU',
'NU' => 'NIU',
'CK' => 'COK',
'XK' => 'XKX',
'CI' => 'CIV',
'CH' => 'CHE',
'CO' => 'COL',
'CN' => 'CHN',
'CM' => 'CMR',
'CL' => 'CHL',
'CC' => 'CCK',
'CA' => 'CAN',
'CG' => 'COG',
'CF' => 'CAF',
'CD' => 'COD',
'CZ' => 'CZE',
'CY' => 'CYP',
'CX' => 'CXR',
'CR' => 'CRI',
'CW' => 'CUW',
'CV' => 'CPV',
'CU' => 'CUB',
'SZ' => 'SWZ',
'SY' => 'SYR',
'SX' => 'SXM',
'KG' => 'KGZ',
'KE' => 'KEN',
'SS' => 'SSD',
'SR' => 'SUR',
'KI' => 'KIR',
'KH' => 'KHM',
'KN' => 'KNA',
'KM' => 'COM',
'ST' => 'STP',
'SK' => 'SVK',
'KR' => 'KOR',
'SI' => 'SVN',
'KP' => 'PRK',
'KW' => 'KWT',
'SN' => 'SEN',
'SM' => 'SMR',
'SL' => 'SLE',
'SC' => 'SYC',
'KZ' => 'KAZ',
'KY' => 'CYM',
'SG' => 'SGP',
'SE' => 'SWE',
'SD' => 'SDN',
'DO' => 'DOM',
'DM' => 'DMA',
'DJ' => 'DJI',
'DK' => 'DNK',
'VG' => 'VGB',
'DE' => 'DEU',
'YE' => 'YEM',
'DZ' => 'DZA',
'US' => 'USA',
'UY' => 'URY',
'YT' => 'MYT',
'UM' => 'UMI',
'LB' => 'LBN',
'LC' => 'LCA',
'LA' => 'LAO',
'TV' => 'TUV',
'TW' => 'TWN',
'TT' => 'TTO',
'TR' => 'TUR',
'LK' => 'LKA',
'LI' => 'LIE',
'LV' => 'LVA',
'TO' => 'TON',
'LT' => 'LTU',
'LU' => 'LUX',
'LR' => 'LBR',
'LS' => 'LSO',
'TH' => 'THA',
'TF' => 'ATF',
'TG' => 'TGO',
'TD' => 'TCD',
'TC' => 'TCA',
'LY' => 'LBY',
'VA' => 'VAT',
'VC' => 'VCT',
'AE' => 'ARE',
'AD' => 'AND',
'AG' => 'ATG',
'AF' => 'AFG',
'AI' => 'AIA',
'VI' => 'VIR',
'IS' => 'ISL',
'IR' => 'IRN',
'AM' => 'ARM',
'AL' => 'ALB',
'AO' => 'AGO',
'AQ' => 'ATA',
'AS' => 'ASM',
'AR' => 'ARG',
'AU' => 'AUS',
'AT' => 'AUT',
'AW' => 'ABW',
'IN' => 'IND',
'AX' => 'ALA',
'AZ' => 'AZE',
'IE' => 'IRL',
'ID' => 'IDN',
'UA' => 'UKR',
'QA' => 'QAT',
'MZ' => 'MOZ',
);

@ -8,6 +8,7 @@
use GeoIp2\Database\Reader;
// Override with a valid public IP when testing on localhost
//$_SERVER['REMOTE_ADDR'] = "206.127.96.82";
try {
@ -71,6 +72,8 @@ try {
$country = $record->country->name;
$region = $record->mostSpecificSubdivision->name;
$city = $record->city->name;
$countrycode = $record->country->isoCode;
$regioncode = $record->mostSpecificSubdivision->isoCode;
$lat = $record->location->latitude;
$lon = $record->location->longitude;
@ -85,6 +88,8 @@ try {
"country" => $country,
"region" => $region,
"city" => $city,
"countrycode" => $countrycode,
"regioncode" => $regioncode,
"lat" => $lat,
"lon" => $lon,
"time" => $time

@ -34,6 +34,24 @@ define("PAGES", [
"static/js/editorparent.js"
]
],
"analytics" => [
"title" => "analytics",
"navbar" => true,
"icon" => "fas fa-chart-bar",
"styles" => [
"static/css/tempusdominus-bootstrap-4.min.css",
"static/css/vertline.css"
],
"scripts" => [
"static/js/moment.min.js",
"static/js/Chart.min.js",
"static/js/topojson.min.js",
"static/js/d3.min.js",
"static/js/datamaps.all.min.js",
"static/js/tempusdominus-bootstrap-4.min.js",
"static/js/analy_reports.js"
]
],
"404" => [
"title" => "404 error"
]

@ -0,0 +1,269 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
$select_filter = [];
if (!is_empty($VARS['siteid'])) {
if ($database->has('sites', ['siteid' => $VARS['siteid']])) {
$select_filter["siteid"] = $VARS['siteid'];
}
}
if (!is_empty($VARS['after'])) {
if (strtotime($VARS['after']) !== FALSE) {
$select_filter["time[>]"] = date("Y-m-d H:i:s", strtotime($VARS['after']));
}
}
if (!is_empty($VARS['before'])) {
if (strtotime($VARS['before']) !== FALSE) {
$select_filter["time[<]"] = date("Y-m-d H:i:s", strtotime($VARS['before']));
}
}
$where = [];
if (count($select_filter) == 1) {
$where = $select_filter;
} else if (count($select_filter) > 1) {
$where = ["AND" => $select_filter];
}
$where["LIMIT"] = 1000;
$where["ORDER"] = ["time" => "DESC"];
$records = $database->select("analytics", [
"[>]sites" => ["siteid" => "siteid"],
"[>]pages" => ["pageid" => "pageid"],
], [
"analytics.siteid", "analytics.pageid", "uuid", "country", "region", "city",
"countrycode", "regioncode",
"lat", "lon", "time", "pages.title (pagetitle)", "pages.slug (pageslug)",
"sites.sitename"
], $where);
?>
<!-- Filter bar -->
<div class="card p-2">
<form class="form-inline" action="app.php" method="GET">
<button type="submit" class="btn btn-primary"><i class="fas fa-sync"></i></button>
<label for="siteid_select" class="sr-only"><?php lang("filter by site") ?></label>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-sitemap"></i></span>
</div>
<select name="siteid" class="form-control pr-4" id="siteid_select">
<option value=""><?php lang("all sites"); ?></option>
<?php
$sites = $database->select("sites", ["siteid", "sitename"]);
foreach ($sites as $s) {
$selected = "";
if (!empty($select_filter["siteid"]) && $select_filter["siteid"] == $s['siteid']) {
$selected = "selected";
}
?>
<option value="<?php echo $s['siteid']; ?>" <?php echo $selected; ?>><?php echo $s['sitename']; ?></option>
<?php
}
?>
</select>
</div>
<span class="vertline d-none d-lg-inline"></span>
<label for="date_after" class="sr-only"><?php lang("filter after date") ?></label>
<label for="date_before" class="sr-only"><?php lang("filter before date") ?></label>
<div class="input-group mx-2">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-calendar"></i></span>
</div>
<input type="text" id="date_after" name="after" value="<?php echo htmlspecialchars($VARS['after']); ?>" class="form-control" placeholder="<?php lang("start date"); ?>" data-toggle="datetimepicker" data-target="#date_after" />
<div class="input-group-prepend input-group-append">
<span class="input-group-text"><i class="fas fa-caret-right"></i></span>
</div>
<input type="text" id="date_before" name="before" value="<?php echo htmlspecialchars($VARS['before']); ?>" class="form-control" placeholder="<?php lang("end date"); ?>" data-toggle="datetimepicker" data-target="#date_before" />
</div>
<input type="hidden" name="page" value="analytics" />
<button type="submit" class="btn btn-secondary"><i class="fas fa-filter"></i> <?php lang("filter"); ?></button>
</form>
</div>
<!-- Data views -->
<?php
if (count($records) > 0) {
?>
<div class="row mt-3">
<div class="col-12 col-sm-6 col-md-6 col-lg-4 mb-4">
<!-- Overview -->
<div class="card">
<div class="card-body">
<h4 class="card-title"><?php lang("overview"); ?></h4>
<?php
$uuids = [];
foreach ($records as $r) {
if (!in_array($r["uuid"], $uuids)) {
$uuids[] = $r["uuid"];
}
}
$visits = count($uuids);
$views = count($records);
$ratio = round($views / $visits, 1);
?>
<h5>
<i class="fas fa-users fa-fw"></i> <?php echo $visits; ?> <?php lang("visits") ?> <br />
<i class="fas fa-eye fa-fw"></i> <?php echo $views; ?> <?php lang("page views") ?> <br />
<i class="fas fa-percent fa-fw"></i> <?php echo $ratio; ?> <?php lang("views per visit") ?>
</h5>
</div>
</div>
<!-- Visits Over Time -->
<div class="card mt-4">
<div class="card-body">
<h4 class="card-title"><?php lang("visits over time"); ?></h4>
<?php
$format = "Y-m-00 00:00:00";
$max = $records[0];
$min = $records[count($records) - 1];
$diff = strtotime($max['time']) - strtotime($min['time']);
if ($diff < 60 * 60) { // 1 hour
$format = "Y-m-d H:i:00";
} else if ($diff < 60 * 60 * 24 * 3) { // 3 days
$format = "Y-m-d H:00:00";
} else if ($diff < 60 * 60 * 24 * 60) { // 30 days
$format = "Y-m-d 00:00:00";
}
$counted = [];
foreach ($records as $r) {
$rf = date($format, strtotime($r['time']));
if (array_key_exists($rf, $counted)) {
$counted[$rf] ++;
} else {
$counted[$rf] = 1;
}
}
?>
<script nonce="<?php echo $SECURE_NONCE; ?>">
var visitsOverTimeData = [
<?php foreach ($counted as $d => $c) { ?>
{
x: "<?php echo $d; ?>",
y: <?php echo $c; ?>
},
<?php } ?>
];
</script>
<div class="w-100 position-relative">
<canvas id="visitsOverTime"></canvas>
</div>
</div>
</div>
</div>
<!-- Recent Actions -->
<div class="col-12 col-sm-6 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<h4 class="card-title"><?php lang("recent actions"); ?></h4>
</div>
<div class="list-group">
<?php
$max = 10;
$i = 0;
foreach ($records as $r) {
$i++;
if ($i > $max) {
break;
}
?>
<div class="list-group-item">
<div>
<div><i class="fas fa-user fa-fw"></i> <?php echo substr($r["uuid"], 0, 8); ?></div>
</div>
<div class="row justify-content-between">
<div class="col-12 col-sm-6 d-flex flex-column">
<span><i class="fas fa-clock fa-fw"></i> <?php echo date("g:i A", strtotime($r["time"])); ?></span>
<span><i class="fas fa-file fa-fw"></i> <?php echo $r["pagetitle"]; ?></span>
</div>
<div class="col-12 col-sm-6 d-flex flex-column">
<span><i class="fas fa-calendar fa-fw"></i> <?php echo date("M j Y", strtotime($r["time"])); ?></span>
<span><i class="fas fa-sitemap fa-fw"></i> <?php echo $r["sitename"]; ?></span>
</div>
</div>
<div>
<div><i class="fas fa-globe fa-fw"></i> <?php echo $r["country"]; ?></div>
<div><i class="fas fa-map-marker fa-fw"></i> <?php echo $r["city"] . ", " . $r["region"]; ?></div>
</div>
</div>
<?php
}
?>
</div>
</div>
</div>
<!-- Visitor Map -->
<div class="col-12 col-sm-6 col-md-6 col-lg-4">
<div class="card">
<div class="card-body">
<h4 class="card-title"><?php lang("visitor map"); ?></h4>
<?php
require_once __DIR__ . "/../lib/countries_2_3.php";
$countries = [];
$states = [];
foreach ($records as $r) {
if (array_key_exists($COUNTRY_CODES[$r['countrycode']], $countries)) {
$countries[$COUNTRY_CODES[$r['countrycode']]] ++;
} else {
$countries[$COUNTRY_CODES[$r['countrycode']]] = 1;
}
if ($r['countrycode'] === "US") {
if (array_key_exists($r['regioncode'], $states)) {
$states[$r['regioncode']] ++;
} else {
$states[$r['regioncode']] = 1;
}
}
}
$countrymapdata = [];
foreach ($countries as $id => $count) {
$countrymapdata[] = [$id, $count];
}
$statemapdata = [];
foreach ($states as $id => $count) {
$statemapdata[] = [$id, $count];
}
?>
<script nonce="<?php echo $SECURE_NONCE; ?>">
visitorMap_Countries = <?php echo json_encode($countrymapdata); ?>;
visitorMap_States = <?php echo json_encode($statemapdata); ?>;
</script>
<div class="w-100" id="visitorMapWorld"></div>
<div class="w-100" id="visitorMapUSA"></div>
</div>
</div>
</div>
</div>
<?php
} else {
?>
<div class="row mt-3 justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
<div class="card">
<div class="card-body text-center">
<i class="fas fa-info-circle"></i> <?php lang("no data"); ?>
</div>
</div>
</div>
</div>
<?php
}
?>

@ -52,7 +52,7 @@ if ($_SESSION['mobile'] === TRUE) {
. "frame-src 'self'; "
. "font-src 'self'; "
. "connect-src *; "
. "style-src 'self' 'nonce-$SECURE_NONCE' $captcha_server; "
. "style-src 'self' 'unsafe-inline' $captcha_server; "
. "script-src 'self' 'nonce-$SECURE_NONCE' $captcha_server");
}

@ -0,0 +1,204 @@
/*@preserve
* Tempus Dominus Bootstrap4 v5.0.0-alpha16 (https://tempusdominus.github.io/bootstrap-4/)
* Copyright 2016-2018 Jonathan Peterson
* Licensed under MIT (https://github.com/tempusdominus/bootstrap-3/blob/master/LICENSE)
*/
.sr-only, .bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after, .bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after, .bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after, .bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after, .bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after, .bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after, .bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after, .bootstrap-datetimepicker-widget .btn[data-action="clear"]::after, .bootstrap-datetimepicker-widget .btn[data-action="today"]::after, .bootstrap-datetimepicker-widget .picker-switch::after, .bootstrap-datetimepicker-widget table th.prev::after, .bootstrap-datetimepicker-widget table th.next::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0; }
.bootstrap-datetimepicker-widget {
list-style: none; }
.bootstrap-datetimepicker-widget.dropdown-menu {
display: block;
margin: 2px 0;
padding: 4px;
width: 14rem; }
@media (min-width: 576px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em; } }
@media (min-width: 768px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em; } }
@media (min-width: 992px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em; } }
.bootstrap-datetimepicker-widget.dropdown-menu:before, .bootstrap-datetimepicker-widget.dropdown-menu:after {
content: '';
display: inline-block;
position: absolute; }
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
top: -7px;
left: 7px; }
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
top: -6px;
left: 8px; }
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #ccc;
border-top-color: rgba(0, 0, 0, 0.2);
bottom: -7px;
left: 6px; }
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
bottom: -6px;
left: 7px; }
.bootstrap-datetimepicker-widget.dropdown-menu.float-right:before {
left: auto;
right: 6px; }
.bootstrap-datetimepicker-widget.dropdown-menu.float-right:after {
left: auto;
right: 7px; }
.bootstrap-datetimepicker-widget .list-unstyled {
margin: 0; }
.bootstrap-datetimepicker-widget a[data-action] {
padding: 6px 0; }
.bootstrap-datetimepicker-widget a[data-action]:active {
box-shadow: none; }
.bootstrap-datetimepicker-widget .timepicker-hour, .bootstrap-datetimepicker-widget .timepicker-minute, .bootstrap-datetimepicker-widget .timepicker-second {
width: 54px;
font-weight: bold;
font-size: 1.2em;
margin: 0; }
.bootstrap-datetimepicker-widget button[data-action] {
padding: 6px; }
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
content: "Increment Hours"; }
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
content: "Increment Minutes"; }
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
content: "Decrement Hours"; }
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
content: "Decrement Minutes"; }
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
content: "Show Hours"; }
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
content: "Show Minutes"; }
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
content: "Toggle AM/PM"; }
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
content: "Clear the picker"; }
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
content: "Set the date to today"; }
.bootstrap-datetimepicker-widget .picker-switch {
text-align: center; }
.bootstrap-datetimepicker-widget .picker-switch::after {
content: "Toggle Date and Time Screens"; }
.bootstrap-datetimepicker-widget .picker-switch td {
padding: 0;
margin: 0;
height: auto;
width: auto;
line-height: inherit; }
.bootstrap-datetimepicker-widget .picker-switch td span {
line-height: 2.5;
height: 2.5em;
width: 100%; }
.bootstrap-datetimepicker-widget table {
width: 100%;
margin: 0; }
.bootstrap-datetimepicker-widget table td,
.bootstrap-datetimepicker-widget table th {
text-align: center;
border-radius: 0.25rem; }
.bootstrap-datetimepicker-widget table th {
height: 20px;
line-height: 20px;
width: 20px; }
.bootstrap-datetimepicker-widget table th.picker-switch {
width: 145px; }
.bootstrap-datetimepicker-widget table th.disabled, .bootstrap-datetimepicker-widget table th.disabled:hover {
background: none;
color: #868e96;
cursor: not-allowed; }
.bootstrap-datetimepicker-widget table th.prev::after {
content: "Previous Month"; }
.bootstrap-datetimepicker-widget table th.next::after {
content: "Next Month"; }
.bootstrap-datetimepicker-widget table thead tr:first-child th {
cursor: pointer; }
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {
background: #e9ecef; }
.bootstrap-datetimepicker-widget table td {
height: 54px;
line-height: 54px;
width: 54px; }
.bootstrap-datetimepicker-widget table td.cw {
font-size: .8em;
height: 20px;
line-height: 20px;
color: #868e96; }
.bootstrap-datetimepicker-widget table td.day {
height: 20px;
line-height: 20px;
width: 20px; }
.bootstrap-datetimepicker-widget table td.day:hover, .bootstrap-datetimepicker-widget table td.hour:hover, .bootstrap-datetimepicker-widget table td.minute:hover, .bootstrap-datetimepicker-widget table td.second:hover {
background: #e9ecef;
cursor: pointer; }
.bootstrap-datetimepicker-widget table td.old, .bootstrap-datetimepicker-widget table td.new {
color: #868e96; }
.bootstrap-datetimepicker-widget table td.today {
position: relative; }
.bootstrap-datetimepicker-widget table td.today:before {
content: '';
display: inline-block;
border: solid transparent;
border-width: 0 0 7px 7px;
border-bottom-color: #007bff;
border-top-color: rgba(0, 0, 0, 0.2);
position: absolute;
bottom: 4px;
right: 4px; }
.bootstrap-datetimepicker-widget table td.active, .bootstrap-datetimepicker-widget table td.active:hover {
background-color: #007bff;
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); }
.bootstrap-datetimepicker-widget table td.active.today:before {
border-bottom-color: #fff; }
.bootstrap-datetimepicker-widget table td.disabled, .bootstrap-datetimepicker-widget table td.disabled:hover {
background: none;
color: #868e96;
cursor: not-allowed; }
.bootstrap-datetimepicker-widget table td span {
display: inline-block;
width: 54px;
height: 54px;
line-height: 54px;
margin: 2px 1.5px;
cursor: pointer;
border-radius: 0.25rem; }
.bootstrap-datetimepicker-widget table td span:hover {
background: #e9ecef; }
.bootstrap-datetimepicker-widget table td span.active {
background-color: #007bff;
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); }
.bootstrap-datetimepicker-widget table td span.old {
color: #868e96; }
.bootstrap-datetimepicker-widget table td span.disabled, .bootstrap-datetimepicker-widget table td span.disabled:hover {
background: none;
color: #868e96;
cursor: not-allowed; }
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
height: 27px;
line-height: 27px; }
.input-group [data-toggle="datetimepicker"] {
cursor: pointer; }

@ -0,0 +1,13 @@
/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.vertline {
margin: 1px 3px;
height: 100%;
max-height: 50px;
width: 1px;
background-color: rgba(0,0,0,.20);
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,159 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Note: This script is *not* called "analytics.js" because adblockers.
*/
$(function () {
$('#date_after').datetimepicker({
format: "MMM D YYYY h:mm A",
useCurrent: false,
icons: {
time: "fas fa-clock",
date: "fas fa-calendar",
up: "fas fa-arrow-up",
down: "fas fa-arrow-down"
}
});
$('#date_before').datetimepicker({
format: "MMM D YYYY h:mm A",
useCurrent: true,
icons: {
time: "fas fa-clock",
date: "fas fa-calendar",
up: "fas fa-arrow-up",
down: "fas fa-arrow-down"
}
});
});
var visitsOverTime = new Chart($("#visitsOverTime"), {
type: 'line',
data: {
datasets: [{
data: visitsOverTimeData
}],
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
type: 'time',
}],
yAxes: [{
type: 'linear',
ticks: {
beginAtZero: true,
callback: function (value) {
if (Number.isInteger(value)) {
return value;
}
},
}
}]
},
tooltips: {
displayColors: false,
callbacks: {
title: function (item) {
var lbl = item[0].xLabel;
if (lbl.endsWith("-00 00:00:00")) {
return moment(lbl).format("MMM YYYY");
} else if (lbl.endsWith(" 00:00:00")) {
return moment(lbl).format("MMM D YYYY");
} else if (lbl.endsWith(":00:00")) {
return moment(lbl).format("MMM D YYYY ha");
} else if (lbl.endsWith(":00")) {
return moment(lbl).format("MMM D YYYY h:mma");
}
return item[0].xLabel;
},
label: function (item) {
return item.yLabel + " visits";
}
}
},
elements: {
line: {
borderWidth: 2,
borderColor: "#ff0000",
backgroundColor: "#ffffff00",
tension: 0
}
}
}
});
function getVisitorMapData(source) {
var visitorMapDataset = {};
var onlyValues = source.map(function (obj) {
return obj[1];
});
var minValue = Math.min.apply(null, onlyValues);
var maxValue = Math.max.apply(null, onlyValues);
var paletteScale = d3.scale.linear()
.domain([minValue, maxValue])
.range(["#A5D6A7", "#124016"]); // blue color
source.forEach(function (item) { //
// item example value ["USA", 70]
var iso = item[0],
value = item[1];
visitorMapDataset[iso] = {numberOfThings: value, fillColor: paletteScale(value)};
});
return visitorMapDataset;
}
var visitorMap;
function showVisitorMap(data, scope, containerid) {
$("visitorMap").html("");
visitorMap = new Datamap({
element: document.getElementById(containerid),
scope: scope,
responsive: true,
fills: {defaultFill: '#F5F5F5'},
data: data,
geographyConfig: {
borderColor: '#DEDEDE',
highlightBorderWidth: 2,
// don't change color on mouse hover
highlightFillColor: function (geo) {
return geo['fillColor'] || '#E8F5E9';
},
// only change border
highlightBorderColor: '#00C853',
// show desired information in tooltip
popupTemplate: function (geo, data) {
// don't show tooltip if country don't present in dataset
if (!data) {
//return;
}
// tooltip content
return ['<div class="hoverinfo">',
'<strong>', geo.properties.name, '</strong>',
'<br>Visits: <strong>', data.numberOfThings, '</strong>',
'</div>'].join('');
}
}
});
}
showVisitorMap(getVisitorMapData(visitorMap_Countries), 'world', 'visitorMapWorld');
showVisitorMap(getVisitorMapData(visitorMap_States), 'usa', 'visitorMapUSA');
$(window).on('resize', function () {
visitorMap.resize();
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save