From 00a23958a7530ac443cf45fd7d288488c4ff31cf Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Sun, 26 Jan 2014 13:35:53 +0000 Subject: [PATCH] use qstr in templates --- Netdisco/lib/App/Netdisco/DB/Result/Device.pm | 18 ++++++++++++++++++ .../DB/Result/Virtual/DuplexMismatch.pm | 13 +++++++++++++ Netdisco/lib/App/Netdisco/Web.pm | 6 ++++++ .../Netdisco/Web/Plugin/Device/Neighbors.pm | 7 +++++-- Netdisco/share/views/admintask.tt | 4 ++-- .../share/views/ajax/admintask/jobqueue.tt | 2 +- .../share/views/ajax/admintask/orphaned.tt | 4 ++-- .../share/views/ajax/admintask/pseudodevice.tt | 2 +- .../share/views/ajax/admintask/slowdevices.tt | 2 +- .../share/views/ajax/admintask/topology.tt | 4 ++-- .../ajax/admintask/undiscoveredneighbors.tt | 4 ++-- Netdisco/share/views/ajax/device/addresses.tt | 2 +- Netdisco/share/views/ajax/device/netmap.tt | 5 ++--- Netdisco/share/views/ajax/device/ports.tt | 11 ++++++----- .../views/ajax/report/apradiochannelpower.tt | 2 +- .../share/views/ajax/report/deviceaddrnodns.tt | 4 ++-- .../views/ajax/report/devicebylocation.tt | 2 +- .../share/views/ajax/report/duplexmismatch.tt | 4 ++-- Netdisco/share/views/ajax/report/halfduplex.tt | 2 +- .../share/views/ajax/report/ipinventory.tt | 2 +- .../views/ajax/report/phonesdiscovered.tt | 2 +- .../share/views/ajax/report/portadmindown.tt | 2 +- .../share/views/ajax/report/portblocking.tt | 2 +- .../share/views/ajax/report/portmultinodes.tt | 3 ++- .../share/views/ajax/report/portutilization.tt | 2 +- Netdisco/share/views/ajax/search/device.tt | 2 +- Netdisco/share/views/ajax/search/node_by_ip.tt | 2 +- .../share/views/ajax/search/node_by_mac.tt | 4 ++-- Netdisco/share/views/ajax/search/port.tt | 2 +- Netdisco/share/views/ajax/search/vlan.tt | 12 ++++++------ 30 files changed, 87 insertions(+), 46 deletions(-) diff --git a/Netdisco/lib/App/Netdisco/DB/Result/Device.pm b/Netdisco/lib/App/Netdisco/DB/Result/Device.pm index 19aefbc2..da8c0d0f 100644 --- a/Netdisco/lib/App/Netdisco/DB/Result/Device.pm +++ b/Netdisco/lib/App/Netdisco/DB/Result/Device.pm @@ -7,6 +7,8 @@ package App::Netdisco::DB::Result::Device; use strict; use warnings; +use URI::Escape; + use base 'DBIx::Class::Core'; __PACKAGE__->table("device"); __PACKAGE__->add_columns( @@ -273,4 +275,20 @@ Number of seconds which have elapsed since the value of C. sub since_last_arpnip { return (shift)->get_column('since_last_arpnip') } +=head1 ADDITIONAL METHODS + +=head2 qstr + +Returns a query string suitable for including in Netdisco URLs which will +reference this device. + +=cut + +sub qstr { + my $row = shift; + return sprintf 'q=%s&uuid=%s', + uri_escape($row->dns || $row->ip), + uri_escape($row->ip); +} + 1; diff --git a/Netdisco/lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm b/Netdisco/lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm index f476ed43..e19d1cc0 100644 --- a/Netdisco/lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm +++ b/Netdisco/lib/App/Netdisco/DB/Result/Virtual/DuplexMismatch.pm @@ -4,6 +4,7 @@ use strict; use warnings; use base 'DBIx::Class::Core'; +use URI::Escape; __PACKAGE__->table_class('DBIx::Class::ResultSource::View'); @@ -58,4 +59,16 @@ __PACKAGE__->add_columns( }, ); +sub left_qstr { + my $row = shift; + return sprintf 'q=%s&uuid=%s', + uri_escape($row->left_dns || $row->left_ip), uri_escape($row->left_ip); +} + +sub right_qstr { + my $row = shift; + return sprintf 'q=%s&uuid=%s', + uri_escape($row->right_dns || $row->right_ip), uri_escape($row->right_ip); +} + 1; diff --git a/Netdisco/lib/App/Netdisco/Web.pm b/Netdisco/lib/App/Netdisco/Web.pm index 548c3c36..1c2c8293 100644 --- a/Netdisco/lib/App/Netdisco/Web.pm +++ b/Netdisco/lib/App/Netdisco/Web.pm @@ -10,6 +10,7 @@ use Socket6 (); # to ensure dependency is met use HTML::Entities (); # to ensure dependency is met use URI::QueryParam (); # part of URI, to add helper methods use Path::Class 'dir'; +use URI::Escape; use App::Netdisco::Web::AuthN; use App::Netdisco::Web::Static; @@ -66,6 +67,11 @@ hook 'before_template' => sub { $tokens->{$_} = $tokens->{$_}->path_query for qw/search_node search_device device_ports/; + # precreated query string which uses uuid + $tokens->{qstr} = sprintf 'q=%s&uuid=%s', + uri_escape(param('q') || ''), + uri_escape(param('uuid') || param('q') || ''); + # allow very long lists of ports $Template::Directive::WHILE_MAX = 10_000; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm index dc283eba..15d56f20 100644 --- a/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/Device/Neighbors.pm @@ -6,6 +6,7 @@ use Dancer::Plugin::DBIC; use Dancer::Plugin::Auth::Extensible; use App::Netdisco::Web::Plugin; +use URI::Escape; register_device_tab({ tag => 'netmap', label => 'Neighbors' }); @@ -35,7 +36,8 @@ sub _add_children { push @legit, $c; push @{$ptr}, { name => _get_name($c), - fullname => (var('devices')->{$c} || $c), + qstr => (sprintf 'q=%s&uuid=%s', + uri_escape(var('devices')->{$c} || $c), uri_escape($c)), ip => $c, }; } @@ -93,7 +95,8 @@ get '/ajax/data/device/netmap' => require_login sub { my %tree = ( ip => $start, name => _get_name($start), - fullname => (var('devices')->{$start} || $start), + qstr => (sprintf 'q=%s&uuid=%s', + uri_escape(var('devices')->{$start} || $start), uri_escape($start)), children => [], ); diff --git a/Netdisco/share/views/admintask.tt b/Netdisco/share/views/admintask.tt index d6eb0ae9..aa3077e3 100644 --- a/Netdisco/share/views/admintask.tt +++ b/Netdisco/share/views/admintask.tt @@ -42,9 +42,9 @@ [% ELSIF task.tag == 'portlog' %] - [% params.q %] + [% params.q %] - - [% params.f %] + [% params.f %] [% ELSIF task.provides_csv %] diff --git a/Netdisco/share/views/ajax/admintask/jobqueue.tt b/Netdisco/share/views/ajax/admintask/jobqueue.tt index 7b4ae58c..def8606c 100644 --- a/Netdisco/share/views/ajax/admintask/jobqueue.tt +++ b/Netdisco/share/views/ajax/admintask/jobqueue.tt @@ -36,7 +36,7 @@ [% row.status.ucfirst | html_entity %] [% END %] [% row.device | html_entity %] + href="[% uri_for('/device') %]?q=[% row.device | uri %]&uuid=[% row.device | uri %]">[% row.device | html_entity %] [% row.port | html_entity %] [% row.subaction | html_entity %] [% row.username | html_entity %] diff --git a/Netdisco/share/views/ajax/admintask/orphaned.tt b/Netdisco/share/views/ajax/admintask/orphaned.tt index b87ebeec..9dec7c0c 100644 --- a/Netdisco/share/views/ajax/admintask/orphaned.tt +++ b/Netdisco/share/views/ajax/admintask/orphaned.tt @@ -22,7 +22,7 @@ [% FOREACH row IN orphans %] - + [% row.dns || row.name || row.ip | html_entity %] [% IF row.location %] @@ -75,7 +75,7 @@ [% FOREACH row IN network %] - + [% row.dns || row.name || row.ip | html_entity %] [% IF row.location %] diff --git a/Netdisco/share/views/ajax/admintask/pseudodevice.tt b/Netdisco/share/views/ajax/admintask/pseudodevice.tt index 38b63c93..54b20d15 100644 --- a/Netdisco/share/views/ajax/admintask/pseudodevice.tt +++ b/Netdisco/share/views/ajax/admintask/pseudodevice.tt @@ -21,7 +21,7 @@ [% SET count = count + 1 %] [% row.dns | html_entity %] + href="[% uri_for('/device') %]?[% row.qstr %]">[% row.dns | html_entity %] [% row.ip | html_entity %] diff --git a/Netdisco/share/views/ajax/admintask/slowdevices.tt b/Netdisco/share/views/ajax/admintask/slowdevices.tt index 219f3957..f403e87a 100644 --- a/Netdisco/share/views/ajax/admintask/slowdevices.tt +++ b/Netdisco/share/views/ajax/admintask/slowdevices.tt @@ -16,7 +16,7 @@ [% row.action.ucfirst | html_entity %] [% row.device | html_entity %] + href="[% uri_for('/device') %]?q=[% row.device | uri %]&uuid=[% row.device | uri %]">[% row.device | html_entity %] [% row.started | html_entity %] [% row.finished | html_entity %] [% row.elapsed | html_entity %] diff --git a/Netdisco/share/views/ajax/admintask/topology.tt b/Netdisco/share/views/ajax/admintask/topology.tt index 22ce1746..176db54f 100644 --- a/Netdisco/share/views/ajax/admintask/topology.tt +++ b/Netdisco/share/views/ajax/admintask/topology.tt @@ -42,11 +42,11 @@ [% WHILE (row = results.next) %] [% SET count = count + 1 %] - + [% (row.device1.dns || row.device1.name || row.device1.ip) | html_entity %] [% row.port1 | html_entity %] - + [% (row.device2.dns || row.device2.name || row.device2.ip) | html_entity %] [% row.port2 | html_entity %] diff --git a/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt b/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt index e3322589..aa5c9334 100644 --- a/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt +++ b/Netdisco/share/views/ajax/admintask/undiscoveredneighbors.tt @@ -10,9 +10,9 @@ [% FOREACH row IN results %] - + [% row.dns || row.name || row.ip | html_entity %] ( [% row.port | html_entity %] ) - + [% row.remote_ip | html_entity %] ([% row.remote_port | html_entity %]) [% ' id: '_ row.remote_id IF row.remote_id %] diff --git a/Netdisco/share/views/ajax/device/addresses.tt b/Netdisco/share/views/ajax/device/addresses.tt index 4eb5c3ea..e4f5c753 100644 --- a/Netdisco/share/views/ajax/device/addresses.tt +++ b/Netdisco/share/views/ajax/device/addresses.tt @@ -14,7 +14,7 @@ [% row.alias | html_entity %] [% row.dns | html_entity %] [% row.port | html_entity %] + href="[% device_ports %]&[% row.device.qstr %]&f=[% row.port | uri %]">[% row.port | html_entity %] [% row.device_port.name | html_entity %] [% row.subnet | html_entity %] diff --git a/Netdisco/share/views/ajax/device/netmap.tt b/Netdisco/share/views/ajax/device/netmap.tt index 10a2b452..523e33de 100644 --- a/Netdisco/share/views/ajax/device/netmap.tt +++ b/Netdisco/share/views/ajax/device/netmap.tt @@ -56,8 +56,7 @@ function to_class(name) { return 'nd_' + name.replace(/\./g, "_") } // handler for clicking on a circle - redirect to that device's netmap function circleClick(d) { - window.location = '[% uri_for('/device') %]?tab=netmap' - + '&q=' + d.fullname + window.location = '[% uri_for('/device') %]?tab=netmap&' + d.qstr + '&depth=[% params.depth | uri %]' + '&vlan=[% params.vlan | uri %]'; } @@ -92,7 +91,7 @@ $.getJSON('[% uri_for('/ajax/data/device/alldevicelinks') %]', function(data) { // draw the tree d3.json("[% uri_for('/ajax/data/device/netmap') %]?" - + '&q=[% params.q | uri %]' + + '[% qstr %]' + '&depth=[% params.depth | uri %]' + '&vlan=[% params.vlan | uri %]', function(error, root) { var tree = d3.layout.tree() diff --git a/Netdisco/share/views/ajax/device/ports.tt b/Netdisco/share/views/ajax/device/ports.tt index a3269099..e24db769 100644 --- a/Netdisco/share/views/ajax/device/ports.tt +++ b/Netdisco/share/views/ajax/device/ports.tt @@ -67,7 +67,7 @@ data-animation="" data-title="Click to Enable"> [% END %] + href="[% uri_for('/admin/portlog') %]?[% device.qstr %]&f=[% row.port | uri %]"> @@ -76,14 +76,14 @@ [% END %] + self_options) %]&[% qstr %]&f=[% row.port | uri %]&prefer=port"> [% IF row.is_master %]   [% END %] [% row.port | html_entity %] [% IF row.slave_of %]
+ self_options) %]&[% qstr %]&f=[% row.slave_of | uri %]&prefer=port"> [% row.slave_of | html_entity %] [% END %] @@ -240,7 +240,8 @@   [% END %] + self_options) %]&q=[% row.neighbor.dns || row.neighbor.ip | uri + %]&uuid=[% row.neighbor.ip | uri %]f=[% row.remote_port | uri %]&prefer=port"> [% row.neighbor.dns.remove(settings.domain_suffix) || row.neighbor.ip | html_entity %] [% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]
[% IF params.neigh_id and (row.remote_id or row.remote_type) %] @@ -254,7 +255,7 @@ [% ELSIF row.remote_type AND row.remote_type.match('(cisco\s+AIR-[L|C]?AP|-K9W8-|^AP:\s)') %]   [% END %] - + [% row.remote_ip | html_entity %] [% ' - ' IF row.remote_port %][% row.remote_port | html_entity %]
[% IF params.neigh_id and (row.remote_id or row.remote_type) %] diff --git a/Netdisco/share/views/ajax/report/apradiochannelpower.tt b/Netdisco/share/views/ajax/report/apradiochannelpower.tt index f8890ef5..41beb500 100644 --- a/Netdisco/share/views/ajax/report/apradiochannelpower.tt +++ b/Netdisco/share/views/ajax/report/apradiochannelpower.tt @@ -30,7 +30,7 @@ [% NEXT UNLESS p.channel # No channel port is admin down %] - + [% p.port | html_entity %] [% p.name %] [% p.descr %] diff --git a/Netdisco/share/views/ajax/report/deviceaddrnodns.tt b/Netdisco/share/views/ajax/report/deviceaddrnodns.tt index f572ccc0..1964284c 100644 --- a/Netdisco/share/views/ajax/report/deviceaddrnodns.tt +++ b/Netdisco/share/views/ajax/report/deviceaddrnodns.tt @@ -10,7 +10,7 @@ [% FOREACH row IN results %] - + [% row.dns || row.name || row.ip | html_entity %] [% row.alias | html_entity %] [% row.contact | html_entity %] @@ -18,4 +18,4 @@ [% END %] - \ No newline at end of file + diff --git a/Netdisco/share/views/ajax/report/devicebylocation.tt b/Netdisco/share/views/ajax/report/devicebylocation.tt index d7aa3b67..590fcc67 100644 --- a/Netdisco/share/views/ajax/report/devicebylocation.tt +++ b/Netdisco/share/views/ajax/report/devicebylocation.tt @@ -20,7 +20,7 @@ [Not Set] [% END %] - [% row.dns || row.ip | html_entity %] + [% row.dns || row.ip | html_entity %] [% row.name | html_entity %] diff --git a/Netdisco/share/views/ajax/report/duplexmismatch.tt b/Netdisco/share/views/ajax/report/duplexmismatch.tt index 1d9ad1c4..73635d20 100644 --- a/Netdisco/share/views/ajax/report/duplexmismatch.tt +++ b/Netdisco/share/views/ajax/report/duplexmismatch.tt @@ -14,13 +14,13 @@ [% row.left_dns || row.left_ip | html_entity %] + href="[% device_ports %]&[% row.left_qstr %]&f=[% row.left_port | uri %]&c_duplex=on"> [% row.left_port | html_entity %] [% row.left_duplex.ucfirst | html_entity %] [% row.right_dns || row.right_ip | html_entity %] + href="[% device_ports %]&[% row.right_qstr %]&f=[% row.right_port | uri %]&c_duplex=on"> [% row.right_port | html_entity %] [% row.right_duplex.ucfirst | html_entity %] diff --git a/Netdisco/share/views/ajax/report/halfduplex.tt b/Netdisco/share/views/ajax/report/halfduplex.tt index f5594292..6766d817 100644 --- a/Netdisco/share/views/ajax/report/halfduplex.tt +++ b/Netdisco/share/views/ajax/report/halfduplex.tt @@ -12,7 +12,7 @@ [% row.device.dns || row.device.ip | html_entity %] + href="[% device_ports %]&[% row.device.qstr %]&f=[% row.port | uri %]&c_duplex=on"> [% row.port | html_entity %] [% row.name | html_entity %] [% row.duplex.ucfirst | html_entity %] diff --git a/Netdisco/share/views/ajax/report/ipinventory.tt b/Netdisco/share/views/ajax/report/ipinventory.tt index d285fc4e..37bf912a 100644 --- a/Netdisco/share/views/ajax/report/ipinventory.tt +++ b/Netdisco/share/views/ajax/report/ipinventory.tt @@ -15,7 +15,7 @@ [% '  ' IF NOT row.active %] [% ELSIF row.time_last %] - [% row.ip | html_entity %] + [% row.ip | html_entity %] [% ELSE %] [% row.ip | html_entity %] diff --git a/Netdisco/share/views/ajax/report/phonesdiscovered.tt b/Netdisco/share/views/ajax/report/phonesdiscovered.tt index 73992a35..c33c92ba 100644 --- a/Netdisco/share/views/ajax/report/phonesdiscovered.tt +++ b/Netdisco/share/views/ajax/report/phonesdiscovered.tt @@ -14,7 +14,7 @@ [% row.dns || row.name || row.ip | html_entity %] + href="[% device_ports %]&[% row.qstr %]&f=[% row.port | uri %]"> [% row.port | html_entity %] [% row.remote_id | html_entity %] [% row.dns || row.name || row.ip | html_entity %] - + [% row.port | html_entity %] [% row.description | html_entity %] [% row.up_admin | html_entity %] diff --git a/Netdisco/share/views/ajax/report/portblocking.tt b/Netdisco/share/views/ajax/report/portblocking.tt index bd0e53e7..513bac8b 100644 --- a/Netdisco/share/views/ajax/report/portblocking.tt +++ b/Netdisco/share/views/ajax/report/portblocking.tt @@ -12,7 +12,7 @@ [% FOREACH row IN results %] [% row.dns || row.name || row.ip | html_entity %] - + [% row.port | html_entity %] [% row.description | html_entity %] [% row.stp | html_entity %] diff --git a/Netdisco/share/views/ajax/report/portmultinodes.tt b/Netdisco/share/views/ajax/report/portmultinodes.tt index a0c8cf9a..bfd9f97b 100644 --- a/Netdisco/share/views/ajax/report/portmultinodes.tt +++ b/Netdisco/share/views/ajax/report/portmultinodes.tt @@ -12,7 +12,8 @@ [% FOREACH row IN results %] [% row.dns || row.name || row.ip | html_entity %] - + [% row.port | html_entity %] [% row.description | html_entity %] [% row.mac_count | format_number %] diff --git a/Netdisco/share/views/ajax/report/portutilization.tt b/Netdisco/share/views/ajax/report/portutilization.tt index 3c981df0..f47a8795 100644 --- a/Netdisco/share/views/ajax/report/portutilization.tt +++ b/Netdisco/share/views/ajax/report/portutilization.tt @@ -11,7 +11,7 @@ [% WHILE (row = results.next) %] - [% row.dns || row.ip | html_entity %] + [% row.dns || row.ip | html_entity %] [% row.port_count %] [% row.ports_in_use %] [% row.ports_shutdown %] diff --git a/Netdisco/share/views/ajax/search/device.tt b/Netdisco/share/views/ajax/search/device.tt index 8ee38d10..eb9502e4 100644 --- a/Netdisco/share/views/ajax/search/device.tt +++ b/Netdisco/share/views/ajax/search/device.tt @@ -14,7 +14,7 @@ [% WHILE (row = results.next) %] - [% row.dns || row.ip | html_entity %] + [% row.dns || row.ip | html_entity %] [% row.contact | html_entity %] [% row.location | html_entity %] [% row.name | html_entity %] diff --git a/Netdisco/share/views/ajax/search/node_by_ip.tt b/Netdisco/share/views/ajax/search/node_by_ip.tt index 4bcfae55..57df5a3e 100644 --- a/Netdisco/share/views/ajax/search/node_by_ip.tt +++ b/Netdisco/share/views/ajax/search/node_by_ip.tt @@ -40,7 +40,7 @@ [% END %] Switch Port + href="[% device_ports %]&[% node.device.qstr %]&f=[% node.port | uri %]&c_nodes=on&c_neighbors=on"> [% node.switch | html_entity %] - [% node.port | html_entity %] [% '  ' IF NOT node.active %] [% IF node.device.dns AND node.device_port AND node.device_port.name %] diff --git a/Netdisco/share/views/ajax/search/node_by_mac.tt b/Netdisco/share/views/ajax/search/node_by_mac.tt index 5f9b9da7..ccc5a13c 100644 --- a/Netdisco/share/views/ajax/search/node_by_mac.tt +++ b/Netdisco/share/views/ajax/search/node_by_mac.tt @@ -70,7 +70,7 @@ [% END %] Switch Port + href="[% device_ports %]&[% node.device.qstr %]&f=[% node.port | uri %]&c_nodes=on&c_neighbors=on"> [% node.switch | html_entity %] - [% node.port | html_entity %] [% '  ' IF NOT node.active %] [% IF node.device.dns AND node.device_port AND node.device_port.name %] @@ -105,7 +105,7 @@ [% END %] Switch Port + href="[% device_ports %]&[% port.device.qstr %]&f=[% port.port | uri %]&c_mac=on&c_nodes=on&c_neighbors=on"> [% port.ip | html_entity %] - [% port.descr | html_entity %] [% IF port.device.dns AND port.name %] ([% port.device.dns | html_entity %] - [% port.name | html_entity %]) diff --git a/Netdisco/share/views/ajax/search/port.tt b/Netdisco/share/views/ajax/search/port.tt index 88416bb1..0703e54b 100644 --- a/Netdisco/share/views/ajax/search/port.tt +++ b/Netdisco/share/views/ajax/search/port.tt @@ -11,7 +11,7 @@ [% WHILE (row = results.next) %] [% row.name | html_entity %] - + [% row.ip | html_entity %] [ [% row.port | html_entity %] ] [% ' (' _ row.device.dns _ ')' IF row.device.dns %] diff --git a/Netdisco/share/views/ajax/search/vlan.tt b/Netdisco/share/views/ajax/search/vlan.tt index 8b128770..97ee1fdf 100644 --- a/Netdisco/share/views/ajax/search/vlan.tt +++ b/Netdisco/share/views/ajax/search/vlan.tt @@ -13,17 +13,17 @@ [% WHILE (row = results.next) %] [% row.vlan.vlan | html_entity %] + href="[% device_ports %]&[% row.sqtr %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.vlan | html_entity %] [% row.dns || row.ip | html_entity %] + href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.dns || row.ip | html_entity %] [% row.vlan.description | html_entity %] + href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.vlan.description | html_entity %] [% row.model | html_entity %] + href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.model | html_entity %] [% row.os | html_entity %] + href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.os | html_entity %] [% row.vendor | html_entity %] + href="[% device_ports %]&[% row.qstr %]&f=[% row.vlan.vlan | uri %]">[% row.vendor | html_entity %] [% END %]