Squashed commit of the following:

commit 49e585ef4bcbafd1964f0d0b8b9eda4b2624c86e
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 23:20:05 2013 +0000

    default is to have age filter enabled

commit c3635b3b096ea73906a553db5b588eaee29b2145
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 23:15:08 2013 +0000

    disable Seen IPs checkbox when v6ish cidr is seen

commit f1550f2dcff96a9b2d77cbc3a8582429f3b078df
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 22:56:12 2013 +0000

    make daterange green when age filter activated

commit 838578f2b9935c6f2abba7679f2ba87f30f651a3
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 22:47:29 2013 +0000

    use class to control input colouring

commit 291d230e6a0216b4f5ad52b8eb04f3949ffe85e3
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 22:43:45 2013 +0000

    move cidr input and remove sidebar title

    this is only so that the tooltip on the cide input isn't obscured
    by the table heading. I've no idea how to avoid that happening :-/

commit d0df5c7af82bea3fc674585962ca67f4cd82e553
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 22:11:34 2013 +0000

    layout fixes for ipinventory sidebar

commit faeb614147e673d4d08822cafb65ca003c400edd
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 21:27:01 2013 +0000

    prevent autoload for ipinventory data; prevent submit if CIDR is empty

commit b993611ff9bf1bd1eaed410beab09c754c786b1a
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 21:08:26 2013 +0000

    revert to 0.0.0.0/32 default search

commit d9e34ec19a2ca0d5893215a0508478985c96076e
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 20:59:22 2013 +0000

    set age sort to desc - show oldest unused

commit 4a4231c69c8483c65d211119d772ad40a3a95b69
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Dec 30 20:55:00 2013 +0000

    fix parameter names; make 0.0.0.0/0 default

commit 5fb6e42426ae8bf96d81b2f2ad25d118c900646d
Author: Eric A. Miller <emiller@cpan.org>
Date:   Mon Dec 30 15:16:13 2013 -0500

    Add IP Inventory report with options sidebar

commit 4b84ca3714990199303cd5e099310e4c1ff1458f
Author: Eric A. Miller <emiller@cpan.org>
Date:   Mon Dec 30 14:34:29 2013 -0500

    Add 'IP' as a report category

commit 849ae4e0a6f51102cb90236629f4e1b43fc9bc2e
Author: Eric A. Miller <emiller@cpan.org>
Date:   Mon Dec 30 14:33:10 2013 -0500

    Add CidrIps virtual view ResultSource. Enumerates all IP addresses within an IPv4 subnet.

commit 30abbb957bd40270a14ca6289e76f70bf721696e
Author: Eric A. Miller <emiller@cpan.org>
Date:   Mon Dec 30 14:29:27 2013 -0500

    Add Dan Grossman's date range picker component for Twitter Bootstrap.  Component relies on Moment.js which is also added.

commit 19830639bdbcd409a49228f18d29f6dff5e933e8
Author: Eric A. Miller <emiller@cpan.org>
Date:   Mon Dec 30 14:23:11 2013 -0500

    DBIx::Class::Helpers is currently a requirement for DBIx::Class::ResultSource::View's which use Helper::Row::SubClass.  Use a base ResultSet class so that we can make Helper::ResultSet::SetOperations and Helper::ResultSet::Shortcut available to all of our classes.  SetOperations is specifically needed for UNION operations, Shortcut's are candy.  Note: Trying to add entire Helper::ResultSet seemed to cause problems.
This commit is contained in:
Eric A. Miller
2013-12-31 13:53:49 -05:00
parent d782ef17b4
commit a2bba4484d
25 changed files with 1578 additions and 11 deletions

View File

@@ -6,7 +6,9 @@ use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
__PACKAGE__->load_namespaces(
default_resultset_class => 'ResultSet',
);
our $VERSION = 31; # schema version used for upgrades, keep as integer

View File

@@ -0,0 +1,45 @@
package App::Netdisco::DB::Result::Virtual::CidrIps;
use strict;
use warnings;
use utf8;
use base 'DBIx::Class::Core';
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table('cidr_ips');
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(<<'ENDSQL');
SELECT host(network (cidr) + sub.int)::inet AS ip, NULL::text AS dns,
NULL::timestamp AS time_first, NULL::timestamp AS time_last, false::boolean AS active
FROM
( SELECT cidr, generate_series(1, broadcast(cidr) - (network(cidr)) - 1) AS int
FROM (
SELECT CASE WHEN family(cidr) = 4 THEN cidr
ELSE '0.0.0.0/32'::inet
END AS cidr
FROM ( SELECT ?::inet AS cidr) AS input) AS addr
) AS sub
ENDSQL
__PACKAGE__->add_columns(
"ip",
{ data_type => "inet", is_nullable => 0 },
"dns",
{ data_type => "text", is_nullable => 1 },
"active",
{ data_type => "boolean", is_nullable => 1 },
"time_first",
{
data_type => "timestamp",
is_nullable => 1,
},
"time_last",
{
data_type => "timestamp",
is_nullable => 1,
},
);
1;

View File

@@ -0,0 +1,11 @@
package App::Netdisco::DB::ResultSet;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';
__PACKAGE__->load_components(
qw{Helper::ResultSet::SetOperations Helper::ResultSet::Shortcut});
1;

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::Admin;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::Device;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::DevicePort;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::Node;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::NodeIp;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::NodeWireless;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -1,5 +1,5 @@
package App::Netdisco::DB::ResultSet::Subnet;
use base 'DBIx::Class::ResultSet';
use base 'App::Netdisco::DB::ResultSet';
use strict;
use warnings FATAL => 'all';

View File

@@ -137,6 +137,10 @@ Port
=item *
IP
=item *
Node
=item *

View File

