Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 159 additions & 87 deletions cmd/server/assets/realmadmin/show.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,46 +36,56 @@ <h1>Realm stats</h1>
</small>
</div>

<div class="card mb-3">
<div class="card-header">
<span class="oi oi-bar-chart mr-2 ml-n1"></span>
Codes issued by user
<span class="font-weight-bold float-right" data-toggle="tooltip"
title="These are the number of codes issued per user per day. It only includes codes generated via the UI with a logged in user.">?</span>
<div class="row">
<div class="col-lg-6 pr-2">
<div class="card mb-3">
<div class="card-header">
<span class="oi oi-bar-chart mr-2 ml-n1"></span>
Codes issued by user by day
<span class="font-weight-bold float-right" data-toggle="tooltip"
title="These are the number of codes issued per user per day. Click on a row to expand or collapse the details.">?</span>
</div>
<div id="per_user_table" class="overflow-auto" style="height:400px">
<div class="container d-flex h-100 w-100">
<p class="justify-content-center align-self-center text-center font-italic w-100">Loading data...</p>
</div>
</div>
<small class="card-footer text-muted text-right">
<span class="mr-1">Export as:</span>
<a href="/realm/stats.csv?scope=user" class="mr-1">CSV</a>
<a href="/realm/stats.json?scope=user">JSON</a>
</small>
</div>
</div>
<div id="per_user_chart" class="container d-flex h-100 w-100" style="min-height:400px;">
<p class="justify-content-center align-self-center text-center font-italic w-100">Loading chart...</p>
</div>
<small class="card-footer text-muted text-right">
<span class="mr-1">Export as:</span>
<a href="/realm/stats.csv?scope=user" class="mr-1">CSV</a>
<a href="/realm/stats.json?scope=user">JSON</a>
</small>
</div>

<div class="card mb-3">
<div class="card-header">
<span class="oi oi-bar-chart mr-2 ml-n1"></span>
Codes issued by external issuers
<span class="font-weight-bold float-right" data-toggle="tooltip"
title="These are the number of codes issued per user by external issuers. These codes can only be issued by the API.">?</span>
<div class="col-lg-6 pl-2">
<div class="card mb-3">
<div class="card-header">
<span class="oi oi-bar-chart mr-2 ml-n1"></span>
Codes issued by external issuers
<span class="font-weight-bold float-right" data-toggle="tooltip"
title="These are the number of codes issued per user by external issuers. These codes can only be issued by the API.">?</span>
</div>
<div id="per_external_issuer_table" class="overflow-auto" style="height:400px">
<div class="container d-flex h-100 w-100">
<p class="justify-content-center align-self-center text-center font-italic w-100">Loading data...</p>
</div>
</div>
<small class="card-footer text-muted text-right">
<span class="mr-1">Export as:</span>
<a href="/realm/stats.csv?scope=external" class="mr-1">CSV</a>
<a href="/realm/stats.json?scope=external">JSON</a>
</small>
</div>
</div>
<div id="external_issuers_chart" class="container d-flex h-100 w-100" style="min-height:400px;">
<p class="justify-content-center align-self-center text-center font-italic w-100">Loading chart...</p>
</div>
<small class="card-footer text-muted text-right">
<span class="mr-1">Export as:</span>
<a href="/realm/stats.csv?scope=external" class="mr-1">CSV</a>
<a href="/realm/stats.json?scope=external">JSON</a>
</small>
</div>
</main>

<script src="https://www.gstatic.com/charts/loader.js"></script>
<script>
let realmChartDiv = document.getElementById('realm_chart');
let perUserChartDiv = document.getElementById('per_user_chart');
let externalIssuersChartDiv = document.getElementById('external_issuers_chart');
let $perUserTable = $('#per_user_table');
let $perExternalIssuerTable = $('#per_external_issuer_table');
let dateFormatter;

