Alternative to Dynamic DNS – Project Lighthouse (part 3)

This is part 3 of the Project Lighthouse tutorial.  During part 1, I set the stage for what this is all about.  In part 2, I laid the building blocks. The building blocks are the key pieces that are required to store and retrieve the network information.  During this post we will put in place the user interface and business logic which is where the magic really happens.

The User Interface

The user interface for this is going to be quite simple.  I didn’t put together any whiz-bang UI and instead opted for something very simplistic.  Why?  Well, for starters this is just a utility and a UI is really unnecessary.  After all, we could achieve all of this with just a URL HTTP GET requests and have no interactive UI.  That is boring though and not as easy to manipulate for testing purposes.

The UI is going to come in two parts:

  1. An HTML Form that shows the IP address of the system calling the web page which will have a text field to allow a user to enter in the name of the network (or the NetworkVO->id).
  2. An HTML table that displays all of the networks that have been captured.  The information to be displayed is the IP address of the network (NetworkVO->ip), the id (NetworkVO->id) and the created on date (NetworkVO->createdOn). In addition to that information we will also display some quick links to protocols that could potentially be available such as http, ftp, vnc, etc.  An additional, link will allow us to delete the NetworkVO entry from the list.

The HTML Page Template

Using a template framework is really handy for quick alteration.  For this, I’m just providing a couple of functions to handle the template, even though this isn’t very robust.  Creating a separate class to handle this or using a framework is a better approach, but this will get the job done.

/**
 * getPage
 * Returns fully formed HTML page displaying the CONTENT
 * @param $content
 */
function getPage($content) {
    $html = getHeader();
    $html .= $content;
    $html .= getFooter();

    return $html;
}

/**
 * Get the HTML header
 * @return string - HTML header
 */
function getHeader() {
    return '<html><head>'
           . '<meta http-equiv="Pragma" content="no-cache" />'
           . '<meta http-equiv="expires" content="0" />'
           . "<title> $PAGE_TITLE </title>"
           . '</head><body>';
}

/**
 * Get the HTML footer
 * @return string
 */
function getFooter() {
    return "</body></html>";
}

Whenever we need to output something to the browser, we just need to call echo getPage(“some content”);. The power of this is that if we wanted to add an image at the top of the page, a menu or copyright in the footer, then you just update the appropriate header and footer functions. You update it once and it is done everywhere. I’ve created my own Template classes that I use typically, but for this to keep it simple I stripped them out. Maybe one day I’ll create a post just for them.

The HTML Form

The form is quite simple. I’ve wrapped it in a function to make the form controller easier to understand and manipulate.

/**
 * Get the HTML form for storing a new Network
 * @param type $ip - The IP address of the network
 * @return string - HTML form to store a network
 */
function getDisplayForm($ip) {
    $html = "Your IP: " . $ip;
    $html .= "<br><br><form action='$VIEW_CONTROLLER_LOCATION' method='post'>";
    $html .= "<input type='hidden' name='action' value='store'><input type='text' name='network-id'><input type='submit' value='Save This IP'>";
    $html .= "</form>";

    return $html;
}

This relies on a HTTP POST to work. We will wire that up in the page controller later.

The HTML Table

I’ll admit after looking at this code it is a bit overkill to create separate methods for the table and the rows. I have no idea why I did this, but it could have easily been wrapped into a single function.

/**
 * Get an HTML table of NetworkVOs
 * @param NetworkVO[] $networkVOArr - an array of networks to display
 * @return string - HTML table of networks
 */
function getTable($networkVOArr) {
    if ($networkVOArr == null)
        return "There are no Networks to be displayed.";

    $html = "<table border='1'>";
    $html .= "<tr><td>IP<td>Name<td>Date<td>Links</tr>";
    usort($networkVOArr, array("NetworkVO", "cmp"));
    foreach ($networkVOArr as $networkVO) {
        $html .= convertNetworkVOtoTableRow($networkVO);
    }
    $html .= "</table>";
    return $html;
}

/**
 * Returns an HTML table row for networks
 * @param NetworkVO $networkVO
 * @return string - Returns an HTML table row for provided network
 */
function convertNetworkVOtoTableRow(NetworkVO $networkVO) {
    //Clean for HTML output
    $id = htmlspecialchars($networkVO->getId());
    $ip = htmlspecialchars($networkVO->getIp());

    $links = "<a href='ftp://$ip'>FTP</a>"
            . " | <a href='sftp://$ip'>SFTP</a>"
            . " | <a href='http://$ip'>HTTP</a>"
            . " | <a href='https://$ip'>HTTPS</a>"
            . " | <a href='vnc://$ip'>VNC</a>"
            . " | <a href='$VIEW_CONTROLLER_LOCATION?action=delete&network-id=$id'>DELETE</a>"
            . "";

    //Return table row HTML with IP, ID and Links (cells)
    return "<tr><td>" . $ip . "<td>" . $id . "<td>" . $networkVO->getCreatedOn() . "<td>" . $links . "</tr>";
}