@@ -16,7 +16,7 @@ set(
'_admin_tasks' => {},
'_reports_menu' => {},
'_reports' => {},
'_report_order' => [qw/Device Port Node VLAN Network Wireless/],
'_report_order' => [qw/Device Port IP Node VLAN Network Wireless/],
);
# this is what Dancer::Template::TemplateToolkit does by default

View File

@@ -0,0 +1,169 @@
package App::Netdisco::Web::Plugin::Report::IpInventory;
use Dancer ':syntax';
use Dancer::Plugin::DBIC;
use Dancer::Plugin::Auth::Extensible;
use App::Netdisco::Web::Plugin;
register_report(
{ category => 'IP',
tag => 'ipinventory',
label => 'IP Inventory',
provides_csv => 1,
}
);
# Following two Perl Core 5.10+
use Time::Piece;
use Time::Seconds;
hook 'before' => sub {
return
unless ( request->path eq uri_for('/report/ipinventory')->path
or index( request->path, uri_for('/ajax/content/report/ipinventory')->path )
== 0 );
my $start = Time::Piece->new - ONE_DAY * 29;
params->{'limit'} ||= 256;
params->{'daterange'}
||= $start->ymd . " to " . Time::Piece->new->ymd;
};
get '/ajax/content/report/ipinventory' => require_login sub {
# Default to something simple with no results to prevent
# "Search failed!" error
my $subnet = param('subnet') || '0.0.0.0/32';
my $age = param('age_on') || '0';
my $agenot = param('age_invert') || '0';
my ( $start, $end ) = param('daterange') =~ /(\d+-\d+-\d+)/gmx;
my $limit = param('limit') || 256;
my $order = param('order') || 'IP';
my $never = param('never') || '0';
# We need a reasonable limit to prevent a potential DoS, especially if
# 'never' is true. TODO: Need better input validation, both JS and
# server-side to provide user feedback
$limit = 8192 if $limit > 8192;
$order = $order eq 'IP' ? \'ip ASC' : \'age DESC';
my $rs1 = schema('netdisco')->resultset('DeviceIp')->search(
undef,
{ join => 'device',
select => [
'alias AS ip',
'creation AS time_first',
'device.last_discover AS time_last',
'dns',
\'true AS active',
\'false AS node',
\'age(device.last_discover) AS age'
],
as => [qw( ip time_first time_last dns active node age)],
}
)->hri;
my $rs2 = schema('netdisco')->resultset('NodeIp')->search(
undef,
{ columns => [qw( ip time_first time_last dns active)],
'+select' => [ \'true AS node', \'age(time_last) AS age' ],
'+as' => [ 'node', 'age' ],
}
)->hri;
my $rs3 = schema('netdisco')->resultset('NodeNbt')->search(
undef,
{ columns => [qw( ip time_first time_last )],
'+select' => [
'nbname AS dns', 'active',
\'true AS node', \'age(time_last) AS age'
],
'+as' => [ 'dns', 'active', 'node', 'age' ],
}
)->hri;
my $rs_union = $rs1->union( [ $rs2, $rs3 ] );
if ( $never ) {
my $rs4 = schema('netdisco')->resultset('Virtual::CidrIps')->search(
undef,
{ bind => [ $subnet ],
columns => [qw( ip time_first time_last dns active)],
'+select' => [ \'false AS node', \'age(time_last) AS age' ],
'+as' => [ 'node', 'age' ],
}
)->hri;
$rs_union = $rs_union->union( [$rs4] );
}
my $rs_sub = $rs_union->search(
{ ip => { '<<' => $subnet } },
{ order_by => [qw( ip time_last )],
select => [
\'DISTINCT ON (ip) ip',
'dns',
\'date_trunc(\'second\', time_last) AS time_last',
\'date_trunc(\'second\', time_first) AS time_first',
'active',
'node',
'age'
],
as => [
'ip', 'dns', 'time_last', 'time_first',
'active', 'node', 'age'
],
}
)->as_query;
my $rs;
if ( $age && $start && $end ) {
$start = $start . ' 00:00:00';
$end = $end . ' 23:59:59';
if ( $agenot ) {
$rs = $rs_union->search(
{ -or => [
time_first => [ { '<', $start }, undef ],
time_last => { '>', $end },
]
},
{ from => { me => $rs_sub }, }
);
}
else {
$rs = $rs_union->search(
{ -and => [
time_first => { '>=', $start },
time_last => { '<=', $end },
]
},
{ from => { me => $rs_sub }, }
);
}
}
else {
$rs = $rs_union->search( undef, { from => { me => $rs_sub }, } );
}
my @results = $rs->order_by($order)->limit($limit)->all;
return unless scalar @results;
if ( request->is_ajax ) {
template 'ajax/report/ipinventory.tt', { results => \@results, },
{ layout => undef };
}
else {
header( 'Content-Type' => 'text/comma-separated-values' );
template 'ajax/report/ipinventory_csv.tt', { results => \@results, },
{ layout => undef };
}
};
1;

View File

@@ -55,4 +55,15 @@ ajax '/ajax/data/port/typeahead' => require_login sub {
to_json \@$results;
};
ajax '/ajax/data/subnet/typeahead' => require_login sub {
my $q = param('query') || param('term');
$q = "$q\%" if $q !~ m/\%/;
my $nets = schema('netdisco')->resultset('Subnet')->search(
{ 'me.net::text' => { '-ilike' => $q }},
{ columns => ['net'] } );
content_type 'application/json';
to_json [map {$_->net} $nets->all];
};
true;

View File

@@ -45,6 +45,7 @@ web_plugins:
- Report::DeviceByLocation
- Report::DevicePoeStatus
- Report::DuplexMismatch
- Report::IpInventory
- Report::NodeMultiIPs
- Report::PhonesDiscovered
- Report::SsidInventory

View File

