A CGI interface to set configuration for other CGIs

For PC type software that runs under some PC opsys.

A CGI interface to set configuration for other CGIs

Postby Doug Coulter » Sun Aug 02, 2020 11:55 am

For some time, I've wanted to have some easy way (not editing code each time) to configure the display and control functions of my LAN of things. I've also wanted to make the display and command-input pieces more and more hardware and location independent where that makes sense. The possible machines there are in general linux machines, though I could use an android tablet - or anything with a browser, for just the user interface parts.
Web servers (I use NGINX) and databases (I use MySQL or MariaDB - whatever the distro reccomends today) are around and work fine whether it's a hot-stuff PC, an old Lenovo, or a Raspberry pi - and the pi doesn't have to be a current version.

I'm still a big fan of perl as a glue language for this sort of work, at least if I wrote it according to my ideas of how to write clear, maintainable code, which I think I've managed to do here. Look at this stuff in some editor with syntax highlighting and see for yourself, even if you don't speak perl. I'm using geany just now with the SpyderDark scheme, as I like a few things about that and dark themes generally. I also use Gedit with inverted syntax colors, SublimeText and others as the mood or whatever is on that machine inspires me.
Screenshot at 2020-08-02 12-27-34.png
Makes things easy to understand - don't leave home without it.

I like this one as it makes questionable practices, like hardcoding numbers, really stand out...

At any rate, it looks like the tools and parts to get on the road with that configuration tool are there, so I got going on it. There's a core perl module (core means it's there already, no need to go get it) that, given a reference, will store (serialize) any data structure that reference points to, and bring it back, instantiating all the things as needed, on command. It'll go to a file, or ram, I'm using the file here. I'm not taking advantage of the ability to make fancy structured setups here, just using a hash (text-keyed lookup dictionary), because that's all I need for the moment. It could go crazy later if desired...

I did not (yet) use the flexibility I imagined even with just the hash, like completely automating things like "if there's a feature specified in a hash key, automate the html/java generation" or "somehow be able to set the order that plots, links or controls are displayed via this interface" - that's maybe for someone a lot more dedicated, and I didn't want the perfect to be the enemy of the good enough - that's what engineering is all about.

I have so much data here, I can't simply show it all, and what options I've set up so far haven't been adequate for the kind of data mining I want. I usually want short-term plots - "how are we doing today?", but it would be nice to have a sliding start window and variable duration - as I have years worth of data, it'd be nice to see, for example, temperatures in spring and fall to know some useful timings for gardening for my precise little ecosystem. I finally have enough years worth of data, taken minute by minute, for that idea not to be a total joke.

I'm getting some of that...It's a thing in progress. It's a good thing to do while stuck on the couch (this is not so bad, it's my toy room cockpit) trying to get the new meds titrated so I can do more-active things.
This looks unfinished, because it is...tweaks will be easy as things go along because it's clean code.
config in progress.png
OK, it needs all kinds of pretty-up, I'm averse to web-monkey but I'll get there.

Lucky, this is not what I see most of the time, which looks more like this.
SteadStat.png
Partially customized, also work in progress

The links shown go to other web servers that show cameras, control relays, let water in from the rain barrel to the cistern (after checking it with a camera) - things like that. But if I plot everything I have at any decent resolution, it's not a fit-on-one-page thing at all...so that's where this is going, to let me set up dashboards for tasks at hand, easily. "I think I can, I think I can".

The other thing that's changes is that the database can be on any machine here- right now it's on an always-on raspberry pi, along with daemons that grab things from the sensors (which are often as not on other machines, from pies to ESP8266) and stuff the DB. That machine has a web server, heck, they all do, and this code doesn't care - any web server can point to a database anywhere and use it's own resources to do plotting, or some android can just log onto whatever server and get the plots. If I used database replication even that could be machine - independent...

Here's the code for the cgi that does the config. It saves all this stuff to a file, is going to redirect back to wherever we came from on a submit (off for the moment because debugging), and just be a quick way to define what shows in the "real" servers.

Code: Select all
#!/usr/bin/perl

=pod
Create a GUI CGI web interface to a hash of values
for other cgi programs to use as configuration data, shared
via a file managed by Storable.
=cut

use Modern::Perl;
use FCGI;
use CGI::Carp qw(fatalsToBrowser);
use Storable qw(nstore retrieve);

##################################### Globals #################################
# setup defaults for config hash
# Storable takes and returns a reference, hench the syntax
my $cfgr = {
   duration => 1,      # days default
   begin_time => 1,    # default begin 24h ago
   show_BatV => 1,         # bools, show plot of same name?
   show_BatA => 1,
   show_Temps => 1,
   show_Humids => 1,
   show_Water => 0,
   show_Baros => 0
   }; # ref to config hash.  Put defaults in now.
my $request = FCGI::Request; # object wrapping CGI request
my $data;  # stuff posted back from cgi form
my $configpath = "./configs.sto"; # pick a name, any name
my $debug = 0;

