补充遗漏翻译,新增启动脚本,整理import
This commit is contained in:
@@ -260,5 +260,14 @@
|
|||||||
"No active sessions found.": "Keine aktiven Sitzungen gefunden.",
|
"No active sessions found.": "Keine aktiven Sitzungen gefunden.",
|
||||||
"Please login to view the webcam stream.": "Bitte melden Sie sich an, um die Live-Kamera zu sehen.",
|
"Please login to view the webcam stream.": "Bitte melden Sie sich an, um die Live-Kamera zu sehen.",
|
||||||
"Remember Me": "Erinnere dich an mich",
|
"Remember Me": "Erinnere dich an mich",
|
||||||
"Merge Guest Data": "Gästendaten zusammenführen"
|
"Merge Guest Data": "Gästendaten zusammenführen",
|
||||||
|
"Main configuration for the printer dimensions, limits and base profiles.": "Hauptkonfiguration für die Druckerabmessungen, -grenzen und Basisprofile.",
|
||||||
|
"API Keys Management": "API-Schlüsselverwaltung",
|
||||||
|
"Create New API Key": "Neuen API-Schlüssel erstellen",
|
||||||
|
"Key Name": "Schlüsselname",
|
||||||
|
"Generate Key": "Schlüssel generieren",
|
||||||
|
"Are you sure you want to delete this API Key?": "Sind Sie sicher, dass Sie diesen API-Schlüssel löschen möchten?",
|
||||||
|
"API Key Name": "API-Schlüsselname",
|
||||||
|
"No API keys found.": "Keine API-Schlüssel gefunden.",
|
||||||
|
"API Keys": "API-Schlüssel"
|
||||||
}
|
}
|
||||||
@@ -260,5 +260,14 @@
|
|||||||
"No active sessions found.": "No active sessions found.",
|
"No active sessions found.": "No active sessions found.",
|
||||||
"Please login to view the webcam stream.": "Please login to view the webcam stream.",
|
"Please login to view the webcam stream.": "Please login to view the webcam stream.",
|
||||||
"Remember Me": "Remember Me",
|
"Remember Me": "Remember Me",
|
||||||
"Merge Guest Data": "Merge Guest Data"
|
"Merge Guest Data": "Merge Guest Data",
|
||||||
|
"Main configuration for the printer dimensions, limits and base profiles.": "Main configuration for the printer dimensions, limits and base profiles.",
|
||||||
|
"API Keys Management": "API Keys Management",
|
||||||
|
"Create New API Key": "Create New API Key",
|
||||||
|
"Key Name": "Key Name",
|
||||||
|
"Generate Key": "Generate Key",
|
||||||
|
"Are you sure you want to delete this API Key?": "Are you sure you want to delete this API Key?",
|
||||||
|
"API Key Name": "API Key Name",
|
||||||
|
"No API keys found.": "No API keys found.",
|
||||||
|
"API Keys": "API Keys"
|
||||||
}
|
}
|
||||||
@@ -260,5 +260,14 @@
|
|||||||
"No active sessions found.": "未找到活跃的会话。",
|
"No active sessions found.": "未找到活跃的会话。",
|
||||||
"Please login to view the webcam stream.": "请登录以查看实时摄像头。",
|
"Please login to view the webcam stream.": "请登录以查看实时摄像头。",
|
||||||
"Remember Me": "记住我",
|
"Remember Me": "记住我",
|
||||||
"Merge Guest Data": "合并访客数据"
|
"Merge Guest Data": "合并访客数据",
|
||||||
|
"Main configuration for the printer dimensions, limits and base profiles.": "打印机尺寸、限制和基础配置的主要配置。",
|
||||||
|
"API Keys Management": "API 密钥管理",
|
||||||
|
"Create New API Key": "创建新的 API 密钥",
|
||||||
|
"Key Name": "密钥名称",
|
||||||
|
"Generate Key": "生成密钥",
|
||||||
|
"Are you sure you want to delete this API Key?": "您确定要删除此 API 密钥吗?",
|
||||||
|
"API Key Name": "API 密钥名称",
|
||||||
|
"No API keys found.": "未找到 API 密钥。",
|
||||||
|
"API Keys": "API 密钥"
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
import json
|
import json
|
||||||
import trimesh
|
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
|
import secrets
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, session, make_response, send_file, abort, jsonify
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, session, make_response, send_file, abort, jsonify
|
||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from app.models import db, User, PrintFile, SystemConfig
|
from app.models import db, User, PrintFile, SystemConfig, ApiKey
|
||||||
from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_task
|
from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_task
|
||||||
from app import i18n_dict
|
from app import i18n_dict
|
||||||
# import trimesh.repair
|
|
||||||
from app.utils.stl_simplifier import simplify_stl
|
from app.utils.stl_simplifier import simplify_stl
|
||||||
from app.utils.slice_engines import get_all_engines
|
from app.utils.slice_engines import get_all_engines
|
||||||
|
|
||||||
@@ -194,14 +193,11 @@ def delete_user(user_id):
|
|||||||
|
|
||||||
@admin_bp.route('/api_keys')
|
@admin_bp.route('/api_keys')
|
||||||
def api_keys():
|
def api_keys():
|
||||||
from app.models import ApiKey
|
|
||||||
keys = ApiKey.query.order_by(ApiKey.created_at.desc()).all()
|
keys = ApiKey.query.order_by(ApiKey.created_at.desc()).all()
|
||||||
return render_template('admin/api_keys.html', keys=keys)
|
return render_template('admin/api_keys.html', keys=keys)
|
||||||
|
|
||||||
@admin_bp.route('/api_key/add', methods=['POST'])
|
@admin_bp.route('/api_key/add', methods=['POST'])
|
||||||
def add_api_key():
|
def add_api_key():
|
||||||
from app.models import ApiKey
|
|
||||||
import secrets
|
|
||||||
name = request.form.get('name')
|
name = request.form.get('name')
|
||||||
if not name:
|
if not name:
|
||||||
flash("Name is required", "danger")
|
flash("Name is required", "danger")
|
||||||
@@ -216,7 +212,6 @@ def add_api_key():
|
|||||||
|
|
||||||
@admin_bp.route('/api_key/<int:key_id>/delete', methods=['POST'])
|
@admin_bp.route('/api_key/<int:key_id>/delete', methods=['POST'])
|
||||||
def delete_api_key(key_id):
|
def delete_api_key(key_id):
|
||||||
from app.models import ApiKey
|
|
||||||
key = ApiKey.query.get_or_404(key_id)
|
key = ApiKey.query.get_or_404(key_id)
|
||||||
db.session.delete(key)
|
db.session.delete(key)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import trimesh
|
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
@@ -8,11 +7,12 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
|
|||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from app.models import db, User, PrintFile, SystemConfig
|
from app.models import db, User, PrintFile, SystemConfig, UserSession
|
||||||
from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_task
|
from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_task
|
||||||
from app import i18n_dict
|
from app import i18n_dict
|
||||||
# import trimesh.repair
|
|
||||||
from app.utils.stl_simplifier import simplify_stl
|
from app.utils.stl_simplifier import simplify_stl
|
||||||
|
from app.routes.main_routes import get_quota_info
|
||||||
|
from app.routes.admin_routes import get_gcode_dir
|
||||||
|
|
||||||
|
|
||||||
main_bp = Blueprint('main', __name__)
|
main_bp = Blueprint('main', __name__)
|
||||||
@@ -36,8 +36,6 @@ def login():
|
|||||||
|
|
||||||
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())
|
session_token = str(uuid.uuid4())
|
||||||
# 尝试获取反向代理传递的真实 IP
|
# 尝试获取反向代理传递的真实 IP
|
||||||
client_ip = request.headers.get('X-Real-IP')
|
client_ip = request.headers.get('X-Real-IP')
|
||||||
@@ -59,7 +57,6 @@ def login():
|
|||||||
if guest_id:
|
if guest_id:
|
||||||
guest_user = User.query.filter_by(guest_cookie_id=guest_id, is_guest=True).first()
|
guest_user = User.query.filter_by(guest_cookie_id=guest_id, is_guest=True).first()
|
||||||
if guest_user:
|
if guest_user:
|
||||||
from app.routes.main_routes import get_quota_info
|
|
||||||
guest_files = PrintFile.query.filter_by(user_id=guest_user.id).all()
|
guest_files = PrintFile.query.filter_by(user_id=guest_user.id).all()
|
||||||
|
|
||||||
stl_quota, stl_used = get_quota_info(user, 'stl')
|
stl_quota, stl_used = get_quota_info(user, 'stl')
|
||||||
@@ -68,7 +65,6 @@ def login():
|
|||||||
stl_quota_bytes = stl_quota * 1024 * 1024 if stl_quota > 0 else float('inf')
|
stl_quota_bytes = stl_quota * 1024 * 1024 if stl_quota > 0 else float('inf')
|
||||||
gcode_quota_bytes = gcode_quota * 1024 * 1024 if gcode_quota > 0 else float('inf')
|
gcode_quota_bytes = gcode_quota * 1024 * 1024 if gcode_quota > 0 else float('inf')
|
||||||
|
|
||||||
from app.routes.admin_routes import get_gcode_dir
|
|
||||||
upload_dir = current_app.config.get('UPLOAD_FOLDER', 'uploads')
|
upload_dir = current_app.config.get('UPLOAD_FOLDER', 'uploads')
|
||||||
gcode_dir = get_gcode_dir()
|
gcode_dir = get_gcode_dir()
|
||||||
|
|
||||||
@@ -135,7 +131,6 @@ def login():
|
|||||||
def logout():
|
def logout():
|
||||||
session_token = session.get('user_session_token')
|
session_token = session.get('user_session_token')
|
||||||
if session_token:
|
if session_token:
|
||||||
from app.models import UserSession
|
|
||||||
user_session = UserSession.query.filter_by(session_token=session_token).first()
|
user_session = UserSession.query.filter_by(session_token=session_token).first()
|
||||||
if user_session:
|
if user_session:
|
||||||
user_session.is_active = False
|
user_session.is_active = False
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import trimesh
|
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
@@ -8,15 +7,13 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
|
|||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from app.models import db, User, PrintFile, SystemConfig
|
from app.models import db, User, PrintFile, SystemConfig, UserSession
|
||||||
from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_task
|
from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_task
|
||||||
from app import i18n_dict
|
from app import i18n_dict
|
||||||
# import trimesh.repair
|
|
||||||
from app.utils.stl_simplifier import simplify_stl
|
from app.utils.stl_simplifier import simplify_stl
|
||||||
from app.routes.admin_routes import get_gcode_dir
|
from app.routes.admin_routes import get_gcode_dir
|
||||||
from app.utils.slice_engines import get_slicer_engine
|
from app.utils.slice_engines import get_slicer_engine
|
||||||
from app.models import UserSession
|
from app.utils.gcode_parser import get_gcode_metadata
|
||||||
from flask_login import logout_user
|
|
||||||
|
|
||||||
main_bp = Blueprint('main', __name__)
|
main_bp = Blueprint('main', __name__)
|
||||||
|
|
||||||
@@ -90,6 +87,8 @@ def check_quota(user, file_type, size_bytes):
|
|||||||
# Guest User Middleware
|
# Guest User Middleware
|
||||||
@main_bp.before_app_request
|
@main_bp.before_app_request
|
||||||
def assign_guest_cookie():
|
def assign_guest_cookie():
|
||||||
|
if request.path.startswith('/api/'):
|
||||||
|
return
|
||||||
if not current_user.is_authenticated:
|
if not current_user.is_authenticated:
|
||||||
guest_id = request.cookies.get('guest_id')
|
guest_id = request.cookies.get('guest_id')
|
||||||
if not guest_id:
|
if not guest_id:
|
||||||
@@ -304,7 +303,6 @@ def preview_gcode(file_id):
|
|||||||
filament_used = "-"
|
filament_used = "-"
|
||||||
|
|
||||||
if os.path.exists(filepath):
|
if os.path.exists(filepath):
|
||||||
from app.utils.gcode_parser import get_gcode_metadata
|
|
||||||
metadata = get_gcode_metadata(filepath)
|
metadata = get_gcode_metadata(filepath)
|
||||||
time_info = metadata.get('print_time', '-')
|
time_info = metadata.get('print_time', '-')
|
||||||
layer1_time = metadata.get('first_layer_time', '-')
|
layer1_time = metadata.get('first_layer_time', '-')
|
||||||
@@ -595,8 +593,6 @@ def account():
|
|||||||
flash('Guests cannot manage accounts.', 'danger')
|
flash('Guests cannot manage accounts.', 'danger')
|
||||||
return redirect(url_for('main.index'))
|
return redirect(url_for('main.index'))
|
||||||
|
|
||||||
from app.models import UserSession
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
action = request.form.get('action')
|
action = request.form.get('action')
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for, current_app, Response
|
import os
|
||||||
from flask_login import login_required, current_user
|
|
||||||
from websockets.sync.client import connect as ws_connect
|
|
||||||
import websockets.exceptions
|
import websockets.exceptions
|
||||||
import threading
|
import threading
|
||||||
import requests
|
import requests
|
||||||
from urllib.parse import urlparse
|
import uuid
|
||||||
from app.models import SystemConfig, db
|
import traceback
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for, current_app, Response
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from flask_sock import Server, ConnectionClosed
|
||||||
|
from websockets.sync.client import connect as ws_connect
|
||||||
|
from urllib.parse import urlparse, urlencode
|
||||||
|
from app.models import SystemConfig, db, PrintFile
|
||||||
from app.utils.octoprint_client import OctoPrintClient
|
from app.utils.octoprint_client import OctoPrintClient
|
||||||
from app.models import PrintFile
|
from app.utils.gcode_parser import get_gcode_metadata
|
||||||
import os
|
|
||||||
|
|
||||||
printer_bp = Blueprint('printer', __name__, url_prefix='/printer')
|
printer_bp = Blueprint('printer', __name__, url_prefix='/printer')
|
||||||
|
|
||||||
@@ -21,17 +25,17 @@ def get_octo_client():
|
|||||||
|
|
||||||
def _enrich_job_data(job_data):
|
def _enrich_job_data(job_data):
|
||||||
if job_data and job_data.get('job', {}).get('file', {}).get('name'):
|
if job_data and job_data.get('job', {}).get('file', {}).get('name'):
|
||||||
from app.models import PrintFile
|
|
||||||
internal_name = job_data['job']['file']['name']
|
internal_name = job_data['job']['file']['name']
|
||||||
internal_stl_name = str(internal_name)[:-5]+"stl"
|
internal_stl_name = str(internal_name)[:-5]+"stl"
|
||||||
user_files = {}
|
if current_user.is_authenticated and current_user.is_admin:
|
||||||
print_file = None
|
pf = PrintFile.query.filter_by(filename=internal_stl_name).first()
|
||||||
for f in PrintFile.query.filter_by(user_id=current_user.id, status='sliced').all():
|
elif current_user.is_authenticated:
|
||||||
user_files[f.filename] = f.original_filename
|
pf = PrintFile.query.filter_by(filename=internal_stl_name, user_id=current_user.id).first()
|
||||||
if internal_stl_name in user_files.keys():
|
else:
|
||||||
print_file = user_files[str(internal_name)[:-5]+"stl"]
|
pf = None
|
||||||
if print_file:
|
|
||||||
job_data['job']['file']['display_name'] = print_file
|
if pf:
|
||||||
|
job_data['job']['file']['display_name'] = pf.original_filename
|
||||||
else:
|
else:
|
||||||
job_data['job']['file']['display_name'] = internal_name
|
job_data['job']['file']['display_name'] = internal_name
|
||||||
return job_data
|
return job_data
|
||||||
@@ -81,9 +85,6 @@ def get_gcode_dir():
|
|||||||
@printer_bp.route('/prepare')
|
@printer_bp.route('/prepare')
|
||||||
@login_required
|
@login_required
|
||||||
def prepare():
|
def prepare():
|
||||||
from app.models import PrintFile
|
|
||||||
import os
|
|
||||||
from app.utils.gcode_parser import get_gcode_metadata
|
|
||||||
|
|
||||||
# Query only the sliced GCode files belonging to the current user
|
# Query only the sliced GCode files belonging to the current user
|
||||||
user_files = PrintFile.query.filter_by(user_id=current_user.id, status='sliced').order_by(PrintFile.created_at.desc()).all()
|
user_files = PrintFile.query.filter_by(user_id=current_user.id, status='sliced').order_by(PrintFile.created_at.desc()).all()
|
||||||
@@ -151,7 +152,6 @@ def prepare():
|
|||||||
|
|
||||||
|
|
||||||
def check_printer_control_permission(client):
|
def check_printer_control_permission(client):
|
||||||
from flask_login import current_user
|
|
||||||
if current_user.is_admin:
|
if current_user.is_admin:
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@@ -167,7 +167,6 @@ def check_printer_control_permission(client):
|
|||||||
if not internal_name:
|
if not internal_name:
|
||||||
return False, "现在有任务正在运行,非管理员无法进行控制。"
|
return False, "现在有任务正在运行,非管理员无法进行控制。"
|
||||||
|
|
||||||
from app.models import PrintFile
|
|
||||||
pf = PrintFile.query.filter_by(filename=internal_name).first()
|
pf = PrintFile.query.filter_by(filename=internal_name).first()
|
||||||
if pf and pf.user_id == current_user.id:
|
if pf and pf.user_id == current_user.id:
|
||||||
return True, None
|
return True, None
|
||||||
@@ -204,7 +203,6 @@ def control():
|
|||||||
try:
|
try:
|
||||||
raw_url = client.get_webcam_stream_url()
|
raw_url = client.get_webcam_stream_url()
|
||||||
# If it's an absolute url pointing to the base url, strip it or proxy it via octo_proxy
|
# If it's an absolute url pointing to the base url, strip it or proxy it via octo_proxy
|
||||||
from urllib.parse import urlparse, urlencode
|
|
||||||
parsed_raw = urlparse(raw_url)
|
parsed_raw = urlparse(raw_url)
|
||||||
base_config = SystemConfig.query.filter_by(key='octoprint_url').first()
|
base_config = SystemConfig.query.filter_by(key='octoprint_url').first()
|
||||||
if base_config and base_config.value:
|
if base_config and base_config.value:
|
||||||
@@ -256,11 +254,6 @@ def api_command():
|
|||||||
@printer_bp.route('/api/upload_gcode', methods=['POST'])
|
@printer_bp.route('/api/upload_gcode', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def upload_gcode():
|
def upload_gcode():
|
||||||
from app.models import PrintFile
|
|
||||||
import os
|
|
||||||
import uuid
|
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
|
|
||||||
if 'file' not in request.files:
|
if 'file' not in request.files:
|
||||||
return jsonify({"success": False, "error": "No file part"}), 400
|
return jsonify({"success": False, "error": "No file part"}), 400
|
||||||
|
|
||||||
@@ -366,8 +359,6 @@ def octo_proxy(path):
|
|||||||
|
|
||||||
# --- WebSocket Proxy Logic ---
|
# --- WebSocket Proxy Logic ---
|
||||||
if request.headers.get('Upgrade', '').lower() == 'websocket':
|
if request.headers.get('Upgrade', '').lower() == 'websocket':
|
||||||
from flask_sock import Server, ConnectionClosed
|
|
||||||
|
|
||||||
# Check if environment supports WebSockets
|
# Check if environment supports WebSockets
|
||||||
try:
|
try:
|
||||||
ws = Server(request.environ)
|
ws = Server(request.environ)
|
||||||
@@ -413,7 +404,6 @@ def octo_proxy(path):
|
|||||||
remote_ws = ws_connect(target_url, additional_headers=ws_headers)
|
remote_ws = ws_connect(target_url, additional_headers=ws_headers)
|
||||||
print("WS Proxy connected to remote.")
|
print("WS Proxy connected to remote.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print(f"Remote WS Connection Error: {e}")
|
print(f"Remote WS Connection Error: {e}")
|
||||||
ws.close(1011, str(e))
|
ws.close(1011, str(e))
|
||||||
@@ -473,7 +463,6 @@ def octo_proxy(path):
|
|||||||
return WebSocketResponse()
|
return WebSocketResponse()
|
||||||
|
|
||||||
# --- Standard HTTP Proxy Logic ---
|
# --- Standard HTTP Proxy Logic ---
|
||||||
# from urllib.parse import urlparse
|
|
||||||
target_url = f"{base_url}/{path}"
|
target_url = f"{base_url}/{path}"
|
||||||
|
|
||||||
if request.query_string:
|
if request.query_string:
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>API Keys Management</h2>
|
<h2>{{ _('API Keys Management') }}</h2>
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">Create New API Key</div>
|
<div class="card-header">{{ _('Create New API Key') }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="{{ url_for('admin.add_api_key') }}" method="POST" class="form-inline">
|
<form action="{{ url_for('admin.add_api_key') }}" method="POST" class="form-inline">
|
||||||
<input type="text" name="name" class="form-control mb-2 mr-sm-2" placeholder="Key Name" required>
|
<input type="text" name="name" class="form-control mb-2 mr-sm-2" placeholder="{{ _('Key Name') }}" required>
|
||||||
<button type="submit" class="btn btn-primary mb-2">Generate Key</button>
|
<button type="submit" class="btn btn-primary mb-2">{{ _('Generate Key') }}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>{{ _('ID') }}</th>
|
||||||
<th>Name</th>
|
<th>{{ _('API Key Name') }}</th>
|
||||||
<th>API Key</th>
|
<th>{{ _('API Key') }}</th>
|
||||||
<th>Created At</th>
|
<th>{{ _('Created At') }}</th>
|
||||||
<th>Action</th>
|
<th>{{ _('Action') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -32,14 +32,14 @@
|
|||||||
<td><code>{{ key.key }}</code></td>
|
<td><code>{{ key.key }}</code></td>
|
||||||
<td>{{ key.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
<td>{{ key.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||||
<td>
|
<td>
|
||||||
<form action="{{ url_for('admin.delete_api_key', key_id=key.id) }}" method="POST" style="display:inline;">
|
<form action="{{ url_for('admin.delete_api_key', key_id=key.id) }}" method="POST" style="display:inline;" onsubmit="event.preventDefault(); window.customConfirm('{{ _('Are you sure you want to delete this API Key?') }}', () => { this.submit(); });">
|
||||||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this API Key?');">Delete</button>
|
<button type="submit" class="btn btn-danger btn-sm">{{ _('Delete') }}</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center">No API keys found.</td>
|
<td colspan="5" class="text-center">{{ _('No API keys found.') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -204,7 +204,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-dark {% if request.endpoint == 'admin.api_keys' %}active text-white shadow-sm{% endif %}" href="{{ url_for('admin.api_keys') }}">
|
<a class="nav-link text-dark {% if request.endpoint == 'admin.api_keys' %}active text-white shadow-sm{% endif %}" href="{{ url_for('admin.api_keys') }}">
|
||||||
<i class="bi bi-key me-2"></i>API Keys
|
<i class="bi bi-key me-2"></i>{{ _('API Keys') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import functools
|
import functools
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from app.models import ApiKey
|
from app.models import ApiKey, PrintFile, SystemConfig
|
||||||
from app.utils.octoprint_client import OctoPrintClient
|
from app.utils.octoprint_client import OctoPrintClient
|
||||||
from app.models import SystemConfig
|
|
||||||
|
|
||||||
api_bp = Blueprint('api_handle', __name__, url_prefix='/api/v1')
|
api_bp = Blueprint('api_handle', __name__, url_prefix='/api/v1')
|
||||||
|
|
||||||
@@ -13,6 +12,17 @@ def get_octo_client():
|
|||||||
return OctoPrintClient(url.value, apikey.value)
|
return OctoPrintClient(url.value, apikey.value)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _enrich_job_data(job_data):
|
||||||
|
if job_data and job_data.get('job', {}).get('file', {}).get('name'):
|
||||||
|
internal_name = job_data['job']['file']['name']
|
||||||
|
internal_stl_name = str(internal_name)[:-5]+"stl"
|
||||||
|
pf = PrintFile.query.filter_by(filename=internal_stl_name).first()
|
||||||
|
if pf:
|
||||||
|
job_data['job']['file']['display_name'] = pf.original_filename
|
||||||
|
else:
|
||||||
|
job_data['job']['file']['display_name'] = internal_name
|
||||||
|
return job_data
|
||||||
|
|
||||||
def require_api_key(f):
|
def require_api_key(f):
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
@@ -30,15 +40,60 @@ def require_api_key(f):
|
|||||||
@api_bp.route('/status', methods=['GET'])
|
@api_bp.route('/status', methods=['GET'])
|
||||||
@require_api_key
|
@require_api_key
|
||||||
def get_status():
|
def get_status():
|
||||||
client = get_octo_client()
|
test_data = {
|
||||||
if not client:
|
'job': {
|
||||||
return jsonify({'error': 'Printer not configured'}), 503
|
'job': {
|
||||||
try:
|
'estimatedPrintTime': 1234,
|
||||||
status_data = client.get_printer_status()
|
'filament': {'length': 765, 'volume': 24356},
|
||||||
job_data = client.get_job_info()
|
'file': {'display_name': 'Test File','date': None, 'name': '20260414135441_42bff5215c6148b8b5f4d8c4f15d5ddc.gcode', 'origin': 'local', 'path': None, 'size': 1468987},
|
||||||
return jsonify({'status': status_data, 'job': job_data})
|
'lastPrintTime': None,
|
||||||
except Exception as e:
|
'user': None
|
||||||
return jsonify({'error': str(e)}), 500
|
},
|
||||||
|
'progress': {
|
||||||
|
'completion': 74.8,
|
||||||
|
'filepos': 1234,
|
||||||
|
'printTime': 1235,
|
||||||
|
'printTimeLeft': 6353,
|
||||||
|
'printTimeLeftOrigin': 5366
|
||||||
|
},
|
||||||
|
'state': 'Operational'
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
'sd': {'ready': False},
|
||||||
|
'state': {
|
||||||
|
'error': '',
|
||||||
|
'flags': {
|
||||||
|
'cancelling': False,
|
||||||
|
'closedOrError': False,
|
||||||
|
'error': False,
|
||||||
|
'finishing': False,
|
||||||
|
'operational': True,
|
||||||
|
'paused': False,
|
||||||
|
'pausing': False,
|
||||||
|
'printing': False,
|
||||||
|
'ready': True,
|
||||||
|
'resuming': False,
|
||||||
|
'sdReady': False
|
||||||
|
},
|
||||||
|
'text': 'Operational test'
|
||||||
|
},
|
||||||
|
'temperature': {
|
||||||
|
'bed': {'actual': 85, 'offset': 0, 'target': 56},
|
||||||
|
'tool0': {'actual': 0.0, 'offset': 0, 'target': 340}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonify(test_data)
|
||||||
|
# client = get_octo_client()
|
||||||
|
# if not client:
|
||||||
|
# return jsonify({'error': 'Printer not configured'}), 503
|
||||||
|
# try:
|
||||||
|
# status_data = client.get_printer_status()
|
||||||
|
# job_data = client.get_job_info()
|
||||||
|
# job_data = _enrich_job_data(job_data)
|
||||||
|
# return jsonify({'status': status_data, 'job': job_data})
|
||||||
|
# except Exception as e:
|
||||||
|
# return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@api_bp.route('/octoprint_client', methods=['POST'])
|
@api_bp.route('/octoprint_client', methods=['POST'])
|
||||||
@require_api_key
|
@require_api_key
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ class ConfParse:
|
|||||||
if evaluated != field_val and not isinstance(evaluated, type):
|
if evaluated != field_val and not isinstance(evaluated, type):
|
||||||
if val_dict.get("type") == "str" and not isinstance(evaluated, str):
|
if val_dict.get("type") == "str" and not isinstance(evaluated, str):
|
||||||
if isinstance(evaluated, (list, dict)):
|
if isinstance(evaluated, (list, dict)):
|
||||||
import json
|
|
||||||
val_dict[field] = json.dumps(evaluated).replace(" ", "")
|
val_dict[field] = json.dumps(evaluated).replace(" ", "")
|
||||||
else:
|
else:
|
||||||
val_dict[field] = str(evaluated)
|
val_dict[field] = str(evaluated)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
import configparser
|
import configparser
|
||||||
from app.utils.conf_parse import ConfParse
|
from app.utils.conf_parse import ConfParse
|
||||||
|
from app.models import SystemConfig
|
||||||
|
|
||||||
class CuraEngine:
|
class CuraEngine:
|
||||||
def __init__(self, print_config_folder=None):
|
def __init__(self, print_config_folder=None):
|
||||||
@@ -44,8 +45,6 @@ class CuraEngine:
|
|||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["CURA_ENGINE_SEARCH_PATH"] = f"{printers_path}:{extruders_path}:{materials_path}:{presets_path}:{variants_path}"
|
env["CURA_ENGINE_SEARCH_PATH"] = f"{printers_path}:{extruders_path}:{materials_path}:{presets_path}:{variants_path}"
|
||||||
|
|
||||||
|
|
||||||
from app.models import SystemConfig
|
|
||||||
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
||||||
p_val = db_printer.value if db_printer and db_printer.value else 'creality_ender3v3se.def.json'
|
p_val = db_printer.value if db_printer and db_printer.value else 'creality_ender3v3se.def.json'
|
||||||
if not p_val.endswith('.def.json'): p_val += '.def.json'
|
if not p_val.endswith('.def.json'): p_val += '.def.json'
|
||||||
@@ -232,8 +231,6 @@ class CuraEngine:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def get_bed_dimensions(self):
|
def get_bed_dimensions(self):
|
||||||
from app.models import SystemConfig
|
|
||||||
import json
|
|
||||||
try:
|
try:
|
||||||
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
||||||
p_val = db_printer.value if db_printer and db_printer.value else 'creality_ender3v3se.def.json'
|
p_val = db_printer.value if db_printer and db_printer.value else 'creality_ender3v3se.def.json'
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import configparser
|
import configparser
|
||||||
import uuid
|
import uuid
|
||||||
|
from app.models import SystemConfig
|
||||||
|
|
||||||
class PrusaSlicerEngine:
|
class PrusaSlicerEngine:
|
||||||
def __init__(self, print_config_folder=None):
|
def __init__(self, print_config_folder=None):
|
||||||
@@ -47,8 +48,6 @@ class PrusaSlicerEngine:
|
|||||||
|
|
||||||
# print(support_pattern)
|
# print(support_pattern)
|
||||||
all_configs = {}
|
all_configs = {}
|
||||||
|
|
||||||
from app.models import SystemConfig
|
|
||||||
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
||||||
p_val = db_printer.value if db_printer and db_printer.value else 'Ender3_V3_SE'
|
p_val = db_printer.value if db_printer and db_printer.value else 'Ender3_V3_SE'
|
||||||
if not p_val.endswith('.ini'): p_val += '.ini'
|
if not p_val.endswith('.ini'): p_val += '.ini'
|
||||||
@@ -156,8 +155,6 @@ class PrusaSlicerEngine:
|
|||||||
|
|
||||||
|
|
||||||
def get_bed_dimensions(self):
|
def get_bed_dimensions(self):
|
||||||
from app.models import SystemConfig
|
|
||||||
import configparser
|
|
||||||
try:
|
try:
|
||||||
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
db_printer = SystemConfig.query.filter_by(key='default_printer').first()
|
||||||
p_val = db_printer.value if db_printer and db_printer.value else 'Ender3_V3_SE.ini'
|
p_val = db_printer.value if db_printer and db_printer.value else 'Ender3_V3_SE.ini'
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
from huey import SqliteHuey
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
from app.models import db, PrintFile, SystemConfig
|
|
||||||
from app.utils.conf_parse import ConfParse
|
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import configparser
|
import configparser
|
||||||
|
from huey import SqliteHuey
|
||||||
|
from app import create_app
|
||||||
|
from app.models import db, PrintFile, SystemConfig
|
||||||
|
from app.utils.conf_parse import ConfParse
|
||||||
from app.utils.slice_engines import get_slicer_engine
|
from app.utils.slice_engines import get_slicer_engine
|
||||||
|
from app.utils.stl_merger import merge_stls
|
||||||
|
from app.utils.stl_simplifier import simplify_stl
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Ensure instance directory exists
|
# Ensure instance directory exists
|
||||||
instance_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'instance')
|
instance_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'instance')
|
||||||
@@ -29,7 +31,6 @@ def get_gcode_dir(app):
|
|||||||
def slice_stl_task(file_id, stl_filepath, quality_preset=None, material_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
|
def slice_stl_task(file_id, stl_filepath, quality_preset=None, material_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
|
||||||
# This is run by the Huey worker
|
# This is run by the Huey worker
|
||||||
# We need to create an app context to interact with the database
|
# We need to create an app context to interact with the database
|
||||||
from app import create_app
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
print_file = PrintFile.query.get(file_id)
|
print_file = PrintFile.query.get(file_id)
|
||||||
@@ -95,10 +96,8 @@ def slice_stl_task(file_id, stl_filepath, quality_preset=None, material_preset=N
|
|||||||
|
|
||||||
@huey.task()
|
@huey.task()
|
||||||
def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None, material_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
|
def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None, material_preset=None, infill_density=None, support_enable=None, support_pattern=None, delete_stl=False):
|
||||||
from app import create_app
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
from app.models import PrintFile, db
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
print_file = PrintFile.query.get(file_id)
|
||||||
if not print_file:
|
if not print_file:
|
||||||
return
|
return
|
||||||
@@ -106,7 +105,6 @@ def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None,
|
|||||||
db.session.remove()
|
db.session.remove()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from app.utils.stl_merger import merge_stls
|
|
||||||
merge_stls(inputs, merged_filepath)
|
merge_stls(inputs, merged_filepath)
|
||||||
|
|
||||||
# Now trigger the regular slicing task
|
# Now trigger the regular slicing task
|
||||||
@@ -123,13 +121,8 @@ def merge_and_slice_task(file_id, inputs, merged_filepath, quality_preset=None,
|
|||||||
|
|
||||||
@huey.task()
|
@huey.task()
|
||||||
def simplify_stl_task(file_id, filepath):
|
def simplify_stl_task(file_id, filepath):
|
||||||
from app import create_app
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
from app.models import PrintFile, SystemConfig, db
|
|
||||||
import os
|
|
||||||
from app.utils.stl_simplifier import simplify_stl
|
|
||||||
|
|
||||||
print_file = PrintFile.query.get(file_id)
|
print_file = PrintFile.query.get(file_id)
|
||||||
if not print_file:
|
if not print_file:
|
||||||
return
|
return
|
||||||
|
|||||||
3
run_huey.sh
Executable file
3
run_huey.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
venv/bin/huey_consumer run_huey.huey
|
||||||
3
run_main.sh
Executable file
3
run_main.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
venv/bin/python run.py
|
||||||
Reference in New Issue
Block a user