Log tab added to download and remove logs

This commit is contained in:
Salandora 2014-01-27 09:35:51 +01:00
parent 7231acc236
commit c5c382ade6
7 changed files with 172 additions and 3 deletions

View file

@ -177,6 +177,7 @@ class Server():
self._tornado_app = Application(self._router.urls + [
(r"/downloads/timelapse/([^/]*\.mpg)", LargeResponseHandler, {"path": settings().getBaseFolder("timelapse"), "as_attachment": True}),
(r"/downloads/files/local/([^/]*\.(gco|gcode))", LargeResponseHandler, {"path": settings().getBaseFolder("uploads"), "as_attachment": True}),
(r"/downloads/logs/([^/]*)", LargeResponseHandler, {"path": settings().getBaseFolder("logs"), "as_attachment": True}),
(r".*", FallbackHandler, {"fallback": WSGIContainer(app.wsgi_app)})
])
self._server = HTTPServer(self._tornado_app)

View file

@ -27,6 +27,7 @@ from . import files as api_files
from . import settings as api_settings
from . import timelapse as api_timelapse
from . import users as api_users
from . import log as api_logs
#~~ first run setup

View file

@ -0,0 +1,62 @@
# coding=utf-8
__author__ = "Marc Hannappel Salandora"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
import os
import datetime
from flask import request, jsonify, make_response, url_for
from werkzeug.utils import secure_filename
import octoprint.util as util
from octoprint.settings import settings, valid_boolean_trues
from octoprint.server import restricted_access, admin_permission
from octoprint.server.util import redirectToTornado
from octoprint.server.api import api
@api.route("/logs", methods=["GET"])
def getLogData():
files = _getLogFiles()
return jsonify(files=files)
@api.route("/logs/online/<filename>", methods=["GET"])
def onlineLog(filename):
secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename))
if not os.path.exists(secure):
return make_response("Unknown filename: %s" % filename, 404)
file = open(secure, 'r')
return jsonify(data=file.read())
@api.route("/logs/<filename>", methods=["GET"])
def downloadLog(filename):
return redirectToTornado(request, url_for("index") + "downloads/logs/" + filename)
@api.route("/logs/<filename>", methods=["DELETE"])
@restricted_access
def deleteLog(filename):
secure = os.path.join(settings().getBaseFolder("logs"), secure_filename(filename))
if os.path.exists(secure):
os.remove(secure)
return getLogData()
def _getLogFiles():
files = []
basedir = settings().getBaseFolder("logs")
for osFile in os.listdir(basedir):
statResult = os.stat(os.path.join(basedir, osFile))
files.append({
"name": osFile,
"size": util.getFormattedSize(statResult.st_size),
"bytes": statResult.st_size,
"date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime))
})
for file in files:
file["url"] = url_for("index") + "downloads/logs/" + file["name"]
return files

View file

@ -1,4 +1,4 @@
function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel) {
function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel, logViewModel) {
var self = this;
self.loginStateViewModel = loginStateViewModel;
@ -10,6 +10,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self.gcodeFilesViewModel = gcodeFilesViewModel;
self.timelapseViewModel = timelapseViewModel;
self.gcodeViewModel = gcodeViewModel;
self.logViewModel = logViewModel;
self._socket = undefined;
self._autoReconnecting = false;
@ -38,7 +39,8 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
self._autoReconnectTrial = 0;
if ($("#offline_overlay").is(":visible")) {
$("#offline_overlay").hide();
$("#offline_overlay").hide();
self.logViewModel.requestData();
self.timelapseViewModel.requestData();
self.loginStateViewModel.requestData();
self.gcodeFilesViewModel.requestData();

View file

@ -14,6 +14,7 @@ $(function() {
var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel);
var gcodeViewModel = new GcodeViewModel(loginStateViewModel, settingsViewModel);
var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);
var logViewModel = new LogViewModel(loginStateViewModel);
var dataUpdater = new DataUpdater(
loginStateViewModel,
@ -24,7 +25,8 @@ $(function() {
terminalViewModel,
gcodeFilesViewModel,
timelapseViewModel,
gcodeViewModel
gcodeViewModel,
logViewModel
);
// work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
@ -302,6 +304,7 @@ $(function() {
ko.applyBindings(navigationViewModel, document.getElementById("navbar"));
ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]);
ko.applyBindings(printerStateViewModel, document.getElementById("drop_overlay"));
ko.applyBindings(logViewModel, document.getElementById("logs"));
var timelapseElement = document.getElementById("timelapse");
if (timelapseElement) {
@ -316,6 +319,7 @@ $(function() {
gcodeFilesViewModel.requestData();
timelapseViewModel.requestData();
settingsViewModel.requestData();
logViewModel.requestData();
loginStateViewModel.subscribe(function(change, data) {
if ("login" == change) {

View file

@ -0,0 +1,62 @@
function LogViewModel(loginStateViewModel) {
var self = this;
self.loginState = loginStateViewModel;
// initialize list helper
self.listHelper = new ItemListHelper(
"logFiles",
{
"name": function(a, b) {
// sorts ascending
if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
return 0;
},
"creation": function(a, b) {
// sorts descending
if (a["date"] > b["date"]) return -1;
if (a["date"] < b["date"]) return 1;
return 0;
},
"size": function(a, b) {
// sorts descending
if (a["bytes"] > b["bytes"]) return -1;
if (a["bytes"] < b["bytes"]) return 1;
return 0;
}
},
{
},
"name",
[],
[],
CONFIG_LOGFILESPERPAGE
);
self.requestData = function() {
$.ajax({
url: API_BASEURL + "logs",
type: "GET",
dataType: "json",
success: self.fromResponse
});
};
self.fromResponse = function(response) {
var files = response.files;
if (files === undefined)
return;
self.listHelper.updateItems(files);
}
self.removeFile = function(filename) {
$.ajax({
url: API_BASEURL + "logs/" + filename,
type: "DELETE",
dataType: "json",
success: self.requestData
});
}
}

View file

@ -27,6 +27,7 @@
var CONFIG_GCODEFILESPERPAGE = 5;
var CONFIG_TIMELAPSEFILESPERPAGE = 10;
var CONFIG_LOGFILESPERPAGE = 10;
var CONFIG_USERSPERPAGE = 10;
var CONFIG_WEBCAM_STREAM = "{{ webcamStream }}";
var CONFIG_ACCESS_CONTROL = {% if enableAccessControl -%} true; {% else %} false; {%- endif %}
@ -252,6 +253,7 @@
{% if enableGCodeVisualizer %}<li><a href="#gcode" data-toggle="tab">GCode Viewer</a></li>{% endif %}
<li><a href="#term" data-toggle="tab">Terminal</a></li>
{% if enableTimelapse %}<li><a href="#timelapse" data-toggle="tab">Timelapse</a></li>{% endif %}
<li><a href="#logs" data-toggle="tab">Logs</a></li>
</ul>
<div class="tab-content">
@ -585,6 +587,40 @@
</div>
</div>
{% endif %}
<div class="tab-pane" id="logs">
<h1>Logs</h1>
<div class="pull-right">
<small>Sort by: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">Name (ascending)</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('creation'); }">Creation date (descending)</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('size'); }">Size (descending)</a></small>
</div>
<table class="table table-striped table-hover table-condensed table-hover" id="log_files">
<thead>
<tr>
<th class="log_files_name">Name</th>
<th class="log_files_size">Size</th>
<th class="log_files_action">Action</th>
</tr>
</thead>
<tbody data-bind="foreach: listHelper.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="log_files_name" data-bind="text: name"></td>
<td class="log_files_size" data-bind="text: size"></td>
<td class="log_files_action"><a href="#" class="icon-trash" data-bind="click: function() { if ($root.loginState.isUser()) { $parent.removeFile($data.name); } else { return; } }, css: {disabled: !$root.loginState.isUser()}"></a>&nbsp;|&nbsp;<a href="#" class="icon-download" data-bind="attr: {href: url}"></a></td>
</tr>
</tbody>
</table>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: listHelper.currentPage() === 0}"><a href="#" data-bind="click: listHelper.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: listHelper.pages">
<li data-bind="css: { active: $data.number === $root.listHelper.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.listHelper.changePage($data.number); }"></a></li>
</ul>
<ul>
<li data-bind="css: {disabled: listHelper.currentPage() === listHelper.lastPage()}"><a href="#" data-bind="click: listHelper.nextPage">»</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
@ -644,6 +680,7 @@
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/terminal.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/timelapse.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/users.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/viewmodels/log.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/dataupdater.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/app/helpers.js') }}"></script>