Bug fixes, and AuthN delegation.
Squashed commit of the following: 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:
		| @@ -192,7 +192,7 @@ sub store_interfaces { | ||||
|   foreach my $entry (keys %$interfaces) { | ||||
|       my $port = $interfaces->{$entry}; | ||||
|  | ||||
|       if (not length $port) { | ||||
|       if (not $port) { | ||||
|           debug sprintf ' [%s] interfaces - ignoring %s (no port mapping)', | ||||
|             $device->ip, $port; | ||||
|           next; | ||||
| @@ -288,7 +288,7 @@ sub store_wireless { | ||||
|       (my $iid = $entry) =~ s/\.\d+$//; | ||||
|       my $port = $interfaces->{$iid}; | ||||
|  | ||||
|       if (not length $port) { | ||||
|       if (not $port) { | ||||
|           debug sprintf ' [%s] wireless - ignoring %s (no port mapping)', | ||||
|             $device->ip, $port; | ||||
|           next; | ||||
| @@ -316,7 +316,7 @@ sub store_wireless { | ||||
|   foreach my $entry (keys %$channel) { | ||||
|       my $port = $interfaces->{$entry}; | ||||
|  | ||||
|       if (not length $port) { | ||||
|       if (not $port) { | ||||
|           debug sprintf ' [%s] wireless - ignoring %s (no port mapping)', | ||||
|             $device->ip, $port; | ||||
|           next; | ||||
| @@ -613,7 +613,7 @@ sub store_neighbors { | ||||
|       my $remote_type = $c_platform->{$entry}; | ||||
|       my $remote_id   = $c_id->{$entry}; | ||||
|  | ||||
|       next unless length $remote_ip; | ||||
|       next unless $remote_ip; | ||||
|  | ||||
|       # a bunch of heuristics to search known devices if we don't have a | ||||
|       # useable remote IP... | ||||
|   | ||||
| @@ -39,13 +39,13 @@ sub txn_do_locked { | ||||
|   } | ||||
|  | ||||
|   $schema->throw_exception('missing Table name to txn_do_locked()') | ||||
|     unless length $table; | ||||
|     unless $table; | ||||
|  | ||||
|   $table = [$table] if ref '' eq ref $table; | ||||
|   my $table_fmt = join ', ', ('%s' x scalar @$table); | ||||
|   my $sql = sprintf $sql_fmt, $table_fmt; | ||||
|  | ||||
|   if (ref '' eq ref $mode and length $mode) { | ||||
|   if (ref '' eq ref $mode and $mode) { | ||||
|       scalar grep {$_ eq $mode} values %lock_modes | ||||
|         or $schema->throw_exception('bad LOCK_MODE to txn_do_locked()'); | ||||
|   } | ||||
|   | ||||
| @@ -196,7 +196,7 @@ sub search_by_field { | ||||
|  | ||||
|     # this is a bit of an inelegant trick to catch junk data entry, | ||||
|     # whilst avoiding returning *all* entries in the table | ||||
|     if (length $p->{ip} and 'NetAddr::IP::Lite' ne ref $p->{ip}) { | ||||
|     if ($p->{ip} and 'NetAddr::IP::Lite' ne ref $p->{ip}) { | ||||
|       $p->{ip} = ( NetAddr::IP::Lite->new($p->{ip}) | ||||
|         || NetAddr::IP::Lite->new('255.255.255.255') ); | ||||
|     } | ||||
|   | ||||
| @@ -8,17 +8,6 @@ our @EXPORT = (); | ||||
| our @EXPORT_OK = qw/ add_jobs capacity_for take_jobs reset_jobs /; | ||||
| our %EXPORT_TAGS = ( all => \@EXPORT_OK ); | ||||
|  | ||||
| # static configuration for the in-memory local job queue | ||||
| setting('plugins')->{DBIC}->{daemon} = { | ||||
|     dsn => 'dbi:SQLite:dbname=:memory:', | ||||
|     options => { | ||||
|         AutoCommit => 1, | ||||
|         RaiseError => 1, | ||||
|         sqlite_use_immediate_transaction => 1, | ||||
|     }, | ||||
|     schema_class => 'App::Netdisco::Daemon::DB', | ||||
| }; | ||||
|  | ||||
| schema('daemon')->deploy; | ||||
| my $queue = schema('daemon')->resultset('Admin'); | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,7 @@ sub worker_body { | ||||
|       } | ||||
|  | ||||
|       debug "$type ($wid): sleeping now..."; | ||||
|       sleep( setting('workers')->{sleep_time} || 5 ); | ||||
|       sleep(1); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,7 @@ sub set_portcontrol { | ||||
|  | ||||
|   my $reconfig_check = port_reconfig_check($port); | ||||
|   return job_error("Cannot alter port: $reconfig_check") | ||||
|     if length $reconfig_check; | ||||
|     if $reconfig_check; | ||||
|  | ||||
|   return _set_port_generic($job, 'up_admin'); | ||||
| } | ||||
| @@ -35,11 +35,11 @@ sub set_vlan { | ||||
|  | ||||
|   my $port_reconfig_check = port_reconfig_check($port); | ||||
|   return job_error("Cannot alter port: $port_reconfig_check") | ||||
|     if length $port_reconfig_check; | ||||
|     if $port_reconfig_check; | ||||
|  | ||||
|   my $vlan_reconfig_check = vlan_reconfig_check($port); | ||||
|   return job_error("Cannot alter vlan: $vlan_reconfig_check") | ||||
|     if length $vlan_reconfig_check; | ||||
|     if $vlan_reconfig_check; | ||||
|  | ||||
|   return _set_port_generic($job, 'vlan'); | ||||
| } | ||||
| @@ -96,7 +96,7 @@ sub set_power { | ||||
|  | ||||
|   my $reconfig_check = port_reconfig_check($port); | ||||
|   return job_error("Cannot alter port: $reconfig_check") | ||||
|     if length $reconfig_check; | ||||
|     if $reconfig_check; | ||||
|  | ||||
|  | ||||
|   my $ip = $job->device; | ||||
|   | ||||
| @@ -84,7 +84,7 @@ sub worker_body { | ||||
|       # TODO also check for stale jobs in Netdisco DB | ||||
|  | ||||
|       debug "mgr ($wid): sleeping now..."; | ||||
|       sleep( setting('workers')->{sleep_time} || 5 ); | ||||
|       sleep( setting('workers')->{sleep_time} || 2 ); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,81 +1,16 @@ | ||||
| package App::Netdisco::Daemon::Worker::Poller::Arpnip; | ||||
|  | ||||
| use Dancer qw/:moose :syntax :script/; | ||||
| use Dancer::Plugin::DBIC 'schema'; | ||||
|  | ||||
| use App::Netdisco::Util::SNMP 'snmp_connect'; | ||||
| use App::Netdisco::Util::Device 'get_device'; | ||||
| use App::Netdisco::Core::Arpnip 'do_arpnip'; | ||||
| use App::Netdisco::Daemon::Util ':all'; | ||||
|  | ||||
| use NetAddr::IP::Lite ':lower'; | ||||
|  | ||||
| use Role::Tiny; | ||||
| use namespace::clean; | ||||
|  | ||||
| # queue an arpnip job for all devices known to Netdisco | ||||
| sub arpwalk { | ||||
|   my ($self, $job) = @_; | ||||
| with 'App::Netdisco::Daemon::Worker::Poller::Common'; | ||||
|  | ||||
|   my $devices = schema('netdisco')->resultset('Device')->get_column('ip'); | ||||
|   my $jobqueue = schema('netdisco')->resultset('Admin'); | ||||
| sub arpnip_action { \&do_arpnip } | ||||
| sub arpnip_layer { 3 } | ||||
|  | ||||
|   if ($job->subaction and $job->subaction eq 'after-discoverall') { | ||||
|       # make sure there are no incomplete discover jobs queued | ||||
|       my $discover = $jobqueue->search( | ||||
|         { action => 'discover', status => { -like => 'queued%' } } | ||||
|       )->count; | ||||
|  | ||||
|       return job_defer("Deferred arpwalk due to pending discover jobs") | ||||
|         if $discover; | ||||
|   } | ||||
|  | ||||
|   schema('netdisco')->txn_do(sub { | ||||
|     # clean up user submitted jobs older than 1min, | ||||
|     # assuming skew between schedulers' clocks is not greater than 1min | ||||
|     $jobqueue->search({ | ||||
|         action => 'arpnip', | ||||
|         status => 'queued', | ||||
|         entered => { '<' => \"(now() - interval '1 minute')" }, | ||||
|     })->delete; | ||||
|  | ||||
|     # is scuppered by any user job submitted in last 1min (bad), or | ||||
|     # any similar job from another scheduler (good) | ||||
|     $jobqueue->populate([ | ||||
|       map {{ | ||||
|           device => $_, | ||||
|           action => 'arpnip', | ||||
|           status => 'queued', | ||||
|       }} ($devices->all) | ||||
|     ]); | ||||
|   }); | ||||
|  | ||||
|   return job_done("Queued arpnip job for all devices"); | ||||
| } | ||||
|  | ||||
| sub arpnip { | ||||
|   my ($self, $job) = @_; | ||||
|  | ||||
|   my $host = NetAddr::IP::Lite->new($job->device); | ||||
|   my $device = get_device($host->addr); | ||||
|  | ||||
|   if ($device->in_storage | ||||
|       and $device->vendor and $device->vendor eq 'netdisco') { | ||||
|       return job_done("Skipped arpnip for pseudo-device $host"); | ||||
|   } | ||||
|  | ||||
|   my $snmp = snmp_connect($device); | ||||
|   if (!defined $snmp) { | ||||
|       return job_error("arpnip failed: could not SNMP connect to $host"); | ||||
|   } | ||||
|  | ||||
|   unless ($snmp->has_layer(3)) { | ||||
|       return job_done("Skipped arpnip for device $host without OSI layer 3 capability"); | ||||
|   } | ||||
|  | ||||
|   do_arpnip($device, $snmp); | ||||
|  | ||||
|   return job_done("Ended arpnip for ". $host->addr); | ||||
| } | ||||
| sub arpwalk { (shift)->_walk_body('arpnip', @_) } | ||||
| sub arpnip  { (shift)->_single_body('arpnip', @_) } | ||||
|  | ||||
| 1; | ||||
|   | ||||
							
								
								
									
										86
									
								
								Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Common.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Netdisco/lib/App/Netdisco/Daemon/Worker/Poller/Common.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| package App::Netdisco::Daemon::Worker::Poller::Common; | ||||
|  | ||||
| use Dancer qw/:moose :syntax :script/; | ||||
| use Dancer::Plugin::DBIC 'schema'; | ||||
|  | ||||
| use App::Netdisco::Util::SNMP 'snmp_connect'; | ||||
| use App::Netdisco::Util::Device 'get_device'; | ||||
| use App::Netdisco::Daemon::Util ':all'; | ||||
|  | ||||
| use NetAddr::IP::Lite ':lower'; | ||||
|  | ||||
| use Role::Tiny; | ||||
| use namespace::clean; | ||||
|  | ||||
| # queue a job for all devices known to Netdisco | ||||
| sub _walk_body { | ||||
|   my ($self, $job_type, $job) = @_; | ||||
|  | ||||
|   my $devices = schema('netdisco')->resultset('Device')->get_column('ip'); | ||||
|   my $jobqueue = schema('netdisco')->resultset('Admin'); | ||||
|  | ||||
|   if ($job->subaction and $job->subaction eq 'after-discoverall') { | ||||
|       # make sure there are no incomplete discover jobs queued | ||||
|       my $discover = $jobqueue->search( | ||||
|         { action => 'discover', status => { -like => 'queued%' } } | ||||
|       )->count; | ||||
|  | ||||
|       return job_defer("Deferred $job_type due to pending discover jobs") | ||||
|         if $discover; | ||||
|   } | ||||
|  | ||||
|   schema('netdisco')->txn_do(sub { | ||||
|     # clean up user submitted jobs older than 1min, | ||||
|     # assuming skew between schedulers' clocks is not greater than 1min | ||||
|     $jobqueue->search({ | ||||
|         action => $job_type, | ||||
|         status => 'queued', | ||||
|         entered => { '<' => \"(now() - interval '1 minute')" }, | ||||
|     })->delete; | ||||
|  | ||||
|     # is scuppered by any user job submitted in last 1min (bad), or | ||||
|     # any similar job from another scheduler (good) | ||||
|     $jobqueue->populate([ | ||||
|       map {{ | ||||
|           device => $_, | ||||
|           action => $job_type, | ||||
|           status => 'queued', | ||||
|       }} ($devices->all) | ||||
|     ]); | ||||
|   }); | ||||
|  | ||||
|   return job_done("Queued $job_type job for all devices"); | ||||
| } | ||||
|  | ||||
| sub _single_body { | ||||
|   my ($self, $job_type, $job) = @_; | ||||
|  | ||||
|   my $action_method = $job_type .'_action'; | ||||
|   my $job_action = $self->$action_method; | ||||
|  | ||||
|   my $layer_method = $job_type .'_layer'; | ||||
|   my $job_layer = $self->$layer_method; | ||||
|  | ||||
|   my $host = NetAddr::IP::Lite->new($job->device); | ||||
|   my $device = get_device($host->addr); | ||||
|  | ||||
|   if ($device->in_storage | ||||
|       and $device->vendor and $device->vendor eq 'netdisco') { | ||||
|       return job_done("Skipped $job_type for pseudo-device $host"); | ||||
|   } | ||||
|  | ||||
|   my $snmp = snmp_connect($device); | ||||
|   if (!defined $snmp) { | ||||
|       return job_error("$job_type failed: could not SNMP connect to $host"); | ||||
|   } | ||||
|  | ||||
|   unless ($snmp->has_layer( $job_layer )) { | ||||
|       return job_done("Skipped $job_type for device $host without OSI layer $job_layer capability"); | ||||
|   } | ||||
|  | ||||
|   $job_action->($device, $snmp); | ||||
|  | ||||
|   return job_done("Ended $job_type for ". $host->addr); | ||||
| } | ||||
|  | ||||
| 1; | ||||
| @@ -1,71 +1,16 @@ | ||||
| package App::Netdisco::Daemon::Worker::Poller::Macsuck; | ||||
|  | ||||
| use Dancer qw/:moose :syntax :script/; | ||||
| use Dancer::Plugin::DBIC 'schema'; | ||||
|  | ||||
| use App::Netdisco::Util::SNMP 'snmp_connect'; | ||||
| use App::Netdisco::Util::Device 'get_device'; | ||||
| use App::Netdisco::Core::Macsuck ':all'; | ||||
| use App::Netdisco::Daemon::Util ':all'; | ||||
|  | ||||
| use NetAddr::IP::Lite ':lower'; | ||||
| use App::Netdisco::Core::Macsuck 'do_macsuck'; | ||||
|  | ||||
| use Role::Tiny; | ||||
| use namespace::clean; | ||||
|  | ||||
| # queue a macsuck job for all devices known to Netdisco | ||||
| sub macwalk { | ||||
|   my ($self, $job) = @_; | ||||
| with 'App::Netdisco::Daemon::Worker::Poller::Common'; | ||||
|  | ||||
|   my $devices = schema('netdisco')->resultset('Device')->get_column('ip'); | ||||
|   my $jobqueue = schema('netdisco')->resultset('Admin'); | ||||
| sub macsuck_action { \&do_macsuck } | ||||
| sub macsuck_layer { 2 } | ||||
|  | ||||
|   schema('netdisco')->txn_do(sub { | ||||
|     # clean up user submitted jobs older than 1min, | ||||
|     # assuming skew between schedulers' clocks is not greater than 1min | ||||
|     $jobqueue->search({ | ||||
|         action => 'macsuck', | ||||
|         status => 'queued', | ||||
|         entered => { '<' => \"(now() - interval '1 minute')" }, | ||||
|     })->delete; | ||||
|  | ||||
|     # is scuppered by any user job submitted in last 1min (bad), or | ||||
|     # any similar job from another scheduler (good) | ||||
|     $jobqueue->populate([ | ||||
|       map {{ | ||||
|           device => $_, | ||||
|           action => 'macsuck', | ||||
|           status => 'queued', | ||||
|       }} ($devices->all) | ||||
|     ]); | ||||
|   }); | ||||
|  | ||||
|   return job_done("Queued macsuck job for all devices"); | ||||
| } | ||||
|  | ||||
| sub macsuck { | ||||
|   my ($self, $job) = @_; | ||||
|  | ||||
|   my $host = NetAddr::IP::Lite->new($job->device); | ||||
|   my $device = get_device($host->addr); | ||||
|  | ||||
|   if ($device->in_storage | ||||
|       and $device->vendor and $device->vendor eq 'netdisco') { | ||||
|       return job_done("Skipped macsuck for pseudo-device $host"); | ||||
|   } | ||||
|  | ||||
|   my $snmp = snmp_connect($device); | ||||
|   if (!defined $snmp) { | ||||
|       return job_error("macsuck failed: could not SNMP connect to $host"); | ||||
|   } | ||||
|  | ||||
|   unless ($snmp->has_layer(2)) { | ||||
|       return job_done("Skipped macsuck for device $host without OSI layer 2 capability"); | ||||
|   } | ||||
|  | ||||
|   do_macsuck($device, $snmp); | ||||
|  | ||||
|   return job_done("Ended macsuck for ". $host->addr); | ||||
| } | ||||
| sub macwalk { (shift)->_walk_body('macsuck', @_) } | ||||
| sub macsuck { (shift)->_single_body('macsuck', @_) } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -54,7 +54,7 @@ colon character) | ||||
|  | ||||
| If you followed the installation instructions, then you should have set the | ||||
| database connection parameters to match those of your local system. That is, | ||||
| the C<dsn> (DB name, host, port), C<user> and C<pass>. | ||||
| the database C<name>, C<host>, C<user> and C<pass>. | ||||
|  | ||||
| =head2 General Settings | ||||
|  | ||||
| @@ -95,14 +95,26 @@ database: | ||||
|  netdisco=> update users set port_control = true where username = 'guest'; | ||||
|  netdisco=> update users set admin = true where username = 'guest'; | ||||
|  | ||||
| =head3 C<port> | ||||
| =head3 C<trust_remote_user> | ||||
|  | ||||
| Value: Number. Default: C<5000>. | ||||
| Value: Boolean. Default: C<false>. | ||||
|  | ||||
| Port which the web server listens on. Netdisco comes with a good pre-forking | ||||
| web server, so you can change this to C<80> if you want to use it directly. | ||||
| However the default is designed to work well with servers such as Apache in | ||||
| reverse-proxy mode. | ||||
| 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. | ||||
|  | ||||
| =head3 C<trust_x_remote_user> | ||||
|  | ||||
| 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: | ||||
|  | ||||
|  RequestHeader unset X-REMOTE_USER | ||||
|  RequestHeader set X-REMOTE_USER "%{REMOTE_USER}e" env=REMOTE_USER | ||||
|  | ||||
| =head3 C<path> | ||||
|  | ||||
| @@ -112,14 +124,6 @@ Mount point for the Netdisco web frontend. This is usually the root of the web | ||||
| server. Set this to the path under which all pages live, e.g. C</netdisco2>. | ||||
| As an alternative you can use the C<--path> option to C<netdisco-web>. | ||||
|  | ||||
| =head3 C<behind_proxy> | ||||
|  | ||||
| Value: Boolean. Default: C<false>. | ||||
|  | ||||
| A hint to the Netdisco web frontend that it's running behind a reverse proxy. | ||||
| In that case, Netdisco will pay attention to the C<X_FORWARDED_PROTOCOL>, | ||||
| C<X_FORWARDED_HOST>, etc settings in HTML headers. | ||||
|  | ||||
| =head3 C<web_plugins> | ||||
|  | ||||
| Value: List of Modules. Default: List of bundled L<App::Netdisco::Web::Plugin> names. | ||||
| @@ -348,7 +352,7 @@ Value: Settings Tree. Default: | ||||
|  workers: | ||||
|    interactives: 2 | ||||
|    pollers: 2 | ||||
|    sleep_time: 5 | ||||
|    sleep_time: 2 | ||||
|  | ||||
| Control the activity of the backend daemon with this configuration setting. | ||||
|  | ||||
|   | ||||
| @@ -14,52 +14,72 @@ Obviously, you'll need to substitute this wherever you see "C<~>" in the | ||||
| installation instructions. The Netdisco application will use this setting | ||||
| itself to locate files and configuration. | ||||
|  | ||||
| =head1 Pass Options to the Web Frontend Daemon | ||||
|  | ||||
| Simply add any options after the "C<start>" command. See other sections of | ||||
| this document for some examples. | ||||
|  | ||||
| =head1 Non-root Hosting | ||||
|  | ||||
| Netdisco will assume its web site is hosted at the apex of your server - that | ||||
| is, the document root. To relocate the web application, pass the C<--path> | ||||
| parameter to the web startup script: | ||||
|  | ||||
|  ~/bin/netdisco-web --path /netdisco2 | ||||
|  ~/bin/netdisco-web start --path=/netdisco2 | ||||
|  | ||||
| Alternatively, can set the C<path> configuration option in your | ||||
| C<deployment.yml> file: | ||||
|  | ||||
|  path: '/netdisco2' | ||||
|  | ||||
| =head1 Listening Port for the Web Frontend | ||||
|  | ||||
| Pass the C<--port> parameter to any of the web scripts. For example: | ||||
|  | ||||
|  ~/bin/netdisco-web start --port=8080 | ||||
|  | ||||
| =head1 Listening Address for the Web Frontend | ||||
|  | ||||
| Pass the C<--listen> parameter to any of the web scripts. Note that you always | ||||
| must include the port number. For example: | ||||
|  | ||||
|  ~/bin/netdisco-web start --listen=127.0.0.1:8080 | ||||
|  | ||||
| =head1 Behind a Proxy | ||||
|  | ||||
| By default the web application daemon starts listening on port 5000 and goes  | ||||
| into the background. This is ideal for hosting behind a web proxy (e.g. Apache | ||||
| with C<mod_proxy>). | ||||
|  | ||||
| After enabling the C<proxy> and C<proxy_http> modules in Apache, a suitable | ||||
| configuration would be: | ||||
| After enabling the C<headers>, C<proxy> and C<proxy_http> modules in Apache, a | ||||
| suitable configuration would be: | ||||
|  | ||||
|  ProxyPreserveHost On | ||||
|  ProxyPass / http://localhost:5000/ | ||||
|  ProxyPassReverse / http://localhost:5000/ | ||||
|   | ||||
|  ProxyRequests Off | ||||
|  <Proxy *> | ||||
|    Order allow,deny | ||||
|    Allow from all | ||||
|  </Proxy> | ||||
|  | ||||
| You also need to set the following configuration in your C<deployment.yml> | ||||
| file: | ||||
|  | ||||
|  behind_proxy: 1 | ||||
|  | ||||
| To combine this with Non-root Hosting as above, simply change the paths | ||||
| referenced in the configuration like so (and use Non-root Hosting as above): | ||||
| referenced in the configuration, and set C<path> in your C<deployment.yml> as | ||||
| discussed above. Note there is no trailing slash in the Apache config: | ||||
|  | ||||
|  ProxyPass /netdisco2 http://localhost:5000/ | ||||
|  ProxyPassReverse /netdisco2 http://localhost:5000/ | ||||
|  ProxyPass /netdisco2 http://localhost:5000/netdisco2 | ||||
|  ProxyPassReverse /netdisco2 http://localhost:5000/netdisco2 | ||||
|  | ||||
| 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 SQL and HTTP Trace | ||||
|  | ||||
| For SQL debugging try the following commands: | ||||
|  | ||||
|  DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv starman --workers=1 ~/bin/netdisco-web-fg | ||||
|  DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv starman ~/bin/netdisco-web-fg --workers=1 --disable-keepalive | ||||
|  DBIC_TRACE_PROFILE=console DBIC_TRACE=1 ~/bin/localenv ~/bin/netdisco-daemon-fg | ||||
|  | ||||
| =head1 Further Reading... | ||||
|   | ||||
| @@ -8,6 +8,23 @@ This document will list only the most significant changes with each release of | ||||
| Netdisco. You are B<STRONGLY> recommended to read this document each time you | ||||
| install and upgrade. | ||||
|  | ||||
| =head1 2.010000 | ||||
|  | ||||
| =head2 General Changes | ||||
|  | ||||
| You can now simplify database configuration to just the following, instead of | ||||
| the more verbose C<plugins/DBIC> setting which was there before: | ||||
|  | ||||
|  database: | ||||
|    name: 'netdisco' | ||||
|    host: 'localhost' | ||||
|    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 | ||||
| Deployment and Configuration documentation for further details. | ||||
|  | ||||
| =head1 2.008000 | ||||
|  | ||||
| =head2 Heath Advice | ||||
| @@ -16,7 +33,7 @@ This release contains the first version of our new poller, which handles | ||||
| device and node discovery. Please make sure to backup any existing Netdisco | ||||
| database before trying it out. | ||||
|  | ||||
| =head2 Other Changes | ||||
| =head2 General Changes | ||||
|  | ||||
| You can remove any settings from C<~/environments/deployment.yml> which you | ||||
| didn't edit or add to the file yourself. All defaults are now properly | ||||
| @@ -35,7 +52,7 @@ Please B<rename or copy> your environment file: | ||||
|  | ||||
|  mv ~/environments/development.yml ~/environments/deployment.yml | ||||
|  | ||||
| =head2 Other Changes | ||||
| =head2 General Changes | ||||
|  | ||||
| The installation is now relocateable outside of a user's home directory by | ||||
| setting the C<NETDISCO_HOME> environment variable. This defaults to your own | ||||
|   | ||||
| @@ -10,6 +10,8 @@ our @EXPORT = (); | ||||
| our @EXPORT_OK = qw/ | ||||
|   get_device | ||||
|   is_discoverable | ||||
|   is_arpnipable | ||||
|   is_macsuckable | ||||
| /; | ||||
| our %EXPORT_TAGS = (all => \@EXPORT_OK); | ||||
|  | ||||
| @@ -54,7 +56,7 @@ sub get_device { | ||||
|     ->find_or_new({ip => $ip}); | ||||
| } | ||||
|  | ||||
| =head2 is_discoverable( $ip ) | ||||
| =head2 is_discoverable( $ip, $device_type? ) | ||||
|  | ||||
| Given an IP address, returns C<true> if Netdisco on this host is permitted by | ||||
| the local configuration to discover the device. | ||||
| @@ -62,6 +64,9 @@ the local configuration to discover the device. | ||||
| The configuration items C<discover_no> and C<discover_only> are checked | ||||
| against the given IP. | ||||
|  | ||||
| If C<$device_type> is also given, then C<discover_no_type> will also be | ||||
| checked. | ||||
|  | ||||
| Returns false if the host is not permitted to discover the target device. | ||||
|  | ||||
| =cut | ||||
| @@ -99,4 +104,82 @@ sub is_discoverable { | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| =head2 is_arpnipable( $ip ) | ||||
|  | ||||
| Given an IP address, returns C<true> if Netdisco on this host is permitted by | ||||
| the local configuration to arpnip the device. | ||||
|  | ||||
| The configuration items C<arpnip_no> and C<arpnip_only> are checked | ||||
| against the given IP. | ||||
|  | ||||
| Returns false if the host is not permitted to arpnip the target device. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub is_arpnipable { | ||||
|   my $ip = shift; | ||||
|   my $device = get_device($ip) or return 0; | ||||
|  | ||||
|   my $addr = NetAddr::IP::Lite->new($device->ip); | ||||
|   my $arpnip_no   = setting('arpnip_no') || []; | ||||
|   my $arpnip_only = setting('arpnip_only') || []; | ||||
|  | ||||
|   if (scalar @$arpnip_no) { | ||||
|       foreach my $item (@$arpnip_no) { | ||||
|           my $ip = NetAddr::IP::Lite->new($item) or return 0; | ||||
|           return 0 if $ip->contains($addr); | ||||
|       } | ||||
|   } | ||||
|  | ||||
|   if (scalar @$arpnip_only) { | ||||
|       my $okay = 0; | ||||
|       foreach my $item (@$arpnip_only) { | ||||
|           my $ip = NetAddr::IP::Lite->new($item) or return 0; | ||||
|           ++$okay if $ip->contains($addr); | ||||
|       } | ||||
|       return 0 if not $okay; | ||||
|   } | ||||
|  | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| =head2 is_macsuckable( $ip ) | ||||
|  | ||||
| Given an IP address, returns C<true> if Netdisco on this host is permitted by | ||||
| the local configuration to macsuck the device. | ||||
|  | ||||
| The configuration items C<macsuck_no> and C<macsuck_only> are checked | ||||
| against the given IP. | ||||
|  | ||||
| Returns false if the host is not permitted to macsuck the target device. | ||||
|  | ||||
| =cut | ||||
|  | ||||
| sub is_macsuckable { | ||||
|   my $ip = shift; | ||||
|   my $device = get_device($ip) or return 0; | ||||
|  | ||||
|   my $addr = NetAddr::IP::Lite->new($device->ip); | ||||
|   my $macsuck_no   = setting('macsuck_no') || []; | ||||
|   my $macsuck_only = setting('macsuck_only') || []; | ||||
|  | ||||
|   if (scalar @$macsuck_no) { | ||||
|       foreach my $item (@$macsuck_no) { | ||||
|           my $ip = NetAddr::IP::Lite->new($item) or return 0; | ||||
|           return 0 if $ip->contains($addr); | ||||
|       } | ||||
|   } | ||||
|  | ||||
|   if (scalar @$macsuck_only) { | ||||
|       my $okay = 0; | ||||
|       foreach my $item (@$macsuck_only) { | ||||
|           my $ip = NetAddr::IP::Lite->new($item) or return 0; | ||||
|           ++$okay if $ip->contains($addr); | ||||
|       } | ||||
|       return 0 if not $okay; | ||||
|   } | ||||
|  | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| 1; | ||||
|   | ||||
| @@ -93,13 +93,13 @@ sub _snmp_connect_generic { | ||||
|  | ||||
|   my $info = undef; | ||||
|   VERSION: foreach my $ver (@versions) { | ||||
|       next unless length $ver; | ||||
|       next unless $ver; | ||||
|  | ||||
|       CLASS: foreach my $class (@classes) { | ||||
|           next unless length $class; | ||||
|           next unless $class; | ||||
|  | ||||
|           COMMUNITY: foreach my $comm (@communities) { | ||||
|               next unless length $comm; | ||||
|               next unless $comm; | ||||
|  | ||||
|               $info = _try_connect($ver, $class, $comm, \%snmp_args) | ||||
|                 and last VERSION; | ||||
|   | ||||
| @@ -43,6 +43,9 @@ if (setting('extra_web_plugins') and ref [] eq ref setting('extra_web_plugins')) | ||||
|     _load_web_plugins( setting('extra_web_plugins') ); | ||||
| } | ||||
|  | ||||
| # workaround for https://github.com/PerlDancer/Dancer/issues/935 | ||||
| hook after_error_render => sub { setting('layout' => 'main') }; | ||||
|  | ||||
| hook 'before_template' => sub { | ||||
|     my $tokens = shift; | ||||
|  | ||||
| @@ -51,7 +54,7 @@ hook 'before_template' => sub { | ||||
|         if request->base->path ne '/'; | ||||
|  | ||||
|     # allow portable dynamic content | ||||
|     $tokens->{uri_for} = sub { uri_for(@_)->path_query() }; | ||||
|     $tokens->{uri_for} = sub { uri_for(@_)->path_query }; | ||||
|  | ||||
|     # allow very long lists of ports | ||||
|     $Template::Directive::WHILE_MAX = 10_000; | ||||
|   | ||||
| @@ -74,7 +74,7 @@ foreach my $jobtype (keys %jobs_all, keys %jobs) { | ||||
|           if exists $jobs{$jobtype} and not param('device'); | ||||
|  | ||||
|         add_job($jobtype, param('device')); | ||||
|         redirect uri_for('/admin/jobqueue')->path_query; | ||||
|         redirect uri_for('/admin/jobqueue')->as_string; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -82,7 +82,7 @@ get '/admin/*' => sub { | ||||
|     my ($tag) = splat; | ||||
|  | ||||
|     if (! eval { var('user')->admin }) { | ||||
|         return redirect uri_for('/')->path_query; | ||||
|         return redirect uri_for('/')->as_string; | ||||
|     } | ||||
|  | ||||
|     # trick the ajax into working as if this were a tabbed page | ||||
|   | ||||
| @@ -7,10 +7,17 @@ use Digest::MD5 (); | ||||
|  | ||||
| hook 'before' => sub { | ||||
|     if (! session('user') && request->path ne uri_for('/login')->path) { | ||||
|         if (setting('no_auth')) { | ||||
|         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('no_auth')) { | ||||
|             session(user => 'guest'); | ||||
|         } | ||||
|         else { | ||||
|             # user has no AuthN - force to handler for '/' | ||||
|             request->path_info('/'); | ||||
|         } | ||||
|     } | ||||
| @@ -27,23 +34,24 @@ hook 'before' => sub { | ||||
|  | ||||
| post '/login' => sub { | ||||
|     if (param('username') and param('password')) { | ||||
|         my $user = schema('netdisco')->resultset('User')->find(param('username')); | ||||
|         my $user = schema('netdisco')->resultset('User') | ||||
|                                      ->find(param('username')); | ||||
|  | ||||
|         if ($user) { | ||||
|             my $sum = Digest::MD5::md5_hex(param('password')); | ||||
|             if (($sum and $user->password) and ($sum eq $user->password)) { | ||||
|                 session(user => $user->username); | ||||
|                 return redirect uri_for('/inventory')->path_query; | ||||
|                 return redirect uri_for('/inventory')->as_string; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     redirect uri_for('/', {failed => 1})->path_query; | ||||
|     redirect uri_for('/', {failed => 1})->as_string; | ||||
| }; | ||||
|  | ||||
| get '/logout' => sub { | ||||
|     session->destroy; | ||||
|     redirect uri_for('/', {logout => 1})->path_query; | ||||
|     redirect uri_for('/', {logout => 1})->as_string; | ||||
| }; | ||||
|  | ||||
| true; | ||||
|   | ||||
| @@ -116,7 +116,7 @@ get '/device' => sub { | ||||
|     }); | ||||
|  | ||||
|     if (!defined $dev) { | ||||
|         return redirect uri_for('/', {nosuchdevice => 1})->path_query; | ||||
|         return redirect uri_for('/', {nosuchdevice => 1})->as_string(); | ||||
|     } | ||||
|  | ||||
|     params->{'tab'} ||= 'details'; | ||||
|   | ||||
| @@ -22,7 +22,7 @@ config->{engines}->{template_toolkit}->{INCLUDE_PATH} ||= [ setting('views') ]; | ||||
| register 'register_template_path' => sub { | ||||
|   my ($self, $path) = plugin_args(@_); | ||||
|  | ||||
|   if (!length $path) { | ||||
|   if (!$path) { | ||||
|       return error "bad template path to register_template_paths"; | ||||
|   } | ||||
|  | ||||
| @@ -34,11 +34,11 @@ register 'register_template_path' => sub { | ||||
| sub _register_include { | ||||
|   my ($type, $plugin) = @_; | ||||
|  | ||||
|   if (!length $type) { | ||||
|   if (!$type) { | ||||
|       return error "bad type to _register_include"; | ||||
|   } | ||||
|  | ||||
|   if (!length $plugin) { | ||||
|   if (!$plugin) { | ||||
|       return error "bad plugin name to register_$type"; | ||||
|   } | ||||
|  | ||||
| @@ -60,7 +60,7 @@ register 'register_device_port_column' => sub { | ||||
|   $config->{default} ||= ''; | ||||
|   $config->{position} ||= 'right'; | ||||
|  | ||||
|   if (!length $config->{name} or !length $config->{label}) { | ||||
|   if (!$config->{name} or !$config->{label}) { | ||||
|       return error "bad config to register_device_port_column"; | ||||
|   } | ||||
|  | ||||
| @@ -77,9 +77,9 @@ register 'register_device_port_column' => sub { | ||||
| register 'register_navbar_item' => sub { | ||||
|   my ($self, $config) = plugin_args(@_); | ||||
|  | ||||
|   if (!length $config->{tag} | ||||
|       or !length $config->{path} | ||||
|       or !length $config->{label}) { | ||||
|   if (!$config->{tag} | ||||
|       or !$config->{path} | ||||
|       or !$config->{label}) { | ||||
|  | ||||
|       return error "bad config to register_navbar_item"; | ||||
|   } | ||||
| @@ -97,8 +97,8 @@ register 'register_navbar_item' => sub { | ||||
| register 'register_admin_task' => sub { | ||||
|   my ($self, $config) = plugin_args(@_); | ||||
|  | ||||
|   if (!length $config->{tag} | ||||
|       or !length $config->{label}) { | ||||
|   if (!$config->{tag} | ||||
|       or !$config->{label}) { | ||||
|  | ||||
|       return error "bad config to register_admin_task"; | ||||
|   } | ||||
| @@ -110,8 +110,8 @@ sub _register_tab { | ||||
|   my ($nav, $config) = @_; | ||||
|   my $stash = setting("_${nav}_tabs"); | ||||
|  | ||||
|   if (!length $config->{tag} | ||||
|       or !length $config->{label}) { | ||||
|   if (!$config->{tag} | ||||
|       or !$config->{label}) { | ||||
|  | ||||
|       return error "bad config to register_${nav}_item"; | ||||
|   } | ||||
| @@ -140,9 +140,9 @@ register 'register_report' => sub { | ||||
|   my ($self, $config) = plugin_args(@_); | ||||
|   my @categories = @{ setting('_report_order') }; | ||||
|  | ||||
|   if (!length $config->{category} | ||||
|       or !length $config->{tag} | ||||
|       or !length $config->{label} | ||||
|   if (!$config->{category} | ||||
|       or !$config->{tag} | ||||
|       or !$config->{label} | ||||
|       or 0 == scalar grep {$config->{category} eq $_} @categories) { | ||||
|  | ||||
|       return error "bad config to register_report"; | ||||
|   | ||||
| @@ -13,7 +13,7 @@ register_admin_task({ | ||||
|  | ||||
| ajax '/ajax/control/admin/jobqueue/del' => sub { | ||||
|     send_error('Forbidden', 403) unless var('user')->admin; | ||||
|     send_error('Missing job', 400) unless length param('job'); | ||||
|     send_error('Missing job', 400) unless param('job'); | ||||
|  | ||||
|     schema('netdisco')->txn_do(sub { | ||||
|       my $device = schema('netdisco')->resultset('Admin') | ||||
| @@ -26,7 +26,10 @@ ajax '/ajax/content/admin/jobqueue' => sub { | ||||
|  | ||||
|     my $set = schema('netdisco')->resultset('Admin') | ||||
|       ->with_times | ||||
|       ->search({}, {order_by => { -desc => [qw/entered device action/] }}); | ||||
|       ->search({}, { | ||||
|         order_by => { -desc => [qw/entered device action/] }, | ||||
|         rows => 200, | ||||
|       }); | ||||
|  | ||||
|     content_type('text/html'); | ||||
|     template 'ajax/admintask/jobqueue.tt', { | ||||
|   | ||||
| @@ -15,14 +15,14 @@ register_admin_task({ | ||||
| sub _sanity_ok { | ||||
|     return 0 unless var('user') and var('user')->admin; | ||||
|  | ||||
|     return 0 unless length param('dns') | ||||
|     return 0 unless param('dns') | ||||
|       and param('dns') =~ m/^[[:print:]]+$/ | ||||
|       and param('dns') !~ m/[[:space:]]/; | ||||
|  | ||||
|     my $ip = NetAddr::IP::Lite->new(param('ip')); | ||||
|     return 0 unless ($ip and$ip->addr ne '0.0.0.0'); | ||||
|  | ||||
|     return 0 unless length param('ports') | ||||
|     return 0 unless param('ports') | ||||
|       and param('ports') =~ m/^[[:digit:]]+$/; | ||||
|  | ||||
|     return 1; | ||||
|   | ||||
| @@ -21,8 +21,8 @@ sub _sanity_ok { | ||||
|     my $dev2 = NetAddr::IP::Lite->new(param('dev2')); | ||||
|     return 0 unless ($dev2 and $dev2->addr ne '0.0.0.0'); | ||||
|  | ||||
|     return 0 unless length param('port1'); | ||||
|     return 0 unless length param('port2'); | ||||
|     return 0 unless param('port1'); | ||||
|     return 0 unless param('port2'); | ||||
|  | ||||
|     return 1; | ||||
| } | ||||
|   | ||||
| @@ -57,8 +57,15 @@ ajax '/ajax/content/device/ports' => sub { | ||||
|     # get number of vlans on the port to control whether to list them or not | ||||
|     $set = $set->with_vlan_count if param('c_vmember'); | ||||
|  | ||||
|     # run single collapsed query for all relations, but only if we're not | ||||
|     # also fetching archived data (tests show it's better this way) | ||||
|     $set = $set->search_rs({}, { prefetch => [{ port_vlans_tagged => 'vlan'}] }) | ||||
|       if param('c_vmember') and not (param('c_nodes') and param('n_archived')); | ||||
|  | ||||
|     # what kind of nodes are we interested in? | ||||
|     my $nodes_name = (param('n_archived') ? 'nodes' : 'active_nodes'); | ||||
|     $set = $set->search_rs({}, { order_by => ["${nodes_name}.mac", "ips.ip"] }) | ||||
|       if param('c_nodes'); | ||||
|     $nodes_name .= '_with_age' if param('c_nodes') and param('n_age'); | ||||
|  | ||||
|     # retrieve active/all connected nodes, if asked for | ||||
|   | ||||
| @@ -60,6 +60,7 @@ ajax '/ajax/content/search/node' => sub { | ||||
|               ->search_by_dns({dns => $node, @active}); | ||||
|         } | ||||
|         return unless $set and $set->count; | ||||
|         $set = $set->search_rs({}, { order_by => 'me.mac' }); | ||||
|  | ||||
|         template 'ajax/search/node_by_ip.tt', { | ||||
|           macs => $set, | ||||
|   | ||||
| @@ -65,7 +65,7 @@ get '/search' => sub { | ||||
|  | ||||
|     if (not param('tab')) { | ||||
|         if (not $q) { | ||||
|             return redirect uri_for('/')->path_query; | ||||
|             return redirect uri_for('/')->as_string; | ||||
|         } | ||||
|  | ||||
|         # pick most likely tab for initial results | ||||
| @@ -82,7 +82,7 @@ get '/search' => sub { | ||||
|                       tab => 'details', | ||||
|                       q => ($nd->first->dns || $nd->first->ip), | ||||
|                       f => '', | ||||
|                     })->path_query; | ||||
|                     })->as_string; | ||||
|                 } | ||||
|  | ||||
|                 # multiple devices | ||||
|   | ||||
| @@ -35,7 +35,7 @@ ajax '/ajax/data/deviceip/typeahead' => sub { | ||||
| ajax '/ajax/data/port/typeahead' => sub { | ||||
|     my $dev  = param('dev1')  || param('dev2'); | ||||
|     my $port = param('port1') || param('port2'); | ||||
|     send_error('Missing device', 400) unless length $dev; | ||||
|     send_error('Missing device', 400) unless $dev; | ||||
|  | ||||
|     my $device = schema('netdisco')->resultset('Device') | ||||
|       ->find({ip => $dev}); | ||||
| @@ -43,7 +43,7 @@ ajax '/ajax/data/port/typeahead' => sub { | ||||
|  | ||||
|     my $set = $device->ports({},{order_by => 'port'}); | ||||
|     $set = $set->search({port => { -ilike => "\%$port\%" }}) | ||||
|       if length $port; | ||||
|       if $port; | ||||
|  | ||||
|     my $results = [ sort { &App::Netdisco::Util::Web::sort_port($a->port, $b->port) } $set->all ]; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user