Made diskspace running low a bit more obvious

* Added "total" space to "free" in file list
  * Added configurable space thresholds. If free space is below "warning"
    threshold, exclamation sign will be added to report. If free space is below
    "critical" threshold, report will be styled bold and red.
This commit is contained in:
Gina Häußge 2015-08-27 10:29:11 +02:00
parent 8666a28f64
commit 097800ae9c
11 changed files with 130 additions and 35 deletions

View file

@ -31,7 +31,8 @@ INSTALL_REQUIRES = [
"rsa",
"pkginfo",
"requests",
"semantic_version"
"semantic_version",
"psutil"
]
# Additional requirements for optional install options

View file

@ -7,7 +7,6 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
from flask import request, jsonify, make_response, url_for
import octoprint.util as util
from octoprint.filemanager.destinations import FileDestinations
from octoprint.settings import settings, valid_boolean_trues
from octoprint.server import printer, fileManager, slicingManager, eventManager, NO_CONTENT
@ -18,6 +17,8 @@ import octoprint.filemanager
import octoprint.filemanager.util
import octoprint.slicing
import psutil
#~~ GCODE file handling
@ -29,7 +30,8 @@ def readGcodeFiles():
filter = request.values["filter"]
files = _getFileList(FileDestinations.LOCAL, filter=filter)
files.extend(_getFileList(FileDestinations.SDCARD))
return jsonify(files=files, free=util.get_free_bytes(settings().getBaseFolder("uploads")))
usage = psutil.disk_usage(settings().getBaseFolder("uploads"))
return jsonify(files=files, free=usage.free, total=usage.total)
@api.route("/files/<string:origin>", methods=["GET"])
@ -40,7 +42,8 @@ def readGcodeFilesForOrigin(origin):
files = _getFileList(origin)
if origin == FileDestinations.LOCAL:
return jsonify(files=files, free=util.get_free_bytes(settings().getBaseFolder("uploads")))
usage = psutil.disk_usage(settings().getBaseFolder("uploads"))
return jsonify(files=files, free=usage.free, total=usage.total)
else:
return jsonify(files=files)

View file

@ -15,15 +15,18 @@ from octoprint.settings import settings
from octoprint.server import NO_CONTENT, admin_permission
from octoprint.server.util.flask import redirect_to_tornado, restricted_access
from octoprint.server.api import api
from octoprint.util import get_free_bytes
@api.route("/logs", methods=["GET"])
@restricted_access
@admin_permission.require(403)
def getLogFiles():
import psutil
usage = psutil.disk_usage(settings().getBaseFolder("logs"))
files = _getLogFiles()
return jsonify(files=files, free=get_free_bytes(settings().getBaseFolder("logs")))
return jsonify(files=files, free=usage.free, total=usage.total)
@api.route("/logs/<path:filename>", methods=["GET"])

View file

@ -123,6 +123,10 @@ def getSettings():
"systemShutdownCommand": s.get(["server", "commands", "systemShutdownCommand"]),
"systemRestartCommand": s.get(["server", "commands", "systemRestartCommand"]),
"serverRestartCommand": s.get(["server", "commands", "serverRestartCommand"])
},
"diskspace": {
"warning": s.getInt(["server", "diskspace", "warning"]),
"critical": s.getInt(["server", "diskspace", "critical"])
}
}
}
@ -276,6 +280,9 @@ def _saveSettings(data):
if "systemShutdownCommand" in data["server"]["commands"].keys(): s.set(["server", "commands", "systemShutdownCommand"], data["server"]["commands"]["systemShutdownCommand"])
if "systemRestartCommand" in data["server"]["commands"].keys(): s.set(["server", "commands", "systemRestartCommand"], data["server"]["commands"]["systemRestartCommand"])
if "serverRestartCommand" in data["server"]["commands"].keys(): s.set(["server", "commands", "serverRestartCommand"], data["server"]["commands"]["serverRestartCommand"])
if "diskspace" in data["server"]:
if "warning" in data["server"]["diskspace"]: s.setInt(["server", "diskspace", "warning"], data["server"]["diskspace"]["warning"])
if "critical" in data["server"]["diskspace"]: s.setInt(["server", "diskspace", "critical"], data["server"]["diskspace"]["critical"])
if "plugins" in data:
for plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin):

