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 )