From ad1095752ea1d7c408c5d5d82d1d16ae3bb2bed6 Mon Sep 17 00:00:00 2001 From: petter Date: Fri, 12 Dec 2008 10:10:05 +0000 Subject: [PATCH] Initial checkin of a work in progress of the web GUI git-svn-id: svn+ssh://projects.linpro.no/svn/varnish/trunk@3461 d4fa192b-c00b-0410-8231-f00ffab90ce4 --- varnish-tools/webgui/README | 107 ++ varnish-tools/webgui/Varnish/Management.pm | 286 +++++ varnish-tools/webgui/Varnish/Node.pm | 128 +++ varnish-tools/webgui/Varnish/NodeManager.pm | 221 ++++ .../webgui/Varnish/RequestHandler.pm | 1005 +++++++++++++++++ varnish-tools/webgui/Varnish/Statistics.pm | 132 +++ varnish-tools/webgui/Varnish/Util.pm | 68 ++ varnish-tools/webgui/css/web.css | 189 ++++ varnish-tools/webgui/images/favicon.png | Bin 0 -> 863 bytes varnish-tools/webgui/images/nograph.png | Bin 0 -> 1036 bytes varnish-tools/webgui/images/running.png | Bin 0 -> 388 bytes varnish-tools/webgui/images/running_nok.png | Bin 0 -> 485 bytes varnish-tools/webgui/images/stopped.png | Bin 0 -> 415 bytes .../webgui/images/varnish-logo-christmas.png | Bin 0 -> 10647 bytes varnish-tools/webgui/images/varnish-logo.png | Bin 0 -> 4386 bytes varnish-tools/webgui/start.pl | 138 +++ .../webgui/templates/.master.tmpl.swp | Bin 0 -> 12288 bytes .../templates/configure_parameters.tmpl | 89 ++ varnish-tools/webgui/templates/edit_vcl.tmpl | 182 +++ .../webgui/templates/management_console.tmpl | 248 ++++ varnish-tools/webgui/templates/master.tmpl | 43 + .../webgui/templates/node_management.tmpl | 138 +++ .../webgui/templates/view_stats.tmpl | 99 ++ varnish-tools/webgui/test/test_management.pl | 41 + 24 files changed, 3114 insertions(+) create mode 100644 varnish-tools/webgui/README create mode 100644 varnish-tools/webgui/Varnish/Management.pm create mode 100644 varnish-tools/webgui/Varnish/Node.pm create mode 100644 varnish-tools/webgui/Varnish/NodeManager.pm create mode 100644 varnish-tools/webgui/Varnish/RequestHandler.pm create mode 100644 varnish-tools/webgui/Varnish/Statistics.pm create mode 100644 varnish-tools/webgui/Varnish/Util.pm create mode 100644 varnish-tools/webgui/css/web.css create mode 100644 varnish-tools/webgui/images/favicon.png create mode 100644 varnish-tools/webgui/images/nograph.png create mode 100644 varnish-tools/webgui/images/running.png create mode 100644 varnish-tools/webgui/images/running_nok.png create mode 100644 varnish-tools/webgui/images/stopped.png create mode 100644 varnish-tools/webgui/images/varnish-logo-christmas.png create mode 100644 varnish-tools/webgui/images/varnish-logo.png create mode 100755 varnish-tools/webgui/start.pl create mode 100644 varnish-tools/webgui/templates/.master.tmpl.swp create mode 100644 varnish-tools/webgui/templates/configure_parameters.tmpl create mode 100644 varnish-tools/webgui/templates/edit_vcl.tmpl create mode 100644 varnish-tools/webgui/templates/management_console.tmpl create mode 100644 varnish-tools/webgui/templates/master.tmpl create mode 100644 varnish-tools/webgui/templates/node_management.tmpl create mode 100644 varnish-tools/webgui/templates/view_stats.tmpl create mode 100644 varnish-tools/webgui/test/test_management.pl diff --git a/varnish-tools/webgui/README b/varnish-tools/webgui/README new file mode 100644 index 00000000..f0da2ab2 --- /dev/null +++ b/varnish-tools/webgui/README @@ -0,0 +1,107 @@ +Web GUI for Varnish, very limited christmas edition +=================================================== + +This is a preview of the upcoming web GUI for Varnish, which will be released with version 2.1 It has most of the features intended for the final version, but a number of things are still missing, so it is just inteded to get feedback. The following known things are _not_ implemented + - saving of state: if the web server goes down, the information is lost. + - smart data collection: it stores ALL the polled data with the given poll intervlal. It does not truncate data used for the hour, day, week and month graph. So eventually you will go out of memory. + - simple configuration: currently, you must edit the start.pl script by hand to set the configuration, and edit Varnish/RequestHandler.pl to add graphs and summary statistics. + - security: there is no form for access control or security implemented. + +So I wouldn't use it for anything with 'prod' in its name, but as a preview of what is to come. It should be noted that when adding nodes to an existing group, all the parameters and VCL of that node are replaced with the default parameters and VCL of the group. So if you add more nodes to a group, be aware of this. + +Any feedback is most welcome, so just post it to varnish-misc@projects.linpro.no. + +So what can you actually do with this thing? + + +Overview +-------- + +The web GUI is written in Perl and runs in its own server container, so a web server is not required, only the necessary Perl modules. The GUI consists of five sections: 'View stats', 'Configure parameters', 'Edit VCL', 'Node management' and 'Management console'. As the names might suggest, the GUI let you + +- view statistics from the Varnish nodes +- configure the parameters of the Varnish nodes and clusters +- edit the VCL for each cluster, which will be shared between all the nodes +- perform node management, like adding clusters and nodes, get the state of the nodes and backend healths +- get access to the management console of each node + +Requirements +------------ + +The web GUI is written in Perl, and uses some modules that might not be installed by default: + +- HTTP::Daemon (libwww-perl) +- HTML::Template (libhtml-template-perl) +- GD::Graph (libgd-graph-perl) +- LWP::UserAgent (libwww-perl) + +The name in the paranthesis are the package name on a standard Ubuntu system. The rest of the modules should be pretty standard. + +As the management port of the Varnish instances are used for collecting data, this must be enaled when starting varnish. This is done with the -T option to varnish, e.g. like this + +$ /opt/varnish/sbin/varnishd -a :80 -b :8080 -T :9003 + +Configuration +------------- + +The configuration is done directly in the Perl code for this very limited christmas edition, but will be more userfriendly in the final version. As this version doesn't save your state, e.g. the nodes and clusters added, I would recommend adding this in the start.pl. The files needing customisation are + +- start.pl: early in the file you'll see '# Configuration starts here', and this is where to set the config. It is all commented, so should be fairly straight forward. +- Varnish/RequestHandler.pl: this is the main enging of the whole web GUI, including the parts creating the summary stats and graphs. This is well documented in the code, so look at line 380 for adding values to the summary statistics and 826 for adding custom graphs (and removing the dummy 'Missing graph'). + +That is configuration for this version. Remember that when creating and editing VCLs, the information is stored on the node, so if the web server is restarted, the nodes will still have the VCLs (unless they are restarted too, of course). + +Starting it +----------- + +'perl start.pl' shoud do it, after reading the Configuration section and setting the values to something reasonable for your setup. If the shebang matches your perl, and the script is executable, './start.pl' should also do it. + +The GUI +====== + +View stats +---------- + +In 'View stats' you will see statistics gathered from the nodes. The 'Summary statistics' is/will be customisable (see Configuration) and shows the most important statistics. If you want to see all the statistics from the Varnish nodes you turn 'Raw statistics' on. If you want the page to be refreshed automatically in order to follow the graph 'live', you can turn 'Auto refresh' on. The refresh rate is the same as the rate the statistics are collected from the nodes. + +Configure parameters +-------------------- + +'Configure' parameters let you configure the parameters for a group (cluster) or a single node. For this version, the parameters for the groups are a copy of the parameters of the first node added to the group, so if a group has not been populated, it will not contain any values. Changing a value in a group will change the same value of all the nodes in that group. Changing a value for node only changes that node, naturally. + +Edit VCL +-------- + +'Edit VCL' lets you edit the VCL of the group, and any changes made here will be reflected on all the nodes of the group. You can add, edit, save and discard VCLs as well as making a VCL active. It discards without warning (isn't baby safe yet), so be carefull. + +A note about the editor: it is a simple text are, with giving you an indent as expected. If you save a VCL with error, the errors are listed and you can click on the 'Line X Pos Y' information in the error list to jump to that position in the editor. + + +Node management +--------------- + +From 'Node management' you can manage groups and nodes. It will let you add, remove, stop and start the nodes and groups. It also displays information about the state of the node: + - the V column is the state of Varnish. Green means it responds with a 200 message to a probe (it probes http://varnish-host/), a yellow light means it anwers with a non-200 status code and a red light means it does not respond at all. + - the M columns is the state of the management port, with green meaning OK and red meaning it is not reachable. As almost all functionality is dependent on the management port, it should never have a red light. + + If a health probe is defined in the VCL, a list of backend healths for the running VCL is shown as well. + + Since this version does not let you save the configuration, it is recommended that you add groups and nodes in the stat.pl file as explained in Configuration. + + WARNING: as noted earlier, adding nodes to a group will replace that nodes parameter and VCL. + +Management console +------------------ + +'Management console' gives you access to the management console of each node. The input field has magical tab completion for the CLI commands and a command history accessable by arrow up/down. In addition to the standard CLI commands, 'cls' can be issued to clear the console. + +The color and size of the console can be changed, but this information is unfortunately not stored when switching nodes. + + +Road ahead +========== + +This is a sneak preview of the web GUI for 2.1, and as such is not complete. It is mostly feature complete (minus things mentioned in the start of this file), so any comments on what is working, what is lacking etc. is most appreciated. Please use the varnish-misc@projects.linpro.no mailing list for discussion. + + +Merry christmas! diff --git a/varnish-tools/webgui/Varnish/Management.pm b/varnish-tools/webgui/Varnish/Management.pm new file mode 100644 index 00000000..1ce373df --- /dev/null +++ b/varnish-tools/webgui/Varnish/Management.pm @@ -0,0 +1,286 @@ +package Varnish::Management; + +use strict; +use IO::Socket::INET; +use Exporter; +use List::Util qw(first); + +{ + my %hostname_of; + my %port_of; + my %error_of; + my %socket_of; + + sub new { + my ($class, $hostname, $port) = @_; + + my $new_object = bless \do{ my $anon_scalar; }, $class; + + $hostname_of{$new_object} = $hostname; + $port_of{$new_object} = $port; + $error_of{$new_object} = ""; + + return $new_object; + } + + sub _send_command { + my ($self, $command) = @_; + + if (!$socket_of{$self} || !$socket_of{$self}->connected ) { + my $socket = new IO::Socket::INET->new( + PeerPort => $port_of{$self}, + Proto => 'tcp', + PeerAddr => $hostname_of{$self} + ); + return ("666", "Could not connect to node") if (!$socket); + $socket_of{$self} = $socket; + } + my $socket = $socket_of{$self}; + + print $socket "$command\n"; + my ($status_code, $response_size) = <$socket> =~ m/^(\d+) (\d+)/; + my $response; + my $remaining_bytes = $response_size; + while ($remaining_bytes > 0 ) { + my $data; + my $read = read $socket, $data, $remaining_bytes; + $response .= $data; + $remaining_bytes -= $read; + } + my $eat_newline = <$socket>; + return ($status_code, $response); + } + + sub send_command { + my ($self, $command) = @_; + + my ($status_code, $response) = _send_command($self, $command); + + return no_error($self, $response) if $status_code eq "200"; + return set_error($self, $response); + } + + sub get_parameters { + my ($self) = @_; + + my %param; + my $current_param; + + my ($status_code, $response) = _send_command($self, "param.show -l"); + return set_error($self, $response) if ($status_code ne "200"); + for my $line (split( '\n', $response)) { + + if ($line =~ /^(\w+)\s+(\w+) (.*)$/) { + my %param_info = ( + value => $2, + unit => $3 + ); + + $current_param = $1; + $param{$1} = \%param_info; + } + elsif ($line =~ /^\s+(.+)$/) { +# The first comment line contains no . and describes the default value. + if (!$param{$current_param}->{'description'}) { + $param{$current_param}->{'description'} = "$1. "; + } + else { + $param{$current_param}->{'description'} .= "$1 "; + } + } + } + + return \%param; + } + + sub get_parameter($) { + my ($self, $parameter) = @_; + + my ($status_code, $response) = _send_command($self, "param.show $parameter"); + + return no_error($self, $1) if ($response =~ /^(?:\w+)\s+(\w+)/); + return set_error($self, $response); + } + + sub set_parameter { + my ($self, $parameter, $value) = @_; + + my ($status_code, $response) = _send_command($self, "param.set $parameter $value"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub get_vcl_names { + my ($self) = @_; + + my ($status_code, $response) = _send_command($self, "vcl.list"); + return set_error($self, $response) if ($status_code ne "200"); + + my @vcl_infos = ($response =~ /^(\w+)\s+\d+\s+(\w+)$/gm); + my $vcl_names_ref = []; + my $active_vcl_name = ""; + while (my ($status, $name) = splice @vcl_infos, 0, 2) { + next if ($status eq "discarded"); + + if ($status eq "active") { + $active_vcl_name = $name; + } + push @$vcl_names_ref, $name; + } + + unshift @$vcl_names_ref, $active_vcl_name; + return no_error($self, $vcl_names_ref) if ($status_code eq "200"); + } + + sub get_vcl { + my ($self, $vcl_name) = @_; + + my ($status_code, $response) = _send_command($self, "vcl.show $vcl_name"); + + return no_error($self, $response) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub set_vcl { + my ($self, $vcl_name, $vcl) = @_; + $vcl =~ s/"/\\"/g; + $vcl =~ s/\r//g; + $vcl =~ s/\n/\\n/g; + + my $need_restart = 0; + my ($active_vcl_name, @vcl_names) = @{get_vcl_names($self)}; + my $editing_active_vcl = $vcl_name eq $active_vcl_name; + + # try to compile the new vcl + my ($status_code, $response) = _send_command($self, "vcl.inline _new_vcl \"$vcl\""); + if ($status_code ne "200") { + _send_command($self, "vcl.discard _new_vcl"); + return set_error($self, $response); + } + + if ($editing_active_vcl) { + ($status_code, $response) = _send_command($self, "vcl.use _new_vcl"); + } + + if (grep { $_ eq $vcl_name } @vcl_names) { + ($status_code, $response) = _send_command($self, "vcl.discard $vcl_name"); + if ($status_code ne "200") { + _send_command($self, "vcl.use $vcl_name"); + _send_command($self, "vcl.discard _new_vcl"); + return set_error($self, $response); + } + } + ($status_code, $response) = _send_command($self, "vcl.inline $vcl_name \"$vcl\""); + + if ($editing_active_vcl) { + ($status_code, $response) = _send_command($self, "vcl.use $vcl_name"); + } + _send_command($self, "vcl.discard _new_vcl"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub discard_vcl { + my ($self, $vcl_name) = @_; + + my ($status_code, $response) = _send_command($self, "vcl.discard $vcl_name"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub make_vcl_active { + my ($self, $vcl_name) = @_; + + my ($status_code, $response) = _send_command($self, "vcl.use $vcl_name"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub get_stats { + my ($self) = @_; + + my ($status_code, $response) = _send_command($self, "stats"); + + my %stat_counter = map { + /^\s*(\d+)\s+(.*?)$/; + $2 => $1 + } split /\n/, $response; + + return no_error($self, \%stat_counter) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub ping { + my ($self) = @_; + + my ($status_code, $response) = _send_command($self, "stats"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub start { + my ($self) = @_; + + my ($status_code, $response) = _send_command($self, "start"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub stop { + my ($self) = @_; + + my ($status_code, $response) = _send_command($self, "stop"); + + return no_error($self) if ($status_code eq "200"); + return set_error($self, $response); + } + + sub set_error { + my ($self, $error) = @_; + + $error_of{$self} = $error; + + return; + } + + sub get_error { + my ($self) = @_; + + return $error_of{$self}; + } + + sub no_error { + my ($self, $return_value) = @_; + + $error_of{$self} = ""; + + return defined($return_value) ? $return_value : 1; + } + + sub close { + my ($self) = @_; + + if ($socket_of{$self} && $socket_of{$self}->connected) { + $socket_of{$self}->close(); + } + } + + sub get_backend_health { + my ($self) = @_; + + my ($status_code, $response) = _send_command($self, "debug.health"); + return set_error($self, $response) if ($status_code ne "200"); + + my %backend_health = ($response =~ /^Backend (\w+) is (\w+)$/gm); + + return no_error($self, \%backend_health); + } +} + +1; diff --git a/varnish-tools/webgui/Varnish/Node.pm b/varnish-tools/webgui/Varnish/Node.pm new file mode 100644 index 00000000..b42160d3 --- /dev/null +++ b/varnish-tools/webgui/Varnish/Node.pm @@ -0,0 +1,128 @@ +package Varnish::Node; + +use strict; +use LWP::UserAgent; +use Varnish::Management; + +{ + my %name_of; + my %address_of; + my %port_of; + my %group_of; + my %management_of; + my %management_port_of; + my %is_master_of; + my %node_id_of; + + my $next_node_id = 1; + + sub new { + my ($class, $arg_ref) = @_; + + my $new_object = bless \do{ my $anon_scalar; }, $class; + + $name_of{$new_object} = $arg_ref->{'name'}; + $address_of{$new_object} = $arg_ref->{'address'}; + $port_of{$new_object} = $arg_ref->{'port'}; + $group_of{$new_object} = $arg_ref->{'group'}; + $management_port_of{$new_object} = $arg_ref->{'management_port'}; + $management_of{$new_object} = Varnish::Management->new($arg_ref->{'address'}, + $arg_ref->{'management_port'}); + $is_master_of{$new_object} = 0; + $node_id_of{$new_object} = $next_node_id++; + + return $new_object; + } + + sub get_id { + my ($self) = @_; + + return $node_id_of{$self}; + } + + sub get_name { + my ($self) = @_; + + return $name_of{$self}; + } + + sub get_address { + my ($self) = @_; + + return $address_of{$self}; + } + + sub get_port { + my ($self) = @_; + + return $port_of{$self}; + } + + sub get_group { + my ($self) = @_; + + return $group_of{$self}; + } + + sub get_management { + my ($self) = @_; + + return $management_of{$self}; + } + + sub get_management_port { + my ($self) = @_; + + return $management_port_of{$self}; + } + + sub is_master { + my ($self) = @_; + + return $is_master_of{$self}; + } + + sub set_master { + my ($self, $master) = @_; + + $is_master_of{$self} = $master; + } + + sub is_running_ok { + my ($self) = @_; + + my $user_agent = LWP::UserAgent->new; + $user_agent->timeout(1); + + my $url = 'http://' . get_address($self) . ':' . get_port($self); + my $response = $user_agent->head($url); + return $response->is_success; + } + + sub is_running { + my ($self) = @_; + + my $user_agent = LWP::UserAgent->new; + $user_agent->timeout(1); + + my $url = 'http://' . get_address($self) . ':' . get_port($self); + my $response = $user_agent->head($url); + return $response->code != 500; + } + + + sub is_management_running { + my ($self) = @_; + + my $management = get_management($self); + if ($management) { + my $ping = $management->ping(); + return defined($ping) && $ping; + } + else { + return 0; + } + } +} + +1; diff --git a/varnish-tools/webgui/Varnish/NodeManager.pm b/varnish-tools/webgui/Varnish/NodeManager.pm new file mode 100644 index 00000000..4878f82d --- /dev/null +++ b/varnish-tools/webgui/Varnish/NodeManager.pm @@ -0,0 +1,221 @@ +package Varnish::NodeManager; +use strict; +use warnings; +use Varnish::Node; +use List::Util qw(first); + +{ + my @groups = (); + my @nodes = (); + my %group_parameters = (); + + my $error = ""; + + sub add_node { + my ($self, $node) = @_; + + my $group = $node->get_group(); + if (! grep { $_->get_group eq $group } @nodes ) { + $node->set_master(1); + my %node_parameters = %{$node->get_management()->get_parameters()}; + while (my ($parameter, $value) = each %node_parameters) { + $group_parameters{$group}->{$parameter} = $value; + } + } + else { +# inherit the VCL and the parameters of the group + my %group_parameters = %{$group_parameters{$group}}; + my $management = $node->get_management(); + while (my ($parameter, $value) = each %group_parameters) { + $management->set_parameter($parameter, $value->{'value'}); + } + + my $vcl_names_ref = $management->get_vcl_names(); + my $active_vcl_name; + my @vcl_names; + if ($vcl_names_ref) { + @vcl_names = @{$vcl_names_ref}; + $active_vcl_name = shift @vcl_names; + } + + for my $vcl_name (@vcl_names) { + if ($vcl_name ne $active_vcl_name) { + $management->discard_vcl($vcl_name); + } + } + + my $discard_active_vcl = 1; + my $group_master = first { + $_->get_group() eq $group + && $_->is_master() + } @nodes; + my $master_management = $group_master->get_management(); + my $master_active_vcl_name; + my @master_vcl_names; + my $master_vcl_names_ref = $group_master->get_management()->get_vcl_names(); + if ($master_vcl_names_ref) { + @master_vcl_names = @{$master_vcl_names_ref}; + $master_active_vcl_name = shift @master_vcl_names; + } + + for my $vcl_name (@master_vcl_names) { + my $vcl = $master_management->get_vcl($vcl_name); + $management->set_vcl($vcl_name, $vcl); + + if ($vcl_name eq $master_active_vcl_name) { + $management->make_vcl_active($vcl_name); + } + if ($vcl_name eq $active_vcl_name) { + $discard_active_vcl = 0; + } + } + + if ($discard_active_vcl) { + $management->discard_vcl($active_vcl_name); + } + } + + push @nodes, $node; + } + + sub remove_node { + my ($self, $node) = @_; + + if ($node) { + @nodes = grep { $_ != $node } @nodes; + + if ($node->is_master()) { + my $new_master = first { + $_->is_master + && $_->get_group() eq $node->get_group() + } @nodes; + if ($new_master) { + $new_master->set_master(1); + } + } + } + } + + sub get_node { + my ($self, $node_id) = @_; + + my $node = first { + $_->get_id() == $node_id + } @nodes; + + return $node; + } + + sub add_group { + my ($self, $name) = @_; + + push @groups, $name; + } + + sub remove_group { + my ($self, $name) = @_; + + @groups = grep { $_ ne $name } @groups; + my @nodes_to_remove = grep { $_->get_group() eq $name } @nodes; + for my $node (@nodes_to_remove) { + remove_node($self, $node); + } + } + + sub get_groups { + + return @groups; + } + + sub get_nodes { + + return @nodes; + } + + sub get_nodes_for_group { + my ($self, $group) = @_; + + return grep { $_->get_group() eq $group } @nodes; + } + + sub get_group_masters { + my ($self) = @_; + + return grep { $_->is_master() } @nodes; + } + + sub load { + + + } + + sub save { + my ($self) = @_; + + } + + sub quit { + my ($self) = @_; + + for my $node (@nodes) { + my $management = $node->get_management(); + if ($management) { + $management->close(); + } + } + + save($self); + } + + sub set_error { + my ($self, $new_error) = @_; + + $error = $new_error; + + return; + } + + sub get_error { + my ($self) = @_; + + return $error; + } + + sub no_error { + my ($self, $return_value) = @_; + + $error = ""; + + return defined($return_value) ? $return_value : 1; + } + + sub set_group_parameter { + my ($self, $group, $parameter, $value) = @_; + + my $error; + + $group_parameters{$group}->{$parameter}->{'value'} = $value; + my @nodes_in_group = grep { $_->get_group() eq $group } @nodes; + for my $node (@nodes_in_group) { + my $management = $node->get_management(); + if (!$management->set_parameter($parameter, $value)) { + $error .= $management->get_error() . "\n"; + } + } + + if ($error) { + return set_error($self, $error); + } + else { + return no_error(); + } + } + + sub get_group_parameters { + my ($self, $group) = @_; + + return $group_parameters{$group}; + } +} + +1; diff --git a/varnish-tools/webgui/Varnish/RequestHandler.pm b/varnish-tools/webgui/Varnish/RequestHandler.pm new file mode 100644 index 00000000..89432151 --- /dev/null +++ b/varnish-tools/webgui/Varnish/RequestHandler.pm @@ -0,0 +1,1005 @@ +package Varnish::RequestHandler; + +use strict; +use warnings; +use HTML::Template; +use HTTP::Request; +use Varnish::Util; +use Varnish::Management; +use Varnish::NodeManager; +use Varnish::Node; +use Varnish::Statistics; +use URI::Escape; +use GD::Graph::lines; +use POSIX qw(strftime); +use List::Util qw(first); + +{ + my %request_ref_of; + my %response_content_ref_of; + my %response_header_ref_of; + my %master_tmpl_var_of; + + sub new { + my ($class, $request_ref, $connection) = @_; + + my $new_object = bless \do{ my $anon_scalar; }, $class; + + $request_ref_of{$new_object} = $request_ref; + $response_content_ref_of{$new_object} = \""; + $response_header_ref_of{$new_object} = {}; + + $master_tmpl_var_of{$new_object}->{'server_host'} = $connection->sockhost(); + $master_tmpl_var_of{$new_object}->{'server_port'} = $connection->sockport(); + + return $new_object; + } + + sub DESTROY { + my ($self) = @_; + + delete $request_ref_of{$self}; + + return; + } + + sub get_response_header { + my ($self) = @_; + + return $response_header_ref_of{$self}; + } + sub get_response_content { + my ($self) = @_; + + return ${$response_content_ref_of{$self}}; + } + + sub _parse_request_parameters { + my ($content_ref) = @_; + + my %parameter = (); + for my $pair (split /&/,$$content_ref) { + my ($key,$value) = split /=/,$pair; + $value = uri_unescape($value); + $value =~ s/\+/ /g; + $parameter{$key} = $value; + } + + return %parameter; + } + + sub process { + my ($self) = @_; + + my $request = ${$request_ref_of{$self}}; + my $operation = $request->uri(); + my $content = $request->content(); + + $operation =~ s:^/::; + if ($operation =~ /^(.*?)\?(.*)/) { + $operation = $1; + $content = $2; + } + + my $content_template; + my $response_content; + my %request_parameter = _parse_request_parameters(\$content); + +# while (my ($k, $v) = each %request_parameter) { +# print "$k => $v\n"; +# } + + my $param; + if ($operation eq 'view_stats' || $operation eq '') { + ($content_template, $param) = view_stats(\%request_parameter); + } + elsif ($operation eq 'configure_parameters') { + ($content_template, $param) = configure_parameters(\%request_parameter); + } + elsif ($operation eq 'edit_vcl') { + ($content_template, $param) = edit_vcl(\%request_parameter); + } + elsif ($operation eq 'node_management') { + ($content_template, $param) = node_management(\%request_parameter); + } + elsif ($operation eq 'management_console') { + ($content_template, $param) = management_console(\%request_parameter); + } + elsif ($operation eq 'send_management_command') { + $response_content = send_management_command(\%request_parameter); + } + elsif ($operation eq 'generate_graph') { + $response_header_ref_of{$self}->{'Content-Type'} = "image/png"; + $response_content = generate_graph(\%request_parameter); + if (!$response_content) { + $response_content = read_file('images/nograph.png'); + } + } + elsif ($operation eq 'collect_data') { + Varnish::Statistics->collect_data(); + $response_content = "Ok"; + } + else { + return; + } + + if ($content_template) { + my $template_text = read_file("templates/master.tmpl"); + $template_text =~ s/CONTENT_TEMPLATE/$content_template/; + + my $template = HTML::Template->new_scalar_ref( \$template_text, + die_on_bad_params => 0); + + my $tmpl_var = $master_tmpl_var_of{$self}; + if ($param) { + while (my ($parameter, $value) = each %{$param}) { + $tmpl_var->{$parameter} = $value; + } + } + $template->param($tmpl_var); + $response_content = $template->output; + } + $response_content_ref_of{$self} = \$response_content; + } + + sub edit_vcl { + my ($parameter_ref) = @_; + + my %param = %{$parameter_ref}; + $param{'vcl'} ||= ""; + $param{'operation'} ||= "load"; + $param{'group_name'} ||= ""; + $param{'vcl_name'} ||= ""; + $param{'new_vcl_name'} ||= ""; + + + my $template = "edit_vcl.tmpl"; + my %tmpl_var; + $tmpl_var{'error'} = ""; + $tmpl_var{'vcl_name'} = $param{'vcl_name'}; + $tmpl_var{'status'} = ""; + $tmpl_var{'vcl_infos'} = []; + $tmpl_var{'group_infos'} = []; + $tmpl_var{'vcl_error'} = ""; + $tmpl_var{'vcl'} = ""; + + my $successfull_save = 0; + my $editing_new_vcl = 0; + + my @group_masters = Varnish::NodeManager->get_group_masters(); + if ($param{'operation'} eq "make_active") { + my $group_master = first { + $_->get_group() eq $param{'group_name'}; + } @group_masters; + + if ($group_master) { + my $management = $group_master->get_management(); + if ($management->make_vcl_active($param{'vcl_name'})) { + my @nodes = Varnish::NodeManager->get_nodes(); + for my $node (@nodes) { + if ($node != $group_master && $node->get_group() eq $param{'group_name'}) { + $node->get_management()->make_vcl_active($param{'vcl_name'}); + } + } + $tmpl_var{'status'} = "VCL activated successfully"; + } + else { + $tmpl_var{'error'} .= "Error activating configuration:\n" . $management->get_error() . "\n"; + } + } + } + elsif ($param{'operation'} eq 'new') { + if ($param{'new_vcl_name'}) { + $tmpl_var{'vcl_name'} = $param{'new_vcl_name'}; + $tmpl_var{'vcl'} = ""; + push @{$tmpl_var{'vcl_infos'}}, { + name => $param{'new_vcl_name'}, + selected => 1, + active => 0 + }; + $editing_new_vcl = 1; + } + } + elsif ($param{'operation'} eq 'save') { + my $group_master = first { + $_->get_group() eq $param{'group_name'} + } @group_masters; + + if ($group_master && $param{'vcl'} ne "" && $param{'vcl_name'} ne "") { + my $master_management = $group_master->get_management(); + if ($master_management->set_vcl($param{'vcl_name'}, $param{'vcl'})) { + my @nodes = Varnish::NodeManager->get_nodes(); + for my $node (@nodes) { + if ($node != $group_master && $node->get_group() eq $param{'group_name'}) { + my $management = $node->get_management(); + if (!$management->set_vcl($param{'vcl_name'}, $param{'vcl'})) { + $tmpl_var{'error'} .= "Error saving configuration for " . $node->get_name() + . ":\n" . $management->get_error() . "\n"; + } + } + } + $successfull_save = 1; + $tmpl_var{'status'} = "VCL saved successfully"; + } + else { + push @{$tmpl_var{'vcl_infos'}}, { + name => $param{'vcl_name'}, + selected => 1, + active => 0 + }; + $editing_new_vcl = 1; + $tmpl_var{'vcl'} = $param{'vcl'}; + my $vcl_error = $master_management->get_error(); + # it is bad bad bad mixing presentation and code, I know, but sometimes you have to + $vcl_error =~ s/Line (\d+) Pos (\d+)/$&<\/a>/g; + $vcl_error =~ s/\n//g; + $tmpl_var{'vcl_error'} = $vcl_error; + } + } + } + elsif ($param{'operation'} eq "discard") { + my ($group_master) = grep { + $_->get_group() eq $param{'group_name'} + } @group_masters; + if ($group_master && $param{'vcl_name'} ne "") { + my $management = $group_master->get_management(); + if ($management->discard_vcl($param{'vcl_name'})) { + my @nodes = Varnish::NodeManager->get_nodes(); + for my $node (@nodes) { + if ($node != $group_master && $node->get_group() eq $param{'group_name'}) { + $node->get_management()->discard_vcl($param{'vcl_name'}); + } + } + + $tmpl_var{'vcl_name'} = ""; + $tmpl_var{'status'} = "VCL discarded successfully"; + } + else { + $tmpl_var{'error'} .= "Error discarding configuration:\n" . $management->get_error() . "\n"; + } + } + } + + my $selected_group_master; + for my $group_master (@group_masters) { + my %group_info = ( + name => $group_master->get_group(), + selected => 0, + ); + if ($param{'group_name'} eq $group_master->get_group()) { + $group_info{'selected'} = '1'; + $selected_group_master = $group_master; + } + push @{$tmpl_var{'group_infos'}}, \%group_info; + } + if (!$selected_group_master && @group_masters > 0) { + $selected_group_master = $group_masters[0]; + $tmpl_var{'group_infos'}->[0]->{'selected'} = 1; + } + + if ($selected_group_master) { + my $active_vcl_name; + my @vcl_names; + my $vcl_names_ref = $selected_group_master->get_management()->get_vcl_names(); + if ($vcl_names_ref) { + @vcl_names = @{$vcl_names_ref}; + $active_vcl_name = shift @vcl_names; + + for my $vcl_name (@vcl_names) { + my %vcl_info = ( + name => $vcl_name, + selected => 0, + active => 0, + ); + if ($vcl_name eq $tmpl_var{'vcl_name'}) { + $vcl_info{'selected'} = 1; + $tmpl_var{'vcl_name'} = $vcl_name; + } + if ($vcl_name eq $active_vcl_name) { + $vcl_info{'active'} = 1; + } + push @{$tmpl_var{'vcl_infos'}}, \%vcl_info; + } + if ($tmpl_var{'vcl_name'} eq "") { + FIND_ACTIVE_VCL: + for my $vcl_info (@{$tmpl_var{'vcl_infos'}}) { + if ($vcl_info->{'active'}) { + $tmpl_var{'vcl_name'} = $vcl_info->{'name'}; + $vcl_info->{'selected'} = 1; + last FIND_ACTIVE_VCL; + } + } + if ($tmpl_var{'vcl_name'} eq "") { + $tmpl_var{'vcl_name'} = $tmpl_var{'vcl_infos'}->[0]->{'name'}; + $tmpl_var{'vcl_infos'}->[0]->{'selected'} = 1; + } + } + + if (!(($param{'operation'} eq 'save' && !$successfull_save + || $param{'operation'} eq 'new'))) { + my $vcl = $selected_group_master->get_management()->get_vcl($tmpl_var{'vcl_name'}); + if ($vcl) { + $tmpl_var{'vcl'} = $vcl; + } + else { + $tmpl_var{'error'} .= "Error retrieving VCL: " . $selected_group_master->get_management()->get_error() . "\n"; + } + } + } + else { + $tmpl_var{'error'} .= "Error retrieving the VCLs: " . $selected_group_master->get_management()->get_error(); + } + } + + $tmpl_var{'editing_new_vcl'} = $editing_new_vcl; + $tmpl_var{'successfull_save'} = $successfull_save; + + return ($template, \%tmpl_var); + } + + + sub view_stats { + my ($parameter_ref) = @_; + + my $template = "view_stats.tmpl"; + + my %param = %{$parameter_ref}; + $param{'view_raw_stats'} ||= 0; + $param{'auto_refresh'} ||= 0; + + my %tmpl_var; + $tmpl_var{'error'} = ""; + $tmpl_var{'stat_time'} = 0; + $tmpl_var{'node_infos'} = []; + $tmpl_var{'summary_stats'} = []; + $tmpl_var{'raw_stats'} = []; + $tmpl_var{'auto_refresh'} = $param{'toggle_auto_refresh'} ? 1 - $param{'auto_refresh'} : $param{'auto_refresh'}; + $tmpl_var{'auto_refresh_interval'} = $tmpl_var{'auto_refresh'} ? get_config_value('poll_interval') : 0; + $tmpl_var{'view_raw_stats'} = $param{'view_raw_stats'}; + + my $error = ""; + + my ($stat_time, $stat_ref) = Varnish::Statistics->get_last_measure(); + my @nodes = Varnish::NodeManager->get_nodes(); + + if ($stat_time) { + $stat_time = strftime("%a %b %e %H:%M:%S %Y", localtime($stat_time)); + } + + my %summary_stat_list; + my %raw_stat_list; + for my $node (@nodes) { + push @{$tmpl_var{'node_infos'}}, { + name => $node->get_name(), + }; + + my $node_stat_ref = $stat_ref->{$node}; + my $node_id = $node->get_id(); + my $time_span = 'minute'; + + # example of adding graph the graph ID must match that of a predefind graph + # which is created in generate_graph found around line 826 + push @{$summary_stat_list{'Hit ratio'}}, { + is_graph => 1, + node_id => $node_id, + graph_id => 'cache_hit_ratio', + }; + push @{$summary_stat_list{'Connect requests'}}, { + is_graph => 1, + node_id => $node_id, + graph_id => 'connect_rate', + }; + + # example of missing graph_id + push @{$summary_stat_list{'Missing graph'}}, { + is_graph => 1, + node_id => $node_id, + graph_id => 'missing_graph', + }; + + # to add custom values, just add values by adding it to the list. The + # get_formatted_bytes() function is usefull for displaying byte values + # as it will convert to MB, GB etc as needed. + push @{$summary_stat_list{'% of requests served from cache'}}, { + value => get_formatted_percentage($$node_stat_ref{'Cache hits'} + , $$node_stat_ref{'Client requests received'}) + }; + + # these are examples of adding plain values from the raw stats + push @{$summary_stat_list{'Client connections accepted'}}, { + value => $$node_stat_ref{'Client connections accepted'} + }; + push @{$summary_stat_list{'Client requests received'}}, { + value => $$node_stat_ref{'Client requests received'} + }; + + my $total_bytes_served; + if ($$node_stat_ref{'Total header bytes'} + && $$node_stat_ref{'Total body bytes'}) { + $total_bytes_served = $$node_stat_ref{'Total header bytes'} + $$node_stat_ref{'Total header bytes'}; + } + push @{$summary_stat_list{'Total bytes served'}}, { + 'value' + => get_formatted_bytes($total_bytes_served) + }; + + if ($param{'view_raw_stats'}) { + while (my ($stat_name, $value) = each %{$node_stat_ref}) { + push @{$raw_stat_list{$stat_name}}, { + value => $value, + }; + } + } + } + + my $row = 1; + while (my ($stat_name, $values_ref) = each %raw_stat_list) { + push @{$tmpl_var{'raw_stats'}}, { + name => $stat_name, + values => $values_ref, + odd_row => $row++ % 2, + } + } + + $row = 1; + my $graph_row = 0; + while (my ($stat_name, $values_ref) = each %summary_stat_list) { + if ($values_ref->[0]->{'is_graph'}) { + unshift @{$tmpl_var{'summary_stats'}}, { + name => $stat_name, + values => $values_ref, + odd_row => $graph_row++ % 2, + } + } + else { + push @{$tmpl_var{'summary_stats'}}, { + name => $stat_name, + values => $values_ref, + odd_row => $row++ % 2, + } + } + } + + $tmpl_var{'error'} = $error; + $tmpl_var{'stat_time'} = $stat_time; + + return ($template, \%tmpl_var); + } + + sub configure_parameters { + my ($parameter_ref) = @_; + + my %param = %{$parameter_ref}; + $param{'node_id'} = $$parameter_ref{'node_id'} || ""; + $param{'group'} = $$parameter_ref{'group'} || ""; + + my $template = "configure_parameters.tmpl"; + my %tmpl_var; + $tmpl_var{'error'} = ""; + $tmpl_var{'status'} = ""; + $tmpl_var{'unit_infos'} = []; + $tmpl_var{'parameter_infos'} = []; + + my $unit_parameter_ref = {}; + my $error = ""; + + my %changed_parameters; + while (my ($parameter, $value) = each %$parameter_ref) { + if ($parameter =~ /^new_(.*?)$/ && + $$parameter_ref{"old_$1"} ne $value) { + $changed_parameters{$1} = $value; + } + } + + my @nodes = Varnish::NodeManager->get_nodes(); + my @groups = Varnish::NodeManager->get_groups(); + if (%changed_parameters) { + my $node = first { $_->get_id() eq $param{'node_id'} } @nodes; + if ($node) { + my $management = $node->get_management(); + while (my ($parameter, $value) = each %changed_parameters) { + if (!$management->set_parameter($parameter, $value)) { + $error .= "Could not set parameter $parameter: ". $node->get_management()->get_error() . "\n"; + } + } + } + else { + my $group = first { $_ eq $param{'group'} } @groups; + if ($group ne "") { + while (my ($parameter, $value) = each %changed_parameters) { + if (!Varnish::NodeManager->set_group_parameter($group, $parameter, $value)) { + $error .= "Could not set parameter $parameter for group $group: " + . Varnish::NodeManager->get_error() . "\n"; + } + } + } + } + if ($error eq "") { + my @changed_parameters = keys %changed_parameters; + my $status = "Parameter" . (@changed_parameters > 1 ? "s " : " "); + + $status .= shift @changed_parameters; + for my $parameter (@changed_parameters) { + $status .= ", $parameter"; + } + $status .= " configured successfully"; + $tmpl_var{'status'} = $status; + } + } + + for my $group (@groups) { + my %unit_info = ( + name => $group, + id => $group, + is_node => 0, + selected => 0, + ); + if ($group eq $param{'group'}) { + $unit_info{'selected'} = 1; + $unit_parameter_ref = Varnish::NodeManager->get_group_parameters($group); + if (!$unit_parameter_ref) { + $error .= "Could not get parameters for group $group. You need to have added a node to set these.\n"; + } + } + push @{$tmpl_var{'unit_infos'}}, \%unit_info; + } + + for my $node (@nodes) { + my %unit_info = ( + name => $node->get_name(), + id => $node->get_id(), + is_node => 1, + selected => 0, + ); + if ($node->get_id() eq $param{'node_id'}) { + $unit_info{'selected'} = 1; + $unit_parameter_ref = $node->get_management()->get_parameters(); + if (!$unit_parameter_ref) { + $error .= "Could not get parameters for node " . $node->get_name() . "\n"; + } + } + push @{$tmpl_var{'unit_infos'}}, \%unit_info; + } + + if ($param{'group'} eq "" && $param{'node_id'} eq "" + && @{$tmpl_var{'unit_infos'}} > 0) { + $tmpl_var{'unit_infos'}->[0]->{'selected'} = 1; + my $group = $tmpl_var{'unit_infos'}->[0]->{'name'}; + $unit_parameter_ref = Varnish::NodeManager->get_group_parameters($group); + if (!$unit_parameter_ref) { + $error .= "Could not get parameters for group $group\n"; + } + } + + my $row = 0; + while (my ($parameter, $info) = each %{$unit_parameter_ref} ) { + my $value = $info->{'value'}; + my $unit = $info->{'unit'}; + my $is_boolean = $unit eq "[bool]"; + if ($is_boolean) { + $value = $value eq "on"; + $unit = ''; + } + + push @{$tmpl_var{'parameter_infos'}}, { + name => $parameter, + value => $value, + unit => $unit, + description => $info->{'description'}, + is_boolean => $is_boolean, + odd_row => $row++ % 2, + }; + } + + $tmpl_var{'error'} = $error; + + return ($template, \%tmpl_var); + } + + + sub node_management { + my ($parameter_ref) = @_; + + my %param = %{$parameter_ref}; + $param{'node_id'} ||= ""; + $param{'group'} ||= ""; + $param{'operation'} ||= ""; + $param{'name'} ||= ""; + $param{'address'} = $$parameter_ref{'address'} || ""; + $param{'port'} ||= ""; + $param{'management_port'} ||= ""; + + my $template = "node_management.tmpl"; + my %tmpl_var = (); + $tmpl_var{'error'} = ""; + $tmpl_var{'status'} = ""; + $tmpl_var{'add_group'} = 0; + $tmpl_var{'group_infos'} = []; + $tmpl_var{'group'} = $param{'group'} || ""; + $tmpl_var{'node_infos'} = []; + $tmpl_var{'default_managment_port'} = 9001; + $tmpl_var{'backend_health_infos'} = []; + + my $error = ""; + my $status = ""; + + if ($param{'operation'} eq "add_group") { + if ($param{'group'}) { + Varnish::NodeManager->add_group($param{'group'}); + $status .= "Group " . $param{'group'} . " added successfully."; + } + else { + $tmpl_var{'add_group'} = 1; + } + } + elsif ($param{'operation'} eq "remove_group") { + if ($param{'group'}) { + Varnish::NodeManager->remove_group($param{'group'}); + $status = "Group " . $param{'group'} . " removed successfully"; + $tmpl_var{'group'} = ""; + } + } + elsif ($param{'operation'} eq "start_group") { + my $node_errors = ""; + for my $node (Varnish::NodeManager->get_nodes()) { + if ($node->get_group() eq $param{'group'} + && !$node->is_running()) { + my $management = $node->get_management(); + if (!$management->start()) { + $node_errors .= "Could not start " . $node->get_name() . ": " + . $management->get_error() . ". "; + } + } + } + + if ($node_errors eq "") { + $status .= "Group " . $param{'group'} . " started successfully."; + } + else { + $status .= "Group " . $param{'group'} . " started with errors: $node_errors."; + } + } + elsif ($param{'operation'} eq "stop_group") { + my $node_errors = ""; + for my $node (Varnish::NodeManager->get_nodes()) { + if ($node->get_group() eq $param{'group'} + && $node->is_running()) { + my $management = $node->get_management(); + if (!$management->stop()) { + $node_errors .= "Could not stop " . $node->get_name() . ": " + . $management->get_error() . ". "; + } + } + } + if ($node_errors eq "") { + $status .= "Group " . $param{'group'} . " stopped successfully."; + } + else { + $status .= "Group " . $param{'group'} . " stopped with errors: $node_errors."; + } + } + elsif ($param{'operation'} eq 'add_node') { + if ($param{'name'} && $param{'address'} && $param{'port'} + && $param{'group'} && $param{'management_port'}) { + my $node = Varnish::Node->new({ + name => $param{'name'}, + address => $param{'address'}, + port => $param{'port'}, + group => $param{'group'}, + management_port => $param{'management_port'} + }); + Varnish::NodeManager->add_node($node); + $status .= "Node " . $node->get_name() . " added successfully."; + } + else { + $error .= "Not enough information to add node:\n"; + $error .= "Name: " . $param{'name'} . ":\n"; + $error .= "Address: " . $param{'address'} . ":\n"; + $error .= "Port: " . $param{'port'} . ":\n"; + $error .= "Group: " . $param{'group'} . ":\n"; + $error .= "Management port: " . $param{'management_port'} . ":\n"; + } + } + elsif ($param{'operation'} eq "remove_node") { + if ($param{'node_id'}) { + my $node = Varnish::NodeManager->get_node($param{'node_id'}); + if ($node) { + $tmpl_var{'group'} = $node->get_group(); + Varnish::NodeManager->remove_node($node); + $status .= "Node " . $node->get_name() . " removed successfully."; + } + } + else { + $error .= "Could not remove node: Missing node ID\n"; + } + } + elsif ($param{'operation'} eq "start_node") { + if ($param{'node_id'}) { + my $node = Varnish::NodeManager->get_node($param{'node_id'}); + if ($node) { + my $management = $node->get_management(); + if ($node->is_running()) { + $status .= "Node " . $node->get_name() . " already running."; + } + elsif ($management->start() ) { + $status .= "Node " . $node->get_name() . " started successfully."; + } + else { + $error .= "Could not start " . $node->get_name() + . ": " . $management->get_error() . "\n"; + } + $tmpl_var{'group'} = $node->get_group(); + } + } + else { + $error .= "Could not start node: Missing node ID\n"; + } + } + elsif ($param{'operation'} eq "stop_node") { + if ($param{'node_id'}) { + my $node = Varnish::NodeManager->get_node($param{'node_id'}); + if ($node) { + my $management = $node->get_management(); + if (!$node->is_running()) { + $status .= "Node " . $node->get_name() . " already stopped."; + } + elsif ($management->stop()) { + $status .= "Node " . $node->get_name() . " stopped successfully."; + } + else { + $error .= "Could not stop " . $node->get_name() + . ": " . $management->get_error() . "\n"; + } + } + $tmpl_var{'group'} = $node->get_group(); + } + else { + $error .= "Could not stop node: Missing node ID\n"; + } + } + + # Populate the node table + my @groups = Varnish::NodeManager->get_groups(); + if (@groups) { + if (!$tmpl_var{'group'} && !$tmpl_var{'add_group'}) { + $tmpl_var{'group'} = $groups[0]; + } + my @group_infos = map { + { + name => $_, + selected => $_ eq $tmpl_var{'group'}, + } + } @groups; + $tmpl_var{'group_infos'} = \@group_infos; + + my @nodes = Varnish::NodeManager->get_nodes(); + for my $node (@nodes) { + next if ($node->get_group() ne $tmpl_var{'group'}); + + push @{$tmpl_var{'node_infos'}}, { + id => $node->get_id(), + is_running_ok => $node->is_running_ok(), + is_running => $node->is_running(), + is_management_running => $node->is_management_running(), + name => $node->get_name(), + address => $node->get_address(), + port => $node->get_port(), + management_port => $node->get_management_port(), + group => $node->get_group(), + }; + + if (@{$tmpl_var{'backend_health_infos'}} == 0) { + my $backend_health = $node->get_management()->get_backend_health(); + if ($backend_health) { + while (my ($backend, $health) = each %{$backend_health}) { + push @{$tmpl_var{'backend_health_infos'}}, { + name => $backend, + health => $health, + }; + } + } + } + } + } + else { + $tmpl_var{'add_group'} = 1; + } + + $tmpl_var{'error'} = $error; + $tmpl_var{'status'} = $status; + + return ($template, \%tmpl_var); + } + +sub generate_graph { + my ($parameter_ref) = @_; + + my %param = %{$parameter_ref}; + $param{'width'} ||= 250; + $param{'height'} ||= 150; + $param{'time_span'} ||= "minute"; + $param{'type'} ||= ""; + $param{'node_id'} ||= 0; + + my $interval = get_config_value('poll_interval'); + + # this hash holds available graphs which can be added to the summary stats in the view_stats + # function. + my %graph_info = ( + # the name of the graph + cache_hit_ratio => { + # the parameters to GD::Graph. y_number_format should be noted, as it let you format + # the presentation, like multiplying with 100 to get the percentage as shown here + graph_parameter => { + y_label => '%', + title => "Cache hit ratio last " . $param{'time_span'}, + y_max_value => 1, + y_min_value => 0, + y_number_format => sub { return $_[0] * 100 } + }, + # the divisors and dividends are lists of names of the statistics to + # use when calculating the values in the graph. The names can be obtained + # by turning 'Raw statistics' on in the GUI. The value in the graph is calculated + # by taking the sum of divisors and divide witht the sum of the dividends, i.e. + # value = (divisor1 + divisor2 + divisor3 ...) / (dividend1 + dividend 2 +..) + # if divisor or dividend is emitted, the value of 1 is used instead + divisors => [ 'Cache hits' ], + dividends => [ 'Cache hits', 'Cache misses' ], + }, + connect_rate => { + graph_parameter => { + y_label => 'Reqs', + title => "Reqs / $interval s last " . $param{'time_span'}, + y_min_value => 0, + }, + # here we have no dividends as we only want to plot 'Client requests received' + divisors => [ 'Client requests received' ], + # if use_delta is set to 1, the derived value is used, i.e. the difference + # in value between two measurements. This is usefull for graphs showing rates + # like this connect rate + use_delta => 1, + }, + ); + my %time_span_graph_parameters = ( + minute => { + x_label => 'Time', + x_tick_number => 6, # need to be set to make x_number_format work + x_number_format => sub { return strftime(":%S", localtime($_[0])); }, + x_max_value => time + }, + hour => { + x_label => 'Time', + x_tick_number => 6, # need to be set to make x_number_format work + x_number_format => sub { return strftime("%H:%M", localtime($_[0])); }, + }, + day => { + x_label => 'Time', + x_tick_number => 4, # need to be set to make x_number_format work + x_number_format => sub { return strftime("%H", localtime($_[0])); }, + }, + week => { + x_label => 'Time', + x_tick_number => 7, # need to be set to make x_number_format work + x_number_format => sub { return strftime("%d", localtime($_[0])); }, + }, + month => { + x_label => 'Time', + x_tick_number => 4, # need to be set to make x_number_format work + x_number_format => sub { return strftime("%d.%m", localtime($_[0])); }, + }, + ); + + + if ( !$graph_info{$param{'type'}} + || !$time_span_graph_parameters{$param{'time_span'}}) { + #print "Error: Missing data"; + return; + } + my $data_ref = Varnish::Statistics->generate_graph_data( + $param{'node_id'}, + $param{'time_span'}, + $graph_info{$param{'type'}}->{'divisors'}, + $graph_info{$param{'type'}}->{'dividends'}, + $graph_info{$param{'type'}}->{'use_delta'} + ); + if (!$data_ref) { + #print "Error generating graph data\n"; + return; + } + my $graph = GD::Graph::lines->new($param{'width'}, $param{'height'}); + $graph->set((%{$graph_info{$param{'type'}}->{'graph_parameter'}}, + %{$time_span_graph_parameters{$param{'time_span'}}}), + dclrs => ["#990200"]); + + my $graph_image = $graph->plot($data_ref); + + if (!$graph_image) { + return; + } + + return $graph_image->png; + } + + sub management_console { + my ($parameter_ref) = @_; + + my %param = %{$parameter_ref}; + $param{'node_id'} = $$parameter_ref{'node_id'} || 0; + + my $template = "management_console.tmpl"; + my %tmpl_var; + $tmpl_var{'error'} = ""; + $tmpl_var{'unit_infos'} = []; + $tmpl_var{'parameter_infos'} = []; + $tmpl_var{'default_console_font_size'} = '1.1em', + $tmpl_var{'default_console_cols'} = 80, + $tmpl_var{'default_console_rows'} = 30, + + my @nodes = Varnish::NodeManager->get_nodes(); + if (@nodes) { + my $node_id = $param{'node_id'} ? $param{'node_id'} : $nodes[0]->get_id(); + for my $node (@nodes) { + my $selected = $node->get_id() == $node_id; + push @{$tmpl_var{'node_infos'}}, { + id => $node->get_id(), + name => $node->get_name(), + selected => $selected, + }; + + if ($selected) { + $tmpl_var{'current_node_name'} = $node->get_name(); + } + } + } + + $tmpl_var{'console_themes'} = [ + { + name => 'Grey on black', + foreground => '#bbb', + background => 'black', + }, + { + name => 'Black on white', + foreground => 'black', + background => 'white', + }, + { + name => 'Retro', + foreground => 'green', + background => 'black', + } + ]; + $tmpl_var{'default_console_foreground'} = $tmpl_var{'console_themes'}->[0]->{'foreground'}, + $tmpl_var{'default_console_background'} = $tmpl_var{'console_themes'}->[0]->{'background'}, + + return ($template, \%tmpl_var); + } + + sub send_management_command { + my ($parameter_ref) = @_; + + my $node_id = $$parameter_ref{'node_id'}; + my $command = $$parameter_ref{'command'}; + + if ($node_id && $command) { + my $node = Varnish::NodeManager->get_node($node_id); + return "Error: Node not found." if (!$node); + + my $management = $node->get_management(); + my $response = $management->send_command($command); + if ($response) { + return $response; + } + else { + return "Error: " . $management->get_error(); + } + } + else { + return "Error: Not valid input"; + } + } + +} + + +1; diff --git a/varnish-tools/webgui/Varnish/Statistics.pm b/varnish-tools/webgui/Varnish/Statistics.pm new file mode 100644 index 00000000..2552e730 --- /dev/null +++ b/varnish-tools/webgui/Varnish/Statistics.pm @@ -0,0 +1,132 @@ +package Varnish::Statistics; + +use strict; +use POSIX qw(strftime); +use List::Util qw(first); + +use Varnish::Util; +use Varnish::NodeManager; + +{ + my %data; + my $last_measure_ref; + my $last_measure_time; + + sub collect_data { + my $time_stamp = time(); + my %measure; + my $good_stat_ref; + my @nodes = Varnish::NodeManager->get_nodes(); + + my @bad_nodes; + for my $node (@nodes) { + my $management = $node->get_management(); + my $stat_ref; + if ($management) { + $stat_ref = $management->get_stats(); + } + if ($stat_ref) { + $measure{$node} = $stat_ref; + if (!$good_stat_ref) { + $good_stat_ref = $stat_ref; + } + } + else { + push @bad_nodes, $node; + } + } + + if (@bad_nodes && $good_stat_ref) { + for my $bad_node (@bad_nodes) { + $measure{$bad_node}->{'missing data'} = 1; + for my $key (keys %$good_stat_ref) { + $measure{$bad_node}->{$key} = -1; + } + } + } + + $data{$time_stamp} = \%measure; + $last_measure_ref = \%measure; + $last_measure_time = $time_stamp; + } + + + sub get_last_measure { + + return ($last_measure_time, $last_measure_ref); + } + + sub generate_graph_data { + my ($self, $node_id, $time_span, $divisors_ref, $dividends_ref, $use_delta) = @_; + + my %seconds_for = ( + minute => 60, + hour => 3600, + day => 86400, + week => 604800, + month => 18144000, # 30 days + ); + my $start_time = time() - $seconds_for{$time_span}; + if ($use_delta) { + $start_time -= get_config_value('poll_interval'); + } + my $node = first { + $_->get_id() == $node_id + } Varnish::NodeManager->get_nodes(); + + my @measures = grep { + $_ > $start_time + } (sort keys %data); + my @values; + my $last_value; + GENERATE_DATA: + for my $measure (@measures) { + my $value; + if (!$data{$measure}->{$node}->{'missing data'}) { + my $divisor_value = 0; + my $dividend_value = 0; + + if ($divisors_ref && @$divisors_ref) { + for my $divisor (@$divisors_ref) { + $divisor_value += $data{$measure}->{$node}->{$divisor}; + } + } + else { + $divisor_value = 1; + } + if ($dividends_ref && @$dividends_ref) { + for my $dividend (@$dividends_ref) { + $dividend_value += $data{$measure}->{$node}->{$dividend}; + } + } + else { + $dividend_value = 1; + } + if ($dividend_value) { + $value = $divisor_value / $dividend_value; + } + + if ($use_delta) { + if (!$last_value) { + $last_value = $value; + next GENERATE_DATA; + } + my $delta_value = $value - $last_value; + $last_value = $value; + # if the value is negative, then we have had restart and don't plot it. + if ($delta_value < 0) { + $value = undef; + } + else { + $value = $delta_value; + } + } + } + push @values, $value; + } + + return [ \@measures, \@values ]; + } +} + +1; diff --git a/varnish-tools/webgui/Varnish/Util.pm b/varnish-tools/webgui/Varnish/Util.pm new file mode 100644 index 00000000..d3be9881 --- /dev/null +++ b/varnish-tools/webgui/Varnish/Util.pm @@ -0,0 +1,68 @@ +package Varnish::Util; + +use strict; +use Exporter; +use base qw(Exporter); + +our @EXPORT = qw( + set_config + get_config_value + read_file + get_formatted_percentage + get_formatted_bytes + ); + +{ + my %config; + + sub set_config { + my ($config_ref) = @_; + + %config = %{$config_ref}; + } + + sub get_config_value { + my ($key) = @_; + + return $config{$key}; + } + + sub read_file($) { + my ($filename) = @_; + + open(my $fh, "<$filename" ); + my $content = do { local($/); <$fh> }; + close($fh); + + return $content; + } + + sub get_formatted_percentage { + my ($divisor, $dividend) = @_; + + return $dividend > 0 ? sprintf( "%.2f", 100 * ($divisor / $dividend)) : "inf"; + } + + # thanks to foxdie at #varnish for php snippet + sub get_formatted_bytes { + my ($bytes) = @_; + + if ($bytes > 1099511627776) { + return sprintf( "%.3f TB", $bytes / 1099511627776); + } + elsif ($bytes > 1073741824) { + return sprintf( "%.3f GB", $bytes / 1073741824); + } + elsif ($bytes > 1048576) { + return sprintf( "%.3f MB", $bytes / 1048576); + } + elsif ($bytes > 1024) { + return sprintf( "%.3f KB", $bytes / 1024); + } + else { + return $bytes . " B"; + } + } +} + +1; diff --git a/varnish-tools/webgui/css/web.css b/varnish-tools/webgui/css/web.css new file mode 100644 index 00000000..7f7cb2a3 --- /dev/null +++ b/varnish-tools/webgui/css/web.css @@ -0,0 +1,189 @@ +* { + padding: 0px; + margin: 0px; +} + +a { + color: black; +} + +body { + background-color: #d7d7d7; + padding: 10px; +} + +#header { + background-color: #990200; + border-style: solid; + padding-bottom: 3px; + border-width: 0px 0px 2px 0px; +} + +img#headerLogo { + background-color: #d7d7d7; +} + +#menu { + padding: 5px 3px 5px 3px; + text-align: center; +} + +a.menu { + color: #fff; + padding-right: 20px; +} + +#error { + font-size: 1.1em; + padding: 5px; + border-width: 3px; + border-style: solid; + border-color: #6c0000; + background-color: #a21616; + margin-bottom: 10px; +} + +#content { + background-color: #f2f2f2; + padding: 10px; +} + +td.header { + font-weight: bold; + border-style: solid; + border-width: 0px 0px 1px 0px; +} + +td.footer { + font-size: 0.7em; + padding-top: 10px; + border-style: solid; + border-width: 1px 0px 0px 0px; +} + + +table#parameters { + padding: 2px; +} + +td.parameterLabel { + padding: 0px 5px 0px 5px; +} + +td.parameterValue { + padding: 0px 5px 0px 5px; + text-align: right; +} + +td.parameterUnit { + font-style: italic; + padding: 0px 5px 0px 5px; +} + +td.parameterDescription { + padding: 0px 5px 0px 5px; +} + +td.parameterEdit { + padding: 0px 5px 0px 5px; + text-align: center; +} + +tr.evenRow { + background-color:#fff; + vertical-align: top; +} + +tr.oddRow { + background-color:#eee; + vertical-align: top; +} + +textarea#vclEditor { + margin: 5px 0px 10px 0px; +} + +span.tab { + padding: 0px 10px 0px 10px; + width: 10em; + background-color: #ddd; + border-style: solid; + border-width: 1px 1px 1px 1px; +} + +span.selectedTab { + padding: 1px 10px 1px 10px; + border-style: solid; + background-color: #fff; + border-width: 1px 1px 0px 1px; +} + +span.space { + border-style: solid; + border-width: 0 0 1px 0; +} + +div.tabArea { + background-color: #fff; + padding: 5px; + border-style: solid; + border-width: 0px 1px 1px 1px; +} + +table#statsTable { + background-color: #fff; + border-style: solid; + border-width: 1px; +} + +option { + padding-right: 10px; +} + +#groupControls { + padding-top: 30px; +} + +td { + padding: 1px 2px 1px 2px; +} + +textarea#vclConfig { + font-size: 1.1em; + border-style: solid; + border-width: 2px; + border-color: #990200; + padding: 2px 0 0 5px; +} + +#themeSelection { + padding: 3px; +} + +li.consoleSetting { + margin-left: 30px; + margin-top: 10px; +} + +input.removeNode { + margin-left: 50px; +} + +#status { + border-color: #2e6c00; + border-style: solid; + border-width: 3px; + background-color: #419a00; + padding: 5px; + margin-bottom: 10px; +} + +#vclError { + font-family: monospace; + font-size: 1.1em; + border-color: black; + border-style: solid; + border-width: 3px; + background-color: red; + padding: 5px; +} diff --git a/varnish-tools/webgui/images/favicon.png b/varnish-tools/webgui/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..44d8dea8110b32a945d4ece02b1fef6518aa5a68 GIT binary patch literal 863 zcmbtS`%BXS5WjC}^GPEe)*v5|GC>h))Rzd_-x9!(vWSbD3JL*cdsgB}O#S z3?Hc5q`6Y2j4v`Lis{l(6jPkrv~cc+&^6hkg|n~y3m*6S+#Lt^c+O!nq9ceA01%zW zq!(cpzb|2jF-=N8S`2`QIRzy-7(@WDD1sm%0HFXl00KY+4wnFs1dt4n3UC47d`L(V z9?t|Q0JsWp3(F=Y-2-R@XaZ;kcnHt}@CKkApaY;2V+3i5iXxNA6bgk(r3#Xh`v?RY zjW!q`KbV*(PfZ;nkzQqG4U@?{9&ao+m(S;qF&N|d`I5@Yp3+i*K+s!LBNBXm^3i$b9rAJ^%0t3yL5ikh@qn?_?F z88H|P>sqZzum6dnb8~Y)^m?PwXt7wVR_m78{A+2+X0vTCFK?`^IUEj;)9G@#+%A`Y zYs>9+|G^x$JFv6k^?H3ipWpBQ>+=Nyf!*ERU@*9MU|L*E!?p+F`$g>H12;QZ1E7Sm zs`me(lEt8ln3IAH420J+OB*nY-5rzM`8yZ)gH83c{mTC@1vh7hr!laUN2iq>|5o0% z$<8jsKaHC>HzOf=M`X_=u|g#CMQ@l&6+37>n=$oXs5utXnb2j;=v+{1U02ePi8zAX z-esQf%_Qi}8h6nox1Z_B>n5dmKRrrmRBszkSD5B?4Y$jb5Sh}3N+Z=rkxN|pD)HTC zj%P7=C~K#7^ZM^X-b&&ZEoyE*SsS@FdDk2xo1IAzR~hOkO7&%`eeZ~kPw|DH(WGG` NLS7D=uF2*~{sG@2;nx5F literal 0 HcmV?d00001 diff --git a/varnish-tools/webgui/images/nograph.png b/varnish-tools/webgui/images/nograph.png new file mode 100644 index 0000000000000000000000000000000000000000..56ba54784e33dc3d87595ad509e49010a21c12ee GIT binary patch literal 1036 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 literal 0 HcmV?d00001 diff --git a/varnish-tools/webgui/start.pl b/varnish-tools/webgui/start.pl new file mode 100755 index 00000000..30af292a --- /dev/null +++ b/varnish-tools/webgui/start.pl @@ -0,0 +1,138 @@ +#!/usr/bin/perl +use threads; +use strict; +use warnings; +use HTTP::Daemon; +use HTTP::Status; +use HTTP::Request; +use LWP::UserAgent; +use Varnish::Util; +use Varnish::RequestHandler; +use Varnish::NodeManager; +use Varnish::Node; +use Varnish::Statistics; + + +# Configuration starts here +my %config = ( +# 'address' is the IP to bind to. If not set, it listens on all. +# address => localhost, + +# 'port' is the port of the web server + port => 8000, + +# 'poll_intervall' is the polling interval for the statistics + poll_interval => 5, +); + +# create some default groups +my @groups = qw(default images); + +# create some default nodes +my @node_params = ( + { + name => 'varnish-1', + address => 'localhost', + port => '80', + group => 'default', + management_port => 9001, + }, + { + name => 'varnish-2', + address => 'localhost', + port => '8181', + group => 'default', + management_port => 9002, + }, + { + name => 'varnish-1', + address => 'localhost', + port => '8888', + group => 'images', + management_port => 9003, + }, +); + +# End of configuration + +set_config(\%config); + +for my $group (@groups) { + Varnish::NodeManager->add_group($group); +} + +for my $node_param_ref (@node_params) { + my $group_exists = grep { + $_ eq $node_param_ref->{'group'} + } @groups; + if ($group_exists) { + my $node = Varnish::Node->new($node_param_ref); + Varnish::NodeManager->add_node($node); + } + else { + print "Node " . $node_param_ref->{'name'} . " has an invalid group " + . $node_param_ref->{'group'} . ". Skipping."; + } +} + +# catch interupt to stop the daemon +$SIG{'INT'} = sub { + print "Interrupt detected.\n"; +}; + +# ignore the occational sigpipe +$SIG{'PIPE'} = sub { +# print "Pipe ignored\n"; +}; + +my $daemon = HTTP::Daemon->new( LocalPort => $config{'port'}, + LocalAddr => $config{'address'}, + ReuseAddr => 1 ) || die "Could not start web server"; +print "Web server started with URL: " . $daemon->url, "\n"; +my $data_collector_handle = threads->create('data_collector_thread'); +while (my $connection = $daemon->accept) { + REQUEST: + while (my $request = $connection->get_request) { + $connection->force_last_request; + if ($request->uri =~ m{/(.*?\.png)} || + $request->uri =~ m{/(.*?\.css)} || + $request->uri =~ m{/(.*?\.ico)}) { + my $filename = $1; + + $connection->send_file($filename); + next REQUEST; + } + + my $request_handler = Varnish::RequestHandler->new(\$request, $connection); + $request_handler->process(); + + my $response = HTTP::Response->new(200); + $response->header( $request_handler->get_response_header() ); + $response->content( $request_handler->get_response_content() ); + $connection->send_response($response); + } + $connection->close(); + undef($connection); +} +print "Shutting down!\n"; +$daemon->close(); +Varnish::NodeManager->quit(); +print "Stopping data collector thread\n"; +$data_collector_handle->join(); + +sub data_collector_thread { + my $url = $daemon->url . "collect_data"; + my $interval = $config{'poll_interval'}; + print "Data collector thread started. Polling URL $url at $interval seconds interval\n"; + + sleep 1; # wait for the server to come up + while (1) { + my $user_agent = LWP::UserAgent->new; + $user_agent->timeout(6); + my $response = $user_agent->get($url); + + last if ($response->code eq "500"); + sleep $interval; + } + print "Data collector thread stopped.\n"; +} diff --git a/varnish-tools/webgui/templates/.master.tmpl.swp b/varnish-tools/webgui/templates/.master.tmpl.swp new file mode 100644 index 0000000000000000000000000000000000000000..8672dcbe6a61ab913d1136f43543dcf41c7e2002 GIT binary patch literal 12288 zcmeI2O>g5w7{_OkxBvwN+8c*iS3)4ftI|>ADtjbDqs1&xX{^V~ zvum;}(PAzygprdx35?WY5@_5+#o&5NYBCW4BCw6X)C}EwJ8b{`>fWw%hrjtoZ<{yS zhyW2F0z`la5CI}U1c(3;cn%4e{1*EM&-DWI9&~FVoai6|M1Tko0U|&IhyW2F0z`la z5CI}U1fCNDLByE%5@RpEj0eL1|MoY4r>`;g4K#!XkOTdR{Q%!WUqBUT7y4_5vEQNJ zpr_CiD1knO-i7{n1#8e1bQcoPJJ4;2L4Tv>uh1{hkI)n72k1NKYv@ZTf^*ac4kADVhyW2F0z`la5CJ0azX;UC*i4c`RudOlbY;&`JX@dq_E~Fi(rLDO zM;`Aq+FpIVJ+*n-@x6{e@I5#+e9uOe{>K{$`i*WGeC{{=-Z`u&!sDp!XcL&JcCfOD z-0-`Z5L;;F(tVZJ>vm7OSS@UgluM;D#j`M+DjD6cT_|y_W?{Tha+zXzXJM8anHuZ2 zM56N`#bv<9N)GD|_UER-NKW82fVa*P>F_WKw5~f?oH~c?l>=Wm)I@MSLz+c$uuiiP z-hq{`tsAj2ip^jaCL2K>mU+L~+VDpGVLSqkK^~}JBC)YpZ$vhiE`05>k!2Rk`M~yU zGqw){FAQabMO8Go1i@*_We`c#%0`*PwF>J_JORVierPv=#SOC;$hK$Iu^^oV#VjpH+4{i2#3qr4{c^`OcX?~7jctfgS&~S_SLL{68fR%u zlp+5aXfQ+Pjmcg4`81x@9SnhzdJKx?sMMWuh*wkD==rCEu6Nw^&L83>^1W`q(K@^_ zdCzuZwHpP|AUl$-f0f%wTUIsl(iCs;hj@}eX=&;`|Jc1(;7a23F;_AHXaqRiAa(&= z6mS=_ylBHPm|4GqJIJl0wYf_0uu__3j-qU+F$rY~e6;)5E5(xS)PlbKe&LssB*klC z@!c0|eVg-x~k` literal 0 HcmV?d00001 diff --git a/varnish-tools/webgui/templates/configure_parameters.tmpl b/varnish-tools/webgui/templates/configure_parameters.tmpl new file mode 100644 index 00000000..5b7244fd --- /dev/null +++ b/varnish-tools/webgui/templates/configure_parameters.tmpl @@ -0,0 +1,89 @@ + + + +
+ +
+ + + + + + + + + + + + + + + + +
NameValueUnit
+ + + + + + + + + + + + + + + + + +? + +
+
+ +No nodes available + diff --git a/varnish-tools/webgui/templates/edit_vcl.tmpl b/varnish-tools/webgui/templates/edit_vcl.tmpl new file mode 100644 index 00000000..28ced305 --- /dev/null +++ b/varnish-tools/webgui/templates/edit_vcl.tmpl @@ -0,0 +1,182 @@ + + +
+
+ + + + + + + + + +
+
+ +
+ + + + + + + +
+ + +| + + + + + + + + + + + + + + + +| +
+
+ + + + +Errors found in the VCL: +
+ +
+
+
+ +No groups available +
diff --git a/varnish-tools/webgui/templates/management_console.tmpl b/varnish-tools/webgui/templates/management_console.tmpl new file mode 100644 index 00000000..95e9cfc8 --- /dev/null +++ b/varnish-tools/webgui/templates/management_console.tmpl @@ -0,0 +1,248 @@ + + + +
+ + + + + + + + + +
+
+
+> +
+Console settings: +
    +
  • +Color theme: + + + + + +
  • +
  • Size: + x + +
  • +
