Changeset 766

Show
Ignore:
Timestamp:
12/06/07 21:25:36 (1 year ago)
Author:
mart
Message:
  • Now uses Perlbal's spangly new API functions to make embedding nicer/safer
  • Uses Template Toolkit for the page, rather than hardcoding icky HTML into the Perl source.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/DJabberd-WebAdmin/lib/DJabberd/WebAdmin.pm

    r765 r766  
    1212package DJabberd::WebAdmin; 
    1313 
     14# Need 5.8 because we use PerlIO 
     15require 5.008; 
     16 
    1417use strict; 
    15 use Perlbal; 
     18use Perlbal; # FIXME: Once a release of Perlbal with the new API has actually been made, require that version explicitly here 
    1619use Perlbal::Plugin::Cgilike; 
     20use Symbol; 
     21use Template; 
    1722 
    1823use base qw(DJabberd::Plugin); 
     
    2126 
    2227my $server = undef; 
     28 
     29my $tt = Template->new({ 
     30    INCLUDE_PATH => 'templates', 
     31     
     32    START_TAG => quotemeta("[["), 
     33    END_TAG => quotemeta("]]"), 
     34    PRE_CHOMP => 2, # CHOMP_COLLAPSE 
     35    POST_CHOMP => 2, # CHOMP_COLLAPSE 
     36    RECURSION => 1, 
     37}); 
     38 
     39sub set_config_listenaddr { 
     40    my ($self, $addr) = @_; 
     41     
     42    $self->{listenaddr} = DJabberd::Util::as_bind_addr($addr); 
     43 
     44    # We default to localhost if no interface is specified 
     45    # User can explicitly say 0.0.0.0: to bind to everything. 
     46    $self->{listenaddr} = "127.0.0.1:".$self->{listenaddr} if $self->{listenaddr} =~ /^\d+$/; 
     47} 
    2348 
    2449sub finalize { 
    2550    my ($self) = @_; 
    26      
    27     # Configure a new service in Perlbal 
    28     my $ctx = Perlbal::CommandContext->new; 
    29     my $writer = sub { 
    30         $logger->info($_[0]); 
    31     }; 
    32      
    33     my $c = sub { 
    34         my ($line) = @_; 
    35         my $success = Perlbal::run_manage_command($line, $writer, $ctx); 
    36          
    37         unless ($success) { 
    38             $logger->logdie("Error configuring Perlbal service when running ".$line); 
    39         } 
    40     }; 
    41      
    42     $c->("LOAD cgilike"); 
    43     $c->("CREATE SERVICE djabberdadmin"); 
    44     $c->("SET listen = 127.0.0.1:8045"); 
    45     $c->("SET role = web_server"); 
    46     $c->("SET plugins = cgilike"); 
    47     $c->("PERLHANDLER = DJabberd::WebAdmin::handle_web_request"); 
    48     $c->("ENABLE djabberdadmin"); 
    49      
    50     # Now for a bit of yuck. 
    51     # Perlbal's not really designed to run in someone else's event loop, 
    52     # so we have to fake it out a bit and do some of the stuff it would 
    53     # otherwise have done in its main run() function. 
    54     # TODO: Make a nicer API for embedding Perlbal 
    55      
    56     $Perlbal::run_started = 1; 
    57     Perlbal::run_global_hook("pre_event_loop"); 
     51 
     52    $logger->logdie("No ListenAddr specified for WebAdmin") unless $self->{listenaddr}; 
     53 
     54    # We depend on the "cgilike" plugin 
     55    # FIXME: Should add a nice API to Perlbal for this 
     56    Perlbal::run_manage_command("LOAD cgilike", sub { $logger->info('[perlbal] '.$_[0]); }); 
     57     
     58    # Create an anonymous Perlbal service 
     59    my $pbsvc = Perlbal->create_service(); 
     60     
     61    $pbsvc->set('listen', $self->{listenaddr}); 
     62    $pbsvc->set('role', 'web_server'); 
     63    $pbsvc->set('plugins', 'cgilike'); 
     64     
     65    # It'd be good if there was a nicer API to do this, but whatever 
     66    $pbsvc->run_manage_command('PERLHANDLER = DJabberd::WebAdmin::handle_web_request'); 
     67     
     68    $pbsvc->enable(); 
     69     
     70    # Let Perlbal do any global initialization it needs to do. 
     71    Perlbal::initialize(); 
    5872     
    5973    # Hopefully by this point Perlbal's screwed around enough with Danga::Socket 
     
    7892    my ($r) = @_; 
    7993 
     94    my $path = $r->path; 
     95 
     96    # If the URL starts with /_/ then it's a static file request. 
     97    if ($path =~ m!^/_/(\w+)$!) { 
     98        my $resource_name = $1; 
     99        return handle_static_resource($r, $resource_name); 
     100    } 
     101    # which we just let Perlbal handle itself. 
     102    return Perlbal::Plugin::Cgilike::DECLINED if ($path =~ m!^/_/!); 
     103 
    80104    # All valid paths end with a slash 
    81105    # (because it makes it easier to construct relative links) 
    82     my $path = $r->path; 
    83106    if (substr($path, -1) ne '/') { 
    84107        $r->response_status_code(302); 
     
    90113    my $page = determine_page_for_request($r); 
    91114     
     115    unless (ref $page) { 
     116        # It's a string containing a relative URL to redirect to 
     117        $r->response_status_code(302); 
     118        $r->response_header('Location' => $path.$page); 
     119        print "..."; 
     120        return Perlbal::Plugin::Cgilike::HANDLED; 
     121    } 
     122     
    92123    if ($page) { 
    93124        output_page($r, $page); 
     
    101132} 
    102133 
     134sub handle_static_resource { 
     135    my ($r, $name) = @_; 
     136     
     137    my $fn = undef; 
     138    my $type = undef; 
     139     
     140    if ($name eq 'style') { 
     141        $fn = 'stat/style.css'; 
     142        $type = 'text/css'; 
     143    } 
     144    else { 
     145        $fn = 'stat/'.$name.'.png'; 
     146        $type = 'image/png'; 
     147    } 
     148 
     149    return 404 unless defined($fn) && -f $fn; 
     150     
     151    $r->response_header('Content-type' => $type); 
     152    $r->send_response_header(); 
     153     
     154    # FIXME: Should really add an API to Cgilike's $r for this, which can then use sendfile 
     155    # This is lame. 
     156     
     157    return 404 unless open (STATFILE, '<', $fn); 
     158     
     159    # FIXME: Really should to binmode() the fh underlying $r, but no nice API for this right now 
     160    #    and DJabberd doesn't work on Windows anyway. 
     161    binmode STATFILE; 
     162     
     163    my $buf = ""; 
     164    while (read(STATFILE, $buf, 1024)) { 
     165        print $buf; 
     166    } 
     167     
     168    close(STATFILE); 
     169     
     170    return Perlbal::Plugin::Cgilike::HANDLED; 
     171} 
     172 
    103173sub determine_page_for_request { 
    104174    my ($r) = @_; 
     
    118188     
    119189    if (scalar(@pathbits) == 0) { 
    120         return DJabberd::WebAdmin::Page::VHostSummary->new($vhost); 
     190        return "summary/"; 
     191    } 
     192     
     193    my $tabname = shift @pathbits; 
     194     
     195    if ($tabname eq 'summary') { 
     196        if (scalar(@pathbits) == 0) { 
     197            return DJabberd::WebAdmin::Page::VHostSummary->new($vhost); 
     198        } 
    121199    } 
    122200     
     
    133211sub output_page { 
    134212    my ($r, $page) = @_; 
    135      
     213 
    136214    my $title = $page->title; 
    137     print q{<html><head><title>}.($title ? ehtml($title)." - " : '').q{DJabberd Web Admin</title><body>}; 
    138      
    139     print "<h1>".ehtml($title)."</h1>"; 
    140  
    141     print "<div id='body'>"; 
    142     $page->print_body; 
    143     print "</div>"; 
    144  
    145     print "<div id='vhostselector'>"; 
    146     print "<h1>Configured VHosts</h1>"; 
    147     print "<ul>"; 
    148  
    149     $server->foreach_vhost(sub { 
    150         my $vhost = shift; 
    151         my $name = $vhost->server_name; 
    152         print "<li><a href='/".ehtml($name)."/'>".ehtml($name)."</a></li>"; 
    153     }); 
    154  
    155     print "</ul>"; 
    156     print "</div>"; 
    157  
    158     print q{</body></html>}; 
     215 
     216    my @pathbits = $r->path_segments; 
     217 
     218    my @tabs = ( 
     219        { 
     220            caption => 'Summary', 
     221            urlname => 'summary', 
     222        }, 
     223        { 
     224            caption => 'Client Sessions', 
     225            urlname => 'clients', 
     226        }, 
     227        { 
     228            caption => 'Server Sessions', 
     229            urlname => 'servers', 
     230        }, 
     231    ); 
     232 
     233    $tt->process('page.tt', { 
     234        section_title => $title ? $title : "DJabberd Web Admin", 
     235        page_title => 'Summary', 
     236        head_title => sub { ($title ? $title.' - ' : '')."DJabberd Web Admin"; }, 
     237        body => sub { return ${ capture_output(sub { $page->print_body; }) }; }, 
     238        tabs => [ 
     239            map { 
     240                { 
     241                    caption => $_->{caption}, 
     242                    url => '../'.$_->{urlname}.'/', 
     243                    current => ($pathbits[1] eq $_->{urlname} ? 1 : 0), 
     244                } 
     245            } @tabs 
     246        ], 
     247        vhosts => sub { 
     248            my @ret = (); 
     249            $server->foreach_vhost(sub { 
     250                my $vhost = shift; 
     251                my $name = $vhost->server_name; 
     252                push @ret, { 
     253                    hostname => $name, # The real hostname 
     254                    url => '/'.$name.'/summary/', # FIXME: should urlencode $name here 
     255                    name => $name, # Some display name (just the hostname for now) 
     256                    current => ($pathbits[0] eq $name ? 1 : 0), 
     257                }; 
     258            }); 
     259            return [ sort { $a->{name} cmp $b->{name} } @ret ]; 
     260        }, 
     261        djabberd_version => $DJabberd::VERSION, 
     262        perlbal_version => $Perlbal::VERSION, 
     263    }, $r); 
     264 
     265
     266 
     267sub capture_output { 
     268    my $sub = shift; 
     269     
     270    my $fh = Symbol::gensym(); 
     271    my $ret = ""; 
     272    open($fh, '>', \$ret); 
     273     
     274    my $oldfh = select($fh); 
     275     
     276    $sub->(@_); 
     277     
     278    select($oldfh); 
     279    close($fh); 
     280     
     281    return \$ret; 
    159282} 
    160283 
     
    228351    # FIXME: Should add some accessors to DJabberd::VHost to get this stuff, rather than 
    229352    #    grovelling around inside. 
    230     print "<h2>Client Sessions</h2>"; 
     353    print "<h3>Client Sessions</h3>"; 
    231354    print "<ul>"; 
    232355    foreach my $jid (keys %{$vhost->{jid2sock}}) { 
     
    236359    print "</ul>"; 
    237360     
    238     print "<h2>Plugins Loaded</h2>"; 
     361    print "<h3>Plugins Loaded</h3>"; 
    239362    print "<ul>"; 
    240363    foreach my $class (keys %{$vhost->{plugin_types}}) {