release 2.011000

Squashed commit of the following:

commit 3f1730957b6accbc11737e46c201453d7219d03e
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Jul 29 08:01:59 2013 +0100

    ready for 2.011000

commit e1873ca58375b458d9543576951f1003e1c28d35
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jul 28 00:02:20 2013 +0100

    Find the RW snmp community string correctly now

commit 039780bc66ca0d8b19767c38a21aa208feafeaf7
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jul 27 23:07:05 2013 +0100

    User Management (for admins only)

commit 213352d54ee8e71cbca5ae2c1c75696800c4216b
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jul 27 21:17:57 2013 +0100

    Table headers float on the page when scrolling

commit 598960e9141b0d9fc4f9a234a7d8fe02a81ba0f9
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jul 27 18:05:00 2013 +0100

    Port Utilization report

commit d25e41894476c74bee747e38960a277e2f5b2072
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jul 27 15:20:23 2013 +0100

    Button to empty the job queue, and improve display when the queue is empty

commit 18125d1a758b5707ab4c0ff8b65dfdd90dc32664
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jul 27 14:23:48 2013 +0100

    Swap play/pause icons in jobqueue

commit 9eead5328a127689701ac28d5bcf1cfa39edaf99
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jul 25 23:11:17 2013 +0100

    Revert "No longer depend on Moo"

    This reverts commit 0a87ad4b410fa784bfbe823f3e6ede7c979144f3.

    Conflicts:

    	Netdisco/Changes

commit d0c31effa834201f1592c1fc3da9a6a689a3a43c
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jul 25 23:01:32 2013 +0100

    REMOTE_USER is an env var, not an HTTP Header

commit 0a87ad4b410fa784bfbe823f3e6ede7c979144f3
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jul 25 22:35:05 2013 +0100

    No longer depend on Moo

commit 7ccbb04e6f7c1701194d996baa557affcda48103
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed Jul 24 23:44:42 2013 +0100

    ready for 2.010004

commit 6314c5a054d56d7829797d37c6627b2cbccde4ab
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed Jul 24 23:41:51 2013 +0100

    Navbar query box was being cleared sometimes under admin task panels

commit 271a5d9db17b288aeff43ee29a6bbf753bf823de
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed Jul 24 00:03:05 2013 +0100

    update TODO

commit 3103f968a9fb128726ed929589137cb6011e2591
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Tue Jul 23 23:58:19 2013 +0100

    ready for 2.010002

commit 0368df1dbdfe6d764eec05f2bf37587fff795995
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Tue Jul 23 23:17:17 2013 +0100

    fix bugs in topo update code

commit 43b7203ca3270dc2e02a097472179517087522d2
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Tue Jul 23 23:12:36 2013 +0100

    fix FF bug with forms embedded in tables

commit f86c5d7d3d8d293a781c2ec7dc7a18bfb3c8bf78
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Tue Jul 23 17:15:35 2013 +0100

    Nullify unused schema changes

commit 649e4c471d524013f87257e11fffa7789dccd01d
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 20 13:48:20 2013 +0100

    version bump

commit ac6ce399b2bd596444a629f24ddea5eca0fff56a
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 20 13:41:08 2013 +0100

    Handle UTF-8 data in the device port remote_id

commit c73b86c0204ddd98e9d27437028a7000d70338bf
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 20 13:35:32 2013 +0100

    revert bytea conversion on remote_id

commit a144f42cf93803882bb8492cd3ce3a8e5679d383
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Jun 17 23:09:50 2013 +0100

    bump version for beta release

commit 3b791c93d7d9b7358bf46f31e322a9b807823d9d
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Jun 17 22:57:59 2013 +0100

    Pass event param to all js functions which require it

commit da38badef893fc1503a797a99c34504db71e7c20
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Jun 17 22:35:48 2013 +0100

    Change data type on device_port remote_id to bytea

commit 727237951a5576b476dee127b3cef777afb51df8
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Jun 17 10:52:28 2013 +0100

    fix help message in netdisco-web-fg

commit 25bc026dc5e0177cd3aa81c11cdace091eb68f36
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Mon Jun 17 08:16:56 2013 +0100

    bump version for new release

commit d4042f6e8db42c7a85df4dcf9690fec72ad2db69
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 18:27:52 2013 +0100

    Job Queue page play/pause/refresh controls

commit b6c9152516d7800409b7a73c5d0cdce6dd405492
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 17:50:06 2013 +0100

    limit size of job queue table

commit ac9e5feb8b774071fcf4423dd862dced74dee9e6
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 17:47:55 2013 +0100

    update bugs link

commit 9c0fb0e9aedc6297f4462c3cf88343f6d0df40b6
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 17:41:29 2013 +0100

    update MANIFEST

commit 7aaa2fff91ed2b1839bdbb79081d90ad3e144f47
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 17:40:35 2013 +0100

    Fix Plack middleware config for Expiry

commit 313e2cf014cf0da7cf85074e390ad394b28bf42d
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 17:23:00 2013 +0100

    Support for delegated authentication with REMOTE_USER and X-REMOTE_USER

commit 85e21f2bf296c4a5ca6b5afb5091694e56e3031f
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 14:24:08 2013 +0100

    Add tooltip showing the job queue item logged status message

commit 9b14f53ebed51eb46ea278807cfe8a2fbd28743c
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 13:43:26 2013 +0100

    Increase default frequency of job queue polling to 2 seconds

commit 6ba46818d8ab2100c652c8eb8e98bc6f5a54e273
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 12:57:43 2013 +0100

    workaround for https://github.com/PerlDancer/Dancer/issues/935

commit c7a2d8a9d45716959bedbbb8db4cdd82a5950642
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sun Jun 16 11:54:18 2013 +0100

    Fix hyperlinks when running behind reverse proxy on custom path

commit 0620efa404bc25cb0a9ada5aa6f1b092d5c4d482
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jun 15 18:31:19 2013 +0100

    update deploy docs

commit 857b1c7aa0fe832f8948349eda5211eb38ba3099
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jun 15 18:16:50 2013 +0100

    add note about compiler dependency

commit 02a2ad6b2c52db9fbc1e24bc8888f658dc7084ad
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jun 15 17:44:29 2013 +0100

    sort vlans, macs, ips in device port view

commit 097bad77310728a98b261a2cfca4de7ab50be94b
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jun 15 16:32:20 2013 +0100

    hint when calling web in fg without starman

commit 6425d89ddb2b56129c610482134482d8f9455d40
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Sat Jun 15 15:53:26 2013 +0100

    macwalk and arpwalk refactored