@@ -0,0 +1,235 @@
/*!
* Stylesheet for the Date Range Picker, for use with Bootstrap 2.x
*
* Copyright 2013 Dan Grossman ( http://www.dangrossman.info )
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Built for http://www.improvely.com
*/
.daterangepicker.dropdown-menu {
max-width: none;
z-index: 3000;
}
.daterangepicker.opensleft .ranges, .daterangepicker.opensleft .calendar {
float: left;
margin: 4px;
}
.daterangepicker.opensright .ranges, .daterangepicker.opensright .calendar {
float: right;
margin: 4px;
}
.daterangepicker .ranges {
width: 160px;
text-align: left;
}
.daterangepicker .ranges .range_inputs>div {
float: left;
}
.daterangepicker .ranges .range_inputs>div:nth-child(2) {
padding-left: 11px;
}
.daterangepicker .calendar {
display: none;
max-width: 250px;
}
.daterangepicker .calendar th, .daterangepicker .calendar td {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
white-space: nowrap;
text-align: center;
}
.daterangepicker .ranges label {
color: #333;
font-size: 11px;
margin-bottom: 2px;
text-transform: uppercase;
text-shadow: 1px 1px 0 #fff;
}
.daterangepicker .ranges input {
font-size: 11px;
}
.daterangepicker .ranges ul {
list-style: none;
margin: 0;
padding: 0;
}
.daterangepicker .ranges li {
font-size: 13px;
background: #f5f5f5;
border: 1px solid #f5f5f5;
color: #08c;
padding: 3px 12px;
margin-bottom: 8px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
cursor: pointer;
}
.daterangepicker .ranges li.active, .daterangepicker .ranges li:hover {
background: #08c;
border: 1px solid #08c;
color: #fff;
}
.daterangepicker .calendar-date {
border: 1px solid #ddd;
padding: 4px;
border-radius: 4px;
background: #fff;
}
.daterangepicker .calendar-time {
text-align: center;
margin: 8px auto 0 auto;
line-height: 30px;
}
.daterangepicker {
position: absolute;
background: #fff;
top: 100px;
left: 20px;
padding: 4px;
margin-top: 1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.daterangepicker.opensleft:before {
position: absolute;
top: -7px;
right: 9px;
display: inline-block;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-left: 7px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.daterangepicker.opensleft:after {
position: absolute;
top: -6px;
right: 10px;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
content: '';
}
.daterangepicker.opensright:before {
position: absolute;
top: -7px;
left: 9px;
display: inline-block;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-left: 7px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.daterangepicker.opensright:after {
position: absolute;
top: -6px;
left: 10px;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
content: '';
}
.daterangepicker table {
width: 100%;
margin: 0;
}
.daterangepicker td, .daterangepicker th {
text-align: center;
width: 20px;
height: 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
}
.daterangepicker td.off {
color: #999;
}
.daterangepicker td.disabled {
color: #999;
}
.daterangepicker td.available:hover, .daterangepicker th.available:hover {
background: #eee;
}
.daterangepicker td.in-range {
background: #ebf4f8;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.daterangepicker td.active, .daterangepicker td.active:hover {
background-color: #006dcc;
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
background-image: linear-gradient(top, #0088cc, #0044cc);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
border-color: #0044cc #0044cc #002a80;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.daterangepicker td.week, .daterangepicker th.week {
font-size: 80%;
color: #ccc;
}
.daterangepicker select.monthselect, .daterangepicker select.yearselect {
font-size: 12px;
padding: 1px;
height: auto;
margin: 0;
cursor: default;
}
.daterangepicker select.monthselect {
margin-right: 2%;
width: 56%;
}
.daterangepicker select.yearselect {
width: 40%;
}
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.ampmselect {
width: 60px;
margin-bottom: 0;
}

View File

@@ -495,6 +495,10 @@ td > form.nd_inline-form {
margin-left: 5px !important;
width: 152px;
}
.nd_sidebar-topinput {
margin-left: 5px !important;
width: 152px;
}
/* set the day/mon/year drop-down width */
#nd_days-select {
@@ -573,6 +577,10 @@ form .clearfix.success input {
padding-top: 3px !important;
}
.nd_sidebar-legend {
margin-bottom: 9px;
}
.nd_netmap-sidebar {
margin-top: 7px;
margin-left: -9px;

View File

@@ -0,0 +1,39 @@
<table class="table table-bordered table-condensed table-striped nd_floatinghead">
<thead>
<tr>
<th>Device</th>
<th class="nd_center-cell">DNS</th>
<th class="nd_center-cell">Last Used</th>
<th class="nd_center-cell">First Discovered</th>
</tr>
</thead>
</tbody>
[% FOREACH row IN results %]
<tr>
[% IF row.time_last && row.node %]
<td><a href="[% search_node %]&q=[% row.ip | uri %]">[% row.ip | html_entity %]</a>
[% '&nbsp;<i class="icon-book text-warning"></i>&nbsp;' IF NOT row.active %]
</td>
[% ELSIF row.time_last %]
<td><a href="[% search_device %]&q=[% row.ip | uri %]">[% row.ip | html_entity %]</a>
</td>
[% ELSE %]
<td>[% row.ip | html_entity %]</td>
[% END %]
<td class="nd_center-cell">[% row.dns | html_entity %]</td>
[% IF row.age %]
[% age = row.age.replace('mon', 'month') %]
[% IF age.match('(day|month|year)') %]
[% age = age.remove('(-)?\d{2}:\d{2}.*$') %]
[% ELSE %]
[% age = age.replace('(\d{2}:\d{2}):\d{2}(\.\d*)$', '$1') %]
[% END %]
[% ELSE %]
[% age = 'Never' %]
[% END %]
<td class="nd_center-cell">[% age | html_entity %]</td>
<td class="nd_center-cell">[% row.time_first || 'Never' | html_entity %]</td>
</tr>
[% END %]
</tbody>
</table>

View File

@@ -0,0 +1,14 @@
[% USE CSV %]
[% CSV.dump([ 'Device' 'DNS' 'Time Last' 'Time First' ]) %]
[% FOREACH row IN results %]
[% mylist = [] %]
[% mylist.push(row.ip) %]
[% mylist.push(row.dns) %]
[% mylist.push(row.time_first) %]
[% mylist.push(row.time_last) %]
[% CSV.dump(mylist) %]
[% END %]

View File

@@ -128,6 +128,8 @@
// on page load, load the content for the active tab
[% IF params.tab %]
[% IF params.tab != 'ipinventory' %]
$('#[% params.tab %]_form').trigger("submit");
[% END %]
[% END %]
});

View File

@@ -0,0 +1,876 @@
/**
* @version: 1.2
* @author: Dan Grossman http://www.dangrossman.info/
* @date: 2013-07-25
* @copyright: Copyright (c) 2012-2013 Dan Grossman. All rights reserved.
* @license: Licensed under Apache License v2.0. See http://www.apache.org/licenses/LICENSE-2.0
* @website: http://www.improvely.com/
*/
!function ($) {
var DateRangePicker = function (element, options, cb) {
var hasOptions = typeof options == 'object';
var localeObject;
//option defaults
this.startDate = moment().startOf('day');
this.endDate = moment().startOf('day');
this.minDate = false;
this.maxDate = false;
this.dateLimit = false;
this.showDropdowns = false;
this.showWeekNumbers = false;
this.timePicker = false;
this.timePickerIncrement = 30;
this.timePicker12Hour = true;
this.ranges = {};
this.opens = 'right';
this.buttonClasses = ['btn', 'btn-small'];
this.applyClass = 'btn-success';
this.cancelClass = 'btn-default';
this.format = 'MM/DD/YYYY';
this.separator = ' - ';
this.locale = {
applyLabel: 'Apply',
cancelLabel: 'Cancel',
fromLabel: 'From',
toLabel: 'To',
weekLabel: 'W',
customRangeLabel: 'Custom Range',
daysOfWeek: moment()._lang._weekdaysMin.slice(),
monthNames: moment()._lang._monthsShort.slice(),
firstDay: 0
};
this.cb = function () { };
// by default, the daterangepicker element is placed at the bottom of HTML body
this.parentEl = 'body';
//element that triggered the date range picker
this.element = $(element);
if (this.element.hasClass('pull-right'))
this.opens = 'left';
if (this.element.is('input')) {
this.element.on({
click: $.proxy(this.show, this),
focus: $.proxy(this.show, this)
});
} else {
this.element.on('click', $.proxy(this.show, this));
}
localeObject = this.locale;
if (hasOptions) {
if (typeof options.locale == 'object') {
$.each(localeObject, function (property, value) {
localeObject[property] = options.locale[property] || value;
});
}
if (options.applyClass) {
this.applyClass = options.applyClass;
}
if (options.cancelClass) {
this.cancelClass = options.cancelClass;
}
}
var DRPTemplate = '<div class="daterangepicker dropdown-menu">' +
'<div class="calendar left"></div>' +
'<div class="calendar right"></div>' +
'<div class="ranges">' +
'<div class="range_inputs">' +
'<div class="daterangepicker_start_input" style="float: left">' +
'<label for="daterangepicker_start">' + this.locale.fromLabel + '</label>' +
'<input class="input-mini" type="text" name="daterangepicker_start" value="" disabled="disabled" />' +
'</div>' +
'<div class="daterangepicker_end_input" style="float: left; padding-left: 11px">' +
'<label for="daterangepicker_end">' + this.locale.toLabel + '</label>' +
'<input class="input-mini" type="text" name="daterangepicker_end" value="" disabled="disabled" />' +
'</div>' +
'<button class="' + this.applyClass + ' applyBtn" disabled="disabled">' + this.locale.applyLabel + '</button>&nbsp;' +
'<button class="' + this.cancelClass + ' cancelBtn">' + this.locale.cancelLabel + '</button>' +
'</div>' +
'</div>' +
'</div>';
this.parentEl = (hasOptions && options.parentEl && $(options.parentEl)) || $(this.parentEl);
//the date range picker
this.container = $(DRPTemplate).appendTo(this.parentEl);
if (hasOptions) {
if (typeof options.format == 'string')
this.format = options.format;
if (typeof options.separator == 'string')
this.separator = options.separator;
if (typeof options.startDate == 'string')
this.startDate = moment(options.startDate, this.format);
if (typeof options.endDate == 'string')
this.endDate = moment(options.endDate, this.format);
if (typeof options.minDate == 'string')
this.minDate = moment(options.minDate, this.format);
if (typeof options.maxDate == 'string')
this.maxDate = moment(options.maxDate, this.format);
if (typeof options.startDate == 'object')
this.startDate = moment(options.startDate);
if (typeof options.endDate == 'object')
this.endDate = moment(options.endDate);
if (typeof options.minDate == 'object')
this.minDate = moment(options.minDate);
if (typeof options.maxDate == 'object')
this.maxDate = moment(options.maxDate);
if (typeof options.ranges == 'object') {
for (var range in options.ranges) {
var start = moment(options.ranges[range][0]);
var end = moment(options.ranges[range][1]);
// If we have a min/max date set, bound this range
// to it, but only if it would otherwise fall
// outside of the min/max.
if (this.minDate && start.isBefore(this.minDate))
start = moment(this.minDate);
if (this.maxDate && end.isAfter(this.maxDate))
end = moment(this.maxDate);
// If the end of the range is before the minimum (if min is set) OR
// the start of the range is after the max (also if set) don't display this
// range option.
if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) {
continue;
}
this.ranges[range] = [start, end];
}
var list = '<ul>';
for (var range in this.ranges) {
list += '<li>' + range + '</li>';
}
list += '<li>' + this.locale.customRangeLabel + '</li>';
list += '</ul>';
this.container.find('.ranges').prepend(list);
}
if (typeof options.dateLimit == 'object')
this.dateLimit = options.dateLimit;
// update day names order to firstDay
if (typeof options.locale == 'object') {
if (typeof options.locale.daysOfWeek == 'object') {
// Create a copy of daysOfWeek to avoid modification of original
// options object for reusability in multiple daterangepicker instances
this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
}
if (typeof options.locale.firstDay == 'number') {
this.locale.firstDay = options.locale.firstDay;
var iterator = options.locale.firstDay;
while (iterator > 0) {
this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
iterator--;
}
}
}
if (typeof options.opens == 'string')
this.opens = options.opens;
if (typeof options.showWeekNumbers == 'boolean') {
this.showWeekNumbers = options.showWeekNumbers;
}
if (typeof options.buttonClasses == 'string') {
this.buttonClasses = [options.buttonClasses];
}
if (typeof options.buttonClasses == 'object') {
this.buttonClasses = options.buttonClasses;
}
if (typeof options.showDropdowns == 'boolean') {
this.showDropdowns = options.showDropdowns;
}
if (typeof options.timePicker == 'boolean') {
this.timePicker = options.timePicker;
}
if (typeof options.timePickerIncrement == 'number') {
this.timePickerIncrement = options.timePickerIncrement;
}
if (typeof options.timePicker12Hour == 'boolean') {
this.timePicker12Hour = options.timePicker12Hour;
}
}
if (!this.timePicker) {
this.startDate = this.startDate.startOf('day');
this.endDate = this.endDate.startOf('day');
}
//apply CSS classes to buttons
var c = this.container;
$.each(this.buttonClasses, function (idx, val) {
c.find('button').addClass(val);
});
if (this.opens == 'right') {
//swap calendar positions
var left = this.container.find('.calendar.left');
var right = this.container.find('.calendar.right');
left.removeClass('left').addClass('right');
right.removeClass('right').addClass('left');
}
if (typeof options == 'undefined' || typeof options.ranges == 'undefined') {
this.container.find('.calendar').show();
this.move();
}
if (typeof cb == 'function')
this.cb = cb;
this.container.addClass('opens' + this.opens);
//try parse date if in text input
if (!hasOptions || (typeof options.startDate == 'undefined' && typeof options.endDate == 'undefined')) {
if ($(this.element).is('input[type=text]')) {
var val = $(this.element).val();
var split = val.split(this.separator);
var start, end;
if (split.length == 2) {
start = moment(split[0], this.format);
end = moment(split[1], this.format);
}
if (start != null && end != null) {
this.startDate = start;
this.endDate = end;
}
}
}
//state
this.oldStartDate = this.startDate.clone();
this.oldEndDate = this.endDate.clone();
this.leftCalendar = {
month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]),
calendar: []
};
this.rightCalendar = {
month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute()]),
calendar: []
};
//event listeners
this.container.on('mousedown', $.proxy(this.mousedown, this));
this.container.find('.calendar')
.on('click', '.prev', $.proxy(this.clickPrev, this))
.on('click', '.next', $.proxy(this.clickNext, this))
.on('click', 'td.available', $.proxy(this.clickDate, this))
.on('mouseenter', 'td.available', $.proxy(this.enterDate, this))
.on('mouseleave', 'td.available', $.proxy(this.updateFormInputs, this))
.on('change', 'select.yearselect', $.proxy(this.updateMonthYear, this))
.on('change', 'select.monthselect', $.proxy(this.updateMonthYear, this))
.on('change', 'select.hourselect,select.minuteselect,select.ampmselect', $.proxy(this.updateTime, this));
this.container.find('.ranges')
.on('click', 'button.applyBtn', $.proxy(this.clickApply, this))
.on('click', 'button.cancelBtn', $.proxy(this.clickCancel, this))
.on('click', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this))
.on('click', 'li', $.proxy(this.clickRange, this))
.on('mouseenter', 'li', $.proxy(this.enterRange, this))
.on('mouseleave', 'li', $.proxy(this.updateFormInputs, this));
this.element.on('keyup', $.proxy(this.updateFromControl, this));
this.updateView();
this.updateCalendars();
};
DateRangePicker.prototype = {
constructor: DateRangePicker,
mousedown: function (e) {
e.stopPropagation();
},
updateView: function () {
this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year());
this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year());
this.updateFormInputs();
},
updateFormInputs: function () {
this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format));
this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format));
if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) {
this.container.find('button.applyBtn').removeAttr('disabled');
} else {
this.container.find('button.applyBtn').attr('disabled', 'disabled');
}
},
updateFromControl: function () {
if (!this.element.is('input')) return;
if (!this.element.val().length) return;
var dateString = this.element.val().split(this.separator);
var start = moment(dateString[0], this.format);
var end = moment(dateString[1], this.format);
if (start == null || end == null) return;
if (end.isBefore(start)) return;
this.oldStartDate = this.startDate.clone();
this.oldEndDate = this.endDate.clone();
this.startDate = start;
this.endDate = end;
if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
this.notify();
this.updateCalendars();
},
notify: function () {
this.updateView();
this.cb(this.startDate, this.endDate);
},
move: function () {
var parentOffset = {
top: this.parentEl.offset().top - (this.parentEl.is('body') ? 0 : this.parentEl.scrollTop()),
left: this.parentEl.offset().left - (this.parentEl.is('body') ? 0 : this.parentEl.scrollLeft())
};
if (this.opens == 'left') {
this.container.css({
top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
right: $(window).width() - this.element.offset().left - this.element.outerWidth() - parentOffset.left,
left: 'auto'
});
if (this.container.offset().left < 0) {
this.container.css({
right: 'auto',
left: 9
});
}
} else {
this.container.css({
top: this.element.offset().top + this.element.outerHeight() - parentOffset.top,
left: this.element.offset().left - parentOffset.left,
right: 'auto'
});
if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
this.container.css({
left: 'auto',
right: 0
});
}
}
},
show: function (e) {
this.container.show();
this.move();
if (e) {
e.stopPropagation();
e.preventDefault();
}
$(document).on('mousedown', $.proxy(this.hide, this));
this.element.trigger('shown', {target: e.target, picker: this});
},
hide: function (e) {
this.container.hide();
if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
this.notify();
this.oldStartDate = this.startDate.clone();
this.oldEndDate = this.endDate.clone();
$(document).off('mousedown', this.hide);
this.element.trigger('hidden', { picker: this });
},
enterRange: function (e) {
var label = e.target.innerHTML;
if (label == this.locale.customRangeLabel) {
this.updateView();
} else {
var dates = this.ranges[label];
this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format));
this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format));
}
},
showCalendars: function() {
this.container.find('.calendar').show();
this.move();
},
updateInputText: function() {
if (this.element.is('input'))
this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
},
clickRange: function (e) {
var label = e.target.innerHTML;
if (label == this.locale.customRangeLabel) {
this.showCalendars();
} else {
var dates = this.ranges[label];
this.startDate = dates[0];
this.endDate = dates[1];
if (!this.timePicker) {
this.startDate.startOf('day');
this.endDate.startOf('day');
}
this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute());
this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute());
this.updateCalendars();
this.updateInputText();
this.container.find('.calendar').hide();
this.hide();
}
},
clickPrev: function (e) {
var cal = $(e.target).parents('.calendar');
if (cal.hasClass('left')) {
this.leftCalendar.month.subtract('month', 1);
} else {
this.rightCalendar.month.subtract('month', 1);
}
this.updateCalendars();
},
clickNext: function (e) {
var cal = $(e.target).parents('.calendar');
if (cal.hasClass('left')) {
this.leftCalendar.month.add('month', 1);
} else {
this.rightCalendar.month.add('month', 1);
}
this.updateCalendars();
},
enterDate: function (e) {
var title = $(e.target).attr('data-title');
var row = title.substr(1, 1);
var col = title.substr(3, 1);
var cal = $(e.target).parents('.calendar');
if (cal.hasClass('left')) {
this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format));
} else {
this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format));
}
},
clickDate: function (e) {
var title = $(e.target).attr('data-title');
var row = title.substr(1, 1);
var col = title.substr(3, 1);
var cal = $(e.target).parents('.calendar');
if (cal.hasClass('left')) {
var startDate = this.leftCalendar.calendar[row][col];
var endDate = this.endDate;
if (typeof this.dateLimit == 'object') {
var maxDate = moment(startDate).add(this.dateLimit).startOf('day');
if (endDate.isAfter(maxDate)) {
endDate = maxDate;
}
}
} else {
var startDate = this.startDate;
var endDate = this.rightCalendar.calendar[row][col];
if (typeof this.dateLimit == 'object') {
var minDate = moment(endDate).subtract(this.dateLimit).startOf('day');
if (startDate.isBefore(minDate)) {
startDate = minDate;
}
}
}
cal.find('td').removeClass('active');
if (startDate.isSame(endDate) || startDate.isBefore(endDate)) {
$(e.target).addClass('active');
this.startDate = startDate;
this.endDate = endDate;
} else if (startDate.isAfter(endDate)) {
$(e.target).addClass('active');
this.startDate = startDate;
this.endDate = moment(startDate).add('day', 1).startOf('day');
}
this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year());
this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year());
this.updateCalendars();
},
clickApply: function (e) {
this.updateInputText();
this.hide();
},
clickCancel: function (e) {
this.startDate = this.oldStartDate;
this.endDate = this.oldEndDate;
this.updateView();
this.updateCalendars();
this.hide();
},
updateMonthYear: function (e) {
var isLeft = $(e.target).closest('.calendar').hasClass('left');
var cal = this.container.find('.calendar.left');
if (!isLeft)
cal = this.container.find('.calendar.right');
// Month must be Number for new moment versions
var month = parseInt(cal.find('.monthselect').val(), 10);
var year = cal.find('.yearselect').val();
if (isLeft) {
this.leftCalendar.month.month(month).year(year);
} else {
this.rightCalendar.month.month(month).year(year);
}
this.updateCalendars();
},
updateTime: function(e) {
var isLeft = $(e.target).closest('.calendar').hasClass('left');
var cal = this.container.find('.calendar.left');
if (!isLeft)
cal = this.container.find('.calendar.right');
var hour = parseInt(cal.find('.hourselect').val());
var minute = parseInt(cal.find('.minuteselect').val());
if (this.timePicker12Hour) {
var ampm = cal.find('.ampmselect').val();
if (ampm == 'PM' && hour < 12)
hour += 12;
if (ampm == 'AM' && hour == 12)
hour = 0;
}
if (isLeft) {
var start = this.startDate.clone();
start.hour(hour);
start.minute(minute);
this.startDate = start;
this.leftCalendar.month.hour(hour).minute(minute);
} else {
var end = this.endDate.clone();
end.hour(hour);
end.minute(minute);
this.endDate = end;
this.rightCalendar.month.hour(hour).minute(minute);
}
this.updateCalendars();
},
updateCalendars: function () {
this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), 'left');
this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), 'right');
this.container.find('.calendar.left').html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate));
this.container.find('.calendar.right').html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.startDate, this.maxDate));
this.container.find('.ranges li').removeClass('active');
var customRange = true;
var i = 0;
for (var range in this.ranges) {
if (this.timePicker) {
if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) {
customRange = false;
this.container.find('.ranges li:eq(' + i + ')').addClass('active');
}
} else {
//ignore times when comparing dates if time picker is not enabled
if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) {
customRange = false;
this.container.find('.ranges li:eq(' + i + ')').addClass('active');
}
}
i++;
}
if (customRange)
this.container.find('.ranges li:last').addClass('active');
},
buildCalendar: function (month, year, hour, minute, side) {
var firstDay = moment([year, month, 1]);
var lastMonth = moment(firstDay).subtract('month', 1).month();
var lastYear = moment(firstDay).subtract('month', 1).year();
var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
var dayOfWeek = firstDay.day();
//initialize a 6 rows x 7 columns array for the calendar
var calendar = [];
for (var i = 0; i < 6; i++) {
calendar[i] = [];
}
//populate the calendar with date objects
var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
if (startDay > daysInLastMonth)
startDay -= 7;
if (dayOfWeek == this.locale.firstDay)
startDay = daysInLastMonth - 6;
var curDate = moment([lastYear, lastMonth, startDay, 12, minute]);
for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('hour', 24)) {
if (i > 0 && col % 7 == 0) {
col = 0;
row++;
}
calendar[row][col] = curDate.clone().hour(hour);
curDate.hour(12);
}
return calendar;
},
renderDropdowns: function (selected, minDate, maxDate) {
var currentMonth = selected.month();
var monthHtml = '<select class="monthselect">';
var inMinYear = false;
var inMaxYear = false;
for (var m = 0; m < 12; m++) {
if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) {
monthHtml += "<option value='" + m + "'" +
(m === currentMonth ? " selected='selected'" : "") +
">" + this.locale.monthNames[m] + "</option>";
}
}
monthHtml += "</select>";
var currentYear = selected.year();
var maxYear = (maxDate && maxDate.year()) || (currentYear + 5);
var minYear = (minDate && minDate.year()) || (currentYear - 50);
var yearHtml = '<select class="yearselect">';
for (var y = minYear; y <= maxYear; y++) {
yearHtml += '<option value="' + y + '"' +
(y === currentYear ? ' selected="selected"' : '') +
'>' + y + '</option>';
}
yearHtml += '</select>';
return monthHtml + yearHtml;
},
renderCalendar: function (calendar, selected, minDate, maxDate) {
var html = '<div class="calendar-date">';
html += '<table class="table-condensed">';
html += '<thead>';
html += '<tr>';
// add empty cell for week number
if (this.showWeekNumbers)
html += '<th></th>';
if (!minDate || minDate.isBefore(calendar[1][1])) {
html += '<th class="prev available"><i class="icon-arrow-left glyphicon glyphicon-arrow-left"></i></th>';
} else {
html += '<th></th>';
}
var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");
if (this.showDropdowns) {
dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate);
}
html += '<th colspan="5" style="width: auto">' + dateHtml + '</th>';
if (!maxDate || maxDate.isAfter(calendar[1][1])) {
html += '<th class="next available"><i class="icon-arrow-right glyphicon glyphicon-arrow-right"></i></th>';
} else {
html += '<th></th>';
}
html += '</tr>';
html += '<tr>';
// add week number label
if (this.showWeekNumbers)
html += '<th class="week">' + this.locale.weekLabel + '</th>';
$.each(this.locale.daysOfWeek, function (index, dayOfWeek) {
html += '<th>' + dayOfWeek + '</th>';
});
html += '</tr>';
html += '</thead>';
html += '<tbody>';
for (var row = 0; row < 6; row++) {
html += '<tr>';
// add week number
if (this.showWeekNumbers)
html += '<td class="week">' + calendar[row][0].week() + '</td>';
for (var col = 0; col < 7; col++) {
var cname = 'available ';
cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off';
if ((minDate && calendar[row][col].isBefore(minDate)) || (maxDate && calendar[row][col].isAfter(maxDate))) {
cname = ' off disabled ';
} else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) {
cname += ' active ';
if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) {
cname += ' start-date ';
}
if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) {
cname += ' end-date ';
}
} else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) {
cname += ' in-range ';
if (calendar[row][col].isSame(this.startDate)) { cname += ' start-date '; }
if (calendar[row][col].isSame(this.endDate)) { cname += ' end-date '; }
}
var title = 'r' + row + 'c' + col;
html += '<td class="' + cname.replace(/\s+/g, ' ').replace(/^\s?(.*?)\s?$/, '$1') + '" data-title="' + title + '">' + calendar[row][col].date() + '</td>';
}
html += '</tr>';
}
html += '</tbody>';
html += '</table>';
html += '</div>';
if (this.timePicker) {
html += '<div class="calendar-time">';
html += '<select class="hourselect">';
var start = 0;
var end = 23;
var selected_hour = selected.hour();
if (this.timePicker12Hour) {
start = 1;
end = 12;
if (selected_hour >= 12)
selected_hour -= 12;
if (selected_hour == 0)
selected_hour = 12;
}
for (var i = start; i <= end; i++) {
if (i == selected_hour) {
html += '<option value="' + i + '" selected="selected">' + i + '</option>';
} else {
html += '<option value="' + i + '">' + i + '</option>';
}
}
html += '</select> : ';
html += '<select class="minuteselect">';
for (var i = 0; i < 60; i += this.timePickerIncrement) {
var num = i;
if (num < 10)
num = '0' + num;
if (i == selected.minute()) {
html += '<option value="' + i + '" selected="selected">' + num + '</option>';
} else {
html += '<option value="' + i + '">' + num + '</option>';
}
}
html += '</select> ';
if (this.timePicker12Hour) {
html += '<select class="ampmselect">';
if (selected.hour() >= 12) {
html += '<option value="AM">AM</option><option value="PM" selected="selected">PM</option>';
} else {
html += '<option value="AM" selected="selected">AM</option><option value="PM">PM</option>';
}
html += '</select>';
}
html += '</div>';
}
return html;
}
};
$.fn.daterangepicker = function (options, cb) {
this.each(function () {
var el = $(this);
if (!el.data('daterangepicker'))
el.data('daterangepicker', new DateRangePicker(el, options, cb));
});
return this;
};
}(window.jQuery);

