zaxis setting, settings not a popup anymore
This commit is contained in:
parent
6f6c07c3dc
commit
de58b08e90
14 changed files with 580 additions and 50 deletions
|
|
@ -36,8 +36,9 @@ function WorkingAreaViewModel(loginStateViewModel, settingsViewModel, printerSta
|
|||
};
|
||||
|
||||
self._fromData = function(data) {
|
||||
workPosition = data.workPosition;
|
||||
self._processPos(workPosition);
|
||||
if(data.workPosition){
|
||||
self._processPos(data.workPosition);
|
||||
}
|
||||
};
|
||||
|
||||
self.fromCurrentData = function(data) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
<li class="active"><a href="#workingarea" data-toggle="tab">working area</a></li>
|
||||
<li><a href="#designlib" data-toggle="tab">design library</a></li>
|
||||
<li><a href="#focus" data-toggle="tab">focus</a></li>
|
||||
<li style="display: none;" data-bind="visible: loginState.isAdmin">
|
||||
<li xstyle="display: none;" xdata-bind="visible: loginState.isAdmin">
|
||||
<a id="navbar_show_settings" class="pull-right" href="#settings" data-toggle="tab">{{ _('settings') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -127,11 +127,19 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span4 accordion">
|
||||
<div class="accordion-group" data-bind="visible: loginState.isUser" id="connection_accordion">
|
||||
|
||||
<!-- <div class="accordion-group" data-bind="visible: loginState.isUser" id="connection_accordion">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#connection"><i class="icon-signal"></i> {{ _('Connection') }}</a>
|
||||
</div>
|
||||
<div class="accordion-body collapse in" id="connection">
|
||||
</div>-->
|
||||
|
||||
<div class="accordion-group" id="state_accordion">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> {{ _('State') }}</a>
|
||||
</div>
|
||||
|
||||
<div class="accordion-body collapse in" id="connection" data-bind="visible: isErrorOrClosed() && loginState.isUser()">
|
||||
<div class="accordion-inner">
|
||||
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Serial Port') }}</label>
|
||||
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
|
||||
|
|
@ -146,12 +154,7 @@
|
|||
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-group" id="state_accordion">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> {{ _('State') }}</a>
|
||||
</div>
|
||||
<div class="accordion-body collapse in" id="state">
|
||||
<div class="accordion-body collapse in" id="state" data-bind="visible: !isErrorOrClosed() && loginState.isUser()">
|
||||
<div class="accordion-inner">
|
||||
{{ _('Machine State') }}: <strong data-bind="text: stateString"></strong><br>
|
||||
{{ _('File') }}: <strong data-bind="text: filename"></strong> <strong data-bind="visible: sd">(SD)</strong><br>
|
||||
|
|
@ -269,12 +272,9 @@
|
|||
</div>
|
||||
<div class="span8">
|
||||
<div id="area_preview" class="workingarea" data-bind="click: move_laser">
|
||||
<div id="crosshair"></div>
|
||||
<div class="laserpos" data-bind="text: laserPos">x,y</div>
|
||||
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
|
||||
<button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting() && loginState.isUser(), css: {'btn-danger': isPaused()}, attr: {title: titlePrintButton}" id="job_print"><i class="icon-white" data-bind="css: {'icon-fire': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? '{{ _('Restart') }}' : '{{ _('Laser') }}')">{{ _('Laser') }}</span></button>
|
||||
<button class="btn span4" id="job_pause" data-bind="click: pause, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser(), css: {active: isPaused()}, attr: {title: titlePauseButton}"><i data-bind="css: {'icon-pause': !isPaused(), 'icon-play': isPaused()}"></i> <span data-bind="visible: !isPaused()">{{ _('Pause') }}</span><span data-bind="visible: isPaused()">{{ _('Resume') }}</span></button>
|
||||
<button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()" title="{{ _('Cancels the job') }}"><i class="icon-stop"></i> {{ _('Cancel') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="" id="control">
|
||||
|
||||
<div class="jog-panel" style="display: none;" data-bind="visible: loginState.isUser">
|
||||
|
|
@ -293,6 +293,8 @@
|
|||
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('y',-1) }"><i class="icon-arrow-down"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if hasZAxis %}
|
||||
<!-- Z jogging control panel -->
|
||||
<div class="jog-panel" id="control_zaxis">
|
||||
<div>
|
||||
|
|
@ -305,6 +307,8 @@
|
|||
<button class="btn box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendJogCommand('z',-1) }"><i class="icon-arrow-down"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Jog distance -->
|
||||
<div class="distance">
|
||||
<div class="btn-group" data-toggle="buttons-radio" id="jog_distance">
|
||||
|
|
@ -376,12 +380,11 @@
|
|||
<div class="tab-pane" id="settings">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
setting menu
|
||||
</div>
|
||||
<div class="span10">
|
||||
<div class="settings" > settings </div>
|
||||
</div>
|
||||
|
||||
<div class="settings" >
|
||||
{% include 'settings_mrbeam.jinja2' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -393,7 +396,7 @@
|
|||
|
||||
<!------>
|
||||
<div class="container octoprint-container">
|
||||
<div class="footer">
|
||||
<footer class="footer">
|
||||
<ul class="pull-left muted">
|
||||
<li><small>{{ _('Version') }}: <span class="version">{{ display_version }}</span></small></li>
|
||||
</ul>
|
||||
|
|
@ -403,10 +406,10 @@
|
|||
<li><a href="https://wiki.mr-beam.org"><i class="icon-book"></i> {{ _('Documentation') }}</a></li>
|
||||
<li><a href="https://github.com/mrbeam/OctoPrint/issues"><i class="icon-flag"></i> {{ _('Bugs and Requests') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{% include 'settings.jinja2' %}
|
||||
|
||||
{% include 'dialogs.jinja2' %}
|
||||
|
||||
<!-- Plugin template files -->
|
||||
|
|
|
|||
|
|
@ -760,6 +760,7 @@ class StateMonitor(object):
|
|||
self._changeEvent.set()
|
||||
|
||||
def setState(self, state):
|
||||
print("state", state)
|
||||
with self._stateMutex:
|
||||
self._state = state
|
||||
self._changeEvent.set()
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ def index():
|
|||
return render_template(
|
||||
root_template,
|
||||
webcamStream=settings().get(["webcam", "stream"]),
|
||||
hasZAxis=settings().get(["feature", "zaxis"]),
|
||||
enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None),
|
||||
enableGCodeVisualizer=settings().get(["gcodeViewer", "enabled"]),
|
||||
enableTemperatureGraph=settings().get(["feature", "temperatureGraph"]),
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ def getSettings():
|
|||
"sdAlwaysAvailable": s.getBoolean(["feature", "sdAlwaysAvailable"]),
|
||||
"swallowOkAfterResend": s.getBoolean(["feature", "swallowOkAfterResend"]),
|
||||
"repetierTargetTemp": s.getBoolean(["feature", "repetierTargetTemp"]),
|
||||
"grbl": s.getBoolean(["feature", "grbl"])
|
||||
"grbl": s.getBoolean(["feature", "grbl"]),
|
||||
"zaxis": s.getBoolean(["feature", "zaxis"])
|
||||
},
|
||||
"serial": {
|
||||
"port": connectionOptions["portPreference"],
|
||||
|
|
@ -169,6 +170,7 @@ def setSettings():
|
|||
if "swallowOkAfterResend" in data["feature"].keys(): s.setBoolean(["feature", "swallowOkAfterResend"], data["feature"]["swallowOkAfterResend"])
|
||||
if "repetierTargetTemp" in data["feature"].keys(): s.setBoolean(["feature", "repetierTargetTemp"], data["feature"]["repetierTargetTemp"])
|
||||
if "grbl" in data["feature"].keys(): s.setBoolean(["feature", "grbl"], data["feature"]["grbl"])
|
||||
if "zaxis" in data["feature"].keys(): s.setBoolean(["feature", "zaxis"], data["feature"]["zaxis"])
|
||||
|
||||
if "serial" in data.keys():
|
||||
if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"])
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
pass
|
||||
|
||||
def sendCurrentData(self, data):
|
||||
print("send_current_data", data)
|
||||
# add current temperature, log and message backlogs to sent data
|
||||
with self._temperatureBacklogMutex:
|
||||
temperatures = self._temperatureBacklog
|
||||
|
|
@ -132,5 +131,4 @@ class PrinterStateConnection(sockjs.tornado.SockJSConnection):
|
|||
self.sendEvent(event, payload)
|
||||
|
||||
def _emit(self, type, payload):
|
||||
print("emit", type, payload)
|
||||
self.send({type: payload})
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ default_settings = {
|
|||
"sdAlwaysAvailable": False,
|
||||
"swallowOkAfterResend": True,
|
||||
"repetierTargetTemp": False,
|
||||
"grbl": True
|
||||
"grbl": True,
|
||||
"zaxis": False
|
||||
},
|
||||
"folder": {
|
||||
"uploads": None,
|
||||
|
|
|
|||
|
|
@ -1504,7 +1504,6 @@ ul.dropdown-menu li a {
|
|||
#control #btn_motors_off,
|
||||
#tab_temp,
|
||||
#temp,
|
||||
#control_zaxis,
|
||||
#gcode_command_slider, #gcode_layer_slider,
|
||||
#renderer_options,
|
||||
#control_extruder {display:none;}
|
||||
|
|
|
|||
|
|
@ -55,15 +55,15 @@ $(function() {
|
|||
}
|
||||
});
|
||||
});
|
||||
$('#navbar_show_settings').click(function() {
|
||||
settingsDialog.modal()
|
||||
.css({
|
||||
width: 'auto',
|
||||
'margin-left': function() { return -($(this).width() /2); }
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
// $('#navbar_show_settings').click(function() {
|
||||
// settingsDialog.modal()
|
||||
// .css({
|
||||
// width: 'auto',
|
||||
// 'margin-left': function() { return -($(this).width() /2); }
|
||||
// });
|
||||
//
|
||||
// return false;
|
||||
// });
|
||||
|
||||
//~~ Initialize view models
|
||||
var loginStateViewModel = new LoginStateViewModel();
|
||||
|
|
@ -391,14 +391,12 @@ $(function() {
|
|||
})
|
||||
}
|
||||
};
|
||||
console.log("wavm", workingAreaViewModel);
|
||||
|
||||
settingsViewModel.requestData(function() {
|
||||
console.log("settingsViewModel.requestData");
|
||||
ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
|
||||
|
||||
ko.applyBindings(connectionViewModel, document.getElementById("connection_accordion"));
|
||||
ko.applyBindings(printerStateViewModel, document.getElementById("state_accordion"));
|
||||
ko.applyBindings(connectionViewModel, document.getElementById("connection"));
|
||||
ko.applyBindings(printerStateViewModel, document.getElementById("state"));
|
||||
ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_accordion"));
|
||||
//ko.applyBindings(temperatureViewModel, document.getElementById("temp"));
|
||||
ko.applyBindings(controlViewModel, document.getElementById("control"));
|
||||
|
|
@ -421,7 +419,6 @@ $(function() {
|
|||
//
|
||||
// ko.applyBindings(slicingViewModel, document.getElementById("slicing_configuration_dialog"));
|
||||
// ko.applyBindings(vectorConversionViewModel, document.getElementById("dialog_vector_graphics_conversion"));
|
||||
// console.log("settingsViewModel.requestData wavm", document.getElementById("working_area"));
|
||||
ko.applyBindings(workingAreaViewModel, document.getElementById("area_preview"));
|
||||
|
||||
// apply bindings and signal startup
|
||||
|
|
|
|||
|
|
@ -79,10 +79,10 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
if (self.previousIsOperational != self.isOperational()) {
|
||||
if (self.isOperational() && connectionTab.hasClass("in")) {
|
||||
// connection just got established, close connection tab for now
|
||||
connectionTab.collapse("hide");
|
||||
// connectionTab.collapse("hide");
|
||||
} else if (!connectionTab.hasClass("in")) {
|
||||
// connection just dropped, make sure connection tab is open
|
||||
connectionTab.collapse("show");
|
||||
// connectionTab.collapse("show");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
|
||||
self._processStateData = function(data) {
|
||||
var prevPaused = self.isPaused();
|
||||
|
||||
self.stateString(gettext(data.text));
|
||||
self.isErrorOrClosed(data.flags.closedOrError);
|
||||
self.isOperational(data.flags.operational);
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
self.feature_sdAlwaysAvailable = ko.observable(undefined);
|
||||
self.feature_swallowOkAfterResend = ko.observable(undefined);
|
||||
self.feature_repetierTargetTemp = ko.observable(undefined);
|
||||
self.feature_zaxis = ko.observable(undefined);
|
||||
|
||||
self.serial_port = ko.observable();
|
||||
self.serial_baudrate = ko.observable();
|
||||
|
|
@ -223,6 +224,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
};
|
||||
|
||||
self.fromResponse = function(response) {
|
||||
console.log("settings resp", response)
|
||||
if (self.settings === undefined) {
|
||||
self.settings = ko.mapping.fromJS(response);
|
||||
} else {
|
||||
|
|
@ -262,6 +264,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
self.feature_sdAlwaysAvailable(response.feature.sdAlwaysAvailable);
|
||||
self.feature_swallowOkAfterResend(response.feature.swallowOkAfterResend);
|
||||
self.feature_repetierTargetTemp(response.feature.repetierTargetTemp);
|
||||
self.feature_zaxis(response.feature.zaxis);
|
||||
|
||||
self.serial_port(response.serial.port);
|
||||
self.serial_baudrate(response.serial.baudrate);
|
||||
|
|
@ -294,7 +297,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
|
||||
self.saveData = function() {
|
||||
var data = ko.mapping.toJS(self.settings);
|
||||
|
||||
console.log("settings save", data)
|
||||
data = _.extend(data, {
|
||||
"api" : {
|
||||
"enabled": self.api_enabled(),
|
||||
|
|
@ -333,7 +336,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
"sdSupport": self.feature_sdSupport(),
|
||||
"sdAlwaysAvailable": self.feature_sdAlwaysAvailable(),
|
||||
"swallowOkAfterResend": self.feature_swallowOkAfterResend(),
|
||||
"repetierTargetTemp": self.feature_repetierTargetTemp()
|
||||
"repetierTargetTemp": self.feature_repetierTargetTemp(),
|
||||
"zaxis": self.feature_zaxis()
|
||||
},
|
||||
"serial": {
|
||||
"port": self.serial_port(),
|
||||
|
|
@ -375,7 +379,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
data: JSON.stringify(data),
|
||||
success: function(response) {
|
||||
self.fromResponse(response);
|
||||
$("#settings_dialog").modal("hide");
|
||||
// $("#settings_dialog").modal("hide");
|
||||
$("#settings_save_btn").attr("disabled", "disabled");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -134,3 +134,25 @@
|
|||
<a href="#" class="btn btn-primary" data-bind="click: keepAccessControl, enable: validData(), css: {disabled: !validData()}">{{ _('Keep Access Control Enabled') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="modal hide fade" data-bind="visible: loginState.isUser" id="connection_popup">
|
||||
<div class="modal-header">
|
||||
<a class="" href="#connection"><i class="icon-signal"></i> {{ _('Connection') }}</a>
|
||||
</div>
|
||||
<div class="modal-body in" id="connection">
|
||||
<div class="">
|
||||
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Serial Port') }}</label>
|
||||
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
|
||||
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Baudrate') }}</label>
|
||||
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Save connection settings') }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="connection_autoconnect" data-bind="checked: settings.serial_autoconnect, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Auto-connect on server startup') }}
|
||||
</label>
|
||||
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer"></div>
|
||||
</div>-->
|
||||
|
|
|
|||
501
src/octoprint/templates/settings_mrbeam.jinja2
Normal file
501
src/octoprint/templates/settings_mrbeam.jinja2
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
<div id="settings_dialog" class="container" xtabindex="-1" role="xdialog" aria-labelledby="settings_dialog_label" aria-hidden="true">
|
||||
<div class="xmodal-header">
|
||||
<!--<button type="button" class="close" data-dismiss="modal">×</button>-->
|
||||
<!--<h3 id="settings_dialog_label">{{ _('Mr Beam Settings') }}</h3>-->
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-list span3" id="settingsTabs">
|
||||
<li class="active"><a href="#settings_serialConnection" data-toggle="tab">{{ _('Serial Connection') }}</a></li>
|
||||
<li><a href="#settings_printerParameters" data-toggle="tab">{{ _('Parameters') }}</a></li>
|
||||
|
||||
{% if enableAccessControl %}
|
||||
<li><a href="#settings_users" data-toggle="tab">{{ _('Access Control') }}</a></li>
|
||||
{% endif %}
|
||||
|
||||
<li><a href="#settings_folder" data-toggle="tab">{{ _('Folders') }}</a></li>
|
||||
<li><a href="#settings_logs" data-toggle="tab">{{ _('Logs') }}</a></li>
|
||||
{% if settingsPlugins %}
|
||||
<li class="nav-header">Plugins</li>
|
||||
{% for plugin_name, vars in settingsPlugins.items() %}
|
||||
{% if vars._settings_menu_entry %}
|
||||
<li><a href="#settings_plugin_{{ plugin_name }}" data-toggle="tab">{{ vars._settings_menu_entry }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content span8">
|
||||
<div class="tab-pane active" id="settings_serialConnection">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialPort">{{ _('Serial Port') }}</label>
|
||||
<div class="controls">
|
||||
<select id="settings-serialPort" data-bind="options: serial_portOptions, optionsCaption: 'AUTO', value: serial_port"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutCommunication">{{ _('Communication timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutCommunication" id="settings-serialTimeoutCommunication">
|
||||
<span class="add-on">s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutConnection">{{ _('Connection timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutConnection" id="settings-serialTimeoutConnection">
|
||||
<span class="add-on">s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutDetection">{{ _('Autodetection timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutDetection" id="settings-serialTimeoutDetection">
|
||||
<span class="add-on">s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: serial_log" id="settings-serialLog"> {{ _('Log communication to serial.log (might negatively impact performance)') }} <span class="label label-important">{{ _('Warning') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_printerParameters">
|
||||
<form class="form-horizontal">
|
||||
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-bedSize">{{ _('Working area') }}</label>
|
||||
<div class="controls form-inline">
|
||||
<label>{{ _('X') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printer_bedDimensionX" id="settings-bedX">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
<label>{{ _('Y') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printer_bedDimensionY" id="settings-bedY">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="control-label" for="settings-zaxis">{{ _('Z Axis') }}</label>
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_zaxis" id="settings-zaxis"> {{ _('Show controls for z-axis') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_features">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_temperatureGraph" id="settings-featureTemperatureGraph"> {{ _('Enable Temperature Graph') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_gcodeViewer" id="settings-featureGcodeViewer"> {{ _('Enable GCode Visualizer') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdSupport" id="settings-featureSdSupport"> {{ _('Enable SD support') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdAlwaysAvailable" id="settings-featureSdAlwaysAvailable"> {{ _('Always assume SD card is present') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> {{ _('Wait for <code>start</code> on connect') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_alwaysSendChecksum" id="settings-featureAlwaysSendChecksum"> {{ _('Send a checksum with <strong>every</strong> command') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_repetierTargetTemp" id="settings-featureRepetierTargetTemp"> {{ _('Support <code>TargetExtr%%n</code>/<code>TargetBed</code> target temperature format') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_swallowOkAfterResend" id="settings-swallowOkAfterResend"> {{ _('Swallow the first "ok" after a resend response') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_folder">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-folderUploads">{{ _('Upload Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_uploads" id="settings-folderUploads">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-folderLogs">{{ _('Logs Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_logs" id="settings-folderLogs">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_temperature">
|
||||
<form class="form-horizontal">
|
||||
<div class="row-fluid">
|
||||
<div class="offset3 span3"><h4>{{ _('Extruder') }}</h4></div>
|
||||
<div class="span3"><h4>{{ _('Bed') }}</h4></div>
|
||||
</div>
|
||||
<div data-bind="foreach: temperature_profiles">
|
||||
<div class="row-fluid" style="margin-bottom: 5px">
|
||||
<div class="span3">
|
||||
<input type="text" class="span12 text-right" data-bind="value: name">
|
||||
</div>
|
||||
<div class="input-append span3">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: extruder">
|
||||
<span class="add-on">°C</span>
|
||||
</div>
|
||||
<div class="input-append span3">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: bed">
|
||||
<span class="add-on">°C</span>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<a title="Remove profile" class="btn btn-danger" data-bind="click: $parent.removeTemperatureProfile"><i class="icon-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="offset9 span2">
|
||||
<a title="Add Profile" class="btn btn-primary" data-bind="click: addTemperatureProfile"><i class="icon-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_terminalFilters">
|
||||
<form class="form-horizontal">
|
||||
<div class="row-fluid">
|
||||
<div class="span4"><h4>{{ _('Name') }}</h4></div>
|
||||
<div class="span6"><h4>{{ _('RegExp') }} <small><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions">?</a></small></h4></div>
|
||||
</div>
|
||||
<div data-bind="foreach: terminalFilters">
|
||||
<div class="row-fluid" style="margin-bottom: 5px">
|
||||
<div class="span4">
|
||||
<input type="text" class="span12" data-bind="value: name">
|
||||
</div>
|
||||
<div class="span6">
|
||||
<input type="text" class="span12" data-bind="value: regex">
|
||||
</div>
|
||||
<div class="span2">
|
||||
<a title="Remove Filter" class="btn btn-danger" data-bind="click: $parent.removeTerminalFilter"><i class="icon-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="offset10 span2">
|
||||
<a title="Add Filter" class="btn btn-primary" data-bind="click: addTerminalFilter"><i class="icon-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_appearance">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-appearanceName">{{ _('Title') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: appearance_name" id="settings-appearanceName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-appearanceColor">{{ _('Color') }}</label>
|
||||
<div class="controls">
|
||||
<select id="settings-appearanceColor" data-bind="value: appearance_color, options: appearance_available_colors, optionsText: 'name', optionsValue: 'key'">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="settings_api">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-apiEnabled" data-bind="checked: api_enabled"> {{ _('Enable') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-apiCors" data-bind="checked: api_allowCrossOrigin"> {{ _('Allow <a href="%(url)s">Cross Origin Resource Sharing (CORS)</a>', url = "https://en.wikipedia.org/wiki/Cross-origin_resource_sharing") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-apiKey">{{ _('API Key') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: api_key" id="settings-apikey">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if enableAccessControl %}
|
||||
<div class="tab-pane" id="settings_users">
|
||||
<table class="table table-condensed table-hover" id="system_users">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="settings_users_name">{{ _('Name') }}</th>
|
||||
<th class="settings_users_active">{{ _('Active') }}</th>
|
||||
<th class="settings_users_admin">{{ _('Admin') }}</th>
|
||||
<th class="settings_users_actions">{{ _('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: users.listHelper.paginatedItems">
|
||||
<tr>
|
||||
<td class="settings_users_name"><span data-bind="text: name"></span><span class="muted" data-bind="visible: $root.api_enabled() && apikey"><br /><small>{{ _('API Key') }}: <span data-bind="text: apikey"></span></small></span></td>
|
||||
<td class="settings_users_active"><i data-bind="css: { 'icon-check': active, 'icon-check-empty': !active }"></i></td>
|
||||
<td class="settings_users_admin"><i data-bind="css: { 'icon-check': admin, 'icon-check-empty': !admin }"></i></td>
|
||||
<td class="settings_users_actions" class="system_users_action">
|
||||
<a href="#" class="icon-pencil" title="{{ _('Update User') }}" data-bind="click: function() { $root.users.showEditUserDialog($data); }"></a> | <a href="#" class="icon-key" title="{{ _('Change password') }}" data-bind="click: function() { $root.users.showChangePasswordDialog($data); }"></a> | <a href="#" class="icon-trash" title="{{ _('Delete user') }}" data-bind="click: function() { $root.users.removeUser($data); }"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination pagination-mini pagination-centered">
|
||||
<ul>
|
||||
<li data-bind="css: {disabled: users.listHelper.currentPage() === 0}"><a href="#" data-bind="click: users.listHelper.prevPage">«</a></li>
|
||||
</ul>
|
||||
<ul data-bind="foreach: users.listHelper.pages">
|
||||
<li data-bind="css: { active: $data.number === $root.users.listHelper.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.users.listHelper.changePage($data.number); }"></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li data-bind="css: {disabled: users.listHelper.currentPage() === users.listHelper.lastPage()}"><a href="#" data-bind="click: users.listHelper.nextPage">»</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button title="Add user" class="btn" data-bind="click: $root.users.showAddUserDialog"><i class="icon-plus"></i> {{ _('Add user') }}</button>
|
||||
|
||||
<!-- Modals for user management -->
|
||||
|
||||
<div id="settings-usersDialogAddUser" class="xmodal xhide xfade">
|
||||
<div class="xmodal-header">
|
||||
<a href="#" xclass="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>{{ _('Add user') }}</h3>
|
||||
</div>
|
||||
<div class="xmodal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-usersDialogAddUserName">{{ _('Username') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" id="settings-usersDialogAddUserName" data-bind="value: $root.users.editorUsername" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-usersDialogAddUserPassword1">{{ _('Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogAddUserPassword1" data-bind="value: $root.users.editorPassword" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="css: {error: $root.users.editorPasswordMismatch()}">
|
||||
<label class="control-label" for="settings-usersDialogAddUserPassword2">{{ _('Repeat Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogAddUserPassword2" data-bind="value: $root.users.editorRepeatedPassword, valueUpdate: 'afterkeydown'" required>
|
||||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">{{ _('Passwords do not match') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogAddUserActive" data-bind="checked: $root.users.editorActive"> {{ _('Active') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogAddUserAdmin" data-bind="checked: $root.users.editorAdmin"> {{ _('Admin') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="xmodal-footer">
|
||||
<button class="btn" data-dismiss="xmodal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmAddUser(); }, enable: !$root.users.editorPasswordMismatch()">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-usersDialogEditUser" class="xmodal hide fade">
|
||||
<div class="xmodal-header">
|
||||
<a href="#" class="close" data-dismiss="xmodal" aria-hidden="true">×</a>
|
||||
<h3>{{ _('Edit user "%(user)s"', user = '<span data-bind="text: $root.users.editorUsername"></span>') }}</h3>
|
||||
</div>
|
||||
<div class="xmodal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogEditUserActive" data-bind="checked: $root.users.editorActive"> {{ _('Active') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogEditUserAdmin" data-bind="checked: $root.users.editorAdmin"> {{ _('Admin') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="xmodal-footer">
|
||||
<button class="btn" data-dismiss="xmodal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmEditUser(); }">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-usersDialogChangePassword" class="xmodal hide fade">
|
||||
<div class="xmodal-header">
|
||||
<a href="#" class="close" data-dismiss="xmodal" aria-hidden="true">×</a>
|
||||
<h3>{{ _('Change password for user "%(user)s"', user='<span data-bind="text: $root.users.editorUsername"></span>') }}</h3>
|
||||
</div>
|
||||
<div class="xmodal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-usersDialogChangePasswordPassword1">{{ _('New Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogChangePasswordPassword1" data-bind="value: $root.users.editorPassword" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="css: {error: $root.users.editorPasswordMismatch()}">
|
||||
<label class="control-label" for="settings-usersDialogChangePasswordPassword2">{{ _('Repeat Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogChangePasswordPassword2" data-bind="value: $root.users.editorRepeatedPassword, valueUpdate: 'afterkeydown'" required>
|
||||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">{{ _('Passwords do not match') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset data-bind="visible: api_enabled">
|
||||
<legend>Apikey</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Current API Key') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-block-level uneditable-input" data-bind="value: $root.users.editorApikey, attr: {placeholder: '{{ _('N/A') }}'}">
|
||||
<a class="btn" title="Generate new Apikey" data-bind="click: function() { $root.users.confirmGenerateApikey(); }"><i class="icon-refresh"></i></a>
|
||||
<a class="btn btn-danger" title="Delete Apikey" data-bind="click: function() { $root.users.confirmDeleteApikey(); }"><i class="icon-trash"></i></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div class="xmodal-footer">
|
||||
<button class="btn" data-dismiss="xmodal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmChangePassword(); }, enable: !$root.users.editorPasswordMismatch()">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for plugin_name, vars in settingsPlugins.items() %}
|
||||
<div class="tab-pane" id="settings_plugin_{{ plugin_name }}" data-bind="allowBindings: false">
|
||||
{% include plugin_name+"_settings_dialog.jinja2" ignore missing %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="tab-pane" id="settings_logs" data-bind="allowBindings: false">
|
||||
<div id="logs">
|
||||
|
||||
<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('modification'); }">{{ _('Modification 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="settings_logs_name">{{ _('Name') }}</th>
|
||||
<th class="settings_logs_size">{{ _('Size') }}</th>
|
||||
<th class="settings_logs_date">{{ _('Date') }}</th>
|
||||
<th class="settings_logs_action">{{ _('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: listHelper.paginatedItems">
|
||||
<tr data-bind="attr: {title: name}">
|
||||
<td class="settings_logs_name" data-bind="text: name"></td>
|
||||
<td class="settings_logs_size" data-bind="text: formatSize(size)"></td>
|
||||
<td class="settings_logs_date" data-bind="text: formatDate(date)"></td>
|
||||
<td class="settings_logs_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> | <a href="#" class="icon-download" data-bind="attr: {href: refs.download}"></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>
|
||||
</div>
|
||||
<div class="">
|
||||
<!--<button class="btn" data-dismiss="xmodal" aria-hidden="true">{{ _('Cancel') }}</button>-->
|
||||
<button id="settings_save_btn" class="btn btn-primary" data-bind="click: saveData">{{ _('Save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in a new issue