mirror of
https://github.com/owntracks/recorder.git
synced 2024-11-15 09:58:40 -07:00
330 lines
9.3 KiB
HTML
330 lines
9.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en-US">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>OwnTracks Recorder Table</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<link rel="icon" sizes="192x192" href="../static/recorder.png">
|
|
<link rel="apple-touch-icon" href="../static/recorder.png">
|
|
|
|
<link rel="stylesheet" href="../static/datatables/css/jquery.dataTables.min.css">
|
|
<script src="../static/datatables/js/jquery.min.js"></script>
|
|
<script src="../static/datatables/js/jquery.dataTables.min.js"></script>
|
|
|
|
<link rel="stylesheet" href="otable.css">
|
|
|
|
<script type="module">
|
|
|
|
import config from "./config.js";
|
|
|
|
import { debug } from "../utils/debug.js";
|
|
import { fetchApiData } from "../utils/network.js";
|
|
import { escapeHTML } from "../utils/misc.js";
|
|
|
|
/*
|
|
* Insert data object into table or update existing row. `data' must
|
|
* have 'topic', as that is the key into column 0 of the datatable.
|
|
*/
|
|
|
|
function upsert(data) {
|
|
|
|
let found = false;
|
|
let idx;
|
|
|
|
tab.rows().indexes().each( function(idx) {
|
|
const d = tab.row(idx).data();
|
|
if (d && (d.topic == data.topic)) {
|
|
found = true;
|
|
/* idx is index of updated row (0--n) */
|
|
idx = tab.row(idx).data(data);
|
|
/* Highlight */
|
|
const row = tab.rows(idx, {order:'index'}).nodes().to$();
|
|
$(row).animate({ 'backgroundColor': '#FF9900' }, 650, function(){
|
|
$(row).animate({'backgroundColor': 'white'}, 650);
|
|
});
|
|
}
|
|
});
|
|
|
|
if (!found) {
|
|
idx = tab.row.add(data);
|
|
}
|
|
tab.draw();
|
|
}
|
|
|
|
|
|
function listusers(data) {
|
|
|
|
for (const d of data) {
|
|
|
|
if (d.username == 'ping' && d.device == 'ping') {
|
|
debug("Skipping ping entry:", d);
|
|
continue;
|
|
}
|
|
|
|
if (!d.topic) {
|
|
debug("Skipping unknown entry:", d);
|
|
continue;
|
|
}
|
|
|
|
d.fulldate = new Date(d.tst * 1000).toLocaleDateString(undefined, {
|
|
day: 'numeric',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
hour: 'numeric',
|
|
minute: 'numeric',
|
|
second: 'numeric',
|
|
});
|
|
d.addr ||= `(${ d.lat },${ d.lon })`;
|
|
d.cc ||= '__';
|
|
d.tid ||= d.topic.slice(-2);
|
|
|
|
upsert(d);
|
|
}
|
|
}
|
|
|
|
|
|
async function getuserlist() {
|
|
|
|
const data = await fetchApiData({ endpoint: "last", includeSearchParams: true });
|
|
|
|
listusers(data);
|
|
}
|
|
|
|
|
|
getuserlist();
|
|
|
|
const tab = $('#livetable').DataTable({
|
|
paging: false,
|
|
searching: true,
|
|
ordering: true,
|
|
autoWidth: false,
|
|
order: [[ 2, "desc" ]],
|
|
columnDefs: [
|
|
{
|
|
orderable: true,
|
|
targets: [ 0, 1, 3, 4, 5 ],
|
|
},
|
|
{
|
|
className: 'topic',
|
|
name: 'topic',
|
|
title: config.column_titles.topic,
|
|
visible: false,
|
|
data: null,
|
|
render: 'topic',
|
|
targets : [0],
|
|
},
|
|
{
|
|
className: 'face',
|
|
name: 'face',
|
|
title: config.column_titles.face,
|
|
visible: true,
|
|
data: null,
|
|
targets : [1],
|
|
render : function(data, type, row) {
|
|
|
|
let src;
|
|
if(data.face){
|
|
src = `'data:image/png;base64,${ data.face.replaceAll("'", "") }'`;
|
|
}else{
|
|
src = `'../static/defaultface.svg'`;
|
|
}
|
|
|
|
return escapeHTML `<img class='face' alt='' src=${ src } height='20' width='20'>`;
|
|
}
|
|
},
|
|
{
|
|
className: 'fulldate',
|
|
name: 'fulldate',
|
|
title: config.column_titles.fulldate,
|
|
visible: true,
|
|
data: null,
|
|
render: 'fulldate',
|
|
targets : [2],
|
|
},
|
|
{
|
|
className: 'name',
|
|
name: 'name',
|
|
title: config.column_titles.name,
|
|
visible: true,
|
|
data: null,
|
|
render: function(data, type, row) {
|
|
return escapeHTML `${data.name || data.topic}`;
|
|
},
|
|
targets : [3],
|
|
},
|
|
{
|
|
className: 'user',
|
|
name: 'user',
|
|
title: config.column_titles.user,
|
|
visible: true,
|
|
data: null,
|
|
createdCell: function(td, cellData, rowData, row, col) {
|
|
if (rowData._http) {
|
|
$(td).css('color', 'gray');
|
|
}
|
|
},
|
|
render: function(data, type, row) {
|
|
return escapeHTML `${ data.username }/${ data.device }`;
|
|
},
|
|
targets : [4],
|
|
},
|
|
{
|
|
className: 'tid',
|
|
name: 'tid',
|
|
title: config.column_titles.tid,
|
|
visible: true,
|
|
data: null,
|
|
render: 'tid',
|
|
targets : [5],
|
|
},
|
|
{
|
|
className: 'batt',
|
|
name: 'batt',
|
|
title: config.column_titles.batt,
|
|
visible: true,
|
|
data: null,
|
|
render: function(data, type, row) {
|
|
if (typeof data.batt === 'undefined' || data.batt === null) {
|
|
return '-';
|
|
}
|
|
if (typeof data.batt === 'object') {
|
|
return escapeHTML `${ data.batt[data.batt.length - 1].batt }v`;
|
|
}
|
|
|
|
let t = escapeHTML `${ data.batt }%`;
|
|
if (data.batt < 25) {
|
|
t = escapeHTML `<span class="warning">${ t }</span>`;
|
|
}
|
|
return t;
|
|
},
|
|
targets : [6],
|
|
},
|
|
{
|
|
className: 'vel',
|
|
name: 'vel',
|
|
title: config.column_titles.vel,
|
|
visible: true,
|
|
data: null,
|
|
render: function(data, type, row) {
|
|
if (typeof data.vel === 'undefined' || data.vel === null) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
const v = parseInt(data.vel, 10);
|
|
if (v < 0) {
|
|
return '';
|
|
}
|
|
} catch(error) {
|
|
return '';
|
|
}
|
|
|
|
return escapeHTML `${data.vel}`;
|
|
},
|
|
targets : [7],
|
|
},
|
|
{
|
|
className: 'cog',
|
|
name: 'cog',
|
|
title: config.column_titles.cog,
|
|
visible: true,
|
|
data: null,
|
|
render: function(data, type, row) {
|
|
if (typeof data.cog === 'undefined' || data.cog === null) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
const c = parseInt(data.cog, 10);
|
|
if (c < 0) {
|
|
return '';
|
|
}
|
|
} catch(error) {
|
|
return '';
|
|
}
|
|
|
|
return escapeHTML `${data.cog}`;
|
|
},
|
|
targets : [8],
|
|
},
|
|
{
|
|
className: 'cc',
|
|
name: 'cc',
|
|
title: config.column_titles.cc,
|
|
visible: true,
|
|
data: null,
|
|
render: function(data, type, row) {
|
|
return escapeHTML `<img src='../static/flags/${ data.cc }.png' title='${ data.cc }'/>`;
|
|
},
|
|
targets : [9],
|
|
},
|
|
{
|
|
className: 'addr',
|
|
name: 'addr',
|
|
title: config.column_titles.addr,
|
|
visible: true,
|
|
data: null,
|
|
render : function(data, type, row) {
|
|
return escapeHTML `<a target='_new' href='https://www.openstreetmap.org/?mlat=${ data.lat }&mlon=${ data.lon }'>${ data.addr }</a>`;
|
|
},
|
|
targets : [10],
|
|
},
|
|
],
|
|
|
|
});
|
|
|
|
|
|
$('button.toggle-vis').on('click', function (event) {
|
|
event.preventDefault();
|
|
// Get column API object
|
|
const column = tab.column( $(this).attr('data-column') );
|
|
column.visible( ! column.visible() );
|
|
});
|
|
|
|
$("#dataload").on('click', function(event) {
|
|
event.preventDefault();
|
|
getuserlist();
|
|
});
|
|
|
|
/* Click on a row should show object; remove `face' */
|
|
$('#livetable tbody').on("click", "td", function(event){
|
|
// alert( "Index: " + tab.row(this).index() );
|
|
|
|
const aPos = $('#livetable').dataTable().fnGetPosition(this);
|
|
const aData = $('#livetable').dataTable().fnGetData(aPos[0]);
|
|
|
|
if (aPos[1] === 1) {
|
|
|
|
// const displayData = tab.row(this).data();
|
|
const displayData = aData;
|
|
delete displayData.face;
|
|
alert(JSON.stringify(displayData));
|
|
}
|
|
});
|
|
|
|
</script>
|
|
</head>
|
|
<body>
|
|
|
|
<ul>
|
|
<li><button id='dataload'>Reload data</button></li>
|
|
</ul>
|
|
<div id='header'>
|
|
<table id="livetable" class="display compact hover">
|
|
</table>
|
|
</div>
|
|
|
|
<div id='togglers'>
|
|
<ul>
|
|
<li>Toggle</li>
|
|
<li><button class='toggle-vis' data-column='0'>topic</button></li>
|
|
<li><button class='toggle-vis' data-column='4'>user</button></li>
|
|
<li><button class='toggle-vis' data-column='6'>battery</button></li>
|
|
</ul>
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|