aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs
diff options
context:
space:
mode:
authorMike Mazur2009-01-28 01:56:04 +0000
committerMike Mazur2009-01-28 01:56:04 +0000
commitfefe0ff3d9ceaaefccb494ef13a5a98cc0131070 (patch)
tree01c5057fb9f23c55f07ff102906f5f14d6453118 /ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs
parentSlight cleanup of docs, removing trailing whitespace. (diff)
downloadopensim-SC-fefe0ff3d9ceaaefccb494ef13a5a98cc0131070.zip
opensim-SC-fefe0ff3d9ceaaefccb494ef13a5a98cc0131070.tar.gz
opensim-SC-fefe0ff3d9ceaaefccb494ef13a5a98cc0131070.tar.bz2
opensim-SC-fefe0ff3d9ceaaefccb494ef13a5a98cc0131070.tar.xz
Removing contents of ThirdParty/3Di. The load balancer can now be found
at http://forge.opensimulator.org/gf/project/loadbalancer/ config key: svn.rmdir
Diffstat (limited to 'ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs')
-rw-r--r--ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MonitorGUI/View.pm214
-rw-r--r--ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MyCGI.pm91
-rw-r--r--ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/XML/RPC.pm100
-rw-r--r--ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/monitor.cgi202
-rw-r--r--ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/readme.txt21
5 files changed, 0 insertions, 628 deletions
diff --git a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MonitorGUI/View.pm b/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MonitorGUI/View.pm
deleted file mode 100644
index bab462f..0000000
--- a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MonitorGUI/View.pm
+++ /dev/null
@@ -1,214 +0,0 @@
1package MonitorGUI::View;
2
3use strict;
4
5my @server_list;
6my $max_port;
7my $regions;
8
9sub screen_header {
10 return << "HEADER";
11<HTML>
12<HEAD>
13<STYLE TYPE="text/css">
14<!--
15a:link {font-size: 12pt; text-decoration:none; color:#0000ff ;}
16a:visited {font-size: 12pt; text-decoration:none; color:#ff0000 ;}
17a:active {font-size: 12pt; text-decoration:none; color:#00ff00 ;}
18a:hover {font-size: 12pt; text-decoration:underline; color:#ff00ff ;}
19td {font-size: 12pt;border:0;}
20th {background-color:#000000; font-size: 12pt;border:0; color:#FFFFFF; }
21tr {background-color:#FFFFFF; }
22b {font-size: 12pt;}
23//table {background-color:#000000; }
24-->
25</STYLE>
26<META http-equiv="content-type" content="text/html;charset=UTF-8" />
27<META name="refresh" content="300" />
28<TITLE>Region Monitor GUI, 3Di</TITLE>
29</HEAD>
30<BODY>
31HEADER
32}
33
34sub screen_footer {
35 return << "FOOTER";
36</BODY>
37</HTML>
38FOOTER
39}
40
41sub html {
42 my $grid_info = shift;
43 my $regions_list = $grid_info->{"sim-profiles"};
44 $regions = undef;
45 foreach(@$regions_list) {
46 my $ip = $_->{sim_ip} || "UNKNOWN";
47 my $port = $_->{sim_port} || "UNKNOWN";
48 $regions->{$ip}->{$port} = $_;
49 if (!$regions->{max_port} || $regions->{max_port} < $port) {
50 $regions->{max_port} = $port;
51 }
52 }
53 @server_list = keys %$regions;
54 $max_port = $regions->{max_port};
55 my $html = "";
56 foreach my $machine (@server_list) {
57 next if ($machine eq "max_port");
58 $html .= &_machine_view($machine, $regions->{$machine});
59 }
60 return $html;
61}
62
63sub _machine_view {
64 my ($ip, $info) = @_;
65 my $region_html = "";
66 foreach my $region (keys %$info) {
67 $region_html .= &_region_html($info->{$region});
68 }
69 my $html =<< "MACHINE_HTML";
70<h3>$ip</h3>
71$region_html
72<hr size=0 noshade />
73MACHINE_HTML
74}
75
76sub _region_html {
77 my $region_info = shift;
78 my $name = $region_info->{name} || "UNKNOWN";
79 my $x = $region_info->{x} || -1;
80 my $y = $region_info->{y} || -1;
81 my $ip = $region_info->{sim_ip} || "UNKNOWN";
82 my $port = $region_info->{sim_port} || "UNKNOWN";
83 my $get_scene_presence_filter = $region_info->{get_scene_presence_filter};
84 my $get_scene_presence = $region_info->{get_scene_presence};
85 my $get_avatar_filter = $region_info->{get_avatar_filter};
86 my $get_avatar = $region_info->{get_avatar};
87 my $avatar_names = $region_info->{avatar_names};
88 my $action_forms = &_action_forms($region_info);
89 my $html = <<"REGION_HTML";
90<strong>$name</strong><br/>
91$ip:$port | X: $x Y: $y<br/>
92<table border="0">
93<tr>
94<td>get_avatar</td>
95<td>$get_avatar</td>
96<td></td>
97</tr>
98<tr>
99<td>get_avatar_filter</td>
100<td>$get_avatar_filter</td>
101<td>$avatar_names</td>
102</tr>
103<tr>
104<td>get_scene_presence</td>
105<td>$get_scene_presence</td>
106<td></td>
107</tr>
108<tr>
109<td>get_scene_presence_filter</td>
110<td>$get_scene_presence_filter</td>
111<td></td>
112</tr>
113</table>
114$action_forms
115REGION_HTML
116 return $html;
117}
118
119sub _action_forms {
120 my $region_info = shift;
121 my $ip = $region_info->{sim_ip};
122 my $port = $region_info->{sim_port};
123 my $default_input_port = $max_port + 1;
124 my $move_to_options = "";
125 my $split_to_options = "";
126 my $merge_ip_options = "";
127 foreach(@server_list) {
128 next if ($_ eq "max_port");
129 $merge_ip_options .= "<option value=\"$_\">$_\n";
130 $split_to_options .= "<option value=\"$_\">$_\n";
131 #next if ($_ eq $ip);
132 $move_to_options .= "<option value=\"$_\">$_\n";
133 }
134 my $merge_port_options = "";
135 my $merge_disabled = "disabled";
136
137 foreach(keys %{$regions->{$ip}}) {
138 next if ($_ eq $port);
139 $merge_disabled = "";
140 }
141# for(9000..$max_port) { # TODO :
142# next if ($_ eq $port);
143# $merge_port_options .= "<option value=\"$_\">$_\n";
144# }
145 my %port = ();
146 foreach my $ip (keys %$regions) {
147 next if ($ip eq "max_port");
148 print STDERR "--" . $ip . "\n";
149 foreach my $region_port (keys %{$regions->{$ip}}) {
150 print STDERR "---" . $region_port . "\n";
151 $port{$region_port} = 1;
152 }
153 }
154 foreach (keys %port) {
155 $merge_port_options .= "<option value=\"$_\">$_\n";
156 $merge_disabled = "";
157 }
158 return << "ACTION_FORMS";
159<table>
160<tr>
161<form method="POST">
162<td>
163<input type="hidden" name="A" value="move" />
164<input type="hidden" name="from_ip" value="$ip" />
165<input type="hidden" name="from_port" value="$port" />
166<input type="submit" value="Move to" />
167<select name="to_ip">
168$move_to_options
169</select>:
170<input type="text" name="to_port" size="5" value="$default_input_port"/>
171</td>
172</form>
173
174<td>
175&nbsp;&nbsp;|&nbsp;&nbsp;
176</td>
177
178<form method="POST">
179<td>
180<input type="hidden" name="A" value="split" />
181<input type="hidden" name="from_ip" value="$ip" />
182<input type="hidden" name="from_port" value="$port" />
183<input type="submit" value="Split to" />
184<select name="to_ip">
185$split_to_options
186</select>:
187<input type="text" name="to_port" size="5" value="$default_input_port"/>
188</td>
189</form>
190
191<td>
192&nbsp;&nbsp;|&nbsp;&nbsp;
193</td>
194
195<form method="POST">
196<td>
197<input type="hidden" name="A" value="merge" />
198<input type="hidden" name="from_ip" value="$ip" />
199<input type="hidden" name="master_port" value="$port" />
200<input type="submit" value="Merge" $merge_disabled />
201<select name="slave_ip" $merge_disabled>
202$merge_ip_options
203</select>
204<select name="slave_port" $merge_disabled>
205$merge_port_options
206</select>
207</td>
208</form>
209</tr>
210</table>
211ACTION_FORMS
212}
213
2141;
diff --git a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MyCGI.pm b/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MyCGI.pm
deleted file mode 100644
index 1f232aa..0000000
--- a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/MyCGI.pm
+++ /dev/null
@@ -1,91 +0,0 @@
1package MyCGI;
2
3use strict;
4use CGI;
5
6sub getParam {
7 my $cgi;
8 if ($ARGV[0]) {
9 $cgi = new CGI($ARGV[0]);
10 } else {
11 $cgi = new CGI;
12 }
13 my @param_names = $cgi->param();
14 my %param = ();
15 foreach (@param_names) {
16 $param{$_} = $cgi->param($_);
17 }
18 return \%param;
19}
20
21sub getCookie {
22 my $name = shift;
23 my $cookie_value = &CGI::cookie($name);
24 return &_parse($cookie_value);
25}
26
27sub outputHtml {
28 my ($charset, $html) = @_;
29 print &CGI::header(-charset => $charset);
30 print $html;
31}
32
33sub outputXml {
34 my ($charset, $xml) = @_;
35 print &CGI::header( -type => 'text/xml', -charset => $charset );
36 print $xml;
37}
38
39sub makeCookieValue {
40 my $param = shift;
41 my @data = ();
42 foreach(keys %$param) {
43 push(@data, $_ . "=" . $param->{$_});
44 }
45 return join("&", @data);
46}
47
48sub setCookie {
49 my $param = shift;
50 my $cookie = &CGI::cookie(
51 -name => $param->{name} || return,
52 -value => $param->{value},
53 -domain => $param->{domain},
54 -path => $param->{path},
55 -expires => $param->{expires},
56 );
57 return &CGI::header(-cookie => $cookie);
58}
59
60sub redirect {
61 my $dest = shift;
62 &CGI::redirect($dest);
63}
64
65sub urlEncode {
66 my $str = shift;
67 $str =~ s/([^\w ])/'%'.unpack('H2', $1)/eg;
68 $str =~ tr/ /+/;
69 return $str;
70}
71
72sub urlDecode {
73 my $str = shift;
74 $str =~ tr/+/ /;
75 $str =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg;
76 return $str;
77}
78
79sub _parse {
80 my $value = shift;
81 my @pair = split(/&/, $value);
82 my %data = ();
83 foreach(@pair) {
84 my ($name, $value) = split(/=/, $_);
85 $data{$name} = $value;
86 }
87 return \%data;
88}
89
901;
91
diff --git a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/XML/RPC.pm b/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/XML/RPC.pm
deleted file mode 100644
index 5d9b388..0000000
--- a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/XML/RPC.pm
+++ /dev/null
@@ -1,100 +0,0 @@
1package XML::RPC;
2
3use strict;
4use Carp;
5use RPC::XML;
6use RPC::XML::Parser;
7use RPC::XML::Client;
8
9sub new {
10 my ($this, $url) = @_;
11 my %fields = (
12 parser => new RPC::XML::Parser(),
13 url => $url,
14 );
15 return bless \%fields, $this;
16}
17
18sub receive {
19 my ($this, $xmldata, $handler) = @_;
20 my $response = undef;
21 eval {
22 my $request = $this->{parser}->parse($xmldata);
23 my @args = map {$_->value} @{$request->args};
24 $response = $handler->($request->{name}, @args);
25 };
26 if ($@) {
27 my %error = (
28 "error" => "ERROR",
29 "message" => $@,
30 );
31 $response = \%error;
32 }
33 if ( ref($response) eq "RPC::XML::response" ) {
34 return $response->as_string;
35 }
36 else {
37 return RPC::XML::response->new($response)->as_string;
38 }
39}
40
41sub call {
42 my ($this, $method_name, $param) = @_;
43 if (!$this->{url}) {
44 Carp::croak("XMLRPC: url not set for calling $method_name");
45 }
46 my $client = RPC::XML::Client->new($this->{url});
47 my $request_param = undef;
48 my $req = undef;
49 if (ref $param eq "ARRAY") {
50 $request_param = &_make_array_param($param);
51 $req = RPC::XML::request->new(
52 $method_name,
53 @$request_param,
54 );
55 } elsif (ref $param eq "HASH"){
56 $request_param = &_make_hash_param($param);
57 $req = RPC::XML::request->new(
58 $method_name,
59 $request_param,
60 );
61 } else {
62 Carp::croak("unexpected param type");
63 }
64 my $rpc_res = undef;
65 eval {
66 $rpc_res = $client->send_request($req);
67 };
68 if ($@) {
69 Carp::croak("request " . $this->{url} . "/" . $method_name . " failed. $@" );
70 }
71 if (ref($rpc_res) eq "RPC::XML::struct") {
72 my %res = map { $_ => $rpc_res->{$_}->value } keys %$rpc_res; # remember good perl !!
73 return \%res;
74 } elsif (ref($rpc_res) eq "RPC::XML::string") {
75 return $rpc_res->value;
76 } else {
77 return undef;
78 }
79}
80
81sub _make_array_param {
82 my $param = shift;
83 my @array_param = ();
84 foreach (@$param) {
85 push @array_param, RPC::XML::string->new($_); # @@@ only string type
86 }
87 return \@array_param;
88}
89
90sub _make_hash_param {
91 my $param = shift;
92 my %hash_param = ();
93 foreach (keys %$param) {
94 $hash_param{$_} = RPC::XML::string->new($param->{$_}); # @@@ only string type
95 }
96 return RPC::XML::struct->new(\%hash_param);
97}
98
991;
100
diff --git a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/monitor.cgi b/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/monitor.cgi
deleted file mode 100644
index a5f6445..0000000
--- a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/monitor.cgi
+++ /dev/null
@@ -1,202 +0,0 @@
1#!/usr/bin/perl -w
2
3use strict;
4use Carp;
5use MyCGI;
6use XML::RPC;
7use MonitorGUI::View;
8
9use vars qw ($THIS_URL $GRID_SERVER_URL $DEFAULT_PROXY_PORT);
10$THIS_URL = "http://10.8.1.165/monitorgui/monitor.cgi";
11$GRID_SERVER_URL = "http://10.8.1.165/opensim/grid.cgi";
12$DEFAULT_PROXY_PORT = 9000;
13
14my %ACTIONS = (
15 # Region commands
16 move => \&move_command,
17 split => \&split_command,
18 merge => \&merge_command,
19 # display commands
20 default => \&main_screen,
21 refresh => \&refresh,
22);
23
24# ##################
25# main
26my $param = &MyCGI::getParam;
27my $act = $param->{A} || "default";
28my $contents = "";
29if (!$ACTIONS{$act}) {
30 &gui_error("404 NOT FOUND");
31} else {
32 eval {
33 $ACTIONS{$act}->($param);
34 };
35 if ($@) {
36 &gui_error($@);
37 }
38}
39
40# #################
41# Region Commands
42sub move_command {
43 my $param = shift;
44 # from
45 my $from_ip = $param->{from_ip} || Carp::croak("not enough params (from_ip)");
46 my $from_port = $param->{from_port} || Carp::croak("not enough params (from_port)");
47 my $from_url = "http://" . $param->{from_ip} . ":" . $DEFAULT_PROXY_PORT;
48 # to
49 my $to_ip = $param->{to_ip} || Carp::croak("not enough params (to_ip)");
50 my $to_port = $param->{to_port} || Carp::croak("not enough params (to_port)");
51 my $to_url = "http://" . $param->{to_ip} . ":" . $DEFAULT_PROXY_PORT;
52 # commands
53 eval {
54 &OpenSim::Utility::XMLRPCCall_array($from_url, "SerializeRegion", [$from_ip, $from_port]);
55 &OpenSim::Utility::XMLRPCCall_array($to_url, "DeserializeRegion_Move", [$from_ip, $from_port, $to_ip, $to_port]);
56 &OpenSim::Utility::XMLRPCCall_array($from_url, "TerminateRegion", [$from_port]);
57 };
58 if ($@) {
59 print STDERR "Get Status Error: $@\n";
60 }
61
62 # client refresh
63 &redirect_refresh({wait=>5, force=>"$from_url|$to_url", msg=>"Move region $from_ip:$from_port from $from_url to $to_url"});
64}
65
66sub split_command {
67 my $param = shift;
68 # from
69 my $from_ip = $param->{from_ip} || Carp::croak("not enough params (from_ip)");
70 my $from_port = $param->{from_port} || Carp::croak("not enough params (from_port)");
71 my $from_url = "http://" . $param->{from_ip} . ":" . $DEFAULT_PROXY_PORT;
72 # to
73 my $to_ip = $param->{to_ip} || Carp::croak("not enough params (to_ip)");
74 my $to_port = $param->{to_port} || Carp::croak("not enough params (to_port)");
75 my $to_url = "http://" . $param->{to_ip} . ":" . $DEFAULT_PROXY_PORT;
76 # commands
77 eval {
78 &OpenSim::Utility::XMLRPCCall_array($from_url, "SerializeRegion", [$from_ip, $from_port]);
79 &OpenSim::Utility::XMLRPCCall_array($to_url, "DeserializeRegion_Clone", [$from_ip, $from_port, $to_ip, $to_port]);
80 };
81 if ($@) {
82 print STDERR "Get Status Error: $@\n";
83 }
84
85 &redirect_refresh({wait=>5, force=>"$from_url", msg=>"Split region $from_ip:$from_port"});
86}
87
88sub merge_command {
89 my $param = shift;
90 # from
91 my $from_ip = $param->{from_ip} || Carp::croak("not enough params (from_ip)");
92 my $url = "http://" . $param->{from_ip} . ":" . $DEFAULT_PROXY_PORT;
93 # ports
94 my $master_port = $param->{master_port} || Carp::croak("not enough params (master_port)");
95 my $slave_ip = $param->{slave_ip} || Carp::croak("not enough params (slave_ip)");
96 my $slave_port = $param->{slave_port} || Carp::croak("not enough params (slave_port)");
97 my $slave_url = "http://" . $param->{slave_ip} . ":" . $DEFAULT_PROXY_PORT;
98 # commands
99 eval {
100 &XMLRPCCall_array($url, "MergeRegions", [$from_ip, $master_port]);
101 &XMLRPCCall_array($slave_url, "TerminateRegion", [$slave_port]);
102 };
103 if ($@) {
104 print STDERR "Get Status Error: $@\n";
105 }
106 &redirect_refresh({wait=>5, force=>"$url", msg=>"Merge region $from_ip:$master_port, $slave_port"});
107}
108
109# #################
110# Display
111sub main_screen {
112 my %xml_rpc_param = (
113 # TODO: should be 0 - 65535 ?
114 xmin => 999, ymin => 999, xmax => 1010, ymax => 1010,
115 );
116 my $res_obj = undef;
117 eval {
118 $res_obj = &XMLRPCCall($GRID_SERVER_URL, "map_block", \%xml_rpc_param);
119 };
120 if ($@) {
121 &gui_error("map_block Error: " . $@);
122 }
123 my %copy_obj = %$res_obj;
124 my $getstatus_failed = "<font color=\"red\">GetStatus Failed</font>";
125 my $regions_list = $res_obj->{"sim-profiles"};
126 foreach(@$regions_list) {
127 if ($_->{sim_ip} && $_->{sim_port}) {
128 my $url = "http://" . $_->{sim_ip} . ":" . $DEFAULT_PROXY_PORT;
129 my $port = $_->{sim_port};
130 my $res = undef;
131 eval {
132 $res = &XMLRPCCall_array($url, "GetStatus", [$port]);
133 };
134 if ($@) {
135 print STDERR "Get Status Error: $@\n";
136 }
137 $_->{get_scene_presence_filter} = $res ? $res->{get_scene_presence_filter} : $getstatus_failed;
138 $_->{get_scene_presence} = $res ? $res->{get_scene_presence} : $getstatus_failed;
139 $_->{get_avatar_filter} = $res ? $res->{get_avatar_filter} : $getstatus_failed;
140 $_->{get_avatar} = $res ? $res->{get_avatar} : $getstatus_failed;
141 $_->{avatar_names} = $res ? $res->{avatar_names} : "NO USER";
142 }
143 }
144 my $html = &MonitorGUI::View::html(\%copy_obj);
145 &MyCGI::outputHtml("UTF-8", &MonitorGUI::View::screen_header . $html . &MonitorGUI::View::screen_footer);
146}
147
148sub gui_error {
149 my $msg = shift;
150 &MyCGI::outputHtml("UTF-8", "<h1>ERROR</h1><hr />$msg");
151}
152
153sub redirect_refresh {
154 my $args = shift;
155 my $wait = $args->{wait};
156 my $force = $args->{force} || "";
157 my $msg = $args->{msg} || "";
158 my $param = "A=refresh&wait=$wait&ip=$force&msg=$msg";
159 my $dist_url = $THIS_URL . "?" . $param;
160 &MyCGI::redirect($dist_url);
161}
162
163sub refresh {
164 my $param = shift;
165 my $msg = $param->{msg} || "";
166 my $wait = $param->{wait} || 0;
167 my $force = $param->{ip} || "";
168 #my $jump_url = $force ? "$THIS_URL?A=force&ip=$force" : $THIS_URL;
169 my $jump_url = $THIS_URL;
170 my $html =<< "HTML";
171<html>
172<head>
173<meta http-equiv="Refresh" content="$wait;URL=$jump_url" />
174<title>Region Monitor GUI REFRESH</title>
175</head>
176<body>
177<h3>$msg</h3>
178<br>
179wait <font color="red"><b>$wait</b></font> sec for server to take effect ... <br>
180(* The page will jump to "Monitor Screen" automatically)
181</body>
182</html>
183HTML
184 &MyCGI::outputHtml("UTF-8", $html);
185}
186
187# ##################
188# Utility
189sub XMLRPCCall {
190 my ($url, $methodname, $param) = @_;
191 my $xmlrpc = new XML::RPC($url);
192 my $result = $xmlrpc->call($methodname, $param);
193 return $result;
194}
195
196sub XMLRPCCall_array {
197 my ($url, $methodname, $param) = @_;
198 my $xmlrpc = new XML::RPC($url);
199 my $result = $xmlrpc->call($methodname, @$param);
200 return $result;
201}
202
diff --git a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/readme.txt b/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/readme.txt
deleted file mode 100644
index 396ba56..0000000
--- a/ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs/readme.txt
+++ /dev/null
@@ -1,21 +0,0 @@
1How to get this working on Linux/Apache:
2
3Create a new directory /var/www/monitor and copy all files htdocs/* files there.
4
5Include these lines in /etc/apache2/httpdocs
6---
7<Directory /var/www/monitor>
8 Options +ExecCGI
9</Directory>
10AddHandler cgi-script .cgi
11---
12
13Restart Apache: sudo /etc/init.d/apache2 restart
14
15Check that the perl XML-RPC modules is available ("sudo apt-get install librcp-xml-perl" on Ubuntu)
16
17Edit /var/www/monitor/monitor.cgi to update the IP addresses for the Grid server (TODO: improve this)
18
19Start OpenSim in grid mode, use a browser to open http://localhost/monitor/monitor.cgi
20
21