App-Netdisco
view release on metacpan or search on metacpan
lib/App/Netdisco/Web.pm view on Meta::CPAN
{ type => 'basic' },
};
$swagger_doc->{security} = [ { APIKeyHeader => [] } ];
if (setting('trust_x_remote_user')) {
foreach my $path (keys %{ $swagger_doc->{paths} }) {
foreach my $method (keys %{ $swagger_doc->{paths}->{$path} }) {
unshift @{ $swagger_doc->{paths}->{$path}->{$method}->{parameters} }, {
name => 'X-REMOTE_USER',
description => 'API client user name',
in => 'header',
required => false,
type => 'string',
};
}
}
}
# manually install Swagger UI routes because plugin doesn't handle non-root
# hosting, so we cannot use show_ui(1)
my $swagger_base = config->{plugins}->{Swagger}->{ui_url};
get $swagger_base => sub {
Dancer::Plugin::Swagger->instance->doc->{schemes} = [ request->scheme ];
redirect uri_for($swagger_base)->path . '/';
};
get $swagger_base.'/' => sub {
Dancer::Plugin::Swagger->instance->doc->{schemes} = [ request->scheme ];
# The ?url= this used to require was dropped with the guard that enforced
# it: the 5.x page resolves its own definition. Reinstating either alone
# makes the two routes redirect to each other, so they move together.
template 'swagger-ui', {}, { layout => undef };
};
# omg the plugin uses system_path and we don't want to go there
get $swagger_base.'/**' => sub {
Dancer::Plugin::Swagger->instance->doc->{schemes} = [ request->scheme ];
my @path = @{ (splat())[0] };
# Netdisco serves its own entry point, so nothing under swagger-ui/ answers
# for index.html. Answered rather than left to 404 because deployments and
# bookmarks link to it.
return redirect uri_for($swagger_base)->path . '/'
if @path == 1 and $path[0] eq 'index.html';
send_file( join '/', 'swagger-ui', @path );
};
# htmx applies a <title> found at the top level of a swapped response, the
# HX-Push-Url and HX-Replace-Url headers to the address bar, and an element
# carrying hx-swap-oob to whatever in the page has that id, so the chrome
# around a pane comes from the response rather than from the browser reading
# the page the fragment replaces.
hook 'after' => sub {
my $r = shift; # a Dancer::Response
# htmx sends this on every request it makes, and htmx is the only thing
# that acts on the title, so it is a closer guard than X-Requested-With.
# It also keeps the title away from the CSV download of a report and from
# the API endpoints, which forward to these same paths.
#
# Read from the PSGI environment because request->header cannot be relied
# on: Dancer::Request::is_ajax carries the same workaround, for headers
# that Plack::Builder leaves unset, and netdisco-web-fg builds its app
# that way.
return unless request->env->{'HTTP_HX_REQUEST'};
return unless $r->status and $r->status =~ m/^2\d\d$/;
# the pane routes alone. Their two-segment shape is what excludes the
# report data and connected-node endpoints, neither of which replaces a
# pane and both of which would set the title wrongly.
my ($page, $tab) =
(request->path =~ m{/ajax/content/(device|search|report|admin)/(\w+)$})
or return;
$r->header( pane_history_header($page, $tab) );
# A tenant URL reaches its pane by forward, and Dancer runs this hook once
# for the inner request and again for the response it rebuilds from it.
# Without this the pane carries two titles, htmx lifts only the first, and
# the second is swapped into the pane as an element, which also makes an
# empty result set stop looking empty; the chrome would swap twice over
# itself. The header above needs no guard: setting it twice leaves one
# header, where prepending twice leaves two of everything.
return if var('nd_fragment_chrome');
var('nd_fragment_chrome' => 1);
my $title = page_title($page, $tab) || '';
my $chrome = pane_chrome($page, $tab);
return unless length $title or length $chrome;
# htmx only lifts a title that is a direct child of the fragment, so this
# goes first and nothing may wrap it. The chrome follows for the same
# reason: an out-of-band element is swapped from the top level of the
# fragment, which is also where htmx 4 will want its <hx-partial>.
$r->content(
(length $title
? ('<title>'. HTML::Entities::encode_entities($title) .'</title>') : '')
. $chrome . ($r->content || ''));
};
# remove empty lines from CSV response
# this makes writing templates much more straightforward!
hook 'after' => sub {
my $r = shift; # a Dancer::Response
if ($r->content_type and $r->content_type eq 'text/comma-separated-values') {
my @newlines = ();
my @lines = split m/\n/, $r->content;
foreach my $line (@lines) {
push @newlines, $line if $line !~ m/^\s*$/;
}
$r->content(join "\n", @newlines);
}
};
#Â support for tenancies
# the segment is echoed back into the page as uri_base, which the layout and
# the SNMP panes emit unfiltered, so only a configured tag may set it.
sub _tenant_is_configured {
my $tenant = shift;
return 0 unless defined $tenant;
return scalar grep { defined $_ and $_ eq $tenant }
@{ setting('tenant_tags') || [] };
}
any qr{^/t/(?<tenant>[^/]+)/?$} => sub {
( run in 0.841 second using v1.01-cache-2.11-cpan-e623d60df62 )