first commit
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<!-- DataTables CSS -->
|
||||
<link href="{{ url_for('static', filename='css/dataTables.bootstrap5.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/buttons.bootstrap5.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/buttons.dataTables.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/colVis.dataTables.min.css') }}" rel="stylesheet">
|
||||
<!-- DateRangePicker CSS -->
|
||||
<link href="{{ url_for('static', filename='css/daterangepicker.css') }}" rel="stylesheet">
|
||||
<!-- Custom DateRangePicker dark theme styles -->
|
||||
<style>
|
||||
/* Dark theme for date picker */
|
||||
.daterangepicker {
|
||||
background-color: #212529;
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
.daterangepicker .calendar-table {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
}
|
||||
.daterangepicker td.available:hover,
|
||||
.daterangepicker th.available:hover {
|
||||
background-color: #495057;
|
||||
}
|
||||
.daterangepicker td.active,
|
||||
.daterangepicker td.active:hover {
|
||||
background-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
/* In-between dates in the selected range - lighter blue */
|
||||
.daterangepicker td.in-range {
|
||||
background-color: #82b1ff; /* Lighter blue */
|
||||
color: #212529; /* Darker text for better contrast */
|
||||
}
|
||||
.daterangepicker td.in-range:hover {
|
||||
background-color: #75a7f7; /* Slightly darker when hovering */
|
||||
color: #212529;
|
||||
}
|
||||
.daterangepicker .calendar-table .next span,
|
||||
.daterangepicker .calendar-table .prev span {
|
||||
border-color: #f8f9fa;
|
||||
}
|
||||
.daterangepicker .ranges li:hover,
|
||||
.daterangepicker .ranges li.active {
|
||||
background-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
.daterangepicker .ranges li {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
border-bottom-color: #212529;
|
||||
}
|
||||
.daterangepicker:before {
|
||||
border-bottom-color: #495057;
|
||||
}
|
||||
/* Calendar header and weekday styling */
|
||||
.daterangepicker .calendar-table th {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Month name */
|
||||
.daterangepicker .month {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Off days (not in current month) */
|
||||
.daterangepicker td.off {
|
||||
color: #6c757d;
|
||||
}
|
||||
/* Input boxes */
|
||||
.daterangepicker input.input-mini {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Time picker */
|
||||
.daterangepicker .calendar-time select {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Apply and cancel buttons */
|
||||
.daterangepicker .drp-buttons {
|
||||
border-top-color: #495057;
|
||||
}
|
||||
.daterangepicker .drp-buttons .btn {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Input fields focus */
|
||||
.daterangepicker input.input-mini:focus {
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
/* Time inputs container */
|
||||
.daterangepicker .calendar-time {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
}
|
||||
/* Make the export buttons more visible */
|
||||
.dt-buttons {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
display: inline-block !important;
|
||||
}
|
||||
.dt-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
/* Page title and export buttons container */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.page-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/* For smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.export-buttons {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Column visibility dropdown styles */
|
||||
.dropdown-menu {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
}
|
||||
.dropdown-item {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
.dropdown-item:hover, .dropdown-item:focus {
|
||||
background-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
.form-check-input:checked {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.form-check-label {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
#column-visibility-menu {
|
||||
min-width: 200px;
|
||||
}
|
||||
.column-checkbox {
|
||||
padding: 0.375rem 1rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">Login Events Dashboard</h2>
|
||||
<div class="export-buttons">
|
||||
<div class="btn-group me-2" role="group">
|
||||
<button id="column-visibility" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Columns
|
||||
</button>
|
||||
<ul class="dropdown-menu" id="column-visibility-menu">
|
||||
<!-- Column visibility checkboxes will be populated by JavaScript -->
|
||||
</ul>
|
||||
</div>
|
||||
<button id="export-csv" class="btn btn-secondary btn-sm">Export CSV</button>
|
||||
<button id="export-excel" class="btn btn-success btn-sm">Export Excel</button>
|
||||
<button id="print-table" class="btn btn-info btn-sm">Print</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3 mb-3">
|
||||
<div class="card-body">
|
||||
<form id="dateRangeForm" method="GET" action="{{ url_for('frontend.dashboard') }}">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label for="daterange" class="form-label">Date Range:</label>
|
||||
<input type="text" id="daterange" name="daterange" class="form-control"
|
||||
value="{{ start_date.strftime('%Y-%m-%d %H:%M') if start_date else '' }} - {{ end_date.strftime('%Y-%m-%d %H:%M') if end_date else '' }}"/>
|
||||
</div>
|
||||
{% if companies %}
|
||||
<div class="col-md-3">
|
||||
<label for="company_id" class="form-label">Company:</label>
|
||||
<select class="form-select" id="company_id" name="company_id">
|
||||
<option value="">All Companies</option>
|
||||
{% for company in companies %}
|
||||
<option value="{{ company.id }}" {% if selected_company_id == company.id %}selected{% endif %}>
|
||||
{{ company.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary">Apply Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table id="logsTable" class="table table-striped table-bordered" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event Type</th>
|
||||
<th>User Name</th>
|
||||
<th>Computer Name</th>
|
||||
<th>IP Address</th>
|
||||
<th>Site</th>
|
||||
<th>Timestamp</th>
|
||||
{% if current_user.is_global_admin() and not selected_company_id %}
|
||||
<th>Company</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.event_type }}</td>
|
||||
<td>{{ log.user_name }}</td>
|
||||
<td>{{ log.computer_name }}</td>
|
||||
<td>{{ log.ip_address }}</td>
|
||||
<td>{{ log.api_key.description if log.api_key else 'N/A' }}</td>
|
||||
<td data-order="{{ log.timestamp.strftime('%Y%m%d%H%M%S') }}">
|
||||
{{ log.timestamp|format_datetime }}
|
||||
</td>
|
||||
{% if current_user.is_global_admin() and not selected_company_id %}
|
||||
<td>{{ log.company.name if log.company else 'N/A' }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<!-- DataTables JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap5.min.js') }}"></script>
|
||||
<!-- DataTables Buttons JS -->
|
||||
<script src="{{ url_for('static', filename='js/dataTables.buttons.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/buttons.bootstrap5.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/buttons.html5.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/buttons.print.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jszip.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/pdfmake.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/vfs_fonts.js') }}"></script>
|
||||
<!-- Moment.js -->
|
||||
<script src="{{ url_for('static', filename='js/moment.min.js') }}"></script>
|
||||
<!-- DateRangePicker -->
|
||||
<script src="{{ url_for('static', filename='js/daterangepicker.min.js') }}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#daterange').daterangepicker({
|
||||
timePicker: true,
|
||||
timePicker24Hour: true,
|
||||
timePickerSeconds: false,
|
||||
timePickerIncrement: 1, // Changed from 15 to 1 minute increments
|
||||
autoUpdateInput: false, // Prevents auto-update so user can edit manually
|
||||
locale: {
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
cancelLabel: 'Clear',
|
||||
applyLabel: 'Apply'
|
||||
},
|
||||
ranges: {
|
||||
'Last 48 Hours': [moment().subtract(48, 'hours'), moment()],
|
||||
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
|
||||
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
|
||||
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
||||
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
|
||||
}
|
||||
});
|
||||
|
||||
// Handle manual input updates
|
||||
$('#daterange').on('apply.daterangepicker', function(ev, picker) {
|
||||
$(this).val(picker.startDate.format('YYYY-MM-DD HH:mm') + ' - ' + picker.endDate.format('YYYY-MM-DD HH:mm'));
|
||||
});
|
||||
|
||||
$('#daterange').on('cancel.daterangepicker', function(ev, picker) {
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
// Allow direct editing of the date input
|
||||
$('#daterange').on('keyup', function(e) {
|
||||
if(e.keyCode === 13) {
|
||||
// Try to parse the input value
|
||||
var parts = $(this).val().split(' - ');
|
||||
if(parts.length === 2) {
|
||||
var startDate = moment(parts[0], 'YYYY-MM-DD HH:mm');
|
||||
var endDate = moment(parts[1], 'YYYY-MM-DD HH:mm');
|
||||
|
||||
if(startDate.isValid() && endDate.isValid()) {
|
||||
var picker = $(this).data('daterangepicker');
|
||||
picker.setStartDate(startDate);
|
||||
picker.setEndDate(endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var table = $('#logsTable').DataTable({
|
||||
pageLength: 50,
|
||||
lengthMenu: [[50, 100, 200, 500, 1000], [50, 100, 200, 500, 1000]],
|
||||
order: [[{% if current_user.is_global_admin() and not selected_company_id %}6{% else %}5{% endif %}, 'desc']], // Sort by timestamp column descending
|
||||
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
|
||||
'<"row"<"col-sm-12"tr>>' +
|
||||
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
|
||||
columnDefs: [
|
||||
{
|
||||
targets: [2, 3], // Computer Name and IP Address columns
|
||||
visible: false // Hide by default
|
||||
}
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
extend: 'csv',
|
||||
text: 'Export CSV',
|
||||
className: 'btn btn-secondary',
|
||||
filename: 'login_events_' + moment().format('YYYY-MM-DD'),
|
||||
exportOptions: {
|
||||
columns: ':visible'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'excel',
|
||||
text: 'Export Excel',
|
||||
className: 'btn btn-success',
|
||||
filename: 'login_events_' + moment().format('YYYY-MM-DD'),
|
||||
exportOptions: {
|
||||
columns: ':visible'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'print',
|
||||
text: 'Print',
|
||||
className: 'btn btn-info',
|
||||
exportOptions: {
|
||||
columns: ':visible'
|
||||
}
|
||||
}
|
||||
],
|
||||
language: {
|
||||
search: "Search records:",
|
||||
lengthMenu: "Show _MENU_ records per page",
|
||||
info: "Showing _START_ to _END_ of _TOTAL_ records",
|
||||
paginate: {
|
||||
first: "First",
|
||||
last: "Last",
|
||||
next: "Next",
|
||||
previous: "Previous"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Column names for the visibility controls
|
||||
var columnNames = [
|
||||
'Event Type',
|
||||
'User Name',
|
||||
'Computer Name',
|
||||
'IP Address',
|
||||
'Site',
|
||||
'Timestamp'
|
||||
{% if current_user.is_global_admin() and not selected_company_id %},
|
||||
'Company'
|
||||
{% endif %}
|
||||
];
|
||||
|
||||
// Load saved column visibility from localStorage
|
||||
function loadColumnVisibility() {
|
||||
var saved = localStorage.getItem('dashboardColumnVisibility');
|
||||
if (saved) {
|
||||
try {
|
||||
return JSON.parse(saved);
|
||||
} catch (e) {
|
||||
console.log('Error parsing saved column visibility:', e);
|
||||
}
|
||||
}
|
||||
// Default visibility - hide Computer Name (2) and IP Address (3)
|
||||
var defaultVisibility = {};
|
||||
columnNames.forEach(function(name, index) {
|
||||
defaultVisibility[index] = index !== 2 && index !== 3;
|
||||
});
|
||||
return defaultVisibility;
|
||||
}
|
||||
|
||||
// Save column visibility to localStorage
|
||||
function saveColumnVisibility(visibility) {
|
||||
localStorage.setItem('dashboardColumnVisibility', JSON.stringify(visibility));
|
||||
}
|
||||
|
||||
// Apply saved column visibility to table
|
||||
var savedVisibility = loadColumnVisibility();
|
||||
Object.keys(savedVisibility).forEach(function(colIndex) {
|
||||
table.column(parseInt(colIndex)).visible(savedVisibility[colIndex]);
|
||||
});
|
||||
|
||||
// Create column visibility dropdown menu
|
||||
function createColumnVisibilityMenu() {
|
||||
var menu = $('#column-visibility-menu');
|
||||
menu.empty();
|
||||
|
||||
columnNames.forEach(function(columnName, index) {
|
||||
var isVisible = table.column(index).visible();
|
||||
var checkboxId = 'col-vis-' + index;
|
||||
|
||||
var menuItem = $('<li class="column-checkbox"></li>');
|
||||
var formCheck = $('<div class="form-check"></div>');
|
||||
var checkbox = $('<input class="form-check-input" type="checkbox" id="' + checkboxId + '"' +
|
||||
(isVisible ? ' checked' : '') + '>');
|
||||
var label = $('<label class="form-check-label" for="' + checkboxId + '">' + columnName + '</label>');
|
||||
|
||||
checkbox.on('change', function() {
|
||||
var colIndex = parseInt(this.id.split('-')[2]);
|
||||
var isChecked = this.checked;
|
||||
table.column(colIndex).visible(isChecked);
|
||||
|
||||
// Update saved visibility
|
||||
savedVisibility[colIndex] = isChecked;
|
||||
saveColumnVisibility(savedVisibility);
|
||||
});
|
||||
|
||||
formCheck.append(checkbox, label);
|
||||
menuItem.append(formCheck);
|
||||
menu.append(menuItem);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize column visibility menu
|
||||
createColumnVisibilityMenu();
|
||||
|
||||
// Prevent dropdown from closing when clicking inside
|
||||
$('#column-visibility-menu').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Connect the custom export buttons to DataTables buttons
|
||||
$('#export-csv').on('click', function() {
|
||||
table.button('.buttons-csv').trigger();
|
||||
});
|
||||
|
||||
$('#export-excel').on('click', function() {
|
||||
table.button('.buttons-excel').trigger();
|
||||
});
|
||||
|
||||
$('#print-table').on('click', function() {
|
||||
table.button('.buttons-print').trigger();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,53 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h1>Welcome to Domain Logon Monitoring</h1>
|
||||
{% if current_user.is_authenticated %}
|
||||
<p class="lead">Hello {{ current_user.username }}, welcome back!</p>
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Quick Links:</h5>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url_for('frontend.dashboard') }}" class="text-decoration-none">View Dashboard</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url_for('frontend.time_spent_report') }}" class="text-decoration-none">Time Spent Report</a>
|
||||
</li>
|
||||
{% if current_user.role == 'Admin' or current_user.role == 'GlobalAdmin' %}
|
||||
<br></br><h5 class="card-title">Management:</h5>
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url_for('auth.manage_users') }}" class="text-decoration-none">Manage Users</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.role == 'GlobalAdmin' %}
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url_for('auth.manage_companies') }}" class="text-decoration-none">Manage Companies</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url_for('auth.admin_settings') }}" class="text-decoration-none">Site Settings</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{{ url_for('auth.view_error_logs') }}" class="text-decoration-none">View Error Logs</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="lead">Please login {% if allow_registration %}or register {% endif %}to access the monitoring system.</p>
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<a href="{{ url_for('auth.login') }}" class="btn btn-primary mr-2">Login</a>
|
||||
{% if allow_registration %}
|
||||
<a href="{{ url_for('auth.register') }}" class="btn btn-secondary">Register</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,302 @@
|
||||
{% extends "base.html" %}
|
||||
{% block head %}
|
||||
<!-- DataTables CSS -->
|
||||
<link href="{{ url_for('static', filename='css/dataTables.bootstrap5.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/buttons.bootstrap5.min.css') }}" rel="stylesheet">
|
||||
<!-- DateRangePicker CSS -->
|
||||
<link href="{{ url_for('static', filename='css/daterangepicker.css') }}" rel="stylesheet">
|
||||
<style>
|
||||
.btn-group-toggle .btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.dataTables_wrapper .dt-buttons {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.dt-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
/* Custom DateRangePicker dark theme styles */
|
||||
.daterangepicker {
|
||||
background-color: #212529;
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
.daterangepicker .calendar-table {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
}
|
||||
.daterangepicker td.available:hover,
|
||||
.daterangepicker th.available:hover {
|
||||
background-color: #495057;
|
||||
}
|
||||
.daterangepicker td.active,
|
||||
.daterangepicker td.active:hover {
|
||||
background-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
/* In-between dates in the selected range - lighter blue */
|
||||
.daterangepicker td.in-range {
|
||||
background-color: #82b1ff; /* Lighter blue */
|
||||
color: #212529; /* Darker text for better contrast */
|
||||
}
|
||||
.daterangepicker td.in-range:hover {
|
||||
background-color: #75a7f7; /* Slightly darker when hovering */
|
||||
color: #212529;
|
||||
}
|
||||
.daterangepicker .calendar-table .next span,
|
||||
.daterangepicker .calendar-table .prev span {
|
||||
border-color: #f8f9fa;
|
||||
}
|
||||
.daterangepicker .ranges li:hover,
|
||||
.daterangepicker .ranges li.active {
|
||||
background-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
.daterangepicker .ranges li {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
border-bottom-color: #212529;
|
||||
}
|
||||
.daterangepicker:before {
|
||||
border-bottom-color: #495057;
|
||||
}
|
||||
/* Calendar header and weekday styling */
|
||||
.daterangepicker .calendar-table th {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Month name */
|
||||
.daterangepicker .month {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Off days (not in current month) */
|
||||
.daterangepicker td.off {
|
||||
color: #6c757d;
|
||||
}
|
||||
/* Input boxes */
|
||||
.daterangepicker input.input-mini {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Time picker */
|
||||
.daterangepicker .calendar-time select {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Apply and cancel buttons */
|
||||
.daterangepicker .drp-buttons {
|
||||
border-top-color: #495057;
|
||||
}
|
||||
.daterangepicker .drp-buttons .btn {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
/* Input fields focus */
|
||||
.daterangepicker input.input-mini:focus {
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
/* Time inputs container */
|
||||
.daterangepicker .calendar-time {
|
||||
background-color: #343a40;
|
||||
border-color: #495057;
|
||||
}
|
||||
/* Make the export buttons more visible */
|
||||
.dt-buttons {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
display: block !important;
|
||||
}
|
||||
.dt-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
/* Align the "Show entries" and search box in the same row */
|
||||
div.dataTables_wrapper div.dataTables_length {
|
||||
float: left;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
float: right;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_info {
|
||||
clear: both;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Time Spent Report{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2>Time Spent Report</h2>
|
||||
<div class="export-buttons d-flex align-items-center">
|
||||
<!-- Process span option moved here -->
|
||||
<div class="form-check me-3">
|
||||
<input type="checkbox" class="form-check-input" name="continue_iterate" id="continue_iterate" form="reportFilterForm" {% if continue_iterate %}checked{% endif %}>
|
||||
<label class="form-check-label" for="continue_iterate" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="When enabled, the system will continue processing time spans across midnight for multi-day sessions. When disabled, each day's activity is calculated separately.">
|
||||
Process multi-day sessions
|
||||
</label>
|
||||
</div>
|
||||
<!-- Export buttons will be placed here by DataTables -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3 mb-3">
|
||||
<div class="card-body">
|
||||
<form id="reportFilterForm" method="GET" action="{{ url_for('frontend.time_spent_report') }}">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label for="daterange" class="form-label">Date Range:</label>
|
||||
<input type="text" id="daterange" name="daterange" class="form-control"
|
||||
value="{{ start_date.strftime('%Y-%m-%d %H:%M') if start_date else '' }} - {{ end_date.strftime('%Y-%m-%d %H:%M') if end_date else '' }}"/>
|
||||
</div>
|
||||
|
||||
{% if companies %}
|
||||
<div class="col-md-2">
|
||||
<label for="company_id" class="form-label">Company:</label>
|
||||
<select class="form-select" id="company_id" name="company_id">
|
||||
<option value="">All Companies</option>
|
||||
{% for company in companies %}
|
||||
<option value="{{ company.id }}" {% if selected_company_id == company.id %}selected{% endif %}>
|
||||
{{ company.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="group_by" class="form-label">Group By:</label>
|
||||
<div class="btn-group btn-group-toggle" data-toggle="buttons">
|
||||
<input type="radio" class="btn-check" name="group_by" id="option1" value="user" {% if group_by == 'user' or not group_by %}checked{% endif %}>
|
||||
<label class="btn btn-outline-primary btn-sm" for="option1">User</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="group_by" id="option2" value="user_computer" {% if group_by == 'user_computer' %}checked{% endif %}>
|
||||
<label class="btn btn-outline-primary btn-sm" for="option2">User + Computer</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mt-3">
|
||||
<button type="submit" class="btn btn-primary">Apply Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table id="timeSpentTable" class="table table-striped table-bordered" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>User Name</th>
|
||||
{% if group_by == 'user_computer' %}
|
||||
<th>Computer Name</th>
|
||||
{% endif %}
|
||||
<th>Company</th>
|
||||
<th>Total Time</th>
|
||||
<th>First Login</th>
|
||||
<th>Last Logout</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in time_data %}
|
||||
<tr>
|
||||
<td>{{ entry.date }}</td>
|
||||
<td>{{ entry.user_name }}</td>
|
||||
{% if group_by == 'user_computer' %}
|
||||
<td>{{ entry.computer_name }}</td>
|
||||
{% endif %}
|
||||
<td>{{ entry.company_name }}</td>
|
||||
<td data-order="{{ entry.total_seconds }}">{{ entry.formatted_time }}</td>
|
||||
<td data-order="{{ entry.first_login.strftime('%Y%m%d%H%M%S') if entry.first_login else '' }}">
|
||||
{{ entry.first_login|format_datetime if entry.first_login else 'N/A' }}
|
||||
</td>
|
||||
<td data-order="{{ entry.last_logout.strftime('%Y%m%d%H%M%S') if entry.last_logout else '' }}">
|
||||
{{ entry.last_logout|format_datetime if entry.last_logout else 'N/A' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<!-- DataTables JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap5.min.js') }}"></script>
|
||||
<!-- DataTables Buttons JS -->
|
||||
<script src="{{ url_for('static', filename='js/dataTables.buttons.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/buttons.bootstrap5.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/buttons.html5.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/buttons.print.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/jszip.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/pdfmake.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/vfs_fonts.js') }}"></script>
|
||||
<!-- Moment.js -->
|
||||
<script src="{{ url_for('static', filename='js/moment.min.js') }}"></script>
|
||||
<!-- DateRangePicker -->
|
||||
<script src="{{ url_for('static', filename='js/daterangepicker.min.js') }}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Initialize date range picker
|
||||
$('#daterange').daterangepicker({
|
||||
timePicker: true,
|
||||
timePicker24Hour: true,
|
||||
timePickerSeconds: false,
|
||||
startDate: moment().subtract(7, 'days'),
|
||||
endDate: moment(),
|
||||
locale: {
|
||||
format: 'YYYY-MM-DD HH:mm'
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize tooltips
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
});
|
||||
|
||||
// Initialize DataTable with export buttons
|
||||
var table = $('#timeSpentTable').DataTable({
|
||||
dom: 'lfrBtip',
|
||||
buttons: [
|
||||
{
|
||||
extend: 'csv',
|
||||
text: 'Export CSV',
|
||||
className: 'btn btn-primary btn-sm',
|
||||
exportOptions: {
|
||||
columns: ':visible'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'excel',
|
||||
text: 'Export Excel',
|
||||
className: 'btn btn-success btn-sm',
|
||||
exportOptions: {
|
||||
columns: ':visible'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'print',
|
||||
text: 'Print',
|
||||
className: 'btn btn-info btn-sm',
|
||||
exportOptions: {
|
||||
columns: ':visible'
|
||||
}
|
||||
}
|
||||
],
|
||||
order: [[0, 'desc']],
|
||||
pageLength: 25
|
||||
});
|
||||
|
||||
// Move export buttons to the header
|
||||
table.buttons().container().appendTo('.export-buttons');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user