diff --git a/Netdisco/Changes b/Netdisco/Changes index 155d9191..c4cb57dd 100644 --- a/Netdisco/Changes +++ b/Netdisco/Changes @@ -12,7 +12,9 @@ * Job Queue view and delete page * Empty device table prompts initial discover on homepage * Support for App::NetdiscoX::Web::Plugin namespace + * Plugins can add columns to Device Ports display * Observium Sparklines port column plugin + * Plugins can have CSS and Javascript loaded within
[ENHANCEMENTS] diff --git a/Netdisco/lib/App/Netdisco/Web.pm b/Netdisco/lib/App/Netdisco/Web.pm index 7c3221c0..98c885e7 100644 --- a/Netdisco/lib/App/Netdisco/Web.pm +++ b/Netdisco/lib/App/Netdisco/Web.pm @@ -10,6 +10,7 @@ use HTML::Entities (); # to ensure dependency is met use URI::QueryParam (); # part of URI, to add helper methods use App::Netdisco::Web::AuthN; +use App::Netdisco::Web::Static; use App::Netdisco::Web::Search; use App::Netdisco::Web::Device; use App::Netdisco::Web::Report; diff --git a/Netdisco/lib/App/Netdisco/Web/AuthN.pm b/Netdisco/lib/App/Netdisco/Web/AuthN.pm index e051071e..7e645fe9 100644 --- a/Netdisco/lib/App/Netdisco/Web/AuthN.pm +++ b/Netdisco/lib/App/Netdisco/Web/AuthN.pm @@ -18,6 +18,8 @@ hook 'before' => sub { if (session('user') && session->id) { var(user => schema('netdisco')->resultset('User') ->find(session('user'))); + var('user')->port_control(0) + if setting('no_port_control'); } }; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin.pm b/Netdisco/lib/App/Netdisco/Web/Plugin.pm index d83e4c26..37b147c8 100644 --- a/Netdisco/lib/App/Netdisco/Web/Plugin.pm +++ b/Netdisco/lib/App/Netdisco/Web/Plugin.pm @@ -4,6 +4,8 @@ use Dancer ':syntax'; use Dancer::Plugin; set( + '_additional_css' => [], + '_additional_javascript' => [], '_extra_device_port_cols' => [], '_navbar_items' => [], '_search_tabs' => [], @@ -30,6 +32,32 @@ register 'register_template_path' => sub { $path; }; +sub _register_include { + my ($type, $plugin) = @_; + + if (!length $type) { + error "bad type to _register_include"; + return; + } + + if (!length $plugin) { + error "bad plugin name to register_$type"; + return; + } + + push @{ setting("_additional_$type") }, $plugin; +} + +register 'register_css' => sub { + my ($self, $plugin) = plugin_args(@_); + _register_include('css', $plugin); +}; + +register 'register_javascript' => sub { + my ($self, $plugin) = plugin_args(@_); + _register_include('javascript', $plugin); +}; + register 'register_device_port_column' => sub { my ($self, $config) = plugin_args(@_); $config->{default} ||= ''; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm index a083be99..12ac58a7 100644 --- a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Ports.pm @@ -77,7 +77,7 @@ ajax '/ajax/content/device/ports' => sub { template 'ajax/device/ports.tt', { results => $results, nodes => $nodes_name, - device => $device->ip, + device => $device, }, { layout => undef }; }; diff --git a/Netdisco/lib/App/Netdisco/Web/Static.pm b/Netdisco/lib/App/Netdisco/Web/Static.pm new file mode 100644 index 00000000..0522386e --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Web/Static.pm @@ -0,0 +1,30 @@ +package App::Netdisco::Web::Static; + +use Dancer ':syntax'; +use Path::Class; + +get '/plugin/*/*.js' => sub { + my ($plugin) = splat; + + my $content = template + "plugin/$plugin/$plugin.js", {}, + { layout => undef }; + + send_file \$content, + content_type => 'application/javascript', + filename => "$plugin.js"; +}; + +get '/plugin/*/*.css' => sub { + my ($plugin) = splat; + + my $content = template + "plugin/$plugin/$plugin.css", {}, + { layout => undef }; + + send_file \$content, + content_type => 'text/css', + filename => "$plugin.css"; +}; + +true; diff --git a/Netdisco/lib/App/NetdiscoX/Web/Plugin/ObserviumSparklines.pm b/Netdisco/lib/App/NetdiscoX/Web/Plugin/ObserviumSparklines.pm index 09e4adf1..2d357226 100644 --- a/Netdisco/lib/App/NetdiscoX/Web/Plugin/ObserviumSparklines.pm +++ b/Netdisco/lib/App/NetdiscoX/Web/Plugin/ObserviumSparklines.pm @@ -5,11 +5,17 @@ use Dancer::Plugin::DBIC; use App::Netdisco::Web::Plugin; +use File::ShareDir 'dist_dir'; +use Path::Class; + register_device_port_column({ - name => 'c_observiumsparklines', + name => 'observiumsparklines', position => 'mid', label => 'Traffic', default => 'on', }); +register_css('observiumsparklines'); +register_javascript('observiumsparklines'); + true; diff --git a/Netdisco/share/public/css/jquery.qtip.min.css b/Netdisco/share/public/css/jquery.qtip.min.css new file mode 100644 index 00000000..d8fa8427 --- /dev/null +++ b/Netdisco/share/public/css/jquery.qtip.min.css @@ -0,0 +1 @@ +/*! qTip2 v2.0.1 (includes: svg ajax tips modal viewport imagemap ie6 / basic css3) | qtip2.com | Licensed MIT, GPL | Fri May 31 2013 12:51:04 */.qtip,.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:10.5px;line-height:12px;direction:ltr}.qtip-content{position:relative;padding:5px 9px;overflow:hidden;text-align:left;word-wrap:break-word}.qtip-titlebar{position:relative;padding:5px 35px 5px 10px;overflow:hidden;border-width:0 0 1px;font-weight:700}.qtip-titlebar+.qtip-content{border-top-width:0!important}.qtip-close{position:absolute;right:-9px;top:-9px;cursor:pointer;outline:medium none;border-width:1px;border-style:solid;border-color:transparent}.qtip-titlebar .qtip-close{right:4px;top:50%;margin-top:-9px}* html .qtip-titlebar .qtip-close{top:16px}.qtip-titlebar .ui-icon,.qtip-icon .ui-icon{display:block;text-indent:-1000em;direction:ltr;vertical-align:middle}.qtip-icon,.qtip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.qtip-icon .ui-icon{width:18px;height:14px;text-align:center;text-indent:0;font:normal bold 10px/13px Tahoma,sans-serif;color:inherit;background:transparent none no-repeat -100em -100em}.qtip-focus{}.qtip-hover{}.qtip-default{border-width:1px;border-style:solid;border-color:#F1D031;background-color:#FFFFA3;color:#555}.qtip-default .qtip-titlebar{background-color:#FFEF93}.qtip-default .qtip-icon{border-color:#CCC;background:#F1F1F1;color:#777}.qtip-default .qtip-titlebar .qtip-close{border-color:#AAA;color:#111}/*! Light tooltip style */.qtip-light{background-color:#fff;border-color:#E2E2E2;color:#454545}.qtip-light .qtip-titlebar{background-color:#f1f1f1}/*! Dark tooltip style */.qtip-dark{background-color:#505050;border-color:#303030;color:#f3f3f3}.qtip-dark .qtip-titlebar{background-color:#404040}.qtip-dark .qtip-icon{border-color:#444}.qtip-dark .qtip-titlebar .ui-state-hover{border-color:#303030}/*! Cream tooltip style */.qtip-cream{background-color:#FBF7AA;border-color:#F9E98E;color:#A27D35}.qtip-cream .qtip-titlebar{background-color:#F0DE7D}.qtip-cream .qtip-close .qtip-icon{background-position:-82px 0}/*! Red tooltip style */.qtip-red{background-color:#F78B83;border-color:#D95252;color:#912323}.qtip-red .qtip-titlebar{background-color:#F06D65}.qtip-red .qtip-close .qtip-icon{background-position:-102px 0}.qtip-red .qtip-icon{border-color:#D95252}.qtip-red .qtip-titlebar .ui-state-hover{border-color:#D95252}/*! Green tooltip style */.qtip-green{background-color:#CAED9E;border-color:#90D93F;color:#3F6219}.qtip-green .qtip-titlebar{background-color:#B0DE78}.qtip-green .qtip-close .qtip-icon{background-position:-42px 0}/*! Blue tooltip style */.qtip-blue{background-color:#E5F6FE;border-color:#ADD9ED;color:#5E99BD}.qtip-blue .qtip-titlebar{background-color:#D0E9F5}.qtip-blue .qtip-close .qtip-icon{background-position:-2px 0}.qtip-shadow{-webkit-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);-moz-box-shadow:1px 1px 3px 1px rgba(0,0,0,.15);box-shadow:1px 1px 3px 1px rgba(0,0,0,.15)}.qtip-rounded,.qtip-tipsy,.qtip-bootstrap{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.qtip-youtube{-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 3px #333;-moz-box-shadow:0 0 3px #333;box-shadow:0 0 3px #333;color:#fff;border-width:0;background:#4A4A4A;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0, #4A4A4A),color-stop(100%,black));background-image:-webkit-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-moz-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-ms-linear-gradient(top, #4A4A4A 0,black 100%);background-image:-o-linear-gradient(top, #4A4A4A 0,black 100%)}.qtip-youtube .qtip-titlebar{background-color:#4A4A4A;background-color:rgba(0,0,0,0)}.qtip-youtube .qtip-content{padding:.75em;font:12px arial,sans-serif;filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);-ms-filter:"progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#4a4a4a, EndColorStr=#000000);"}.qtip-youtube .qtip-icon{border-color:#222}.qtip-youtube .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-jtools{background:#232323;background:rgba(0,0,0,.7);background-image:-webkit-gradient(linear,left top,left bottom,from( #717171),to( #232323));background-image:-moz-linear-gradient(top, #717171, #232323);background-image:-webkit-linear-gradient(top, #717171, #232323);background-image:-ms-linear-gradient(top, #717171, #232323);background-image:-o-linear-gradient(top, #717171, #232323);border:2px solid #ddd;border:2px solid rgba(241,241,241,1);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 12px #333;-moz-box-shadow:0 0 12px #333;box-shadow:0 0 12px #333}.qtip-jtools .qtip-titlebar{background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171, endColorstr=#4A4A4A)"}.qtip-jtools .qtip-content{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A, endColorstr=#232323)"}.qtip-jtools .qtip-titlebar,.qtip-jtools .qtip-content{background:transparent;color:#fff;border:0 dashed transparent}.qtip-jtools .qtip-icon{border-color:#555}.qtip-jtools .qtip-titlebar .ui-state-hover{border-color:#333}.qtip-cluetip{-webkit-box-shadow:4px 4px 5px rgba(0,0,0,.4);-moz-box-shadow:4px 4px 5px rgba(0,0,0,.4);box-shadow:4px 4px 5px rgba(0,0,0,.4);background-color:#D9D9C2;color:#111;border:0 dashed transparent}.qtip-cluetip .qtip-titlebar{background-color:#87876A;color:#fff;border:0 dashed transparent}.qtip-cluetip .qtip-icon{border-color:#808064}.qtip-cluetip .qtip-titlebar .ui-state-hover{border-color:#696952;color:#696952}.qtip-tipsy{background:#000;background:rgba(0,0,0,.87);color:#fff;border:0 solid transparent;font-size:11px;font-family:'Lucida Grande',sans-serif;font-weight:700;line-height:16px;text-shadow:0 1px black}.qtip-tipsy .qtip-titlebar{padding:6px 35px 0 10;background-color:transparent}.qtip-tipsy .qtip-content{padding:6px 10}.qtip-tipsy .qtip-icon{border-color:#222;text-shadow:none}.qtip-tipsy .qtip-titlebar .ui-state-hover{border-color:#303030}.qtip-tipped{border:3px solid #959FA9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-color:#F9F9F9;color:#454545;font-weight:400;font-family:serif}.qtip-tipped .qtip-titlebar{border-bottom-width:0;color:#fff;background:#3A79B8;background-image:-webkit-gradient(linear,left top,left bottom,from( #3A79B8),to( #2E629D));background-image:-webkit-linear-gradient(top, #3A79B8, #2E629D);background-image:-moz-linear-gradient(top, #3A79B8, #2E629D);background-image:-ms-linear-gradient(top, #3A79B8, #2E629D);background-image:-o-linear-gradient(top, #3A79B8, #2E629D);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8, endColorstr=#2E629D)"}.qtip-tipped .qtip-icon{border:2px solid #285589;background:#285589}.qtip-tipped .qtip-icon .ui-icon{background-color:#FBFBFB;color:#555}.qtip-bootstrap{font-size:14px;line-height:20px;color:#333;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.qtip-bootstrap .qtip-titlebar{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.qtip-bootstrap .qtip-titlebar .qtip-close{right:11px;top:45%;border-style:none}.qtip-bootstrap .qtip-content{padding:9px 14px}.qtip-bootstrap .qtip-icon{background:transparent}.qtip-bootstrap .qtip-icon .ui-icon{width:auto;height:auto;float:right;font-size:20px;font-weight:700;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.qtip-bootstrap .qtip-icon .ui-icon:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}.qtip:not(.ie9haxors) div.qtip-content,.qtip:not(.ie9haxors) div.qtip-titlebar{filter:none;-ms-filter:none}.qtip .qtip-tip{margin:0 auto;overflow:hidden;z-index:10}.qtip .qtip-tip,.qtip .qtip-tip .qtip-vml{position:absolute;color:#123456;background:transparent;border:0 dashed transparent}.qtip .qtip-tip canvas{top:0;left:0}.qtip .qtip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}#qtip-overlay{position:fixed;left:-10000em;top:-10000em}#qtip-overlay.blurs{cursor:pointer}#qtip-overlay div{position:absolute;left:0;top:0;width:100%;height:100%;background-color:#000;opacity:.7;filter:alpha(opacity=70);-ms-filter:"alpha(Opacity=70)"}.qtipmodal-ie6fix{position:absolute!important} \ No newline at end of file diff --git a/Netdisco/share/public/javascripts/jquery.qtip.min.js b/Netdisco/share/public/javascripts/jquery.qtip.min.js new file mode 100644 index 00000000..eec2ea45 --- /dev/null +++ b/Netdisco/share/public/javascripts/jquery.qtip.min.js @@ -0,0 +1,2 @@ +/*! qTip2 v2.0.1 (includes: svg ajax tips modal viewport imagemap ie6 / basic css3) | qtip2.com | Licensed MIT, GPL | Fri May 31 2013 12:51:03 */ +(function(a,b,c){(function(a){"use strict",typeof define=="function"&&define.amd?define(["jquery"],a):jQuery&&!jQuery.fn.qtip&&a(jQuery)})(function(d){function G(c){v={pageX:c.pageX,pageY:c.pageY,type:"mousemove",scrollX:a.pageXOffset||b.body.scrollLeft||b.documentElement.scrollLeft,scrollY:a.pageYOffset||b.body.scrollTop||b.documentElement.scrollTop}}function H(a){var b=function(a){return a===g||"object"!=typeof a},c=function(a){return!d.isFunction(a)&&(!a&&!a.attr||a.length<1||"object"==typeof a&&!a.jquery&&!a.then)};if(!a||"object"!=typeof a)return f;b(a.metadata)&&(a.metadata={type:a.metadata});if("content"in a){if(b(a.content)||a.content.jquery)a.content={text:a.content};c(a.content.text||f)&&(a.content.text=f),"title"in a.content&&(b(a.content.title)&&(a.content.title={text:a.content.title}),c(a.content.title.text||f)&&(a.content.title.text=f))}return"position"in a&&b(a.position)&&(a.position={my:a.position,at:a.position}),"show"in a&&b(a.show)&&(a.show=a.show.jquery?{target:a.show}:{event:a.show}),"hide"in a&&b(a.hide)&&(a.hide=a.hide.jquery?{target:a.hide}:{event:a.hide}),"style"in a&&b(a.style)&&(a.style={classes:a.style}),d.each(u,function(){this.sanitize&&this.sanitize(a)}),a}function I(h,i,j,k){function O(a){var b=0,c,d=i,e=a.split(".");while(d=d[e[b++]])b