Prechádzať zdrojové kódy

Panel - Refactor Networking and Advanced for use tabs + other changes

DSR! 3 rokov pred
rodič
commit
8593bc3538

+ 35 - 0
src/pineapple/css/main.css

@@ -129,6 +129,11 @@ body {
     background-color: #eee;
 }
 
+.sidebar .active a {
+    color: #555;
+    font-weight: bold;
+}
+
 .sidebar, .module-content {
     -webkit-transition: margin 200ms ease-out;
     -moz-transition: margin 200ms ease-out;
@@ -184,6 +189,10 @@ body {
         margin-left: 0;
         float: left;
     }
+
+    .sidebar-nav .module-text {
+        visibility: hidden !important;
+    }
 }
 
 .nav > li > a {
@@ -239,6 +248,7 @@ body {
         width: 300px !important;
         max-width: 300px !important;
     }
+
     .dropdown-menu-top li a {
         white-space: normal !important;
     }
@@ -261,6 +271,10 @@ body {
         float: left !important;
         margin-right: 10px;
     }
+
+    .tab-pane {
+        padding: 15px !important;
+    }
 }
 
 .login-logo {
@@ -297,6 +311,27 @@ body {
     transform: rotate(-90deg);
 }
 
+.tab-pane {
+    padding: 25px;
+    border: 1px solid #ddd;
+    border-top: 0;
+    border-radius: 0 0 4px 4px;
+}
+
+.nav-tabs > li.active > a {
+    font-weight: bold;
+}
+
+.nav-tabs > li > a {
+    border-color: #eee #eee #ddd;
+    background-color: #eeeeee78;
+}
+
+.sidebar-nav .module-text {
+    text-align: center;
+    visibility: visible;
+}
+
 /* Use this for menu auto open on hover
     .sidebar:hover {
         margin-left: 0;

+ 2 - 1
src/pineapple/index.html

@@ -79,8 +79,9 @@
                         <a href="#!/modules/ModuleManager" title="Manage Modules">Manage Modules</a>
                     </li>
                     <li ng-class="getClass(userModule.name)" ng-repeat="userModule in userModules" module="{{ userModule.name }}">
-                        <a href="#!/modules/{{ userModule.name }}">
+                        <a href="#!/modules/{{ userModule.name }}" title="{{ userModule.title }}">
                             <img class="module-icon pull-right" ng-src="{{ userModule.icon }}" ng-if="userModule.icon != null"/>
+                            <span class="module-icon module-text pull-right" ng-if="userModule.icon == null">{{ getAlias(userModule.title) }}</span>
                             {{ userModule.title }}
                         </a>
                     </li>

+ 4 - 0
src/pineapple/js/controllers.js

@@ -33,6 +33,10 @@
             });
         });
 
+        $scope.getAlias = function(moduleName) {
+            return moduleName.match(/(\b\S)?/g).join('').match(/(^\S|\S$)?/g).join('').toUpperCase();
+        };
+
         $api.registerNavbar($scope.getModuleList);
         $scope.getModuleList();
     }])

+ 36 - 2
src/pineapple/modules/Advanced/api/module.php

@@ -89,6 +89,10 @@ class Advanced extends SystemModule
             case 'revokeApiToken':
                 $this->revokeApiToken();
                 break;
+
+            case 'downloadManualUpgrade':
+                $this->downloadManualUpgrade();
+                break;
         }
     }
 
@@ -174,6 +178,7 @@ class Advanced extends SystemModule
         if (file_exists('/tmp/hotpatch.patch')) {
             exec("cd / && patch < /tmp/hotpatch.patch");
         }
+
         $version = $this->request->version;
         $device = $this->getDevice();
         @unlink("/tmp/upgrade.bin");
@@ -184,14 +189,31 @@ class Advanced extends SystemModule
 
     private function getDownloadStatus()
     {
+        $uploadPath = '/tmp/upgrade.bin';
+        //if (!\helper\checkRunningFull("/usr/sbin/wget")) {
+        //    $this->error = "Download error";
         if (file_exists("/tmp/upgradeDownloaded")) {
-            if (hash_file('sha256', '/tmp/upgrade.bin') == $this->request->checksum) {
+            $fileHash = hash_file('sha256', $uploadPath);
+            if ((bool)$this->request->isManuelUpdate) {
+                $bytes = filesize($uploadPath);
+                $sz = 'BKMGTP';
+                $factor = floor((strlen($bytes) - 1) / 3);
+  
+                $this->response = array(
+                    "completed" => true,
+                    "sha256" => $fileHash,
+                    "downloaded" => sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor]
+                );
+            } else if ($fileHash == $this->request->checksum) {
                 $this->response = array("completed" => true);
             } else {
                 $this->error = "Checksum mismatch";
             }
         } else {
-            $this->response = array("completed" => false, "downloaded" => filesize('/tmp/upgrade.bin'));
+            $this->response = array(
+                "completed" => false,
+                "downloaded" => filesize($uploadPath)
+            );
         }
     }
 
@@ -273,4 +295,16 @@ class Advanced extends SystemModule
             $this->error = "The revokeApiToken API call requires either a 'id', 'token', or 'name' parameter";
         }
     }
+
+    private function downloadManualUpgrade()
+    {
+        $uploadPath = '/tmp/upgrade.bin';
+        @unlink($uploadPath);
+        @unlink("/tmp/upgradeDownloaded");
+
+        $url = escapeshellarg($this->request->manualUpgradeUrl);
+        $this->execBackground("wget '{$url}' -O {$uploadPath} && touch /tmp/upgradeDownloaded");
+
+        $this->response = array('success' => true);
+    }
 }

+ 104 - 46
src/pineapple/modules/Advanced/js/module.js

@@ -2,14 +2,19 @@ registerController("AdvancedResourcesController", ['$api', '$scope', '$timeout',
     $scope.freeDisk = "";
     $scope.freeMem = "";
     $scope.droppedCaches = false;
-    $scope.device = undefined;
-
-    $api.request({
-        module: 'Advanced',
-        action: 'getResources'
-    }, function(response){
-        $scope.freeDisk = response.freeDisk;
-        $scope.freeMem = response.freeMem;
+    $scope.device = "";
+
+    $scope.reloadData = (function() {
+        $scope.freeDisk = "";
+        $scope.freeMem = "";
+
+        $api.request({
+            module: 'Advanced',
+            action: 'getResources'
+        }, function(response){
+            $scope.freeDisk = response.freeDisk;
+            $scope.freeMem = response.freeMem;
+        });
     });
 
     $scope.dropCaches = (function() {
@@ -34,7 +39,9 @@ registerController("AdvancedResourcesController", ['$api', '$scope', '$timeout',
             $scope.device = response.device;
         });
     });
+
     $scope.getDevice();
+    $scope.reloadData();
 
     $api.onDeviceIdentified(function(device, scope) {
         scope.device = device;
@@ -49,6 +56,7 @@ registerController("AdvancedUSBController", ['$api', '$scope', '$timeout', '$int
     $scope.device = "";
 
     $scope.getDevice = (function() {
+        $scope.device = "";
         $api.request({
             module: 'Configuration',
             action: 'getDevice'
@@ -56,7 +64,6 @@ registerController("AdvancedUSBController", ['$api', '$scope', '$timeout', '$int
             $scope.device = response.device;
         });
     });
-    $scope.getDevice();
 
     $scope.formatSDCard = (function() {
         $api.request({
@@ -85,20 +92,25 @@ registerController("AdvancedUSBController", ['$api', '$scope', '$timeout', '$int
         });
     });
 
-    $api.request({
-        module: 'Advanced',
-        action: 'getUSB'
-    }, function(response){
-        $scope.lsusb = response.lsusb;
-    });
+    $scope.reloadData = (function() {
+        $scope.lsusb = "";
+        $scope.fstab = "";
 
-    $api.request({
-        module: 'Advanced',
-        action: 'getFstab'
-    }, function(response) {
-        if (response.error === undefined) {
-            $scope.fstab = response.fstab;
-        }
+        $api.request({
+            module: 'Advanced',
+            action: 'getUSB'
+        }, function(response){
+            $scope.lsusb = response.lsusb;
+        });
+
+        $api.request({
+            module: 'Advanced',
+            action: 'getFstab'
+        }, function(response) {
+            if (response.error === undefined) {
+                $scope.fstab = response.fstab;
+            }
+        });
     });
 
     $scope.saveFstab = (function() {
@@ -116,6 +128,9 @@ registerController("AdvancedUSBController", ['$api', '$scope', '$timeout', '$int
         });
     });
 
+    $scope.getDevice();
+    $scope.reloadData();
+
     $scope.$on('$destroy', function() {
         $interval.cancel($scope.SDCardInterval);
     });
@@ -125,13 +140,15 @@ registerController("AdvancedCSSController", ['$api', '$scope', '$timeout', funct
     $scope.css = "";
     $scope.cssSaved = false;
 
-    $api.request({
-        module: 'Advanced',
-        action: 'getCSS'
-    }, function(response) {
-        if (response.error === undefined) {
-            $scope.css = response.css;
-        }
+    $scope.reloadData = (function() {
+        $api.request({
+            module: 'Advanced',
+            action: 'getCSS'
+        }, function(response) {
+            if (response.error === undefined) {
+                $scope.css = response.css;
+            }
+        });
     });
 
     $scope.saveCSS = (function() {
@@ -160,14 +177,20 @@ registerController("AdvancedUpgradeController", ['$api', '$scope', '$interval',
     $scope.upgradeData = {};
     $scope.downloadPercentage = 0;
     $scope.firmwareVersion = "";
+    $scope.performUpgradeStart = false;
+    $scope.isManualUpgrade = false;
+    $scope.manualUpgradeUrl = "";
+    $scope.showManualUpgradeUrlError = false;
 
-    $api.request({
-        module: 'Advanced',
-        action: 'getCurrentVersion'
-    }, function(response) {
-        if (response.error === undefined) {
-            $scope.firmwareVersion = response.firmwareVersion;
-        }
+    $scope.reloadData = (function() {
+        $api.request({
+            module: 'Advanced',
+            action: 'getCurrentVersion'
+        }, function(response) {
+            if (response.error === undefined) {
+                $scope.firmwareVersion = response.firmwareVersion;
+            }
+        });
     });
 
     $scope.checkForUpgrade = (function() {
@@ -196,24 +219,59 @@ registerController("AdvancedUpgradeController", ['$api', '$scope', '$interval',
             if (response.success === true) {
                 $scope.downloading = true;
                 $scope.downloadInterval = $interval(function() {
-                    $scope.getDownloadStatus();
+                    $scope.getDownloadStatus($scope.upgradeData['checksum'], false);
                 }, 1000);
             }
         });
     });
 
-    $scope.getDownloadStatus = (function() {
+    $scope.downloadManualUpgrade = (function() {
+        var isValid = $scope.manualUpgradeUrl.match(
+            /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g
+        );
+        if (isValid === null) {
+            $scope.showManualUpgradeUrlError = true;
+            $timeout(function(){
+                $scope.showManualUpgradeUrlError = false;
+            }, 2000);
+            return;
+        }
+
+        $scope.isManualUpgrade = true;
+        $api.request({
+            module: 'Advanced',
+            action: 'downloadManualUpgrade',
+            manualUpgradeUrl: $scope.manualUpgradeUrl
+        }, function(response) {
+            if (response.success === true) {
+                $scope.downloading = true;
+                $scope.downloadInterval = $interval(function() {
+                    $scope.getDownloadStatus('', true);
+                }, 1000);
+            }
+        });
+    });
+
+    $scope.getDownloadStatus = (function(checksum, isManuelUpdate) {
         $api.request({
             module: 'Advanced',
             action: 'getDownloadStatus',
-            checksum: $scope.upgradeData['checksum']
+            checksum: checksum,
+            isManuelUpdate: isManuelUpdate
         }, function(response) {
-            if ($scope.downloaded) return;
+            if ($scope.downloaded) {
+                return;
+            }
+
             if (response.completed === true) {
                 $scope.downloading = false;
                 $scope.downloaded = true;
                 $interval.cancel($scope.downloadInterval);
-                $scope.performUpgrade();
+                if (isManuelUpdate) {
+                    $scope.upgradeData = response;
+                } else {
+                    $scope.performUpgrade();
+                }
             } else if (response.error) {
                 $scope.error = response.error;
             } else {
@@ -223,11 +281,13 @@ registerController("AdvancedUpgradeController", ['$api', '$scope', '$interval',
     });
 
     $scope.performUpgrade = (function() {
+        $scope.performUpgradeStart = true;
         $api.request({
             module: 'Advanced',
             action: 'performUpgrade'
         }, function(response) {
             if (response.success === true) {
+                $scope.performUpgradeStart = false;
             }
         });
     });
@@ -244,7 +304,7 @@ registerController("APITokenController", ['$api', '$scope', function($api, $scop
         token: ""
     };
 
-    $scope.getApiTokens = function(){
+    $scope.reloadData = function(){
         $api.request({
             module: 'Advanced',
             action: 'getApiTokens'
@@ -261,7 +321,7 @@ registerController("APITokenController", ['$api', '$scope', function($api, $scop
         }, function(response){
             $scope.newToken.name = "";
             $scope.newToken.token = response.token;
-            $scope.getApiTokens();
+            $scope.reloadData();
         });
     };
 
@@ -272,7 +332,7 @@ registerController("APITokenController", ['$api', '$scope', function($api, $scop
             action: 'revokeApiToken',
             id: id
         }, function(){
-            $scope.getApiTokens();
+            $scope.reloadData();
         });
     };
 
@@ -288,6 +348,4 @@ registerController("APITokenController", ['$api', '$scope', function($api, $scop
         var elem = $event.target;
         $scope.selectElem(elem);
     };
-
-    $scope.getApiTokens();
 }]);

+ 244 - 162
src/pineapple/modules/Advanced/module.html

@@ -1,185 +1,267 @@
-<div class="row">
-    <div class="col-md-6" ng-controller="AdvancedResourcesController">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h3 class="panel-title">
-                    Resources
-                    <span class="dropdown">
-                        <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="resourcesDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <span class="caret"></span>
-                        </button>
-                        <ul class="dropdown-menu" aria-labelledby="generalDropdown">
-                            <li ng-click="dropCaches()"><a>Drop Page Caches</a></li>
-                        </ul>
-                    </span>
-                </h3>
-            </div>
-            <div class="panel-body">
-                <div class="alert well-sm alert-success ng-hide" ng-show="droppedCaches">Successfully dropped caches.</div>
-                <pre class="scrollable-pre">{{ freeDisk }}</pre>
-                <br/>
-                <pre class="scrollable-pre">{{ freeMem }}</pre>
-            </div>
-        </div>
-    </div>
-    <div class="col-md-6" ng-controller="AdvancedUSBController">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h3 class="panel-title">
-                    USB &amp; Storage
-                    <span class="dropdown" ng-if="device == 'nano'">
-                        <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="usbDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <span class="caret"></span>
-                        </button>
-                        <ul class="dropdown-menu" aria-labelledby="generalDropdown">
-                            <li ng-click="formatSDCard()"><a>Format SD Card</a></li>
-                        </ul>
-                    </span>
-                </h3>
+<div class="tab-content">
+    <ul class="nav nav-tabs tabs-block" role="tablist">
+        <li class="nav-item active">
+            <a href="#advanced-info" role="tab" data-toggle="tab">
+                Info
+            </a>
+        </li>
+        <li class="nav-item">
+            <a href="#advanced-extras" role="tab" data-toggle="tab">
+                Extras
+            </a>
+        </li>
+        <li class="nav-item">
+            <a href="#advanced-update" role="tab" data-toggle="tab">
+                Update
+            </a>
+        </li>
+    </ul>
+
+    <div class="tab-pane fade active in" id="advanced-info">
+        <div class="row">
+            <div class="col-md-6 controller" ng-controller="AdvancedResourcesController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            Resources
+                            <span class="dropdown">
+                                <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="resourcesDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    <span class="caret"></span>
+                                </button>
+                                <ul class="dropdown-menu" aria-labelledby="generalDropdown">
+                                    <li ng-click="dropCaches()"><a>Drop Page Caches</a></li>
+                                </ul>
+                            </span>
+                            <span class="pull-right"><button class="btn btn-default btn-xs btn-fixed-length" ng-click="reloadData();">Refresh</button></span>
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <div class="alert well-sm alert-success ng-hide" ng-show="droppedCaches">Successfully dropped caches.</div>
+                        <pre class="scrollable-pre">{{ freeDisk }}</pre>
+                        <br/>
+                        <pre class="scrollable-pre">{{ freeMem }}</pre>
+                    </div>
+                </div>
             </div>
-            <div class="panel-body">
-                <div class="center-block" ng-hide="lsusb">
-                    <img class="center-block" src="img/throbber.gif">
-                    <br/>
+            <div class="col-md-6 controller" ng-controller="AdvancedUSBController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            USB &amp; Storage
+                            <span class="dropdown" ng-if="device == 'nano'">
+                                <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="usbDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    <span class="caret"></span>
+                                </button>
+                                <ul class="dropdown-menu" aria-labelledby="generalDropdown">
+                                    <li ng-click="formatSDCard()"><a>Format SD Card</a></li>
+                                </ul>
+                            </span>
+                            <span class="pull-right"><button class="btn btn-default btn-xs btn-fixed-length" ng-click="reloadData();">Refresh</button></span>
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <div class="center-block" ng-hide="lsusb">
+                            <img class="center-block" src="img/throbber.gif">
+                            <br/>
+                        </div>
+                        <div class="alert well-sm alert-info ng-hide" ng-show="formattingSDCard">Formatting SD Card, please wait. <img src="img/throbber.gif"></div>
+                        <div class="alert well-sm alert-success ng-hide" ng-show="formatSuccess">Successfully formatted SD Card</div>
+                        <pre class="scrollable-pre" ng-show="lsusb">{{ lsusb }}</pre>
+                        <p>
+                            <textarea class="form-control" rows="8" ng-model="fstab"></textarea>
+                        </p>
+                        <p class="alert well-sm alert-success" ng-show="fstabSaved">Fstab saved successfully</p>
+                        <button type="submit" class="btn btn-default" ng-click="saveFstab()">Save Fstab</button>
+                    </div>
                 </div>
-                <div class="alert well-sm alert-info ng-hide" ng-show="formattingSDCard">Formatting SD Card, please wait. <img src="img/throbber.gif"></div>
-                <div class="alert well-sm alert-success ng-hide" ng-show="formatSuccess">Successfully formatted SD Card</div>
-                <pre class="scrollable-pre" ng-show="lsusb">{{ lsusb }}</pre>
-                <p>
-                    <textarea class="form-control" rows="8" ng-model="fstab"></textarea>
-                </p>
-                <p class="alert well-sm alert-success" ng-show="fstabSaved">Fstab saved successfully</p>
-                <button type="submit" class="btn btn-default" ng-click="saveFstab()">Save Fstab</button>
             </div>
         </div>
     </div>
-</div>
+    <div class="tab-pane fade" id="advanced-extras">
+        <div class="row">
+            <div class="col-md-6 controller" ng-controller="APITokenController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            Manage API Tokens
+                            <span class="pull-right"><button class="btn btn-default btn-xs btn-fixed-length" ng-click="getApiTokens();">Refresh</button></span>
+                        </h3>
+                    </div>
+                    <table class="table table-hover table-responsive table-condensed table-layout-fixed" ng-show="apiTokens.length">
+                        <thead>
+                            <th>ID</th>
+                            <th>Name</th>
+                            <th>Token</th>
+                        </thead>
+                        <tbody>
+                            <tr ng-repeat="apiToken in apiTokens">
+                                <td class="col-md-1">{{ apiToken.rowid }}</td>
+                                <td class="col-md-3">{{ apiToken.name }}</td>
+                                <td class="col-md-5 truncated" ng-click="selectOnClick($event);">{{ apiToken.token }}</td>
+                                <td class="col-md-3"><span class="pull-right"><button tokenid="{{ apiToken.rowid }}" class="btn btn-danger btn-sm" ng-click="revokeApiToken($event);">Revoke</button></span></td>
+                            </tr>
+                        </tbody>
+                    </table>
 
-<div class="row">
-    <div class="col-md-6">
-        <div class="panel panel-default" ng-controller="AdvancedUpgradeController">
-            <div class="panel-heading">
-                <h3 class="panel-title">Firmware Upgrade (Current version: {{ firmwareVersion }})</h3>
-            </div>
-            <div class="panel-body">
-                <div ng-hide="upgradeFound">
-                    <p class="text-center text-danger" ng-show="error">{{ error }}</p>
-                    <button class="btn btn-default center-block" ng-click="checkForUpgrade()" ng-hide="loading">Check for Upgrades</button>
-                    <img class="center-block" src="img/throbber.gif" ng-show="loading">
-                    <p>
-                        <hr>
-                    </p>
-                    <p ng-if="device == 'tetra'">
-                        Warning: Firmware upgrades replace all data on the device. Please ensure any important non-system data has been backed up to external storage.
-                    </p>
-                    <p ng-if="device == 'nano'">
-                        Warning: Firmware upgrades replace all data not stored on the SD card. Please ensure any important non-system data has been backed up to external storage.
-                    </p>
-                    <p>
-                        Please stop any unnecessary services and modules before upgrading. Restarting the WiFi Pineapple without starting additional services and modules is recommended to ensure extra processes have been halted properly.
-                    </p>
-                    <p>
-                        Upgrading firmware should only be done while using a stable power source. An Ethernet connection to the WiFi Pineapple is recommended for this process.
-                    </p>
-                    <p>
-                        Once the firmware upgrade has completed the WiFi Pineapple will reboot into an initial setup state. This process will take several minutes. Do not interrupt the upgrade process by unplugging power or closing the web interface as this may result in a soft-brick state.
-                    </p>
-                    <p>
-                        For recovery or manual upgrade instructions and help please visit <a href="https://www.wifipineapple.com/?flashing">https://www.wifipineapple.com/?flashing</a>.
-                    </p>
-                </div>
-                <div ng-show="upgradeFound && !downloading && !downloaded">
-                    <p>
-                        A new Firmware (version {{upgradeData['version']}}) is available.
-                    </p>
-                    <h4>Changelog:</h4>
-                    <p ng-bind-html="upgradeData['changelog'] | rawHTML">
-                        Changelog:
-                    </p>
-                    <p>
-                        <button class="btn btn-default center-block" ng-click="downloadUpgrade()">Perform Upgrade</button>
-                    </p>
-                </div>
-                <div class="text-center" ng-show="downloading">
-                    Please wait while the firmware is being downloaded.
-                    <div class="progress">
-                        <div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="downloadPercentage" aria-valuemin="0" aria-valuemax="100" style="width: {{ downloadPercentage }}%">
-                            <span class="sr-only">{{downloadPercentage}}% Complete</span>
-                        </div>
+                    <div class="panel-body text-center" ng-hide="apiTokens.length">
+                        <span class="text-info">No API Tokens</span>
                     </div>
                 </div>
-                <div class="text-center" ng-show="downloaded">
-                    <p>Download completed. Please wait while the firmware is being flashed.</p>
-                    <p>
-                        <img src="img/throbber.gif">
-                    </p>
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            Generate New Token
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <form class="form-inline" role="form" ng-submit="genApiToken()" novalidate>
+                            <div class="form-group">
+                                <div class="input-group">
+                                    <span class="input-group-addon">Token Name</span>
+                                    <input name="tokenName" type="text" class="form-control" id="tokenName" ng-model="newToken.name">
+                                </div>
+                            </div>
+                            <button type="submit" class="btn btn-default">Generate</button>
+                        </form>
+                    </div>
                 </div>
             </div>
-        </div>
-    </div>
-    <div class="col-md-6" ng-controller="AdvancedCSSController">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h3 class="panel-title">CSS</h3>
-            </div>
-            <div class="panel-body">
-                <p>
-                    <textarea class="form-control" rows="13" ng-model="css"></textarea>
-                </p>
-                <p class="alert well-sm alert-success" ng-show="cssSaved">CSS saved successfully</p>
-                <button type="submit" class="btn btn-default" ng-click="saveCSS()">Save CSS</button>
+            <div class="col-md-6 controller" ng-controller="AdvancedCSSController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">CSS</h3>
+                    </div>
+                    <div class="panel-body">
+                        <p>
+                            <textarea class="form-control" rows="13" ng-model="css"></textarea>
+                        </p>
+                        <p class="alert well-sm alert-success" ng-show="cssSaved">CSS saved successfully</p>
+                        <button type="submit" class="btn btn-default" ng-click="saveCSS()">Save CSS</button>
+                    </div>
+                </div>
             </div>
         </div>
     </div>
-</div>
-<div class="row">
-    <div ng-controller="APITokenController">
-        <div class="col-md-6">
-            <div class="panel panel-default">
-                <div class="panel-heading">
-                    <h3 class="panel-title">
-                        Manage API Tokens
-                        <span class="pull-right"><button class="btn btn-default btn-xs btn-fixed-length" ng-click="getApiTokens();">Refresh</button></span>
-                    </h3>
-                </div>
-                <table class="table table-hover table-responsive table-condensed table-layout-fixed" ng-show="apiTokens.length">
-                    <thead>
-                        <th>ID</th>
-                        <th>Name</th>
-                        <th>Token</th>
-                    </thead>
-                    <tbody>
-                        <tr ng-repeat="apiToken in apiTokens">
-                            <td class="col-md-1">{{ apiToken.rowid }}</td>
-                            <td class="col-md-3">{{ apiToken.name }}</td>
-                            <td class="col-md-5 truncated" ng-click="selectOnClick($event);">{{ apiToken.token }}</td>
-                            <td class="col-md-3"><span class="pull-right"><button tokenid="{{ apiToken.rowid }}" class="btn btn-danger btn-sm" ng-click="revokeApiToken($event);">Revoke</button></span></td>
-                        </tr>
-                    </tbody>
-                </table>
-
-                <div class="panel-body text-center" ng-hide="apiTokens.length">
-                    <span class="text-info">No API Tokens</span>
+    <div class="tab-pane fade" id="advanced-update">
+        <div class="row controller" ng-controller="AdvancedUpgradeController">
+            <div class="col-md-6">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">Firmware Upgrade (Current version: {{ firmwareVersion }})</h3>
+                    </div>
+                    <div class="panel-body">
+                        <div ng-hide="upgradeFound">
+                            <p class="text-center text-danger" ng-show="error">{{ error }}</p>
+                            <button class="btn btn-default center-block" ng-click="checkForUpgrade()" ng-hide="loading">Check for Upgrades</button>
+                            <img class="center-block" src="img/throbber.gif" ng-show="loading">
+                            <p>
+                                <hr>
+                            </p>
+                            <p ng-if="device == 'tetra'">
+                                Warning: Firmware upgrades replace all data on the device. Please ensure any important non-system data has been backed up to external storage.
+                            </p>
+                            <p ng-if="device == 'nano'">
+                                Warning: Firmware upgrades replace all data not stored on the SD card. Please ensure any important non-system data has been backed up to external storage.
+                            </p>
+                            <p>
+                                Please stop any unnecessary services and modules before upgrading. Restarting the WiFi Pineapple without starting additional services and modules is recommended to ensure extra processes have been halted properly.
+                            </p>
+                            <p>
+                                Upgrading firmware should only be done while using a stable power source. An Ethernet connection to the WiFi Pineapple is recommended for this process.
+                            </p>
+                            <p>
+                                Once the firmware upgrade has completed the WiFi Pineapple will reboot into an initial setup state. This process will take several minutes. Do not interrupt the upgrade process by unplugging power or closing the web interface as this may result in a soft-brick state.
+                            </p>
+                            <p>
+                                For recovery or manual upgrade instructions and help please visit <a href="https://www.wifipineapple.com/?flashing">https://www.wifipineapple.com/?flashing</a>.
+                            </p>
+                        </div>
+                        <div ng-show="upgradeFound && !downloading && !downloaded">
+                            <p>
+                                A new Firmware (version {{upgradeData['version']}}) is available.
+                            </p>
+                            <h4>Changelog:</h4>
+                            <p ng-bind-html="upgradeData['changelog'] | rawHTML">
+                                Changelog:
+                            </p>
+                            <p>
+                                <button class="btn btn-default center-block" ng-click="downloadUpgrade()">Perform Upgrade</button>
+                            </p>
+                        </div>
+                        <div class="text-center" ng-show="!isManualUpgrade && downloading">
+                            Please wait while the firmware is being downloaded.
+                            <div class="progress">
+                                <div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="downloadPercentage" aria-valuemin="0" aria-valuemax="100" style="width: {{ downloadPercentage }}%">
+                                    <span class="sr-only">{{downloadPercentage}}% Complete</span>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="text-center" ng-show="!isManualUpgrade && downloaded">
+                            <p>Download completed. Please wait while the firmware is being flashed.</p>
+                            <p>
+                                <img src="img/throbber.gif">
+                            </p>
+                        </div>
+                    </div>
                 </div>
             </div>
-            <div class="panel panel-default">
-                <div class="panel-heading">
-                    <h3 class="panel-title">
-                        Generate New Token
-                    </h3>
-                </div>
-                <div class="panel-body">
-                    <form class="form-inline" role="form" ng-submit="genApiToken()" novalidate>
-                        <div class="form-group">
+            <div class="col-md-6 controller">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">Manual upgrade</h3>
+                    </div>
+                    <div class="panel-body">
+                        <div ng-hide="upgradeFound">
+                            <p class="alert well-sm alert-success" ng-show="showPasswordSuccess">Password changed successfully.</p>
+                            <p class="alert well-sm alert-danger" ng-show="showManualUpgradeUrlError">Please check the url entered are correct.</p>
                             <div class="input-group">
-                                <span class="input-group-addon">Token Name</span>
-                                <input name="tokenName" type="text" class="form-control" id="tokenName" ng-model="newToken.name">
+                                <span class="input-group-addon">Firmware URL</span>
+                                <input type="text" class="form-control" ng-model="manualUpgradeUrl">
+                            </div>
+                            <br/>
+                            <div class="input-group">
+                                <button class="btn btn-default" ng-click="downloadManualUpgrade()" ng-disabled="isManualUpgrade && downloading">
+                                    <span ng-hide="isManualUpgrade && downloading">Download</span>
+                                    <img class="image-small-18" src="img/throbber.gif" ng-show="isManualUpgrade && downloading">
+                                </button>
                             </div>
                         </div>
-                        <button type="submit" class="btn btn-default">Generate</button>
-                    </form>
+                        <div class="text-center" ng-show="isManualUpgrade && downloaded">
+                            <p><hr></p>
+                            <p>Download completed. If the data is correct continue with the installation.</p>
+                            <p>Downloaded: {{ upgradeData['downloaded'] }}</p>
+                            <p>Hash sha256: {{ upgradeData['sha256'] }}</p>
+
+                            <button class="btn btn-default" ng-click="performUpgrade()" ng-disabled="performUpgradeStart">
+                                <span ng-hide="performUpgradeStart">Install</span>
+                                <img class="image-small-18" src="img/throbber.gif" ng-show="performUpgradeStart">
+                            </button>
+                        </div>
+                    </div>
                 </div>
             </div>
         </div>
     </div>
 </div>
+
+<script type="text/javascript">
+    function loadActiveTabData() {
+        $('.tab-pane.active .controller').each(function(index) {
+            var controller = angular.element( $(this)[0] ).scope();
+            if (typeof controller.reloadData === "function") { 
+                controller.reloadData();
+            }
+        });
+    }
+
+    $(function() {
+        $('.tabs-block a').click(function(event) {
+            event.preventDefault();
+            $(this).tab('show');
+        });
+
+        $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+            loadActiveTabData();
+        });
+    });
+</script>

+ 11 - 5
src/pineapple/modules/Networking/api/Interfaces.php

@@ -43,7 +43,7 @@ class Interfaces
         return false;
     }
 
-    public function setMac($random, $interface, $newMac)
+    public function setMac($random, $interface, $newMac, $forceReload)
     {
         $uciID = $this->getUciID($interface);
         $interface = escapeshellarg($interface);
@@ -56,7 +56,12 @@ class Interfaces
         }
 
         uciSet("wireless.@wifi-iface[{$uciID}].macaddr", $mac);
-        exec("wifi");
+        if ($forceReload) {
+            exec("wifi");
+        } else {
+            exec("ifconfig {$interface} up");
+        }
+
         return array("success" => true, "uci" => $uciID);
     }
 
@@ -86,11 +91,12 @@ class Interfaces
         exec("ifconfig -a | grep wlan | awk '{print \$1}'", $interfaceArray);
 
         foreach ($interfaceArray as $interface) {
-            if (substr($interface, 0, 5) === "wlan0") {
-                continue;
-            }
+            //if (substr($interface, 0, 5) === "wlan0") {
+            //    continue;
+            //}
             array_push($clientInterfaces, $interface);
         }
+
         return array_reverse($clientInterfaces);
     }
 }

+ 25 - 4
src/pineapple/modules/Networking/api/module.php

@@ -96,6 +96,10 @@ class Networking extends SystemModule
             case 'saveWirelessConfig':
                 $this->saveWirelessConfig();
                 break;
+
+            case 'getInfoData':
+                $this->getInfoData();
+                break;
         }
     }
 
@@ -124,10 +128,8 @@ class Networking extends SystemModule
 
     private function getAdvancedData()
     {
-        exec("ifconfig -a", $ifconfig);
         $this->response = array(
             "hostname" => gethostname(),
-            "ifconfig" => implode("\n", $ifconfig),
             "wireless" => file_get_contents('/etc/config/wireless')
         );
     }
@@ -179,10 +181,10 @@ class Networking extends SystemModule
         $this->response = $interfaceHelper->getMacData();
     }
 
-    private function setMac($random)
+    private function setMac($force_random)
     {
         $interfaceHelper = new \helper\Interfaces();
-        $this->response = $interfaceHelper->setMac($random, $this->request->interface, $this->request->mac);
+        $this->response = $interfaceHelper->setMac($force_random, $this->request->interface, $this->request->mac, $this->request->forceReload);
     }
 
     private function resetMac()
@@ -269,4 +271,23 @@ class Networking extends SystemModule
             $this->response = array("success" => true);
         }
     }
+
+    private function getInfoData()
+    {
+        $type = (int)$this->request->type;
+        $command = '';
+        switch ($type) {
+            case 2:
+                $command = 'iw dev';
+                break;
+            case 3:
+                $command = 'airmon-ng';
+                break;
+            default:
+                $command = 'ifconfig -a';
+        }
+
+        exec($command, $info);
+        $this->response = implode("\n", $info);
+    }
 }

+ 38 - 22
src/pineapple/modules/Networking/js/module.js

@@ -5,7 +5,7 @@ registerController('NetworkingRouteController', ['$api', '$scope', '$timeout', f
     $scope.routeInterfaces = [];
 
 
-    $scope.getRoute = (function(){
+    $scope.reloadData = (function(){
         $api.request({
             module: 'Networking',
             action: 'getRoutingTable'
@@ -37,7 +37,7 @@ registerController('NetworkingRouteController', ['$api', '$scope', '$timeout', f
             routeInterface: $scope.routeInterface
         }, function(response) {
             if (response.success === true) {
-                $scope.getRoute();
+                $scope.reloadData();
                 $scope.updatedRoute = true;
                 $timeout(function(){
                     $scope.updatedRoute = false;
@@ -46,8 +46,7 @@ registerController('NetworkingRouteController', ['$api', '$scope', '$timeout', f
         });
     });
 
-    $scope.getRoute();
-
+    //$scope.reloadData();
 }]);
 
 registerController('NetworkingAccessPointsController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
@@ -85,7 +84,7 @@ registerController('NetworkingAccessPointsController', ['$api', '$scope', '$time
         })
     });
 
-    $scope.getAPConfiguration = (function() {
+    $scope.reloadData = (function() {
         $api.request({
             module: "Networking",
             action: "getAPConfig"
@@ -99,7 +98,7 @@ registerController('NetworkingAccessPointsController', ['$api', '$scope', '$time
         })
     });
 
-    $scope.getAPConfiguration();
+    //$scope.reloadData();
 }]);
 
 registerController('NetworkingClientModeController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
@@ -154,13 +153,13 @@ registerController('NetworkingClientModeController', ['$api', '$scope', '$timeou
         }, function() {
             $scope.key = "";
             $timeout(function() {
-                $scope.checkConnection();
+                $scope.reloadData();
                 $scope.connecting = false;
             }, 10000);
         });
     });
 
-    $scope.checkConnection = (function() {
+    $scope.reloadData = (function() {
         $api.request({
             module: 'Networking',
             action: 'checkConnection'
@@ -197,7 +196,7 @@ registerController('NetworkingClientModeController', ['$api', '$scope', '$timeou
         });
     });
 
-    $scope.checkConnection();
+    $scope.reloadData();
 }]);
 
 registerController('NetworkingFirewallController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
@@ -216,7 +215,7 @@ registerController('NetworkingFirewallController', ['$api', '$scope', '$timeout'
     });
     $scope.getDevice();
 
-    $scope.getFirewallConfig = (function() {
+    $scope.reloadData = (function() {
         $api.request({
             module: 'Networking',
             action: 'getFirewallConfig'
@@ -244,7 +243,7 @@ registerController('NetworkingFirewallController', ['$api', '$scope', '$timeout'
         })
     });
 
-    $scope.getFirewallConfig();
+    //$scope.reloadData();
 }]);
 
 registerController('NetworkingMACAddressesController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
@@ -252,8 +251,9 @@ registerController('NetworkingMACAddressesController', ['$api', '$scope', '$time
     $scope.selectedInterface = "wlan0";
     $scope.newMac = "";
     $scope.modifyingMAC = false;
+    $scope.forceReload = false;
 
-    $scope.getMacData = (function() {
+    $scope.reloadData = (function() {
         $api.request({
             module: 'Networking',
             action: 'getMacData'
@@ -270,13 +270,14 @@ registerController('NetworkingMACAddressesController', ['$api', '$scope', '$time
             module: 'Networking',
             action: 'setMac',
             interface: $scope.selectedInterface,
-            mac: $scope.newMac
+            mac: $scope.newMac,
+            forceReload: $scope.forceReload
         }, function(response) {
             if (response.error === undefined) {
                 $scope.newMac = "";
                 $timeout(function(){
                     $scope.modifyingMAC = false;
-                    $scope.getMacData();
+                    $scope.reloadData();
                 }, 6000);
             }
         });
@@ -287,13 +288,14 @@ registerController('NetworkingMACAddressesController', ['$api', '$scope', '$time
         $api.request({
             module: 'Networking',
             action: 'setRandomMac',
-            interface: $scope.selectedInterface
+            interface: $scope.selectedInterface,
+            forceReload: $scope.forceReload
         }, function(response) {
             if (response.error === undefined) {
                 $scope.newMac = "";
                 $timeout(function(){
                     $scope.modifyingMAC = false;
-                    $scope.getMacData();
+                    $scope.reloadData();
                 }, 6000);
             }
         });
@@ -310,13 +312,13 @@ registerController('NetworkingMACAddressesController', ['$api', '$scope', '$time
                 $scope.newMac = "";
                 $timeout(function(){
                     $scope.modifyingMAC = false;
-                    $scope.getMacData();
+                    $scope.reloadData();
                 }, 6000);
             }
         });
     });
 
-    $scope.getMacData();
+    $scope.reloadData();
 }]);
 
 registerController('NetworkingAdvancedController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
@@ -325,12 +327,10 @@ registerController('NetworkingAdvancedController', ['$api', '$scope', '$timeout'
     $scope.wirelessUpdated = false;
     $scope.data = {
         hostname: "Pineapple",
-        ifconfig: "",
         wireless: ""
     };
 
     $scope.reloadData = (function() {
-        $scope.data['ifconfig'] = 'Loading...';
         $scope.data['wireless'] = 'Loading...';
         $api.request({
             module: 'Networking',
@@ -388,7 +388,7 @@ registerController('NetworkingAdvancedController', ['$api', '$scope', '$timeout'
         });
     });
 
-    $scope.reloadData();
+    //$scope.reloadData();
 }]);
 
 registerController("OUILookupController", ['$api', '$scope', '$timeout', '$http', function($api, $scope, $timeout, $http) {
@@ -490,5 +490,21 @@ registerController("OUILookupController", ['$api', '$scope', '$timeout', '$http'
             }, 2000);
         };
     };
+}]);
+
+registerController('NetworkingInfoController', ['$api', '$scope', '$timeout', function($api, $scope, $timeout) {
+    $scope.info = '';
 
-}]);
+    $scope.getInfoData = (function(type) {
+        $scope.info = 'Loading...';
+        $api.request({
+            module: 'Networking',
+            action: 'getInfoData',
+            type: type
+        }, function(response) {
+            if (response.error === undefined) {
+                $scope.info = response;
+            }
+        });
+    });
+}]);

+ 398 - 331
src/pineapple/modules/Networking/module.html

@@ -1,386 +1,453 @@
-<div class="row">
-    <div class="col-md-12" ng-controller="NetworkingRouteController">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h3 class="panel-title">Route
-                    <span class="dropdown">
-                        <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="NetworkingRouteDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <span class="caret"></span>
-                        </button>
-                        <ul class="dropdown-menu" aria-labelledby="NetworkingRouteDropdown">
-                            <li ng-click="restartDNS()"><a>Restart DNS</a></li>
-                        </ul>
-                    </span>
-                </h3>
-            </div>
-            <div class="panel-body">
-                <p class="alert well-sm alert-success" ng-show="restartedDNS">DNS restarted successfully</p>
-                <pre class="scrollable-pre">{{ routeTable }}</pre>
+<div class="tab-content">
+    <ul class="nav nav-tabs tabs-block" role="tablist">
+        <li class="nav-item active">
+            <a href="#networking-main" role="tab" data-toggle="tab">
+                Tools
+            </a>
+        </li>
+        <li class="nav-item">
+            <a href="#networking-config" role="tab" data-toggle="tab">
+                Config
+            </a>
+        </li>
+    </ul>
 
-                <div class="col-md-8">
-                    <div class="row">
-                        <div class="input-group">
-                            <span class="input-group-addon">Default Route</span>
-                            <input type="text" class="form-control" id="routeIP" name="routeIP" placeholder="172.16.42.42" ng-model="routeIP">
-                            <span class="input-group-addon">Interface</span>
-                            <select class="form-control inline-form" id="routeInterface" name="routeInterface" ng-model="routeInterface">
-                                <option ng-repeat="interface in routeInterfaces">{{ interface }}</option>
-                            </select>
-                        </div>
-                        <div class="col-md-12 input-group" ng-show="updatedRoute">
-                            <br/>
-                            <p class="alert well-sm alert-success">The default route has been updated successfully</p>
+    <div class="tab-pane fade active in" id="networking-main">
+        <div class="row">
+            <div class="col-md-6">
+                <div class="controller" ng-controller="OUILookupController">
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h3 class="panel-title">OUI Lookup
+                                <span class="dropdown">
+                                <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="OUIDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    <span class="caret"></span>
+                                </button>
+                                <ul class="dropdown-menu" aria-labelledby="OUIDropdown">
+                                    <li ng-click="removeOUIDatabase()"><a>Remove OUI Database</a></li>
+                                </ul>
+                            </span>
+                            </h3>
                         </div>
-                    </div>
-                </div>
-                <div class="col-md-1"></div>
-                <div class="col-md-3">
-                    <div class="row">
-                        <div class="input-group">
-                            <button class="btn btn-default" ng-click="updateRoute()">Update Route</button>
+                        <div class="panel-body">
+                            <div ng-if="isOUIPresent()">
+                                <form class="form-inline" role="form" novalidate>
+                                    <div class="form-group">
+                                        <div class="input-group">
+                                            <span class="input-group-addon">MAC Address</span>
+                                            <input name="macAddress" placeholder="00:11:22:33:44:55" type="text" class="form-control" id="macAddress" ng-model="$parent.macAddress">
+                                        </div>
+                                        <br/>
+                                    </div>
+                                    <div class="btn-group" role="group">
+                                        <button ng-click="lookupMACAddress();" ng-disabled="ouiLoading" class="btn btn-default"><span ng-hide="ouiLoading">Lookup</span><img class="center-block" ng-show="ouiLoading" src="img/throbber.gif" /></button>
+                                    </div>
+                                    <br/>
+                                </form>
+                                <br/>
+                                <span ng-show="vendor">{{ vendor }}</span>
+                            </div>
+                            <div ng-if="!isOUIPresent()">
+                                <button ng-click="downloadOUIDatabase()" ng-disabled="gettingOUI" class="btn btn-default"><span ng-hide="gettingOUI">Download OUI Database</span><img class="center-block" ng-show="gettingOUI" src="img/throbber.gif" /></button>
+                                <br/>
+                                <span class="text-muted">Note: The OUI Database is downloaded from WiFiPineapple.com</span>
+                            </div>
                         </div>
                     </div>
                 </div>
-            </div>
-        </div>
-    </div>
-</div>
 
-<div class="row">
-    <div class="col-md-6" ng-controller="NetworkingAccessPointsController">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h3 class="panel-title">Access Points</h3>
-            </div>
-            <div class="panel-body">
-                <div class="col-md-12">
-                    <div class="row">
-                        <div class="input-group">
-                            <span class="input-group-addon">AP Channel</span>
-                            <select class="form-control" ng-model="apConfig['selectedChannel']">
-                                <option ng-repeat="channel in apConfig['availableChannels']">{{ channel }}</option>
-                            </select>
-                        </div>
-                        <br/>
-                        <div class="input-group">
-                            <span class="fixed-addon-width-3 input-group-addon">Management SSID</span>
-                            <input type="text" class="form-control" ng-model="apConfig['managementSSID']">
-                        </div>
-                        <br/>
-                        <div class="input-group">
-                            <span class="fixed-addon-width-3 input-group-addon">Management PSK</span>
-                            <input type="password" class="form-control" ng-model="apConfig['managementKey']">
-                        </div>
-                        <br/>
-                        <div class="input-group">
-                            <div class="col-md-5">
-                               <div class="checkbox">
-                                    <label for="hideManagementAP"><input id="hideManagementAP" type="checkbox" value="" ng-model="apConfig['hideManagementAP']">Hide Management AP</label>
+                <div class="panel panel-default controller" ng-controller="NetworkingMACAddressesController">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">MAC Addresses
+                            <span class="dropdown">
+                                <button ng-disabled="modifyingMAC" class="btn btn-xs btn-default dropdown-toggle" type="button" id="NetworkingAccessPointsDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    <span class="caret"></span>
+                                </button>
+                                <ul class="dropdown-menu" aria-labelledby="NetworkingAccessPointsDropdown">
+                                    <li ng-disabled="waiting" ng-click="resetMac()"><a>Restore Default MAC Address</a></li>
+                                </ul>
+                            </span>
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <div class="col-md-12">
+                            <div class="row">
+                                <div class="input-group">
+                                    <span class="input-group-addon">Interface</span>
+                                    <select ng-disabled="modifyingMAC" class="form-control" ng-model="selectedInterface">
+                                        <option ng-repeat="(interface, mac) in interfaces">{{ interface }}</option>
+                                    </select>
                                 </div>
-                            </div>
-                            <div class="col-md-5">
-                                <div class="checkbox">
-                                    <label for="disableManagementAP"><input id="disableManagementAP" type="checkbox" value="" ng-model="apConfig['disableManagementAP']">Disable Management AP</label>
+                                <br/>
+                                <div class="input-group">
+                                    <span class="fixed-addon-width-3 input-group-addon">Current MAC</span>
+                                    <input type="text" class="form-control autoselect" ng-model="interfaces[selectedInterface]" disabled>
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <span class="fixed-addon-width-3 input-group-addon">New MAC</span>
+                                    <input ng-disabled="modifyingMAC" type="text" class="form-control" ng-model="newMac">
+                                </div>
+                                <div class="input-group">
+                                    <div class="checkbox">
+                                        <label><input type="checkbox" value="" ng-model="forceReload">
+                                            Reload Wifi after change
+                                        </label>
+                                    </div>
                                 </div>
+                                <br/>
+                                <button ng-disabled="modifyingMAC" class="btn btn-default" ng-click="setMac()">Set New MAC</button>
+                                <button ng-disabled="modifyingMAC" class="btn btn-default" ng-click="setRandomMac()">Set Random MAC</button>
+                                <img ng-show="modifyingMAC" src="img/throbber.gif"/>
+                                <br/>
+                                <small class="text-muted">Note: Changing MAC addresses will restart the WiFi.</small>
                             </div>
                         </div>
-                        <br/>
                     </div>
-                    <div class="row">
-                        <div class="input-group">
-                            <span class="input-group-addon">Open SSID</span>
-                            <input type="text" class="form-control" ng-model="apConfig['openSSID']">
-                        </div>
-                        <br/>
-                        <div class="input-group">
-                            <span class="input-group-addon">Maximum Clients</span>
-                            <input type="text" class="form-control" ng-model="apConfig['maxClients']">
+                </div>
+            
+                <div class="controller" ng-controller="NetworkingClientModeController">
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h3 class="panel-title">WiFi Client Mode</h3>
                         </div>
-                        <br/>
-                        <div class="input-group">
-                            <div class="col-md-12">
-                               <div class="checkbox">
-                                    <label for="hideOpenAP"><input id="hideOpenAP" type="checkbox" value="" ng-model="apConfig['hideOpenAP']">Hide Open SSID</label>
+                        <div class="panel-body">
+                            <div class="col-md-12" ng-show="connected">
+                                <div class="row">
+                                    <div class="input-group">
+                                        <span class="input-group-addon">Interface</span>
+                                        <input type="text" class="form-control" ng-model="connectedInterface" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width input-group-addon">SSID</span>
+                                        <input type="text" class="form-control" ng-model="connectedSSID" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width input-group-addon">IP</span>
+                                        <input type="text" class="form-control" ng-model="connectedIP" disabled>
+                                    </div>
+                                    <br/>
+                                    <button class="btn btn-default" ng-click="checkConnection()" ng-hide="disconnecting">Refresh</button>
+                                    <button class="btn btn-default" ng-click="disconnect()" ng-hide="disconnecting">Disconnect</button>
+                                    <img src="img/throbber.gif" ng-show="disconnecting">
+                                </div>
+                            </div>
+                            <div class="col-md-12" ng-hide="connected">
+                                <div class="row">
+                                    <div class="input-group">
+                                        <span class="input-group-addon">Interface</span>
+                                        <select class="form-control" ng-model="selectedInterface" ng-disabled="scanning">
+                                            <option ng-repeat="interface in interfaces">{{ interface }}</option>
+                                        </select>
+                                        <span class="input-group-btn">
+                                            <button ng-disabled="scanning" class="btn btn-default" type="button" ng-click="scanForNetworks()">
+                                                <span ng-hide="scanning">Scan</span>
+                                                <img class="image-small-18" src="img/throbber.gif" ng-show="scanning">
+                                            </button>
+                                        </span>
+                                    </div>
+                                    <small class="text-muted" ng-hide="accessPoints.length !== 0" ng-show="selectedInterface === 'wlan1' || selectedInterface === 'wlan1mon'"> Note: Choosing wlan1 will interfere with PineAP.</small>
+                                    <div class="alert well-sm alert-warning" role="alert" ng-show="noNetworkFound">
+                                        <span>No networks were found.</span>
+                                    </div>
+                                    <br/>
+                                </div>
+                                <div class="row" ng-show="accessPoints.length">
+                                    <div class="input-group">
+                                        <span class="input-group-addon">Access Point</span>
+                                        <select class="form-control" ng-options="ap.ssid for ap in accessPoints track by ap.mac" ng-model="selectedAP"></select>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group" ng-show="(selectedAP['security'] !== 'Open')">
+                                        <span class="input-group-addon">Password</span>
+                                        <input type="password" class="form-control" ng-model="key">
+                                    </div>
+                                    <br/>
+                                </div>
+                                <br/><br/>
+                                <div class="row" ng-show="accessPoints.length">
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width-2 input-group-addon">BSSID</span>
+                                        <input type="text" class="form-control" ng-model="selectedAP['mac']" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width-2 input-group-addon">SSID</span>
+                                        <input type="text" class="form-control" ng-model="selectedAP['ssid']" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width-2 input-group-addon">Channel</span>
+                                        <input type="text" class="form-control" ng-model="selectedAP['channel']" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width-2 input-group-addon">Signal</span>
+                                        <input type="text" class="form-control" ng-model="selectedAP['signal']" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width-2 input-group-addon">Quality</span>
+                                        <input type="text" class="form-control" ng-model="selectedAP['quality']" disabled>
+                                    </div>
+                                    <br/>
+                                    <div class="input-group">
+                                        <span class="fixed-addon-width-2 input-group-addon">Security</span>
+                                        <input type="text" class="form-control" ng-model="selectedAP['security']" disabled>
+                                    </div>
+                                    <br/>
+                                </div>
+                                <div class="row">
+                                    <button class="btn btn-default" ng-click="connectToAP()" ng-show="accessPoints.length" ng-disabled="connecting">
+                                        <span ng-hide="connecting">Connect</span>
+                                        <img class="image-small-18" src="img/throbber.gif" ng-show="connecting">
+                                    </button>
                                 </div>
                             </div>
                         </div>
-                        <br/>
-                        <div class="input-group">
-                            <button class="btn btn-default" ng-click="saveAPConfiguration()">Update Access Points</button>
-                        </div>
-                        <br/>
-                        <div class="alert well-sm alert-success" ng-show="apConfigurationSaved">The Wireless AP Configuration has been saved. The radios will now restart. If connected over Wireless, you may need to reconnect.</div>
-                        <div class="alert well-sm alert-danger" ng-show="apConfigurationError">{{ apConfigurationError }}</div>
                     </div>
                 </div>
             </div>
-        </div>
-    </div>
 
-    <div class="col-md-6">
-        <div ng-controller="NetworkingFirewallController" ng-show="device == 'tetra'">
-            <div class="panel panel-default">
-                <div class="panel-heading">
-                    <h3 class="panel-title">Firewall</h3>
-                </div>
-                <div class="panel-body">
-                    <div class="row">
+            <div class="col-md-6 controller" ng-controller="NetworkingInfoController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">Info</h3>
+                    </div>
+                    <div class="panel-body">
                         <div class="col-md-12">
-                            <div class="input-group">
-                                <div class="checkbox">
-                                    <label><input type="checkbox" ng-model="WANSSHAccess">Allow SSH Access via WAN</label><br/>
-                                    <label><input type="checkbox" ng-model="WANUIAccess">Allow Web UI Access via WAN</label>
-                                </div>
-                                <br/>
-                                <button class="btn btn-default" ng-click="setFirewallConfig();">Save</button>
+                            <div class="row">
+                                <button class="btn btn-default" ng-click="getInfoData(1)">ifconfig</button>
+                                <button class="btn btn-default" ng-click="getInfoData(2)">iw dev</button>
+                                <button class="btn btn-default" ng-click="getInfoData(3)">airmon-ng</button>
                             </div>
                             <br/>
-                            <div class="alert well-sm alert-success" ng-show="firewallUpdated">The Firewall Configuration has been updated.</div>
+                            <div class="row">
+                                <pre class="scrollable-pre">{{ info }}</pre>
+                            </div>
                         </div>
                     </div>
                 </div>
             </div>
         </div>
-        <div ng-controller="OUILookupController">
-            <div class="panel panel-default">
-                <div class="panel-heading">
-                    <h3 class="panel-title">OUI Lookup
-                        <span class="dropdown">
-                        <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="OUIDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <span class="caret"></span>
-                        </button>
-                        <ul class="dropdown-menu" aria-labelledby="OUIDropdown">
-                            <li ng-click="removeOUIDatabase()"><a>Remove OUI Database</a></li>
-                        </ul>
-                    </span>
-                    </h3>
-                </div>
-                <div class="panel-body">
-                    <div ng-if="isOUIPresent()">
-                        <form class="form-inline" role="form" novalidate>
-                            <div class="form-group">
+
+    </div>
+    
+    <div class="tab-pane fade" id="networking-config">
+        <div class="row">
+            <div class="col-md-12 controller" ng-controller="NetworkingRouteController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">Route
+                            <span class="dropdown">
+                                <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="NetworkingRouteDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    <span class="caret"></span>
+                                </button>
+                                <ul class="dropdown-menu" aria-labelledby="NetworkingRouteDropdown">
+                                    <li ng-click="restartDNS()"><a>Restart DNS</a></li>
+                                </ul>
+                            </span>
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <p class="alert well-sm alert-success" ng-show="restartedDNS">DNS restarted successfully</p>
+                        <pre class="scrollable-pre">{{ routeTable }}</pre>
+
+                        <div class="col-md-8">
+                            <div class="row">
                                 <div class="input-group">
-                                    <span class="input-group-addon">MAC Address</span>
-                                    <input name="macAddress" placeholder="00:11:22:33:44:55" type="text" class="form-control" id="macAddress" ng-model="$parent.macAddress">
+                                    <span class="input-group-addon">Default Route</span>
+                                    <input type="text" class="form-control" id="routeIP" name="routeIP" placeholder="172.16.42.42" ng-model="routeIP">
+                                    <span class="input-group-addon">Interface</span>
+                                    <select class="form-control inline-form" id="routeInterface" name="routeInterface" ng-model="routeInterface">
+                                        <option ng-repeat="interface in routeInterfaces">{{ interface }}</option>
+                                    </select>
+                                </div>
+                                <div class="col-md-12 input-group" ng-show="updatedRoute">
+                                    <br/>
+                                    <p class="alert well-sm alert-success">The default route has been updated successfully</p>
                                 </div>
-                                <br/>
                             </div>
-                            <div class="btn-group" role="group">
-                                <button ng-click="lookupMACAddress();" ng-disabled="ouiLoading" class="btn btn-default"><span ng-hide="ouiLoading">Lookup</span><img class="center-block" ng-show="ouiLoading" src="img/throbber.gif" /></button>
+                        </div>
+                        <div class="col-md-1"></div>
+                        <div class="col-md-3">
+                            <div class="row">
+                                <div class="input-group">
+                                    <button class="btn btn-default" ng-click="updateRoute()">Update Route</button>
+                                </div>
                             </div>
-                            <br/>
-                        </form>
-                        <br/>
-                        <span ng-show="vendor">{{ vendor }}</span>
-                    </div>
-                    <div ng-if="!isOUIPresent()">
-                        <button ng-click="downloadOUIDatabase()" ng-disabled="gettingOUI" class="btn btn-default"><span ng-hide="gettingOUI">Download OUI Database</span><img class="center-block" ng-show="gettingOUI" src="img/throbber.gif" /></button>
-                        <br/>
-                        <span class="text-muted">Note: The OUI Database is downloaded from WiFiPineapple.com</span>
+                        </div>
                     </div>
                 </div>
             </div>
         </div>
-        <div ng-controller="NetworkingClientModeController">
-            <div class="panel panel-default" >
-                <div class="panel-heading">
-                    <h3 class="panel-title">WiFi Client Mode</h3>
-                </div>
-                <div class="panel-body">
-                    <div class="col-md-12" ng-show="connected">
-                        <div class="row">
-                            <div class="input-group">
-                                <span class="input-group-addon">Interface</span>
-                                <input type="text" class="form-control" ng-model="connectedInterface" disabled>
-                            </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width input-group-addon">SSID</span>
-                                <input type="text" class="form-control" ng-model="connectedSSID" disabled>
-                            </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width input-group-addon">IP</span>
-                                <input type="text" class="form-control" ng-model="connectedIP" disabled>
-                            </div>
-                            <br/>
-                            <button class="btn btn-default" ng-click="checkConnection()" ng-hide="disconnecting">Refresh</button>
-                            <button class="btn btn-default" ng-click="disconnect()" ng-hide="disconnecting">Disconnect</button>
-                            <img src="img/throbber.gif" ng-show="disconnecting">
-                        </div>
+
+        <div class="row">
+            <div class="col-md-6 controller" ng-controller="NetworkingAccessPointsController">
+                <div class="panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">Access Points</h3>
                     </div>
-                    <div class="col-md-12" ng-hide="connected">
-                        <div class="row">
-                            <div class="input-group">
-                                <span class="input-group-addon">Interface</span>
-                                <select class="form-control" ng-model="selectedInterface" ng-disabled="scanning">
-                                    <option ng-repeat="interface in interfaces">{{ interface }}</option>
-                                </select>
-                                <span class="input-group-btn">
-                                    <button ng-disabled="scanning" class="btn btn-default" type="button" ng-click="scanForNetworks()">
-                                        <span ng-hide="scanning">Scan</span>
-                                        <img class="image-small-18" src="img/throbber.gif" ng-show="scanning">
-                                    </button>
-                                </span>
-                            </div>
-                            <small class="text-muted" ng-hide="accessPoints.length !== 0" ng-show="selectedInterface === 'wlan1' || selectedInterface === 'wlan1mon'"> Note: Choosing wlan1 will interfere with PineAP.</small>
-                            <div class="alert well-sm alert-warning" role="alert" ng-show="noNetworkFound">
-                                <span>No networks were found.</span>
-                            </div>
-                            <br/>
-                        </div>
-                        <div class="row" ng-show="accessPoints.length">
-                            <div class="input-group">
-                                <span class="input-group-addon">Access Point</span>
-                                <select class="form-control" ng-options="ap.ssid for ap in accessPoints track by ap.mac" ng-model="selectedAP"></select>
-                            </div>
-                            <br/>
-                            <div class="input-group" ng-show="(selectedAP['security'] !== 'Open')">
-                                <span class="input-group-addon">Password</span>
-                                <input type="password" class="form-control" ng-model="key">
-                            </div>
-                            <br/>
-                        </div>
-                        <br/><br/>
-                        <div class="row" ng-show="accessPoints.length">
-                            <div class="input-group">
-                                <span class="fixed-addon-width-2 input-group-addon">BSSID</span>
-                                <input type="text" class="form-control" ng-model="selectedAP['mac']" disabled>
-                            </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width-2 input-group-addon">SSID</span>
-                                <input type="text" class="form-control" ng-model="selectedAP['ssid']" disabled>
-                            </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width-2 input-group-addon">Channel</span>
-                                <input type="text" class="form-control" ng-model="selectedAP['channel']" disabled>
-                            </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width-2 input-group-addon">Signal</span>
-                                <input type="text" class="form-control" ng-model="selectedAP['signal']" disabled>
-                            </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width-2 input-group-addon">Quality</span>
-                                <input type="text" class="form-control" ng-model="selectedAP['quality']" disabled>
+                    <div class="panel-body">
+                        <div class="col-md-12">
+                            <div class="row">
+                                <div class="input-group">
+                                    <span class="input-group-addon">AP Channel</span>
+                                    <select class="form-control" ng-model="apConfig['selectedChannel']">
+                                        <option ng-repeat="channel in apConfig['availableChannels']">{{ channel }}</option>
+                                    </select>
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <span class="fixed-addon-width-3 input-group-addon">Management SSID</span>
+                                    <input type="text" class="form-control" ng-model="apConfig['managementSSID']">
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <span class="fixed-addon-width-3 input-group-addon">Management PSK</span>
+                                    <input type="password" class="form-control" ng-model="apConfig['managementKey']">
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <div class="col-md-5">
+                                       <div class="checkbox">
+                                            <label for="hideManagementAP"><input id="hideManagementAP" type="checkbox" value="" ng-model="apConfig['hideManagementAP']">Hide Management AP</label>
+                                        </div>
+                                    </div>
+                                    <div class="col-md-5">
+                                        <div class="checkbox">
+                                            <label for="disableManagementAP"><input id="disableManagementAP" type="checkbox" value="" ng-model="apConfig['disableManagementAP']">Disable Management AP</label>
+                                        </div>
+                                    </div>
+                                </div>
+                                <br/>
                             </div>
-                            <br/>
-                            <div class="input-group">
-                                <span class="fixed-addon-width-2 input-group-addon">Security</span>
-                                <input type="text" class="form-control" ng-model="selectedAP['security']" disabled>
+                            <div class="row">
+                                <div class="input-group">
+                                    <span class="input-group-addon">Open SSID</span>
+                                    <input type="text" class="form-control" ng-model="apConfig['openSSID']">
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <span class="input-group-addon">Maximum Clients</span>
+                                    <input type="text" class="form-control" ng-model="apConfig['maxClients']">
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <div class="col-md-12">
+                                       <div class="checkbox">
+                                            <label for="hideOpenAP"><input id="hideOpenAP" type="checkbox" value="" ng-model="apConfig['hideOpenAP']">Hide Open SSID</label>
+                                        </div>
+                                    </div>
+                                </div>
+                                <br/>
+                                <div class="input-group">
+                                    <button class="btn btn-default" ng-click="saveAPConfiguration()">Update Access Points</button>
+                                </div>
+                                <br/>
+                                <div class="alert well-sm alert-success" ng-show="apConfigurationSaved">The Wireless AP Configuration has been saved. The radios will now restart. If connected over Wireless, you may need to reconnect.</div>
+                                <div class="alert well-sm alert-danger" ng-show="apConfigurationError">{{ apConfigurationError }}</div>
                             </div>
-                            <br/>
-                        </div>
-                        <div class="row">
-                            <button class="btn btn-default" ng-click="connectToAP()" ng-show="accessPoints.length" ng-disabled="connecting">
-                                <span ng-hide="connecting">Connect</span>
-                                <img class="image-small-18" src="img/throbber.gif" ng-show="connecting">
-                            </button>
                         </div>
                     </div>
                 </div>
             </div>
-        </div>
-    </div>
-</div>
 
-<div class="row">
-    <div class="col-md-6">
-        <div class="panel panel-default" ng-controller="NetworkingMACAddressesController">
-            <div class="panel-heading">
-                <h3 class="panel-title">MAC Addresses
-                    <span class="dropdown">
-                        <button ng-disabled="modifyingMAC" class="btn btn-xs btn-default dropdown-toggle" type="button" id="NetworkingAccessPointsDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <span class="caret"></span>
-                        </button>
-                        <ul class="dropdown-menu" aria-labelledby="NetworkingAccessPointsDropdown">
-                            <li ng-disabled="waiting" ng-click="resetMac()"><a>Restore Default MAC Address</a></li>
-                        </ul>
-                    </span>
-                </h3>
-            </div>
-            <div class="panel-body">
-                <div class="col-md-12">
-                    <div class="row">
-                        <div class="input-group">
-                            <span class="input-group-addon">Interface</span>
-                            <select ng-disabled="modifyingMAC" class="form-control" ng-model="selectedInterface">
-                                <option ng-repeat="(interface, mac) in interfaces">{{ interface }}</option>
-                            </select>
-                        </div>
-                        <br/>
-                        <div class="input-group">
-                            <span class="fixed-addon-width-3 input-group-addon">Current MAC</span>
-                            <input type="text" class="form-control autoselect" ng-model="interfaces[selectedInterface]" disabled>
-                        </div>
-                        <br/>
-                        <div class="input-group">
-                            <span class="fixed-addon-width-3 input-group-addon">New MAC</span>
-                            <input ng-disabled="modifyingMAC" type="text" class="form-control" ng-model="newMac">
+            <div class="col-md-6">
+                <div class="panel panel-default controller" ng-controller="NetworkingAdvancedController">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">Advanced</h3>
+                    </div>
+                    <div class="panel-body">
+                        <div class="alert well-sm alert-success" ng-show="hostnameUpdated">The hostname has been updated successfully.</div>
+                        <div class="col-md-12">
+                            <div class="row">
+                                <div class="input-group">
+                                    <span class="input-group-addon">Hostname</span>
+                                    <input type="text" class="form-control" id="hostname" ng-model="data['hostname']">
+                                    <div class="input-group-btn">
+                                        <button class="btn btn-default" ng-click="setHostname()">Update</button>
+                                    </div>
+                                </div>
+                            </div>
                         </div>
-                        <br/><br/>
-                        <button ng-disabled="modifyingMAC" class="btn btn-default" ng-click="setMac()">Set New MAC</button>
-                        <button ng-disabled="modifyingMAC" class="btn btn-default" ng-click="setRandomMac()">Set Random MAC</button>
-                        <img ng-show="modifyingMAC" src="img/throbber.gif"/>
-                        <br/>
-                        <small class="text-muted">Note: Changing MAC addresses will restart the WiFi.</small>
                     </div>
                 </div>
-            </div>
-        </div>
-        <div class="panel panel-default" ng-controller="NetworkingAdvancedController">
-            <div class="panel-heading">
-                <h3 class="panel-title">
-                    Wireless config
-                    <span class="dropdown">
-                        <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="NetworkingAdvancedDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <small><span class="caret"></span></small>
-                        </button>
-                        <ul class="dropdown-menu" aria-labelledby="NetworkingAdvancedDropdown">
-                            <li ng-click="resetWirelessConfig()"><a>Reset WiFi Config to Defaults</a></li>
-                        </ul>
-                    </span>
-                </h3>
-            </div>
-            <div class="panel-body">
-                <div class="alert well-sm alert-success" ng-show="wirelessReset">
-                    The Wireless Configuration has been reset. The Network will now restart.
-                </div>
-                <p>
-                    <textarea class="form-control" rows="12" ng-model="data['wireless']"></textarea>
-                </p>
-                <p class="alert well-sm alert-success" ng-show="wirelessUpdated">Config saved successfully</p>
-                <button type="submit" class="btn btn-default" ng-click="saveWirelessConfig()">Save config</button>
-            </div>
-        </div>
-    </div>
 
-    <div class="col-md-6" ng-controller="NetworkingAdvancedController">
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h3 class="panel-title">Advanced</h3>
-            </div>
-            <div class="panel-body">
-                <div class="alert well-sm alert-success" ng-show="hostnameUpdated">The hostname has been updated successfully.</div>
-                <div class="col-md-12">
-                    <div class="row">
-                        <div class="input-group">
-                            <span class="input-group-addon">Hostname</span>
-                            <input type="text" class="form-control" id="hostname" ng-model="data['hostname']">
-                            <div class="input-group-btn">
-                                <button class="btn btn-default" ng-click="setHostname()">Update</button>
+                <div class="controller" ng-controller="NetworkingFirewallController" ng-show="device == 'tetra'">
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h3 class="panel-title">Firewall</h3>
+                        </div>
+                        <div class="panel-body">
+                            <div class="row">
+                                <div class="col-md-12">
+                                    <div class="input-group">
+                                        <div class="checkbox">
+                                            <label><input type="checkbox" ng-model="WANSSHAccess">Allow SSH Access via WAN</label><br/>
+                                            <label><input type="checkbox" ng-model="WANUIAccess">Allow Web UI Access via WAN</label>
+                                        </div>
+                                        <br/>
+                                        <button class="btn btn-default" ng-click="setFirewallConfig();">Save</button>
+                                    </div>
+                                    <br/>
+                                    <div class="alert well-sm alert-success" ng-show="firewallUpdated">The Firewall Configuration has been updated.</div>
+                                </div>
                             </div>
                         </div>
                     </div>
-                    <br/>
-                    <div class="row">
-                        <pre class="scrollable-pre">{{ data['ifconfig'] }}</pre>
-                    </div>
                 </div>
+
+                <div class="panel panel-default controller" ng-controller="NetworkingAdvancedController">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            Wireless config
+                            <span class="dropdown">
+                                <button class="btn btn-xs btn-default dropdown-toggle" type="button" id="NetworkingAdvancedDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                    <small><span class="caret"></span></small>
+                                </button>
+                                <ul class="dropdown-menu" aria-labelledby="NetworkingAdvancedDropdown">
+                                    <li ng-click="resetWirelessConfig()"><a>Reset WiFi Config to Defaults</a></li>
+                                </ul>
+                            </span>
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <div class="alert well-sm alert-success" ng-show="wirelessReset">
+                            The Wireless Configuration has been reset. The Network will now restart.
+                        </div>
+                        <p>
+                            <textarea class="form-control" rows="12" ng-model="data['wireless']"></textarea>
+                        </p>
+                        <p class="alert well-sm alert-success" ng-show="wirelessUpdated">Config saved successfully</p>
+                        <button type="submit" class="btn btn-default" ng-click="saveWirelessConfig()">Save config</button>
+                    </div>
+                </div>    
             </div>
         </div>
     </div>
 </div>
+
+<script type="text/javascript">
+    function loadActiveTabData() {
+        $('.tab-pane.active .controller').each(function(index) {
+            var controller = angular.element( $(this)[0] ).scope();
+            if (typeof controller.reloadData === "function") { 
+                controller.reloadData();
+            }
+        });
+    }
+
+    $(function() {
+        $('.tabs-block a').click(function(event) {
+            event.preventDefault();
+            $(this).tab('show');
+        });
+
+        $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+            loadActiveTabData();
+        });
+    });
+</script>

+ 1 - 1
src/pineapple/modules/PineAP/module.html

@@ -19,7 +19,7 @@
                     </div>
                     <div class="row">
                         <div class="input-group margin-bottom">
-                            <span class="input-group-addon">PineAP Daemon: {{ settings.pineAPDaemon ? "Enabled" : "Disabled" }}</span>
+                            <span class="input-group-addon">PineAP Daemon: {{ settings.pineAPDaemon ? "Start" : "Stop" }}</span>
                             <span class="input-group-btn">
                                 <button ng-disabled="pineAPenabling" class="btn btn-default" type="button"
                                         ng-click="togglePineAP()">