diff options
-rw-r--r-- | lib/Qooxdoo/JSONRPC.pm | 970 | ||||
-rw-r--r-- | lib/Qooxdoo/Services/tr.pm | 295 | ||||
-rw-r--r-- | qx08/source/class/tr/Application.js | 123 | ||||
-rw-r--r-- | qx08/source/class/tr/Server.js | 41 | ||||
-rw-r--r-- | qx08/source/class/tr/test/DemoTest.js | 58 | ||||
-rw-r--r-- | qx08/source/class/tr/theme/Color.js | 14 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/ActionButton.js | 239 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/Cellrenderer.js | 12 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/Config.js | 116 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/CopyBuffer.js | 45 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/Footer.js | 8 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/Link.js | 18 | ||||
-rw-r--r-- | qx08/source/class/tr/ui/TraceTable.js | 279 |
13 files changed, 1644 insertions, 574 deletions
diff --git a/lib/Qooxdoo/JSONRPC.pm b/lib/Qooxdoo/JSONRPC.pm new file mode 100644 index 0000000..b948ffb --- /dev/null +++ b/lib/Qooxdoo/JSONRPC.pm @@ -0,0 +1,970 @@ +package Qooxdoo::JSONRPC; + +# qooxdoo - the new era of web development +# +# http://qooxdoo.org +# +# Copyright: +# 2006-2007 Nick Glencross +# +# License: +# LGPL: http://www.gnu.org/licenses/lgpl.html +# EPL: http://www.eclipse.org/org/documents/epl-v10.php +# See the LICENSE file in the project's top-level directory for details. +# +# Authors: +# * Nick Glencross + +# The JSON-RPC implementation. +# Use perldoc on this file to view documentation + +use strict; + +use JSON; + +#use CGI; +#use CGI::Session; + +# Enabling debugging will log information in the apache logs, and in +# some cases provide more information in error responses +$Qooxdoo::JSONRPC::debug = 0; + +############################################################################## + +# JSON-RPC error origins + +use constant JsonRpcError_Origin_Server => 1; +use constant JsonRpcError_Origin_Application => 2; +use constant JsonRpcError_Origin_Transport => 3; +use constant JsonRpcError_Origin_Client => 4; + + +# JSON-RPC server-generated error codes + +use constant JsonRpcError_Unknown => 0; +use constant JsonRpcError_IllegalService => 1; +use constant JsonRpcError_ServiceNotFound => 2; +use constant JsonRpcError_ClassNotFound => 3; +use constant JsonRpcError_MethodNotFound => 4; +use constant JsonRpcError_ParameterMismatch => 5; +use constant JsonRpcError_PermissionDenied => 6; + +# Method Accessibility values + +use constant Accessibility_Public => "public"; +use constant Accessibility_Domain => "domain"; +use constant Accessibility_Session => "session"; +use constant Accessibility_Fail => "fail"; + +use constant defaultAccessibility => Accessibility_Domain; + +# Script transport not-in-use setting + +use constant ScriptTransport_NotInUse => -1; + +############################################################################## + +# This is the main entry point for handling requests + +sub handle_request +{ + my ($cgi, $session) = @_; + + my $session_id = $session->id (); + + print STDERR "Session id: $session_id\n" + if $Qooxdoo::JSONRPC::debug; + + print $session->header; + + # 'selfconvert' is enabled for date conversion. Ideally we also want + # 'convblessed', but this then disabled 'selfconvert'. + my $json = new JSON (selfconvert => 1); + + # Create the RPC error state + + my $error = new Qooxdoo::JSONRPC::error ($json); + + $error->set_session($session); + + my $script_transport_id = ScriptTransport_NotInUse; + + #---------------------------------------------------------------------- + + # Deal with various types of HTTP request and extract the JSON + # body + + my $input; + + my $request_method = $cgi->request_method || ''; + + if ($request_method eq 'POST') + { + my $content_type = $cgi->content_type; + + print STDERR "POST Content type is '$content_type'\n" + if $Qooxdoo::JSONRPC::debug; + + if ($content_type =~ m{application/json}) + { + $input = $cgi->param('POSTDATA'); + } + else + { + print "JSON-RPC request expected -- unexpected data received\n"; + exit; + } + } + elsif ($request_method eq 'GET' && + defined $cgi->param ('_ScriptTransport_id') && + $cgi->param ('_ScriptTransport_id') != ScriptTransport_NotInUse && + defined $cgi->param ('_ScriptTransport_data')) + { + print STDERR "GET request\n" if $Qooxdoo::JSONRPC::debug; + + # We have what looks like a valid ScriptTransport request + $script_transport_id = $cgi->param ('_ScriptTransport_id'); + $error->set_script_transport_id ($script_transport_id); + + $input = $cgi->param ('_ScriptTransport_data'); + + } + else + { + print "Your HTTP Client is not using the JSON-RPC protocol\n"; + exit; + } + + #---------------------------------------------------------------------- + + # Transform dates into JSON which the parser can handle + Qooxdoo::JSONRPC::Date::transform_date (\$input); + my $sanitized = $input; + # try to NOT to print passwords + $sanitized =~ s/(pass[a-z]+":").+?("[,}])/${1}*******${2}/g; + print STDERR "JSON received: $sanitized\n" if $Qooxdoo::JSONRPC::debug; + + #---------------------------------------------------------------------- + + # Convert the JSON string to a Perl datastructure + + $@ = ''; + my $json_input; + eval + { + $json_input = $json->jsonToObj ($input); + }; + + if ($@) + { + print"A bad JSON-RPC request was received which could not be parsed\n"; + exit; + } + + unless ($json_input && + exists $json_input->{service} && + exists $json_input->{method} && + exists $json_input->{params}) + { + print "A bad JSON-RPC request was received\n"; + exit; + } + + $error->set_id ($json_input->{id}); + + #---------------------------------------------------------------------- + + # Perform various sanity checks on the received request + + unless ($json_input->{service} =~ /^[_.a-zA-Z0-9]+$/) + { + $error->set_error (JsonRpcError_IllegalService, + "Illegal character found in service name"); + $error->send_and_exit; + } + + if ($json_input->{service} =~ /\.\./) + { + $error->set_error (JsonRpcError_IllegalService, + "Illegal use of two consecutive dots " . + "in service name"); + $error->send_and_exit; + } + + my @service_components = split (/\./, $json_input->{service}); + + # Surely this can't actually happen after earlier checks? + foreach (@service_components) + { + unless (/^[_.a-zA-Z0-9]+$/) + { + $error->set_error (JsonRpcError_IllegalService, + "A service name component does not begin " . + "with a letter"); + $error->send_and_exit; + } + } + + #---------------------------------------------------------------------- + + # Generate the name of the module corresponding to the Service + + my $module = join ('::', ('Qooxdoo', 'Services', @service_components)); + + # Attempt to load the module + + $@ = ''; + eval "require $module"; + + if ($@) + { + print STDERR "$@\n" if $Qooxdoo::JSONRPC::debug; + + # The error description used here provides more information when + # debugging, but probably reveals too much on a live stable + # server + + if ($Qooxdoo::JSONRPC::debug) + { + $error->set_error (JsonRpcError_ServiceNotFound, + "Service '$module' could not be loaded ($@)"); + } + else + { + $error->set_error (JsonRpcError_ServiceNotFound, + "Service '$module' not found"); + } + $error->send_and_exit; + + } + + #---------------------------------------------------------------------- + + # Determine the accessibility of the requested method + + my $method = $json_input->{method}; + + my $accessibility = defaultAccessibility; + + my $accessibility_method = "${module}::GetAccessibility"; + + if (defined $accessibility_method) + { + print STDERR "Module $module has GetAccessibility\n" + if $Qooxdoo::JSONRPC::debug; + + $@ = ''; + $accessibility = eval $accessibility_method . + '($method,$accessibility,$session)'; + + if ($@) + { + print STDERR "$@\n" if $Qooxdoo::JSONRPC::debug; + + $error->set_error (JsonRpcError_Unknown, + $@); + $error->send_and_exit; + } + + print STDERR "GetAccessibility for $method returns $accessibility\n" + if $Qooxdoo::JSONRPC::debug; + + } + + #---------------------------------------------------------------------- + + # Do referer checking based on accessibility + + + if ($accessibility eq Accessibility_Public) + { + # Nothing to do as the method is always accessible + } + elsif ($accessibility eq Accessibility_Domain) + { + my $requestUriDomain; + + my $server_protocol = $cgi->server_protocol; + + my $is_https = $cgi->https ? 1 : 0; + + $requestUriDomain = $is_https ? 'https://' : 'http://'; + + $requestUriDomain .= $cgi->server_name; + + $requestUriDomain .= ":" . $cgi->server_port + if $cgi->server_port != ($is_https ? 443 : 80); + + if ($cgi->referer and $cgi->referer !~ m|^(https?://[^/]*)|) + { + $error->set_error (JsonRpcError_PermissionDenied, + "Permission denied"); + $error->send_and_exit; + } + + my $refererDomain = $1; + + if ($refererDomain ne $requestUriDomain) + { + $error->set_error (JsonRpcError_PermissionDenied, + "Permission denied"); + $error->send_and_exit; + } + + if (!defined $session->param ('session_referer_domain')) + { + $session->param ('session_referer_domain', $refererDomain); + } + + } + elsif ($accessibility eq Accessibility_Session) + { + if ($cgi->referer !~ m|^(https?://[^/]*)|) + { + $error->set_error (JsonRpcError_PermissionDenied, + "Permission denied"); + $error->send_and_exit; + } + + my $refererDomain = $1; + + if (defined $session->param ('session_referer_domain') && + $session->param ('session_referer_domain') ne $refererDomain) + { + $error->set_error (JsonRpcError_PermissionDenied, + "Permission denied"); + $error->send_and_exit; + } + else + { + $session->param ('session_referer_domain', $refererDomain); + } + } + elsif ($accessibility eq Accessibility_Fail) + { + $error->set_error (JsonRpcError_PermissionDenied, + "Permission denied"); + $error->send_and_exit; + + } + else + { + $error->set_error (JsonRpcError_PermissionDenied, + "Service error: unknown accessibility"); + $error->send_and_exit; + } + + #---------------------------------------------------------------------- + + # Generate the name of the function to call and check it exists + + my $package_method = "${module}::method_${method}"; + + unless (defined &$package_method) + { + $error->set_error (JsonRpcError_MethodNotFound, + "Method '$method' not found " . + "in service class '$module'"); + $error->send_and_exit; + } + + #---------------------------------------------------------------------- + + # Errors from here come from the Application + + $error->set_origin (JsonRpcError_Origin_Application); + + # Retrieve the arguments + + my $params = $json_input->{params}; + + unless (ref $params eq 'ARRAY') + { + $error->set_error (JsonRpcError_ParameterMismatch, + "Arguments were not received in an array"); + $error->send_and_exit; + } + + my @params = @{$params}; + + # Do a shallow scan of parameters, and promote hashes which are + # dates + foreach (@params) + { + if (ref eq 'HASH' && + exists $_->{Qooxdoo_date}) + { + bless $_, 'Qooxdoo::JSONRPC::Date'; + } + } + + # Invoke the method dynamically using eval + + $@ = ''; + my @result = eval $package_method . '($error, @params)'; + + if ($@) + { + print STDERR "$@\n" if $Qooxdoo::JSONRPC::debug; + + $error->set_error (JsonRpcError_Unknown, + $@); + $error->send_and_exit; + + } + + # (I've had to assume this behaviour based on the test results) + + my $result; + + if ($#result == 0) + { + $result = shift @result; + } + else + { + $result = \@result; + } + + # Either send an error, or the application response + + if (ref $result eq 'Qooxdoo::JSONRPC::error') + { + $error->send_and_exit (); + } + + $result = {id => $json_input->{id}, + result => $result}; + + send_reply ($json->objToJson ($result), $script_transport_id); +} + + +############################################################################## + +# Send the application response + +sub send_reply +{ + my ($reply, $script_transport_id) = @_; + + if ($script_transport_id == ScriptTransport_NotInUse) + { + print STDERR "Send $reply\n" if $Qooxdoo::JSONRPC::debug; + print $reply; + } + else + { + $reply = "qx.io.remote.ScriptTransport._requestFinished" . + "($script_transport_id, $reply);"; + + print STDERR "Send $reply\n" if $Qooxdoo::JSONRPC::debug; + print $reply; + } +} + + +############################################################################## + +# These two routines are useful to the Services themselves + +sub json_bool +{ + my $value = shift; + + return $value ? JSON::True : JSON::False; +} + + +sub json_istrue +{ + my $value = shift; + + my $is_true = ref $value eq 'JSON::NotString' + && defined $value->{value} && $value->{value} eq 'true'; + + return $is_true; +} + +############################################################################## + +package Qooxdoo::JSONRPC::error; + +use strict; + +# The error object enumerates various types of error + +sub new +{ + my $self = shift ; + my $class = ref ($self) || $self ; + + my $json = shift ; + my $origin = shift || Qooxdoo::JSONRPC::JsonRpcError_Origin_Server; + my $code = shift || Qooxdoo::JSONRPC::JsonRpcError_Unknown; + my $message = shift || "Unknown error"; + + $self = bless + { + json => $json, + origin => $origin, + code => $code, + message => $message, + script_transport_id => Qooxdoo::JSONRPC::ScriptTransport_NotInUse + + }, $class ; + + return $self ; +} + +sub set_origin +{ + my $self = shift; + my $origin = shift; + + $self->{origin} = $origin; +} + +sub set_error +{ + my $self = shift; + my $code = shift; + my $message = shift; + + $self->{code} = $code; + $self->{message} = $message; +} + +sub set_id +{ + my $self = shift; + my $id = shift; + + $self->{id} = $id; +} + +sub set_session +{ + my $self = shift; + my $session = shift; + + $self->{session} = $session; +} + +sub set_script_transport_id +{ + my $self = shift; + my $script_transport_id = shift; + + $self->{script_transport_id} = $script_transport_id; +} + + +sub send_and_exit +{ + my $self = shift; + + my $result = {'id' => $self->{id}, + 'error' => {origin => $self->{origin}, + code => $self->{code}, + message => $self->{message}}}; + + my $script_transport_id = $self->{script_transport_id}; + + Qooxdoo::JSONRPC::send_reply ($self->{json}->objToJson ($result), + $script_transport_id); + exit; +} + +############################################################################## + +# Implementation of a Date class with set/get methods + +package Qooxdoo::JSONRPC::Date; + +use strict; + +sub new +{ + my $self = shift ; + my $class = ref ($self) || $self ; + + my $time = shift; + $self = bless {}, $class ; + + $self->set_epoch_time ($time); + + return $self ; +} + + +sub set_epoch_time +{ + my $self = shift; + my $time = shift; + + $time = time () unless defined $time; + + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = + gmtime ($time); + + $self->{year} = 1900+$year; + $self->{month} = $mon; # Starts from 0 + $self->{day} = $mday; + $self->{hour} = $hour; + $self->{minute} = $min; + $self->{second} = $sec; + $self->{millisecond} = 0; + + return $self; +} + + +# Month is passed in 1..12, but stored 0..11 + +sub set +{ + my $self = shift; + my ($year, $month, $day, $hour, $minute, $second, $millisecond) = @_; + + $hour ||= 0; + $minute ||= 0; + $second ||= 0; + $millisecond ||= 0; + + $self->{year} = $year; + $self->{month} = $month-1; + $self->{day} = $day; + $self->{hour} = $hour; + $self->{minute} = $minute; + $self->{second} = $second; + $self->{millisecond} = $millisecond; +} + +sub set_year +{ + my $self = shift; + my $year = shift; + + $self->{year} = $year; +} + +sub set_month +{ + my $self = shift; + my $month = shift; + + $self->{month} = $month-1; +} + + +sub set_day +{ + my $self = shift; + my $day = shift; + + $self->{day} = $day; +} + + +sub set_hour +{ + my $self = shift; + my $hour = shift; + + $self->{hour} = $hour; +} + + +sub set_minute +{ + my $self = shift; + my $minute = shift; + + $self->{minute} = $minute; +} + +sub set_second +{ + my $self = shift; + my $second = shift; + + $self->{second} = $second; +} + +sub set_millisecond +{ + my $self = shift; + my $millisecond = shift; + + $self->{millisecond} = $millisecond; +} + + + + +sub get_year +{ + my $self = shift; + + return $self->{year}; +} + +sub get_month +{ + my $self = shift; + + return $self->{month}+1; +} + + +sub get_day +{ + my $self = shift; + + return $self->{day}; +} + + +sub get_hour +{ + my $self = shift; + + return $self->{hour}; +} + + +sub get_minute +{ + my $self = shift; + + return $self->{minute}; +} + +sub get_second +{ + my $self = shift; + + return $self->{second}; +} + +sub get_millisecond +{ + my $self = shift; + + return $self->{millisecond}; +} + + +# This is the special method used by the JSON module to serialise a class. +# The feature is enabled with the 'selfconvert' parameter + +sub toJson +{ + my $self = shift; + + my $time = $self->{time}; + + my $year = $self->{year}; + my $month = $self->{month}; + my $day = $self->{day}; + my $hour = $self->{hour}; + my $minute = $self->{minute}; + my $second = $self->{second}; + my $millisecond = $self->{millisecond}; + + return sprintf 'new Date(Date.UTC(%d,%d,%d,%d,%d,%d,%d))', + $year, + $month, + $day, + $hour, + $minute, + $second, + $millisecond; +} + +# Routine to convert the date embedded in the JSON string to something +# that can be parsed + +sub transform_date +{ + my $input_ref = shift; + + ${$input_ref} =~ + s/new\s+Date\s*\(Date.UTC\( + (\d+),(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)\)/ + blessed_date($1,$2,$3,$4,$5,$6,$7)/gxe; +} + +# This function is called by the regexp in transform_date + +sub blessed_date +{ + my ($year, $month, $day, $hour, $minute, $second, $millisecond) = @_; + + return sprintf('{"Qooxdoo_date":1,"year":%d,"month":%d,"day":%d,"hour":%d,"minute":%d,"second":%d,"millisecond":%d}', + $year, + $month, + $day, + $hour, + $minute, + $second, + $millisecond); +} + + + +############################################################################## + +=head1 NAME + +Qooxdoo::JSONRPC.pm - A Perl implementation of JSON-RPC for Qooxdoo + +=head1 SYNOPSIS + +RPC-JSON is a straightforward Remote Procedure Call mechanism, primarily +targeted at Javascript clients, and hence ideal for Qooxdoo. + +Services may be implemented in any language provided they provide a +conformant implementation. This module uses the CGI module to parse +HTTP headers, and the JSON module to manipulate the JSON body. + +A simple, but typical exchange might be: + +client->server: + + {"service":"qooxdoo.test","method":"echo","id":1,"params":["Hello"],"server_data":null} + +server->client: + + {"id":1,"result":"Client said: [Hello]"} + +Here the service 'qooxdoo.test' is requested to run a method called +'echo' with an argument 'Hello'. This Perl implementation will locate +a module called Qooxdoo::Services::qooxdoo::test (corresponding to +Qooxoo/Services/qooxdoo/test.pm in Perl's library path). It will +then execute the function Qooxdoo::Services::qooxdoo::test::echo +with the supplied arguments. + +The function will receive the error object as the first argument, and +subsequent arguments are supplied by the remote call. Your method call +would therefore start with something equivalent to: + + my $error = shift; + my @params = @_; + +See test.pm for how to deal with errors and return responses. + +The response is sent back with the corresponding id (essential for +asynchronous calls). + +The protocol also provides an exception handling mechanism, where a +response is formatted something like: + + {"error":{"origin":2,"code":23,"message":"This is an application-provided error"},"id":21} + +There are 4 error origins: + +=over 4 + +=item * JsonRpcError_Origin_Server 1 + +The error occurred within the server. + +=item * JsonRpcError_Origin_Application 2 + +The error occurred within the application. + +=item * JsonRpcError_Origin_Transport 3 + +The error occurred somewhere in the communication (not raised in this module). + +=item * JsonRpcError_Origin_Client 4 + +The error occurred in the client (not raised in this module). + +=back + +For Server errors, there are also some predefined error codes. + +=over 4 + +=item * JsonRpcError_Unknown 0 + +The cause of the error was not known. + +=item * JsonRpcError_IllegalService 1 + +The Service name was not valid, typically due to a bad character in the name. + +=item * JsonRpcError_ServiceNotFound 2 + +The Service was not found. In this implementation this means that the +module containing the Service could not be found in the library path. + +=item * JsonRpcError_ClassNotFound 3 + +This means the class could not be found with, is not actually raised +by this implementation. + +=item * JsonRpcError_MethodNotFound 4 + +The method could not be found. This is raised if a function cannot be +found with the method name in the requested package namespace. + +Note: In Perl, modules (files containing functionality) and packages +(namespaces) are closely knitted together, but there need not be a +one-for-one correspondence -- packages can be shared across multiple +modules, or a module can use multiple packages. This module assumes a +one-for-one correspondence by looking for the method in the same +namespace as the module name. + +=item * JsonRpcError_ParameterMismatch 5 + +This is typically raised by individual methods when they do not +receive the parameters they are expecting. + +=item * JsonRpcError_PermissionDenied 6 + +Again, this error is raised by individual methods. Remember that RPC +calls need to be as secure as the rest of your application! + +=back + +There is also some infrastructure to allow access control on methods +depending on the relationship of the referer. Have a look at test.pm +to see how this can be done by defining C<GetAccessibility> which +returns one of the following for a supplied method name: + +=over 4 + +=item * Accessibility_Public ("public") + +The method may be called from any session, and without any checking of +who the Referer is. + +=item * Accessibility_Domain ("domain") + +The method may only be called by a script obtained via a web page +loaded from this server. The Referer must match the request URI, +through the domain part. + +=item * Accessibility_Session ("session") + +The Referer must match the Referer of the very first RPC request +issued during the session. + +=item * Accessibility_Fail ("fail") + +Access is denied + +=back + +=head1 AUTHOR + +Nick Glencross E<lt>nick.glencross@gmail.comE<gt> + +=cut + + +1; diff --git a/lib/Qooxdoo/Services/tr.pm b/lib/Qooxdoo/Services/tr.pm new file mode 100644 index 0000000..d78070c --- /dev/null +++ b/lib/Qooxdoo/Services/tr.pm @@ -0,0 +1,295 @@ +package Qooxdoo::Services::tr; +use strict; +use POSIX qw(setsid); +use Time::HiRes qw(usleep); +use Socket; + +my $variant = 'butskoy'; +my $config = { + # Modern traceroute for Linux, version 2.0.9, Nov 19 2007 + # Copyright (c) 2006 Dmitry Butskoy, License: GPL + butskoy => [ + { + arg => '-q', + type => 'static', + }, + { + arg => '1', + type => 'static', + }, + { + key => 'host', + type => 'intern', + }, + { + key => 'pkglen', + label => 'Packetlength', + type => 'spinner', + min => 0, + max => 1024, + default => 53, + }, + { + key => 'method', + arg => '-M', + label => 'Traceroute Method', + type => 'select', + pick => [ + default => 'Classic UDP', + icmp => 'ICMP ECHO', + tcp => 'TCP Syn', + udp => 'UDP to port 53', + udplite => 'UDPLITE Datagram', + ], + default => 'icmp', + }, + { + key => 'nofrag', + arg => '-F', + label => 'Do not Fragment', + type => 'boolean', + default => 0, + }, + ], + # Version 1.4a12 + lbl => [ + { + arg => '-q', + type => 'static', + }, + { + arg => '1', + type => 'static', + }, + { + key => 'host', + type => 'intern', + }, + { + key => 'pkglen', + label => 'Packetlength', + type => 'spinner', + min => 0, + max => 1024, + default => 53, + }, + { + key => 'icmpecho', + arg => '-I', + label => 'Use ICMP ECHO', + type => 'boolean', + default => 1, + } + ] +}; + +sub GetAccessibility { + my $method = shift; + my $access = shift; + my $session = shift; +# if ($method eq 'auth' or $session->param('authenticated') eq 'yes'){ + return 'public'; +# } +# else { +# return 'fail'; +# } +} + +sub launch { + my $error = shift; + my $task = shift; + my $cfg = $config->{$variant}; + my @exec; + for (my $i = 0;$i < @{$cfg};$i++){ + my $ch = $cfg->[$i]; + if ($ch->{key}){ + if ($task->{$ch->{key}}){ + if ($ch->{arg}){ + push @exec, $ch->{arg}; + } + push @exec, $task->{$ch->{key}} + } + } + elsif ($ch->{arg}){ + push @exec, $ch->{arg}; + }; + } + use Data::Dumper; + my $rounds = $task->{rounds}; + my $delay = $task->{delay}; +# warn Dumper '### task: '.$task; + defined(my $pid = fork) or do { $error->set_error(101,"Can't fork: $!");return $error}; + if ($pid){ + open my $x, ">/tmp/tr_session.$pid" or do { + $error->set_error(199,"Opening /tmp/tr_session.$$: $!"); + return $error; + }; + close ($x); + return $pid; + } + local $SIG{CHLD}; + chdir '/' or die "Can't chdir to /: $!"; + +# $|++; # unbuffer + open STDOUT, ">>/tmp/tr_session.$$" + or die "Can't write to /tmp/tr_session.$$: $!"; + open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; + setsid or die "Can't start a new session: $!"; + open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; + for (my $i = 0; $i<$rounds;$i++){ + my $start = time; + system "traceroute",@exec; + if ($? == -1) { + print "ERROR: failed to execute traceroute: $? $!\n"; + exit 1; + } + elsif ($? & 127) { + printf "ERROR: traceroute died with signal %d, %s coredump\n", + ($? & 127), ($? & 128) ? 'with' : 'without'; + exit 1; + } + elsif ($? != 0) { + printf "ERROR: traceroute exited with value %d\n", $? >> 8; + exit 1; + } + my $wait = $delay - (time - $start); + if ($wait > 0 and $i+1< $rounds){ + print "SLEEP $wait\n"; + sleep $wait; + } + } + exit 0; +} + +sub get_number { + my $error = shift; + my $data = shift; + $data = 'Undefined' unless defined $data; + if ($data =~ /^(\d+)$/){ + return $1; + } + else { + $error->set_error(104,"Expected a number but got: $data"); + return $error; + } +} + +sub method_stop_tr { + my $error = shift; + my $arg = shift; + my $handle = get_number($error,$arg); + + return $handle if ref $handle; + my $data = "/tmp/tr_session.".$handle; + if (-r $data){ + warn "Sending kill $handle"; + if (kill('KILL',$handle)){ + waitpid($handle,0); + } + } + return 'ok'; +} + + +sub method_start +{ + my $error = shift; + my $arg = shift; + my $session = $error->{session}; + if ($arg->{host}){ + my $delay = get_number($error,$arg->{delay}); + return $delay if ref $delay; + my $rounds = get_number($error,$arg->{rounds}); + return $rounds if ref $rounds; + return launch ($error,$arg); + } + $error->set_error(103,"No host set"); + return $error; +} + + +sub method_poll +{ + my $error = shift; + my $arg = shift; + my $session = $error->{session}; + my %return; + for my $pid (sort keys %$arg){ + my $point = $arg->{$pid}; + my $data = "/tmp/tr_session.".$pid; + my $problem = ''; + if (open my $fh,$data){ + my $again; + my @array; + my $rounds = 0; + waitpid($pid,1); + $again = kill(0, $pid); + my $size = -s $fh; + if ($point < $size and seek $fh, $point,0){ + while (<$fh>){ + next if /^\s*$/ or /^traceroute to/; + if (/^\s*(\d+)\s+(\S+)\s+\((\S+?)\)\s+(\S+)\s+ms/){ + my ($hop,$host,$ip,$value) = ($1,$2,$3,$4); + $value = undef unless $value =~ /^\d+(\.\d+)?$/; + push @array, [$hop,$host,$ip,$value]; + } + elsif (/^\s*(\d+)\s+\*/){ + push @array, [$1,undef,undef,undef]; + } + else { + s/ERROR:\s*//; + $problem .= $_; + } + } + $arg->{$pid} = tell($fh); + $return{$pid}{data} = \@array; + }; + close $fh; + warn 'problem: '.$problem; + if ($problem){ + $return{$pid}{type} = 'error'; + $return{$pid}{msg} = $problem; + delete $arg->{$pid}; + unlink $data; + } + elsif (not $again) { + $return{$pid}{type} = 'state'; + $return{$pid}{msg} = 'idle'; + delete $arg->{$pid}; + unlink $data; + } + } + else { + $return{$pid}{type} = 'error'; + $return{$pid}{msg} = "Opening $data: $!"; + delete $arg->{$pid}; + } + } + $return{handles} = $arg; + return \%return; +} + +sub method_auth { + my $error = shift; + my $user = shift; + my $password = shift; + my $session = $error->{session}; + if ($user eq 'tobi' and $password eq 'robi'){ + $session->param('authenticated','yes'); + } +} + +sub method_get_config { + my $error = shift; + my @list; + for (my $i=0;defined $config->{$variant}[$i]; $i+=2){ + next if not defined $config->{$variant}[$i+1]{label}; + push @list, $config->{$variant}[$i],$config->{$variant}[$i+1]; + }; + return \@list; +} + + + +1; + diff --git a/qx08/source/class/tr/Application.js b/qx08/source/class/tr/Application.js index fe7df8f..17044ad 100644 --- a/qx08/source/class/tr/Application.js +++ b/qx08/source/class/tr/Application.js @@ -1,18 +1,13 @@ /* ************************************************************************ + Copyright: 2008, OETIKER+PARTNER AG + License: GPL + Authors: Tobias Oetiker + $Id: $ +* ************************************************************************ */ - Copyright: OETIKER+PARTNER AG - - License: Gnu GPL Verrsion 3 - - Authors: Tobias Oetiker <tobi@oetiker.ch> - -************************************************************************ */ - -/* ************************************************************************ - +/* #asset(tr/*) - -************************************************************************ */ +*/ /** * This is the main application class of your custom application "qx08" @@ -26,7 +21,7 @@ qx.Class.define("tr.Application", { * during startup of the application * * @type member - * @return {void} + * @return {void} */ main : function() { // Call super class @@ -34,19 +29,37 @@ qx.Class.define("tr.Application", { // Enable logging in debug variant if (qx.core.Variant.isSet("qx.debug", "on")) { - // support native logging capabilities, e.g. Firebug for Firefox qx.log.appender.Native; - - // support additional cross-browser console. Press F7 to toggle visibility qx.log.appender.Console; } // if we run with a file:// url make sure // the app finds the Tr service (tr.cgi) - tr.Server.getInstance().setLocalUrl('http://localhosth/~oetiker/tr/'); + tr.Server.getInstance().setLocalUrl('http://localhost/~oetiker/tr/'); + + this.getRoot().add(new tr.ui.CopyBuffer(), { + left : 0, + top : 0 + }); + + this.getRoot().add(new tr.ui.Error(), { + left : 0, + top : 0 + }); + + this.getRoot().add(new tr.ui.Config(), { + left : 0, + top : 0 + }); + + this.getRoot().add(new tr.ui.Link('SmokeTrace 2.4.2', 'http://oss.oetiker.ch/smokeping/', '#b0b0b0', '17px bold sans-serif'), { + right : 7, + top : 5 + }); // Document is the application root var root = new qx.ui.container.Composite(new qx.ui.layout.VBox()); + root.setPadding(5); this.getRoot().add(root, { left : 0, @@ -55,40 +68,58 @@ qx.Class.define("tr.Application", { bottom : 0 }); - root.set({ margin : 10 }); - var top = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({ alignY : 'top' })); - var title = new tr.ui.Link('SmokeTrace 2.4.2', 'http://oss.oetiker.ch/smokeping/', '#b0b0b0', '20px bold sans-serif'); - - top.add(title); - top.add(new qx.ui.core.Spacer(), { flex : 1 }); - top.add(new tr.ui.ActionButton()); - root.add(top); - - var trace = new tr.ui.TraceTable(); - root.add(trace, { flex : 1 }); + var tabs = new qx.ui.tabview.TabView(); + root.add(tabs, { flex : 1 }); root.add(new tr.ui.Footer(this.tr("SmokeTrace is part of the of the SmokePing suite created by Tobi Oetiker, Copyright 2008."), 'http://oss.oetiker.ch/smokeping/')); - var cfgwin = new tr.ui.Config(); + tabs.add(new tr.ui.TraceTab()); + this.__handles = {}; + qx.event.message.Bus.subscribe('add_handle',this.__add_handle,this); + }, - this.getRoot().add(cfgwin, { - left : 30, - top : 30 - }); + __handles: null, + __handle_count: 0, - qx.event.message.Bus.subscribe('tr.config', function(e) { - switch(e.getData()) - { - case 'open': - cfgwin.open(); - break; - - case 'cancel': - case 'ok': - cfgwin.close(); - break; + __add_handle: function(m){ + var handle = m.getData(); + this.__handles[handle]=0; + if (this.__handle_count == 0){ + this.__run_poller(); + } + }, + __run_poller: function(){ + var that = this; + tr.Server.getInstance().callAsync( + function(ret,exc,id){that.__process_input(ret,exc,id);},'poll',this.__handles + ); + }, + __process_input: function(ret,exc,id){ + if (exc == null) { + for (var hand in ret){ + this.info('got '+hand); + if (hand == 'handles'){ + this.__handles = ret[hand]; + } + if (ret[hand]['data']){ + qx.event.message.Bus.dispatch(hand+'::data', ret[hand]['data']); } - }); + if (ret[hand]['type']){ + qx.event.message.Bus.dispatch(hand+'::status', {type : ret[hand]['type'], + msg : ret[hand]['msg'] }); + } + }; + } + else { + qx.event.message.Bus.dispatch('error', [ this.tr("Server Error"), '' + exc ]); + } + this.__handle_count = 0; + for(var i in this.__handles){ + this.__handle_count ++; + }; + if (this.__hanlde_count > 0){ + qx.event.Timer.once(this.__run_poller,this,this.__interval); } } - });
\ No newline at end of file + } +}); diff --git a/qx08/source/class/tr/Server.js b/qx08/source/class/tr/Server.js index c58ee53..af9cbc4 100644 --- a/qx08/source/class/tr/Server.js +++ b/qx08/source/class/tr/Server.js @@ -1,6 +1,9 @@ /* ************************************************************************ -#module(Tr) -************************************************************************ */ + Copyright: 2008, OETIKER+PARTNER AG + License: GPL + Authors: Tobias Oetiker + $Id: $ +* ************************************************************************ */ /** * A Tr specific rpc call which works @@ -10,47 +13,20 @@ qx.Class.define('tr.Server', { type : "singleton", - - - /* - ***************************************************************************** - CONSTRUCTOR - ***************************************************************************** - */ - /** - * @param local_url {String} When running the application in file:// mode. - * where will we find our RPC server. - */ + * @param local_url {String} When running the application in file:// mode, where will we find our RPC server. + */ construct : function(local_url) { this.base(arguments); this.set({ timeout : 60000, url : 'tr.cgi', - serviceName : 'Tr', - crossDomain : true + serviceName : 'tr' }); - - return this; }, - - - - /* - ***************************************************************************** - MEMBERS - ***************************************************************************** - */ - members : { - /* - --------------------------------------------------------------------------- - CORE METHODS - --------------------------------------------------------------------------- - */ - /** * Tell about the BaseUrl we found. * @@ -71,6 +47,7 @@ qx.Class.define('tr.Server', { */ setLocalUrl : function(local_url) { if (document.location.host === '') { + this.setCrossDomain(true); this.setUrl(local_url + 'tr.cgi'); } } diff --git a/qx08/source/class/tr/test/DemoTest.js b/qx08/source/class/tr/test/DemoTest.js deleted file mode 100644 index 0f6a04f..0000000 --- a/qx08/source/class/tr/test/DemoTest.js +++ /dev/null @@ -1,58 +0,0 @@ -/* ************************************************************************ - - Copyright: - - License: - - Authors: - -************************************************************************ */ - -/** - * This class demonstrates how to define unit tests for your application. - * - * Execute <code>generate.py test</code> to generate a testrunner application - * and open it from <tt>test/index.html</tt> - * - * The methods that contain the tests are instance methods with a - * <code>test</code> prefix. You can create an arbitrary number of test - * classes like this one. They can be organized in a regular class hierarchy, - * i.e. using deeper namespaces and a corresponding file structure within the - * <tt>test</tt> folder. - */ -qx.Class.define("tr.test.DemoTest", { - extend : qx.dev.unit.TestCase, - - members : { - /* - --------------------------------------------------------------------------- - TESTS - --------------------------------------------------------------------------- - */ - - /** - * Here are some simple tests - * - * @type member - * @return {void} - */ - testSimple : function() { - this.assertEquals(4, 3 + 1, "This should never fail!"); - this.assertFalse(false, "Can false be true?!"); - }, - - - /** - * Here are some more advanced tests - * - * @type member - * @return {void} - */ - testAdvanced : function() { - var a = 3; - var b = a; - this.assertIdentical(a, b, "A rose by any other name is still a rose"); - this.assertInRange(3, 1, 10, "You must be kidding, 3 can never be outside [1,10]!"); - } - } -});
\ No newline at end of file diff --git a/qx08/source/class/tr/theme/Color.js b/qx08/source/class/tr/theme/Color.js new file mode 100644 index 0000000..389e0dc --- /dev/null +++ b/qx08/source/class/tr/theme/Color.js @@ -0,0 +1,14 @@ +qx.Theme.define("tr.theme.Color", { + extend : qx.theme.modern.Color, + + colors : { + // application, desktop, ... + 'background-application' : '#f0f0f0', + + // pane color for windows, splitpanes, ... + "background-pane" : '#f8f8f8', + + // textfields, ... + 'background-light' : '#ffffff' + } +});
\ No newline at end of file diff --git a/qx08/source/class/tr/ui/ActionButton.js b/qx08/source/class/tr/ui/ActionButton.js deleted file mode 100644 index 6b91e5d..0000000 --- a/qx08/source/class/tr/ui/ActionButton.js +++ /dev/null @@ -1,239 +0,0 @@ -/* ************************************************************************ -#module(Tr) -************************************************************************ */ - -/** - * a widget showing the Tr graph overview - */ -qx.Class.define('tr.ui.ActionButton', { - extend : qx.ui.container.Composite, - - - - - /* - ***************************************************************************** - CONSTRUCTOR - ***************************************************************************** - */ - - construct : function() { - this.base(arguments, new qx.ui.layout.VBox); - - // this.set({ alignX : 'left' }); - // return this; - var hbox = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({ - alignY : 'middle', - spacing : 5 - })); - - var lab1 = new qx.ui.basic.Label(this.tr("Host")); - lab1.set({ paddingRight : 6 }); - hbox.add(lab1); - var host = new qx.ui.form.TextField(); - - host.set({ - width : 200, - padding : 1 - }); - - hbox.add(host); - this.__host = host; - var lab2 = new qx.ui.basic.Label(this.tr("Delay")); - - lab2.set({ - paddingRight : 6, - paddingLeft : 12 - }); - - hbox.add(lab2); - - var delay = new qx.ui.form.Spinner(0, 2, 60); - - delay.set({ width : 45 }); - hbox.add(delay); - this.__delay = delay; - - var lab3 = new qx.ui.basic.Label(this.tr("Rounds")); - - lab3.set({ - paddingRight : 6, - paddingLeft : 12 - }); - - hbox.add(lab3); - var rounds = new qx.ui.form.Spinner(0, 20, 200); - - rounds.set({ width : 45 }); - - hbox.add(rounds); - this.__rounds = rounds; - - var button = new qx.ui.form.Button(''); - this.__button = button; - - button.set({ - marginLeft : 10, - width : 60, - padding : 2, - center : true - }); - - hbox.add(button); - - var config = new qx.ui.form.Button(this.tr("Config ...")); - hbox.add(config); - - config.addListener('execute', function(e) { - qx.event.message.Bus.dispatch('tr.config', 'open'); - }); - - this.add(hbox); - - var info = new qx.ui.basic.Atom(); - - info.set({ - marginTop : 3, - padding : 3, - textColor : 'red', - backgroundColor : '#f0f0f0', - visibility : 'hidden' - }); - - qx.event.message.Bus.subscribe('tr.info', this.__set_info, this); - this.add(info); - this.__info = info; - - qx.event.message.Bus.subscribe('tr.status', this.__set_status, this); - qx.event.message.Bus.dispatch('tr.status', 'stopped'); - - var start_trace = function(event) { - qx.event.message.Bus.dispatch('tr.cmd', { - action : button.getUserData('action'), - host : host.getValue(), - delay : delay.getValue(), - rounds : rounds.getValue() - }); - }; - - host.addListener('keydown', function(e) { - if (e.getKeyIdentifier() == 'Enter') { - start_trace(); - } - }); - - // host.addListener('execute', start_trace); - button.addListener('execute', start_trace); - - var history = qx.bom.History.getInstance(); - - var history_action = function(event) { - var targ = event.getData(); - host.setValue(targ); - history.addToHistory(targ, 'SmokeTrace to ' + targ); - start_trace(); - }; - - history.addListener('request', history_action); - - // if we got called with a host on the commandline - var initial_host = qx.bom.History.getInstance().getState(); - - if (initial_host) { - host.setValue(initial_host); - history.addToHistory(initial_host, 'SmokeTrace to ' + initial_host); - - // dispatch this task once all the initializations are done - qx.event.Timer.once(start_trace, this, 0); - } - }, - - members : { - __host : null, - __delay : null, - __rounds : null, - __button : null, - __info : null, - - - /** - * TODOC - * - * @type member - * @param e {Event} TODOC - * @return {void} - */ - __set_info : function(e) { - this.__info.set({ - label : e.getData(), - visibility : 'visible' - }); - }, - - - /** - * TODOC - * - * @type member - * @param m {var} TODOC - * @return {void} - */ - __set_status : function(m) { - var host = this.__host; - var rounds = this.__rounds; - var delay = this.__delay; - var button = this.__button; - var action = button.getUserData('action'); - - // this.debug(m.getData()); - switch(m.getData()) - { - case 'starting': - if (action == 'go') { - button.setLabel(this.tr("Starting")); - this.__info.setVisibility('hidden'); - - // border:'dark-shadow', - button.setEnabled(false); - host.setEnabled(false); - rounds.setEnabled(false); - delay.setEnabled(false); - } - - break; - - case 'stopping': - if (action == 'stop') { - button.setLabel(this.tr("Stopping")); - button.setEnabled(false); - host.setEnabled(false); - rounds.setEnabled(false); - delay.setEnabled(false); - } - - break; - - case 'stopped': - button.setUserData('action', 'go'); - button.setLabel(this.tr("Go")); - button.setEnabled(true); - host.setEnabled(true); - rounds.setEnabled(true); - delay.setEnabled(true); - break; - - case 'started': - button.setUserData('action', 'stop'); - button.setLabel(this.tr("Stop")); - button.setEnabled(true); - host.setEnabled(false); - rounds.setEnabled(false); - delay.setEnabled(false); - break; - - default: - this.error('Unknown Status Message: ' + m.getData()); - } - } - } -});
\ No newline at end of file diff --git a/qx08/source/class/tr/ui/Cellrenderer.js b/qx08/source/class/tr/ui/Cellrenderer.js index 6a89da9..79edf4d 100644 --- a/qx08/source/class/tr/ui/Cellrenderer.js +++ b/qx08/source/class/tr/ui/Cellrenderer.js @@ -19,12 +19,12 @@ qx.Class.define('tr.ui.Cellrenderer', { /** - * Format a number with a configurable number of fraction digits - * and add optional pre and postfix. - * @param digits {Integer} how many digits should there be. Default is 0. - * @param prefix {String} optional prefix. - * @param postfix {String} optional postfix. - */ + * Format a number with a configurable number of fraction digits + * and add optional pre and postfix. + * @param digits {Integer} how many digits should there be. Default is 0. + * @param prefix {String} optional prefix. + * @param postfix {String} optional postfix. + */ construct : function(digits, postfix, prefix) { if (digits == undefined) { digits = 0; diff --git a/qx08/source/class/tr/ui/Config.js b/qx08/source/class/tr/ui/Config.js index 5d264d2..3310bee 100644 --- a/qx08/source/class/tr/ui/Config.js +++ b/qx08/source/class/tr/ui/Config.js @@ -11,15 +11,6 @@ qx.Class.define('tr.ui.Config', { extend : qx.ui.window.Window, - - - - /* - ***************************************************************************** - CONSTRUCTOR - ***************************************************************************** - */ - construct : function() { this.base(arguments, this.tr("Traceroute Configuration")); var layout = new qx.ui.layout.Grid(3, 5); @@ -39,87 +30,114 @@ qx.Class.define('tr.ui.Config', { showMinimize : false }); - var self = this; + var that = this; var create_config = function(retval, exc, id) { if (exc == null) { - self.__create_config(retval); + that.__create_config(retval); } else { - self.error(exc); + qx.event.message.Bus.dispatch('error', [ that.tr("Server Error"), '' + exc ]); } }; tr.Server.getInstance().callAsync(create_config, 'get_config'); + + qx.event.message.Bus.subscribe('config', function(e) { + this.__task = e.getData(); + this.__seed(); + this.center(); + this.open(); + }, + this); }, members : { + __task : null, + __setters : null, + + + /** + * Load configuration values into dialog. If no values are provided, + * the default values get loaded. + * + * @type member + * @return {void} + */ + __seed : function() { + for (var key in this.__setters) { + this.info(key+': '+this.__task[key]) + this.__setters[key](this.__task[key]); + } + }, + + /** * TODOC * * @type member * @param data {var} TODOC - * @return {void} + * @return {void} */ __create_config : function(data) { var entries = data.length; var status = {}; - var setdef = {}; - var r = 0; - var self = this; + var setters = {}; + this.__setters = setters; - for (var k=0; k<entries; k+=2) { - (function() {* // force local scoping - var v = k + 1; + var r = 0; + var that = this; + for (var k=0; k<entries; k++) { + (function() { for (var check in { 'default' : 0, 'label' : 0, 'type' : 0 }) { - if (data[v][check] == undefined) { - self.debug('Skipping ' + data[k] + ' since there is no ' + check); - return ;* // we are inside a function, so we return instead of continue + if (data[k][check] == undefined) { + that.debug('Skipping ' + data[k] + ' since there is no ' + check); + // exit from function is like 'next' + return; } } - var def = data[v]['default']; + var def = data[k]['default']; var widget; var pick; var items; - var check; var c; - self.add(new qx.ui.basic.Label(data[v]['label']).set({ marginRight : 5 }), { + that.add(new qx.ui.basic.Label(data[k]['label']).set({ marginRight : 5 }), { row : r, column : 0 }); - switch(data[v]['type']) + switch(data[k]['type']) { case 'spinner': - widget = new qx.ui.form.Spinner(data[v]['min'], def, data[v]['max']); - status[data[k]] = function() { + widget = new qx.ui.form.Spinner(data[k]['min'], def, data[k]['max']); + status[data[k]['key']] = function() { return widget.getValue(); }; - setdef[data[k]] = function() { - widget.setValue(def); + setters[data[k]['key']] = function(value) { + widget.setValue(value == undefined ? def : value); }; break; case 'select': widget = new qx.ui.form.SelectBox(); - status[data[k]] = function() { + status[data[k]['key']] = function() { return widget.getValue(); }; - setdef[data[k]] = function() { - widget.setValue(def); + setters[data[k]['key']] = function(value) { + widget.setValue(value == undefined ? def : value); }; - pick = data[v]['pick']; + pick = data[k]['pick']; items = pick.length; for (c=0; c<items; c+=2) { @@ -130,45 +148,37 @@ qx.Class.define('tr.ui.Config', { case 'boolean': widget = new qx.ui.form.CheckBox(); - status[data[k]] = function() { + status[data[k]['key']] = function() { return widget.getChecked(); }; - setdef[data[k]] = function() { - widget.setChecked(def > 0); + setters[data[k]['key']] = function(value) { + widget.setChecked(value == undefined ? def > 0 : value > 0); }; break; } - self.add(widget, { + that.add(widget, { row : r, column : 1 }); r++; })(); - - }* // this is the rest of the scoping trick - - for (var key in setdef) { - setdef[key](); } var ok = new qx.ui.form.Button(this.tr("Apply")).set({ marginTop : 10, - marginLeft : 20 + marginLeft : 40 }); ok.addListener('execute', function(e) { - var config = {}; - for (var key in status) { - config[key] = status[key](); + that.__task[key] = status[key](); } - self.close(); - qx.event.message.Bus.dispatch('tr.setup', config); + that.close(); }); this.add(ok, { @@ -178,12 +188,12 @@ qx.Class.define('tr.ui.Config', { var cancel = new qx.ui.form.Button(this.tr("Reset")).set({ marginTop : 10, - marginRight : 20 + marginRight : 30 }); cancel.addListener('execute', function(e) { - for (var key in setdef) { - setdef[key](); + for (var key in setters) { + setters[key](); } }); @@ -193,4 +203,4 @@ qx.Class.define('tr.ui.Config', { }); } } -});
\ No newline at end of file +}); diff --git a/qx08/source/class/tr/ui/CopyBuffer.js b/qx08/source/class/tr/ui/CopyBuffer.js new file mode 100644 index 0000000..a48fee2 --- /dev/null +++ b/qx08/source/class/tr/ui/CopyBuffer.js @@ -0,0 +1,45 @@ +/* ************************************************************************ + Copyright: 2008, OETIKER+PARTNER AG + License: GPL + Authors: Tobias Oetiker + $Id: $ +* ************************************************************************ */ + +/** + * Place an instance of this widget into the application root. It will remain + * invisible. I will listen on the 'copy' bus for data to get ready for copying with + * [ctrl]+[c] + */ +qx.Class.define('tr.ui.CopyBuffer', { + extend : qx.ui.form.TextArea, + + construct : function() { + this.base(arguments); + + this.set({ + width : 0, + height : 0, + allowGrowX : false, + allowGrowY : false, + decorator : null + }); + + qx.event.message.Bus.subscribe('copy', this.__copy, this); + }, + + members : { + /** + * TODOC + * + * @type member + * @param m {var} TODOC + * @return {void} + */ + __copy : function(m) { + var data = m.getData(); + this.info('set: ' + data); + this.setValue(data); + this.selectAll(); + } + } +});
\ No newline at end of file diff --git a/qx08/source/class/tr/ui/Footer.js b/qx08/source/class/tr/ui/Footer.js index 6b77aa3..4e2a2d2 100644 --- a/qx08/source/class/tr/ui/Footer.js +++ b/qx08/source/class/tr/ui/Footer.js @@ -12,10 +12,10 @@ qx.Class.define('tr.ui.Footer', { /* - ***************************************************************************** - CONSTRUCTOR - ***************************************************************************** - */ + ***************************************************************************** + CONSTRUCTOR + ***************************************************************************** + */ construct : function(text, url) { this.base(arguments, new qx.ui.layout.HBox().set({ alignX : 'right' })); diff --git a/qx08/source/class/tr/ui/Link.js b/qx08/source/class/tr/ui/Link.js index dd7f33e..d7bd28b 100644 --- a/qx08/source/class/tr/ui/Link.js +++ b/qx08/source/class/tr/ui/Link.js @@ -12,17 +12,17 @@ qx.Class.define('tr.ui.Link', { /* - ***************************************************************************** - CONSTRUCTOR - ***************************************************************************** - */ + ***************************************************************************** + CONSTRUCTOR + ***************************************************************************** + */ /** - * @param text {String} Initial label - * @param url {String} Where to link to - * @param color {String} Hex Color for the text - * @param font {String} Font from string representation - */ + * @param text {String} Initial label + * @param url {String} Where to link to + * @param color {String} Hex Color for the text + * @param font {String} Font from string representation + */ construct : function(text, url, color, font) { this.base(arguments, text); diff --git a/qx08/source/class/tr/ui/TraceTable.js b/qx08/source/class/tr/ui/TraceTable.js index 2291c2c..42ed8cc 100644 --- a/qx08/source/class/tr/ui/TraceTable.js +++ b/qx08/source/class/tr/ui/TraceTable.js @@ -1,6 +1,9 @@ /* ************************************************************************ -#module(Tr) -************************************************************************ */ + Copyright: 2008, OETIKER+PARTNER AG + License: GPL + Authors: Tobias Oetiker + $Id: $ +* ************************************************************************ */ /** * a widget showing the Tr target tree @@ -8,21 +11,11 @@ qx.Class.define('tr.ui.TraceTable', { extend : qx.ui.table.Table, - - - - /* - ***************************************************************************** - CONSTRUCTOR - ***************************************************************************** - */ - construct : function() { var tableModel = new qx.ui.table.model.Simple(); this.__tableModel = tableModel; - tableModel.setColumns([ this.tr("Hop"), this.tr("Host"), this.tr("Ip"), this.tr("Loss [%]"), this.tr("Sent"), this.tr("Last [ms]"), * // "; help syntax highliter - this.tr("Avg [ms]"), this.tr("Best [ms]"), this.tr("Worst [ms]"), this.tr("StDev [ms]") ]); + tableModel.setColumns([ this.tr("Hop"), this.tr("Host"), this.tr("Ip"), this.tr("Loss [%]"), this.tr("Sent"), this.tr("Last [ms]"), this.tr("Avg [ms]"), this.tr("Best [ms]"), this.tr("Worst [ms]"), this.tr("StDev [ms]") ]); var custom = { tableColumnModel : function(obj) { @@ -63,18 +56,8 @@ qx.Class.define('tr.ui.TraceTable', { resizeBehavior.set(i, { width : '3*' }); } - qx.event.message.Bus.subscribe('tr.cmd', this.__handle_tr, this); }, - - - - /* - ***************************************************************************** - Statics - ***************************************************************************** - */ - members : { __handle : null, __data : null, @@ -116,151 +99,191 @@ qx.Class.define('tr.ui.TraceTable', { * TODOC * * @type member - * @param m {var} TODOC + * @param retval {var} TODOC + * @param exc {Exception} TODOC + * @param id {var} TODOC * @return {void} */ - __handle_tr : function(m) { - var self = this; - var f_host = 1, f_ip = 2, f_loss = 3, f_snt = 4, f_last = 5, f_avg = 6, f_best = 7, f_worst = 8, f_stdev = 9, f_cnt = 10, f_sum = 11, f_sqsum = 12; - var fill_table; - - fill_table = function(retval, exc, id) { - if (exc == null) { - if (self.__handle == undefined) { - qx.event.message.Bus.dispatch('tr.status', 'started'); - } + __fill_table : function(retval, exc, id) { + var f_host = 1; + var f_ip = 2; + var f_loss = 3; + var f_snt = 4; + var f_last = 5; + var f_avg = 6; + var f_best = 7; + var f_worst = 8; + var f_stdev = 9; + var f_cnt = 10; + var f_sum = 11; + var f_sqsum = 12; + var that = this; + + if (exc == null) { + if (this.__handle == undefined) { + qx.event.message.Bus.dispatch('tr.status', 'started'); + } - self.__handle = retval['handle']; - var tableModel = self.__tableModel; - var lines = retval['output'].length; - var data = self.__data; - var sleep = 0; + this.__handle = retval['handle']; + var tableModel = this.__tableModel; + var lines = retval['output'].length; + var data = this.__data; + var sleep = 0; - for (var i=0; i<lines; i++) { - sleep = 0; - var hop = retval['output'][i][0]; + for (var i=0; i<lines; i++) { + sleep = 0; + var hop = retval['output'][i][0]; - if (hop == 'SLEEP') { - sleep = retval['output'][i][1]; - continue; - } - else if (hop == 'INFO') { - qx.event.message.Bus.dispatch('tr.info', retval['output'][i][1]); - continue; + if (hop == 'SLEEP') { + sleep = retval['output'][i][1]; + continue; + } + else if (hop == 'INFO') { + qx.event.message.Bus.dispatch('tr.info', retval['output'][i][1]); + continue; + } + + var host = retval['output'][i][1]; + var ip = retval['output'][i][2]; + var value = retval['output'][i][3]; + var ii = 0; + var max = data.length; + + while (true) { + if (ii == max) { + break; } - var host = retval['output'][i][1]; - var ip = retval['output'][i][2]; - var value = retval['output'][i][3]; - var ii = 0; - var max = data.length; + if (Math.floor(data[ii][0]) > hop) { + break; + } - while (true) { - if (ii == max) { + if (Math.floor(data[ii][0]) == hop) { + if (ip == undefined) { break; } - if (Math.floor(data[ii][0]) > hop) { + if (ip == data[ii][2]) { break; } - - if (Math.floor(data[ii][0]) == hop) { - if (ip == undefined) { - break; - } - - if (ip == data[ii][2]) { - break; - } - } - - ii++; } - if (ii == max || Math.floor(data[ii][0]) > hop) { - if (ii > 0 && Math.floor(data[ii - 1][0]) == hop) { - hop = data[ii - 1][0] + 0.1; - } + ii++; + } - data.splice(ii, 0, self.__make_empty_row()); - data[ii][0] = hop; + if (ii == max || Math.floor(data[ii][0]) > hop) { + if (ii > 0 && Math.floor(data[ii - 1][0]) == hop) { + hop = data[ii - 1][0] + 0.1; } - var drow = data[ii]; + data.splice(ii, 0, this.__make_empty_row()); + data[ii][0] = hop; + } - if (drow[f_host] == undefined && host != undefined) { - drow[f_host] = host; - } + var drow = data[ii]; - if (drow[f_ip] == undefined && ip != undefined) { - drow[f_ip] = ip; - } + if (drow[f_host] == undefined && host != undefined) { + drow[f_host] = host; + } - drow[f_snt]++; - drow[f_last] = value; + if (drow[f_ip] == undefined && ip != undefined) { + drow[f_ip] = ip; + } - if (value != undefined) { - var best = drow[f_best]; + drow[f_snt]++; + drow[f_last] = value; - if (best == undefined || best > value) { - drow[f_best] = value; - } + if (value != undefined) { + var best = drow[f_best]; - var worst = drow[f_worst]; + if (best == undefined || best > value) { + drow[f_best] = value; + } - if (worst == undefined || worst < value) { - drow[f_worst] = value; - } + var worst = drow[f_worst]; - drow[f_sum] += value; - var sum = drow[f_sum]; - drow[f_cnt]++; - var cnt = drow[f_cnt]; - var sqsum = drow[f_sqsum] + value * value; - drow[f_sqsum] = sqsum; - drow[f_avg] = drow[f_sum] / drow[f_cnt]; - drow[f_stdev] = Math.sqrt((cnt * sqsum - sum * sum) / (cnt * (cnt - 1))); + if (worst == undefined || worst < value) { + drow[f_worst] = value; } - drow[f_loss] = ((drow[f_snt] - drow[f_cnt]) / drow[f_snt]) * 100; + drow[f_sum] += value; + var sum = drow[f_sum]; + drow[f_cnt]++; + var cnt = drow[f_cnt]; + var sqsum = drow[f_sqsum] + value * value; + drow[f_sqsum] = sqsum; + drow[f_avg] = drow[f_sum] / drow[f_cnt]; + drow[f_stdev] = Math.sqrt((cnt * sqsum - sum * sum) / (cnt * (cnt - 1))); } - tableModel.setData(data); + drow[f_loss] = ((drow[f_snt] - drow[f_cnt]) / drow[f_snt]) * 100; + } - if (retval['again']) { - var next_round = function() { - tr.Server.getInstance().callAsync(fill_table, 'run_tr', { - handle : retval['handle'], - point : retval['point'] - }); - }; + tableModel.setData(data); - qx.event.Timer.once(next_round, self, sleep * 1000); - } - else { - self.__stop_table(); - } + if (retval['again']) { + var next_round = function() { + tr.Server.getInstance().callAsync(function(ret, exc, id) { + that.__fill_table(ret, exc, id); + }, 'run_tr', { + handle : retval['handle'], + point : retval['point'] + }); + }; + + qx.event.Timer.once(next_round, this, sleep * 1000); } else { - self.__stop_table(); + this.__stop_table(); } - }; + } + else { + this.__stop_table(); + } + }, + + + /** + * TODOC + * + * @type member + * @param data {var} TODOC + * @param exc {Exception} TODOC + * @param id {var} TODOC + * @return {void} + */ + __stop_handler : function(data, exc, id) { + if (exc == null) { + qx.event.message.Bus.dispatch('tr.status', 'stopped'); + } else { + this.error(exc); + } + }, + + subscribe: function(handle){ + qx.event.message.Bus.subscribe(handle+'::data', this.__handle_tr, this); + }, - var stop_handler = function(data, exc, id) { - if (exc == null) { - qx.event.message.Bus.dispatch('tr.status', 'stopped'); - } else { - this.error(exc); - } - }; + /** + * TODOC + * + * @type member + * @param m {var} TODOC + * @return {void} + */ + __handle_tr : function(m) { + var that = this; var cmd = m.getData(); switch(cmd['action']) { case 'stop': qx.event.message.Bus.dispatch('tr.status', 'stopping'); - tr.Server.getInstance().callAsync(stop_handler, 'stop_tr', this.__handle); + tr.Server.getInstance().callAsync(function(ret, exc, id) { + that.__stop_handler(ret, exc, id); + }, 'stop_tr', this.__handle); + break; case 'go': @@ -273,7 +296,9 @@ qx.Class.define('tr.ui.TraceTable', { } qx.event.message.Bus.dispatch('tr.status', 'starting'); - tr.Server.getInstance().callAsync(fill_table, 'run_tr', { + tr.Server.getInstance().callAsync(function(ret, exc, id) { + that.__fill_table(ret, exc, id); + }, 'run_tr', { host : cmd['host'], rounds : cmd['rounds'], delay : cmd['delay'] @@ -286,4 +311,4 @@ qx.Class.define('tr.ui.TraceTable', { } } } -});
\ No newline at end of file +}); |