implement "no devices" prompt for admin users to do first discover
This commit is contained in:
		| @@ -55,4 +55,46 @@ __PACKAGE__->add_columns( | |||||||
| __PACKAGE__->set_primary_key("job"); | __PACKAGE__->set_primary_key("job"); | ||||||
|  |  | ||||||
| # You can replace this text with custom code or comments, and it will be preserved on regeneration | # You can replace this text with custom code or comments, and it will be preserved on regeneration | ||||||
|  |  | ||||||
|  | =head1 ADDITIONAL COLUMNS | ||||||
|  |  | ||||||
|  | =head2 entererd_stamp | ||||||
|  |  | ||||||
|  | Formatted version of the C<entered> field, accurate to the minute. | ||||||
|  |  | ||||||
|  | The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T> | ||||||
|  | between the date stamp and time stamp. That is: | ||||||
|  |  | ||||||
|  |  2012-02-06 12:49 | ||||||
|  |  | ||||||
|  | =cut | ||||||
|  |  | ||||||
|  | sub entered_stamp  { return (shift)->get_column('entered_stamp')  } | ||||||
|  |  | ||||||
|  | =head2 started_stamp | ||||||
|  |  | ||||||
|  | Formatted version of the C<started> field, accurate to the minute. | ||||||
|  |  | ||||||
|  | The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T> | ||||||
|  | between the date stamp and time stamp. That is: | ||||||
|  |  | ||||||
|  |  2012-02-06 12:49 | ||||||
|  |  | ||||||
|  | =cut | ||||||
|  |  | ||||||
|  | sub started_stamp  { return (shift)->get_column('started_stamp')  } | ||||||
|  |  | ||||||
|  | =head2 finished_stamp | ||||||
|  |  | ||||||
|  | Formatted version of the C<finished> field, accurate to the minute. | ||||||
|  |  | ||||||
|  | The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T> | ||||||
|  | between the date stamp and time stamp. That is: | ||||||
|  |  | ||||||
|  |  2012-02-06 12:49 | ||||||
|  |  | ||||||
|  | =cut | ||||||
|  |  | ||||||
|  | sub finished_stamp  { return (shift)->get_column('finished_stamp')  } | ||||||
|  |  | ||||||
| 1; | 1; | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								Netdisco/lib/App/Netdisco/DB/ResultSet/Admin.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Netdisco/lib/App/Netdisco/DB/ResultSet/Admin.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | package App::Netdisco::DB::ResultSet::Admin; | ||||||
|  | use base 'DBIx::Class::ResultSet'; | ||||||
|  |  | ||||||
|  | use strict; | ||||||
|  | use warnings FATAL => 'all'; | ||||||
|  |  | ||||||
|  | =head1 ADDITIONAL METHODS | ||||||
|  |  | ||||||
|  | =head2 with_times | ||||||
|  |  | ||||||
|  | This is a modifier for any C<search()> (including the helpers below) which | ||||||
|  | will add the following additional synthesized columns to the result set: | ||||||
|  |  | ||||||
|  | =over 4 | ||||||
|  |  | ||||||
|  | =item entered_stamp | ||||||
|  |  | ||||||
|  | =item started_stamp | ||||||
|  |  | ||||||
|  | =item finished_stamp | ||||||
|  |  | ||||||
|  | =back | ||||||
|  |  | ||||||
|  | =cut | ||||||
|  |  | ||||||
|  | sub with_times { | ||||||
|  |   my ($rs, $cond, $attrs) = @_; | ||||||
|  |  | ||||||
|  |   return $rs | ||||||
|  |     ->search_rs($cond, $attrs) | ||||||
|  |     ->search({}, | ||||||
|  |       { | ||||||
|  |         '+columns' => { | ||||||
|  |           entered_stamp => \"to_char(entered, 'YYYY-MM-DD HH24:MI')", | ||||||
|  |           started_stamp => \"to_char(started, 'YYYY-MM-DD HH24:MI')", | ||||||
|  |           finished_stamp => \"to_char(finished, 'YYYY-MM-DD HH24:MI')", | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 1; | ||||||
| @@ -16,6 +16,7 @@ use App::Netdisco::Web::Report; | |||||||
| use App::Netdisco::Web::AdminTask; | use App::Netdisco::Web::AdminTask; | ||||||
| use App::Netdisco::Web::TypeAhead; | use App::Netdisco::Web::TypeAhead; | ||||||
| use App::Netdisco::Web::PortControl; | use App::Netdisco::Web::PortControl; | ||||||
|  | use App::Netdisco::Web::JobControl; | ||||||
|  |  | ||||||
| sub _load_web_plugins { | sub _load_web_plugins { | ||||||
|   my $plugin_list = shift; |   my $plugin_list = shift; | ||||||
| @@ -54,6 +55,12 @@ hook 'before_template' => sub { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| get '/' => sub { | get '/' => sub { | ||||||
|  |     if (var('user') and var('user')->admin) { | ||||||
|  |         if (schema('netdisco')->resultset('Device')->count == 0) { | ||||||
|  |             var('nodevices' => true); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     template 'index'; |     template 'index'; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								Netdisco/lib/App/Netdisco/Web/JobControl.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Netdisco/lib/App/Netdisco/Web/JobControl.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | package App::Netdisco::Web::JobControl; | ||||||
|  |  | ||||||
|  | use Dancer ':syntax'; | ||||||
|  | use Dancer::Plugin::DBIC; | ||||||
|  |  | ||||||
|  | post '/admin/discover' => sub { | ||||||
|  |     return unless var('user') and var('user')->admin; | ||||||
|  |  | ||||||
|  |     my $ip = NetAddr::IP::Lite->new(param('device')); | ||||||
|  |     return unless $ip | ||||||
|  |       and $ip->addr ne '0.0.0.0'; | ||||||
|  |  | ||||||
|  |     schema('netdisco')->resultset('Admin')->create({ | ||||||
|  |       device => $ip->addr, | ||||||
|  |       action => 'discover', | ||||||
|  |       status => 'queued', | ||||||
|  |       username => session('user'), | ||||||
|  |       userip => request->remote_address, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     status(302); | ||||||
|  |     header(Location => uri_for('/admin/jobqueue')->path_query()); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | true; | ||||||
							
								
								
									
										27
									
								
								Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/JobQueue.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package App::Netdisco::Web::Plugin::AdminTask::JobQueue; | ||||||
|  |  | ||||||
|  | use Dancer ':syntax'; | ||||||
|  | use Dancer::Plugin::Ajax; | ||||||
|  | use Dancer::Plugin::DBIC; | ||||||
|  |  | ||||||
|  | use App::Netdisco::Web::Plugin; | ||||||
|  |  | ||||||
|  | register_admin_task({ | ||||||
|  |   tag => 'jobqueue', | ||||||
|  |   label => 'Job Queue', | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | ajax '/ajax/content/admin/jobqueue' => sub { | ||||||
|  |     return unless var('user') and var('user')->admin; | ||||||
|  |  | ||||||
|  |     my $set = schema('netdisco')->resultset('Admin') | ||||||
|  |       ->with_times | ||||||
|  |       ->search({}, {order_by => { -desc => [qw/entered device action/] }}); | ||||||
|  |  | ||||||
|  |     content_type('text/html'); | ||||||
|  |     template 'ajax/admintask/jobqueue.tt', { | ||||||
|  |       results => $set, | ||||||
|  |     }, { layout => undef }; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | true; | ||||||
| @@ -13,7 +13,7 @@ register_admin_task({ | |||||||
| }); | }); | ||||||
|  |  | ||||||
| sub _sanity_ok { | sub _sanity_ok { | ||||||
|     return 0 unless var('user')->admin; |     return 0 unless var('user') and var('user')->admin; | ||||||
|  |  | ||||||
|     return 0 unless length param('dns') |     return 0 unless length param('dns') | ||||||
|       and param('dns') =~ m/^[[:print:]]+$/ |       and param('dns') =~ m/^[[:print:]]+$/ | ||||||
| @@ -86,7 +86,7 @@ ajax '/ajax/content/admin/pseudodevice/update' => sub { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| ajax '/ajax/content/admin/pseudodevice' => sub { | ajax '/ajax/content/admin/pseudodevice' => sub { | ||||||
|     return unless var('user')->admin; |     return unless var('user') and var('user')->admin; | ||||||
|  |  | ||||||
|     my $set = schema('netdisco')->resultset('Device') |     my $set = schema('netdisco')->resultset('Device') | ||||||
|       ->search( |       ->search( | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ register_admin_task({ | |||||||
| }); | }); | ||||||
|  |  | ||||||
| sub _sanity_ok { | sub _sanity_ok { | ||||||
|     return 0 unless var('user')->admin; |     return 0 unless var('user') and var('user')->admin; | ||||||
|  |  | ||||||
|     my $dev1 = NetAddr::IP::Lite->new(param('dev1')); |     my $dev1 = NetAddr::IP::Lite->new(param('dev1')); | ||||||
|     return 0 unless ($dev1 and $dev1->addr ne '0.0.0.0'); |     return 0 unless ($dev1 and $dev1->addr ne '0.0.0.0'); | ||||||
| @@ -54,7 +54,7 @@ ajax '/ajax/content/admin/topology/del' => sub { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| ajax '/ajax/content/admin/topology' => sub { | ajax '/ajax/content/admin/topology' => sub { | ||||||
|     return unless var('user')->admin; |     return unless var('user') and var('user')->admin; | ||||||
|  |  | ||||||
|     my $set = schema('netdisco')->resultset('Topology') |     my $set = schema('netdisco')->resultset('Topology') | ||||||
|       ->search({},{order_by => [qw/dev1 dev2 port1/]}); |       ->search({},{order_by => [qw/dev1 dev2 port1/]}); | ||||||
|   | |||||||
| @@ -28,6 +28,9 @@ | |||||||
|     <ul id="search_results" class="nav nav-tabs"> |     <ul id="search_results" class="nav nav-tabs"> | ||||||
|       <li class="active"><a id="[% task.tag %]_link" class="nd_single_tab" |       <li class="active"><a id="[% task.tag %]_link" class="nd_single_tab" | ||||||
|         href="#[% task.tag %]_pane">[% task.label %]</a></li> |         href="#[% task.tag %]_pane">[% task.label %]</a></li> | ||||||
|  |       [% IF task.tag == 'jobqueue' %] | ||||||
|  |       <span id="nd_device_name"></span> | ||||||
|  |       [% END %] | ||||||
|     </ul> |     </ul> | ||||||
|     <div class="tab-content"> |     <div class="tab-content"> | ||||||
|       <div class="tab-pane active" id="[% task.tag %]_pane"></div> |       <div class="tab-pane active" id="[% task.tag %]_pane"></div> | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								Netdisco/share/views/ajax/admintask/jobqueue.tt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Netdisco/share/views/ajax/admintask/jobqueue.tt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | <table class="table table-bordered table-condensed table-striped"> | ||||||
|  |   <thead> | ||||||
|  |     <tr> | ||||||
|  |       <th class="center_cell">Entered</th> | ||||||
|  |       <th class="center_cell">Status</th> | ||||||
|  |       <th class="center_cell">Action</th> | ||||||
|  |       <th class="center_cell">Device</th> | ||||||
|  |       <th class="center_cell">Port</th> | ||||||
|  |       <th class="center_cell">Param</th> | ||||||
|  |       <th class="center_cell">User</th> | ||||||
|  |       <th class="center_cell">Started</th> | ||||||
|  |       <th class="center_cell">Finished</th> | ||||||
|  |       <th class="center_cell">Action</th> | ||||||
|  |     </tr> | ||||||
|  |   </thead> | ||||||
|  |   </tbody> | ||||||
|  |     [% WHILE (row = results.next) %] | ||||||
|  |     <tr | ||||||
|  |       [% ' class="success"' IF row.status == 'done' %] | ||||||
|  |       [% ' class="error"'   IF row.status == 'error' %] | ||||||
|  |       [% ' class="info"'    IF row.status.search('^queued-') %] | ||||||
|  |     > | ||||||
|  |       <td class="center_cell">[% row.entered_stamp | html_entity %]</td> | ||||||
|  |       <td class="center_cell">[% row.action.ucfirst | html_entity %]</td> | ||||||
|  |       [% IF row.status.search('^queued-') %] | ||||||
|  |       <td class="center_cell">Running on "[% row.status.remove('^queued-') | html_entity %]"</td> | ||||||
|  |       [% ELSE %] | ||||||
|  |       <td class="center_cell">[% row.status.ucfirst | html_entity %]</td> | ||||||
|  |       [% END %] | ||||||
|  |       <td class="center_cell"><a class="nd_linkcell" | ||||||
|  |         href="[% device_ports %]&q=[% row.device | uri %]">[% row.device | html_entity %]</a></td> | ||||||
|  |       <td class="center_cell">[% row.port | html_entity %]</td> | ||||||
|  |       <td class="center_cell">[% row.subaction | html_entity %]</td> | ||||||
|  |       <td class="center_cell">[% row.username | html_entity %]</td> | ||||||
|  |       <td class="center_cell">[% row.started_stamp | html_entity %]</td> | ||||||
|  |       <td class="center_cell">[% row.finished_stamp | html_entity %]</td> | ||||||
|  |       <td class="center_cell"> | ||||||
|  |       <form name="del" class="nd_inline_form"> | ||||||
|  |         <input name="job" type="hidden" value="[% row.job | html_entity %]"> | ||||||
|  |         <button class="btn" name="del" type="submit"><i class="icon-trash text-error"></i></button> | ||||||
|  |       </form> | ||||||
|  |       </td> | ||||||
|  |     </tr> | ||||||
|  |     [% END %] | ||||||
|  |   </tbody> | ||||||
|  | </table> | ||||||
|  |  | ||||||
| @@ -44,6 +44,19 @@ | |||||||
|         <script type="text/javascript"> $('#nq').focus(); // set focus to navbar search </script> |         <script type="text/javascript"> $('#nq').focus(); // set focus to navbar search </script> | ||||||
|         [% END %] |         [% END %] | ||||||
|       </div> |       </div> | ||||||
|  |       [% IF vars.nodevices %] | ||||||
|  |       <div class="hero-unit"> | ||||||
|  |         <h3>Initial Discovery</h3> | ||||||
|  |         <p>You haven't discovered any devices yet.<p> | ||||||
|  |         <p>Enter a network device name or IP to queue the first discovery:</p> | ||||||
|  |         <form method="post" action="[% uri_for('/admin/discover') %]"> | ||||||
|  |           <div class="form-horizontal"> | ||||||
|  |             <input placeholder="Device hostname or IP" class="span4" name="device" type="text"/> | ||||||
|  |             <button type="submit" class="btn btn-info">Discover</button> | ||||||
|  |           </div> | ||||||
|  |         </form> | ||||||
|  |       </div> | ||||||
|  |       [% END %] | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </div> <!-- /container --> | </div> <!-- /container --> | ||||||
|   | |||||||
| @@ -7,6 +7,23 @@ | |||||||
|   // but which cannot use jQuery delegation via .on() |   // but which cannot use jQuery delegation via .on() | ||||||
|   function inner_view_processing(tab) { |   function inner_view_processing(tab) { | ||||||
|  |  | ||||||
|  |     // reload this table every 10 seconds | ||||||
|  |     if (tab == 'jobqueue') { | ||||||
|  |         $('#nd_device_name').text('10'); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('9') }, 1000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('8') }, 2000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('7') }, 3000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('6') }, 4000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('5') }, 5000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('4') }, 6000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('3') }, 7000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('2') }, 8000 ); | ||||||
|  |         setTimeout(function() { $('#nd_device_name').text('1') }, 9000 ); | ||||||
|  |         setTimeout(function() { | ||||||
|  |           $('#' + tab + '_form').trigger('submit'); | ||||||
|  |         }, 10000); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // activate typeahead on the topo boxes |     // activate typeahead on the topo boxes | ||||||
|     $('.nd_topo_dev').autocomplete({ |     $('.nd_topo_dev').autocomplete({ | ||||||
|       source: '/ajax/data/deviceip/typeahead' |       source: '/ajax/data/deviceip/typeahead' | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								TODO
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								TODO
									
									
									
									
									
								
							| @@ -1,14 +1,17 @@ | |||||||
| FRONTEND | FRONTEND | ||||||
| ======== | ======== | ||||||
|  |  | ||||||
| * UI for topo DB table editing |  | ||||||
| * No devices - trigger first discover splash page |  | ||||||
| * view job queue | * view job queue | ||||||
| * view logs with colour | * view logs with colour | ||||||
| * moar reports | * moar reports | ||||||
|  |  | ||||||
| * (jeneric) device module tab | * (jeneric) device module tab | ||||||
|  |  | ||||||
|  | BACKEND | ||||||
|  | ======= | ||||||
|  |  | ||||||
|  | * Job Delete handler | ||||||
|  |  | ||||||
| DAEMON | DAEMON | ||||||
| ====== | ====== | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user