+ + +No nodes to manage. +
diff --git a/varnish-tools/webgui/templates/master.tmpl b/varnish-tools/webgui/templates/master.tmpl new file mode 100644 index 00000000..403a1cc9 --- /dev/null +++ b/varnish-tools/webgui/templates/master.tmpl @@ -0,0 +1,43 @@ + + + + + + + + + +Varnish - Lust controller christmas edition + + +
+ +
+ +
+An error occured:
+
+
+
+
+
+ +
+ +
+
+ +
+
+ + diff --git a/varnish-tools/webgui/templates/node_management.tmpl b/varnish-tools/webgui/templates/node_management.tmpl new file mode 100644 index 00000000..21448a78 --- /dev/null +++ b/varnish-tools/webgui/templates/node_management.tmpl @@ -0,0 +1,138 @@ +
+ + + + + + + + + + + + + + +Add.. + +
+
+ +
+ + + + +
Name:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VMNameAddressPortManagement port
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ + + +
+
+ +
+ + + + + + +
BackendHealth
+ +No backend health information available +
+
+ + + + + + +
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ +
diff --git a/varnish-tools/webgui/templates/view_stats.tmpl b/varnish-tools/webgui/templates/view_stats.tmpl new file mode 100644 index 00000000..0cb01182 --- /dev/null +++ b/varnish-tools/webgui/templates/view_stats.tmpl @@ -0,0 +1,99 @@ + + + +