commit d527b9d05addc82fb38c84f6fea1aa5818fc68d5
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 13 22:27:34 2013 +0100

    implement is_macsuckable and is_arpnipable

commit 7af10ed313e25f5d99a22b53ba438225c2259069
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 13 22:17:39 2013 +0100

    version bump

commit 8ace3bf8fa48cf3e14bdf86fad5a4862aad50a4b
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 13 22:14:05 2013 +0100

    tidy up user menu

commit e6eef605c248471dbfe7ec62cd04d73d653523ca
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 13 22:02:52 2013 +0100

    Add discoverall, macwalk, arpwalk items to the Admin Tasks menu

commit 2631fabd1eccd8a3971e4762eebe57f406623bee
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 13 21:21:50 2013 +0100

    remove length() which only became sane in 5.12

commit a7b7169070a58685cacde26a3b6d462e74be9928
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Thu Jun 13 19:07:56 2013 +0100

    Use DBIx::Class new collapsed query support when we can

commit 77cddab8ba7033ccb1ecae257bafa4eef8f99f47
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed Jun 12 17:26:47 2013 +0100

    Database config simplified to only four essential settings

commit 6ed0802bf2ab0fd898ce6945451b8ca6566ae551
Author: Oliver Gorwits <oliver@cpan.org>
Date:   Wed Jun 12 13:03:20 2013 +0100

    Ask to set up guest user for Admin/Port Control rights in deploy script
This commit is contained in:
Oliver Gorwits
2013-07-29 08:03:07 +01:00
parent 5f9065b753
commit 86d74b0100
39 changed files with 1013 additions and 37 deletions

View File

@@ -1,3 +1,22 @@
2.011000 - 2013-07-29
[NEW FEATURES]
* Port Utilization report
* User Management (for admins only)
[ENHANCEMENTS]
* Add docs note about SSL support
* Button to empty the job queue, and improve display when the queue is empty
* Table headers float on the page when scrolling
[BUG FIXES]
* REMOTE_USER is an env var, not an HTTP Header
* Swap play/pause icons in jobqueue
* Find the RW snmp community string correctly now
2.010004 - 2013-07-24
[BUG FIXES]

View File

@@ -72,6 +72,7 @@ lib/App/Netdisco/DB/Result/Virtual/DevicePortVlanNative.pm
lib/App/Netdisco/DB/Result/Virtual/DevicePortVlanTagged.pm
lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm
lib/App/Netdisco/DB/Result/Virtual/NodeWithAge.pm
lib/App/Netdisco/DB/Result/Virtual/PortUtilization.pm
lib/App/Netdisco/DB/ResultSet/Admin.pm
lib/App/Netdisco/DB/ResultSet/Device.pm
lib/App/Netdisco/DB/ResultSet/DevicePort.pm
@@ -98,6 +99,7 @@ lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-2-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-20-21-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-21-22-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-22-23-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-23-24-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-3-4-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-4-5-PostgreSQL.sql
lib/App/Netdisco/DB/schema_versions/App-Netdisco-DB-5-6-PostgreSQL.sql
@@ -127,6 +129,7 @@ lib/App/Netdisco/Web/Plugin.pm
lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
lib/App/Netdisco/Web/Plugin/AdminTask/PseudoDevice.pm
lib/App/Netdisco/Web/Plugin/AdminTask/Topology.pm
lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm
lib/App/Netdisco/Web/Plugin/Device/Addresses.pm
lib/App/Netdisco/Web/Plugin/Device/Details.pm
lib/App/Netdisco/Web/Plugin/Device/Modules.pm
@@ -134,6 +137,7 @@ lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm
lib/App/Netdisco/Web/Plugin/Device/Ports.pm
lib/App/Netdisco/Web/Plugin/Inventory.pm
lib/App/Netdisco/Web/Plugin/Report/DuplexMismatch.pm
lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm
lib/App/Netdisco/Web/Plugin/Search/Device.pm
lib/App/Netdisco/Web/Plugin/Search/Node.pm
lib/App/Netdisco/Web/Plugin/Search/Port.pm
@@ -194,19 +198,23 @@ share/public/javascripts/jquery-deserialize.js
share/public/javascripts/jquery-history.js
share/public/javascripts/jquery-latest.min.js
share/public/javascripts/jquery-ui.custom.min.js
share/public/javascripts/jquery.floatThead.js
share/public/javascripts/jquery.qtip.min.js
share/public/javascripts/netdisco.js
share/public/javascripts/netdisco_portcontrol.js
share/public/javascripts/toastr.js
share/public/javascripts/underscore.min.js
share/views/admintask.tt
share/views/ajax/admintask/jobqueue.tt
share/views/ajax/admintask/pseudodevice.tt
share/views/ajax/admintask/topology.tt
share/views/ajax/admintask/users.tt
share/views/ajax/device/addresses.tt
share/views/ajax/device/details.tt
share/views/ajax/device/netmap.tt
share/views/ajax/device/ports.tt
share/views/ajax/report/duplexmismatch.tt
share/views/ajax/report/portutilization.tt
share/views/ajax/search/device.tt
share/views/ajax/search/node_by_ip.tt
share/views/ajax/search/node_by_mac.tt

View File

@@ -60,4 +60,4 @@ resources:
homepage: http://netdisco.org/
license: http://opensource.org/licenses/bsd-license.php
repository: git://git.code.sf.net/p/netdisco/netdisco-ng
version: 2.010004
version: 2.011000

View File

@@ -7,7 +7,7 @@ use 5.010_000;
use File::ShareDir 'dist_dir';
use Path::Class;
our $VERSION = '2.010004';
our $VERSION = '2.011000';
BEGIN {
if (not ($ENV{DANCER_APPDIR} || '')

View File

@@ -8,7 +8,7 @@ use base 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces;
our $VERSION = 23; # schema version used for upgrades, keep as integer
our $VERSION = 24; # schema version used for upgrades, keep as integer
use Path::Class;
use File::Basename;

View File

@@ -65,6 +65,8 @@ __PACKAGE__->add_columns(
{ data_type => "integer", is_nullable => 1 },
"snmp_comm",
{ data_type => "text", is_nullable => 1 },
"snmp_comm_rw",
{ data_type => "text", is_nullable => 1 },
"snmp_class",
{ data_type => "text", is_nullable => 1 },
"vtp_domain",

View File

@@ -0,0 +1,46 @@
package App::Netdisco::DB::Result::Virtual::PortUtilization;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table('port_utilization');
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(<<ENDSQL
SELECT d.dns AS dns, d.ip as ip,
sum(CASE WHEN (dp.type != 'propVirtual') THEN 1 ELSE 0 END) as port_count,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up = 'up') THEN 1 ELSE 0 END) as ports_in_use,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin != 'up') THEN 1 ELSE 0 END) as ports_shutdown,
sum(CASE WHEN (dp.type != 'propVirtual' AND dp.up_admin = 'up' AND dp.up != 'up') THEN 1 ELSE 0 END) as ports_free
FROM device d LEFT JOIN device_port dp
ON d.ip = dp.ip
GROUP BY d.dns, d.ip
ORDER BY d.dns, d.ip
ENDSQL
);
__PACKAGE__->add_columns(
'dns' => {
data_type => 'text',
},
'ip' => {
data_type => 'inet',
},
'port_count' => {
data_type => 'integer',
},
'ports_in_use' => {
data_type => 'integer',
},
'ports_shutdown' => {
data_type => 'integer',
},
'ports_free' => {
data_type => 'integer',
},
);
1;