View file

@ -112,6 +112,10 @@ default_settings = {
"systemShutdownCommand": None,
"systemRestartCommand": None,
"serverRestartCommand": None
},
"diskspace": {
"warning": 500 * 1024 * 1024, # 500 MB
"critical": 200 * 1024 * 1024, # 200 MB
}
},
"webcam": {

View file

@ -333,6 +333,34 @@ function formatSize(bytes) {
return _.sprintf("%.1f%s", bytes, "TB");
}
function bytesFromSize(size) {
if (size == undefined || size.trim() == "") return undefined;
var parsed = size.match(/^([+]?[0-9]*\.?[0-9]+)(?:\s*)?(.*)$/);
var number = parsed[1];
var unit = parsed[2].trim();
if (unit == "") return parseFloat(number);
var units = {
b: 1,
byte: 1,
bytes: 1,
kb: 1024,
mb: Math.pow(1024, 2),
gb: Math.pow(1024, 3),
tb: Math.pow(1024, 4)
};
unit = unit.toLowerCase();
if (!units.hasOwnProperty(unit)) {
return undefined;
}
var factor = units[unit];
return number * factor;
}
function formatDuration(seconds) {
if (!seconds) return "-";
if (seconds < 0) return "00:00:00";
@ -698,3 +726,17 @@ function callViewModelsIf(allViewModels, method, condition, callback) {
}
});
}
var sizeObservable = function(observable) {
return ko.computed({
read: function() {
return formatSize(observable());
},
write: function(value) {
var result = bytesFromSize(value);
if (result != undefined) {
observable(result);
}
}
})
};

View file

@ -2,9 +2,10 @@ $(function() {
function GcodeFilesViewModel(parameters) {
var self = this;
self.printerState = parameters[0];
self.settingsViewModel = parameters[0];
self.loginState = parameters[1];
self.slicing = parameters[2];
self.printerState = parameters[2];
self.slicing = parameters[3];
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
@ -21,11 +22,35 @@ $(function() {
});
self.freeSpace = ko.observable(undefined);
self.totalSpace = ko.observable(undefined);
self.freeSpaceString = ko.computed(function() {
if (!self.freeSpace())
return "-";
return formatSize(self.freeSpace());
});
self.totalSpaceString = ko.computed(function() {
if (!self.totalSpace())
return "-";
return formatSize(self.totalSpace());
});
self.diskusageWarning = ko.computed(function() {
return self.freeSpace() != undefined
&& self.freeSpace() < self.settingsViewModel.server_diskspace_warning();
});
self.diskusageCritical = ko.computed(function() {
return self.freeSpace() != undefined
&& self.freeSpace() < self.settingsViewModel.server_diskspace_critical();
});
self.diskusageString = ko.computed(function() {
if (self.diskusageCritical()) {
return gettext("Your available free disk space is critically low.");
} else if (self.diskusageWarning()) {
return gettext("Your available free disk space is starting to run low.");
} else {
return gettext("Your current disk usage.");
}
});
self.uploadButton = undefined;
@ -155,10 +180,14 @@ $(function() {
}
}
if (response.free) {
if (response.free != undefined) {
self.freeSpace(response.free);
}
if (response.total != undefined) {
self.totalSpace(response.total);
}
self.highlightFilename(self.printerState.filename());
};
@ -575,7 +604,7 @@ $(function() {
OCTOPRINT_VIEWMODELS.push([
GcodeFilesViewModel,
["printerStateViewModel", "loginStateViewModel", "slicingViewModel"],
["settingsViewModel", "loginStateViewModel", "printerStateViewModel", "slicingViewModel"],
"#files_wrapper"
]);
});

View file

@ -165,6 +165,11 @@ $(function() {
self.server_commands_systemRestartCommand = ko.observable(undefined);
self.server_commands_serverRestartCommand = ko.observable(undefined);
self.server_diskspace_warning = ko.observable();
self.server_diskspace_critical = ko.observable();
self.server_diskspace_warning_str = sizeObservable(self.server_diskspace_warning);
self.server_diskspace_critical_str = sizeObservable(self.server_diskspace_critical);
self.settings = undefined;
self.lastReceivedSettings = undefined;

View file

@ -1,4 +1,6 @@
<form class="form-horizontal">
<h3>{{ _('Folders') }}</h3>
<div class="control-group">
<label class="control-label" for="settings-folderUploads">{{ _('Upload Folder') }}</label>
<div class="controls">
@ -36,4 +38,21 @@
</label>
</div>
</div>
<h3>{{ _('Disk space thresholds') }}</h3>
<p>{{ _('If the free disk space falls below these thresholds, OctoPrint will warn the user.') }}</p>
<div class="control-group" title="{{ ('Threshold after which to warn about the remaining free space on disk.') }}">
<label class="control-label" for="settings.serverDiskusageWarning">{{ _('Warning') }}</label>
<div class="controls">
<input type="text" class="input-mini text-right" data-bind="value: server_diskspace_warning_str">
</div>
</div>
<div class="control-group" title="{{ ('Threshold after which to consider remaining free space on disk as critical.') }}">
<label class="control-label" for="settings.serverDiskusageCritical">{{ _('Critical') }}</label>
<div class="controls">
<input type="text" class="input-mini text-right" data-bind="value: server_diskspace_critical_str">
</div>
</div>
</form>

View file

@ -33,8 +33,8 @@
<div class="title" data-bind="text: name"></div>
</script>
</div>
<div class="muted text-right">
<small>{{ _('Free') }}: <span data-bind="text: freeSpaceString"></span></small>
<div class="text-right muted" data-bind="attr: {title: diskusageString}, css: {'text-error': diskusageCritical}, style: {'font-weight': diskusageCritical() || diskusageWarning() ? 'bold' : 'normal'}">
<small>{{ _('Free') }}: <span data-bind="text: freeSpaceString"></span> / {{ _('Total') }}: <span data-bind="text: totalSpaceString"></span> <i class="icon-exclamation-sign" data-bind="visible: diskusageWarning" style="display: none"></i></small>
</div>
<div style="display: none;" data-bind="visible: loginState.isUser">
<div class="row-fluid upload-buttons">

View file

@ -19,7 +19,6 @@ from functools import wraps
import warnings
import contextlib
logger = logging.getLogger(__name__)
def warning_decorator_factory(warning_type):
@ -198,29 +197,12 @@ def get_exception_string():
return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])
@deprecated("get_free_bytes has been deprecated and will be removed in the future",
includedoc="Replaced by `psutil.disk_usage <http://pythonhosted.org/psutil/#psutil.disk_usage>`_.",
since="1.2.5")
def get_free_bytes(path):
"""
Retrieves the number of free bytes on the partition ``path`` is located at and returns it. Works on both Windows and
Unix/Linux.
Taken from http://stackoverflow.com/a/2372171/2028598
Arguments:
path (string): The path for which to check the remaining partition space.
Returns:
int: The amount of bytes still left on the partition.
"""
path = os.path.abspath(path)
if sys.platform == "win32":
import ctypes
freeBytes = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(path), None, None, ctypes.pointer(freeBytes))
return freeBytes.value
else:
st = os.statvfs(path)
return st.f_bavail * st.f_frsize
import psutil
return psutil.disk_usage(path).free
def get_dos_filename(origin, existing_filenames=None, extension=None, **kwargs):