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:
parent
8666a28f64
commit
097800ae9c
11 changed files with 130 additions and 35 deletions
3
setup.py
3
setup.py
|
|
@ -31,7 +31,8 @@ INSTALL_REQUIRES = [
|
|||
"rsa",
|
||||
"pkginfo",
|
||||
"requests",
|
||||
"semantic_version"
|
||||
"semantic_version",
|
||||
"psutil"
|
||||
]
|
||||
|
||||
# Additional requirements for optional install options
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in a new issue