View File

@@ -511,12 +511,16 @@ sub with_port_count {
->search_rs($cond, $attrs)
->search({},
{
'+columns' => { port_count =>
'+columns' => {
port_count =>
$rs->result_source->schema->resultset('DevicePort')
->search(
{ 'dp.ip' => { -ident => 'me.ip' } },
{
'dp.ip' => { -ident => 'me.ip' },
'dp.type' => { '!=' => 'propVirtual' },
},
{ alias => 'dp' }
)->count_rs->as_query
)->count_rs->as_query,
},
});
}

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE device ADD COLUMN snmp_comm_rw text;
COMMIT;

View File

@@ -101,8 +101,9 @@ Value: Boolean. Default: C<false>.
Enable this if Netdisco is running within another web server such as Apache,
and you want that server to handle user authentication. Normally the
authenticated username will automatically be set in the C<REMOTE_USER> HTTP
Header. See L<Dancer::Deployment/Running from Apache> for further details.
authenticated username will automatically be set in the C<REMOTE_USER>
environment variable. See L<Dancer::Deployment/Running from Apache> for
further details.
=head3 C<trust_x_remote_user>
@@ -110,8 +111,8 @@ Value: Boolean. Default: C<false>.
Enable this if you proxy requests to Netdisco via another web server such as
Apache, and you want that server to handle user authentication. You need to
configure the authorized username to be passed in the C<X-REMOTE_USER> HTTP
Header. For example with Apache:
configure the authorized username to be passed from the frontend environment
to Netdisco in the C<X-REMOTE_USER> HTTP Header. For example with Apache:
RequestHeader unset X-REMOTE_USER
RequestHeader set X-REMOTE_USER "%{REMOTE_USER}e" env=REMOTE_USER

View File

@@ -75,6 +75,16 @@ To delegate user authentication to Apache, use the C<trust_remote_user> or
C<trust_x_remote_user> settings. See L<App::Netdisco::Manual::Configuration>
for more details.
=head1 SSL Support
There is no SSL support in the built-in web server. This is because it's not
straightforward to support all the SSL options, and using port 443 requires
root privilege, which the Netdisco application should not have.
You are instead recommended to run C<netdisco-web> behind a reverse proxy as
described elsewhere in this document. Apache can easily act as an SSL reverse
proxy.
=head1 SQL and HTTP Trace
For SQL debugging try the following commands:

View File

@@ -21,8 +21,8 @@ the more verbose C<plugins/DBIC> setting which was there before:
user: 'someuser'
pass: 'somepass'
Also, the C<REMOTE_USER> and C<X-REMOTE_USER> environment variables are now
supported for delegating authentication to another web server. See the
Also, the C<REMOTE_USER> environment variable and C<X-REMOTE_USER> HTTP Header
are now supported for delegating authentication to another web server. See the
Deployment and Configuration documentation for further details.
=head1 2.008000

View File