+ +Statistics collected at + +(auto-refreshing) + + +No statistics yet collected + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Summary

+
+ + +
+ +
+
+Last: +Mi +H +D +W +Mo +
+ + + +
+

Raw statistics

+
+ + +
+Raw statistics: On Off + + +Raw statistics: On Off +
+| Auto refresh: + +On Off + +On Off + + +No nodes available + diff --git a/varnish-tools/webgui/test/test_management.pl b/varnish-tools/webgui/test/test_management.pl new file mode 100644 index 00000000..67f1b927 --- /dev/null +++ b/varnish-tools/webgui/test/test_management.pl @@ -0,0 +1,41 @@ +use strict; +use warnings; +use Varnish::Management; + +my $console = Varnish::Management->new("localhost", "9001"); + +my $status = $console->set_config("testing", "backend b0 { .host = \"localhost\"; .port = \"8080\"; }"); + +if ($status ne "") { + print "Error:\n$status\n"; +} + +for my $config ($console->get_config_names()) { + print "Config: $config\n"; + print "-" x 80 . "\n"; + print $console->get_config($config) . "\n"; +} + + +my %stats_counter = $console->get_stats(); +while (my ($stat, $value) = each %stats_counter) { + print "$stat = $value\n"; +} + +if ($console->ping()) { + print "I am alive!\n"; +} +else { + print "I am dead: " . $console->get_error() . "\n"; +} + +my $console2 = Varnish::Management->new("localhost", "9002"); + + +if ($console2->ping()) { + print "I am alive!\n"; +} +else { + print "I am dead: " . $console2->get_error() . "\n"; +} + -- 2.39.5