6
Netdisco/share/views/js/moment.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,14 +2,59 @@
// ajax content is loaded
var path = 'report';
// fields in the IP Inventory Report form
var form_inputs = $(".nd_colored-input");
// this is called by do_search to support local code
// here, when tab changes need to strike/unstrike the navbar search
function inner_view_processing(tab) { }
function inner_view_processing(tab) {
// activate modals, tooltips and popovers
$('.nd_modal').modal({show: false});
$("[rel=tooltip]").tooltip({live: true});
$("[rel=popover]").popover({live: true});
}
// on load, check initial Device Search Options form state,
// and on each change to the form fields
$(document).ready(function() {
var tab = '[% report.tag %]'
var target = '#' + tab + '_pane';
});
// sidebar form fields should change colour and have trash icon
form_inputs.each(function() {device_form_state($(this))});
form_inputs.change(function() {device_form_state($(this))});
$('#nd_ipinventory-subnet').on('input', function(event) {
if ($(this).val().indexOf(':') != -1) {
$('#never').attr('disabled', 'disabled');
}
else {
$('#never').removeAttr('disabled');
}
});
// activate typeahead on prefix/subnet box
$('#nd_ipinventory-subnet').typeahead({
source: function (query, process) {
return $.get( uri_base + '/ajax/data/subnet/typeahead', { query: query }, function (data) {
return process(data);
});
}
,matcher: function () { return true; } // trust backend
,delay: 250
,minLength: 3
});
// change color of daterange input when activated
$('#age_on').change(function() {
if ($(this).is(':checked')) {
$('#daterange').parent('.clearfix').addClass('success');
}
else {
$('#daterange').parent('.clearfix').removeClass('success');
}
});
// fire the event to set success class correctly on page load
$('#age_on').change();
});