@@ -90,6 +90,9 @@ sub _snmp_connect_generic {
unshift @communities, $device->snmp_comm
if defined $device->snmp_comm
and defined $comm_type and $comm_type eq 'community';
unshift @communities, $device->snmp_comm_rw
if defined $device->snmp_comm_rw
and defined $comm_type and $comm_type eq 'community_rw';
my $info = undef;
VERSION: foreach my $ver (@versions) {
@@ -101,8 +104,13 @@ sub _snmp_connect_generic {
COMMUNITY: foreach my $comm (@communities) {
next unless $comm;
$info = _try_connect($ver, $class, $comm, \%snmp_args)
and last VERSION;
$info = _try_connect($ver, $class, $comm, \%snmp_args);
if ($comm_type eq 'community_rw') {
_try_write($info, $comm, $device) or next COMMUNITY;
}
last VERSION if $info;
}
}
}
@@ -110,6 +118,25 @@ sub _snmp_connect_generic {
return $info;
}
sub _try_write {
my ($info, $comm, $device) = @_;
my $happy = 0;
try {
debug sprintf '[%s] try_write with comm: %s', $device->ip, $comm;
$info->clear_cache;
my $rv = $info->set_location( $info->location );
$device->update({snmp_comm_rw => $comm})
if $device->in_storage;
$happy = 1 if $rv;
}
catch {
debug $_;
};
return $happy;
}
sub _try_connect {
my ($ver, $class, $comm, $snmp_args) = @_;
my $info = undef;
@@ -154,7 +181,7 @@ sub _build_mibdirs {
sub _get_mibdirs_content {
my $home = shift;
warning 'Netdisco SNMP work will be slow - loading ALL MIBs. Consider setting mibdirs.';
# warning 'Netdisco SNMP work will be slow - loading ALL MIBs. Consider setting mibdirs.';
my @list = map {s|$home/||; $_} grep {-d} glob("$home/*");
return \@list;
}

View File

@@ -10,8 +10,8 @@ hook 'before' => sub {
if (setting('trust_x_remote_user') and scalar request->header('X-REMOTE_USER')) {
session(user => scalar request->header('X-REMOTE_USER'));
}
elsif (setting('trust_remote_user') and scalar request->header('REMOTE_USER')) {
session(user => scalar request->header('REMOTE_USER'));
elsif (setting('trust_remote_user') and $ENV{REMOTE_USER}) {
session(user => $ENV{REMOTE_USER});
}
elsif (setting('no_auth')) {
session(user => 'guest');

View File

@@ -21,6 +21,14 @@ ajax '/ajax/control/admin/jobqueue/del' => sub {
});
};
ajax '/ajax/control/admin/jobqueue/delall' => sub {
send_error('Forbidden', 403) unless var('user')->admin;
schema('netdisco')->txn_do(sub {
my $device = schema('netdisco')->resultset('Admin')->delete;
});
};
ajax '/ajax/content/admin/jobqueue' => sub {
send_error('Forbidden', 403) unless var('user')->admin;

View File

@@ -0,0 +1,80 @@
package App::Netdisco::Web::Plugin::AdminTask::Users;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
use Digest::MD5 ();
register_admin_task({
tag => 'users',
label => 'User Management',
});
sub _sanity_ok {
return 0 unless var('user') and var('user')->admin;
return 0 unless param('username')
and param('username') =~ m/^[[:print:]]+$/
and param('username') !~ m/[[:space:]]/;
return 1;
}
ajax '/ajax/control/admin/users/add' => sub {
send_error('Bad Request', 400) unless _sanity_ok();
schema('netdisco')->txn_do(sub {
my $user = schema('netdisco')->resultset('User')
->create({
username => param('username'),
password => Digest::MD5::md5_hex(param('password')),
fullname => param('fullname'),
port_control => (param('port_control') ? \'true' : \'false'),
admin => (param('admin') ? \'true' : \'false'),
});
});
};
ajax '/ajax/control/admin/users/del' => sub {
send_error('Bad Request', 400) unless _sanity_ok();
schema('netdisco')->txn_do(sub {
schema('netdisco')->resultset('User')
->find({username => param('username')})->delete;
});
};
ajax '/ajax/control/admin/users/update' => sub {
send_error('Bad Request', 400) unless _sanity_ok();
schema('netdisco')->txn_do(sub {
my $user = schema('netdisco')->resultset('User')
->find({username => param('username')});
return unless $user;
$user->update({
((param('password') ne '********')
? (password => Digest::MD5::md5_hex(param('password')))
: ()),
fullname => param('fullname'),
port_control => (param('port_control') ? \'true' : \'false'),
admin => (param('admin') ? \'true' : \'false'),
});
});
};
ajax '/ajax/content/admin/users' => sub {
send_error('Forbidden', 403) unless var('user')->admin;
my $set = schema('netdisco')->resultset('User')
->search(undef, { order_by => [qw/fullname username/]});
content_type('text/html');
template 'ajax/admintask/users.tt', {
results => $set,
}, { layout => undef };
};
true;

View File

@@ -0,0 +1,25 @@
package App::Netdisco::Web::Plugin::Report::PortUtilization;
use Dancer ':syntax';
use Dancer::Plugin::Ajax;
use Dancer::Plugin::DBIC;
use App::Netdisco::Web::Plugin;
register_report({
category => 'Device',
tag => 'portutilization',
label => 'Port Utilization',
});
ajax '/ajax/content/report/portutilization' => sub {
return unless schema('netdisco')->resultset('Device')->count;
my $set = schema('netdisco')->resultset('Virtual::PortUtilization');
content_type('text/html');
template 'ajax/report/portutilization.tt', {
results => $set,
}, { layout => undef };
};
true;

View File

@@ -23,10 +23,12 @@ path: '/'
behind_proxy: false
web_plugins:
- Inventory
- Report::PortUtilization
- Report::DuplexMismatch
- AdminTask::PseudoDevice
- AdminTask::Topology
- AdminTask::JobQueue
- AdminTask::Users
- Search::Device
- Search::Node
- Search::VLAN

View File

@@ -28,6 +28,11 @@ body {
width: 100%;
}
/* results table header should have a background, for floatThead */
div.content > div.tab-content table.nd_floatinghead thead {
background-color: floralWhite;
}
/* jquery ui autocomplete scrollable */
.ui-autocomplete {
max-height: 200px;
@@ -70,6 +75,11 @@ body {
line-height: 8px;
}
/* for where min-width is set but we don't want it */
.nd_no-min-width {
min-width: 0px;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* styles to adjust the hero box used for homepage + login */

View File

@@ -0,0 +1,612 @@
/*!
* jQuery.floatThead
* Copyright (c) 2012 - 2013 Misha Koryak - https://github.com/mkoryak/floatThead
* Licensed under Creative Commons Attribution-NonCommercial 3.0 Unported - http://creativecommons.org/licenses/by-sa/3.0/
* Date: 8/25/13
*
* @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header
*
* Dependencies:
* jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core
* underscore.js 1.3.0 + [required]
*
* http://notetodogself.blogspot.com
* http://programmingdrunk.com/floatThead/
*
* Tested on FF13+, Chrome 21+, IE9, IE8
*
* @author Misha Koryak
* @version 1.0.0
*/
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
// @output_file_name jquery.floatThead.min.js
// ==/ClosureCompiler==
/**
* @preserve jQuery.floatThead 1.0.0
* Copyright (c) 2013 Misha Koryak - https://github.com/mkoryak/floatThead
* Licensed under Creative Commons Attribution-NonCommercial 3.0 Unported - http://creativecommons.org/licenses/by-sa/3.0/
*/
(function( $ ) {
//browser stuff
var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="<!--[if gt IE "+ ++a+"]><i><![endif]-->",c[0];);return 4<a?a:document.documentMode}();
var ifChrome = function(){
var $table = $("<table><colgroup><col></colgroup><tbody><tr><td style='width:10px'></td></tbody></table>");
$('body').append($table);
var width = $table.find('col').width();
$table.remove();
return width == 0;
};
/**
* provides a default config object. You can modify this after including this script if you want to change the init defaults
* @type {Object}
*/
$.floatThead = {
defaults: {
cellTag: 'th',
zIndex: 1001, //zindex of the floating thead (actually a container div)
debounceResizeMs: 1,
useAbsolutePositioning: true, //if set to NULL - defaults: has scrollContainer=true, doesnt have scrollContainer=false
scrollingTop: 0, //String or function($table) - offset from top of window where the header should not pass above
//TODO: this got lost somewhere - needs to be re-implemented
scrollingBottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling
scrollContainer: function($table){
return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
},
floatTableClass: 'floatThead-table'
}
};
var $window = $(window);
var floatTheadCreated = 0;
/**
* debounce and fix window resize event for ie7. ie7 is evil and will fire window resize event when ANY dom element is resized.
* @param debounceMs
* @param cb
*/
function windowResize(debounceMs, cb){
var winWidth = $window.width();
var debouncedCb = _.debounce(function(){
var winWidthNew = $window.width();
if(winWidth != winWidthNew){
winWidth = winWidthNew;
cb();
}
}, debounceMs);
$window.bind('resize.floatTHead', debouncedCb);
}
/**
* try to calculate the scrollbar width for your browser/os
* @return {Number}
*/
function scrollbarWidth() {
var $div = $('<div/>')
.css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 })
.prependTo('body').append('<div/>').find('div')
.css({ width: '100%', height: 200 });
var scrollbarWidth = 100 - $div.width();
$div.parent().remove();
return scrollbarWidth;
}
/**
* Check if a given table has been datatableized (http://datatables.net)
* @param $table
* @return {Boolean}
*/
function isDatatable($table){
if($table.dataTableSettings){
for(var i = 0; i < $table.dataTableSettings.length; i++){
var table = $table.dataTableSettings[i].nTable;
if($table[0] == table){
return true;
}
}
}
return false;
}
$.fn.floatThead = function(map){
if(ieVersion < 8){
return this; //no more crappy browser support.
}
isChrome = ifChrome(); //need to call this after dom ready, and now it is.
if(isChrome){
//because chrome cant read <col> width, these elements are used for sizing the table. Need to create new elements because they must be unstyled by user's css.
document.createElement('fthtr'); //tr
document.createElement('fthtd'); //td
document.createElement('fthfoot'); //tfoot
}
if(_.isString(map)){
var command = map;
var ret = this;
this.filter('table').each(function(){
var obj = $(this).data('floatThead-attached');
if(obj && _.isFunction(obj[command])){
r = obj[command]();
if(typeof r !== 'undefined'){
ret = r;
}
}
});
return ret;
}
var opts = $.extend({}, $.floatThead.defaults, map);
this.filter(':not(.'+opts.floatTableClass+')').each(function(){
var $table = $(this);
if($table.data('floatThead-attached')){
return true; //continue the each loop
}
if(!$table.is('table')){
throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');
}
var $header = $table.find('thead:first');
var $tbody = $table.find('tbody:first');
if($header.length == 0){
throw new Error('jQuery.floatThead must be run on a table that contains a <thead> element');
}
var headerFloated = true;
var scrollingTop, scrollingBottom;
var scrollbarOffset = {vertical: 0, horizontal: 0};
var scWidth = scrollbarWidth();
var lastColumnCount = 0; //used by columnNum()
var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls
var useAbsolutePositioning = opts.useAbsolutePositioning;
if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false
useAbsolutePositioning = opts.scrollContainer($table).length;
}
var $fthGrp = $('<fthfoot style="display:table-footer-group;"/>');
var locked = $scrollContainer.length > 0;
var wrappedContainer = false; //used with absolute positioning enabled. did we need to wrap the scrollContainer/table with a relative div?
var absoluteToFixedOnScroll = ieVersion && !locked && useAbsolutePositioning; //on ie using absolute positioning doesnt look good with window scrolling, so we change positon to fixed on scroll, and then change it back to absolute when done.
var $floatTable = $("<table/>");
var $floatColGroup = $("<colgroup/>");
var $tableColGroup = $("<colgroup/>");
var $fthRow = $('<fthrow style="display:table-row;height:0;"/>'); //created unstyled elements
var $floatContainer = $('<div style="overflow: hidden;"></div>');
var $newHeader = $("<thead/>");
var $sizerRow = $('<tr class="size-row"/>');
var $sizerCells = $([]);
var $tableCells = $([]); //used for sizing - either $sizerCells or $tableColGroup cols. $tableColGroup cols are only created in chrome for borderCollapse:collapse because of a chrome bug.
var $headerCells = $([]);
var $fthCells = $([]); //created elements
$newHeader.append($sizerRow);
$header.detach();
$table.prepend($newHeader);
$table.prepend($tableColGroup);
if(isChrome){
$fthGrp.append($fthRow);
$table.append($fthGrp);
}
$floatTable.append($floatColGroup);
$floatContainer.append($floatTable);
$floatTable.attr('class', $table.attr('class'));
$floatTable.addClass(opts.floatTableClass).css('margin', 0); //must have no margins or you wont be able to click on things under floating table
if(useAbsolutePositioning){
var makeRelative = function($container, alwaysWrap){
var positionCss = $container.css('position');
var relativeToScrollContainer = (positionCss == "relative" || positionCss == "absolute");
if(!relativeToScrollContainer || alwaysWrap){
var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')};
$floatContainer.css(css);
$container = $container.wrap("<div style='position: relative; clear:both;'></div>").parent();
wrappedContainer = true;
}
return $container;
};
if(locked){
var $relative = makeRelative($scrollContainer, true);
$relative.append($floatContainer);
} else {
makeRelative($table);
$table.after($floatContainer);
}
} else {
$table.after($floatContainer);
}
$floatContainer.css({
position: useAbsolutePositioning ? 'absolute' : 'fixed',
marginTop: 0,
top: useAbsolutePositioning ? 0 : 'auto',
zIndex: opts.zIndex
});
updateScrollingOffsets();
var layoutFixed = {'table-layout': 'fixed'};
var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'};
function setHeaderHeight(){
var headerHeight = $header.outerHeight(true);
$sizerRow.outerHeight(headerHeight);
$sizerCells.outerHeight(headerHeight);
}
function setFloatWidth(){
var tableWidth = $table.outerWidth();
var width = $scrollContainer.width() || tableWidth;
$floatContainer.width(width - scrollbarOffset.vertical);
if(locked){
var percent = 100 * tableWidth / (width - scrollbarOffset.vertical);
$floatTable.css('width', percent+'%');
} else {
$floatTable.outerWidth(tableWidth);
}
}
function updateScrollingOffsets(){
scrollingTop = (_.isFunction(opts.scrollingTop) ? opts.scrollingTop($table) : opts.scrollingTop) || 0;
scrollingBottom = (_.isFunction(opts.scrollingBottom) ? opts.scrollingBottom($table) : opts.scrollingBottom) || 0;
}
/**
* get the number of columns and also rebuild resizer rows if the count is different then the last count
*/
function columnNum(){
var $headerColumns = $header.find('tr:first>'+opts.cellTag);
var count = _.reduce($headerColumns, function(sum, cell){
var colspan = parseInt(($(cell).attr('colspan') || 1), 10);
return sum + colspan;
}, 0);
if(count != lastColumnCount){
lastColumnCount = count;
var cells = [], cols = [], psuedo = [];
for(var x = 0; x < count; x++){
cells.push('<'+opts.cellTag+' class="floatThead-col-'+x+'"/>');
cols.push('<col/>');
psuedo.push("<fthcell style='display:table-cell;height:0;width:auto;'/>");
}
cols = cols.join('');
cells = cells.join('');
if(isChrome){
psuedo = psuedo.join('');
$fthRow.html(psuedo);
$fthCells = $fthRow.find('fthcell')
}
$sizerRow.html(cells);
$tableColGroup.html(cols);
$tableCells = $tableColGroup.find('col');
$floatColGroup.html(cols);
$headerCells = $floatColGroup.find("col");
}
return count;
}
function refloat(){
if(!headerFloated){
headerFloated = true;
$table.css(layoutFixed);
$floatTable.css(layoutFixed);
$floatTable.append($header); //append because colgroup must go first in chrome
$tbody.before($newHeader);
setHeaderHeight();
}
}
function unfloat(){
if(headerFloated){
headerFloated = false;
$newHeader.detach();
$table.prepend($header);
$table.css(layoutAuto);
$floatTable.css(layoutAuto);
}
}
function changePositioning(isAbsolute){
if(useAbsolutePositioning != isAbsolute){
useAbsolutePositioning = isAbsolute;
$floatContainer.css({
position: useAbsolutePositioning ? 'absolute' : 'fixed'
});
}
}
/**
* returns a function that updates the floating header's cell widths.
* @return {Function}
*/
function reflow(){
var numCols = columnNum(); //if the tables columns change dynamically since last time (datatables) we need to rebuild the sizer rows and get new count
var flow = function(){
var badReflow = false;
var $rowCells = isChrome ? $fthCells : $tableCells;
if($rowCells.length == numCols && numCols > 0){
unfloat();
for(var i=0; i < numCols; i++){
var $rowCell = $rowCells.eq(i);
var rowWidth = $rowCell.outerWidth(true);
$headerCells.eq(i).outerWidth(rowWidth);
$tableCells.eq(i).outerWidth(rowWidth);
}
refloat();
for(var i=0; i < numCols; i++){
var hw = $headerCells.eq(i).outerWidth(true);
var tw = $tableCells.eq(i).outerWidth(true);
if(hw != tw){
badReflow = true;
break;
}
}
} else {
$floatTable.append($header);
$table.css(layoutAuto);
$floatTable.css(layoutAuto);
setHeaderHeight();
}
return badReflow;
};
return flow;
}
/**
* first performs initial calculations that we expect to not change when the table, window, or scrolling container are scrolled.
* returns a function that calculates the floating container's top and left coords. takes into account if we are using page scrolling or inner scrolling
* @return {Function}
*/
function calculateFloatContainerPosFn(){
var scrollingContainerTop = $scrollContainer.scrollTop();
//this floatEnd calc was moved out of the returned function because we assume the table height doesnt change (otherwise we must reinit by calling calculateFloatContainerPosFn)
var floatEnd;
var tableContainerGap = 0;
var floatContainerHeight = $floatContainer.height();
var tableOffset = $table.offset();
if(locked){
var containerOffset = $scrollContainer.offset();
tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop;
} else {
floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal;
}
var windowTop = $window.scrollTop();
var windowLeft = $window.scrollLeft();
var scrollContainerLeft = $scrollContainer.scrollLeft();
scrollingContainerTop = $scrollContainer.scrollTop();
var positionFn = function(eventType){
if(eventType == 'windowScroll'){
windowTop = $window.scrollTop();
windowLeft = $window.scrollLeft();
} else if(eventType == 'containerScroll'){
scrollingContainerTop = $scrollContainer.scrollTop();
scrollContainerLeft = $scrollContainer.scrollLeft();
} else if(eventType != 'init') {
windowTop = $window.scrollTop();
windowLeft = $window.scrollLeft();
scrollingContainerTop = $scrollContainer.scrollTop();
scrollContainerLeft = $scrollContainer.scrollLeft();
}
if(absoluteToFixedOnScroll){
if(eventType == 'windowScrollDone'){
changePositioning(true); //change to absolute
} else {
changePositioning(false); //change to fixed
}
} else if(eventType == 'windowScrollDone'){
return null; //event is fired when they stop scrolling. ignore it if not 'absoluteToFixedOnScroll'
}
tableOffset = $table.offset();
var top, left, tableHeight;
//absolute positioning
if(locked && useAbsolutePositioning){ //inner scrolling
if (tableContainerGap >= scrollingContainerTop) {
var gap = tableContainerGap - scrollingContainerTop;
gap = gap > 0 ? gap : 0;
top = gap;
// unfloat(); //more trouble than its worth
} else {
top = wrappedContainer ? 0 : scrollingContainerTop;
// refloat(); //more trouble than its worth
//headers stop at the top of the viewport
}
left = 0;
} else if(!locked && useAbsolutePositioning) { //window scrolling
tableHeight = $table.outerHeight();
if(windowTop > floatEnd + tableHeight){
top = tableHeight - floatContainerHeight; //scrolled past table
} else if (tableOffset.top > windowTop + scrollingTop) {
top = 0; //scrolling to table
unfloat();
} else {
top = scrollingTop + windowTop - tableOffset.top + tableContainerGap;
refloat(); //scrolling within table. header floated
}
left = 0;
//fixed positioning:
} else if(locked && !useAbsolutePositioning){ //inner scrolling
if (tableContainerGap > scrollingContainerTop) {
top = tableOffset.top - windowTop;
unfloat();
} else {
top = tableOffset.top + scrollingContainerTop - windowTop - tableContainerGap;
refloat();
//headers stop at the top of the viewport
}
left = tableOffset.left + scrollContainerLeft - windowLeft;
} else if(!locked && !useAbsolutePositioning) { //window scrolling
tableHeight = $table.outerHeight();
if(windowTop > floatEnd + tableHeight){
top = tableHeight + scrollingTop - windowTop + floatEnd;
unfloat();
} else if (tableOffset.top > windowTop + scrollingTop) {
top = tableOffset.top - windowTop;
refloat();
} else {
top = scrollingTop;
}
left = tableOffset.left - windowLeft;
}
return {top: top, left: left};
};
return positionFn;
}
/**
* returns a function that caches old floating container position and only updates css when the position changes
* @return {Function}
*/
function repositionFloatContainerFn(){
var oldTop = null;
var oldLeft = null;
var oldScrollLeft = null;
return function(pos, setWidth, setHeight){
if(pos != null && (oldTop != pos.top || oldLeft != pos.left)){
$floatContainer.css({
top: pos.top,
left: pos.left
});
oldTop = pos.top;
oldLeft = pos.left;
}
if(setWidth){
setFloatWidth();
}
if(setHeight){
setHeaderHeight();
}
var scrollLeft = $scrollContainer.scrollLeft();
if(oldScrollLeft != scrollLeft){
$floatContainer.scrollLeft(scrollLeft);
oldScrollLeft = scrollLeft;
}
}
}
/**
* checks if THIS table has scrollbars, and finds their widths
*/
function calculateScrollBarSize(){ //this should happen after the floating table has been positioned
if($scrollContainer.length){
scrollbarOffset.horizontal = $scrollContainer.width() < $table.width() ? scWidth : 0;
scrollbarOffset.vertical = $scrollContainer.height() < $table.height() ? scWidth: 0;
}
}
//finish up. create all calculation functions and bind them to events
calculateScrollBarSize();
var flow = reflow();
flow();
var calculateFloatContainerPos = calculateFloatContainerPosFn();
var repositionFloatContainer = repositionFloatContainerFn();
repositionFloatContainer(calculateFloatContainerPos('init'), true, true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead
var windowScrollDoneEvent = _.debounce(function(){
repositionFloatContainer(calculateFloatContainerPos('windowScrollDone'), false);
}, 300);
var windowScrollEvent = function(){
repositionFloatContainer(calculateFloatContainerPos('windowScroll'), false);
windowScrollDoneEvent();
};
var containerScrollEvent = function(){
repositionFloatContainer(calculateFloatContainerPos('containerScroll'), false);
};
var windowResizeEvent = function(){
updateScrollingOffsets();
calculateScrollBarSize();
flow = reflow();
var badReflow = flow();
if(badReflow){
flow();
}
calculateFloatContainerPos = calculateFloatContainerPosFn();
repositionFloatContainer = repositionFloatContainerFn();
repositionFloatContainer(calculateFloatContainerPos('resize'), true, true);
};
var reflowEvent = _.debounce(function(){
calculateScrollBarSize();
updateScrollingOffsets();
flow = reflow();
var badReflow = flow();
if(badReflow){
flow();
}
calculateFloatContainerPos = calculateFloatContainerPosFn();
repositionFloatContainer(calculateFloatContainerPos('reflow'), true);
}, 1);
if(locked){ //internal scrolling
if(useAbsolutePositioning){
$scrollContainer.bind('scroll.floatTHead', containerScrollEvent);
} else {
$scrollContainer.bind('scroll.floatTHead', containerScrollEvent);
$window.bind('scroll.floatTHead', windowScrollEvent);
}
} else { //window scrolling
$window.bind('scroll.floatTHead', windowScrollEvent);
}
$window.bind('load.floatTHead', reflowEvent); //for tables with images
windowResize(opts.debounceResizeMs, windowResizeEvent);
$table.bind('reflow', reflowEvent);
if(isDatatable($table)){
$table
.bind('filter', reflowEvent)
.bind('sort', reflowEvent)
.bind('page', reflowEvent);
}
//attach some useful functions to the table.
$table.data('floatThead-attached', {
destroy: function(){
$table.css(layoutAuto);
$tableColGroup.remove();
$fthGrp.remove();
$newHeader.replaceWith($header);
$table.unbind('reflow');
reflowEvent = windowResizeEvent = containerScrollEvent = windowScrollEvent = function() {};
$scrollContainer.unbind('scroll.floatTHead');
$floatContainer.remove();
$table.data('floatThead-attached', false);
floatTheadCreated--;
if(floatTheadCreated == 0){
$window.unbind('scroll.floatTHead');
$window.unbind('resize.floatTHead');
$window.unbind('load.floatTHead');
}
},
reflow: function(){
reflowEvent();
},
setHeaderHeight: function(){
setHeaderHeight();
},
getFloatContainer: function(){
return $floatContainer;
}
});
floatTheadCreated++;
});
return this;
};
})(jQuery);

View File

@@ -78,6 +78,10 @@ function do_search (event, tab) {
}
// delegate to any [device|search] specific JS code
$('div.content > div.tab-content table.nd_floatinghead').floatThead({
scrollingTop: 40
,useAbsolutePositioning: false
});
inner_view_processing(tab);
}
);
@@ -216,11 +220,21 @@ $(document).ready(function() {
$('.nd_sidebar').toggle(250);
$('#nd_sidebar-toggle-img-out').toggle();
$('.content').css('margin-right', '10px');
$('div.content > div.tab-content table.nd_floatinghead').floatThead('destroy');
$('div.content > div.tab-content table.nd_floatinghead').floatThead({
scrollingTop: 40
,useAbsolutePositioning: false
});
sidebar_hidden = 1;
});
$('#nd_sidebar-toggle-img-out').click(function() {
$('#nd_sidebar-toggle-img-out').toggle();
$('.content').css('margin-right', '215px');
$('div.content > div.tab-content table.nd_floatinghead').floatThead('destroy');
$('div.content > div.tab-content table.nd_floatinghead').floatThead({
scrollingTop: 40
,useAbsolutePositioning: false
});
$('.nd_sidebar').toggle(250);
if (! $('.nd_sidebar').hasClass('nd_sidebar-pinned')) {
$(window).scrollTop(0);

File diff suppressed because one or more lines are too long

View File

@@ -32,7 +32,7 @@
<span id="nd_device-name">
<a id="nd_countdown-refresh" href="#"><i class="text-success icon-refresh"></i></a>
<a id="nd_countdown-control" href="#">
<i id="nd_countdown-control-icon" class="text-error icon-pause"></i></a>
<i id="nd_countdown-control-icon" class="text-success icon-play"></i></a>
<span id="nd_countdown"></span>
</span>
[% END %]

View File

@@ -1,4 +1,7 @@
<table class="table table-bordered table-condensed table-hover">
[% IF results.count == 0 %]
<div class="span2 alert alert-info">The job queue is empty.</div>
[% ELSE %]
<table class="table table-bordered table-condensed table-hover nd_floatinghead">
<thead>
<tr>
<th class="nd_center-cell">Entered</th>
@@ -10,7 +13,21 @@
<th class="nd_center-cell">User</th>
<th class="nd_center-cell">Started</th>
<th class="nd_center-cell">Finished</th>
<th class="nd_center-cell">Action</th>
<th class="nd_center-cell">
<div class="btn-group">
<button class="btn dropdown-toggle" data-toggle="dropdown">
Action
<span class="caret"></span>
</button>
<ul class="dropdown-menu nd_no-min-width">
<li>
<a class="nd_adminbutton" name="delall" href="#">
<i class="icon-trash text-error"></i> Empty Queue
</a>
</li>
</ul>
</div>
</th>
</tr>
</thead>
</tbody>
@@ -47,4 +64,5 @@
[% END %]
</tbody>
</table>
[% END %]

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-striped">
<table class="table table-bordered table-striped nd_floatinghead">
<thead>
<tr>
<th class="nd_center-cell">Device Name</th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-striped">
<table class="table table-bordered table-striped nd_floatinghead">
<thead>
<tr>
<th class="nd_center-cell">Left Device</th>

View File

@@ -0,0 +1,52 @@
<table class="table table-bordered table-striped nd_floatinghead">
<thead>
<tr>
<th class="nd_center-cell">Full Name</th>
<th class="nd_center-cell">Username</th>
<th class="nd_center-cell">Password</th>
<!-- <th class="nd_center-cell">External Auth</th> -->
<th class="nd_center-cell">Port Control</th>
<th class="nd_center-cell">Administrator</th>
<th class="nd_center-cell">Action</th>
</tr>
</thead>
</tbody>
<tr>
<td class="nd_center-cell"><input data-form="add" name="fullname" type="text"></td>
<td class="nd_center-cell"><input data-form="add" name="username" type="text"></td>
<td class="nd_center-cell"><input data-form="add" name="password" type="text"></td>
<td class="nd_center-cell"><input data-form="add" type="checkbox" name="port_control"></td>
<td class="nd_center-cell"><input data-form="add" type="checkbox" name="admin"></td>
<td class="nd_center-cell">
<button class="btn btn-small nd_adminbutton" name="add" type="submit"><i class="icon-plus-sign"></i> Add</button>
</td>
</tr>
[% WHILE (row = results.next) %]
<tr>
<td class="nd_center-cell">
<input data-form="update" name="fullname" type="text" value="[% row.fullname | html_entity %]">
</td>
<td class="nd_center-cell">
<input data-form="update" name="username" type="text" value="[% row.username | html_entity %]">
</td>
<td class="nd_center-cell">
<input data-form="update" name="password" type="text" value="********">
</td>
<td class="nd_center-cell">
<input data-form="update" name="port_control" type="checkbox" [% 'checked="checked"' IF row.port_control %]>
</td>
<td class="nd_center-cell">
<input data-form="update" name="admin" type="checkbox" [% 'checked="checked"' IF row.admin %]>
</td>
<td class="nd_center-cell">
<button class="btn nd_adminbutton" name="update" type="submit"><i class="icon-save text-warning"></i></button>
<input data-form="del" name="username" type="hidden" value="[% row.username | html_entity %]">
<button class="btn nd_adminbutton" name="del" type="submit"><i class="icon-trash text-error"></i></button>
</td>
</tr>
[% END %]
</tbody>
</table>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-condensed table-striped">
<table class="table table-bordered table-condensed table-striped nd_floatinghead">
<thead>
<tr>
<th>Address</th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-striped">
<table class="table table-bordered table-striped nd_floatinghead">
<thead>
<tr>
<th></th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-condensed table-striped">
<table class="table table-bordered table-condensed table-striped nd_floatinghead">
<thead>
<tr>
<th class="nd_center-cell">Left Device</th>
@@ -12,7 +12,7 @@
</tbody>
[% WHILE (row = results.next) %]
<tr>
<td class="nd_center-cell">[% row.left_dns || row.left_ip | html_entity %]</a>
<td class="nd_center-cell">[% row.left_dns || row.left_ip | html_entity %]</td>
<td class="nd_center-cell"><a class="nd_linkcell"
href="[% device_ports %]&q=[% row.left_dns || row.left_ip | uri %]&f=[% row.left_port | uri %]&c_duplex=on">
[% row.left_port | html_entity %]</a></td>

View File

@@ -0,0 +1,23 @@
<table class="table table-bordered table-condensed table-hover nd_floatinghead">
<thead>
<tr>
<th>Device</th>
<th class="nd_center-cell">Total Ports</th>
<th class="nd_center-cell">In Use</th>
<th class="nd_center-cell">Shutdown</th>
<th class="nd_center-cell">Free</th>
</tr>
</thead>
</tbody>
[% WHILE (row = results.next) %]
<tr>
<td><a href="[% device_ports %]&q=[% row.dns || row.ip | uri %]">[% row.dns || row.ip | html_entity %]</a></td>
<td class="nd_center-cell">[% row.port_count %]</td>
<td class="nd_center-cell">[% row.ports_in_use %]</td>
<td class="nd_center-cell">[% row.ports_shutdown %]</td>
<td class="nd_center-cell">[% row.ports_free %]</td>
</tr>
[% END %]
</tbody>
</table>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-condensed table-striped">
<table class="table table-bordered table-condensed table-striped nd_floatinghead">
<thead>
<tr>
<th>Device</th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-hover">
<table class="table table-bordered table-hover nd_floatinghead">
<thead>
<tr>
<th>MAC</th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-hover">
<table class="table table-bordered table-hover nd_floatinghead">
<thead>
<tr>
<th>MAC</th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-condensed table-striped">
<table class="table table-bordered table-condensed table-striped nd_floatinghead">
<thead>
<tr>
<th>Description</th>

View File

@@ -1,4 +1,4 @@
<table class="table table-bordered table-condensed table-striped">
<table class="table table-bordered table-condensed table-striped nd_floatinghead">
<thead>
<tr>
<th>Vlan</th>

View File

@@ -12,7 +12,7 @@
// reload this table every 5 seconds
if (tab == 'jobqueue'
&& $('#nd_countdown-control-icon').hasClass('icon-pause')) {
&& $('#nd_countdown-control-icon').hasClass('icon-play')) {
$('#nd_countdown').text('5');
nd_timers.push(setTimeout(function() { $('#nd_countdown').text('4') }, 1000 ));
@@ -94,7 +94,7 @@
var icon = $('#nd_countdown-control-icon');
icon.toggleClass('icon-pause icon-play text-error text-success');
if (icon.hasClass('icon-play')) {
if (icon.hasClass('icon-pause')) {
for (var i = 0; i < nd_timers.length; i++) {
clearTimeout(nd_timers[i]);
}

View File

@@ -17,9 +17,11 @@
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery-history.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery-deserialize.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/bootstrap.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/underscore.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery.qtip.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/d3.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/toastr.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery.floatThead.js"></script>
<script type="text/javascript">
var uri_base = '[% uri_base %]';

4
TODO
View File

@@ -2,9 +2,11 @@ FRONTEND
========
* report on port usage
* SSL support
* user management admin task
* LDAP authentication and RBAC
- http://advent.perldancer.org/2012/2
* moar reports
* (jeneric) device module tab