lhye200
This commit is contained in:
11
app.log
11
app.log
@@ -1,11 +0,0 @@
|
|||||||
Admin already exists.
|
|
||||||
* Serving Flask app 'app'
|
|
||||||
* Debug mode: on
|
|
||||||
[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
|
||||||
* Running on all addresses (0.0.0.0)
|
|
||||||
* Running on http://127.0.0.1:5001
|
|
||||||
* Running on http://192.168.1.115:5001
|
|
||||||
[33mPress CTRL+C to quit[0m
|
|
||||||
* Restarting with stat
|
|
||||||
* Debugger is active!
|
|
||||||
* Debugger PIN: 837-836-472
|
|
||||||
@@ -26,6 +26,15 @@ class PrintFile(db.Model):
|
|||||||
status = db.Column(db.String(50), default='waiting') # waiting, slicing, sliced, failed
|
status = db.Column(db.String(50), default='waiting') # waiting, slicing, sliced, failed
|
||||||
transform_matrix = db.Column(db.Text, nullable=True) # json format of 16-element array
|
transform_matrix = db.Column(db.Text, nullable=True) # json format of 16-element array
|
||||||
|
|
||||||
|
class UserSession(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
session_token = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
|
ip_address = db.Column(db.String(50))
|
||||||
|
user_agent = db.Column(db.String(255))
|
||||||
|
last_active = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
is_active = db.Column(db.Boolean, default=True)
|
||||||
|
|
||||||
class SystemConfig(db.Model):
|
class SystemConfig(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
key = db.Column(db.String(50), unique=True, nullable=False)
|
key = db.Column(db.String(50), unique=True, nullable=False)
|
||||||
|
|||||||
@@ -32,9 +32,28 @@ def login():
|
|||||||
remember = bool(request.form.get('remember'))
|
remember = bool(request.form.get('remember'))
|
||||||
merge_data = bool(request.form.get('merge_data'))
|
merge_data = bool(request.form.get('merge_data'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if user and check_password_hash(user.password_hash, password):
|
if user and check_password_hash(user.password_hash, password):
|
||||||
login_user(user, remember=remember)
|
login_user(user, remember=remember)
|
||||||
|
|
||||||
|
from app.models import UserSession
|
||||||
|
session_token = str(uuid.uuid4())
|
||||||
|
# 尝试获取反向代理传递的真实 IP
|
||||||
|
client_ip = request.headers.get('X-Real-IP')
|
||||||
|
if not client_ip:
|
||||||
|
client_ip = request.remote_addr
|
||||||
|
|
||||||
|
user_session = UserSession(
|
||||||
|
user_id=user.id,
|
||||||
|
session_token=session_token,
|
||||||
|
ip_address=client_ip,
|
||||||
|
user_agent=request.user_agent.string
|
||||||
|
)
|
||||||
|
db.session.add(user_session)
|
||||||
|
db.session.commit()
|
||||||
|
session['user_session_token'] = session_token
|
||||||
|
|
||||||
if merge_data:
|
if merge_data:
|
||||||
guest_id = request.cookies.get('guest_id')
|
guest_id = request.cookies.get('guest_id')
|
||||||
if guest_id:
|
if guest_id:
|
||||||
@@ -110,10 +129,20 @@ def login():
|
|||||||
flash('Invalid username or password', 'danger')
|
flash('Invalid username or password', 'danger')
|
||||||
return render_template('auth/login.html')
|
return render_template('auth/login.html')
|
||||||
|
|
||||||
|
|
||||||
@auth_bp.route('/logout')
|
@auth_bp.route('/logout')
|
||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
|
session_token = session.get('user_session_token')
|
||||||
|
if session_token:
|
||||||
|
from app.models import UserSession
|
||||||
|
user_session = UserSession.query.filter_by(session_token=session_token).first()
|
||||||
|
if user_session:
|
||||||
|
user_session.is_active = False
|
||||||
|
db.session.commit()
|
||||||
logout_user()
|
logout_user()
|
||||||
|
session.pop('user_session_token', None)
|
||||||
|
|
||||||
response = make_response(redirect(url_for('main.index')))
|
response = make_response(redirect(url_for('main.index')))
|
||||||
response.delete_cookie('guest_id') # Optionally clear guest cookie
|
response.delete_cookie('guest_id') # Optionally clear guest cookie
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -18,6 +18,24 @@ from app.routes.admin_routes import get_gcode_dir
|
|||||||
|
|
||||||
main_bp = Blueprint('main', __name__)
|
main_bp = Blueprint('main', __name__)
|
||||||
|
|
||||||
|
@main_bp.before_app_request
|
||||||
|
def check_user_session():
|
||||||
|
if current_user.is_authenticated and not current_user.is_guest:
|
||||||
|
from app.models import UserSession
|
||||||
|
session_token = session.get('user_session_token')
|
||||||
|
if session_token:
|
||||||
|
user_session = UserSession.query.filter_by(session_token=session_token).first()
|
||||||
|
if not user_session or not user_session.is_active:
|
||||||
|
from flask_login import logout_user
|
||||||
|
logout_user()
|
||||||
|
session.pop('user_session_token', None)
|
||||||
|
flash('Your session has been terminated.', 'warning')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
else:
|
||||||
|
user_session.last_active = datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||||
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||||
@@ -562,3 +580,52 @@ def engine_options(engine_name):
|
|||||||
patterns = engine.get_support_patterns(current_app)
|
patterns = engine.get_support_patterns(current_app)
|
||||||
materials = engine.get_materials(current_app) if hasattr(engine, 'get_materials') else []
|
materials = engine.get_materials(current_app) if hasattr(engine, 'get_materials') else []
|
||||||
return jsonify({'presets': presets, 'support_patterns': patterns, 'materials': materials})
|
return jsonify({'presets': presets, 'support_patterns': patterns, 'materials': materials})
|
||||||
|
|
||||||
|
@main_bp.route('/account', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def account():
|
||||||
|
if current_user.is_guest:
|
||||||
|
flash('Guests cannot manage accounts.', 'danger')
|
||||||
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
|
from app.models import UserSession
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
action = request.form.get('action')
|
||||||
|
|
||||||
|
if action == 'change_password':
|
||||||
|
current_pass = request.form.get('current_password')
|
||||||
|
new_pass = request.form.get('new_password')
|
||||||
|
confirm_pass = request.form.get('confirm_password')
|
||||||
|
|
||||||
|
if not check_password_hash(current_user.password_hash, current_pass):
|
||||||
|
flash('Current password is incorrect.', 'danger')
|
||||||
|
elif new_pass != confirm_pass:
|
||||||
|
flash('New passwords do not match.', 'danger')
|
||||||
|
elif len(new_pass) < 6:
|
||||||
|
flash('New password must be at least 6 characters.', 'danger')
|
||||||
|
else:
|
||||||
|
current_user.password_hash = generate_password_hash(new_pass)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Password updated successfully.', 'success')
|
||||||
|
|
||||||
|
elif action == 'terminate_session':
|
||||||
|
session_id = request.form.get('session_id')
|
||||||
|
token_to_terminate = request.form.get('session_token')
|
||||||
|
|
||||||
|
my_session_token = session.get('user_session_token')
|
||||||
|
if token_to_terminate == my_session_token:
|
||||||
|
flash('You cannot terminate your current session from here. Please logout instead.', 'warning')
|
||||||
|
else:
|
||||||
|
us = UserSession.query.filter_by(id=session_id, user_id=current_user.id).first()
|
||||||
|
if us:
|
||||||
|
us.is_active = False
|
||||||
|
db.session.commit()
|
||||||
|
flash('Session terminated.', 'success')
|
||||||
|
|
||||||
|
return redirect(url_for('main.account'))
|
||||||
|
|
||||||
|
sessions = UserSession.query.filter_by(user_id=current_user.id, is_active=True).order_by(UserSession.last_active.desc()).all()
|
||||||
|
current_token = session.get('user_session_token')
|
||||||
|
|
||||||
|
return render_template('slice/account.html', sessions=sessions, current_token=current_token)
|
||||||
|
|||||||
@@ -107,6 +107,13 @@
|
|||||||
<i class="bi bi-arrows-move me-2"></i>{{ _('Control') }}
|
<i class="bi bi-arrows-move me-2"></i>{{ _('Control') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if current_user.is_authenticated and not current_user.is_guest %}
|
||||||
|
<li class="nav-item mb-1">
|
||||||
|
<a class="nav-link text-dark {% if request.endpoint == 'main.account' %}active text-white shadow-sm{% endif %}" href="{{ url_for('main.account') }}">
|
||||||
|
<i class="bi bi-person-badge me-2"></i>{{ _('Account Management') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||||
@@ -147,6 +154,13 @@
|
|||||||
<i class="bi bi-grid-3x3 me-2"></i>{{ _('Plater') }}
|
<i class="bi bi-grid-3x3 me-2"></i>{{ _('Plater') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if current_user.is_authenticated and not current_user.is_guest %}
|
||||||
|
<li class="nav-item mb-1">
|
||||||
|
<a class="nav-link text-dark {% if request.endpoint == 'main.account' %}active text-white shadow-sm{% endif %}" href="{{ url_for('main.account') }}">
|
||||||
|
<i class="bi bi-person-badge me-2"></i>{{ _('Account Management') }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||||
|
|||||||
99
app/templates/slice/account.html
Normal file
99
app/templates/slice/account.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row g-4 mt-1">
|
||||||
|
<div class="col-12 d-flex justify-content-between align-items-center">
|
||||||
|
<h4 class="mb-0 fw-bold"><i class="bi bi-person-badge me-2"></i>{{ _('Account Management') }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password Change Section -->
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header bg-white pt-3 pb-2 border-bottom-0">
|
||||||
|
<h5 class="card-title fw-bold text-primary mb-0"><i class="bi bi-shield-lock me-2"></i>{{ _('Change Password') }}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ url_for('main.account') }}" method="POST">
|
||||||
|
<input type="hidden" name="action" value="change_password">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">{{ _('Current Password') }}</label>
|
||||||
|
<input type="password" name="current_password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">{{ _('New Password') }}</label>
|
||||||
|
<input type="password" name="new_password" class="form-control" required minlength="6">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label text-muted small fw-bold">{{ _('Confirm New Password') }}</label>
|
||||||
|
<input type="password" name="confirm_password" class="form-control" required minlength="6">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100 fw-bold rounded-pill"><i class="bi bi-check-circle me-2"></i>{{ _('Update Password') }}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Sessions Section -->
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="card shadow-sm border-0 h-100">
|
||||||
|
<div class="card-header bg-white pt-3 pb-2 border-bottom-0 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="card-title fw-bold text-success mb-0"><i class="bi bi-laptop me-2"></i>{{ _('Active Sessions') }}</h5>
|
||||||
|
<span class="badge bg-success rounded-pill">{{ sessions|length }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-borderless align-middle mb-0">
|
||||||
|
<thead class="table-light text-muted small">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4 fw-normal">{{ _('Device') }}</th>
|
||||||
|
<th class="fw-normal">{{ _('IP Address') }}</th>
|
||||||
|
<th class="fw-normal">{{ _('Last Active') }}</th>
|
||||||
|
<th class="text-end pe-4 fw-normal">{{ _('Action') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for s in sessions %}
|
||||||
|
<tr class="{% if s.session_token == current_token %}bg-light{% endif %}">
|
||||||
|
<td class="ps-4">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bi bi-display me-2 text-secondary fs-5"></i>
|
||||||
|
<div>
|
||||||
|
<div class="text-truncate" style="max-width: 150px" title="{{ s.user_agent }}">{{ s.user_agent.split(' ')[0] if s.user_agent else _('Unknown Device') }}</div>
|
||||||
|
{% if s.session_token == current_token %}
|
||||||
|
<span class="badge bg-primary mt-1">{{ _('This Device') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-secondary font-monospace">{{ s.ip_address }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small class="text-muted">{{ s.last_active.strftime('%Y-%m-%d %H:%M:%S') }}</small>
|
||||||
|
</td>
|
||||||
|
<td class="text-end pe-4">
|
||||||
|
{% if s.session_token != current_token %}
|
||||||
|
<form action="{{ url_for('main.account') }}" method="POST" class="d-inline" id="form-{{ s.id }}">
|
||||||
|
<input type="hidden" name="action" value="terminate_session">
|
||||||
|
<input type="hidden" name="session_id" value="{{ s.id }}">
|
||||||
|
<input type="hidden" name="session_token" value="{{ s.session_token }}">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger px-3 rounded-pill" onclick="customConfirm('{{ _('Are you sure you want to terminate this session?') }}', () => document.getElementById('form-{{ s.id }}').submit());"><i class="bi bi-x-octagon me-1"></i>{{ _('Logout') }}</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary px-3 rounded-pill" onclick="customConfirm('{{ _('Logout from this device?') }}', () => window.location.href='{{ url_for('auth.logout') }}');"><i class="bi bi-box-arrow-right me-1"></i>{{ _('Logout') }}</button>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-4 text-muted">{{ _('No active sessions found.') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
# 大模型语义映射表 (Creality/Bambu -> PrusaSlicer)
|
|
||||||
SEMANTIC_MAP = {
|
|
||||||
"outer_wall_line_width": "external_perimeter_extrusion_width",
|
|
||||||
"outer_wall_speed": "external_perimeter_speed",
|
|
||||||
"line_width": "extrusion_width",
|
|
||||||
"infill_direction": "fill_angle",
|
|
||||||
"sparse_infill_density": "fill_density",
|
|
||||||
"sparse_infill_pattern": "fill_pattern",
|
|
||||||
"initial_layer_line_width": "first_layer_extrusion_width",
|
|
||||||
"initial_layer_print_height": "first_layer_height",
|
|
||||||
"initial_layer_speed": "first_layer_speed",
|
|
||||||
"gap_infill_speed": "gap_fill_speed",
|
|
||||||
"infill_wall_overlap": "infill_overlap",
|
|
||||||
"sparse_infill_speed": "infill_speed",
|
|
||||||
"initial_layer_acceleration": "first_layer_acceleration",
|
|
||||||
"travel_speed": "travel_speed",
|
|
||||||
"bottom_shell_layers": "bottom_solid_layers",
|
|
||||||
"top_shell_layers": "top_solid_layers",
|
|
||||||
"top_surface_speed": "top_solid_infill_speed",
|
|
||||||
"layer_height": "layer_height",
|
|
||||||
"wall_loops": "perimeters",
|
|
||||||
"inner_wall_speed": "perimeter_speed",
|
|
||||||
"raft_layers": "raft_layers",
|
|
||||||
"brim_width": "brim_width",
|
|
||||||
"print_sequence": "complete_objects",
|
|
||||||
"elefant_foot_compensation": "elefant_foot_compensation",
|
|
||||||
"nozzle_temperature": "temperature",
|
|
||||||
"first_layer_bed_temperature": "first_layer_bed_temperature",
|
|
||||||
"bed_temperature": "bed_temperature",
|
|
||||||
"filament_diameter": "filament_diameter",
|
|
||||||
"support_material": "support_material",
|
|
||||||
"support_material_style": "support_material_style",
|
|
||||||
"retract_length": "retract_length",
|
|
||||||
"retract_speed": "retract_speed",
|
|
||||||
"z_hop": "retract_lift"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 从 prusa_new_cli.txt (或 Prusa 官方默认配置) 中允许通过的原生参数白名单
|
|
||||||
NATIVE_ALLOWED = {
|
|
||||||
"bridge_speed", "bridge_flow", "default_acceleration", "brim_object_gap",
|
|
||||||
"ironing_type", "filament_cost", "filament_density", "filament_type",
|
|
||||||
"gcode_flavor", "nozzle_diameter", "start_gcode", "end_gcode",
|
|
||||||
"before_layer_gcode", "printer_model", "z_offset"
|
|
||||||
}
|
|
||||||
|
|
||||||
def process_file(filepath):
|
|
||||||
with open(filepath, 'r') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
new_lines = []
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
stripped = line.strip()
|
|
||||||
if not stripped or stripped.startswith('[') or stripped.startswith(';'):
|
|
||||||
new_lines.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if '=' in line:
|
|
||||||
parts = line.split('=', 1)
|
|
||||||
key = parts[0].strip()
|
|
||||||
val = parts[1].strip()
|
|
||||||
|
|
||||||
# 处理特殊语义转换值
|
|
||||||
if key == "print_sequence" and val == "by layer":
|
|
||||||
val = "0"
|
|
||||||
elif key == "print_sequence" and val == "by object":
|
|
||||||
val = "1"
|
|
||||||
|
|
||||||
if key in SEMANTIC_MAP:
|
|
||||||
new_key = SEMANTIC_MAP[key]
|
|
||||||
new_lines.append(f"{new_key} = {val}\n")
|
|
||||||
changed = True
|
|
||||||
elif key in NATIVE_ALLOWED:
|
|
||||||
new_lines.append(f"{key} = {val}\n")
|
|
||||||
else:
|
|
||||||
new_lines.append(f";;;{line}")
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
new_lines.append(line)
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
with open(filepath, 'w') as f:
|
|
||||||
f.writelines(new_lines)
|
|
||||||
print(f"Applied semantic map to {filepath}")
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk('print_config/prusa_slicer'):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.ini'):
|
|
||||||
process_file(os.path.join(root, file))
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user