Update Evil Portal to 3.1 (#32)
parent
ac9df1c5bb
commit
af3ed0d98c
|
@ -43,6 +43,10 @@ class EvilPortal extends Module
|
|||
$this->response = $this->getFileOrDirectoryContents($this->request->filePath);
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
$this->response = $this->download($this->request->filePath);
|
||||
break;
|
||||
|
||||
case 'listAvailablePortals':
|
||||
$this->response = $this->getListOfPortals();
|
||||
break;
|
||||
|
@ -138,7 +142,7 @@ class EvilPortal extends Module
|
|||
$contents = array(
|
||||
"name" => basename($file),
|
||||
"path" => $file,
|
||||
"size" => filesize($file),
|
||||
"size" => $this->readableFileSize($file),
|
||||
"fileContent" => file_get_contents($file)
|
||||
);
|
||||
$success = true;
|
||||
|
@ -153,7 +157,7 @@ class EvilPortal extends Module
|
|||
$obj = array("name" => $object, "directory" => is_dir("{$file}/{$object}"),
|
||||
"path" => realpath("{$file}/{$object}"),
|
||||
"permissions" => substr(sprintf('%o', fileperms("{$file}/{$object}")), -4),
|
||||
"size" => filesize("{$file}/{$object}"),
|
||||
"size" => $this->readableFileSize("{$file}/{$object}"),
|
||||
"deletable" => $this->isFileDeletable($object));
|
||||
array_push($contents, $obj);
|
||||
}
|
||||
|
@ -202,6 +206,20 @@ class EvilPortal extends Module
|
|||
return array("success" => $success, "message" => $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file
|
||||
* @param: The path to the file to download
|
||||
* @return array : array
|
||||
*/
|
||||
private function download($filePath)
|
||||
{
|
||||
if (file_exists($filePath)) {
|
||||
return array("success" => true, "message" => null, "download" => $this->downloadFile($filePath));
|
||||
} else {
|
||||
return array("success" => false, "message" => "File does not exist", "download" => null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of portals found on internal and sd storage.
|
||||
*/
|
||||
|
@ -226,6 +244,7 @@ class EvilPortal extends Module
|
|||
$portal = array(
|
||||
"title" => $object,
|
||||
"portalType" => $this->getValueFromJSONFile(array("type"), "{$storageLocation}{$object}/{$object}.ep")["type"],
|
||||
"size" => $this->readableFileSize("{$storageLocation}{$object}"),
|
||||
"location" => "{$storageLocation}{$object}",
|
||||
"storage" => $medium,
|
||||
"active" => (file_exists("/www/{$object}.ep"))
|
||||
|
@ -547,7 +566,7 @@ class EvilPortal extends Module
|
|||
$allowedClients = file_get_contents($this->ALLOWED_FILE);
|
||||
file_put_contents($this->CLIENTS_FILE, $allowedClients);
|
||||
|
||||
// // Configure other rules
|
||||
// Configure other rules
|
||||
exec("iptables -A INPUT -s 172.16.42.0/24 -j DROP");
|
||||
exec("iptables -A OUTPUT -s 172.16.42.0/24 -j DROP");
|
||||
exec("iptables -A INPUT -s 172.16.42.0/24 -p udp --dport 53 -j ACCEPT");
|
||||
|
@ -719,4 +738,25 @@ class EvilPortal extends Module
|
|||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a file and add a unit to the end of it.
|
||||
* @param $file: The file to get size of
|
||||
* @return string: File size plus unit. Exp: 3.14M
|
||||
*/
|
||||
private function readableFileSize($file) {
|
||||
$size = filesize($file);
|
||||
|
||||
if ($size == null)
|
||||
return "0 Bytes";
|
||||
|
||||
if ($size < 1024) {
|
||||
return "{$size} Bytes";
|
||||
} else if ($size >= 1024 && $size < 1024*1024) {
|
||||
return round($size / 1024, 2) . "K";
|
||||
} else if ($size >= 1024*1024) {
|
||||
return round($size / (1024*1024), 2) . "M";
|
||||
}
|
||||
return "{$size} Bytes";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,11 +29,37 @@ abstract class Portal
|
|||
* Run a command in the background and don't wait for it to finish.
|
||||
* @param $command: The command to run
|
||||
*/
|
||||
protected function execBackground($command)
|
||||
protected final function execBackground($command)
|
||||
{
|
||||
exec("echo \"{$command}\" | at now");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notifications to the web UI.
|
||||
* @param $message: The notification message
|
||||
*/
|
||||
protected final function notify($message)
|
||||
{
|
||||
$this->execBackground("notify {$message}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a log to the portals log file.
|
||||
* These logs can be retrieved from the web UI for .logs in the portals directory.
|
||||
* The log file is automatically appended to so there is no reason to add new line characters to your message.
|
||||
* @param $message: The message to write to the log file.
|
||||
*/
|
||||
protected final function writeLog($message)
|
||||
{
|
||||
try {
|
||||
$reflector = new \ReflectionClass(get_class($this));
|
||||
$logPath = dirname($reflector->getFileName());
|
||||
file_put_contents("{$logPath}/.logs", "{$message}\n", FILE_APPEND);
|
||||
} catch (\ReflectionException $e) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an iptables rule allowing the client to access the internet and writes them to the authorized clients.
|
||||
* Override this method to add other authorization steps validation.
|
||||
|
@ -44,7 +70,7 @@ abstract class Portal
|
|||
{
|
||||
if (!$this->isClientAuthorized($clientIP)) {
|
||||
exec("iptables -t nat -I PREROUTING -s {$clientIP} -j ACCEPT");
|
||||
// exec("{$this->BASE_EP_COMMAND} add {$clientIP}");
|
||||
// exec("{$this->BASE_EP_COMMAND} add {$clientIP}");
|
||||
file_put_contents($this->AUTHORIZED_CLIENTS_FILE, "{$clientIP}\n", FILE_APPEND);
|
||||
}
|
||||
return true;
|
||||
|
@ -82,7 +108,7 @@ abstract class Portal
|
|||
*/
|
||||
protected function onSuccess()
|
||||
{
|
||||
$this->execBackground("notify New client authorized through EvilPortal!");
|
||||
$this->notify("New client authorized through EvilPortal!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,6 @@ class MyPortal extends Portal
|
|||
|
||||
// Call parent to handle basic authorization first
|
||||
parent::handleAuthorization();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -33,10 +33,14 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
$scope.portalDeleteValidation = null;
|
||||
|
||||
// the portal workshop
|
||||
$scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true},
|
||||
"onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null
|
||||
$scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null,
|
||||
"editFile": {"path": null, "isNewFile": true}, "onEnable": null, "onDisable": null,
|
||||
"concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null
|
||||
};
|
||||
|
||||
// active log file
|
||||
$scope.activeLog = {"title": null, "path": null, "contents": null};
|
||||
|
||||
/**
|
||||
* Reset the workshop object to a blank slate with initial values.
|
||||
*/
|
||||
|
@ -396,6 +400,16 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load logs for a given portal
|
||||
* @param portal: The portal to load logs for
|
||||
*/
|
||||
$scope.loadPortalLog = function(portal) {
|
||||
var basePath = (portal.storage === "sd") ? "/sd/portals/" : "/root/portals/";
|
||||
var logPath = basePath + portal.title + "/.logs";
|
||||
$scope.loadLog(logPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a given object is empty.
|
||||
* @param obj: The object to check
|
||||
|
@ -405,6 +419,51 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
return (Object.keys(obj).length === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads a file from filePath and puts the data into the activeLog object.
|
||||
*/
|
||||
$scope.loadLog = function(filePath) {
|
||||
getFileOrDirectoryContent(filePath, function(response) {
|
||||
console.log(response);
|
||||
if (!response.success) {
|
||||
$scope.activeLog = {
|
||||
"title": "Unknown",
|
||||
"path": null,
|
||||
"contents": null,
|
||||
"size": "0 Bytes"
|
||||
};
|
||||
return;
|
||||
}
|
||||
$scope.activeLog = {
|
||||
"title": response.content.name,
|
||||
"path": response.content.path,
|
||||
"contents": response.content.fileContent,
|
||||
"size": response.content.size
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes an empty string to whatever file path is in $scope.activeLog.path
|
||||
* On successful write, $scope.activeLog.contents is set to null.
|
||||
*
|
||||
* If $scope.activeLog.path is null, the request if not made.
|
||||
*
|
||||
*/
|
||||
$scope.clearLog = function() {
|
||||
if ($scope.activeLog.path == null)
|
||||
return;
|
||||
|
||||
writeToFile($scope.activeLog.path, '', false, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error Clearing Log", response.message);
|
||||
return;
|
||||
}
|
||||
$scope.activeLog.contents = response.content;
|
||||
$scope.activeLog.size = "0 Bytes";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the contents of a given file.
|
||||
* @param filePath: The path to the file to load
|
||||
|
@ -431,6 +490,7 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
var basePath = ($scope.workshop.portal.storage === "sd") ? "/sd/portals/" : "/root/portals/";
|
||||
$scope.workshop.editFile.path = basePath + $scope.workshop.portal.title + "/";
|
||||
$scope.workshop.editFile.isNewFile = true;
|
||||
$scope.workshop.editFile.size = "0 Bytes";
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -442,7 +502,6 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
if (!editFile.path.includes(editFile.name))
|
||||
editFile.path = editFile.path + editFile.name;
|
||||
|
||||
console.log(editFile.path);
|
||||
writeToFile(editFile.path, editFile.content, false, function(response) {
|
||||
if (!response.success)
|
||||
$scope.sendMessage("Error write to file " + editFile.name, response.message);
|
||||
|
@ -463,6 +522,20 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
});
|
||||
};
|
||||
|
||||
$scope.download = function(filePath) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "download",
|
||||
filePath: filePath
|
||||
}, function (response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error", response.message);
|
||||
return;
|
||||
}
|
||||
window.location = "/api/?download=" + response.download;
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Load either the white list or the authorized clients (access) list
|
||||
* @param listName: The name of the list: whiteList or accessList (authorized clients)
|
||||
|
@ -654,6 +727,7 @@ registerController("EvilPortalController", ['$api', '$scope', function ($api, $s
|
|||
response.portals.forEach(function(item, index) {
|
||||
$scope.portals.unshift({
|
||||
title: item.title,
|
||||
size: item.size,
|
||||
storage: item.storage,
|
||||
active: item.active,
|
||||
type: item.portalType,
|
||||
|
|
|
@ -84,36 +84,40 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-striped" align="center">
|
||||
<thead>
|
||||
<th>Portal Name</th>
|
||||
<th>Portal Type</th>
|
||||
<th>Location</th>
|
||||
<th ng-show="evilPortal.sdAvailable">Move</th>
|
||||
<th>Activate</th>
|
||||
<th>Delete</th>
|
||||
<th>Portal Name</th>
|
||||
<th>Portal Type</th>
|
||||
<th>Location</th>
|
||||
<th ng-show="evilPortal.sdAvailable">Move To</th>
|
||||
<th>Logs</th>
|
||||
<th>Activate</th>
|
||||
<th>Delete</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="portal in portals">
|
||||
<!-- Open portal work shop -->
|
||||
<td><a href="javascript:;" ng-click="loadPortal(portal)" style="text-transform: capitalize"><b>{{ portal.title }}</b></a></td>
|
||||
<!-- Portal type -->
|
||||
<td class="text-muted" style="text-transform: capitalize"><i>{{ portal.type }}</i></td>
|
||||
<!-- What storage device the portal is on -->
|
||||
<td class="text-muted" style="text-transform: capitalize"><i>{{ portal.storage }}</i></td>
|
||||
<!-- change storage -->
|
||||
<td ng-show="evilPortal.sdAvailable">
|
||||
<a ng-show="(portal.storage == 'internal') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'sd');">Move to SD</a>
|
||||
<a ng-show="(portal.storage == 'sd') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'internal');">Move to Internal</a>
|
||||
</td>
|
||||
<!-- activate/deactivate -->
|
||||
<td>
|
||||
<a ng-hide="portal.active" href="javascript:;" ng-click="activatePortal(portal)">Activate</a>
|
||||
<a ng-show="portal.active" href="javascript:;" ng-click="deactivatePortal(portal)">Deactivate</a>
|
||||
</td>
|
||||
<!-- delete portal button -->
|
||||
<td>
|
||||
<a ng-hide="portal.active" data-toggle="modal" data-target="#deleteModal" ng-click="deletePortal(false, portal)">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat="portal in portals">
|
||||
<!-- Open portal work shop -->
|
||||
<td><a href="javascript:;" ng-click="loadPortal(portal)" style="text-transform: capitalize"><b>{{ portal.title }}</b></a></td>
|
||||
<!-- Portal type -->
|
||||
<td class="text-muted" style="text-transform: capitalize">{{ portal.type }}</td>
|
||||
<!-- What storage device the portal is on -->
|
||||
<td class="text-muted" style="text-transform: capitalize">{{ portal.storage }}</td>
|
||||
<!-- change storage -->
|
||||
<td ng-show="evilPortal.sdAvailable">
|
||||
<a ng-show="(portal.storage == 'internal') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'sd');">SD</a>
|
||||
<a ng-show="(portal.storage == 'sd') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'internal');">Internal</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="javascript:;" data-toggle="modal" data-target="#readLogsModal" ng-click="loadPortalLog(portal)">View</a>
|
||||
</td>
|
||||
<!-- activate/deactivate -->
|
||||
<td>
|
||||
<a ng-hide="portal.active" href="javascript:;" ng-click="activatePortal(portal)">Activate</a>
|
||||
<a ng-show="portal.active" href="javascript:;" ng-click="deactivatePortal(portal)">Deactivate</a>
|
||||
</td>
|
||||
<!-- delete portal button -->
|
||||
<td>
|
||||
<a ng-hide="portal.active" data-toggle="modal" data-target="#deleteModal" ng-click="deletePortal(false, portal)">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -129,9 +133,10 @@
|
|||
<!-- Editor Buttons -->
|
||||
<div class="row-fluid">
|
||||
<span class="pull-left">
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="evilPortal.library = true; resetWorkshop()">Back To Library</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="evilPortal.library = true; resetWorkshop()">Back</button>
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#readLogsModal" ng-click="loadPortalLog(workshop.portal)">View Log</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#fileModal" ng-click="setupNewFile()">New File</button>
|
||||
<!-- Holding off on toggle commands until a future release. Still need to think through how they should work. -->
|
||||
<!--<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#commandEditorModal" ng-click="loadToggleCommands()">Toggle Commands</button>-->
|
||||
|
@ -148,12 +153,14 @@
|
|||
<table class="table table-striped" align="center">
|
||||
<thead>
|
||||
<th>File Name</th>
|
||||
<th>Size</th>
|
||||
<th>Edit</th>
|
||||
<th>Delete</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="file in workshop.dirContents">
|
||||
<td>{{ file.name }}</td>
|
||||
<td>{{ file.size }}</td>
|
||||
<td><a href="javascript:;" ng-click="loadFileContent(file.path)" data-toggle="modal" data-target="#fileModal">Edit</a></td>
|
||||
<td><a href="javascript:;" ng-click="workshop.deleteFile = file" data-toggle="modal" data-target="#deleteFileModal" ng-show="file.deletable">Delete</a></td>
|
||||
</tr>
|
||||
|
@ -281,6 +288,17 @@
|
|||
<fieldset>
|
||||
<legend>Change Log</legend>
|
||||
<ul>
|
||||
<li><b>3.1</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Added ability to write and view logs on a per-portal basis</li>
|
||||
<li class="text-muted">Created method <i>writeLog($message)</i> that writes to the portal log file</li>
|
||||
<li class="text-muted">Created method <i>notify($message)</i> that sends a notification to the web ui</li>
|
||||
<li class="text-muted">Added ability to download files</li>
|
||||
<li class="text-muted">Tab button in file editor will now insert four spaces</li>
|
||||
<li class="text-muted">Revamped the file editor modal</li>
|
||||
<li class="text-muted">Showing file sizes in the portal work bench</li>
|
||||
<li class="text-muted">Various quality of life improvements</li>
|
||||
</ul>
|
||||
<li><b>3.0</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Added ability to route clients to different portals based upon some identifier [ssid, mac vendor, ip, etc...]</li>
|
||||
|
@ -333,58 +351,84 @@
|
|||
// Thanks to sud0nick on forums.hak5.org for this snippet
|
||||
$(document).delegate('#evilportalEditor', 'keydown', function(e) {
|
||||
var keyCode = e.keyCode || e.which;
|
||||
|
||||
if (keyCode == 9) {
|
||||
e.preventDefault();
|
||||
var start = $(this).get(0).selection;
|
||||
var end = $(this).get(0).selectionEnd;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
// get caret position/selection
|
||||
var start = this.selectionStart;
|
||||
var end = this.selectionEnd;
|
||||
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
$(this).val($(this).val().substring(0, start)
|
||||
+ "\t"
|
||||
+ $(this).val().substring(end));
|
||||
var $this = $(this);
|
||||
var value = $this.val();
|
||||
|
||||
// put caret at right position again
|
||||
$(this).get(0).selectionStart =
|
||||
$(this).get(0).selectionEnd = start + 1;
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
$this.val(value.substring(0, start)
|
||||
+ " "
|
||||
+ value.substring(end));
|
||||
|
||||
// put caret at right position again (add one for the tab)
|
||||
this.selectionStart = this.selectionEnd = start + 1;
|
||||
|
||||
// prevent the focus lose
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
ng-click="workshop.editFile = {}">×</button>
|
||||
<h4 class="modal-title" ng-show="!workshop.editFile.isNewFile">Editing File {{ workshop.editFile.name }}</h4>
|
||||
<h4 class="modal-title" ng-show="!workshop.editFile.isNewFile">Editing File</h4>
|
||||
<h4 class="modal-title" ng-show="workshop.editFile.isNewFile">Creating New File</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label">File Name</label>
|
||||
<input type="text" class="form-control" ng-model="workshop.editFile.name" placeholder="Notes.txt" ng-disabled="!workshop.editFile.isNewFile">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">File Contents</label> <a href="javascript:;" ng-click="workshop.editFile.content = null">Clear</a>
|
||||
<textarea class="form-control" rows="10" ng-model="workshop.editFile.content"
|
||||
placeholder="Write some text here" id="evilportalEditor"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
<label class="control-label">File Name</label>
|
||||
<input type="text" class="form-control" ng-model="workshop.editFile.name" placeholder="Notes.txt" ng-disabled="!workshop.editFile.isNewFile">
|
||||
<br />
|
||||
<label class="control-label pull-left">File Contents</label>
|
||||
<a href="javascript:;" ng-click="workshop.editFile.content = null" class="pull-right">Clear</a>
|
||||
<textarea class="form-control" rows="20" ng-model="workshop.editFile.content" placeholder="Write some text here" id="evilportalEditor"/>
|
||||
<label class="control-label pull-right">File Size: {{ workshop.editFile.size }}</label>
|
||||
<a href="javascript:;" class="pull-left" ng-click="download(workshop.editFile.path)">Download</a>
|
||||
<br />
|
||||
<!-- <script src="modules/EvilPortal/includes/js/acejs/ace.js" type="text/javascript" charset="utf-8"></script> -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" ng-click="saveFileContent(workshop.editFile);" class="btn btn-success pull-right" data-dismiss="modal" ng-disabled="workshop.editFile.content == null">Save</button>
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal"
|
||||
ng-click="workshop.editFile = {}">Cancel
|
||||
</button>
|
||||
<button type="button" ng-click="saveFileContent(workshop.editFile);" class="btn btn-success pull-right" data-dismiss="modal"
|
||||
ng-disabled="workshop.editFile.content == null">Save</button>
|
||||
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="workshop.editFile = {}">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Read Logs Modal -->
|
||||
<div id="readLogsModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content -->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="activeLog = null;">×</button>
|
||||
<h4 class="modal-title">Viewing <i><b>{{ activeLog.path }}</b></i></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<a href="javascript:;" class="pull-left" ng-click="loadLog(activeLog.path)">Refresh</a>
|
||||
<a href="javascript:;" class="pull-right" ng-click="clearLog()">Clear</a>
|
||||
<textarea class="form-control" rows="20" ng-model="activeLog.contents" placeholder="Nothing to show." readonly/>
|
||||
<label class="control-label pull-right">Log Size: {{ activeLog.size }}</label>
|
||||
<a href="javascript:;" class="pull-left" ng-click="download(activeLog.path)">Download</a>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Portal Modal -->
|
||||
<div id="deleteModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"author": "newbi3",
|
||||
"description": "An Evil Captive Portal.",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "Evil Portal",
|
||||
"version": "3.0"
|
||||
"author": "newbi3",
|
||||
"description": "An Evil Captive Portal.",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "Evil Portal",
|
||||
"version": "3.1"
|
||||
}
|
Loading…
Reference in New Issue