google.charts.load('current', {
Expand Down Expand Up @@ -155,36 +165,68 @@ <h1>Realm stats</h1>
return;
}

var dataTable = new google.visualization.DataTable();

dataTable.addColumn('date', 'Date');

data.statistics[0].issuer_data.forEach(function(row) {
dataTable.addColumn('number', row.name);
})

data.statistics.reverse().forEach(function(row) {
let rowData = [utcDate(row.date)];
row.issuer_data.forEach(function(entry) {
rowData.push(entry.codes_issued);
$perUserTable.empty();

let $listGroup = $('<div>');
$listGroup.addClass('list-group');
$listGroup.addClass('list-group-flush');
$perUserTable.append($listGroup);

data.statistics.forEach(function(row) {
let date = utcDate(row.date);
let id = `collapse-user-${date.getTime()}`;

let $dateRow = $('<div>');
$dateRow.addClass('list-group-item');
$dateRow.addClass('list-group-item-action');
$dateRow.attr('data-toggle', 'collapse');
$dateRow.attr('data-target', `#${id}`);
$dateRow.attr('aria-expanded', false);
$dateRow.attr('aria-controls', id);
$dateRow.text(date.toLocaleDateString());
$listGroup.append($dateRow);

let $issuerData = $('<div>');
$issuerData.attr('id', id);
$issuerData.addClass('collapse');
$issuerData.addClass('list-group-item');
$issuerData.addClass('p-0 pl-3');
$issuerData.data('parent', '#per_user_table');
$listGroup.append($issuerData);

let $table = $('<table>');
$table.addClass('table');
$table.addClass('table-bordered');
$table.addClass('table-striped');
$table.addClass('table-fixed');
$table.addClass('table-inner-border-only');
$table.addClass('border-left');
$table.addClass('mb-0');
$issuerData.append($table);

let $thead = $('<thead>');
$table.append($thead)

let $trhead = $('<tr>');
$trhead.append(
$('<th>').text('Name'),
$('<th>').text('Email'),
$('<th width="80">').text('Issued'));
$thead.append($trhead);

let $tbody = $('<tbody>');
$table.append($tbody);

row.issuer_data.forEach(function(issuer) {
let $name = $('<td>').text(issuer.name);
let $email = $('<td>').text(issuer.email);
let $codes_issued = $('<td align="right">').text(issuer.codes_issued);

let $tr = $('<tr>');
$tr.append($name, $email, $codes_issued);
$tbody.append($tr);
});
dataTable.addRow(rowData);
});

dateFormatter.format(dataTable, 0);

let options = {
chartArea: {
left: 30, // leave room for y-axis labels
width: '100%'
},
hAxis: { format: 'M/d' },
legend: 'none',
width: '100%'
};

let chart = new google.visualization.LineChart(perUserChartDiv);
chart.draw(dataTable, options);
})
.fail(function(xhr, status, err) {
flash.error('Failed to render user stats: ' + err);
Expand All @@ -202,36 +244,66 @@ <h1>Realm stats</h1>
return;
}

var dataTable = new google.visualization.DataTable();

dataTable.addColumn('date', 'Date');

data.statistics[0].issuer_data.forEach(function(row) {
dataTable.addColumn('number', row.issuer_id);
})

data.statistics.reverse().forEach(function(row) {
let rowData = [utcDate(row.date)];
row.issuer_data.forEach(function(entry) {
rowData.push(entry.codes_issued);
$perExternalIssuerTable.empty();

let $listGroup = $('<div>');
$listGroup.addClass('list-group');
$listGroup.addClass('list-group-flush');
$perExternalIssuerTable.append($listGroup);

data.statistics.forEach(function(row) {
let date = utcDate(row.date);
let id = `collapse-external-${date.getTime()}`;

let $dateRow = $('<div>');
$dateRow.addClass('list-group-item');
$dateRow.addClass('list-group-item-action');
$dateRow.attr('data-toggle', 'collapse');
$dateRow.attr('data-target', `#${id}`);
$dateRow.attr('aria-expanded', false);
$dateRow.attr('aria-controls', id);
$dateRow.text(date.toLocaleDateString());
$listGroup.append($dateRow);

let $issuerData = $('<div>');
$issuerData.attr('id', id);
$issuerData.addClass('collapse');
$issuerData.addClass('list-group-item');
$issuerData.addClass('p-0 pl-3');
$issuerData.data('parent', '#per_user_table');
$listGroup.append($issuerData);

let $table = $('<table>');
$table.addClass('table');
$table.addClass('table-bordered');
$table.addClass('table-striped');
$table.addClass('table-fixed');
$table.addClass('table-inner-border-only');
$table.addClass('border-left');
$table.addClass('mb-0');
$issuerData.append($table);

let $thead = $('<thead>');
$table.append($thead)

let $trhead = $('<tr>');
$trhead.append(
$('<th>').text('ID'),
$('<th width="80">').text('Issued'));
$thead.append($trhead);

let $tbody = $('<tbody>');
$table.append($tbody);

row.issuer_data.forEach(function(issuer) {
let $name = $('<td>').text(issuer.issuer_id);
let $codes_issued = $('<td align="right">').text(issuer.codes_issued);

let $tr = $('<tr>');
$tr.append($name, $codes_issued);
$tbody.append($tr);
});
dataTable.addRow(rowData);
});

dateFormatter.format(dataTable, 0);

let options = {
chartArea: {
left: 30, // leave room for y-axis labels
width: '100%'
},
hAxis: { format: 'M/d' },
legend: 'none',
width: '100%'
};

let chart = new google.visualization.LineChart(externalIssuersChartDiv);
chart.draw(dataTable, options);
})
.fail(function(xhr, status, err) {
flash.error('Failed to render external issuer stats: ' + err);
Expand Down
2 changes: 1 addition & 1 deletion pkg/database/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,7 @@ func (r *Realm) UserStats(db *Database, start, stop time.Time) (RealmUserStats,
) d
LEFT JOIN user_stats s ON s.realm_id = $1 AND s.user_id = d.user_id AND s.date = d.date
LEFT JOIN users u ON u.id = d.user_id
ORDER BY date DESC, user_id`
ORDER BY date DESC, u.name`

var stats []*RealmUserStat
if err := db.db.Raw(sql, r.ID, start, stop).Scan(&stats).Error; err != nil {
Expand Down