fix: 5 letzte 'niedrig'-Priorität Probleme aus Review behoben
- serve.py: explizite if/else Content-Type (GET), PUT antwortet mit passendem CT - index.html: Original-Indent speichern/wiederverwenden, View-Modal-Status nach Edit - smoke_test.sh: $$ statt $PORT für Log-PID, pkill mit TERM+Retry+SIGKILL - agent_verify.sh: try/except FileNotFoundError für node-Check - validate.py: re.fullmatch statt re.match für Pattern-Validierung
This commit is contained in:
parent
f3a91e940e
commit
d808013395
5 changed files with 57 additions and 19 deletions
|
|
@ -14,7 +14,11 @@ html = open(sys.argv[1] if len(sys.argv) > 1 else 'web/index.html').read()
|
||||||
for i, s in enumerate(re.findall(r'<script[^>]*>([\s\S]*?)</script>', html)):
|
for i, s in enumerate(re.findall(r'<script[^>]*>([\s\S]*?)</script>', html)):
|
||||||
with tempfile.NamedTemporaryFile('w', suffix='.js', delete=True) as f:
|
with tempfile.NamedTemporaryFile('w', suffix='.js', delete=True) as f:
|
||||||
f.write(s); p = f.name
|
f.write(s); p = f.name
|
||||||
|
try:
|
||||||
r = subprocess.run(['node', '--check', p], capture_output=True, text=True)
|
r = subprocess.run(['node', '--check', p], capture_output=True, text=True)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print('WARN: node nicht gefunden — JS-Syntax-Check übersprungen')
|
||||||
|
sys.exit(0)
|
||||||
if r.returncode != 0:
|
if r.returncode != 0:
|
||||||
print(f'JS[{i}] syntax error:\n{r.stderr}')
|
print(f'JS[{i}] syntax error:\n{r.stderr}')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,21 @@ set -euo pipefail
|
||||||
PORT="${1:-8082}"
|
PORT="${1:-8082}"
|
||||||
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "Usage: $0 [port]" >&2; exit 1; }
|
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "Usage: $0 [port]" >&2; exit 1; }
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
LOG="/tmp/smoke.$PORT.log"
|
LOG="/tmp/smoke.$$.log"
|
||||||
EXIT_CODE=1
|
EXIT_CODE=1
|
||||||
MARKER="SMOKE_TEST_SERVER_$PORT"
|
MARKER="SMOKE_TEST_SERVER_$PORT"
|
||||||
TIMEOUT=30
|
TIMEOUT=30
|
||||||
START_TIME=$(date +%s)
|
START_TIME=$(date +%s)
|
||||||
|
|
||||||
# Vorherige Instanzen dieses Smoke-Tests beenden
|
# Vorherige Instanzen dieses Smoke-Tests beenden
|
||||||
pkill -f "$MARKER" 2>/dev/null || true
|
# Präziser: nur Prozesse mit exaktem Marker-String killen
|
||||||
sleep 0.1
|
if pgrep -f "$MARKER" >/dev/null 2>&1; then
|
||||||
|
pkill -TERM -f "$MARKER" 2>/dev/null || true
|
||||||
|
sleep 0.5
|
||||||
|
if pgrep -f "$MARKER" >/dev/null 2>&1; then
|
||||||
|
pkill -9 -f "$MARKER" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Pre-Check: serve.py muss sich kompilieren lassen. Sonst stirbt der
|
# Pre-Check: serve.py muss sich kompilieren lassen. Sonst stirbt der
|
||||||
# Server beim Import und das Polling unten wartet umsonst — wir wollen
|
# Server beim Import und das Polling unten wartet umsonst — wir wollen
|
||||||
|
|
@ -49,7 +55,13 @@ cleanup() {
|
||||||
kill "$PID" 2>/dev/null || true
|
kill "$PID" 2>/dev/null || true
|
||||||
wait "$PID" 2>/dev/null || true
|
wait "$PID" 2>/dev/null || true
|
||||||
# Defensiv: falls der Kill-Signal den Python-Prozess nicht trifft
|
# Defensiv: falls der Kill-Signal den Python-Prozess nicht trifft
|
||||||
pkill -f "$MARKER" 2>/dev/null || true
|
if pgrep -f "$MARKER" >/dev/null 2>&1; then
|
||||||
|
pkill -TERM -f "$MARKER" 2>/dev/null || true
|
||||||
|
sleep 0.3
|
||||||
|
if pgrep -f "$MARKER" >/dev/null 2>&1; then
|
||||||
|
pkill -9 -f "$MARKER" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
exit "$EXIT_CODE"
|
exit "$EXIT_CODE"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,15 @@ def validate_json_template(filepath: Path) -> Tuple[bool, List[str]]:
|
||||||
|
|
||||||
# Pattern validieren
|
# Pattern validieren
|
||||||
if "pattern" in schema and isinstance(data[field], str):
|
if "pattern" in schema and isinstance(data[field], str):
|
||||||
if not re.match(schema["pattern"], data[field]):
|
pattern = schema["pattern"]
|
||||||
|
# Sicherstellen dass Pattern ^...$ hat für fullmatch
|
||||||
|
if not (pattern.startswith("^") and pattern.endswith("$")):
|
||||||
|
fullmatch_pattern = f"^{pattern}$"
|
||||||
|
else:
|
||||||
|
fullmatch_pattern = pattern
|
||||||
|
if not re.fullmatch(fullmatch_pattern, data[field]):
|
||||||
errors.append(
|
errors.append(
|
||||||
f"❌ Feld '{field}' entspricht nicht dem Pattern: {schema['pattern']}"
|
f"❌ Feld '{field}' entspricht nicht dem Pattern: {pattern}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Template prüfen
|
# Template prüfen
|
||||||
|
|
|
||||||
|
|
@ -540,6 +540,8 @@ $ python web/serve.py</div>
|
||||||
let allTemplates = [];
|
let allTemplates = [];
|
||||||
let currentEditTemplate = null;
|
let currentEditTemplate = null;
|
||||||
let editContainerRef = null;
|
let editContainerRef = null;
|
||||||
|
let currentIndent = 2;
|
||||||
|
let wasViewModalOpen = false;
|
||||||
|
|
||||||
// XSS-schutz:_esc-Helper
|
// XSS-schutz:_esc-Helper
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
|
|
@ -557,6 +559,7 @@ $ python web/serve.py</div>
|
||||||
currentEditTemplate = path;
|
currentEditTemplate = path;
|
||||||
const title = path.split('/').pop();
|
const title = path.split('/').pop();
|
||||||
document.getElementById('edit-title').textContent = `Template bearbeiten: ${title}`;
|
document.getElementById('edit-title').textContent = `Template bearbeiten: ${title}`;
|
||||||
|
wasViewModalOpen = document.getElementById('modal').classList.contains('active');
|
||||||
|
|
||||||
// Inhalt laden und editierbare Formulare abhängig vom Dateityp erstellen
|
// Inhalt laden und editierbare Formulare abhängig vom Dateityp erstellen
|
||||||
fetch(path)
|
fetch(path)
|
||||||
|
|
@ -568,6 +571,9 @@ $ python web/serve.py</div>
|
||||||
if (path.endsWith('.json')) {
|
if (path.endsWith('.json')) {
|
||||||
try {
|
try {
|
||||||
const jsonData = JSON.parse(content);
|
const jsonData = JSON.parse(content);
|
||||||
|
// Original-Indent erkennen
|
||||||
|
const indentMatch = content.match(/^\s{2,8}/m);
|
||||||
|
currentIndent = indentMatch ? indentMatch[0].length : 2;
|
||||||
createJsonEditUI(editContainer, jsonData, path);
|
createJsonEditUI(editContainer, jsonData, path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Falls JSON ungültig, als Text bearbeiten
|
// Falls JSON ungültig, als Text bearbeiten
|
||||||
|
|
@ -811,7 +817,7 @@ $ python web/serve.py</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedData = extractJsonFromForm(formDiv);
|
const updatedData = extractJsonFromForm(formDiv);
|
||||||
const finalJsonString = JSON.stringify(updatedData, null, 2);
|
const finalJsonString = JSON.stringify(updatedData, null, currentIndent);
|
||||||
|
|
||||||
// Valid JSON prüfen
|
// Valid JSON prüfen
|
||||||
try {
|
try {
|
||||||
|
|
@ -830,7 +836,7 @@ $ python web/serve.py</div>
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast('✓ Änderungen gespeichert');
|
showToast('✓ Änderungen gespeichert');
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
closeModal();
|
if (wasViewModalOpen) closeModal();
|
||||||
viewTemplate(currentEditTemplate);
|
viewTemplate(currentEditTemplate);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`HTTP ${response.status}`);
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
|
@ -848,7 +854,7 @@ $ python web/serve.py</div>
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast('✓ Änderungen gespeichert');
|
showToast('✓ Änderungen gespeichert');
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
closeModal();
|
if (wasViewModalOpen) closeModal();
|
||||||
viewTemplate(currentEditTemplate);
|
viewTemplate(currentEditTemplate);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`HTTP ${response.status}`);
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
|
@ -867,6 +873,7 @@ $ python web/serve.py</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
|
wasViewModalOpen = true;
|
||||||
document.getElementById('modal').classList.remove('active');
|
document.getElementById('modal').classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
25
web/serve.py
25
web/serve.py
|
|
@ -121,10 +121,16 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
f.write(file_content)
|
f.write(file_content)
|
||||||
|
|
||||||
|
# Response Content-Type je nach Dateityp setzen
|
||||||
|
if file_path.endswith(".json"):
|
||||||
|
response_content_type = "application/json"
|
||||||
|
else:
|
||||||
|
response_content_type = "text/plain"
|
||||||
|
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-type", "text/plain")
|
self.send_header("Content-type", response_content_type)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(b"File saved successfully")
|
self.wfile.write("File saved successfully".encode())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.send_error(500, f"Failed to save file: {e}")
|
self.send_error(500, f"Failed to save file: {e}")
|
||||||
|
|
||||||
|
|
@ -138,15 +144,18 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
# Anfragen für /templates.json oder /templates/* umleiten
|
# Anfragen für /templates.json oder /templates/* umleiten
|
||||||
file_path = self._validate_template_path(urlparse(self.path).path)
|
file_path = self._validate_template_path(urlparse(self.path).path)
|
||||||
if file_path is not None:
|
if file_path is not None:
|
||||||
|
# Content-Type je nach Dateiendung setzen
|
||||||
|
if file_path.endswith(".md"):
|
||||||
|
content_type = "text/plain"
|
||||||
|
elif file_path.endswith(".json"):
|
||||||
|
content_type = "application/json"
|
||||||
|
else:
|
||||||
|
content_type = "text/plain"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header(
|
self.send_header("Content-type", content_type)
|
||||||
"Content-type",
|
|
||||||
"text/plain"
|
|
||||||
if file_path.endswith(".md")
|
|
||||||
else "application/json",
|
|
||||||
)
|
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(f.read())
|
self.wfile.write(f.read())
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue