353 lines
19 KiB
PHP
353 lines
19 KiB
PHP
<div id="dashboard" class="container">
|
|
|
|
<!-- Search with welcome message -->
|
|
<?php
|
|
$username = $login->username();
|
|
$user = new User($username);
|
|
$name = '';
|
|
if ($user->nickname()) {
|
|
$name = $user->nickname();
|
|
} elseif ($user->firstName()) {
|
|
$name = $user->firstName();
|
|
}
|
|
?>
|
|
<div class="quick-search-trigger mb-4" id="searchTrigger">
|
|
<div class="quick-search-icon">
|
|
<span class="fa fa-hand-spock-o" id="hello-icon"></span>
|
|
</div>
|
|
<span class="quick-search-text">
|
|
<span id="hello-text"><?php echo $L->g('welcome') . ($name ? ', ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') : '') ?></span>
|
|
<small class="quick-search-hint"><?php $L->p('click-here-for-quick-search') ?></small>
|
|
</span>
|
|
<span class="quick-search-shortcut">⌘K</span>
|
|
</div>
|
|
<script>
|
|
$(document).ready(function() {
|
|
var date = new Date()
|
|
var hours = date.getHours()
|
|
var icon, greeting
|
|
var suffix = <?php echo json_encode($name ? ', ' . $name : '', JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?>
|
|
if (hours >= 6 && hours < 12) {
|
|
icon = 'fa-sun-o'; greeting = <?php echo json_encode($L->g('good-morning'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
|
|
} else if (hours >= 12 && hours < 18) {
|
|
icon = 'fa-sun-o'; greeting = <?php echo json_encode($L->g('good-afternoon'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
|
|
} else if (hours >= 18 && hours < 22) {
|
|
icon = 'fa-moon-o'; greeting = <?php echo json_encode($L->g('good-evening'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
|
|
} else {
|
|
icon = 'fa-moon-o'; greeting = <?php echo json_encode($L->g('good-night'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?> + suffix
|
|
}
|
|
$('#hello-icon').attr('class', 'fa ' + icon)
|
|
$('#hello-text').text(greeting)
|
|
});
|
|
</script>
|
|
|
|
<!-- Quick Search Modal -->
|
|
<div class="quick-search-modal" id="searchModal">
|
|
<div class="quick-search-overlay" id="searchOverlay"></div>
|
|
<div class="quick-search-content">
|
|
<div class="quick-search-header">
|
|
<span class="fa fa-search"></span>
|
|
<input type="text" id="jsclippy" class="quick-search-input" placeholder="<?php $L->p('search-placeholder') ?>">
|
|
</div>
|
|
<div id="searchResults" class="quick-search-results"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
var searchInput = $('#jsclippy');
|
|
var searchResults = $('#searchResults');
|
|
var modal = $('#searchModal');
|
|
var trigger = $('#searchTrigger');
|
|
var overlay = $('#searchOverlay');
|
|
var searchTimeout;
|
|
|
|
function openSearch() {
|
|
modal.addClass('active');
|
|
$('body').css('overflow', 'hidden');
|
|
setTimeout(function() {
|
|
searchInput.focus();
|
|
}, 150);
|
|
}
|
|
|
|
function closeSearch() {
|
|
modal.removeClass('active');
|
|
$('body').css('overflow', '');
|
|
searchInput.val('');
|
|
searchResults.empty();
|
|
}
|
|
|
|
function performSearch(query) {
|
|
if (!query) {
|
|
searchResults.empty();
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: HTML_PATH_ADMIN_ROOT + "ajax/clippy",
|
|
data: { query: query },
|
|
success: function(data) {
|
|
searchResults.empty();
|
|
|
|
if (data.results && data.results.length > 0) {
|
|
data.results.forEach(function(item) {
|
|
var resultHtml = '';
|
|
if (item.type == 'menu') {
|
|
resultHtml = '<a href="' + item.url + '" class="search-suggestion">';
|
|
resultHtml += '<span class="fa fa-' + item.icon + '"></span>' + item.text + '</a>';
|
|
} else {
|
|
resultHtml = '<div class="search-suggestion">';
|
|
resultHtml += '<div class="search-suggestion-item">' + item.text + ' <span class="badge badge-pill badge-light">' + item.type + '</span></div>';
|
|
resultHtml += '<div class="search-suggestion-options">';
|
|
resultHtml += '<a target="_blank" href="' + DOMAIN_PAGES + item.id + '"><?php $L->p('view') ?></a>';
|
|
resultHtml += '<a class="ml-2" href="' + DOMAIN_ADMIN + 'edit-content/' + item.id + '"><?php $L->p('edit') ?></a>';
|
|
resultHtml += '</div></div>';
|
|
}
|
|
searchResults.append(resultHtml);
|
|
});
|
|
} else {
|
|
searchResults.html('<div class="search-no-results"><?php $L->p("no-results-found") ?></div>');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
searchInput.on('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
var query = $(this).val();
|
|
searchTimeout = setTimeout(function() {
|
|
performSearch(query);
|
|
}, 300);
|
|
});
|
|
|
|
trigger.on('click', openSearch);
|
|
overlay.on('click', closeSearch);
|
|
|
|
$(document).on('keydown', function(e) {
|
|
if (e.key === 'Escape' && modal.hasClass('active')) {
|
|
closeSearch();
|
|
}
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
openSearch();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<!-- Dashboard Metric Cards -->
|
|
<div class="container px-0">
|
|
<div class="row">
|
|
|
|
<!-- Content Metrics Card -->
|
|
<div class="col-lg col-12 mb-4">
|
|
<div class="card metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<h5 class="card-title mb-0"><?php $L->p('Content') ?></h5>
|
|
</div>
|
|
<div class="mt-3 metric-card-list">
|
|
<div class="list-group list-group-flush">
|
|
<a href="<?php echo HTML_PATH_ADMIN_ROOT.'content' ?>" class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span><?php $L->p('Published') ?></span>
|
|
<span class="badge badge-primary badge-pill"><?php echo count($pages->getPublishedDB()); ?></span>
|
|
</a>
|
|
<a href="<?php echo HTML_PATH_ADMIN_ROOT.'content#draft' ?>" class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span><?php $L->p('Drafts') ?></span>
|
|
<span class="badge badge-primary badge-pill"><?php echo count($pages->getDraftDB()); ?></span>
|
|
</a>
|
|
<a href="<?php echo HTML_PATH_ADMIN_ROOT.'content#scheduled' ?>" class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span><?php $L->p('Scheduled') ?></span>
|
|
<span class="badge badge-primary badge-pill"><?php echo count($pages->getScheduledDB()); ?></span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<?php
|
|
// Categories Card - Only show if Categories plugin is active
|
|
if (pluginActivated('pluginCategories')) {
|
|
$categoryList = $categories->keys();
|
|
?>
|
|
<!-- Categories Card -->
|
|
<div class="col-lg col-12 mb-4">
|
|
<div class="card metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<h5 class="card-title mb-0"><?php $L->p('Categories') ?></h5>
|
|
</div>
|
|
<div class="mt-3 metric-card-list">
|
|
<?php if (!empty($categoryList)): ?>
|
|
<div class="list-group list-group-flush">
|
|
<?php foreach ($categoryList as $categoryKey):
|
|
$category = new Category($categoryKey);
|
|
$pageCount = count($category->pages());
|
|
?>
|
|
<a href="<?php echo HTML_PATH_ADMIN_ROOT . 'edit-category/' . $categoryKey ?>" class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span><?php echo $category->name() ?></span>
|
|
<span class="badge badge-primary badge-pill"><?php echo $pageCount ?></span>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<p class="text-muted text-center py-3"><?php $L->p('No categories') ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php } ?>
|
|
|
|
<?php
|
|
// Tags Card - Only show if Tags plugin is active
|
|
if (pluginActivated('pluginTags')) {
|
|
$tagList = $tags->keys();
|
|
?>
|
|
<!-- Tags Card -->
|
|
<div class="col-lg col-12 mb-4">
|
|
<div class="card metric-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<h5 class="card-title mb-0"><?php $L->p('Tags') ?></h5>
|
|
</div>
|
|
<div class="mt-3 metric-card-list">
|
|
<?php if (!empty($tagList)): ?>
|
|
<div class="list-group list-group-flush">
|
|
<?php foreach ($tagList as $tagKey):
|
|
$tag = new Tag($tagKey);
|
|
$pageCount = count($tag->pages());
|
|
?>
|
|
<a href="<?php echo HTML_PATH_ADMIN_ROOT . 'content/tag/' . $tagKey ?>" class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span><?php echo $tag->name() ?></span>
|
|
<span class="badge badge-primary badge-pill"><?php echo $pageCount ?></span>
|
|
</a>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<p class="text-muted text-center py-3"><?php $L->p('No tags') ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php } ?>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
// Analytics Section - Only show if Visits Stats plugin is active
|
|
$visitsStats = getPlugin('pluginVisitsStats');
|
|
if ($visitsStats && $visitsStats->installed()):
|
|
$currentDate = Date::current('Y-m-d');
|
|
$visitsToday = $visitsStats->visits($currentDate);
|
|
$uniqueVisitors = $visitsStats->uniqueVisitors($currentDate);
|
|
$weekData = $visitsStats->getLastDaysData(7);
|
|
?>
|
|
<!-- Analytics Section -->
|
|
<div class="analytics-section mb-4">
|
|
<ul class="list-group list-group-striped b-0 mb-3">
|
|
<li class="list-group-item">
|
|
<h4 class="m-0"><?php $L->p('Analytics') ?></h4>
|
|
</li>
|
|
</ul>
|
|
<div class="row align-items-center">
|
|
<div class="col-lg-3 col-12 mb-3 mb-lg-0">
|
|
<div class="row text-center">
|
|
<div class="col-4 col-lg-12 mb-0 mb-lg-4">
|
|
<div class="metric-value"><?php echo $visitsToday; ?></div>
|
|
<div class="metric-label"><?php $L->p('Visits Today') ?></div>
|
|
</div>
|
|
<div class="col-4 col-lg-12 mb-0 mb-lg-4">
|
|
<div class="metric-value"><?php echo $uniqueVisitors; ?></div>
|
|
<div class="metric-label"><?php $L->p('Unique Visitors') ?></div>
|
|
</div>
|
|
<div class="col-4 col-lg-12">
|
|
<div class="metric-value"><?php echo $weekData['total']; ?></div>
|
|
<div class="metric-label"><?php $L->p('7-Day Total') ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-9 col-12">
|
|
<canvas id="analytics-chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
(function() {
|
|
var ctx = document.getElementById('analytics-chart');
|
|
if (!ctx || typeof Chart === 'undefined') { return; }
|
|
new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode($weekData['labels']); ?>,
|
|
datasets: [{
|
|
label: <?php echo json_encode($L->g('unique-visitors'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?>,
|
|
backgroundColor: 'rgba(0,120,212,0.45)',
|
|
borderColor: 'rgba(0,120,212,0.75)',
|
|
borderWidth: 1,
|
|
data: <?php echo json_encode($weekData['unique']); ?>
|
|
}, {
|
|
label: <?php echo json_encode($L->g('visits-today'), JSON_HEX_TAG | JSON_UNESCAPED_UNICODE) ?>,
|
|
backgroundColor: 'rgba(148,163,184,0.5)',
|
|
borderColor: 'rgba(100,116,139,0.8)',
|
|
borderWidth: 1,
|
|
data: <?php echo json_encode($weekData['visits']); ?>
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: true,
|
|
aspectRatio: 4,
|
|
legend: {
|
|
display: true,
|
|
position: 'bottom',
|
|
labels: { fontSize: 11, boxWidth: 12, fontColor: '#475569' }
|
|
},
|
|
scales: {
|
|
yAxes: [{
|
|
ticks: { beginAtZero: true, stepSize: 1, fontColor: '#94A3B8', fontSize: 11 },
|
|
gridLines: { color: 'rgba(0,0,0,0.05)', zeroLineColor: 'rgba(0,0,0,0.1)' }
|
|
}],
|
|
xAxes: [{
|
|
ticks: { fontColor: '#94A3B8', fontSize: 11 },
|
|
gridLines: { display: false }
|
|
}]
|
|
},
|
|
tooltips: { mode: 'index', intersect: false }
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<!-- Notifications -->
|
|
<div class="mt-4">
|
|
<ul class="list-group list-group-striped b-0">
|
|
<li class="list-group-item">
|
|
<h4 class="m-0"><?php $L->p('Notifications') ?></h4>
|
|
</li>
|
|
<?php
|
|
$logs = array_slice($syslog->db, 0, NOTIFICATIONS_AMOUNT);
|
|
foreach ($logs as $log) {
|
|
$phrase = $L->g($log['dictionaryKey']);
|
|
echo '<li class="list-group-item">';
|
|
echo $phrase;
|
|
if (!empty($log['notes'])) {
|
|
echo ' « <b>' . htmlspecialchars($log['notes'], ENT_QUOTES, 'UTF-8') . '</b> »';
|
|
}
|
|
echo '<br><span class="notification-date"><small>';
|
|
echo Date::format($log['date'], DB_DATE_FORMAT, NOTIFICATIONS_DATE_FORMAT);
|
|
echo ' [ ' . htmlspecialchars($log['username'], ENT_QUOTES, 'UTF-8') . ' ]';
|
|
echo '</small></span>';
|
|
echo '</li>';
|
|
}
|
|
?>
|
|
</ul>
|
|
</div>
|
|
|
|
</div>
|