Extending the WebUI: Devices
It’s often desirable to display simple data in Kismet as a table. Kismet handles this in the web UI as a jquery plugin, jquery.kismet.devicedata
.
devicedata
takes a JSON object and a set of options, which includes a set of flexible field definitions able to map simple data/title pairs as well as filtering, complex render functions, empty field substitutions, and nested groups.
Applying $.devicedata()
devicedata
can be applied to any entity, but generally only makes sense to apply to a div
or similar container.
<div id="devicedata">
<script>
$('#devicedata').devicedata(data, options);
</script>
Devicedata options
A devicedata
instance takes a small number of options and an array of field defintions which perform the heavy lifting of the table display.
id - string (optional)
Sets the entity ID of the table created by devicedata
. Defaults to ‘kismetDeviceData’.
fields - array
Array of field definitions, discussed in the next section.
Devicedata fields
The field definitions are where the magic happens in the devicedata
plugin. They function similarly to DataTable
column definitions - a field definition can provide a simple title/content map, or can insert fully rendered graphics, other objects, etc.
Many options can be passed as a string or as a function. Functions all take an options dictionary object, containing at least:
- key - String key of current field
- data - Complete data set assigned to this
devicedata
object - value - Resolved value for the current field
- basekey - Optional string key of base object, used in iterative groups (more on these later)
- base - Object of the sub-set of data for the current field, used it iterative groups.
Additional fields may be present in the options object depending on the callback, and will be mentioned below.
field
A Kismet-style field spec. This allows for addressing nested and index data by chaining the fields, for example, kismet.device.base.signal/kismet.common.signal.last_signal_dbm
.
title - string
Title text presented to the user in the header for the row
empty - string | function(opts) (optional)
Text to be substituted if the key is not available in the data set.
filter - function (optional)
A function, taking a opts argument and returning boolean, which determines if this entire display is rendered or skipped. Filter functions should return true
to display the field and false
to hide it.
{
...
filter: function(opts) {
if (data[something])
return true;
return false;
}
}
filterOnEmpty - boolean (optional)
Filter this field if it is undefined, or an empty string. This is identical to comparing the value in a filter function.
filterOnZero - boolean (optional)
Filter this field if it is undefined, or a zero number. This is identical to comparing the value in a filter function.
render - string | function (optional)
If the render option is a string, it is placed in the HTML of the <td>
container.
If the render option is a function, it will called during creation of the table row, and the returned string is placed directly in the cell, prior to completion of the entire table. The render function is called before the DOM of the container is finalized. Render functions may return complex HTML.
The render function is called with a standard options dictionary.
For example, a row rendering the timestamp as a human-readable time:
{
field: "kismet.device.base.first_time",
title: "First Seen",
render: function(options) {
return new Date(options['value'] * 1000);
}
},
draw - function(opts) (optional)
Draw function called AFTER creation of the table. This function can be used to manipulate the contents of the row (or entire table) after it has been created and rendered. The draw callback is mostly used for graphs or other non-text content. Draw is called after the DOM is finalized so can manipulate the objects directly.
Draw functions receive the normal options object, with an additional value:
- container - object container for the
<td>
cell for this field.
For example, to insert a sparkline in a row you could combine the draw
and render
functions:
{
field: "some.data.array",
title: "Sparkline",
render: '<i>Graph coming soon</i>';
draw: function(opts) {
opts['container'].sparkline({data: opts['value'];});
}
}
A more complex example could create themed elements in the draw
function and later utilize them in the render
function:
{
field: "some.data",
title: "Dynamid draw",
render: '<div class="custom" />',
draw: function(opts) {
var mydiv = $('div.custom', opts['container']);
mydiv.html('Added dynamically in draw');
}
}
groupTitle string | function (optional)
Indicates that this is a sub-group and provides a user-visible title for the group.
Sub-groups are rendered as a nested table, and fields defining a subgroup act as a top-level options directive - that is, they may then contain their own id
and fields
options. The id
option is applied to the nested table, and the fields
are placed inside the nested instance.
In simpler terms, subgroups create a new devicedata
table, which can, in turn, contain more fields, subgroups, etc.
The groupTitle
option can be a fixed string, or a function taking an options set.
For example, to define a location group:
{
// Indicate that we're a subgroup, and give it the title 'Avg. Location'
groupTitle: "Avg. Location",
// The subgroup doesn't apply to a specific field, so we give it a junk value
field: "group_avg_location",
// Assign an ID to our subgroup
id: "group_avg_location",
// Subgroups can have filters, too, so we query the data and see if we have
// a valid location. If not, this subgroup will be hidden entirely
filter: function(opts) {
return (kismet.ObjectByString(opts['data'], "kismet.device.base.location/kismet.common.location.avg_loc/kismet.common.location.valid") == 1);
},
// Fields in subgroup
fields: [
{
field: "kismet.device.base.location/kismet.common.location.avg_loc/kismet.common.location.lat",
title: "Latitude"
},
{
field: "kismet.device.base.location/kismet.common.location.avg_loc/kismet.common.location.lon",
title: "Longitude"
},
],
}
groupIterate - boolean (optional)
Indicated that this field contains an iterative group.
Iterative groups apply to data sets like arrays or dictionaries. The group is treated like a subgroup, and loops over each value in the array or dictionary.
Iterative groups treat the fields
option slightly differently: all options for fields are processed as normal, however the field definition should be the field name only - the complete, indexed field path will be prepended automatically.
It is simpler to provide an example.
Given the data:
var data = {
'somegroup': [
{
'field1': "one",
'field2': "two"
},
{
'field1': "three",
'field2': "four"
}
]
};
A standard field definition might be somegroup/field1
. However, for an iterative group, you would want to use:
{
// We're an iterative group
groupIterate: true,
// Provide the GROUP FIELD, ie the dictionary or array object field
field: 'somegroup',
// Now our fields are referenced within `somegroup` automatically for us
fields: [
{
field: 'field1',
title: 'Field One'
},
{
field: 'field2',
title: 'Field Two'
}
]
}
Additionally, field functions called as part of an iterative group are given an additional option in the callback options:
- index - The index value used for this iteration.
iterateTitle - string | function (optional)
Provide a title for an iterative group. May be a fixed string, or a function taking the standard options group.
Additionally, the options group will contain:
- index - The index value used for this iteration.
span - boolean (optional)
Spans the <td>
table cell containing the output across both columns (eliminating the title). This allows for rows with custom graphs to center them above the following columns, and other custom behavior.
A spanned column will not show any title.
For example to simply show the value, centered in bold across the entire column:
{
field: "some_placeholder",
span: true,
render: function(key, data, value) {
return '<b>' + value + '</b>';
}
}
sanitize - boolean (optional)
By default, devicedata fields are run through a sanitization function to convert HTML characters. If you are sure your data is safe (for instance, it is locally generated HTML content), this can be overridden.
Manipulating Devicedata Tables
Most of the requirements for manipulating the content of a Devicedata table should be met by the internal functions for rendering, drawing, etc. However, if you need to modify or access data from outside the Devicedata object, the following elements are created:
- table,
id
using the suppliedid
field, classkismet_devicedata
. This table is created for every group of fields - a devicedata table. The parent table uses the masterid
option, and subgroup tables are each created using theid
of the subgroup definition. - tr,
id
using a sanitized field reference prefixed withtr_
. To meet the requirements of an ID, complex field references (nested field names, indexed fields, etc) are converted by replacing all special characters with_
. A field reference such asfoo_bar_baz.sub_field
will form the table IDtr_foo_bar_baz_sub_field
.