View File

@@ -45,6 +45,7 @@
<link rel="stylesheet" href="[% uri_base %]/css/toastr.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/netdisco.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/bootstrap-tree.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/daterangepicker-bs2.css"/>
<link rel="stylesheet" href="[% uri_base %]/css/nd_print.css" media="print"/>
[% FOREACH add_css IN settings._additional_css %]

View File

@@ -0,0 +1,98 @@
<div class="clearfix">
<input id="nd_ipinventory-subnet" class="nd_sidebar-topinput nd_colored-input"
placeholder="CIDR Prefix/Subnet" required="required"
name="subnet" value="[% params.subnet | html_entity %]" type="text" autocomplete="off"
rel="tooltip" data-placement="left" data-offset="5" data-title="Prefix/Subnet in CIDR Format"/>
</div>
<fieldset>
<legend class="nd_sidebar-legend">
<label class="checkbox">
<input type="checkbox" id="age_on" name="age_on"
[% 'checked="checked"' UNLESS (params.exists('age_on') AND params.age_on == '') %]/>
<em><strong>Age Filter</strong></em>
</label>
</legend>
<div class="clearfix">
<ul class="unstyled">
<li>
<div class="clearfix input-prepend">
<label class="add-on">
<input type="checkbox" id="age_invert"
name="age_invert"[% ' checked="checked"' IF params.age_invert %]/>
</label>
<label class="nd_checkboxlabel" for="age_invert">
<span class="nd_searchcheckbox uneditable-input">Not</span>
</label>
</div>
</li>
</ul>
<em class="muted">Seen within the date range:</em><br/>
<input class="nd_side-input" id="daterange"
type="text" name="daterange" value="[% params.daterange | html_entity %]"/>
</fieldset>
<fieldset>
<legend class="nd_sidebar-legend">
<label><em><strong>Options</strong></em></label>
</legend>
<div class="clearfix">
<ul class="unstyled">
<li>
<em class="muted">Limit:</em><br/>
<select id="nd_mac-format" class="nd_side-select" name="limit">
[% FOREACH size IN [ '32', '64', '128', '256', '1024', '2048', '4096', '8192' ] %]
<option[% ' selected="selected"' IF params.limit == size %]>[% size %]</option>
[% END %]
</select>
</li>
<li>
<em class="muted">Order By:</em><br/>
<select id="nd_mac-format" class="nd_side-select" name="order">
[% FOREACH item IN [ 'IP', 'Age' ] %]
<option[% ' selected="selected"' IF params.order == item %]>[% item %]</option>
[% END %]
</select>
</li>
</ul>
<div class="clearfix input-prepend"
rel="tooltip" data-placement="left" data-offset="5" data-title="Applies to IPv4 Only">
<label class="add-on">
<input type="checkbox" id="never"
name="never"[% ' checked="checked"' IF params.never %]/>
</label>
<label class="nd_checkboxlabel" for="never">
<span class="nd_searchcheckbox uneditable-input">List IP's Never Seen</span>
</label>
</div>
</div>
</fieldset>
<button id="[% tab.id %]_submit" type="submit" class="btn btn-info">
<i class="icon-search icon-large pull-left nd_navbar-icon"></i> Search IPs</button>
<script type = "text/javascript">
[%+ INCLUDE 'js/daterangepicker.js' -%]
[%+ INCLUDE 'js/moment.min.js' -%]
$('#daterange').daterangepicker({
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract('days', 1), moment().subtract('days', 1)],
'Last 7 Days': [moment().subtract('days', 6), moment()],
'Last 30 Days': [moment().subtract('days', 29), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract('month', 1).startOf('month'), moment().subtract('month', 1).endOf('month')]
},
startDate: moment().subtract('days', 29),
endDate: moment(),
minDate: '2004-01-01',
showDropdowns: true,
timePicker: false,
opens: 'left',
format: 'YYYY-MM-DD',
separator: ' to ',
}, function(start, end) {
$('#daterange span').html(start.format('MMM D, YYYY') + ' - ' + end.format('MMM D, YYYY'));
});
</script>