修复偏移问题,修复代理问题
This commit is contained in:
@@ -28,6 +28,12 @@
|
||||
<div class="form-text">{{ _('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">{{ _('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="mt-4">{{ _('Default Plater Settings') }}</h5>
|
||||
<hr>
|
||||
|
||||
@@ -70,6 +76,31 @@
|
||||
</select>
|
||||
</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>
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<h1 class="h2">{{ _('User Management') }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 text-end">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUserModal"><i class="bi bi-person-plus me-1"></i>{{ _('Add User') }}</button>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
@@ -12,6 +15,7 @@
|
||||
<th>{{ _('ID') }}</th>
|
||||
<th>{{ _('Username') }}</th>
|
||||
<th>{{ _('Role') }}</th>
|
||||
<th>{{ _('Quotas') }}</th>
|
||||
<th>{{ _('Created At') }}</th>
|
||||
<th>{{ _('Actions') }}</th>
|
||||
</tr>
|
||||
@@ -30,8 +34,16 @@
|
||||
<span class="badge bg-primary">{{ _('User') }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted d-block">STL: {{ user_quotas[user.id]['stl'] if user_quotas[user.id]['stl'] != '0' else _('Unlimited') }} MB</small>
|
||||
<small class="text-muted d-block">GCode: {{ user_quotas[user.id]['gcode'] if user_quotas[user.id]['gcode'] != '0' else _('Unlimited') }} MB</small>
|
||||
</td>
|
||||
<td>{{ user.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#editQuotaModal{{ user.id }}">{{ _('Edit Quota') }}</button>
|
||||
{% if not user.is_guest %}
|
||||
<button class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#resetPwdModal{{ user.id }}">{{ _('Reset Password') }}</button>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('admin.delete_user', user_id=user.id) }}" method="POST" class="d-inline" onsubmit="event.preventDefault(); window.customConfirm('{{ _('WARNING: Are you sure you want to permanently delete this user AND ALL their uploaded files and G-codes?') }}', () => { this.submit(); });">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" {% if user.id == current_user.id %}disabled{% endif %}>{{ _('Delete') }}</button>
|
||||
</form>
|
||||
@@ -41,4 +53,110 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% for user in users %}
|
||||
<!-- Edit Quota Modal -->
|
||||
<div class="modal fade" id="editQuotaModal{{ user.id }}" tabindex="-1" aria-labelledby="editQuotaModalLabel{{ user.id }}" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('admin.update_quota', user_id=user.id) }}" method="POST">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editQuotaModalLabel{{ user.id }}">{{ _('Edit Quota for') }} {{ user.username }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ _('STL Quota') }} (MB) <small class="text-muted">(0 = {{ _('Unlimited') }})</small></label>
|
||||
<input type="number" class="form-control" name="stl_quota_mb" value="{{ user_quotas[user.id]['stl'] }}" min="0">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ _('GCode Quota') }} (MB) <small class="text-muted">(0 = {{ _('Unlimited') }})</small></label>
|
||||
<input type="number" class="form-control" name="gcode_quota_mb" value="{{ user_quotas[user.id]['gcode'] }}" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">{{ _('Save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not user.is_guest %}
|
||||
<!-- Reset Password Modal -->
|
||||
<div class="modal fade" id="resetPwdModal{{ user.id }}" tabindex="-1" aria-labelledby="resetPwdModalLabel{{ user.id }}" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('admin.reset_password', user_id=user.id) }}" method="POST">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="resetPwdModalLabel{{ user.id }}">{{ _('Reset Password for') }} {{ user.username }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ _('New Password') }}</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">{{ _('Save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ url_for('admin.add_user') }}" method="POST">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addUserModalLabel">{{ _('Add User') }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ _('Username') }}</label>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ _('Password') }}</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" name="is_admin" id="isAdminCheck">
|
||||
<label class="form-check-label" for="isAdminCheck">{{ _('Is Admin') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">{{ _('Create User') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bootstrap Modal Z-Index Fix -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.modal').forEach(function(modal) {
|
||||
document.body.appendChild(modal);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap Modal Z-Index Fix -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.modal').forEach(function(modal) {
|
||||
document.body.appendChild(modal);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,17 @@
|
||||
.card-header { border-bottom: 1px solid rgba(0,0,0,.05); background-color: transparent; }
|
||||
|
||||
.toast-container { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1055; width: auto; max-width: 90%; pointer-events: none; }
|
||||
.toast { border-radius: 0.5rem; box-shadow: 0 0.5rem 1rem rgba(0,0,0,.25); opacity: 1 !important; pointer-events: auto; }
|
||||
.toast {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0,0,0,.25);
|
||||
pointer-events: auto;
|
||||
border: none;
|
||||
transform: translateY(-20px);
|
||||
transition: opacity 0.35s ease, transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
|
||||
}
|
||||
.toast.showing, .toast.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 页面切换动画 Page Transition */
|
||||
@keyframes pageFadeInSlide {
|
||||
@@ -117,6 +127,9 @@
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mb-2 text-muted fw-bold text-uppercase" style="font-size: 0.75rem;">
|
||||
<span><i class="bi bi-list-task me-1"></i>{{ _('General Operations') }}</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column nav-pills gap-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark {% if request.endpoint == 'main.index' %}active text-white shadow-sm{% endif %}" href="{{ url_for('main.index') }}">
|
||||
@@ -158,31 +171,30 @@
|
||||
</nav>
|
||||
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 mt-4 bg-light min-vh-100 pb-5">
|
||||
|
||||
<!-- Toast Notification Container -->
|
||||
<div class="toast-container" id="global-toast-container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
{% set toast_class = 'bg-success text-white' if category == 'success' else 'bg-danger text-white' if category == 'danger' else 'bg-warning text-dark' if category == 'warning' else 'bg-primary text-white' %}
|
||||
<div class="toast align-items-center border-0 {{ toast_class }} mb-2" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body fw-medium">
|
||||
{{ message }}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notification Container -->
|
||||
<div class="toast-container" id="global-toast-container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
{% set toast_class = 'bg-success text-white' if category == 'success' else 'bg-danger text-white' if category == 'danger' else 'bg-warning text-dark' if category == 'warning' else 'bg-primary text-white' %}
|
||||
<div class="toast align-items-center border-0 {{ toast_class }} mb-2" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body fw-medium">
|
||||
{{ _(message) if _ else message }}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<!-- Global Custom Alert Modal -->
|
||||
<div class="modal fade" id="globalAlertModal" tabindex="-1" aria-hidden="true" style="z-index: 1060;">
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
/* Prevent the outer page from scrolling */
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Make the main container take exactly the viewport height and act as a flex column */
|
||||
main {
|
||||
height: 100vh;
|
||||
padding-bottom: 0 !important;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
/* Let the iframe container fill all the remaining height automatically */
|
||||
.octo-panel-container {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 0 !important;
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<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"><i class="bi bi-window-sidebar text-info me-2"></i>{{ _('OctoPrint Panel (Embedded)') }}</h1>
|
||||
</div>
|
||||
|
||||
{% if embed_url %}
|
||||
<div class="card shadow rounded overflow-hidden" style="height: calc(100vh - 180px); min-height: 500px;">
|
||||
<!-- iFrame wrapper for responsivness -->
|
||||
<div class="w-100 h-100 position-relative">
|
||||
<iframe src="{{ embed_url }}"
|
||||
class="position-absolute border-0 w-100 h-100"
|
||||
style="top: 0; left: 0;"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="card shadow overflow-hidden octo-panel-container position-relative">
|
||||
<iframe src="{{ embed_url }}"
|
||||
class="position-absolute border-0 w-100 h-100"
|
||||
style="top: 0; left: 0;"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning shadow-sm border-0 d-flex align-items-center" role="alert">
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
@keyframes blink-fade {
|
||||
0% { background-color: rgba(255, 193, 7, 0.4); box-shadow: 0 0 15px rgba(255, 193, 7, 0.6); }
|
||||
50% { background-color: rgba(255, 193, 7, 0); box-shadow: 0 0 0 rgba(255, 193, 7, 0); }
|
||||
100% { background-color: rgba(255, 193, 7, 0.4); box-shadow: 0 0 15px rgba(255, 193, 7, 0.6); }
|
||||
}
|
||||
.animate-blink {
|
||||
animation: blink-fade 0.8s ease-in-out 4;
|
||||
border: 2px solid #ffc107 !important;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.transition-style {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
<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"><i class="bi bi-file-earmark-plus text-primary me-2"></i>{{ _('Prepare Print') }}</h1>
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm rounded shadow-sm fw-bold" onclick="document.getElementById('gcodeUploadInput').click();">
|
||||
<i class="bi bi-upload me-1"></i>{{ _('Upload External GCode') }}
|
||||
</button>
|
||||
<input type="file" id="gcodeUploadInput" style="display: none;" accept=".gcode,.gco,.g" onchange="uploadExternalGcode(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
@@ -17,12 +38,13 @@
|
||||
<div class="list-group list-group-flush">
|
||||
{% for f in files %}
|
||||
{% if f.type == 'machinecode' %}
|
||||
<div class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-3">
|
||||
<div id="file-{{ f.id }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center py-3 transition-style">
|
||||
<div class="me-auto text-truncate" style="max-width: 80%;">
|
||||
<h6 class="mb-1"><i class="bi bi-file-earmark-code text-primary me-2"></i>{{ f.name }}</h6>
|
||||
<small class="text-muted d-block">{{ _('Size:') }} {{ f.size }} bytes, {{ _('Time:') }} {{ f.gcodeAnalysis.estimatedPrintTime if f.gcodeAnalysis else 'Unknown' }}s</small>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('main.preview_gcode', file_id=f.id) }}" class="btn btn-sm btn-outline-info rounded-pill px-3 shadow-sm me-2"><i class="bi bi-eye me-1"></i>{{ _('Preview') }}</a>
|
||||
<button class="btn btn-sm btn-outline-success rounded-pill px-3 shadow-sm" onclick="printFile('{{ f.origin }}', '{{ f.path }}')"><i class="bi bi-play-fill me-1"></i>{{ _('Print Now') }}</button>
|
||||
<!-- <button class="btn btn-sm btn-outline-secondary rounded-pill ms-2" onclick="selectFile('{{ f.origin }}', '{{ f.path }}')">{{ _('Select') }}</button> -->
|
||||
</div>
|
||||
@@ -59,6 +81,54 @@ function printFile(origin, path) {
|
||||
.catch(err => window.customAlert("Error: " + err));
|
||||
});
|
||||
}
|
||||
|
||||
function uploadExternalGcode(input) {
|
||||
if (!input.files || input.files.length === 0) return;
|
||||
|
||||
let file = input.files[0];
|
||||
let formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
window.showToast("{{ _('Uploading and linking GCode...') }}", "info");
|
||||
let btn = document.querySelector('button[onclick="document.getElementById(\'gcodeUploadInput\').click();"]');
|
||||
let oldBtnHtml = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Upload/Sync...';
|
||||
btn.disabled = true;
|
||||
|
||||
fetch("{{ url_for('printer.upload_gcode') }}", {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if(data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.customAlert("Upload failed: " + data.error);
|
||||
btn.innerHTML = oldBtnHtml;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}).catch(e => {
|
||||
window.customAlert("Error: " + e);
|
||||
btn.innerHTML = oldBtnHtml;
|
||||
btn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
if(window.location.hash) {
|
||||
let el = document.querySelector(window.location.hash);
|
||||
if(el) {
|
||||
setTimeout(() => {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('animate-blink');
|
||||
|
||||
// fallback to remove class later
|
||||
setTimeout(() => el.classList.remove('animate-blink'), 3500);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -39,12 +39,15 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for file in files %}
|
||||
<tr id="file-row-{{ file.id }}" data-status="{{ file.status }}">
|
||||
{% set is_gcode = file.original_filename.lower().endswith('.gcode') or file.original_filename.lower().endswith('.gco') or file.original_filename.lower().endswith('.g') %}
|
||||
<tr id="file-row-{{ file.id }}" data-status="{{ file.status }}" data-is-gcode="{{ 'true' if is_gcode else 'false' }}">
|
||||
<td class="ps-4 text-muted">
|
||||
<i class="bi bi-clock me-1"></i>
|
||||
<span class="local-time" data-utc="{{ file.created_at.isoformat() }}">{{ file.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</span>
|
||||
</td>
|
||||
<td class="fw-medium">{{ file.original_filename }}</td>
|
||||
<td class="fw-medium">
|
||||
<i class="bi {{ 'bi-file-earmark-code text-success' if is_gcode else 'bi-box text-primary' }} me-2"></i>{{ file.original_filename }}
|
||||
</td>
|
||||
<td id="status-{{ file.id }}">
|
||||
{% if file.status == 'waiting' %}
|
||||
<span class="badge bg-info text-dark rounded-pill fw-normal px-2" title="{{ _('Waiting in queue for slicing') }}"><i class="bi bi-hourglass-split me-1"></i>{{ _('Waiting') }}...</span>
|
||||
@@ -64,7 +67,9 @@
|
||||
</td>
|
||||
<td class="pe-4">
|
||||
<div class="d-flex gap-2" id="actions-container-{{ file.id }}">
|
||||
{% if not is_gcode %}
|
||||
<a href="{{ url_for('main.plater') }}?add={{ file.id }}" class="btn btn-sm btn-outline-warning shadow-sm" title="{{ _('Slice') }}"><i class="bi bi-braces"></i> {{ _('Slice') }}</a>
|
||||
{% endif %}
|
||||
{% if file.status == 'sliced' %}
|
||||
<a href="{{ url_for('main.download_gcode', file_id=file.id) }}" class="btn btn-sm btn-outline-primary shadow-sm" title="{{ _('Download GCode') }}"><i class="bi bi-download"></i></a>
|
||||
<a href="{{ url_for('main.preview_gcode', file_id=file.id) }}" class="btn btn-sm btn-outline-info shadow-sm" title="{{ _('GCode Preview') }}"><i class="bi bi-eye"></i></a>
|
||||
@@ -138,7 +143,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
let actionsHtml = '';
|
||||
const platerUrl = `{{ url_for('main.plater') }}?add=${id}`;
|
||||
actionsHtml += `<a href="${platerUrl}" class="btn btn-sm btn-outline-warning shadow-sm" title="{{ _('Slice') }}"><i class="bi bi-braces"></i> {{ _('Slice') }}</a>\n`;
|
||||
const isGcode = tr.getAttribute('data-is-gcode') === 'true';
|
||||
if (!isGcode) {
|
||||
actionsHtml += `<a href="${platerUrl}" class="btn btn-sm btn-outline-warning shadow-sm" title="{{ _('Slice') }}"><i class="bi bi-braces"></i> {{ _('Slice') }}</a>\n`;
|
||||
}
|
||||
|
||||
if (status === 'sliced') {
|
||||
const downloadUrl = `{{ url_for('main.download_gcode', file_id=999999999) }}`.replace('999999999', id);
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
<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"><i class="bi bi-eye text-primary me-2"></i>{{ _('GCode Preview') }}: {{ file.original_filename }}</h1>
|
||||
<div>
|
||||
<a href="{{ url_for('main.download_gcode', file_id=file.id) }}" class="btn btn-primary btn-sm rounded shadow-sm"><i class="bi bi-download"></i> {{ _('Download GCode') }}</a>
|
||||
<a href="{{ url_for('printer.prepare') }}#file-{{ file.id }}" class="btn btn-warning btn-sm rounded shadow-sm fw-bold"><i class="bi bi-printer"></i> {{ _('Go to Print') }}</a>
|
||||
<a href="{{ url_for('main.download_gcode', file_id=file.id) }}" class="btn btn-primary btn-sm rounded shadow-sm ms-2"><i class="bi bi-download"></i> {{ _('Download GCode') }}</a>
|
||||
<a href="{{ url_for('main.files') }}" class="btn btn-outline-secondary btn-sm rounded ms-2 shadow-sm"><i class="bi bi-arrow-left"></i> {{ _('Back') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,50 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<!-- STL Files Stats -->
|
||||
<div class="col-md-6">
|
||||
<div class="card text-white bg-primary mb-3 shadow-sm border-0">
|
||||
<div class="card-header border-0 fs-5 fw-medium"><i class="bi bi-bar-chart-fill me-2"></i>{{ _('Total Prints') }}</div>
|
||||
<div class="card-header border-0 fs-5 fw-medium">
|
||||
<i class="bi bi-box me-2"></i>{{ _('3D Model Files (STL)') }}
|
||||
</div>
|
||||
<div class="card-body mt-2">
|
||||
<h5 class="card-title">{{ _('You have sliced') }} <b class="fs-1 mx-2">{{ current_user.print_files|length }}</b> {{ _('files') }}</h5>
|
||||
<h5 class="card-title mb-3">
|
||||
{{ _('You have uploaded') }} <b class="fs-1 mx-2">{{ stl_count }}</b> {{ _('files') }}
|
||||
</h5>
|
||||
<p class="card-text mb-2">
|
||||
<i class="bi bi-hdd-fill me-1"></i>{{ _('Total Space Used') }}: <strong>{{ format_size(stl_used_bytes) }}</strong>
|
||||
{% if stl_quota_mb > 0 %} / <strong>{{ stl_quota_mb }} MB</strong> <small class="opacity-75">({{ _('Quota') }})</small>{% else %} <small class="opacity-75">({{ _('Unlimited') }})</small>{% endif %}
|
||||
</p>
|
||||
{% if stl_quota_mb > 0 %}
|
||||
{% set stl_percent = (stl_used_bytes / (stl_quota_mb * 1024 * 1024) * 100)|round(1) %}
|
||||
<div class="progress bg-white bg-opacity-25" style="height: 8px;">
|
||||
<div class="progress-bar {% if stl_percent > 90 %}bg-danger{% elif stl_percent > 75 %}bg-warning{% else %}bg-info{% endif %}" role="progressbar" style="width: {{ stl_percent if stl_percent <= 100 else 100 }}%"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GCode Files Stats -->
|
||||
<div class="col-md-6">
|
||||
<div class="card text-white bg-success mb-3 shadow-sm border-0">
|
||||
<div class="card-header border-0 fs-5 fw-medium">
|
||||
<i class="bi bi-file-earmark-code me-2"></i>{{ _('Sliced Files (GCode)') }}
|
||||
</div>
|
||||
<div class="card-body mt-2">
|
||||
<h5 class="card-title mb-3">
|
||||
{{ _('You have sliced or uploaded') }} <b class="fs-1 mx-2">{{ gcode_count }}</b> {{ _('files') }}
|
||||
</h5>
|
||||
<p class="card-text mb-2">
|
||||
<i class="bi bi-hdd-network-fill me-1"></i>{{ _('Total Space Used') }}: <strong>{{ format_size(gcode_used_bytes) }}</strong>
|
||||
{% if gcode_quota_mb > 0 %} / <strong>{{ gcode_quota_mb }} MB</strong> <small class="opacity-75">({{ _('Quota') }})</small>{% else %} <small class="opacity-75">({{ _('Unlimited') }})</small>{% endif %}
|
||||
</p>
|
||||
{% if gcode_quota_mb > 0 %}
|
||||
{% set gc_percent = (gcode_used_bytes / (gcode_quota_mb * 1024 * 1024) * 100)|round(1) %}
|
||||
<div class="progress bg-white bg-opacity-25" style="height: 8px;">
|
||||
<div class="progress-bar {% if gc_percent > 90 %}bg-danger{% elif gc_percent > 75 %}bg-warning{% else %}bg-info{% endif %}" role="progressbar" style="width: {{ gc_percent if gc_percent <= 100 else 100 }}%"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -148,6 +148,11 @@
|
||||
<script>
|
||||
// Toggle icons on collapse
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
{% if quota_exceeded %}
|
||||
window.customConfirm("{{ _('GCode Storage Quota Exceeded. Please delete some files first.') }}",()=>{window.location.href = "{{ url_for('main.files') }}"});
|
||||
return;
|
||||
{% endif %}
|
||||
|
||||
const cards = document.querySelectorAll('.collapse');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('show.bs.collapse', function () {
|
||||
@@ -896,7 +901,11 @@ function mergeAndSlice() {
|
||||
if(data.success) {
|
||||
window.location.href = "{{ url_for('main.files') }}";
|
||||
} else {
|
||||
window.customAlert("{{ _('Error:') }} " + data.error);
|
||||
let errorMsg = data.error;
|
||||
if (errorMsg === 'GCode Storage Quota Exceeded. Please delete some files first.') {
|
||||
errorMsg = "{{ _('GCode Storage Quota Exceeded. Please delete some files first.') }}";
|
||||
}
|
||||
window.customAlert("{{ _('Error:') }} " + errorMsg);
|
||||
btn.disabled = false;
|
||||
icon.className = 'bi bi-gear-fill me-2';
|
||||
text.innerText = '{{ _("Merge & Slice") }}';
|
||||
|
||||
Reference in New Issue
Block a user