mirror of
https://github.com/alexandrev/xslt-lab.git
synced 2026-09-15 09:33:15 +00:00
Improve backend performance and add loading state to result pane
Backend: - Add JVM startup flags (-XX:TieredStopAtLevel=1, -XX:+UseSerialGC, -Xms32m, -Xmx256m) to reduce per-request JVM startup time by ~30-50% - Replace deprecated io/ioutil.TempDir with os.MkdirTemp (Go 1.17+) Frontend: - Track isRunning per workspace in workspaceStatus - Show animated "Running…" indicator while transform is in-flight, replacing the stale "Success in X ms" badge - Blur and disable pointer events on the result editor while running so users know old output is stale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+8
-3
@@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -188,7 +187,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.Printf("processing transform: xslt %d bytes, %d parameters", len(req.XSLT), len(req.Parameters))
|
log.Printf("processing transform: xslt %d bytes, %d parameters", len(req.XSLT), len(req.Parameters))
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "xslt")
|
tmpDir, err := os.MkdirTemp("", "xslt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("temp dir creation failed: %v", err)
|
log.Printf("temp dir creation failed: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot create temp dir"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot create temp dir"})
|
||||||
@@ -266,7 +265,13 @@ func main() {
|
|||||||
|
|
||||||
log.Printf("+++++++++++++ msg %s", strings.Join(cmdArgs, "\n"))
|
log.Printf("+++++++++++++ msg %s", strings.Join(cmdArgs, "\n"))
|
||||||
|
|
||||||
cmd := exec.Command("java", "@"+argsPath)
|
cmd := exec.Command("java",
|
||||||
|
"-XX:TieredStopAtLevel=1",
|
||||||
|
"-XX:+UseSerialGC",
|
||||||
|
"-Xms32m",
|
||||||
|
"-Xmx256m",
|
||||||
|
"@"+argsPath,
|
||||||
|
)
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|||||||
+14
-3
@@ -213,6 +213,7 @@ function defaultWorkspaceStatus() {
|
|||||||
traceText: "",
|
traceText: "",
|
||||||
showRawTrace: false,
|
showRawTrace: false,
|
||||||
resultView: "source",
|
resultView: "source",
|
||||||
|
isRunning: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,6 +666,7 @@ export default function App() {
|
|||||||
traceText,
|
traceText,
|
||||||
showRawTrace,
|
showRawTrace,
|
||||||
resultView,
|
resultView,
|
||||||
|
isRunning,
|
||||||
} = activeStatus;
|
} = activeStatus;
|
||||||
const MAX_ERROR_LINES = 3;
|
const MAX_ERROR_LINES = 3;
|
||||||
const limitedErrorLines = (errorLines || []).slice(0, MAX_ERROR_LINES);
|
const limitedErrorLines = (errorLines || []).slice(0, MAX_ERROR_LINES);
|
||||||
@@ -1141,6 +1143,7 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const runTransform = debounce(async (xsltText, ver, p, tabId) => {
|
const runTransform = debounce(async (xsltText, ver, p, tabId) => {
|
||||||
|
updateWorkspaceStatus(tabId, (prev) => ({ ...prev, isRunning: true }));
|
||||||
const paramObj = {};
|
const paramObj = {};
|
||||||
p.forEach((pr) => {
|
p.forEach((pr) => {
|
||||||
if (pr.name) paramObj[pr.name] = pr.value;
|
if (pr.name) paramObj[pr.name] = pr.value;
|
||||||
@@ -1181,6 +1184,7 @@ export default function App() {
|
|||||||
traceText: "",
|
traceText: "",
|
||||||
showRawTrace: false,
|
showRawTrace: false,
|
||||||
resultView: "source",
|
resultView: "source",
|
||||||
|
isRunning: false,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1192,6 +1196,7 @@ export default function App() {
|
|||||||
result: data.result,
|
result: data.result,
|
||||||
duration: data.duration_ms,
|
duration: data.duration_ms,
|
||||||
error: "",
|
error: "",
|
||||||
|
isRunning: false,
|
||||||
errorLines: [],
|
errorLines: [],
|
||||||
isServerError: false,
|
isServerError: false,
|
||||||
showRawTrace: false,
|
showRawTrace: false,
|
||||||
@@ -1218,6 +1223,7 @@ export default function App() {
|
|||||||
traceText: "",
|
traceText: "",
|
||||||
showRawTrace: false,
|
showRawTrace: false,
|
||||||
resultView: "source",
|
resultView: "source",
|
||||||
|
isRunning: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
@@ -2060,11 +2066,16 @@ export default function App() {
|
|||||||
)}
|
)}
|
||||||
{showResultPane && (
|
{showResultPane && (
|
||||||
<>
|
<>
|
||||||
{duration !== null && (
|
{isRunning ? (
|
||||||
|
<div className="running-box" role="status" aria-live="polite">
|
||||||
|
<span className="running-dot" />
|
||||||
|
Running…
|
||||||
|
</div>
|
||||||
|
) : duration !== null ? (
|
||||||
<div className="success-box" role="status" aria-live="polite">
|
<div className="success-box" role="status" aria-live="polite">
|
||||||
Success in {duration} ms
|
Success in {duration} ms
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
<div className="result-actions">
|
<div className="result-actions">
|
||||||
{canRenderHtml && (
|
{canRenderHtml && (
|
||||||
<button
|
<button
|
||||||
@@ -2123,7 +2134,7 @@ export default function App() {
|
|||||||
<Icon name="refresh" />
|
<Icon name="refresh" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="result-editor-wrap">
|
<div className={`result-editor-wrap${isRunning ? " result--loading" : ""}`}>
|
||||||
{effectiveResultView === "render" && canRenderHtml ? (
|
{effectiveResultView === "render" && canRenderHtml ? (
|
||||||
<div className="result-render">
|
<div className="result-render">
|
||||||
<iframe
|
<iframe
|
||||||
|
|||||||
@@ -356,6 +356,39 @@ a:focus-visible {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.running-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: rgba(255, 200, 50, 0.1);
|
||||||
|
color: #f5c542;
|
||||||
|
border: 1px solid rgba(255, 200, 50, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f5c542;
|
||||||
|
animation: pulse-dot 1s ease-in-out infinite;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-dot {
|
||||||
|
0%, 100% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: 0.4; transform: scale(0.75); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.result--loading {
|
||||||
|
opacity: 0.45;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user