]> mj.ucw.cz Git - arexx.git/commitdiff
A web interface
authorMartin Mares <mj@ucw.cz>
Mon, 2 Jan 2012 08:51:07 +0000 (09:51 +0100)
committerMartin Mares <mj@ucw.cz>
Mon, 2 Jan 2012 08:51:07 +0000 (09:51 +0100)
web/UCW/CGI.pm [new file with mode: 0644]
web/index.cgi [new file with mode: 0755]
web/rh-12h.cgi [new file with mode: 0755]
web/rh-48h.cgi [new file with mode: 0755]
web/rh-month.cgi [new file with mode: 0755]
web/temp-12h.cgi [new file with mode: 0755]
web/temp-48h.cgi [new file with mode: 0755]
web/temp-month.cgi [new file with mode: 0755]
web/temp-quick.cgi [new file with mode: 0755]

diff --git a/web/UCW/CGI.pm b/web/UCW/CGI.pm
new file mode 100644 (file)
index 0000000..d2468b8
--- /dev/null
@@ -0,0 +1,517 @@
+#      Poor Man's CGI Module for Perl
+#
+#      (c) 2002--2011 Martin Mares <mj@ucw.cz>
+#      Slightly modified by Tomas Valla <tom@ucw.cz>
+#
+#      This software may be freely distributed and used according to the terms
+#      of the GNU Lesser General Public License.
+
+package UCW::CGI;
+
+# First of all, set up error handling, so that even errors during parsing
+# will be reported properly.
+
+# Variables to be set by the calling module:
+#      $UCW::CGI::error_mail           mail address of the script admin (optional)
+#                                      (this one has to be set in the BEGIN block!)
+#      $UCW::CGI::error_hook           function to be called for reporting errors
+
+my $error_reported;
+my $exit_code;
+my $debug = 0;
+
+sub report_bug($)
+{
+       if (!defined $error_reported) {
+               $error_reported = 1;
+               print STDERR $_[0];
+               if (defined($UCW::CGI::error_hook)) {
+                       &$UCW::CGI::error_hook($_[0]);
+               } else {
+                       print "Content-Type: text/plain\n\n";
+                       print "Internal bug:\n";
+                       print $_[0], "\n";
+                       print "Please notify $UCW::CGI::error_mail\n" if defined $UCW::CGI::error_mail;
+               }
+       }
+       die;
+}
+
+BEGIN {
+       $SIG{__DIE__} = sub { report_bug($_[0]); };
+       $SIG{__WARN__} = sub { report_bug("WARNING: " . $_[0]); };
+       $exit_code = 0;
+}
+
+END {
+       $? = $exit_code;
+}
+
+use strict;
+use warnings;
+
+require Exporter;
+our $VERSION = 1.0;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(&html_escape &url_escape &url_deescape &url_param_escape &url_param_deescape &self_ref &self_form &http_get);
+our @EXPORT_OK = qw();
+
+our $utf8_mode = 0;
+
+sub http_error($;@) {
+       my $err = shift @_;
+       print join("\n", "Status: $err", "Content-Type: text/plain", @_, "", $err, "");
+       exit;
+}
+
+### Escaping ###
+
+sub url_escape($) {
+       my $x = shift @_;
+       utf8::encode($x) if $utf8_mode;
+       $x =~ s/([^-\$_.!*'(),0-9A-Za-z\x80-\xff])/"%".unpack('H2',$1)/ge;
+       utf8::decode($x) if $utf8_mode;
+       return $x;
+}
+
+sub url_deescape($) {
+       my $x = shift @_;
+       utf8::encode($x) if $utf8_mode;
+       $x =~ s/%(..)/pack("H2",$1)/ge;
+       utf8::decode($x) if $utf8_mode;
+       return $x;
+}
+
+sub url_param_escape($) {
+       my $x = shift @_;
+       $x = url_escape($x);
+       $x =~ s/%20/+/g;
+       return $x;
+}
+
+sub url_param_deescape($) {
+       my $x = shift @_;
+       $x =~ s/\+/ /g;
+       return url_deescape($x);
+}
+
+sub html_escape($) {
+       my $x = shift @_;
+       $x =~ s/&/&amp;/g;
+       $x =~ s/</&lt;/g;
+       $x =~ s/>/&gt;/g;
+       $x =~ s/"/&quot;/g;
+       $x =~ s/'/&#39;/g;
+       return $x;
+}
+
+### Analysing RFC 822 Style Headers ###
+
+sub rfc822_prepare($) {
+       my $x = shift @_;
+       # Convert all %'s and backslash escapes to %xx escapes
+       $x =~ s/%/%25/g;
+       $x =~ s/\\(.)/"%".unpack("H2",$1)/ge;
+       # Remove all comments, beware, they can be nested (unterminated comments are closed at EOL automatically)
+       while ($x =~ s/^(("[^"]*"|[^"(])*(\([^)]*)*)(\([^()]*(\)|$))/$1 /) { }
+       # Remove quotes and escape dangerous characters inside (again closing at the end automatically)
+       $x =~ s{"([^"]*)("|$)}{my $z=$1; $z =~ s/([^0-9a-zA-Z%_-])/"%".unpack("H2",$1)/ge; $z;}ge;
+       # All control characters are properly escaped, tokens are clearly visible.
+       # Finally remove all unnecessary spaces.
+       $x =~ s/\s+/ /g;
+       $x =~ s/(^ | $)//g;
+       $x =~ s{\s*([()<>@,;:\\"/\[\]?=])\s*}{$1}g;
+       return $x;
+}
+
+sub rfc822_deescape($) {
+       my $x = shift @_;
+       return url_deescape($x);
+}
+
+### Reading of HTTP headers ###
+
+sub http_get($) {
+       my $h = shift @_;
+       $h =~ tr/a-z-/A-Z_/;
+       return $ENV{"HTTP_$h"} // $ENV{"$h"};
+}
+
+### Parsing of Arguments ###
+
+my $main_arg_table;
+my %raw_args;
+
+sub parse_raw_args_ll($$) {
+       my ($arg, $s) = @_;
+       $s =~ s/\r\n/\n/g;
+       $s =~ s/\r/\n/g;
+       utf8::decode($s) if $utf8_mode;
+       push @{$raw_args{$arg}}, $s;
+}
+
+sub parse_raw_args($) {
+       my ($s) = @_;
+       $s =~ s/\s+//;
+       for $_ (split /[&:]/, $s) {
+               (/^([^=]+)=(.*)$/) or next;
+               my $arg = $1;
+               $_ = $2;
+               s/\+/ /g;
+               s/%(..)/pack("H2",$1)/eg;
+               parse_raw_args_ll($arg, $_);
+       }
+}
+
+sub parse_multipart_form_data();
+
+sub init_args() {
+       if (!defined $ENV{"GATEWAY_INTERFACE"}) {
+               print STDERR "Must be called as a CGI script.\n";
+               $exit_code = 1;
+               exit;
+       }
+
+       my $method = $ENV{"REQUEST_METHOD"};
+       if (my $qs = $ENV{"QUERY_STRING"}) {
+               parse_raw_args($qs);
+       }
+       if ($method eq "GET" || $method eq "HEAD") {
+       } elsif ($method eq "POST") {
+               my $content_type = $ENV{"CONTENT_TYPE"} // "";
+               if ($content_type =~ /^application\/x-www-form-urlencoded\b/i) {
+                       while (<STDIN>) {
+                               chomp;
+                               parse_raw_args($_);
+                       }
+               } elsif ($content_type =~ /^multipart\/form-data\b/i) {
+                       parse_multipart_form_data();
+               } else {
+                       http_error "415 Unsupported Media Type";
+                       exit;
+               }
+       } else {
+               http_error "405 Method Not Allowed", "Allow: GET, HEAD, PUT";
+       }
+}
+
+sub parse_args($) {                    # CAVEAT: attached files must be defined in the main arg table
+       my $args = shift @_;
+       if (!$main_arg_table) {
+               $main_arg_table = $args;
+               init_args();
+       }
+
+       for my $a (values %$args) {
+               my $r = ref($a->{'var'});
+               defined($a->{'default'}) or $a->{'default'}="";
+               if ($r eq 'SCALAR') {
+                       ${$a->{'var'}} = $a->{'default'};
+               } elsif ($r eq 'ARRAY') {
+                       @{$a->{'var'}} = ();
+               }
+       }
+
+       for my $arg (keys %$args) {
+               my $a = $args->{$arg};
+               defined($raw_args{$arg}) or next;
+               for (@{$raw_args{$arg}}) {
+                       $a->{'multiline'} or s/(\n|\t)/ /g;
+                       s/^\s+//;
+                       s/\s+$//;
+                       if (my $rx = $a->{'check'}) {
+                               if (!/^$rx$/) { $_ = $a->{'default'}; }
+                       }
+
+                       my $v = $a->{'var'};
+                       my $r = ref($v);
+                       if ($r eq 'SCALAR') {
+                               $$v = $_;
+                       } elsif ($r eq 'ARRAY') {
+                               push @$v, $_;
+                       }
+               }
+       }
+}
+
+### Parsing Multipart Form Data ###
+
+my $boundary;
+my $boundary_len;
+my $mp_buffer;
+my $mp_buffer_i;
+my $mp_buffer_boundary;
+my $mp_eof;
+
+sub refill_mp_data($) {
+       my ($more) = @_;
+       if ($mp_buffer_boundary >= $mp_buffer_i) {
+               return $mp_buffer_boundary - $mp_buffer_i;
+       } elsif ($mp_buffer_i + $more <= length($mp_buffer) - $boundary_len) {
+               return $more;
+       } else {
+               if ($mp_buffer_i) {
+                       $mp_buffer = substr($mp_buffer, $mp_buffer_i);
+                       $mp_buffer_i = 0;
+               }
+               while ($mp_buffer_i + $more > length($mp_buffer) - $boundary_len) {
+                       last if $mp_eof;
+                       my $data;
+                       my $n = read(STDIN, $data, 2048);
+                       if ($n > 0) {
+                               $mp_buffer .= $data;
+                       } else {
+                               $mp_eof = 1;
+                       }
+               }
+               $mp_buffer_boundary = index($mp_buffer, $boundary, $mp_buffer_i);
+               if ($mp_buffer_boundary >= 0) {
+                       return $mp_buffer_boundary;
+               } elsif ($mp_eof) {
+                       return length($mp_buffer);
+               } else {
+                       return length($mp_buffer) - $boundary_len;
+               }
+       }
+}
+
+sub get_mp_line($) {
+       my ($allow_empty) = @_;
+       my $n = refill_mp_data(1024);
+       my $i = index($mp_buffer, "\r\n", $mp_buffer_i);
+       if ($i >= $mp_buffer_i && $i < $mp_buffer_i + $n - 1) {
+               my $s = substr($mp_buffer, $mp_buffer_i, $i - $mp_buffer_i);
+               $mp_buffer_i = $i + 2;
+               return $s;
+       } elsif ($allow_empty) {
+               if ($n) {                                                       # An incomplete line
+                       my $s = substr($mp_buffer, $mp_buffer_i, $n);
+                       $mp_buffer_i += $n;
+                       return $s;
+               } else {                                                        # No more lines
+                       return undef;
+               }
+       } else {
+               http_error "400 Bad Request: Premature end of multipart POST data";
+       }
+}
+
+sub skip_mp_boundary() {
+       if ($mp_buffer_boundary != $mp_buffer_i) {
+               http_error "400 Bad Request: Premature end of multipart POST data";
+       }
+       $mp_buffer_boundary = -1;
+       $mp_buffer_i += 2;
+       my $b = get_mp_line(0);
+       print STDERR "SEP $b\n" if $debug;
+       $mp_buffer_boundary = index($mp_buffer, $boundary, $mp_buffer_i);
+       if (substr("\r\n$b", 0, $boundary_len) eq "$boundary--") {
+               return 0;
+       } else {
+               return 1;
+       }
+}
+
+sub parse_mp_header() {
+       my $h = {};
+       my $last;
+       while ((my $l = get_mp_line(0)) ne "") {
+               print STDERR "HH $l\n" if $debug;
+               if (my ($name, $value) = ($l =~ /([A-Za-z0-9-]+)\s*:\s*(.*)/)) {
+                       $name =~ tr/A-Z/a-z/;
+                       $h->{$name} = $value;
+                       $last = $name;
+               } elsif ($l =~ /^\s+/ && $last) {
+                       $h->{$last} .= $l;
+               } else {
+                       $last = undef;
+               }
+       }
+       foreach my $n (keys %$h) {
+               $h->{$n} = rfc822_prepare($h->{$n});
+               print STDERR "H $n: $h->{$n}\n" if $debug;
+       }
+       return (keys %$h) ? $h : undef;
+}
+
+sub parse_multipart_form_data() {
+       # First of all, find the boundary string
+       my $ct = rfc822_prepare($ENV{"CONTENT_TYPE"});
+       if (!(($boundary) = ($ct =~ /^.*;\s*boundary=([^; ]+)/))) {
+               http_error "400 Bad Request: Multipart content with no boundary string received";
+       }
+       $boundary = rfc822_deescape($boundary);
+       print STDERR "BOUNDARY IS $boundary\n" if $debug;
+
+       # BUG: IE 3.01 on Macintosh forgets to add the "--" at the start of the boundary string
+       # as the MIME specs preach. Workaround borrowed from CGI.pm in Perl distribution.
+       my $agent = http_get("User-Agent") // "";
+       $boundary = "--$boundary" unless $agent =~ /MSIE\s+3\.0[12];\s*Mac/;
+       $boundary = "\r\n$boundary";
+       $boundary_len = length($boundary) + 2;
+
+       # Check upload size in advance
+       if (my $size = http_get("Content-Length")) {
+               my $max_allowed = 0;
+               foreach my $a (values %$main_arg_table) {
+                       $max_allowed += $a->{"maxsize"} || 65536;
+               }
+               if ($size > $max_allowed) {
+                       http_error "413 Request Entity Too Large";
+               }
+       }
+
+       # Initialize our buffering mechanism and part splitter
+       $mp_buffer = "\r\n";
+       $mp_buffer_i = 0;
+       $mp_buffer_boundary = -1;
+       $mp_eof = 0;
+
+       # Skip garbage before the 1st part
+       while (my $i = refill_mp_data(256)) { $mp_buffer_i += $i; }
+       skip_mp_boundary() || return;
+
+       # Process individual parts
+       do { PART: {
+               print STDERR "NEXT PART\n" if $debug;
+               my $h = parse_mp_header();
+               my ($field, $cdisp, $a);
+               if ($h &&
+                   ($cdisp = $h->{"content-disposition"}) &&
+                   $cdisp =~ /^form-data/ &&
+                   (($field) = ($cdisp =~ /;name=([^;]+)/)) &&
+                   ($a = $main_arg_table->{"$field"})) {
+                       print STDERR "FIELD $field\n" if $debug;
+                       if (defined $h->{"content-transfer-encoding"}) {
+                               http_error "400 Bad Request: Unexpected Content-Transfer-Encoding";
+                       }
+                       if (defined $a->{"var"}) {
+                               while (defined (my $l = get_mp_line(1))) {
+                                       print STDERR "VALUE $l\n" if $debug;
+                                       parse_raw_args_ll($field, $l);
+                               }
+                               next PART;
+                       } elsif (defined $a->{"file"}) {
+                               require File::Temp;
+                               require IO::Handle;
+                               my $max_size = $a->{"maxsize"} || 1048576;
+                               my @tmpargs = (undef, UNLINK => 1);
+                               push @tmpargs, DIR => $a->{"tmpdir"} if defined $a->{"tmpdir"};
+                               my ($fh, $fn) = File::Temp::tempfile(@tmpargs);
+                               print STDERR "FILE UPLOAD to $fn\n" if $debug;
+                               ${$a->{"file"}} = $fn;
+                               ${$a->{"fh"}} = $fh if defined $a->{"fh"};
+                               my $total_size = 0;
+                               while (my $i = refill_mp_data(4096)) {
+                                       print $fh substr($mp_buffer, $mp_buffer_i, $i);
+                                       $mp_buffer_i += $i;
+                                       $total_size += $i;
+                                       if ($total_size > $max_size) { http_error "413 Request Entity Too Large"; }
+                               }
+                               $fh->flush();   # Don't close the handle, the file would disappear otherwise
+                               next PART;
+                       }
+               }
+               print STDERR "SKIPPING\n" if $debug;
+               while (my $i = refill_mp_data(256)) { $mp_buffer_i += $i; }
+       } } while (skip_mp_boundary());
+}
+
+### Generating Self-ref URL's ###
+
+sub make_out_args(@) {         # Usage: make_out_args([arg_table, ...] name => value, ...)
+       my @arg_tables = ( $main_arg_table );
+       while (@_ && ref($_[0]) eq 'HASH') {
+               push @arg_tables, shift @_;
+       }
+       my %overrides = @_;
+       my $out = {};
+       for my $table (@arg_tables) {
+               for my $name (keys %$table) {
+                       my $arg = $table->{$name};
+                       defined($arg->{'var'}) || next;
+                       defined($arg->{'pass'}) && !$arg->{'pass'} && !exists $overrides{$name} && next;
+                       defined $arg->{'default'} or $arg->{'default'} = "";
+                       my $value;
+                       if (!defined($value = $overrides{$name})) {
+                               if (exists $overrides{$name}) {
+                                       $value = $arg->{'default'};
+                               } else {
+                                       $value = ${$arg->{'var'}};
+                                       defined $value or $value = $arg->{'default'};
+                               }
+                       }
+                       if ($value ne $arg->{'default'}) {
+                               $out->{$name} = $value;
+                       }
+               }
+       }
+       return $out;
+}
+
+sub self_ref(@) {
+       my $out = make_out_args(@_);
+       return "?" . join(':', map { "$_=" . url_param_escape($out->{$_}) } sort keys %$out);
+}
+
+sub self_form(@) {
+       my $out = make_out_args(@_);
+       return join('', map { "<input type=hidden name=$_ value='" . html_escape($out->{$_}) . "'>\n" } sort keys %$out);
+}
+
+### Cookies
+
+sub set_cookie($$@) {
+       #
+       # Unfortunately, the support for the new cookie standard (RFC 2965) among
+       # web browsers is still very scarce, so we are still using the old Netscape
+       # specification.
+       #
+       # Usage: set_cookie(name, value, option => value...), where options are:
+       #
+       #       max-age         maximal age in seconds
+       #       domain          domain name scope
+       #       path            path name scope
+       #       secure          if present, cookie applies only to SSL connections
+       #                       (in this case, the value should be undefined)
+       #       discard         if present with any value, the cookie is discarded
+       #
+
+       my $key = shift @_;
+       my $value = shift @_;
+       my %other = @_;
+       if (exists $other{'discard'}) {
+               delete $other{'discard'};
+               $other{'max-age'} = 0;
+       }
+       if (defined(my $age = $other{'max-age'})) {
+               delete $other{'max-age'};
+               my $exp = ($age ? (time + $age) : 0);
+               # Avoid problems with locales
+               my ($S,$M,$H,$d,$m,$y,$wd) = gmtime $exp;
+               my @wdays = ( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' );
+               my @mons = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
+               $other{'expires'} = sprintf("%s, %02d-%s-%d %02d:%02d:%02d GMT",
+                       $wdays[$wd], $d, $mons[$m], $y+1900, $H, $M, $S);
+       }
+
+       print "Set-Cookie: $key=", url_escape($value);
+       foreach my $k (keys %other) {
+               print "; $k";
+               print "=", $other{$k} if defined $other{$k};
+       }
+       print "\n";
+}
+
+sub parse_cookies() {
+       my $h = http_get("Cookie") or return ();
+       my @cook = ();
+       foreach my $x (split /;\s*/, $h) {
+               my ($k,$v) = split /=/, $x;
+               $v = url_deescape($v) if defined $v;
+               push @cook, $k => $v;
+       }
+       return @cook;
+}
+
+1;  # OK
diff --git a/web/index.cgi b/web/index.cgi
new file mode 100755 (executable)
index 0000000..760c662
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib '.';
+use UCW::CGI;
+
+my $graph;
+
+UCW::CGI::parse_args({
+       'g' => { 'var' => \$graph, 'default' => 'temp-12h' },
+});
+
+print <<AMEN ;
+Content-type: text/html
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+<title>Weather in the Burrow</title>
+<body>
+<h1>Weather in the Burrow</h1>
+AMEN
+
+sub links(@) {
+       my $prefix = shift @_;
+       my $out = "";
+       for my $x (@_) {
+               my $y = $prefix . $x;
+               if ($graph eq $y) {
+                       $out .= " <b>$x</b>";
+               } else {
+                       $out .= " <a href='?g=$y'>$x</a>";
+               }
+       }
+       return $out;
+}
+
+print "<p>Temperature:", links("temp-", "12h", "48h", "month"), "\n";
+print "<p>Humidity:", links("rh-", "12h", "48h", "month"), "\n";
+print "<p>Power:", links("power-", "2h", "2h-detail", "day", "day-detail", "month"), "\n";
+
+if ($graph =~ /^power-/) {
+       $graph = "http://micac.burrow.ucw.cz/cgi-bin/$graph";
+}
+print "<p><img src='$graph.cgi'>\n";
diff --git a/web/rh-12h.cgi b/web/rh-12h.cgi
new file mode 100755 (executable)
index 0000000..ece763f
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-12h' \
+       --end 'now' \
+       --title "Relative Humidity" \
+       -w 720 -h 600 \
+       -x MINUTE:10:HOUR:1:HOUR:2:0:%H:%M \
+       -y 5:2 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --legend-position east \
+       --units-exponent 0 --lower-limit 0 --upper-limit 100 --rigid \
+       DEF:a=$D/sensor-19247.rrd:rh:AVERAGE 'LINE1:a#cc0000:Catarium'
diff --git a/web/rh-48h.cgi b/web/rh-48h.cgi
new file mode 100755 (executable)
index 0000000..bfc4be3
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-48h' \
+       --end 'now' \
+       --title "Relative Humidity: Last 2 days" \
+       -w 720 -h 600 \
+       -x HOUR:1:HOUR:8:HOUR:8:0:%H:%M \
+       -y 5:2 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --legend-position east \
+       --units-exponent 0 --lower-limit 0 --upper-limit 100 --rigid \
+       DEF:a=$D/sensor-19247.rrd:rh:AVERAGE 'LINE1:a#cc0000:Catarium'
diff --git a/web/rh-month.cgi b/web/rh-month.cgi
new file mode 100755 (executable)
index 0000000..ced298c
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-30d' \
+       --end 'now' \
+       --title "Relative Humidity: MIN and MAX" \
+       -w 720 -h 600 \
+       -x DAY:1:DAY:5:DAY:5:0:%d:%m \
+       -y 5:2 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --legend-position east \
+       --units-exponent 0 --lower-limit 0 --upper-limit 100 --rigid \
+       DEF:alo=$D/sensor-19247.rrd:rh:MIN 'LINE1:alo#cc0000:Catarium' \
+       DEF:ahi=$D/sensor-19247.rrd:rh:MAX 'LINE1:ahi#cc0000'
diff --git a/web/temp-12h.cgi b/web/temp-12h.cgi
new file mode 100755 (executable)
index 0000000..013461e
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-12h' \
+       --end 'now' \
+       --title "Temperature" \
+       -w 720 -h 600 \
+       -x MINUTE:10:HOUR:1:HOUR:2:0:%H:%M \
+       -y 5:1 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --units-exponent 0 --lower-limit -20 --upper-limit 40 --rigid \
+       --legend-position east \
+       DEF:a=$D/sensor-10415.rrd:temp:AVERAGE 'LINE1:a#0000cc:Ursarium\n' \
+       DEF:b=$D/sensor-12133.rrd:temp:AVERAGE 'LINE1:b#00cc00:Balcony\n' \
+       DEF:c=$D/sensor-19246.rrd:temp:AVERAGE 'LINE1:c#cc0000:Catarium'
diff --git a/web/temp-48h.cgi b/web/temp-48h.cgi
new file mode 100755 (executable)
index 0000000..a67deca
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-48h' \
+       --end 'now' \
+       --title "Temperature: Last 2 days" \
+       -w 720 -h 600 \
+       -x HOUR:1:HOUR:8:HOUR:8:0:%H:%M \
+       -y 5:1 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --units-exponent 0 --lower-limit -20 --upper-limit 40 --rigid \
+       --legend-position east \
+       DEF:a=$D/sensor-10415.rrd:temp:AVERAGE 'LINE1:a#0000cc:Ursarium\n' \
+       DEF:b=$D/sensor-12133.rrd:temp:AVERAGE 'LINE1:b#00cc00:Balcony\n' \
+       DEF:c=$D/sensor-19246.rrd:temp:AVERAGE 'LINE1:c#cc0000:Catarium'
diff --git a/web/temp-month.cgi b/web/temp-month.cgi
new file mode 100755 (executable)
index 0000000..46ab4e6
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-30d' \
+       --end 'now' \
+       --title "Temperature: MIN and MAX" \
+       -w 720 -h 600 \
+       -x DAY:1:DAY:5:DAY:5:0:%d:%m \
+       -y 5:1 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --units-exponent 0 --lower-limit -20 --upper-limit 40 --rigid \
+       --legend-position east \
+       DEF:alo=$D/sensor-10415.rrd:temp:MIN 'LINE1:alo#0000cc:Ursarium\n' \
+       DEF:ahi=$D/sensor-10415.rrd:temp:MAX 'LINE1:ahi#0000cc' \
+       DEF:blo=$D/sensor-12133.rrd:temp:MIN 'LINE1:blo#00cc00:Balcony\n' \
+       DEF:bhi=$D/sensor-12133.rrd:temp:MAX 'LINE1:bhi#00cc00' \
+       DEF:clo=$D/sensor-19246.rrd:temp:MIN 'LINE1:clo#cc0000:Catarium' \
+       DEF:chi=$D/sensor-19246.rrd:temp:MAX 'LINE1:chi#cc0000'
diff --git a/web/temp-quick.cgi b/web/temp-quick.cgi
new file mode 100755 (executable)
index 0000000..1ee67ea
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+echo "Content-type: image/png"
+echo
+D=/var/log/arexxd
+exec rrdtool graph - \
+       --start 'now-2h' \
+       --end 'now' \
+       --title "Temperature" \
+       -w 360 -h 200 \
+       -x MINUTE:10:MINUTE:30:MINUTE:30:0:%H:%M \
+       -y 5:1 \
+       --right-axis 1:0 --right-axis-format "%3.0lf" \
+       --units-exponent 0 --lower-limit -20 --upper-limit 40 --rigid \
+       --legend-position east \
+       DEF:a=$D/sensor-10415.rrd:temp:AVERAGE 'LINE1:a#0000cc:Ursarium\n' \
+       DEF:b=$D/sensor-12133.rrd:temp:AVERAGE 'LINE1:b#00cc00:Balcony\n' \
+       DEF:c=$D/sensor-19246.rrd:temp:AVERAGE 'LINE1:c#cc0000:Catarium'