api fix and script fe improv
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s

This commit is contained in:
2026-03-01 11:57:54 +01:00
parent 60634a77eb
commit 7c08aed33a
3 changed files with 661 additions and 509 deletions

View File

@@ -493,11 +493,7 @@ async def upload_script(
except ValueError as e: except ValueError as e:
logger.warning("Invalid name provided: %s", name) logger.warning("Invalid name provided: %s", name)
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
if not script.filename.endswith(".py"):
logger.warning("Upload rejected: script not .py (name=%s original=%s)", name, script.filename)
raise HTTPException(status_code=400, detail="only .py scripts allowed")
spath = script_path_for(name) spath = script_path_for(name)
if os.path.exists(spath): if os.path.exists(spath):
logger.warning("Upload rejected: script with name already exists: %s", name) logger.warning("Upload rejected: script with name already exists: %s", name)

View File

@@ -1,76 +1,111 @@
// src/components/PythonEditor.tsx
import Prism from 'prismjs'; import Prism from 'prismjs';
import 'prismjs/components/prism-python'; import 'prismjs/components/prism-python';
import 'prismjs/themes/prism.css'; import 'prismjs/themes/prism.css';
import { useCallback, useMemo } from 'react'; import React, { useEffect, useMemo, useRef } from 'react';
import Editor from 'react-simple-code-editor';
// Props for the editor
export type PythonEditorProps = { export type PythonEditorProps = {
value: string; value: string;
onChange: (code: string) => void; onChange: (code: string) => void;
readOnly?: boolean; readOnly?: boolean;
className?: string; height?: string | number;
height?: string | number; // e.g. '200px' or 200 wrap?: boolean;
wrap?: boolean; // if true, long lines will wrap; default false (no wrap) fontSize?: number;
tabSize?: number;
}; };
const DEFAULT_FONT =
'"JetBrains Mono", "Fira Code", "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace';
export default function PythonEditor({ export default function PythonEditor({
value, value,
onChange, onChange,
readOnly = false, readOnly = false,
height = 480, height = 480,
className = '',
wrap = false, wrap = false,
fontSize = 14,
tabSize = 4,
}: PythonEditorProps) { }: PythonEditorProps) {
const highlight = useCallback((code: string) => { const textareaRef = useRef<HTMLTextAreaElement | null>(null);
try { const preRef = useRef<HTMLPreElement | null>(null);
return Prism.highlight(code, Prism.languages.python, 'python');
} catch (e) { const h = typeof height === 'number' ? `${height}px` : height;
return code;
const lineHeight = Math.round(fontSize * 1.6);
// Highlight whenever value changes
useEffect(() => {
if (preRef.current) {
preRef.current.innerHTML = Prism.highlight(value, Prism.languages.python, 'python');
} }
}, []); }, [value]);
// container style: fixed height + scrolling // Scroll sync
const containerStyle = useMemo(() => { const handleScroll = () => {
const h = typeof height === 'number' ? `${height}px` : height; if (!textareaRef.current || !preRef.current) return;
return { preRef.current.scrollTop = textareaRef.current.scrollTop;
height: h, preRef.current.scrollLeft = textareaRef.current.scrollLeft;
maxHeight: h, };
overflow: 'auto' as const, // enable scrolling
position: 'relative' as const,
borderRadius: 6,
border: '1px solid rgba(0,0,0,0.06)',
background: 'var(--code-bg, white)',
};
}, [height]);
// editor style: fill container and use monospace font const sharedStyle: React.CSSProperties = useMemo(
const editorStyle = useMemo(
() => ({ () => ({
fontFamily: '"JetBrains Mono", "Fira Code", monospace', fontFamily: DEFAULT_FONT,
fontSize: 14, fontSize: `${fontSize}px`,
// make editor fill the container (pre/textarea inside will take up height) lineHeight: `${lineHeight}px`,
minHeight: '100%', tabSize,
boxSizing: 'border-box' as const, whiteSpace: wrap ? 'pre-wrap' : 'pre',
wordBreak: 'break-word',
padding: 12, padding: 12,
outline: 'none', boxSizing: 'border-box',
whiteSpace: wrap ? 'pre-wrap' : 'pre', // pre-wrap if wrap requested
wordBreak: 'break-word' as const,
}), }),
[wrap], [fontSize, lineHeight, tabSize, wrap],
); );
return ( return (
<div className={`python-editor ${className}`} style={containerStyle}> <div
<Editor style={{
position: 'relative',
height: h,
borderRadius: 6,
border: '1px solid rgba(0,0,0,0.06)',
overflow: 'hidden',
}}
>
{/* Highlight layer */}
<pre
ref={preRef}
aria-hidden="true"
className="language-python"
style={{
...sharedStyle,
margin: 0,
position: 'absolute',
inset: 0,
overflow: 'auto',
pointerEvents: 'none',
}}
/>
{/* Editable layer */}
<textarea
ref={textareaRef}
value={value} value={value}
onValueChange={(code) => onChange(code)}
highlight={highlight}
padding={0} // padding handled by editorStyle
preClassName="language-python"
textareaId="python-editor"
style={editorStyle}
readOnly={readOnly} readOnly={readOnly}
onChange={(e) => onChange(e.target.value)}
onScroll={handleScroll}
spellCheck={false}
style={{
...sharedStyle,
position: 'absolute',
inset: 0,
resize: 'none',
border: 'none',
outline: 'none',
background: 'transparent',
color: 'transparent', // hide textarea text
caretColor: 'black', // show caret
overflow: 'auto',
}}
/> />
</div> </div>
); );

File diff suppressed because it is too large Load Diff