Files
2026-05-08 01:16:40 +08:00

231 lines
13 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{{ _('System Settings') }}</h1>
</div>
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3">{{ _('SliceEngine Configurations') }}</h5>
<form id="settingsForm" onsubmit="submitSettings(event)">
<div class="mb-3">
<label for="offset_x" class="form-label">{{ _('Plater Origin Offset X (mm)') }}</label>
<input type="number" class="form-control" name="offset_x" id="offset_x" value="{{ configs.get('offset_x', '0') }}">
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Adjust the X-axis compilation offset for combined files on the build plate.') }}</div>
</div>
<div class="mb-3">
<label for="offset_y" class="form-label">{{ _('Plater Origin Offset Y (mm)') }}</label>
<input type="number" class="form-control" name="offset_y" id="offset_y" value="{{ configs.get('offset_y', '0') }}">
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Adjust the Y-axis compilation offset for combined files on the build plate.') }}</div>
</div>
<div class="mb-3">
<label for="proxy_skip_size_mb" class="form-label">{{ _('Proxy Skip Size (MB)') }}</label>
<input type="number" class="form-control" name="proxy_skip_size_mb" id="proxy_skip_size_mb" value="{{ configs.get('proxy_skip_size_mb', '5.0') }}" step="0.1" min="0">
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Files smaller than this will not generate a simplified proxy.') }}</div>
</div>
<div class="mb-3">
<label for="gcode_upload_folder" class="form-label"><i class="bi bi-folder2-open me-2"></i>{{ _('Custom GCode Output Folder') }}</label>
<input type="text" class="form-control" name="gcode_upload_folder" id="gcode_upload_folder" value="{{ configs.get('gcode_upload_folder', '') }}">
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Absolute path to save locally sliced GCode files (e.g. OctoPrint uploads folder like "/home/pi/.octoprint/uploads"). Leave empty to use system default.') }}</div>
</div>
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3"><i class="bi bi-grid-3x3 me-2"></i>{{ _('Default Plater Settings') }}</h5>
<div class="mb-3">
<label for="build_plate_model_path" class="form-label">{{ _('Build Plate Model Path (.stl)') }}</label>
<input type="text" class="form-control" name="build_plate_model_path" id="build_plate_model_path" value="{{ configs.get('build_plate_model_path', '') }}">
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Absolute path to a custom build plate STL model to show in the plater. Leave empty to use none.') }}</div>
</div>
<div class="mb-3">
<label for="default_printer" class="form-label">{{ _('Default Printer Profile') }}</label>
<select class="form-select" name="default_printer" id="default_printer" data-selected="{{ configs.get('default_printer', '') }}">
<!-- Loaded via JS -->
</select>
<div class="form-text"><i class="bi bi-info-circle me-1"></i>{{ _('Main configuration for the printer dimensions, limits and base profiles.') }}</div>
</div>
<div class="mb-3">
<label for="default_infill" class="form-label">{{ _('Default Infill Density (%)') }}</label>
<input type="number" class="form-control" name="default_infill" id="default_infill" value="{{ configs.get('default_infill', '20') }}" min="0" max="100">
</div>
<div class="mb-3">
<label for="default_support" class="form-label">{{ _('Default Support') }}</label>
<select class="form-select" name="default_support" id="default_support">
<option value="false" {% if configs.get('default_support', 'false') == 'false' %}selected{% endif %}>{{ _('None') }}</option>
<option value="buildplate" {% if configs.get('default_support', 'false') == 'buildplate' %}selected{% endif %}>{{ _('Touching Buildplate') }}</option>
<option value="true" {% if configs.get('default_support', 'false') == 'true' %}selected{% endif %}>{{ _('Everywhere') }}</option>
</select>
</div>
<div class="mb-3">
<label for="default_support_pattern" class="form-label">{{ _('Default Support Type') }}</label>
<select class="form-select" name="default_support_pattern" id="default_support_pattern" data-selected="{{ configs.get('default_support_pattern', 'tree') }}">
<!-- Loaded via JS -->
</select>
</div>
<div class="mb-3">
<label for="default_quality" class="form-label">{{ _('Default Quality Profile') }}</label>
<select class="form-select" name="default_quality" id="default_quality" data-selected="{{ configs.get('default_quality', 'base_global_standard.inst.cfg') }}">
<!-- Loaded via JS -->
</select>
</div>
<div class="mb-4">
<label for="default_material" class="form-label">{{ _('Default Material Profile') }}</label>
<select class="form-select" name="default_material" id="default_material" data-selected="{{ configs.get('default_material', '') }}">
<!-- Loaded via JS -->
</select>
</div>
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3"><i class="bi bi-cpu me-2"></i>{{ _('Slicing Engine Configurations') }}</h5>
<div class="mb-3">
<label for="slicer_engine" class="form-label">{{ _('Slicing Engine') }}</label>
<select class="form-select" name="slicer_engine" id="slicer_engine">
{% for engine in engines %}
<option value="{{ engine.name }}" {% if configs.get('slicer_engine', 'cura') == engine.name %}selected{% endif %} {% if not engine.is_available %}disabled{% endif %}>
{{ engine.display_name }} {% if not engine.is_available %}({{ _('Not Available') }}){% endif %}
</option>
{% endfor %}
</select>
<div class="form-text mt-2"><i class="bi bi-info-circle me-1"></i>{{ _('Select the engine to be used globally. Ensure the selected engine is installed and accessible on the server.') }}</div>
</div>
<h5 class="card-title text-primary border-bottom pb-2 mt-4 mb-3"><i class="bi bi-hdd-network me-2"></i>{{ _('Default Storage Quotas (MB)') }}</h5>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">{{ _('Guest STL Quota') }} <small class="text-muted">(0 = {{ _('Unlimited') }})</small></label>
<input type="number" class="form-control" name="default_guest_stl_quota_mb" value="{{ configs.get('default_guest_stl_quota_mb', '0') }}" min="0">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">{{ _('Guest GCode Quota') }} <small class="text-muted">(0 = {{ _('Unlimited') }})</small></label>
<input type="number" class="form-control" name="default_guest_gcode_quota_mb" value="{{ configs.get('default_guest_gcode_quota_mb', '0') }}" min="0">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">{{ _('New User STL Quota') }} <small class="text-muted">(0 = {{ _('Unlimited') }})</small></label>
<input type="number" class="form-control" name="default_user_stl_quota_mb" value="{{ configs.get('default_user_stl_quota_mb', '0') }}" min="0">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-semibold">{{ _('New User GCode Quota') }} <small class="text-muted">(0 = {{ _('Unlimited') }})</small></label>
<input type="number" class="form-control" name="default_user_gcode_quota_mb" value="{{ configs.get('default_user_gcode_quota_mb', '0') }}" min="0">
</div>
</div>
<button type="submit" class="btn btn-primary" id="btn-save-settings">{{ _('Save Settings') }}</button>
</form>
</div>
</div>
<script>
function submitSettings(event) {
event.preventDefault();
const form = document.getElementById('settingsForm');
const formData = new FormData(form);
const btn = document.getElementById('btn-save-settings');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Saving...';
fetch("{{ url_for('admin.settings') }}", {
method: "POST",
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (response.ok) {
window.showToast("{{ _('Settings updated successfully') }}", "success");
} else {
window.showToast("{{ _('Error updating settings') }}", "danger");
}
})
.catch(error => {
console.error('Error:', error);
window.showToast("{{ _('Network error') }}", "danger");
})
.finally(() => {
btn.disabled = false;
btn.innerHTML = originalText;
});
}
document.addEventListener('DOMContentLoaded', function() {
const engineSelect = document.getElementById('slicer_engine');
const qualitySelect = document.getElementById('default_quality');
const materialSelect = document.getElementById('default_material');
const patternSelect = document.getElementById('default_support_pattern');
const printerSelect = document.getElementById('default_printer');
function updateOptions(engine) {
fetch(`/api/engine_options/${engine}`)
.then(res => res.json())
.then(data => {
const selPtr = printerSelect.getAttribute('data-selected');
printerSelect.innerHTML = '';
if(data.printers) {
data.printers.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
printerSelect.appendChild(opt);
});
}
if(selPtr) printerSelect.value = selPtr;
const selQ = qualitySelect.getAttribute('data-selected');
qualitySelect.innerHTML = '';
data.presets.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
qualitySelect.appendChild(opt);
});
if(selQ) qualitySelect.value = selQ;
const selM = materialSelect.getAttribute('data-selected');
materialSelect.innerHTML = '';
// Add an empty option for material (optional fallback)
const emptyOpt = document.createElement('option');
emptyOpt.value = ''; emptyOpt.textContent = "{{ _('Auto / Default') }}";
materialSelect.appendChild(emptyOpt);
if(data.materials) {
data.materials.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
materialSelect.appendChild(opt);
});
}
if(selM) materialSelect.value = selM;
const selP = patternSelect.getAttribute('data-selected');
patternSelect.innerHTML = '';
data.support_patterns.forEach(p => {
const opt = document.createElement('option');
opt.value = p.id; opt.textContent = p.name;
patternSelect.appendChild(opt);
});
if(selP) patternSelect.value = selP;
});
}
engineSelect.addEventListener('change', function() {
updateOptions(this.value);
});
// Initial load
updateOptions(engineSelect.value);
});
</script>
{% endblock %}