Page Business Logic (Controller)

Now this is where everything comes together. If we describe all of this in the context of the model, view and controller. We discussed in the second post the Model (or value object) and the data storage (or data access object). Earlier in this post we laid down the user interface components or the View. Now we need to wire all of the pieces together, also known as the Controller.

We are developing this around a single index.php php page. This will handle all of the interactions from the URL and the HTML form calls. Speaking of URL calls, we haven’t really talked about that yet. So let’s talk about that quickly now. In part 1, I talked about achieving this by having a scheduled process that would call a URL. What we showed at the very beginning of this post was all about a UI. What we are going to require though for the a scheduled job is not the HTML form, but an HTTP GET off the URL.

URL – HTTP GET

What do we need? The only thing we require is the network id. To have a single controller (index.php) we are also going to add an action parameter. This will give us the ability to handle CRUD operations all with one controller page.

Here are the HTTP GET parameters –
action – Describes the action that is intended to take place.

  • list – Tells the controller to show the HTML table
  • store – Tell the controller to save a network
  • delete – Tells the controller to delete a network
  • lookup – Tells the controller to lookup a single network and show the contents

network-id – the network identifier (or NetworkVO->id)

Here are a few examples of what the URLs would look like.?

  • http://location.of.lighthouse/index.php?action=list
  • http://location.of.lighthouse/index.php?action=store&network-id=home
  • http://location.of.lighthouse/index.php?action=lookup&network-id=home
  • http://location.of.lighthouse/index.php?action=delete&network-id=home

The controller

//get the action from the GET or POST
$action = isset($_GET['action']) ? $_GET['action'] : $_POST['action'];
//get the network ID from the GET or POST
$networkId = isset($_GET['network-id']) ? $_GET['network-id'] : $_POST['network-id'];

This is how the controller receives the information from the URL or the HTML Form. We are pulling both and populating it.

//retrieve the IP address from the current connection
$incomingIP = $_SERVER['REMOTE_ADDR'];

Here we are using PHP to obtain the IP of the system that is calling the index.php page. This is going to translate into the NetworkVO->ip.

//Create a new data access object
$networkDAO = new NetworkDAO($FILENAME);

//Controller for the action.
switch ($action) {
    case 'delete':
        //Delete a network from the list.
        $networkDAO->deleteByID($networkId);
        echo getPage(getTable($networkDAO->getAll()));
        break;
    case 'lookup':
        //Lookup the results of a single network
        $networkVO = $networkDAO->getById($networkId);
        if ($networkVO != null) {
            echo getPage("Located:<br>" . $networkVO->toString());
        } else {
            echo getPage("Not found.");
        }
        break;
    case 'store':
        //Store a new network, done by either URL call or form
        //Create Machine object and populate
        $networkVO = new NetworkVO();
        $networkVO->setCreatedOn(date("Y-m-d g:i a"));
        $networkVO->setIp($incomingIP);
        $networkVO->setId($networkId);

        //Store the data
        $networkDAO->save($networkVO, $conf['mail_ip_change']);

        //Show List - Only show list if it triggered by GET (which assuming deleted)
        if (isset($_GET['network-id'])) {
            echo getPage(getTable($networkDAO->getAll()));
        } else {
            echo getPage("Stored: $networkId <br>" . getDisplayForm($incomingIP));
        }
        break;
    case 'list':
        //Display a list of all of the networks
        echo getPage(getTable($networkDAO->getAll()));
        break;
    default:
        //Display a form for adding a new network
        echo getPage(getDisplayForm($incomingIP));
}

The first thing we do is create the NetworkDAO to allow us to interact with the datastore. We are switching on the action parameter which should be fairly straight forward to follow.

The Configuration File

In the last post we talked briefly about a configuration file. There are several ways to do this in PHP. The solution chosen here is a php file called ‘config.ini.php’ which has an associative array of configuration parameters. This file is imported into each php file where they are needed and then used at will.

$conf['app_name'] = "Lighthouse v1.0";
$conf['data_file'] = "lighthouse.dat";
$conf['controller_php_location'] = "http://www.google.com/lighthouse/index.php";
$conf['mail_ip_change'] = true;
$conf['mail_to'] = "myemail@address.com";
$conf['mail_from'] = "ipchange@address.com";
$conf['mail_reply'] = "no-reply@address.com";
$conf['mail_subj'] = "IP Change Notification";

OK that is it. In the next post I will show how to use this puppy via a client.

You may also like...

Leave a Reply