summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/Qooxdoo/JSONRPC.pm970
-rw-r--r--lib/Qooxdoo/Services/tr.pm295
-rw-r--r--qx08/source/class/tr/Application.js123
-rw-r--r--qx08/source/class/tr/Server.js41
-rw-r--r--qx08/source/class/tr/test/DemoTest.js58
-rw-r--r--qx08/source/class/tr/theme/Color.js14
-rw-r--r--qx08/source/class/tr/ui/ActionButton.js239
-rw-r--r--qx08/source/class/tr/ui/Cellrenderer.js12
-rw-r--r--qx08/source/class/tr/ui/Config.js116
-rw-r--r--qx08/source/class/tr/ui/CopyBuffer.js45
-rw-r--r--qx08/source/class/tr/ui/Footer.js8
-rw-r--r--qx08/source/class/tr/ui/Link.js18
-rw-r--r--qx08/source/class/tr/ui/TraceTable.js279
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
+});