# the below are set up and intermplated into the generated html
my ($bvc,$bac,$btc,$bhc,$bwc,$bbc); # one per checkbox "BoolIdChecked"
my ($pd,$ps); # slider numeric values, "plot duration and start"
############################### Subs ##########################################
sub print_config { # for debugging

foreach my $key (sort(keys %$cfgr)) {
print "$key => $cfgr->{$key}\n";
#print "<TR><TD>$key</TD><TD>$cfgr->{$key}</TD></TR>\n";
}
#*************************************************************
sub print_header {  # get the page started

print <<EndOfHead; # just get all this junk sent out
<!DOCTYPE html>\n
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.slidecontainer {
  width: 90%;
}

.slider {
  -webkit-appearance: none;
  width: 100%;
  height: 25px;
  background: #d3d3d3;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .2s;
  transition: opacity .2s;
}

.slider:hover {
  opacity: 1;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 25px;
  height: 25px;
  background: #4CAF50;
  cursor: pointer;
}

.slider::-moz-range-thumb {
  width: 25px;
  height: 25px;
  background: #4CAF50;
  cursor: pointer;
}
</style>
</head>

EndOfHead

}
#*************************************************************
sub print_body {

# the meat of the display - interpolating a few perl vars as we go
print <<EndOfBody;
<body>

<h1>Configure Homestead display</h1>

<FORM action=\"Config.cgi\" method=\"post\"><br>

<div class="slidecontainer">

  Duration, days: <span id="DurTxt"></span> 
  <input type="range" min="1" max="365" value="$pd" class="slider" id="PlotDur" name="plotdur">

  Start Time, days ago: <span id="StartTxt"></span>
  <input type="range" min="1" max="365" value="$ps" class="slider" id="PlotStart" name = "plotstart">

</div>
<p>
<INPUT type=\"checkbox\" name=\"batv\" $bvc>plot Volts&nbsp
<INPUT type=\"checkbox\" name=\"bata\" $bac>plot Amps <br>
<INPUT type=\"checkbox\" name=\"temps\" $btc>plot Temperatures&nbsp
<INPUT type=\"checkbox\" name=\"rH\" $bhc>plot Relative humidity <br>
<INPUT type=\"checkbox\" name=\"water\" $bwc >plot Cistern&nbsp
<INPUT type=\"checkbox\" name=\"baro\" $bbc>plot Barometers <br>


<INPUT type =\"submit\" value =\"Update Configuration\"/>
</FORM><br>\n

<script>
var sliderD = document.getElementById("PlotDur");
var Durout = document.getElementById("DurTxt");
Durout.innerHTML = sliderD.value;

var sliderS = document.getElementById("PlotStart");
var Startout = document.getElementById("StartTxt");
Startout.innerHTML = sliderS.value;


sliderD.oninput = function() {
  Durout.innerHTML = this.value;
}

sliderS.oninput = function() {
  Startout.innerHTML = this.value;
}

</script>

</body>
</html>

EndOfBody
   
}
#*************************************************************

############################### Main ##########################################


# check for file exists, setup defaults and save if not

unless (-e $configpath)
{ # create it, then
nstore($cfgr,$configpath) or die "Storage create fail:$!\n";   
}

$cfgr = retrieve($configpath) or die "$configpath exists but can't retrieve:#!\n";

print_config if $debug;

while ($request->Accept >= 0)
{
{ # closure for local $/
  local $/;
  $data = <>; # suck up any submit data
}
if ($data) # update config hash from user input
{
  $cfgr->{show_BatV} = $data =~ m/batv/ ? 1:0;
  $cfgr->{show_BatA} = $data =~ m/bata/ ? 1:0;
  $cfgr->{show_Temps} = $data =~ m/temps/ ? 1:0;
  $cfgr->{show_Humids} = $data =~ m/rH/ ? 1:0;
  $cfgr->{show_Water} = $data =~ m/water/ ? 1:0;
  $cfgr->{show_Baros} = $data =~ m/baro/ ? 1:0;
 
  $data =~ /plotdur=(\d+)/; # capture the number
  $cfgr->{duration} = $1;
  $data =~ /plotstart=(\d+)/; # capture the number
  $cfgr->{begin_time} = $1;

# at the end, save whatever the user did
  nstore($cfgr,$configpath) or die "Configuration save fail:$!\n";   
}

=nore
  if ($data && $ENV{HTTP_REFERER} =~ m/Config/)       
{ #     redirect to plots 
  print <<EndORedirect;
<!DOCTYPE html> \n\n
<html><head><meta http-equiv="refresh" content="0 url=./steadstat.cgi"></head>
</html>
EndORedirect
  last;
}
=cut

# set control states from saved config hash data
$bvc = $cfgr->{show_BatV} ? "checked" : ""; # repeat for other checkboxes
$bac = $cfgr->{show_BatA} ? "checked" : ""; # repeat for other checkboxes
$btc = $cfgr->{show_Temps} ? "checked" : ""; # repeat for other checkboxes
$bhc = $cfgr->{show_Humids} ? "checked" : ""; # repeat for other checkboxes
$bwc = $cfgr->{show_Water} ? "checked": "";
$bbc = $cfgr->{show_Baros} ? "checked" : "";
$pd = $cfgr->{duration};
$ps = $cfgr->{begin_time};

print_header;
print_body;

}


Posting as just me, not as the forum owner. Everything I say is "in my opinion" and YMMV -- which should go for everyone without saying.
User avatar
Doug Coulter
 
Posts: 3515
Joined: Wed Jul 14, 2010 7:05 pm
Location: Floyd county, VA, USA

Return to PC

Who is online

Users browsing this forum: No registered users and 6 guests

cron