Compare commits
185 Commits
eb9c20e734
...
v0.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebe768711b | ||
|
|
af45f93854 | ||
|
|
da1d74309e | ||
|
|
7a4d7b6915 | ||
|
|
ab7031e6ed | ||
|
|
cf3cec0164 | ||
|
|
79762c3f0d | ||
|
|
715cbc1894 | ||
|
|
dd398178f0 | ||
|
|
8b0d473fcd | ||
|
|
30e9b55379 | ||
|
|
3091754b0f | ||
|
|
26de222884 | ||
|
|
2f2f93f37e | ||
|
|
1b9a3b84a0 | ||
|
|
c77de4a232 | ||
|
|
15b8c09e17 | ||
|
|
77e87504d6 | ||
|
|
d8a21f0724 | ||
|
|
4a91ca0774 | ||
|
|
52c22f1eb9 | ||
|
|
a517785050 | ||
|
|
474738a894 | ||
|
|
41397ae067 | ||
|
|
dd91a4989b | ||
|
|
f06f5f2bb1 | ||
|
|
c8caf3dc44 | ||
|
|
2de10f6eb0 | ||
|
|
e2c0f203f9 | ||
|
|
a383b9bcf4 | ||
|
|
6aeba1fe83 | ||
|
|
7a1625c297 | ||
|
|
9d2d87e7e1 | ||
|
|
b5c19b6774 | ||
|
|
213aa86c47 | ||
|
|
b2ae37637d | ||
|
|
7e968dc06b | ||
|
|
0ec41bc02c | ||
|
|
59ddbb65b9 | ||
|
|
673f0958c5 | ||
|
|
e8039f9cc4 | ||
|
|
9eb2c2692b | ||
|
|
090c51c809 | ||
|
|
32cde5363f | ||
|
|
604e5db874 | ||
|
|
a4fcb8810f | ||
|
|
3d71345181 | ||
|
|
5103f40196 | ||
|
|
09a60c5a6c | ||
|
|
7a84914866 | ||
|
|
88c51b75bf | ||
|
|
3f87f37095 | ||
|
|
ac4476ccd6 | ||
|
|
30344d29b1 | ||
|
|
f12f5f3c8d | ||
|
|
c6f70968a2 | ||
|
|
faf5d505f4 | ||
|
|
c4b396e618 | ||
|
|
e5e6175aca | ||
|
|
0516207e83 | ||
|
|
d79e7d0168 | ||
|
|
7c88b03956 | ||
|
|
55e1c7cbb5 | ||
|
|
6a1d199da6 | ||
|
|
459f4d2e0c | ||
|
|
27249c2440 | ||
|
|
f59423bc91 | ||
|
|
e5be9f81e0 | ||
|
|
9f281c3354 | ||
|
|
f2a094f349 | ||
|
|
dd1cae6f70 | ||
|
|
7903a300db | ||
|
|
5873e6a57c | ||
|
|
816a034d4a | ||
|
|
2fade7192a | ||
|
|
175e62f514 | ||
|
|
b4c9be9334 | ||
|
|
8b276a92a7 | ||
|
|
01c6d5c131 | ||
|
|
626501cb04 | ||
|
|
3362417907 | ||
|
|
7b2622fca9 | ||
|
|
24d760af8a | ||
|
|
d32bde58e2 | ||
|
|
3d86d57a80 | ||
|
|
29f4be542b | ||
|
|
2f2e503447 | ||
|
|
7ee57ca975 | ||
|
|
c8fcee9d09 | ||
|
|
0ed30d92f1 | ||
|
|
4e59b0bcd0 | ||
|
|
eaeef6f0b2 | ||
|
|
9f0c2e1225 | ||
|
|
e934b31164 | ||
|
|
77d871c4f8 | ||
|
|
4296d41cad | ||
|
|
a5ba684c7d | ||
|
|
a658ed9135 | ||
|
|
b863370511 | ||
|
|
048f6566a9 | ||
|
|
5cb3de03af | ||
|
|
ef9d8c8066 | ||
|
|
1ca4cac396 | ||
|
|
6b06e7f86b | ||
|
|
e703a9d39d | ||
|
|
67bae5640c | ||
|
|
c06f0c89e5 | ||
|
|
73560d761d | ||
|
|
4ed804141a | ||
|
|
de2281cad2 | ||
|
|
5af20d0f63 | ||
|
|
91171590e6 | ||
|
|
699ef86f8f | ||
|
|
d63a9f8ce7 | ||
|
|
77c73fe3e6 | ||
|
|
1e6de17084 | ||
| 7ee7076eec | |||
|
|
698b97d536 | ||
|
|
4fe418cc89 | ||
|
|
66abb1fe3a | ||
|
|
611c201887 | ||
|
|
f2abe296ee | ||
|
|
fc27880d96 | ||
|
|
8219c54422 | ||
|
|
c1b156bdb4 | ||
|
|
0eb377b515 | ||
|
|
facf7fb6ef | ||
|
|
90be1875e0 | ||
|
|
065517f032 | ||
|
|
99b97c53dd | ||
|
|
79e5caaf7a | ||
|
|
5b5fa28ba0 | ||
|
|
3b2c5ccdbe | ||
|
|
c8d824d347 | ||
|
|
615a3c6e99 | ||
|
|
dbf64ecb48 | ||
|
|
1702200a60 | ||
|
|
004574d442 | ||
|
|
41111b082c | ||
|
|
e9b1c94d1a | ||
|
|
0d7d04501c | ||
|
|
6393e5096f | ||
|
|
4af71aabac | ||
|
|
acb7cade90 | ||
|
|
19d3c8fa93 | ||
|
|
990d607d4b | ||
|
|
0df7735d20 | ||
|
|
7926179ed9 | ||
|
|
1855153dbe | ||
|
|
3751762c69 | ||
|
|
56f98671ca | ||
|
|
cbe41d7ac7 | ||
|
|
bd8e95c6ce | ||
|
|
fee9b4bd83 | ||
|
|
7ec683aca0 | ||
|
|
ac750b603f | ||
|
|
5306be3f2e | ||
|
|
b0dcd0ac6b | ||
|
|
159e4adf07 | ||
|
|
085c4e395b | ||
|
|
d7166b6d0a | ||
|
|
25e23c0b87 | ||
|
|
cf9e847f84 | ||
|
|
bfd76261ef | ||
|
|
0b8efa1998 | ||
|
|
3027e9b24f | ||
|
|
3d5d462de0 | ||
|
|
f675451384 | ||
|
|
021a52e56b | ||
|
|
5ccefa3cdb | ||
|
|
e4c66b1311 | ||
|
|
5da03d0938 | ||
|
|
3af1d1f3b6 | ||
|
|
1984c597de | ||
|
|
3029704051 | ||
|
|
2b805ec196 | ||
|
|
ff59dc5d57 | ||
|
|
3928743ea7 | ||
|
|
cf6c4bd60c | ||
|
|
edd841ffeb | ||
|
|
889f0e5263 | ||
|
|
3a41e1f1d3 | ||
|
|
509159417b | ||
|
|
30c8fe1091 | ||
|
|
b1ff05439a |
89
.gitea/workflows/sonarqube.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
name: SonarQube
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sonarqube:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: gitea.siegeln.net/cameleer/cameleer-build:1
|
||||
credentials:
|
||||
username: cameleer
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure Gitea Maven Registry
|
||||
run: |
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml << 'SETTINGS'
|
||||
<settings>
|
||||
<servers>
|
||||
<server>
|
||||
<id>gitea</id>
|
||||
<username>cameleer</username>
|
||||
<password>${env.REGISTRY_TOKEN}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
SETTINGS
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- name: Cache Maven dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-maven-
|
||||
|
||||
- name: Build and Test Java
|
||||
run: mvn clean verify -DskipITs -U --batch-mode
|
||||
|
||||
- name: Install UI dependencies
|
||||
working-directory: ui
|
||||
run: |
|
||||
echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
|
||||
npm ci
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- name: Lint UI
|
||||
working-directory: ui
|
||||
run: npm run lint -- --format json --output-file eslint-report.json || true
|
||||
|
||||
- name: Install sonar-scanner
|
||||
run: |
|
||||
SONAR_SCANNER_VERSION=6.2.1.4610
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
aarch64|arm64) PLATFORM="linux-aarch64" ;;
|
||||
*) PLATFORM="linux-x64" ;;
|
||||
esac
|
||||
curl -sSLo sonar-scanner.zip "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-${PLATFORM}.zip"
|
||||
unzip -q sonar-scanner.zip
|
||||
ln -s "$(pwd)/sonar-scanner-${SONAR_SCANNER_VERSION}-${PLATFORM}/bin/sonar-scanner" /usr/local/bin/sonar-scanner
|
||||
|
||||
- name: SonarQube Analysis
|
||||
run: |
|
||||
sonar-scanner \
|
||||
-Dsonar.host.url="$SONAR_HOST_URL" \
|
||||
-Dsonar.token="$SONAR_TOKEN" \
|
||||
-Dsonar.projectKey=cameleer3-server \
|
||||
-Dsonar.projectName="Cameleer3 Server" \
|
||||
-Dsonar.sources=cameleer3-server-core/src/main/java,cameleer3-server-app/src/main/java,ui/src \
|
||||
-Dsonar.tests=cameleer3-server-core/src/test/java,cameleer3-server-app/src/test/java \
|
||||
-Dsonar.java.binaries=cameleer3-server-core/target/classes,cameleer3-server-app/target/classes \
|
||||
-Dsonar.java.test.binaries=cameleer3-server-core/target/test-classes,cameleer3-server-app/target/test-classes \
|
||||
-Dsonar.java.libraries="$HOME/.m2/repository/**/*.jar" \
|
||||
-Dsonar.typescript.eslint.reportPaths=ui/eslint-report.json \
|
||||
-Dsonar.eslint.reportPaths=ui/eslint-report.json \
|
||||
-Dsonar.exclusions="ui/node_modules/**,ui/dist/**,**/target/**"
|
||||
env:
|
||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
1
.gitignore
vendored
@@ -39,3 +39,4 @@ logs/
|
||||
|
||||
# Claude
|
||||
.claude/
|
||||
.worktrees/
|
||||
|
||||
BIN
.playwright-mcp/page-2026-03-24T18-41-08-535Z.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
.playwright-mcp/page-2026-03-24T18-41-32-291Z.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
.playwright-mcp/page-2026-03-24T19-34-41-650Z.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
.playwright-mcp/page-2026-03-24T19-51-20-426Z.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
.playwright-mcp/page-2026-03-24T19-53-00-560Z.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
1
.superpowers/brainstorm/10188-1774613058/.server-stopped
Normal file
@@ -0,0 +1 @@
|
||||
{"reason":"idle timeout","timestamp":1774616238650}
|
||||
1
.superpowers/brainstorm/10188-1774613058/.server.pid
Normal file
@@ -0,0 +1 @@
|
||||
10188
|
||||
@@ -0,0 +1,105 @@
|
||||
<h2>ProcessDiagram Component Hierarchy</h2>
|
||||
<p class="subtitle">How the SVG rendering is structured — from data fetch to pixels</p>
|
||||
|
||||
<div class="section">
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Component Tree</div>
|
||||
<div class="mockup-body" style="padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.8; color: #1A1612;">
|
||||
<div><strong style="color: #1A7F8E;">ProcessDiagram</strong> — root, fetches layout, manages state</div>
|
||||
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
|
||||
<div><svg> container with viewBox (zoom/pan transforms)</div>
|
||||
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
|
||||
<div><strong style="color: #7C3AED;">DiagramSection</strong> label="Main Route"</div>
|
||||
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
|
||||
<div><strong style="color: #9C9184;"><g></strong> edges layer (rendered first, behind nodes)</div>
|
||||
<div style="padding-left: 24px;">
|
||||
<div><strong style="color: #C6820E;">DiagramEdge</strong> × N — SVG <path> with arrowhead</div>
|
||||
</div>
|
||||
<div><strong style="color: #9C9184;"><g></strong> nodes layer</div>
|
||||
<div style="padding-left: 24px;">
|
||||
<div><strong style="color: #C6820E;">DiagramNode</strong> × N — top-bar card</div>
|
||||
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
|
||||
<div><strong style="color: #3D7C47;">ConfigBadge</strong> × 0..N — tap/trace indicators</div>
|
||||
<div><strong style="color: #3D7C47;">NodeToolbar</strong> — floating on hover</div>
|
||||
</div>
|
||||
<div><strong style="color: #C6820E;">CompoundNode</strong> × 0..N — choice/split container</div>
|
||||
<div style="padding-left: 24px; border-left: 2px solid #E4DFD8;">
|
||||
<div><strong style="color: #C6820E;">DiagramNode</strong> × N — children inside compound</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px;"><strong style="color: #C0392B;">DiagramSection</strong> label="onException" variant="error"</div>
|
||||
<div style="padding-left: 24px; border-left: 2px solid #C0392B;">
|
||||
<div><em style="color: #9C9184;">same edge + node structure as above</em></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px;"><strong style="color: #1A7F8E;">ZoomControls</strong> — HTML overlay (not SVG)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="margin-top: 24px;">
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">SVG Structure (simplified)</div>
|
||||
<div class="mockup-body" style="padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.7; color: #5C5347; background: #F5F2ED;">
|
||||
<pre style="margin: 0;"><div class="process-diagram"> <span style="color:#9C9184">/* wrapper div */</span>
|
||||
<svg viewBox="0 0 {w} {h}"> <span style="color:#9C9184">/* zoom = viewBox transform */</span>
|
||||
<g class="diagram-content"> <span style="color:#9C9184">/* pan offset */</span>
|
||||
|
||||
<span style="color:#7C3AED"><!-- Main Route section --></span>
|
||||
<g class="section section--main">
|
||||
<g class="edges">
|
||||
<path d="M 100 40 C ..." /> <span style="color:#9C9184">/* cubic bezier edge */</span>
|
||||
<marker>...</marker> <span style="color:#9C9184">/* arrowhead def */</span>
|
||||
</g>
|
||||
<g class="nodes">
|
||||
<g transform="translate(x, y)"> <span style="color:#9C9184">/* positioned by ELK */</span>
|
||||
<rect .../> <span style="color:#9C9184">/* card background */</span>
|
||||
<rect .../> <span style="color:#9C9184">/* color top bar */</span>
|
||||
<text>LOG</text> <span style="color:#9C9184">/* label */</span>
|
||||
<g class="badges">...</g> <span style="color:#9C9184">/* config indicators */</span>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<span style="color:#C0392B"><!-- Error Handler section --></span>
|
||||
<g class="section section--error"
|
||||
transform="translate(0, {mainH + gap})">
|
||||
<text>onException</text> <span style="color:#9C9184">/* section label */</span>
|
||||
<line .../> <span style="color:#9C9184">/* divider line */</span>
|
||||
<g class="edges">...</g>
|
||||
<g class="nodes">...</g>
|
||||
</g>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
<div class="zoom-controls">...</div> <span style="color:#9C9184">/* HTML overlay */</span>
|
||||
</div></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="margin-top: 24px;">
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Data Flow</div>
|
||||
<div class="mockup-body" style="padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.8; color: #5C5347;">
|
||||
<pre style="margin: 0;">
|
||||
<span style="color:#1A7F8E">GET /diagrams/{hash}/render?direction=LR</span>
|
||||
│
|
||||
▼
|
||||
DiagramLayout { nodes[], edges[], width, height }
|
||||
│
|
||||
▼
|
||||
<span style="color:#7C3AED">separateFlows(nodes)</span> → mainNodes[] + errorSections[]
|
||||
│ │
|
||||
▼ ▼
|
||||
<span style="color:#C6820E">renderMainSection()</span> <span style="color:#C0392B">renderErrorSection()</span>
|
||||
│ │
|
||||
▼ ▼
|
||||
SVG groups with SVG groups offset below
|
||||
ELK x/y coordinates main section by mainHeight + gap
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,164 @@
|
||||
<h2>Node Interactions & Config Badges</h2>
|
||||
<p class="subtitle">Hover toolbar, selection states, and active config indicators</p>
|
||||
|
||||
<div class="section">
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Node States</div>
|
||||
<div class="mockup-body" style="padding: 24px; background: #F5F2ED;">
|
||||
<svg width="100%" height="340" viewBox="0 0 520 340">
|
||||
|
||||
<!-- 1. Normal state -->
|
||||
<text x="10" y="16" fill="#9C9184" font-size="11" font-weight="600">NORMAL</text>
|
||||
<g transform="translate(10, 24)">
|
||||
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
</g>
|
||||
|
||||
<!-- 2. Hovered state with toolbar -->
|
||||
<text x="270" y="16" fill="#9C9184" font-size="11" font-weight="600">HOVERED (toolbar appears)</text>
|
||||
<g transform="translate(270, 24)">
|
||||
<rect x="0" y="0" width="200" height="56" rx="4" fill="#FFFCF5" stroke="#C6820E" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<!-- Floating toolbar -->
|
||||
<g transform="translate(30, -32)">
|
||||
<rect x="0" y="0" width="140" height="28" rx="6" fill="#1A1612" opacity="0.92"/>
|
||||
<!-- Icons as circles -->
|
||||
<g transform="translate(10, 4)">
|
||||
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
|
||||
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">🔍</text>
|
||||
</g>
|
||||
<g transform="translate(40, 4)">
|
||||
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
|
||||
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">T</text>
|
||||
</g>
|
||||
<g transform="translate(70, 4)">
|
||||
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
|
||||
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">✎</text>
|
||||
</g>
|
||||
<g transform="translate(100, 4)">
|
||||
<circle cx="10" cy="10" r="9" fill="rgba(255,255,255,0.15)"/>
|
||||
<text x="10" y="14" fill="white" font-size="11" text-anchor="middle">⋯</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 3. Selected state -->
|
||||
<text x="10" y="112" fill="#9C9184" font-size="11" font-weight="600">SELECTED (click)</text>
|
||||
<g transform="translate(10, 120)">
|
||||
<rect x="-2" y="-2" width="204" height="60" rx="6" fill="none" stroke="#C6820E" stroke-width="2.5" stroke-dasharray="none"/>
|
||||
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#C6820E" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
</g>
|
||||
|
||||
<!-- 4. With config badges -->
|
||||
<text x="270" y="112" fill="#9C9184" font-size="11" font-weight="600">WITH CONFIG BADGES</text>
|
||||
<g transform="translate(270, 120)">
|
||||
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="192" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<!-- Trace badge (top-right corner) -->
|
||||
<g transform="translate(165, -6)">
|
||||
<rect x="0" y="0" width="38" height="16" rx="8" fill="#1A7F8E"/>
|
||||
<text x="19" y="12" fill="white" font-size="8" font-weight="600" text-anchor="middle">TRACE</text>
|
||||
</g>
|
||||
<!-- Tap badge -->
|
||||
<g transform="translate(124, -6)">
|
||||
<rect x="0" y="0" width="36" height="16" rx="8" fill="#7C3AED"/>
|
||||
<text x="18" y="12" fill="white" font-size="8" font-weight="600" text-anchor="middle">TAP</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 5. Error node style -->
|
||||
<text x="10" y="210" fill="#9C9184" font-size="11" font-weight="600">ERROR HANDLER NODE</text>
|
||||
<g transform="translate(10, 218)">
|
||||
<rect x="0" y="0" width="200" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="200" height="6" rx="4" fill="#C0392B"/>
|
||||
<rect x="4" y="0" width="192" height="6" fill="#C0392B"/>
|
||||
<text x="16" y="32" fill="#C0392B" font-size="14">⚠</text>
|
||||
<text x="36" y="28" fill="#1A1612" font-size="11" font-weight="600">ON_EXCEPTION</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">java.lang.Exception</text>
|
||||
</g>
|
||||
|
||||
<!-- 6. Compound node (Choice) -->
|
||||
<text x="270" y="210" fill="#9C9184" font-size="11" font-weight="600">COMPOUND NODE (CHOICE)</text>
|
||||
<g transform="translate(270, 218)">
|
||||
<rect x="0" y="0" width="220" height="110" rx="4" fill="white" stroke="#7C3AED" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="220" height="22" rx="4" fill="#7C3AED"/>
|
||||
<rect x="4" y="4" width="212" height="18" fill="#7C3AED"/>
|
||||
<text x="110" y="16" fill="white" font-size="10" font-weight="600" text-anchor="middle">◆ CHOICE</text>
|
||||
<!-- Children -->
|
||||
<g transform="translate(10, 30)">
|
||||
<rect x="0" y="0" width="200" height="32" rx="3" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="200" height="4" rx="3" fill="#7C3AED"/>
|
||||
<rect x="3" y="0" width="194" height="4" fill="#7C3AED"/>
|
||||
<text x="12" y="22" fill="#7C3AED" font-size="10">◆</text>
|
||||
<text x="28" y="22" fill="#1A1612" font-size="10" font-weight="600">WHEN</text>
|
||||
<text x="66" y="22" fill="#5C5347" font-size="9">type == 'A'</text>
|
||||
</g>
|
||||
<g transform="translate(10, 70)">
|
||||
<rect x="0" y="0" width="200" height="32" rx="3" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="200" height="4" rx="3" fill="#7C3AED"/>
|
||||
<rect x="3" y="0" width="194" height="4" fill="#7C3AED"/>
|
||||
<text x="12" y="22" fill="#7C3AED" font-size="10">◆</text>
|
||||
<text x="28" y="22" fill="#1A1612" font-size="10" font-weight="600">OTHERWISE</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="margin-top: 24px;">
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Toolbar Actions</div>
|
||||
<div class="mockup-body" style="padding: 16px;">
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 13px;">
|
||||
<thead>
|
||||
<tr style="border-bottom: 2px solid #E4DFD8;">
|
||||
<th style="text-align: left; padding: 8px; color: #5C5347;">Icon</th>
|
||||
<th style="text-align: left; padding: 8px; color: #5C5347;">Action</th>
|
||||
<th style="text-align: left; padding: 8px; color: #5C5347;">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid #EDE9E3;">
|
||||
<td style="padding: 8px;">🔍</td>
|
||||
<td style="padding: 8px; font-weight: 600;">Inspect</td>
|
||||
<td style="padding: 8px; color: #5C5347;">Select node & open detail side-panel</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #EDE9E3;">
|
||||
<td style="padding: 8px;">T</td>
|
||||
<td style="padding: 8px; font-weight: 600;">Toggle Trace</td>
|
||||
<td style="padding: 8px; color: #5C5347;">Enable/disable capture of input+output for this processor</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #EDE9E3;">
|
||||
<td style="padding: 8px;">✎</td>
|
||||
<td style="padding: 8px; font-weight: 600;">Configure Tap</td>
|
||||
<td style="padding: 8px; color: #5C5347;">Open tap expression editor for this processor</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px;">⋯</td>
|
||||
<td style="padding: 8px; font-weight: 600;">More</td>
|
||||
<td style="padding: 8px; color: #5C5347;">Copy processor ID, jump to code, view in search</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
119
.superpowers/brainstorm/10188-1774613058/node-interactions.html
Normal file
@@ -0,0 +1,119 @@
|
||||
<h2>Node Interaction Model</h2>
|
||||
<p class="subtitle">What happens when you interact with a processor node on the diagram?</p>
|
||||
|
||||
<div class="cards">
|
||||
<!-- Option A: Click-to-select + context menu -->
|
||||
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
|
||||
<svg width="100%" height="180" viewBox="0 0 420 180">
|
||||
<!-- Normal node -->
|
||||
<g transform="translate(10, 10)">
|
||||
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<text x="96" y="72" fill="#9C9184" font-size="10" text-anchor="middle" font-style="italic">normal state</text>
|
||||
</g>
|
||||
|
||||
<!-- Selected node (amber ring) -->
|
||||
<g transform="translate(10, 100)">
|
||||
<rect x="-2" y="-2" width="184" height="60" rx="6" fill="none" stroke="#C6820E" stroke-width="2.5"/>
|
||||
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#C6820E" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<text x="96" y="72" fill="#C6820E" font-size="10" text-anchor="middle" font-weight="600">click = select</text>
|
||||
</g>
|
||||
|
||||
<!-- Context menu on right-click -->
|
||||
<g transform="translate(220, 10)">
|
||||
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<!-- Context menu -->
|
||||
<g transform="translate(100, 40)">
|
||||
<rect x="0" y="0" width="140" height="96" rx="6" fill="white" stroke="#E4DFD8" stroke-width="1" filter="url(#shadow)"/>
|
||||
<text x="12" y="20" fill="#1A1612" font-size="11">🔍 View Snapshot</text>
|
||||
<line x1="8" y1="28" x2="132" y2="28" stroke="#EDE9E3" stroke-width="1"/>
|
||||
<text x="12" y="44" fill="#1A7F8E" font-size="11">⚙ Enable Tracing</text>
|
||||
<text x="12" y="64" fill="#1A7F8E" font-size="11">📌 Set Tap</text>
|
||||
<line x1="8" y1="72" x2="132" y2="72" stroke="#EDE9E3" stroke-width="1"/>
|
||||
<text x="12" y="88" fill="#5C5347" font-size="11">📋 Copy Processor ID</text>
|
||||
</g>
|
||||
<text x="90" y="152" fill="#9C9184" font-size="10" text-anchor="middle" font-style="italic">right-click = context menu</text>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<filter id="shadow" x="-4" y="-2" width="148" height="104">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-opacity="0.12"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>A: Click-Select + Right-Click Menu</h3>
|
||||
<p>Click to select a node (amber highlight ring). Right-click for context menu with tracing/tap/snapshot actions. Clean separation of concerns. Standard desktop UX.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option B: Hover toolbar -->
|
||||
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
|
||||
<svg width="100%" height="180" viewBox="0 0 420 180">
|
||||
<!-- Normal node -->
|
||||
<g transform="translate(10, 10)">
|
||||
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<text x="96" y="72" fill="#9C9184" font-size="10" text-anchor="middle" font-style="italic">normal state</text>
|
||||
</g>
|
||||
|
||||
<!-- Hovered node with floating toolbar -->
|
||||
<g transform="translate(10, 100)">
|
||||
<rect x="0" y="0" width="180" height="56" rx="4" fill="#FFFCF5" stroke="#C6820E" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<!-- Floating toolbar above -->
|
||||
<g transform="translate(20, -30)">
|
||||
<rect x="0" y="0" width="140" height="26" rx="13" fill="#1A1612" opacity="0.9"/>
|
||||
<text x="18" y="17" fill="white" font-size="12" title="View">🔍</text>
|
||||
<text x="46" y="17" fill="white" font-size="12" title="Trace">⚙</text>
|
||||
<text x="74" y="17" fill="white" font-size="12" title="Tap">📌</text>
|
||||
<text x="102" y="17" fill="white" font-size="12" title="Copy">📋</text>
|
||||
<text x="124" y="17" fill="white" font-size="12" title="More">⋯</text>
|
||||
</g>
|
||||
<text x="96" y="72" fill="#C6820E" font-size="10" text-anchor="middle" font-weight="600">hover = toolbar appears</text>
|
||||
</g>
|
||||
|
||||
<!-- Click = select (same as A) -->
|
||||
<g transform="translate(220, 50)">
|
||||
<rect x="-2" y="-2" width="184" height="60" rx="6" fill="none" stroke="#C6820E" stroke-width="2.5"/>
|
||||
<rect x="0" y="0" width="180" height="56" rx="4" fill="white" stroke="#C6820E" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="180" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="172" height="6" fill="#C6820E"/>
|
||||
<text x="16" y="32" fill="#C6820E" font-size="14">⚙</text>
|
||||
<text x="36" y="30" fill="#1A1612" font-size="11" font-weight="600">LOG</text>
|
||||
<text x="36" y="44" fill="#5C5347" font-size="10">Processing order</text>
|
||||
<text x="96" y="72" fill="#C6820E" font-size="10" text-anchor="middle" font-weight="600">click = select</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>B: Hover Floating Toolbar</h3>
|
||||
<p>Hover reveals a dark floating icon toolbar above the node. Click still selects. More discoverable than right-click, but can feel cluttered on dense diagrams.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
208
.superpowers/brainstorm/10188-1774613058/node-style.html
Normal file
@@ -0,0 +1,208 @@
|
||||
<h2>Node Visual Style</h2>
|
||||
<p class="subtitle">Which processor node style fits our design system best? Think MuleSoft / TIBCO BW5 but adapted to our warm parchment theme.</p>
|
||||
|
||||
<div class="cards">
|
||||
<!-- Option A: Icon-first blocks (MuleSoft-inspired) -->
|
||||
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
|
||||
<svg width="100%" height="220" viewBox="0 0 400 220">
|
||||
<!-- FROM node -->
|
||||
<g transform="translate(20, 10)">
|
||||
<rect x="0" y="0" width="160" height="56" rx="8" fill="#1A7F8E" opacity="0.12" stroke="#1A7F8E" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="42" height="56" rx="8" fill="#1A7F8E"/>
|
||||
<rect x="8" y="0" width="34" height="56" fill="#1A7F8E"/>
|
||||
<text x="21" y="34" fill="white" font-size="20" text-anchor="middle">▶</text>
|
||||
<text x="100" y="25" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">FROM</text>
|
||||
<text x="100" y="42" fill="#5C5347" font-size="11" text-anchor="middle">direct:orders</text>
|
||||
</g>
|
||||
<!-- Connector -->
|
||||
<line x1="100" y1="66" x2="100" y2="86" stroke="#9C9184" stroke-width="1.5"/>
|
||||
<polygon points="95,82 100,90 105,82" fill="#9C9184"/>
|
||||
<!-- PROCESS node -->
|
||||
<g transform="translate(20, 90)">
|
||||
<rect x="0" y="0" width="160" height="56" rx="8" fill="#C6820E" opacity="0.12" stroke="#C6820E" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="42" height="56" rx="8" fill="#C6820E"/>
|
||||
<rect x="8" y="0" width="34" height="56" fill="#C6820E"/>
|
||||
<text x="21" y="34" fill="white" font-size="18" text-anchor="middle">⚙</text>
|
||||
<text x="100" y="25" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">LOG</text>
|
||||
<text x="100" y="42" fill="#5C5347" font-size="11" text-anchor="middle">Processing order</text>
|
||||
</g>
|
||||
<!-- Connector -->
|
||||
<line x1="100" y1="146" x2="100" y2="166" stroke="#9C9184" stroke-width="1.5"/>
|
||||
<polygon points="95,162 100,170 105,162" fill="#9C9184"/>
|
||||
<!-- TO node -->
|
||||
<g transform="translate(20, 170)">
|
||||
<rect x="0" y="0" width="160" height="56" rx="8" fill="#3D7C47" opacity="0.12" stroke="#3D7C47" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="42" height="56" rx="8" fill="#3D7C47"/>
|
||||
<rect x="8" y="0" width="34" height="56" fill="#3D7C47"/>
|
||||
<text x="21" y="34" fill="white" font-size="18" text-anchor="middle">◼</text>
|
||||
<text x="100" y="25" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">TO</text>
|
||||
<text x="100" y="42" fill="#5C5347" font-size="11" text-anchor="middle">kafka:processed</text>
|
||||
</g>
|
||||
|
||||
<!-- CHOICE compound on the right -->
|
||||
<g transform="translate(210, 10)">
|
||||
<rect x="0" y="0" width="180" height="210" rx="10" fill="#7C3AED" opacity="0.06" stroke="#7C3AED" stroke-width="1.5" stroke-dasharray="4 2"/>
|
||||
<text x="10" y="20" fill="#7C3AED" font-size="11" font-weight="600">CHOICE</text>
|
||||
<!-- When child -->
|
||||
<g transform="translate(10, 30)">
|
||||
<rect x="0" y="0" width="160" height="48" rx="6" fill="#7C3AED" opacity="0.12" stroke="#7C3AED" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="36" height="48" rx="6" fill="#7C3AED"/>
|
||||
<rect x="6" y="0" width="30" height="48" fill="#7C3AED"/>
|
||||
<text x="18" y="30" fill="white" font-size="14" text-anchor="middle">◆</text>
|
||||
<text x="96" y="20" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">WHEN</text>
|
||||
<text x="96" y="36" fill="#5C5347" font-size="10" text-anchor="middle">header.type == 'A'</text>
|
||||
</g>
|
||||
<!-- Otherwise child -->
|
||||
<g transform="translate(10, 88)">
|
||||
<rect x="0" y="0" width="160" height="48" rx="6" fill="#7C3AED" opacity="0.12" stroke="#7C3AED" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="36" height="48" rx="6" fill="#7C3AED"/>
|
||||
<rect x="6" y="0" width="30" height="48" fill="#7C3AED"/>
|
||||
<text x="18" y="30" fill="white" font-size="14" text-anchor="middle">◆</text>
|
||||
<text x="96" y="20" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">OTHERWISE</text>
|
||||
<text x="96" y="36" fill="#5C5347" font-size="10" text-anchor="middle">default branch</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>A: Icon Sidebar Blocks</h3>
|
||||
<p>MuleSoft-style: colored icon strip on the left, label + detail on the right. Color encodes node type. Compound nodes (choice, split) use dashed containers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option B: Rounded pill with centered icon -->
|
||||
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
|
||||
<svg width="100%" height="220" viewBox="0 0 400 220">
|
||||
<!-- FROM node -->
|
||||
<g transform="translate(20, 10)">
|
||||
<rect x="0" y="0" width="160" height="50" rx="25" fill="#1A7F8E" opacity="0.15" stroke="#1A7F8E" stroke-width="1.5"/>
|
||||
<circle cx="30" cy="25" r="16" fill="#1A7F8E"/>
|
||||
<text x="30" y="31" fill="white" font-size="14" text-anchor="middle">▶</text>
|
||||
<text x="98" y="22" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">FROM</text>
|
||||
<text x="98" y="38" fill="#5C5347" font-size="10" text-anchor="middle">direct:orders</text>
|
||||
</g>
|
||||
<!-- Connector -->
|
||||
<line x1="100" y1="60" x2="100" y2="80" stroke="#9C9184" stroke-width="1.5"/>
|
||||
<polygon points="95,76 100,84 105,76" fill="#9C9184"/>
|
||||
<!-- PROCESS node -->
|
||||
<g transform="translate(20, 84)">
|
||||
<rect x="0" y="0" width="160" height="50" rx="25" fill="#C6820E" opacity="0.15" stroke="#C6820E" stroke-width="1.5"/>
|
||||
<circle cx="30" cy="25" r="16" fill="#C6820E"/>
|
||||
<text x="30" y="31" fill="white" font-size="14" text-anchor="middle">⚙</text>
|
||||
<text x="98" y="22" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">LOG</text>
|
||||
<text x="98" y="38" fill="#5C5347" font-size="10" text-anchor="middle">Processing order</text>
|
||||
</g>
|
||||
<!-- Connector -->
|
||||
<line x1="100" y1="134" x2="100" y2="154" stroke="#9C9184" stroke-width="1.5"/>
|
||||
<polygon points="95,150 100,158 105,150" fill="#9C9184"/>
|
||||
<!-- TO node -->
|
||||
<g transform="translate(20, 158)">
|
||||
<rect x="0" y="0" width="160" height="50" rx="25" fill="#3D7C47" opacity="0.15" stroke="#3D7C47" stroke-width="1.5"/>
|
||||
<circle cx="30" cy="25" r="16" fill="#3D7C47"/>
|
||||
<text x="30" y="31" fill="white" font-size="14" text-anchor="middle">◼</text>
|
||||
<text x="98" y="22" fill="#1A1612" font-size="12" font-weight="600" text-anchor="middle">TO</text>
|
||||
<text x="98" y="38" fill="#5C5347" font-size="10" text-anchor="middle">kafka:processed</text>
|
||||
</g>
|
||||
|
||||
<!-- CHOICE compound on the right -->
|
||||
<g transform="translate(210, 10)">
|
||||
<rect x="0" y="0" width="180" height="200" rx="12" fill="#7C3AED" opacity="0.06" stroke="#7C3AED" stroke-width="1.5" stroke-dasharray="5 3"/>
|
||||
<text x="90" y="20" fill="#7C3AED" font-size="11" font-weight="600" text-anchor="middle">CHOICE</text>
|
||||
<!-- When child -->
|
||||
<g transform="translate(10, 30)">
|
||||
<rect x="0" y="0" width="160" height="44" rx="22" fill="#7C3AED" opacity="0.15" stroke="#7C3AED" stroke-width="1"/>
|
||||
<circle cx="26" cy="22" r="14" fill="#7C3AED"/>
|
||||
<text x="26" y="28" fill="white" font-size="12" text-anchor="middle">◆</text>
|
||||
<text x="96" y="18" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">WHEN</text>
|
||||
<text x="96" y="34" fill="#5C5347" font-size="10" text-anchor="middle">type == 'A'</text>
|
||||
</g>
|
||||
<!-- Otherwise child -->
|
||||
<g transform="translate(10, 84)">
|
||||
<rect x="0" y="0" width="160" height="44" rx="22" fill="#7C3AED" opacity="0.15" stroke="#7C3AED" stroke-width="1"/>
|
||||
<circle cx="26" cy="22" r="14" fill="#7C3AED"/>
|
||||
<text x="26" y="28" fill="white" font-size="12" text-anchor="middle">◆</text>
|
||||
<text x="96" y="18" fill="#1A1612" font-size="11" font-weight="600" text-anchor="middle">OTHERWISE</text>
|
||||
<text x="96" y="34" fill="#5C5347" font-size="10" text-anchor="middle">default</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>B: Rounded Pills</h3>
|
||||
<p>Softer, more modern look with pill-shaped nodes and circular icons. Lighter feel. Compounds still use dashed containers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option C: TIBCO BW5 style - rectangular with top color bar -->
|
||||
<div class="card" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="padding: 24px; background: #F5F2ED;">
|
||||
<svg width="100%" height="220" viewBox="0 0 400 220">
|
||||
<!-- FROM node -->
|
||||
<g transform="translate(20, 10)">
|
||||
<rect x="0" y="0" width="160" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="160" height="6" rx="4" fill="#1A7F8E"/>
|
||||
<rect x="4" y="0" width="152" height="6" fill="#1A7F8E"/>
|
||||
<text x="18" y="32" fill="#1A7F8E" font-size="16">▶</text>
|
||||
<text x="40" y="30" fill="#1A1612" font-size="12" font-weight="600">FROM</text>
|
||||
<text x="40" y="46" fill="#5C5347" font-size="10">direct:orders</text>
|
||||
</g>
|
||||
<!-- Connector -->
|
||||
<line x1="100" y1="66" x2="100" y2="86" stroke="#9C9184" stroke-width="1.5"/>
|
||||
<polygon points="95,82 100,90 105,82" fill="#9C9184"/>
|
||||
<!-- PROCESS node -->
|
||||
<g transform="translate(20, 90)">
|
||||
<rect x="0" y="0" width="160" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="160" height="6" rx="4" fill="#C6820E"/>
|
||||
<rect x="4" y="0" width="152" height="6" fill="#C6820E"/>
|
||||
<text x="18" y="32" fill="#C6820E" font-size="16">⚙</text>
|
||||
<text x="40" y="30" fill="#1A1612" font-size="12" font-weight="600">LOG</text>
|
||||
<text x="40" y="46" fill="#5C5347" font-size="10">Processing order</text>
|
||||
</g>
|
||||
<!-- Connector -->
|
||||
<line x1="100" y1="146" x2="100" y2="166" stroke="#9C9184" stroke-width="1.5"/>
|
||||
<polygon points="95,162 100,170 105,162" fill="#9C9184"/>
|
||||
<!-- TO node -->
|
||||
<g transform="translate(20, 170)">
|
||||
<rect x="0" y="0" width="160" height="56" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="160" height="6" rx="4" fill="#3D7C47"/>
|
||||
<rect x="4" y="0" width="152" height="6" fill="#3D7C47"/>
|
||||
<text x="18" y="32" fill="#3D7C47" font-size="16">◼</text>
|
||||
<text x="40" y="30" fill="#1A1612" font-size="12" font-weight="600">TO</text>
|
||||
<text x="40" y="46" fill="#5C5347" font-size="10">kafka:processed</text>
|
||||
</g>
|
||||
|
||||
<!-- CHOICE compound on the right -->
|
||||
<g transform="translate(210, 10)">
|
||||
<rect x="0" y="0" width="180" height="210" rx="4" fill="white" stroke="#7C3AED" stroke-width="1.5"/>
|
||||
<rect x="0" y="0" width="180" height="22" rx="4" fill="#7C3AED"/>
|
||||
<rect x="4" y="4" width="172" height="18" fill="#7C3AED"/>
|
||||
<text x="90" y="16" fill="white" font-size="11" font-weight="600" text-anchor="middle">CHOICE</text>
|
||||
<!-- When child -->
|
||||
<g transform="translate(10, 32)">
|
||||
<rect x="0" y="0" width="160" height="48" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="160" height="5" rx="4" fill="#7C3AED"/>
|
||||
<rect x="4" y="0" width="152" height="5" fill="#7C3AED"/>
|
||||
<text x="14" y="28" fill="#7C3AED" font-size="14">◆</text>
|
||||
<text x="34" y="26" fill="#1A1612" font-size="11" font-weight="600">WHEN</text>
|
||||
<text x="34" y="40" fill="#5C5347" font-size="10">type == 'A'</text>
|
||||
</g>
|
||||
<!-- Otherwise child -->
|
||||
<g transform="translate(10, 90)">
|
||||
<rect x="0" y="0" width="160" height="48" rx="4" fill="white" stroke="#E4DFD8" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="160" height="5" rx="4" fill="#7C3AED"/>
|
||||
<rect x="4" y="0" width="152" height="5" fill="#7C3AED"/>
|
||||
<text x="14" y="28" fill="#7C3AED" font-size="14">◆</text>
|
||||
<text x="34" y="26" fill="#1A1612" font-size="11" font-weight="600">OTHERWISE</text>
|
||||
<text x="34" y="40" fill="#5C5347" font-size="10">default</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>C: Top-Bar Cards</h3>
|
||||
<p>TIBCO BW5-inspired: white cards with colored top accent bar. Clean, professional, card-like. Compound nodes get a full colored header bar with white title text.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
.superpowers/brainstorm/10188-1774613058/waiting.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
1
.superpowers/brainstorm/14618-1774629192/.server-stopped
Normal file
@@ -0,0 +1 @@
|
||||
{"reason":"idle timeout","timestamp":1774632733532}
|
||||
1
.superpowers/brainstorm/14618-1774629192/.server.pid
Normal file
@@ -0,0 +1 @@
|
||||
14618
|
||||
287
.superpowers/brainstorm/14618-1774629192/detail-panel-tabs.html
Normal file
@@ -0,0 +1,287 @@
|
||||
<h2>Detail Panel: Tab Designs</h2>
|
||||
<p class="subtitle">Bottom panel content when a processor node is selected</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Info Tab — processor metadata + attributes</div>
|
||||
<div class="mockup-body" style="background: #fff; padding: 0;">
|
||||
<!-- Processor header -->
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">bean:validate</span>
|
||||
<span style="font-size: 10px; color: #C0392B; background: #FDF2F0; padding: 1px 6px; border-radius: 8px;">FAILED</span>
|
||||
<span style="font-size: 10px; color: #9C9184;">processor-5</span>
|
||||
</div>
|
||||
<!-- Tabs -->
|
||||
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Info</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C0392B; cursor: pointer;">Error</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
|
||||
</div>
|
||||
<!-- Info content -->
|
||||
<div style="padding: 12px 14px; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px 24px; font-size: 12px;">
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Processor ID</div>
|
||||
<div style="color: #1A1612; font-family: monospace; font-size: 11px;">processor-5</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Type</div>
|
||||
<div style="color: #1A1612;">BEAN</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Status</div>
|
||||
<div style="color: #C0392B; font-weight: 500;">FAILED</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Start Time</div>
|
||||
<div style="color: #1A1612;">14:32:05.123</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">End Time</div>
|
||||
<div style="color: #1A1612;">14:32:05.243</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Duration</div>
|
||||
<div style="color: #1A1612; font-weight: 500;">120ms</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Endpoint URI</div>
|
||||
<div style="color: #1A1612; font-family: monospace; font-size: 11px;">bean:orderValidator?method=validate</div>
|
||||
</div>
|
||||
<div style="grid-column: span 2;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Resolved URI</div>
|
||||
<div style="color: #1A1612; font-family: monospace; font-size: 11px;">bean://com.example.OrderValidator?method=validate</div>
|
||||
</div>
|
||||
<!-- Attributes from taps -->
|
||||
<div style="grid-column: span 3; border-top: 1px solid #E4DFD8; padding-top: 8px; margin-top: 4px;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px;">Attributes</div>
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
||||
<span style="font-size: 10px; padding: 2px 8px; background: #F5F0EA; border-radius: 10px; color: #5C5347;">orderId: <strong>ORD-1234</strong></span>
|
||||
<span style="font-size: 10px; padding: 2px 8px; background: #F5F0EA; border-radius: 10px; color: #5C5347;">customer: <strong>Acme Corp</strong></span>
|
||||
<span style="font-size: 10px; padding: 2px 8px; background: #F5F0EA; border-radius: 10px; color: #5C5347;">priority: <strong>HIGH</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Headers Tab — input vs output side by side</div>
|
||||
<div class="mockup-body" style="background: #fff; padding: 0;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">log:incoming</span>
|
||||
<span style="font-size: 10px; color: #3D7C47; background: #F0F9F1; padding: 1px 6px; border-radius: 8px;">COMPLETED</span>
|
||||
<span style="font-size: 10px; color: #9C9184;">processor-2</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Headers</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Error</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
|
||||
</div>
|
||||
<!-- Headers side by side -->
|
||||
<div style="display: flex; gap: 0; padding: 0;">
|
||||
<!-- Input headers -->
|
||||
<div style="flex: 1; padding: 10px 14px; border-right: 1px solid #E4DFD8;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Input Headers</div>
|
||||
<table style="width: 100%; font-size: 11px; border-collapse: collapse;">
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500; width: 40%;">Content-Type</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">application/json</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">JMSMessageID</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">ID:broker-42</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">breadcrumbId</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">abc-123-def</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">CamelHttpMethod</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">POST</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Output headers -->
|
||||
<div style="flex: 1; padding: 10px 14px;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Output Headers</div>
|
||||
<table style="width: 100%; font-size: 11px; border-collapse: collapse;">
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500; width: 40%;">Content-Type</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">application/json</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">JMSMessageID</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">ID:broker-42</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">breadcrumbId</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">abc-123-def</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #F5F0EA;">
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500;">CamelHttpMethod</td>
|
||||
<td style="padding: 3px 0; color: #1A1612; font-family: monospace; font-size: 10px;">POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 3px 0; color: #5C5347; font-weight: 500; color: #3D7C47;">orderStatus</td>
|
||||
<td style="padding: 3px 0; color: #3D7C47; font-family: monospace; font-size: 10px;">validated <span style="font-size: 9px; color: #9C9184; font-family: sans-serif;">(new)</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Input Tab — formatted message body</div>
|
||||
<div class="mockup-body" style="background: #fff; padding: 0;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">log:incoming</span>
|
||||
<span style="font-size: 10px; color: #3D7C47; background: #F0F9F1; padding: 1px 6px; border-radius: 8px;">COMPLETED</span>
|
||||
<span style="font-size: 10px; color: #9C9184;">processor-2 · 5ms</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Input</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Error</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
|
||||
</div>
|
||||
<!-- Body content with syntax highlighting -->
|
||||
<div style="padding: 10px 14px;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px;">
|
||||
<span style="font-size: 10px; color: #9C9184;">JSON · 234 bytes</span>
|
||||
<button style="font-size: 9px; padding: 2px 8px; border: 1px solid #E4DFD8; background: #FAFAF8; border-radius: 3px; cursor: pointer; color: #5C5347;">Copy</button>
|
||||
</div>
|
||||
<pre style="font-size: 11px; background: #1A1612; color: #E4DFD8; padding: 12px; border-radius: 6px; margin: 0; line-height: 1.6; overflow-x: auto;">{
|
||||
<span style="color: #1A7F8E;">"orderId"</span>: <span style="color: #C6820E;">"ORD-1234"</span>,
|
||||
<span style="color: #1A7F8E;">"customer"</span>: {
|
||||
<span style="color: #1A7F8E;">"name"</span>: <span style="color: #C6820E;">"Acme Corp"</span>,
|
||||
<span style="color: #1A7F8E;">"id"</span>: <span style="color: #7C3AED;">42</span>
|
||||
},
|
||||
<span style="color: #1A7F8E;">"items"</span>: [
|
||||
{
|
||||
<span style="color: #1A7F8E;">"product"</span>: <span style="color: #C6820E;">"Widget A"</span>,
|
||||
<span style="color: #1A7F8E;">"quantity"</span>: <span style="color: #7C3AED;">5</span>,
|
||||
<span style="color: #1A7F8E;">"price"</span>: <span style="color: #7C3AED;">29.99</span>
|
||||
}
|
||||
],
|
||||
<span style="color: #1A7F8E;">"priority"</span>: <span style="color: #C6820E;">"HIGH"</span>
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Timeline Tab — Gantt-style processor durations</div>
|
||||
<div class="mockup-body" style="background: #fff; padding: 0;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">content-based-routing</span>
|
||||
<span style="font-size: 10px; color: #C0392B; background: #FDF2F0; padding: 1px 6px; border-radius: 8px;">FAILED</span>
|
||||
<span style="font-size: 10px; color: #9C9184;">247ms total</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C0392B; cursor: pointer;">Error</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C6820E; border-bottom: 2px solid #C6820E; font-weight: 600; cursor: pointer;">Timeline</div>
|
||||
</div>
|
||||
<!-- Gantt chart -->
|
||||
<div style="padding: 10px 14px;">
|
||||
<!-- Time axis -->
|
||||
<div style="display: flex; justify-content: space-between; font-size: 9px; color: #9C9184; margin-bottom: 4px; padding-left: 110px;">
|
||||
<span>0ms</span><span>50ms</span><span>100ms</span><span>150ms</span><span>200ms</span><span>247ms</span>
|
||||
</div>
|
||||
<!-- Processor rows -->
|
||||
<div style="display: flex; flex-direction: column; gap: 3px;">
|
||||
<!-- from:jms -->
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">from:jms</span>
|
||||
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
|
||||
<div style="position: absolute; left: 0%; width: 0.8%; height: 100%; background: #3D7C47; border-radius: 2px; min-width: 3px;"></div>
|
||||
</div>
|
||||
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">2ms</span>
|
||||
</div>
|
||||
<!-- log -->
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">log</span>
|
||||
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
|
||||
<div style="position: absolute; left: 0.8%; width: 2%; height: 100%; background: #3D7C47; border-radius: 2px; min-width: 3px;"></div>
|
||||
</div>
|
||||
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">5ms</span>
|
||||
</div>
|
||||
<!-- setHeader -->
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">setHeader</span>
|
||||
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
|
||||
<div style="position: absolute; left: 2.8%; width: 0.4%; height: 100%; background: #3D7C47; border-radius: 2px; min-width: 3px;"></div>
|
||||
</div>
|
||||
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">1ms</span>
|
||||
</div>
|
||||
<!-- bean:validate (FAILED - long) -->
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 10px; color: #C0392B; font-weight: 600; width: 100px; text-align: right; flex-shrink: 0;">bean:validate</span>
|
||||
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px; position: relative;">
|
||||
<div style="position: absolute; left: 3.2%; width: 48.6%; height: 100%; background: #C0392B; border-radius: 2px; opacity: 0.8;"></div>
|
||||
</div>
|
||||
<span style="font-size: 9px; color: #C0392B; font-weight: 500; width: 36px; flex-shrink: 0;">120ms</span>
|
||||
</div>
|
||||
<!-- to:http (skipped) -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; opacity: 0.35;">
|
||||
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">to:http</span>
|
||||
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px;"></div>
|
||||
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">—</span>
|
||||
</div>
|
||||
<!-- to:jms (skipped) -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; opacity: 0.35;">
|
||||
<span style="font-size: 10px; color: #5C5347; width: 100px; text-align: right; flex-shrink: 0;">to:jms</span>
|
||||
<div style="flex: 1; height: 16px; background: #F5F0EA; border-radius: 2px;"></div>
|
||||
<span style="font-size: 9px; color: #9C9184; width: 36px; flex-shrink: 0;">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: 10px; color: #9C9184;">Click a bar to select that processor in the diagram</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Error Tab — grayed out when no error on selected processor</div>
|
||||
<div class="mockup-body" style="background: #fff; padding: 0;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: #1A1612;">log:incoming</span>
|
||||
<span style="font-size: 10px; color: #3D7C47; background: #F0F9F1; padding: 1px 6px; border-radius: 8px;">COMPLETED</span>
|
||||
<span style="font-size: 10px; color: #9C9184;">processor-2 · 5ms</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4; cursor: not-allowed;">Error</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.4;">Config</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
|
||||
</div>
|
||||
<div style="padding: 20px 14px; text-align: center; color: #9C9184; font-size: 12px;">
|
||||
No error on this processor
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,207 @@
|
||||
<h2>Execution Overlay: Full Design Mockup</h2>
|
||||
<p class="subtitle">ExecutionDiagram component — diagram with execution overlay + detail panel</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">ExecutionDiagram — Failed Exchange View</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 0;">
|
||||
<!-- Top bar: Exchange summary -->
|
||||
<div style="display: flex; align-items: center; gap: 12px; padding: 8px 14px; background: #fff; border-bottom: 1px solid #E4DFD8; font-size: 12px; color: #5C5347;">
|
||||
<span style="font-weight: 600; color: #1A1612;">Exchange</span>
|
||||
<code style="font-size: 11px; background: #F5F0EA; padding: 2px 6px; border-radius: 3px; color: #1A1612;">abc-123-def-456</code>
|
||||
<span style="background: #C0392B; color: white; font-size: 10px; padding: 1px 8px; border-radius: 10px; font-weight: 600;">FAILED</span>
|
||||
<span style="color: #9C9184;">sample-app / content-based-routing</span>
|
||||
<span style="color: #9C9184;">247ms</span>
|
||||
<div style="margin-left: auto; display: flex; gap: 6px;">
|
||||
<button style="font-size: 10px; padding: 3px 10px; border: 1px solid #C0392B; background: #FDF2F0; color: #C0392B; border-radius: 4px; cursor: pointer; font-weight: 500;">Jump to Error</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main content: Diagram top, Detail bottom -->
|
||||
<div style="display: flex; flex-direction: column; height: 480px;">
|
||||
|
||||
<!-- TOP: Process Diagram with Overlay -->
|
||||
<div style="flex: 1; position: relative; overflow: hidden; background: #fff; border-bottom: 2px solid #E4DFD8;">
|
||||
|
||||
<!-- Breadcrumbs (if drilled down) -->
|
||||
|
||||
<!-- Diagram content -->
|
||||
<div style="padding: 24px 30px;">
|
||||
<!-- Main flow -->
|
||||
<div style="display: flex; align-items: center; gap: 0;">
|
||||
|
||||
<!-- Node: from:jms (COMPLETED) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #1A7F8E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
|
||||
</div>
|
||||
|
||||
<!-- Edge -->
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="30" y2="5" stroke="#9CA3AF" stroke-width="1.5"/><polygon points="25,2 30,5 25,8" fill="#9CA3AF"/></svg>
|
||||
|
||||
<!-- Node: log (COMPLETED) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">log:incoming</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">LOG</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">5ms</div>
|
||||
</div>
|
||||
|
||||
<!-- Edge -->
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="30" y2="5" stroke="#9CA3AF" stroke-width="1.5"/><polygon points="25,2 30,5 25,8" fill="#9CA3AF"/></svg>
|
||||
|
||||
<!-- Node: CHOICE compound -->
|
||||
<div style="position: relative; border: 2px dashed #7C3AED; border-radius: 8px; padding: 0; background: #FAFAFF;">
|
||||
<!-- Compound header -->
|
||||
<div style="background: #7C3AED; color: white; font-size: 10px; font-weight: 600; padding: 3px 10px; border-radius: 5px 5px 0 0;">CHOICE</div>
|
||||
<div style="padding: 10px; display: flex; gap: 16px;">
|
||||
<!-- WHEN branch (taken, failed) -->
|
||||
<div style="border: 1px solid #E4DFD8; border-radius: 5px; padding: 6px; background: #fff;">
|
||||
<div style="font-size: 8px; color: #7C3AED; font-weight: 600; margin-bottom: 4px;">WHEN: header.type == 'A'</div>
|
||||
<div style="display: flex; align-items: center; gap: 0;">
|
||||
<!-- Node: bean (FAILED) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 120px; height: 48px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #C0392B;">bean:validate</div>
|
||||
<div style="font-size: 8px; color: #C0392B;">FAILED</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #C0392B; font-weight: 500;">120ms</div>
|
||||
<!-- Error icon -->
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
|
||||
</div>
|
||||
|
||||
<svg width="20" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="20" y2="5" stroke="#9CA3AF" stroke-width="1"/></svg>
|
||||
|
||||
<!-- Node: to:http (NOT EXECUTED - dimmed) -->
|
||||
<div style="position: relative; opacity: 0.35;">
|
||||
<div style="width: 120px; height: 48px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">to:http:api</div>
|
||||
<div style="font-size: 8px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OTHERWISE branch (not taken - dimmed) -->
|
||||
<div style="border: 1px solid #E4DFD8; border-radius: 5px; padding: 6px; background: #fff; opacity: 0.35;">
|
||||
<div style="font-size: 8px; color: #7C3AED; font-weight: 600; margin-bottom: 4px;">OTHERWISE</div>
|
||||
<div style="width: 120px; height: 48px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">to:direct:alt</div>
|
||||
<div style="font-size: 8px; color: #9C9184;">DIRECT</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zoom controls (bottom-right) -->
|
||||
<div style="position: absolute; bottom: 8px; right: 8px; display: flex; align-items: center; gap: 3px; background: #fff; border: 1px solid #E4DFD8; border-radius: 4px; padding: 3px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);">
|
||||
<button style="width: 22px; height: 22px; border: none; background: transparent; font-size: 12px; cursor: pointer; color: #1A1612;">+</button>
|
||||
<span style="font-size: 9px; color: #9C9184; min-width: 30px; text-align: center;">100%</span>
|
||||
<button style="width: 22px; height: 22px; border: none; background: transparent; font-size: 12px; cursor: pointer; color: #1A1612;">-</button>
|
||||
<button style="width: 22px; height: 22px; border: none; background: transparent; font-size: 10px; cursor: pointer; color: #1A1612;">Fit</button>
|
||||
</div>
|
||||
|
||||
<!-- Minimap (bottom-left) -->
|
||||
<div style="position: absolute; bottom: 8px; left: 8px; width: 100px; height: 60px; background: #fff; border: 1px solid #E4DFD8; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); overflow: hidden;">
|
||||
<div style="padding: 4px;">
|
||||
<div style="display: flex; gap: 2px; align-items: center; transform: scale(0.3); transform-origin: top left;">
|
||||
<div style="width: 60px; height: 20px; background: #1A7F8E; border-radius: 2px;"></div>
|
||||
<div style="width: 60px; height: 20px; background: #C6820E; border-radius: 2px;"></div>
|
||||
<div style="width: 100px; height: 40px; border: 1px solid #7C3AED; border-radius: 2px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SPLITTER -->
|
||||
<div style="height: 4px; background: #E4DFD8; cursor: row-resize; flex-shrink: 0;"></div>
|
||||
|
||||
<!-- BOTTOM: Detail Panel -->
|
||||
<div style="flex: 0 0 180px; background: #fff; display: flex; flex-direction: column;">
|
||||
<!-- Selected processor header -->
|
||||
<div style="display: flex; align-items: center; gap: 10px; padding: 6px 14px; border-bottom: 1px solid #E4DFD8; background: #FAFAF8;">
|
||||
<span style="font-size: 11px; font-weight: 600; color: #C0392B;">bean:validate</span>
|
||||
<span style="font-size: 10px; color: #C0392B; background: #FDF2F0; padding: 1px 6px; border-radius: 8px;">FAILED</span>
|
||||
<span style="font-size: 10px; color: #9C9184;">processor-5 · 120ms</span>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div style="display: flex; gap: 0; border-bottom: 1px solid #E4DFD8; background: #FAFAF8; padding: 0 14px;">
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Info</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Headers</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Input</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Output</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #C0392B; border-bottom: 2px solid #C0392B; font-weight: 600; cursor: pointer;">Error</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer; opacity: 0.5;">Config</div>
|
||||
<div style="font-size: 11px; padding: 6px 12px; color: #9C9184; cursor: pointer;">Timeline</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab content: Error -->
|
||||
<div style="flex: 1; padding: 10px 14px; overflow-y: auto;">
|
||||
<div style="margin-bottom: 8px;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Exception</div>
|
||||
<div style="font-size: 12px; color: #C0392B; font-weight: 500;">javax.validation.ValidationException</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Message</div>
|
||||
<div style="font-size: 12px; color: #1A1612;">Order quantity must be positive: received -3</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Root Cause</div>
|
||||
<div style="font-size: 12px; color: #C0392B;">java.lang.IllegalArgumentException: quantity must be > 0</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 10px; color: #9C9184; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px;">Stack Trace</div>
|
||||
<pre style="font-size: 10px; color: #5C5347; background: #F5F0EA; padding: 8px; border-radius: 4px; overflow-x: auto; margin: 0; line-height: 1.6;">at com.example.OrderValidator.validate(OrderValidator.java:42)
|
||||
at com.example.OrderRoute.process(OrderRoute.java:18)
|
||||
at org.apache.camel.processor.DelegateSyncProcessor.process(...)
|
||||
at org.apache.camel.processor.Pipeline.process(Pipeline.java:184)
|
||||
... 8 more</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 24px;">
|
||||
<h3>Design Decisions Shown</h3>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px; color: #5C5347;">
|
||||
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #3D7C47;">
|
||||
<strong style="color: #1A1612;">Executed (OK)</strong><br/>
|
||||
Green left border, duration badge bottom-right
|
||||
</div>
|
||||
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #C0392B;">
|
||||
<strong style="color: #1A1612;">Failed</strong><br/>
|
||||
Red border, red tint background, red ! badge top-right
|
||||
</div>
|
||||
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #9C9184;">
|
||||
<strong style="color: #1A1612;">Not Executed</strong><br/>
|
||||
Dimmed to 35% opacity — full topology visible
|
||||
</div>
|
||||
<div style="background: #f8f8f6; padding: 10px; border-radius: 6px; border-left: 3px solid #C6820E;">
|
||||
<strong style="color: #1A1612;">Selected</strong><br/>
|
||||
Amber ring (existing), detail panel updates below
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
143
.superpowers/brainstorm/14618-1774629192/iteration-stepper.html
Normal file
@@ -0,0 +1,143 @@
|
||||
<h2>Per-Compound Iteration Stepper</h2>
|
||||
<p class="subtitle">Each loop/split compound gets its own stepper in the header bar</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Loop with iteration stepper — iteration 3 of 5</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
|
||||
|
||||
<!-- LOOP compound -->
|
||||
<div style="position: relative; border: 2px dashed #7C3AED; border-radius: 8px; background: #FAFAFF; max-width: 600px;">
|
||||
<!-- Compound header with stepper -->
|
||||
<div style="background: #7C3AED; color: white; font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 5px 5px 0 0; display: flex; align-items: center; justify-content: space-between;">
|
||||
<span>LOOP</span>
|
||||
<!-- Iteration stepper -->
|
||||
<div style="display: flex; align-items: center; gap: 4px; background: rgba(255,255,255,0.15); border-radius: 3px; padding: 1px 4px;">
|
||||
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">‹</button>
|
||||
<span style="font-size: 10px; min-width: 30px; text-align: center; font-variant-numeric: tabular-nums;">3 / 5</span>
|
||||
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loop body: showing iteration 3 data -->
|
||||
<div style="padding: 12px; display: flex; align-items: center; gap: 0;">
|
||||
<!-- transform (OK in iteration 3) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 130px; height: 48px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 4px; background: #C6820E;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">transform</div>
|
||||
<div style="font-size: 8px; color: #9C9184;">TRANSFORM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">3ms</div>
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">✓</div>
|
||||
</div>
|
||||
|
||||
<svg width="24" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="20" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="17,2 22,5 17,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- to:http (OK in iteration 3) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 130px; height: 48px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 4px; background: #3D7C47;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">to:http:api</div>
|
||||
<div style="font-size: 8px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">45ms</div>
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">✓</div>
|
||||
</div>
|
||||
|
||||
<svg width="24" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="20" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="17,2 22,5 17,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- log (OK in iteration 3) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 130px; height: 48px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 4px; background: #C6820E;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">log:result</div>
|
||||
<div style="font-size: 8px; color: #9C9184;">LOG</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">1ms</div>
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">✓</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 24px;">Nested Loops</h3>
|
||||
<p class="subtitle">Each compound level has its own independent stepper</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Outer loop (iteration 2/3) containing inner split (branch 1/4)</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
|
||||
|
||||
<!-- Outer LOOP -->
|
||||
<div style="border: 2px dashed #7C3AED; border-radius: 8px; background: #FAFAFF; max-width: 550px;">
|
||||
<div style="background: #7C3AED; color: white; font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 5px 5px 0 0; display: flex; align-items: center; justify-content: space-between;">
|
||||
<span>LOOP</span>
|
||||
<div style="display: flex; align-items: center; gap: 4px; background: rgba(255,255,255,0.15); border-radius: 3px; padding: 1px 4px;">
|
||||
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">‹</button>
|
||||
<span style="font-size: 10px; min-width: 30px; text-align: center;">2 / 3</span>
|
||||
<button style="width: 18px; height: 18px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 10px; display: flex; align-items: center; justify-content: center;">›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 12px;">
|
||||
<div style="display: flex; align-items: center; gap: 0;">
|
||||
|
||||
<!-- Processor before split -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 110px; height: 44px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 5px; border-left: 3px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 4px; background: #C6820E;"></div>
|
||||
<div style="padding: 3px 6px;">
|
||||
<div style="font-size: 9px; font-weight: 600; color: #1A1612;">setBody</div>
|
||||
<div style="font-size: 8px; color: #9C9184;">SET_BODY</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 13px; height: 13px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">✓</div>
|
||||
</div>
|
||||
|
||||
<svg width="20" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="16" y2="5" stroke="#3D7C47" stroke-width="1.5"/></svg>
|
||||
|
||||
<!-- Inner SPLIT -->
|
||||
<div style="border: 2px dashed #7C3AED; border-radius: 6px; background: #F8F7FF;">
|
||||
<div style="background: #9B6AED; color: white; font-size: 10px; font-weight: 600; padding: 3px 8px; border-radius: 3px 3px 0 0; display: flex; align-items: center; justify-content: space-between;">
|
||||
<span>SPLIT</span>
|
||||
<div style="display: flex; align-items: center; gap: 3px; background: rgba(255,255,255,0.15); border-radius: 3px; padding: 1px 3px;">
|
||||
<button style="width: 16px; height: 16px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 9px; display: flex; align-items: center; justify-content: center;">‹</button>
|
||||
<span style="font-size: 9px; min-width: 26px; text-align: center;">1 / 4</span>
|
||||
<button style="width: 16px; height: 16px; border: none; background: rgba(255,255,255,0.2); color: white; border-radius: 2px; cursor: pointer; font-size: 9px; display: flex; align-items: center; justify-content: center;">›</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 8px; display: flex; align-items: center; gap: 0;">
|
||||
<div style="position: relative;">
|
||||
<div style="width: 100px; height: 40px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 4px; border-left: 3px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 3px; background: #3D7C47;"></div>
|
||||
<div style="padding: 2px 5px;">
|
||||
<div style="font-size: 8px; font-weight: 600; color: #1A1612;">to:kafka</div>
|
||||
<div style="font-size: 7px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; top: -4px; right: -4px; width: 12px; height: 12px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white;">✓</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 24px;">Stepper Behavior</h3>
|
||||
<div style="font-size: 13px; color: #5C5347; line-height: 1.8;">
|
||||
<ul style="margin: 0; padding-left: 20px;">
|
||||
<li><strong>Independent per compound</strong> — outer loop at iteration 2, inner split at branch 1</li>
|
||||
<li><strong>Overlay updates per-compound</strong> — stepping the loop re-renders its children's execution data for that iteration</li>
|
||||
<li><strong>CHOICE shows which branch was taken</strong> — no stepper, just highlights the taken branch</li>
|
||||
<li><strong>Keyboard</strong> — when a compound is focused/hovered, left/right arrow keys step through iterations</li>
|
||||
<li><strong>Detail panel syncs</strong> — selecting a processor inside a loop shows that iteration's data</li>
|
||||
</ul>
|
||||
</div>
|
||||
166
.superpowers/brainstorm/14618-1774629192/layout-overview.html
Normal file
@@ -0,0 +1,166 @@
|
||||
<h2>Execution Overlay: Page Layout</h2>
|
||||
<p class="subtitle">How should the diagram + execution details be arranged?</p>
|
||||
|
||||
<div class="cards">
|
||||
<!-- Option A: Horizontal Split -->
|
||||
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="card-image">
|
||||
<div style="padding: 16px; background: #1a1612; border-radius: 6px;">
|
||||
<!-- IDE-style: diagram top, detail bottom -->
|
||||
<div style="display: flex; flex-direction: column; gap: 8px; height: 280px;">
|
||||
<!-- Top: Diagram -->
|
||||
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 12px; position: relative; overflow: hidden;">
|
||||
<div style="font-size: 10px; color: #9C9184; margin-bottom: 8px;">DIAGRAM</div>
|
||||
<!-- Mini route flow mockup -->
|
||||
<div style="display: flex; align-items: center; gap: 6px; margin-left: 20px;">
|
||||
<div style="width: 60px; height: 28px; background: #1A7F8E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">from:jms</div>
|
||||
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 60px; height: 28px; background: #C6820E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #3D7C47;">log</div>
|
||||
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 60px; height: 28px; background: #C0392B; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #C0392B; opacity: 0.9;">bean</div>
|
||||
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 60px; height: 28px; background: #3D7C47; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; opacity: 0.4;">to:http</div>
|
||||
</div>
|
||||
<!-- Zoom controls hint -->
|
||||
<div style="position: absolute; bottom: 6px; right: 6px; font-size: 8px; color: #5C5347; background: #2a2520; padding: 2px 6px; border: 1px solid #3a3530; border-radius: 3px;">100%</div>
|
||||
<!-- Iteration stepper -->
|
||||
<div style="position: absolute; top: 6px; right: 6px; font-size: 8px; color: #C6820E; background: #2a2520; padding: 2px 8px; border: 1px solid #3a3530; border-radius: 3px;">Loop 2/5</div>
|
||||
</div>
|
||||
<!-- Resizable splitter -->
|
||||
<div style="height: 3px; background: #3a3530; border-radius: 2px; cursor: row-resize;"></div>
|
||||
<!-- Bottom: Details -->
|
||||
<div style="flex: 0 0 100px; background: #2a2520; border-radius: 4px; padding: 8px; overflow: hidden;">
|
||||
<div style="display: flex; gap: 12px; font-size: 9px; color: #9C9184; border-bottom: 1px solid #3a3530; padding-bottom: 4px; margin-bottom: 6px;">
|
||||
<span style="color: #C6820E; border-bottom: 2px solid #C6820E; padding-bottom: 3px;">Input</span>
|
||||
<span>Output</span>
|
||||
<span>Headers</span>
|
||||
<span>Error</span>
|
||||
<span>Timeline</span>
|
||||
</div>
|
||||
<div style="font-family: monospace; font-size: 8px; color: #9C9184; line-height: 1.5;">
|
||||
<div>{"orderId": "ORD-1234",</div>
|
||||
<div> "product": "Widget A",</div>
|
||||
<div> "quantity": 5}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>A: Top/Bottom Split (IDE Style)</h3>
|
||||
<p>Diagram on top, tabbed detail panel below. Resizable splitter between them. Maximizes diagram width. Tabs: Input, Output, Headers, Error, Timeline.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Pros</h4><ul><li>Full diagram width</li><li>Familiar IDE pattern</li><li>Detail panel always visible</li></ul></div>
|
||||
<div class="cons"><h4>Cons</h4><ul><li>Vertical space shared</li><li>Diagram shrinks on small screens</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option B: Right Panel -->
|
||||
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="card-image">
|
||||
<div style="padding: 16px; background: #1a1612; border-radius: 6px;">
|
||||
<div style="display: flex; gap: 8px; height: 280px;">
|
||||
<!-- Left: Diagram -->
|
||||
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 12px; position: relative; overflow: hidden;">
|
||||
<div style="font-size: 10px; color: #9C9184; margin-bottom: 8px;">DIAGRAM</div>
|
||||
<div style="display: flex; align-items: center; gap: 6px; margin-left: 10px;">
|
||||
<div style="width: 55px; height: 26px; background: #1A7F8E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white;">from:jms</div>
|
||||
<div style="width: 14px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 55px; height: 26px; background: #C6820E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white; border: 2px solid #3D7C47;">log</div>
|
||||
<div style="width: 14px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 55px; height: 26px; background: #C0392B; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 7px; color: white; border: 2px solid #C0392B;">bean</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 6px; right: 6px; font-size: 8px; color: #5C5347; background: #2a2520; padding: 2px 6px; border: 1px solid #3a3530; border-radius: 3px;">100%</div>
|
||||
</div>
|
||||
<!-- Resizable splitter -->
|
||||
<div style="width: 3px; background: #3a3530; border-radius: 2px; cursor: col-resize;"></div>
|
||||
<!-- Right: Detail Panel -->
|
||||
<div style="flex: 0 0 200px; background: #2a2520; border-radius: 4px; padding: 8px; overflow: hidden;">
|
||||
<div style="font-size: 9px; color: #C6820E; font-weight: 600; margin-bottom: 6px;">log (processor-3)</div>
|
||||
<div style="font-size: 8px; color: #3D7C47; margin-bottom: 8px;">COMPLETED - 12ms</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 2px; font-size: 8px; color: #9C9184; border-bottom: 1px solid #3a3530; padding-bottom: 4px; margin-bottom: 6px;">
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<span style="color: #C6820E; font-weight: 600;">Input</span>
|
||||
<span>Output</span>
|
||||
<span>Headers</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-family: monospace; font-size: 7px; color: #9C9184; line-height: 1.4;">
|
||||
<div>{"orderId": "ORD-1234",</div>
|
||||
<div> "product": "Widget A",</div>
|
||||
<div> "quantity": 5,</div>
|
||||
<div> "price": 29.99}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>B: Left/Right Split</h3>
|
||||
<p>Diagram on left, collapsible detail panel on right. Slide-in when node selected. Diagram keeps full height.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Pros</h4><ul><li>Full diagram height</li><li>Panel can collapse</li><li>Good for wide screens</li></ul></div>
|
||||
<div class="cons"><h4>Cons</h4><ul><li>Steals diagram width</li><li>Tight on narrow screens</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option C: Hybrid -->
|
||||
<div class="card" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="card-image">
|
||||
<div style="padding: 16px; background: #1a1612; border-radius: 6px;">
|
||||
<div style="display: flex; flex-direction: column; gap: 8px; height: 280px;">
|
||||
<!-- Top: Full width diagram -->
|
||||
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 12px; position: relative; overflow: hidden;">
|
||||
<div style="font-size: 10px; color: #9C9184; margin-bottom: 8px;">DIAGRAM</div>
|
||||
<div style="display: flex; align-items: center; gap: 6px; margin-left: 20px;">
|
||||
<div style="width: 60px; height: 28px; background: #1A7F8E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">from:jms</div>
|
||||
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 60px; height: 28px; background: #C6820E; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #3D7C47;">log</div>
|
||||
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 60px; height: 28px; background: #C0392B; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; border: 2px solid #C0392B;">bean</div>
|
||||
<div style="width: 20px; height: 1px; background: #5C5347;"></div>
|
||||
<div style="width: 60px; height: 28px; background: #3D7C47; border-radius: 3px; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; opacity: 0.4;">to:http</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 6px; right: 6px; font-size: 8px; color: #5C5347; background: #2a2520; padding: 2px 6px; border: 1px solid #3a3530; border-radius: 3px;">100%</div>
|
||||
</div>
|
||||
<!-- Bottom: Two-column detail -->
|
||||
<div style="height: 3px; background: #3a3530; border-radius: 2px;"></div>
|
||||
<div style="flex: 0 0 100px; display: flex; gap: 8px;">
|
||||
<!-- Left: Processor list / timeline -->
|
||||
<div style="flex: 0 0 140px; background: #2a2520; border-radius: 4px; padding: 6px; overflow: hidden;">
|
||||
<div style="font-size: 8px; color: #9C9184; margin-bottom: 4px; font-weight: 600;">Processors</div>
|
||||
<div style="font-size: 7px; line-height: 1.8;">
|
||||
<div style="color: #3D7C47; padding: 1px 4px; background: #2a2a20; border-radius: 2px;">from:jms - 2ms</div>
|
||||
<div style="color: #C6820E; padding: 1px 4px; background: #3a3020; border-radius: 2px; border-left: 2px solid #C6820E;">log - 12ms</div>
|
||||
<div style="color: #C0392B; padding: 1px 4px;">bean - FAILED</div>
|
||||
<div style="color: #5C5347; padding: 1px 4px; opacity: 0.5;">to:http - skipped</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right: Selected processor detail -->
|
||||
<div style="flex: 1; background: #2a2520; border-radius: 4px; padding: 6px; overflow: hidden;">
|
||||
<div style="display: flex; gap: 8px; font-size: 8px; color: #9C9184; border-bottom: 1px solid #3a3530; padding-bottom: 3px; margin-bottom: 4px;">
|
||||
<span style="color: #C6820E;">Input</span>
|
||||
<span>Output</span>
|
||||
<span>Headers</span>
|
||||
</div>
|
||||
<div style="font-family: monospace; font-size: 7px; color: #9C9184; line-height: 1.4;">
|
||||
<div>{"orderId": "ORD-1234",</div>
|
||||
<div> "product": "Widget A"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>C: Top/Bottom with Processor List</h3>
|
||||
<p>Diagram on top, bottom split into processor list (left) + detail tabs (right). Clicking processor in list or diagram syncs selection. Most information density.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Pros</h4><ul><li>Processor list as navigation</li><li>Full diagram width</li><li>Maximum information density</li></ul></div>
|
||||
<div class="cons"><h4>Cons</h4><ul><li>More complex layout</li><li>May feel crowded</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
190
.superpowers/brainstorm/14618-1774629192/overlay-intensity.html
Normal file
@@ -0,0 +1,190 @@
|
||||
<h2>Execution Overlay: Visual Intensity Comparison</h2>
|
||||
<p class="subtitle">How strong should the overlay tinting be?</p>
|
||||
|
||||
<div class="split">
|
||||
<!-- Current: Subtle -->
|
||||
<div class="mockup" data-choice="subtle" onclick="toggleSelect(this)">
|
||||
<div class="mockup-header">Current: Subtle (border only)</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 16px;">
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<!-- OK node - border only -->
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 10px; color: #9C9184; width: 70px;">Completed</span>
|
||||
<div style="position: relative; width: 160px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #1A7F8E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Failed node - border only -->
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 10px; color: #9C9184; width: 70px;">Failed</span>
|
||||
<div style="position: relative; width: 160px; height: 52px; background: #fff; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">bean:validate</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">BEAN</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Skipped node -->
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 10px; color: #9C9184; width: 70px;">Skipped</span>
|
||||
<div style="opacity: 0.35; width: 160px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Proposed: Tinted backgrounds -->
|
||||
<div class="mockup" data-choice="tinted" onclick="toggleSelect(this)">
|
||||
<div class="mockup-header">Proposed: Tinted backgrounds</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 16px;">
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<!-- OK node - green tint -->
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 10px; color: #9C9184; width: 70px;">Completed</span>
|
||||
<div style="position: relative; width: 160px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #1A7F8E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Failed node - red tint -->
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 10px; color: #9C9184; width: 70px;">Failed</span>
|
||||
<div style="position: relative; width: 160px; height: 52px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #C0392B;">bean:validate</div>
|
||||
<div style="font-size: 9px; color: #C0392B;">FAILED</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Skipped node -->
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 10px; color: #9C9184; width: 70px;">Skipped</span>
|
||||
<div style="opacity: 0.35; width: 160px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 24px;">Full Flow Comparison</h3>
|
||||
<p class="subtitle">Same route, tinted version — see how it reads at a glance</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Tinted overlay on a full route</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
|
||||
<div style="display: flex; align-items: center; gap: 0;">
|
||||
|
||||
<!-- from:jms (OK) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #1A7F8E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- log (OK) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">log:incoming</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">LOG</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">5ms</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- setHeader (OK) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">setHeader:type</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">SET_HEADER</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">1ms</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- bean:validate (FAILED) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #C0392B;">bean:validate</div>
|
||||
<div style="font-size: 9px; color: #C0392B;">FAILED</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
|
||||
|
||||
<!-- to:http (SKIPPED) -->
|
||||
<div style="opacity: 0.35;">
|
||||
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
|
||||
|
||||
<!-- to:jms (SKIPPED) -->
|
||||
<div style="opacity: 0.35;">
|
||||
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:jms:result</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 16px; font-size: 11px; color: #5C5347;">
|
||||
<strong>Note:</strong> Edges between executed nodes turn green. Edges leading to skipped nodes become dashed gray.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,159 @@
|
||||
<h2>Execution Overlay: Success + Error Markers</h2>
|
||||
<p class="subtitle">Every executed node gets a status badge — green check or red exclamation</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Full route with status markers</div>
|
||||
<div class="mockup-body" style="background: #FAFAF8; padding: 20px;">
|
||||
<div style="display: flex; align-items: center; gap: 0;">
|
||||
|
||||
<!-- from:jms (OK) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #1A7F8E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">from:jms:orders</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">ENDPOINT</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">2ms</div>
|
||||
<!-- Success marker -->
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; font-weight: bold;">✓</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- log (OK) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">log:incoming</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">LOG</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">5ms</div>
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; font-weight: bold;">✓</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- setHeader (OK) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 6px; border-left: 4px solid #3D7C47; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">setHeader:type</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">SET_HEADER</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #3D7C47; font-weight: 500;">1ms</div>
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: white; font-weight: bold;">✓</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="22,2 28,5 22,8" fill="#3D7C47"/></svg>
|
||||
|
||||
<!-- bean:validate (FAILED) -->
|
||||
<div style="position: relative;">
|
||||
<div style="width: 140px; height: 52px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #C6820E;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #C0392B;">bean:validate</div>
|
||||
<div style="font-size: 9px; color: #C0392B;">FAILED</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 2px; right: 6px; font-size: 8px; color: #C0392B; font-weight: 500;">120ms</div>
|
||||
<!-- Error marker -->
|
||||
<div style="position: absolute; top: -6px; right: -6px; width: 16px; height: 16px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: white; font-weight: bold;">!</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
|
||||
|
||||
<!-- to:http (SKIPPED) -->
|
||||
<div style="opacity: 0.35;">
|
||||
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:http:api</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg width="30" height="10" style="flex-shrink:0;"><line x1="0" y1="5" x2="25" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/></svg>
|
||||
|
||||
<!-- to:jms (SKIPPED) -->
|
||||
<div style="opacity: 0.35;">
|
||||
<div style="width: 140px; height: 52px; background: #fff; border: 1px solid #E4DFD8; border-radius: 6px; overflow: hidden;">
|
||||
<div style="height: 5px; background: #3D7C47;"></div>
|
||||
<div style="padding: 4px 8px;">
|
||||
<div style="font-size: 10px; font-weight: 600; color: #1A1612;">to:jms:result</div>
|
||||
<div style="font-size: 9px; color: #9C9184;">TO</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 24px;">Node State Legend</h3>
|
||||
<div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px;">
|
||||
|
||||
<!-- Completed -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
|
||||
<div style="position: relative; width: 80px; height: 36px; background: #F0F9F1; border: 1.5px solid #3D7C47; border-radius: 4px; border-left: 3px solid #3D7C47;">
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; background: #3D7C47; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white;">✓</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #3D7C47;">5ms</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; font-weight: 600; color: #3D7C47;">Completed</div>
|
||||
<div style="font-size: 10px; color: #9C9184;">Green tint + border + check badge + duration</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Failed -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
|
||||
<div style="position: relative; width: 80px; height: 36px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 4px;">
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; font-weight: bold;">!</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #C0392B;">120ms</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; font-weight: 600; color: #C0392B;">Failed</div>
|
||||
<div style="font-size: 10px; color: #9C9184;">Red tint + border + ! badge + duration</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub-route failure -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
|
||||
<div style="position: relative; width: 80px; height: 36px; background: #FDF2F0; border: 2px solid #C0392B; border-radius: 4px;">
|
||||
<div style="position: absolute; top: -5px; right: -5px; width: 14px; height: 14px; background: #C0392B; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; font-weight: bold;">!</div>
|
||||
<div style="position: absolute; bottom: 1px; left: 4px; font-size: 8px; color: #C0392B;">↴</div>
|
||||
<div style="position: absolute; bottom: 1px; right: 4px; font-size: 7px; color: #C0392B;">85ms</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; font-weight: 600; color: #C0392B;">Sub-route Failure</div>
|
||||
<div style="font-size: 10px; color: #9C9184;">Same as failed + drill-down arrow</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skipped -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
|
||||
<div style="opacity: 0.35; width: 80px; height: 36px; background: #fff; border: 1px solid #E4DFD8; border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 12px; font-weight: 600; color: #9C9184;">Skipped</div>
|
||||
<div style="font-size: 10px; color: #9C9184;">35% opacity, no badge, no duration</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 24px;">Edge States</h3>
|
||||
<div style="display: flex; gap: 16px; flex-wrap: wrap; margin-top: 8px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
|
||||
<svg width="60" height="10"><line x1="0" y1="5" x2="50" y2="5" stroke="#3D7C47" stroke-width="1.5"/><polygon points="47,2 53,5 47,8" fill="#3D7C47"/></svg>
|
||||
<div style="font-size: 11px; color: #5C5347;"><strong>Traversed</strong> — green, solid</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px; background: #f8f8f6; padding: 10px 14px; border-radius: 6px;">
|
||||
<svg width="60" height="10"><line x1="0" y1="5" x2="50" y2="5" stroke="#9CA3AF" stroke-width="1" stroke-dasharray="3,3"/><polygon points="47,2 53,5 47,8" fill="#9CA3AF"/></svg>
|
||||
<div style="font-size: 11px; color: #5C5347;"><strong>Not traversed</strong> — gray, dashed</div>
|
||||
</div>
|
||||
</div>
|
||||
3
.superpowers/brainstorm/14618-1774629192/waiting.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
@@ -0,0 +1,181 @@
|
||||
<h2>AppConfigDetailPage — New Sections</h2>
|
||||
<p class="subtitle">Taps overview, route recording map, and compress success toggle added to existing config page</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">AppConfigDetailPage — Full Layout (scrollable)</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
|
||||
|
||||
<!-- Back + Header -->
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:16px;">←</span>
|
||||
<span style="font-size:16px;font-weight:600;">order-service</span>
|
||||
<span style="font-family:monospace;font-size:11px;color:#6b7280;margin-left:8px;">v14 · Updated 3 min ago</span>
|
||||
<div style="margin-left:auto;display:flex;gap:8px;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">✎</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ EXISTING: Logging Section ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Logging</div>
|
||||
<div style="display:flex;gap:24px;">
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Log Forwarding Level</div>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">INFO</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ EXISTING: Observability Section ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Observability</div>
|
||||
<div style="display:flex;gap:24px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Engine Level</div>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">REGULAR</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Payload Capture</div>
|
||||
<span style="background:#2d1f3b;color:#d8b4fe;padding:2px 10px;border-radius:4px;font-size:11px;">BOTH</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Metrics</div>
|
||||
<span style="background:#1a3a2a;color:#86efac;padding:2px 10px;border-radius:4px;font-size:11px;">ON</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Sampling Rate</div>
|
||||
<span style="font-family:monospace;font-size:12px;color:#e0e0e0;">1.0</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Compress Success</div>
|
||||
<span style="background:#3b2f1f;color:#fcd34d;padding:2px 10px;border-radius:4px;font-size:11px;">OFF</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ EXISTING: Traced Processors ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traced Processors</div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:8px;">2 processors with custom capture modes</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor ID</th>
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture Mode</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">unmarshal1</td>
|
||||
<td style="padding:6px 8px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">toDatabase</td>
|
||||
<td style="padding:6px 8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ═══ NEW: Data Extraction Taps ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Data Extraction Taps</div>
|
||||
<span style="font-size:11px;color:#6b7280;">3 taps · manage on route pages</span>
|
||||
</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Attribute</th>
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Expression</th>
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Language</th>
|
||||
<th style="text-align:center;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Enabled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-weight:500;">orderId</td>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:1px 6px;border-radius:4px;">${body.orderId}</span></td>
|
||||
<td style="padding:6px 8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:4px;font-size:10px;">simple</span></td>
|
||||
<td style="padding:6px 8px;text-align:center;"><span style="color:#4ade80;">✓</span></td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-weight:500;">customerId</td>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:1px 6px;border-radius:4px;">${body.customer.id}</span></td>
|
||||
<td style="padding:6px 8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:4px;font-size:10px;">simple</span></td>
|
||||
<td style="padding:6px 8px;text-align:center;"><span style="color:#4ade80;">✓</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 8px;font-weight:500;">orderTotal</td>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:1px 6px;border-radius:4px;">$.total</span></td>
|
||||
<td style="padding:6px 8px;"><span style="background:#3b2f1f;color:#fcd34d;padding:1px 6px;border-radius:4px;font-size:10px;">jsonpath</span></td>
|
||||
<td style="padding:6px 8px;text-align:center;"><span style="color:#6b7280;">✗</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ═══ NEW: Route Recording ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Route Recording</div>
|
||||
<span style="font-size:11px;color:#6b7280;">4 of 5 routes recording</span>
|
||||
</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
|
||||
<th style="text-align:center;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Recording</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processOrder</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processPayment</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">sendNotification</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">handleRefund</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#4b5563;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;left:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">healthCheck</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,149 @@
|
||||
<h2>AppConfigDetailPage — Final Layout</h2>
|
||||
<p class="subtitle">Three clean sections: Settings, Traces & Taps, Route Recording</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">AppConfigDetailPage — Complete</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
|
||||
|
||||
<!-- Back + Header -->
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:16px;">←</span>
|
||||
<span style="font-size:16px;font-weight:600;">order-service</span>
|
||||
<span style="font-family:monospace;font-size:11px;color:#6b7280;margin-left:8px;">v14 · Updated 3 min ago</span>
|
||||
<div style="margin-left:auto;display:flex;gap:8px;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">✎</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Section 1: Settings ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:12px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Settings</div>
|
||||
<div style="display:flex;gap:28px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Log Forwarding</div>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">INFO</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Engine Level</div>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:4px;font-size:11px;">REGULAR</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Payload Capture</div>
|
||||
<span style="background:#2d1f3b;color:#d8b4fe;padding:2px 10px;border-radius:4px;font-size:11px;">BOTH</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Metrics</div>
|
||||
<span style="background:#1a3a2a;color:#86efac;padding:2px 10px;border-radius:4px;font-size:11px;">ON</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Sampling Rate</div>
|
||||
<span style="font-family:monospace;font-size:12px;color:#e0e0e0;">1.0</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:#6b7280;margin-bottom:3px;">Compress Success</div>
|
||||
<span style="background:#3b2f1f;color:#fcd34d;padding:2px 10px;border-radius:4px;font-size:11px;">OFF</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Section 2: Traces & Taps ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;margin-bottom:12px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traces & Taps</div>
|
||||
<span style="font-size:11px;color:#6b7280;">2 traced · 3 taps · manage taps on route pages</span>
|
||||
</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Taps</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
|
||||
<td style="padding:8px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span></td>
|
||||
<td style="padding:8px;">
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderId <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">customerId <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">toDatabase</td>
|
||||
<td style="padding:8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span></td>
|
||||
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;">—</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
|
||||
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;">—</span></td>
|
||||
<td style="padding:8px;">
|
||||
<span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderTotal <span style="color:#6b7280;margin-left:2px;">✗</span></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Section 3: Route Recording ═══ -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Route Recording</div>
|
||||
<span style="font-size:11px;color:#6b7280;">4 of 5 routes recording</span>
|
||||
</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
|
||||
<th style="text-align:center;padding:6px 8px;color:#9ca3af;font-size:11px;font-weight:500;width:80px;">Recording</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processOrder</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">processPayment</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">sendNotification</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">handleRefund</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#4b5563;border-radius:9px;position:relative;margin:0 auto;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;left:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:6px 8px;font-family:monospace;font-size:11px;">healthCheck</td>
|
||||
<td style="padding:6px 8px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
<h2>AppConfigDetailPage — Merged "Traces & Taps" Section</h2>
|
||||
<p class="subtitle">Single table combining traced processors and data extraction taps</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Traces & Taps — Merged Table</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
|
||||
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traces & Taps</div>
|
||||
<span style="font-size:11px;color:#6b7280;">2 traced · 3 taps · manage taps on route pages</span>
|
||||
</div>
|
||||
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Taps</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Processor with both trace + taps -->
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
|
||||
<td style="padding:8px;">
|
||||
<span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span>
|
||||
</td>
|
||||
<td style="padding:8px;">
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderId <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">customerId <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Processor with trace only -->
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">toDatabase</td>
|
||||
<td style="padding:8px;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span>
|
||||
</td>
|
||||
<td style="padding:8px;">
|
||||
<span style="color:#6b7280;font-size:11px;">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Processor with tap only (no trace override) -->
|
||||
<tr>
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
|
||||
<td style="padding:8px;">
|
||||
<span style="color:#6b7280;font-size:11px;">—</span>
|
||||
</td>
|
||||
<td style="padding:8px;">
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;">
|
||||
<span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderTotal <span style="color:#6b7280;margin-left:2px;">✗</span></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px;"></div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Design Notes</h3>
|
||||
<ul style="font-size:14px;line-height:1.8;">
|
||||
<li><strong>One row per processor</strong> that has either a capture override or taps (or both)</li>
|
||||
<li><strong>Capture column:</strong> shows the trace capture mode badge, or em-dash if default</li>
|
||||
<li><strong>Taps column:</strong> attribute name badges with enabled/disabled indicator (✓ / ✗), or em-dash if none</li>
|
||||
<li><strong>Tap badges color-coded by language:</strong> blue = simple, yellow = jsonpath (matches RouteDetail tap table)</li>
|
||||
<li><strong>Edit mode:</strong> capture column becomes a dropdown, taps remain read-only (manage on route pages)</li>
|
||||
<li><strong>Empty state:</strong> "No processor-specific traces or taps configured" with link to route pages</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,150 @@
|
||||
<h2>ExchangeDetail — Business Attributes & Replay</h2>
|
||||
<p class="subtitle">New elements added to the existing exchange detail page</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Exchange Detail Page — Header Card (enhanced)</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
|
||||
|
||||
<!-- Exchange Header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px;">
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
|
||||
<span style="width:10px;height:10px;border-radius:50%;background:#4ade80;display:inline-block;"></span>
|
||||
<span style="font-family:monospace;font-size:15px;font-weight:600;">a1b2c3d4-e5f6-7890-abcd-ef1234567890</span>
|
||||
<span style="background:#065f46;color:#6ee7b7;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;">COMPLETED</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:16px;font-size:12px;color:#9ca3af;">
|
||||
<span>Route: <span style="color:#60a5fa;">processOrder</span></span>
|
||||
<span>App: <span style="font-family:monospace;">order-service</span></span>
|
||||
<span>Correlation: <span style="font-family:monospace;">corr-abc123</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<!-- REPLAY BUTTON (NEW) -->
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:6px;">
|
||||
↻ Replay
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Business Attributes Strip (NEW) -->
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;padding:10px 14px;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;margin-bottom:16px;">
|
||||
<span style="font-size:11px;color:#9ca3af;margin-right:4px;line-height:24px;">Attributes</span>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">orderId: ORD-2024-78542</span>
|
||||
<span style="background:#3b1f4b;color:#d8b4fe;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">customerId: CUST-1234</span>
|
||||
<span style="background:#1a3a2a;color:#86efac;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">orderTotal: €149.90</span>
|
||||
<span style="background:#3b2f1f;color:#fcd34d;padding:2px 10px;border-radius:12px;font-size:11px;font-family:monospace;">region: EU-WEST</span>
|
||||
</div>
|
||||
|
||||
<!-- Stat boxes row -->
|
||||
<div style="display:flex;gap:12px;">
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
|
||||
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Duration</div>
|
||||
<div style="font-size:18px;font-weight:600;color:#4ade80;">245ms</div>
|
||||
</div>
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
|
||||
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Agent</div>
|
||||
<div style="font-size:14px;font-family:monospace;color:#e0e0e0;">order-svc-01</div>
|
||||
</div>
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
|
||||
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Started</div>
|
||||
<div style="font-size:14px;font-family:monospace;color:#e0e0e0;">14:23:45.123</div>
|
||||
</div>
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:10px 14px;">
|
||||
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Processors</div>
|
||||
<div style="font-size:18px;font-weight:600;color:#e0e0e0;">12</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Replay Confirmation Dialog</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:24px;width:480px;box-shadow:0 20px 60px rgba(0,0,0,0.5);">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
||||
<span style="font-size:15px;font-weight:600;">Replay Exchange</span>
|
||||
<span style="color:#9ca3af;cursor:pointer;">✕</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#9ca3af;margin-bottom:16px;">
|
||||
This will re-execute the exchange on the target agent. The original exchange data will be used as input.
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;">Original Exchange</div>
|
||||
<div style="font-family:monospace;font-size:12px;background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;">a1b2c3d4-e5f6-7890-abcd-ef1234567890</div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;">Target Agent</div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span style="font-family:monospace;">order-svc-01</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;">Route</div>
|
||||
<div style="font-family:monospace;font-size:12px;background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;">processOrder</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;">
|
||||
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">↻ Replay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Dashboard — Exchanges Table (with business attributes)</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:16px;font-family:system-ui,-apple-system,sans-serif;font-size:12px;">
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;text-align:left;">
|
||||
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Status</th>
|
||||
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
|
||||
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">App</th>
|
||||
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Attributes</th>
|
||||
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Exchange ID</th>
|
||||
<th style="padding:8px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #1e1e3a;">
|
||||
<td style="padding:8px 12px;"><span style="width:8px;height:8px;border-radius:50%;background:#4ade80;display:inline-block;"></span> <span style="color:#6ee7b7;font-size:11px;">OK</span></td>
|
||||
<td style="padding:8px 12px;color:#60a5fa;">processOrder</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;">order-svc</td>
|
||||
<td style="padding:8px 12px;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:8px;font-size:10px;font-family:monospace;">ORD-78542</span>
|
||||
<span style="background:#3b1f4b;color:#d8b4fe;padding:1px 6px;border-radius:8px;font-size:10px;font-family:monospace;">CUST-1234</span>
|
||||
</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;">a1b2c3d4-e5f6…</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;color:#4ade80;">245ms</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #1e1e3a;">
|
||||
<td style="padding:8px 12px;"><span style="width:8px;height:8px;border-radius:50%;background:#f87171;display:inline-block;"></span> <span style="color:#fca5a5;font-size:11px;">ERR</span></td>
|
||||
<td style="padding:8px 12px;color:#60a5fa;">processPayment</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;">payment-svc</td>
|
||||
<td style="padding:8px 12px;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 6px;border-radius:8px;font-size:10px;font-family:monospace;">PAY-91023</span>
|
||||
<span style="color:#6b7280;font-size:10px;">+2</span>
|
||||
</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;">f8e7d6c5-b4a3…</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;color:#f87171;">1,234ms</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #1e1e3a;">
|
||||
<td style="padding:8px 12px;"><span style="width:8px;height:8px;border-radius:50%;background:#4ade80;display:inline-block;"></span> <span style="color:#6ee7b7;font-size:11px;">OK</span></td>
|
||||
<td style="padding:8px 12px;color:#60a5fa;">sendNotification</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;">notif-svc</td>
|
||||
<td style="padding:8px 12px;"><span style="color:#6b7280;font-size:10px;font-style:italic;">—</span></td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;">12345678-abcd…</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;color:#4ade80;">89ms</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="margin-top:12px;font-size:11px;color:#6b7280;">
|
||||
Note: Attributes column shows first 2 values as compact badges, "+N" overflow indicator when more exist. Em-dash when no attributes extracted.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,138 @@
|
||||
<h2>Replay Dialog — Revised</h2>
|
||||
<p class="subtitle">Target agent selection + editable payload and headers</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Replay Exchange Dialog (large modal)</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:640px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
|
||||
|
||||
<!-- Dialog header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
|
||||
<span style="font-size:15px;font-weight:600;">Replay Exchange</span>
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:18px;">✕</span>
|
||||
</div>
|
||||
|
||||
<div style="padding:20px;">
|
||||
<!-- Warning -->
|
||||
<div style="font-size:12px;color:#fbbf24;background:#3b2f1f;border:1px solid #854d0e;border-radius:6px;padding:8px 12px;margin-bottom:16px;display:flex;align-items:center;gap:8px;">
|
||||
<span>⚠</span> This will re-execute the exchange on the selected agent.
|
||||
</div>
|
||||
|
||||
<!-- Target Agent -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target Agent</div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span style="font-family:monospace;">order-svc-01</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#6b7280;margin-top:4px;">Only LIVE agents for this application are shown</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs: Headers / Body -->
|
||||
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
|
||||
<div style="padding:8px 16px;font-size:12px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Headers</div>
|
||||
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Body</div>
|
||||
</div>
|
||||
|
||||
<!-- Headers tab content -->
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:12px;margin-bottom:16px;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:11px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:4px 8px;color:#9ca3af;font-weight:500;width:35%;">Key</th>
|
||||
<th style="text-align:left;padding:4px 8px;color:#9ca3af;font-weight:500;">Value</th>
|
||||
<th style="width:32px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #1e1e3a;">
|
||||
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="Content-Type" /></td>
|
||||
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="application/json" /></td>
|
||||
<td style="padding:4px 8px;text-align:center;"><span style="color:#f87171;cursor:pointer;font-size:14px;">✕</span></td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #1e1e3a;">
|
||||
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="X-Correlation-Id" /></td>
|
||||
<td style="padding:4px 8px;"><input style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:4px;color:#e0e0e0;padding:4px 8px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;" value="corr-abc123" /></td>
|
||||
<td style="padding:4px 8px;text-align:center;"><span style="color:#f87171;cursor:pointer;font-size:14px;">✕</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" style="padding:6px 8px;">
|
||||
<span style="color:#3b82f6;cursor:pointer;font-size:11px;">+ Add header</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
|
||||
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">↻ Replay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Replay Dialog — Body Tab</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:640px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
|
||||
|
||||
<!-- Dialog header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
|
||||
<span style="font-size:15px;font-weight:600;">Replay Exchange</span>
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:18px;">✕</span>
|
||||
</div>
|
||||
|
||||
<div style="padding:20px;">
|
||||
<!-- Warning -->
|
||||
<div style="font-size:12px;color:#fbbf24;background:#3b2f1f;border:1px solid #854d0e;border-radius:6px;padding:8px 12px;margin-bottom:16px;display:flex;align-items:center;gap:8px;">
|
||||
<span>⚠</span> This will re-execute the exchange on the selected agent.
|
||||
</div>
|
||||
|
||||
<!-- Target Agent (collapsed) -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target Agent</div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span style="font-family:monospace;">order-svc-01</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs: Headers / Body -->
|
||||
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
|
||||
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Headers</div>
|
||||
<div style="padding:8px 16px;font-size:12px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Body</div>
|
||||
</div>
|
||||
|
||||
<!-- Body tab content — editable code area -->
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:0;margin-bottom:16px;position:relative;">
|
||||
<div style="display:flex;justify-content:flex-end;padding:6px 8px;border-bottom:1px solid #2d2d50;">
|
||||
<span style="font-size:10px;color:#6b7280;background:#1a1a2e;padding:2px 8px;border-radius:4px;">JSON</span>
|
||||
</div>
|
||||
<pre style="margin:0;padding:12px;font-family:monospace;font-size:11px;line-height:1.6;color:#e0e0e0;min-height:160px;overflow:auto;white-space:pre;"><span style="color:#9ca3af;">{</span>
|
||||
<span style="color:#7dd3fc;">"orderId"</span><span style="color:#9ca3af;">:</span> <span style="color:#fcd34d;">"ORD-2024-78542"</span><span style="color:#9ca3af;">,</span>
|
||||
<span style="color:#7dd3fc;">"customerId"</span><span style="color:#9ca3af;">:</span> <span style="color:#fcd34d;">"CUST-1234"</span><span style="color:#9ca3af;">,</span>
|
||||
<span style="color:#7dd3fc;">"items"</span><span style="color:#9ca3af;">:</span> <span style="color:#9ca3af;">[</span>
|
||||
<span style="color:#9ca3af;">{</span>
|
||||
<span style="color:#7dd3fc;">"sku"</span><span style="color:#9ca3af;">:</span> <span style="color:#fcd34d;">"WIDGET-001"</span><span style="color:#9ca3af;">,</span>
|
||||
<span style="color:#7dd3fc;">"qty"</span><span style="color:#9ca3af;">:</span> <span style="color:#c4b5fd;">3</span><span style="color:#9ca3af;">,</span>
|
||||
<span style="color:#7dd3fc;">"price"</span><span style="color:#9ca3af;">:</span> <span style="color:#c4b5fd;">49.97</span>
|
||||
<span style="color:#9ca3af;">}</span>
|
||||
<span style="color:#9ca3af;">],</span>
|
||||
<span style="color:#7dd3fc;">"total"</span><span style="color:#9ca3af;">:</span> <span style="color:#c4b5fd;">149.90</span>
|
||||
<span style="color:#9ca3af;">}</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
|
||||
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">↻ Replay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,221 @@
|
||||
<h2>RouteDetail — Tap Management & Recording Toggle</h2>
|
||||
<p class="subtitle">New "Taps" tab on RouteDetail + recording toggle in header</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">RouteDetail Page — Header with Recording Toggle</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
|
||||
|
||||
<!-- Route header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px;">
|
||||
<div>
|
||||
<div style="font-size:16px;font-weight:600;margin-bottom:4px;">processOrder</div>
|
||||
<div style="font-size:12px;color:#9ca3af;">
|
||||
<span style="font-family:monospace;">order-service</span>
|
||||
<span style="margin:0 8px;color:#2d2d50;">|</span>
|
||||
<span style="color:#4ade80;">99.2% success</span>
|
||||
<span style="margin:0 8px;color:#2d2d50;">|</span>
|
||||
<span>245ms avg</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<!-- Recording toggle -->
|
||||
<div style="display:flex;align-items:center;gap:8px;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:6px 12px;">
|
||||
<span style="font-size:11px;color:#9ca3af;">Recording</span>
|
||||
<div style="width:36px;height:20px;background:#3b82f6;border-radius:10px;position:relative;cursor:pointer;">
|
||||
<div style="width:16px;height:16px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;transition:all 0.2s;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI strip (abbreviated) -->
|
||||
<div style="display:flex;gap:10px;margin-bottom:16px;">
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
|
||||
<div style="font-size:10px;color:#9ca3af;">Success Rate</div>
|
||||
<div style="font-size:16px;font-weight:600;color:#4ade80;">99.2%</div>
|
||||
</div>
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
|
||||
<div style="font-size:10px;color:#9ca3af;">Avg Duration</div>
|
||||
<div style="font-size:16px;font-weight:600;">245ms</div>
|
||||
</div>
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
|
||||
<div style="font-size:10px;color:#9ca3af;">Total</div>
|
||||
<div style="font-size:16px;font-weight:600;">12,482</div>
|
||||
</div>
|
||||
<div style="flex:1;background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:8px 12px;">
|
||||
<div style="font-size:10px;color:#9ca3af;">Active Taps</div>
|
||||
<div style="font-size:16px;font-weight:600;color:#60a5fa;">3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div style="display:flex;gap:0;border-bottom:1px solid #2d2d50;margin-bottom:16px;">
|
||||
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Overview</div>
|
||||
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Processors</div>
|
||||
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Errors</div>
|
||||
<div style="padding:8px 16px;font-size:12px;color:#9ca3af;cursor:pointer;">Executions</div>
|
||||
<div style="padding:8px 16px;font-size:12px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Taps</div>
|
||||
</div>
|
||||
|
||||
<!-- Taps tab content -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<div style="font-size:13px;font-weight:600;">Data Extraction Taps</div>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;display:flex;align-items:center;gap:4px;">+ Add Tap</button>
|
||||
</div>
|
||||
|
||||
<!-- Taps table -->
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;overflow:hidden;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Attribute</th>
|
||||
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
|
||||
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Expression</th>
|
||||
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Language</th>
|
||||
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Target</th>
|
||||
<th style="text-align:left;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Type</th>
|
||||
<th style="text-align:center;padding:10px 12px;color:#9ca3af;font-size:11px;font-weight:500;">Enabled</th>
|
||||
<th style="width:60px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px 12px;font-weight:500;">orderId</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:2px 6px;border-radius:4px;">${body.orderId}</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">simple</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">OUTPUT</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#1a3a2a;color:#86efac;padding:1px 8px;border-radius:4px;font-size:10px;">BUSINESS</span></td>
|
||||
<td style="padding:8px 12px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px 12px;text-align:center;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">✎</span>
|
||||
<span style="color:#f87171;cursor:pointer;font-size:14px;margin-left:6px;" title="Delete">✕</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px 12px;font-weight:500;">customerId</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;color:#60a5fa;">unmarshal1</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:2px 6px;border-radius:4px;">${body.customer.id}</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">simple</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">OUTPUT</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#1a3a2a;color:#86efac;padding:1px 8px;border-radius:4px;font-size:10px;">CORRELATION</span></td>
|
||||
<td style="padding:8px 12px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#3b82f6;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px 12px;text-align:center;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">✎</span>
|
||||
<span style="color:#f87171;cursor:pointer;font-size:14px;margin-left:6px;" title="Delete">✕</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 12px;font-weight:500;">orderTotal</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;color:#60a5fa;">enrichPrice</td>
|
||||
<td style="padding:8px 12px;font-family:monospace;font-size:11px;"><span style="background:#161630;padding:2px 6px;border-radius:4px;">$.total</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:4px;font-size:10px;">jsonpath</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">OUTPUT</span></td>
|
||||
<td style="padding:8px 12px;"><span style="background:#1a3a2a;color:#86efac;padding:1px 8px;border-radius:4px;font-size:10px;">BUSINESS</span></td>
|
||||
<td style="padding:8px 12px;text-align:center;">
|
||||
<div style="width:32px;height:18px;background:#4b5563;border-radius:9px;position:relative;margin:0 auto;cursor:pointer;">
|
||||
<div style="width:14px;height:14px;background:white;border-radius:50%;position:absolute;top:2px;left:2px;"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px 12px;text-align:center;">
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:14px;" title="Edit">✎</span>
|
||||
<span style="color:#f87171;cursor:pointer;font-size:14px;margin-left:6px;" title="Delete">✕</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Add/Edit Tap — Modal Dialog</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:520px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
|
||||
|
||||
<!-- Header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
|
||||
<span style="font-size:15px;font-weight:600;">Add Tap</span>
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:18px;">✕</span>
|
||||
</div>
|
||||
|
||||
<div style="padding:20px;">
|
||||
<!-- Attribute Name -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Name <span style="color:#f87171;">*</span></div>
|
||||
<input style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-size:12px;box-sizing:border-box;" placeholder="e.g. orderId, customerId" />
|
||||
</div>
|
||||
|
||||
<!-- Processor -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Processor <span style="color:#f87171;">*</span></div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span style="color:#6b7280;">Select processor…</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#6b7280;margin-top:3px;">Processors from this route's diagram</div>
|
||||
</div>
|
||||
|
||||
<!-- Two columns: Language + Target -->
|
||||
<div style="display:flex;gap:12px;margin-bottom:14px;">
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Language <span style="color:#f87171;">*</span></div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span>simple</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target <span style="color:#f87171;">*</span></div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span>OUTPUT</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expression -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Expression <span style="color:#f87171;">*</span></div>
|
||||
<textarea style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-family:monospace;font-size:12px;box-sizing:border-box;resize:vertical;min-height:48px;" placeholder="e.g. ${body.orderId} or $.customer.id">${body.orderId}</textarea>
|
||||
<div style="font-size:10px;color:#6b7280;margin-top:3px;">Camel expression — evaluated at the selected processor</div>
|
||||
</div>
|
||||
|
||||
<!-- Attribute Type -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Type</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="background:#1e3a5f;color:#7dd3fc;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #3b82f6;">BUSINESS_OBJECT</div>
|
||||
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CORRELATION</div>
|
||||
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">EVENT</div>
|
||||
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CUSTOM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enabled -->
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">
|
||||
<div style="width:36px;height:20px;background:#3b82f6;border-radius:10px;position:relative;cursor:pointer;">
|
||||
<div style="width:16px;height:16px;background:white;border-radius:50%;position:absolute;top:2px;right:2px;"></div>
|
||||
</div>
|
||||
<span style="font-size:12px;color:#e0e0e0;">Enabled</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
|
||||
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">Save Tap</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,175 @@
|
||||
<h2>Add Tap — With Expression Testing</h2>
|
||||
<p class="subtitle">Collapsible test section at bottom of the tap modal</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Add Tap Modal — Test Expression (Recent Exchange)</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:560px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
|
||||
|
||||
<!-- Header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
|
||||
<span style="font-size:15px;font-weight:600;">Add Tap</span>
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:18px;">✕</span>
|
||||
</div>
|
||||
|
||||
<div style="padding:20px;max-height:70vh;overflow-y:auto;">
|
||||
<!-- Form fields (collapsed for brevity) -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Name <span style="color:#f87171;">*</span></div>
|
||||
<input style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-size:12px;box-sizing:border-box;" value="orderId" />
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Processor <span style="color:#f87171;">*</span></div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span style="font-family:monospace;">unmarshal1</span>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:12px;margin-bottom:14px;">
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Language</div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span>simple</span><span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Target</div>
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:12px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<span>OUTPUT</span><span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Expression <span style="color:#f87171;">*</span></div>
|
||||
<textarea style="background:#161630;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-family:monospace;font-size:12px;box-sizing:border-box;resize:vertical;min-height:40px;">${body.orderId}</textarea>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom:14px;">
|
||||
<div style="font-size:11px;color:#9ca3af;margin-bottom:4px;font-weight:500;">Attribute Type</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="background:#1e3a5f;color:#7dd3fc;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #3b82f6;">BUSINESS_OBJECT</div>
|
||||
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CORRELATION</div>
|
||||
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">EVENT</div>
|
||||
<div style="background:#161630;color:#9ca3af;padding:4px 12px;border-radius:6px;font-size:11px;cursor:pointer;border:1px solid #2d2d50;">CUSTOM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ TEST EXPRESSION SECTION ═══ -->
|
||||
<div style="border-top:1px solid #2d2d50;margin-top:8px;padding-top:14px;">
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:12px;cursor:pointer;">
|
||||
<span style="color:#60a5fa;font-size:10px;">▼</span>
|
||||
<span style="font-size:12px;font-weight:600;color:#60a5fa;">Test Expression</span>
|
||||
</div>
|
||||
|
||||
<!-- Data source tabs -->
|
||||
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
|
||||
<div style="padding:6px 14px;font-size:11px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Recent Exchange</div>
|
||||
<div style="padding:6px 14px;font-size:11px;color:#9ca3af;cursor:pointer;">Custom Payload</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent exchange picker -->
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:12px;">
|
||||
<div style="margin-bottom:10px;">
|
||||
<div style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:6px;padding:8px 12px;font-size:11px;display:flex;justify-content:space-between;align-items:center;">
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<span style="width:7px;height:7px;border-radius:50%;background:#4ade80;display:inline-block;"></span>
|
||||
<span style="font-family:monospace;color:#e0e0e0;">a1b2c3d4-e5f6-7890</span>
|
||||
<span style="color:#6b7280;">·</span>
|
||||
<span style="color:#6b7280;">245ms</span>
|
||||
<span style="color:#6b7280;">·</span>
|
||||
<span style="color:#6b7280;">2 min ago</span>
|
||||
</div>
|
||||
<span style="color:#9ca3af;font-size:10px;">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test button + result -->
|
||||
<div style="display:flex;gap:8px;align-items:flex-start;">
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;">▶ Test</button>
|
||||
<div style="flex:1;background:#0f2a1a;border:1px solid #166534;border-radius:6px;padding:8px 12px;">
|
||||
<div style="font-size:10px;color:#6b7280;margin-bottom:2px;">Result</div>
|
||||
<div style="font-family:monospace;font-size:12px;color:#4ade80;">ORD-2024-78542</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
|
||||
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">Save Tap</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:24px;"></div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Test Expression — Custom Payload Mode</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:40px;display:flex;justify-content:center;">
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:12px;padding:0;width:560px;box-shadow:0 20px 60px rgba(0,0,0,0.5);overflow:hidden;">
|
||||
|
||||
<!-- Header -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #2d2d50;">
|
||||
<span style="font-size:15px;font-weight:600;">Add Tap</span>
|
||||
<span style="color:#9ca3af;cursor:pointer;font-size:18px;">✕</span>
|
||||
</div>
|
||||
|
||||
<div style="padding:20px;">
|
||||
<!-- Form fields abbreviated -->
|
||||
<div style="text-align:center;padding:8px;font-size:11px;color:#6b7280;border:1px dashed #2d2d50;border-radius:6px;margin-bottom:14px;">
|
||||
⬆ Form fields above (attribute name, processor, language, target, expression, type)
|
||||
</div>
|
||||
|
||||
<!-- ═══ TEST EXPRESSION SECTION ═══ -->
|
||||
<div style="border-top:1px solid #2d2d50;padding-top:14px;">
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:12px;cursor:pointer;">
|
||||
<span style="color:#60a5fa;font-size:10px;">▼</span>
|
||||
<span style="font-size:12px;font-weight:600;color:#60a5fa;">Test Expression</span>
|
||||
</div>
|
||||
|
||||
<!-- Data source tabs -->
|
||||
<div style="display:flex;gap:0;margin-bottom:0;border-bottom:1px solid #2d2d50;">
|
||||
<div style="padding:6px 14px;font-size:11px;color:#9ca3af;cursor:pointer;">Recent Exchange</div>
|
||||
<div style="padding:6px 14px;font-size:11px;font-weight:600;color:#60a5fa;border-bottom:2px solid #3b82f6;cursor:pointer;">Custom Payload</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom payload editor -->
|
||||
<div style="background:#161630;border:1px solid #2d2d50;border-top:none;border-radius:0 0 6px 6px;padding:12px;">
|
||||
<div style="margin-bottom:10px;">
|
||||
<textarea style="background:#1a1a2e;border:1px solid #2d2d50;border-radius:6px;color:#e0e0e0;padding:8px 12px;width:100%;font-family:monospace;font-size:11px;box-sizing:border-box;resize:vertical;min-height:100px;line-height:1.5;">{
|
||||
"orderId": "ORD-2024-78542",
|
||||
"customer": {
|
||||
"id": "CUST-1234",
|
||||
"name": "Acme Corp"
|
||||
},
|
||||
"total": 149.90
|
||||
}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Test button + error result -->
|
||||
<div style="display:flex;gap:8px;align-items:flex-start;">
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 14px;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;">▶ Test</button>
|
||||
<div style="flex:1;background:#2a0f0f;border:1px solid #991b1b;border-radius:6px;padding:8px 12px;">
|
||||
<div style="font-size:10px;color:#6b7280;margin-bottom:2px;">Error</div>
|
||||
<div style="font-family:monospace;font-size:11px;color:#f87171;">Expression evaluation timed out (50ms limit)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#6b7280;margin-top:8px;">Evaluated by agent <span style="font-family:monospace;">order-svc-01</span> using Camel's <span style="font-family:monospace;">simple</span> language</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;padding:12px 20px;border-top:1px solid #2d2d50;background:#1a1a30;">
|
||||
<button style="background:transparent;color:#9ca3af;border:1px solid #2d2d50;padding:6px 16px;border-radius:6px;font-size:12px;cursor:pointer;">Cancel</button>
|
||||
<button style="background:#3b82f6;color:white;border:none;padding:6px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;">Save Tap</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,61 @@
|
||||
<h2>Traces & Taps — With Route Column</h2>
|
||||
<p class="subtitle">Route column added to prevent ambiguity across routes</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Traces & Taps — Updated</div>
|
||||
<div class="mockup-body" style="background:#1a1a2e;color:#e0e0e0;padding:20px;font-family:system-ui,-apple-system,sans-serif;font-size:13px;">
|
||||
|
||||
<div style="background:#1e1e3a;border:1px solid #2d2d50;border-radius:8px;padding:16px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div style="font-size:12px;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.5px;">Traces & Taps</div>
|
||||
<span style="font-size:11px;color:#6b7280;">3 traced · 4 taps · manage taps on route pages</span>
|
||||
</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid #2d2d50;">
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Route</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Processor</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Capture</th>
|
||||
<th style="text-align:left;padding:8px;color:#9ca3af;font-size:11px;font-weight:500;">Taps</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;color:#60a5fa;font-size:11px;">processOrder</td>
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;">unmarshal1</td>
|
||||
<td style="padding:8px;"><span style="background:#2d1f3b;color:#d8b4fe;padding:1px 8px;border-radius:4px;font-size:10px;">BOTH</span></td>
|
||||
<td style="padding:8px;">
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderId <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">customerId <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;color:#60a5fa;font-size:11px;">processOrder</td>
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;">enrichPrice</td>
|
||||
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;">—</span></td>
|
||||
<td style="padding:8px;">
|
||||
<span style="background:#3b2f1f;color:#fcd34d;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">orderTotal <span style="color:#6b7280;margin-left:2px;">✗</span></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid #161630;">
|
||||
<td style="padding:8px;color:#60a5fa;font-size:11px;">processPayment</td>
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;">toDatabase</td>
|
||||
<td style="padding:8px;"><span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:4px;font-size:10px;">INPUT</span></td>
|
||||
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;">—</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px;color:#60a5fa;font-size:11px;">processPayment</td>
|
||||
<td style="padding:8px;font-family:monospace;font-size:11px;">validate1</td>
|
||||
<td style="padding:8px;"><span style="color:#6b7280;font-size:11px;">—</span></td>
|
||||
<td style="padding:8px;">
|
||||
<span style="background:#1e3a5f;color:#7dd3fc;padding:1px 8px;border-radius:10px;font-size:10px;font-family:monospace;">paymentRef <span style="color:#4ade80;margin-left:2px;">✓</span></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{"reason":"idle timeout","timestamp":1774552065018}
|
||||
1
.superpowers/brainstorm/2048-1774541143/state/server.pid
Normal file
@@ -0,0 +1 @@
|
||||
2048
|
||||
@@ -36,7 +36,7 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
|
||||
- Spring Boot 3.4.3 parent POM
|
||||
- Depends on `com.cameleer3:cameleer3-common` from Gitea Maven registry
|
||||
- Jackson `JavaTimeModule` for `Instant` deserialization
|
||||
- Communication: receives HTTP POST data from agents (executions, diagrams, metrics, logs), serves SSE event streams for config push/commands
|
||||
- Communication: receives HTTP POST data from agents (executions, diagrams, metrics, logs), serves SSE event streams for config push/commands (config-update, deep-trace, replay, route-control)
|
||||
- Maintains agent instance registry with states: LIVE → STALE → DEAD
|
||||
- Storage: PostgreSQL (TimescaleDB) for structured data, OpenSearch for full-text search and application log storage
|
||||
- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing, bootstrap token for registration
|
||||
@@ -57,6 +57,10 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
|
||||
- K8s probes: server uses `/api/v1/health`, PostgreSQL uses `pg_isready`, OpenSearch uses `/_cluster/health`
|
||||
- Docker build uses buildx registry cache + `--provenance=false` for Gitea compatibility
|
||||
|
||||
## UI Styling
|
||||
|
||||
- Always use `@cameleer/design-system` CSS variables for colors (`var(--amber)`, `var(--error)`, `var(--success)`, etc.) — never hardcode hex values. This applies to CSS modules, inline styles, and SVG `fill`/`stroke` attributes. SVG presentation attributes resolve `var()` correctly.
|
||||
|
||||
## Disabled Skills
|
||||
|
||||
- Do NOT use any `gsd:*` skills in this project. This includes all `/gsd:` prefixed commands.
|
||||
|
||||
8
HOWTO.md
@@ -325,6 +325,12 @@ curl -s -X POST http://localhost:8081/api/v1/agents/groups/order-service-prod/co
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"type":"deep-trace","payload":{"routeId":"route-1","durationSeconds":60}}'
|
||||
|
||||
# Send route control command to agent group (start/stop/suspend/resume)
|
||||
curl -s -X POST http://localhost:8081/api/v1/agents/groups/order-service-prod/commands \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"type":"route-control","payload":{"routeId":"route-1","action":"stop","nonce":"unique-uuid"}}'
|
||||
|
||||
# Broadcast command to all live agents
|
||||
curl -s -X POST http://localhost:8081/api/v1/agents/commands \
|
||||
-H "Content-Type: application/json" \
|
||||
@@ -338,7 +344,7 @@ curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/commands/{commandId}
|
||||
|
||||
**Agent lifecycle:** LIVE (heartbeat within 90s) → STALE (missed 3 heartbeats) → DEAD (5min after STALE). DEAD agents kept indefinitely.
|
||||
|
||||
**SSE events:** `config-update`, `deep-trace`, `replay` commands pushed in real time. Server sends ping keepalive every 15s.
|
||||
**SSE events:** `config-update`, `deep-trace`, `replay`, `route-control` commands pushed in real time. Server sends ping keepalive every 15s.
|
||||
|
||||
**Command expiry:** Unacknowledged commands expire after 60 seconds.
|
||||
|
||||
|
||||
294
UI_FINDINGS.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# UI/UX Evaluation Report — Cameleer3 Server
|
||||
|
||||
**Date:** 2026-03-25
|
||||
**Evaluated URL:** http://192.168.50.86:30090/
|
||||
**Methodology:** Playwright-driven navigation of all major pages (14 screenshots), evaluated by 3 specialist agents: Visual Design, Information Architecture & Usability, Readability & Accessibility.
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The Cameleer3 dashboard has a **distinctive, well-crafted warm amber design language** that stands out in the observability space. The core monitoring pages (Dashboard, Exchange Detail, Routes, Agents) are polished and consistent. The design system provides a solid foundation.
|
||||
|
||||
**Key strengths:** KPI strip pattern, command palette (Ctrl+K), agent card grouping, dark mode token system, cohesive brand identity.
|
||||
|
||||
**Critical gaps to address:**
|
||||
1. **Font sizes too small** — pervasive 10-11px text for critical data impairs reading under stress
|
||||
2. **Color contrast failures** — `--text-muted` and `--text-faint` fail WCAG AA in both themes
|
||||
3. **Status indicators rely on color alone** — not accessible for color-blind users
|
||||
4. **Admin infrastructure pages lag in polish** — Database/OpenSearch use ad-hoc styling
|
||||
5. **Dashboard is a monitoring display, not yet an incident response tool** — missing error highlighting, per-route error breakdowns, actionable status pages
|
||||
|
||||
**Overall Score: 7/10** — Strong foundation, needs targeted fixes for production readiness under stress.
|
||||
|
||||
---
|
||||
|
||||
## Pages Evaluated
|
||||
|
||||
| # | Page | Screenshot |
|
||||
|---|------|-----------|
|
||||
| 1 | Login | `screenshots/14-login-page.png` |
|
||||
| 2 | Dashboard (light) | `screenshots/01-dashboard.png` |
|
||||
| 3 | Dashboard + Detail Panel | `screenshots/02-dashboard-detail-panel.png` |
|
||||
| 4 | Exchange Detail | `screenshots/03-exchange-detail.png` |
|
||||
| 5 | Routes Metrics | `screenshots/04-routes-metrics.png` |
|
||||
| 6 | Agent Health | `screenshots/05-agents.png` |
|
||||
| 7 | Agent Instance | `screenshots/06-agent-instance.png` |
|
||||
| 8 | Admin RBAC | `screenshots/07-admin-rbac.png` |
|
||||
| 9 | Admin Audit Log | `screenshots/08-admin-audit.png` |
|
||||
| 10 | Admin OIDC | `screenshots/09-admin-oidc.png` |
|
||||
| 11 | Admin Database | `screenshots/10-admin-database.png` |
|
||||
| 12 | Admin OpenSearch | `screenshots/11-admin-opensearch.png` |
|
||||
| 13 | Command Palette | `screenshots/12-command-palette.png` |
|
||||
| 14 | Dashboard (dark) | `screenshots/13-dashboard-dark-mode.png` |
|
||||
|
||||
---
|
||||
|
||||
## Page-by-Page Findings
|
||||
|
||||
### Login Page
|
||||
|
||||
- **[Important]** No brand identity — missing camel logo/icon from sidebar. First impression feels generic.
|
||||
- **[Important]** Sign-in button color mismatch — uses washed-out gold, not the saturated `--amber` (#C6820E) used throughout the app.
|
||||
- **[Important]** No SSO/OIDC button visible — system supports OIDC but login page only shows username/password.
|
||||
- **[Important]** Subtitle text `--text-muted` (#9C9184) on white fails WCAG AA (~2.8:1, needs 4.5:1).
|
||||
- **[Important]** White text on amber button fails WCAG AA for normal text (~3.2:1).
|
||||
- **[Nice-to-have]** Card has no shadow/border against the `--bg-body` cream background — minimal separation.
|
||||
|
||||
### Dashboard
|
||||
|
||||
- **[Important]** Errors KPI card uses red/orange accent border even when errors = 0. Zero-error state should feel reassuring (green/neutral), not alarming. Creates false alarm fatigue.
|
||||
- **[Important]** Table lacks visible sort indicators — no arrows showing current sort direction.
|
||||
- **[Important]** Duration column uses color alone (`.durFast` green, `.durSlow` amber, `.durBreach` red) — not color-blind safe.
|
||||
- **[Important]** Status dots are ~6px — too small to reliably identify, especially for color-blind users.
|
||||
- **[Critical]** Table meta text at 11px with `--text-muted` is borderline illegible for fatigued users.
|
||||
- **[Critical]** KPI stat labels at 10px with `--text-muted` — below recommended 12px minimum.
|
||||
- **[Nice-to-have]** Exchange ID column too wide — truncate to 8 chars with copy-on-click.
|
||||
|
||||
### Dashboard — Detail Panel
|
||||
|
||||
- **[Important]** Panel lacks clear visual separation from main table — needs left border accent or different background.
|
||||
- **[Important]** Processor timeline preview in panel is too small to read — adds visual noise without utility.
|
||||
- **[Critical]** Overview labels at 10px with `--text-muted` — nearly invisible.
|
||||
- **[Critical]** Panel section meta at 10px with `--text-faint` (#C4BAB0) on white — contrast ratio ~1.9:1, severely fails WCAG AA.
|
||||
- **[Nice-to-have]** No quick actions (copy exchange ID, view logs, view route diagram).
|
||||
|
||||
### Exchange Detail
|
||||
|
||||
- **[Critical]** Processor timeline label column too narrow — processor names are truncated/illegible. This is the page's primary visualization.
|
||||
- **[Critical]** No error highlighting in processor timeline — failed processors need red bars/icons. During incidents, engineers must instantly see WHICH processor failed.
|
||||
- **[Important]** No linkage to route diagram — "View in Route Diagram" would overlay execution on the visual route graph.
|
||||
- **[Important]** Long exchange ID in breadcrumb is visually heavy — truncate with copy button.
|
||||
- **[Important]** Header stat labels at 10px uppercase with `--text-muted` — same contrast issue.
|
||||
|
||||
### Routes Metrics
|
||||
|
||||
- **[Important]** KPI number formatting inconsistent — Dashboard shows "11.742 ms" (decimal + space), Routes shows "11742ms" (no decimal, no space).
|
||||
- **[Important]** No per-route error rate column — error rate in KPI strip but not broken down per route.
|
||||
- **[Important]** Charts disconnected from table — clicking a route should filter/highlight its chart data.
|
||||
- **[Nice-to-have]** No visual comparison between routes (bar chart or heatmap for quick identification of slowest).
|
||||
|
||||
### Agent Health
|
||||
|
||||
- **[Critical]** Stale/Dead agent visual distinction is too subtle — at 3am, the difference between LIVE and DEAD must scream. Dead agents should have prominent red background or strikethrough, not just `--text-muted`.
|
||||
- **[Critical]** Agent state dots (green live, amber stale, gray dead) use color alone — no shape variation for color-blind users.
|
||||
- **[Important]** "2/26" active routes KPI is ambiguous — unit and meaning need to be explicit.
|
||||
- **[Nice-to-have]** Timeline at bottom takes significant space — consider making it collapsible.
|
||||
|
||||
### Agent Instance Detail
|
||||
|
||||
- **[Important]** Charts lack threshold/alert lines — CPU at 2% is fine, but where is "concerning"? Configurable thresholds (CPU > 80%, Memory > 90%) would make charts actionable.
|
||||
- **[Important]** Chart axis labels appear too small.
|
||||
- **[Nice-to-have]** GC Pauses uses area fill while others use line charts — minor inconsistency.
|
||||
- **[Nice-to-have]** Six charts in 2x3 grid can create cognitive overload — consider collapsible groups.
|
||||
|
||||
### Admin — RBAC
|
||||
|
||||
- **[Important]** KPI strip for "Users: 1, Groups: 2, Roles: 4" has too much visual weight — these low-value numbers don't need full stat-card treatment.
|
||||
- **[Important]** "ADMIN" role badge vs "ADMINS" group badge look identical — different badge styles needed (outlined for groups, filled for roles).
|
||||
- **[Nice-to-have]** Empty detail panel ("Select a user to view details") needs icon/illustration.
|
||||
|
||||
### Admin — Audit Log
|
||||
|
||||
- **[Important]** "no data" empty state is uninformative — should explain "No audit events match your filters" with guidance.
|
||||
- **[Important]** No export functionality — audit logs need CSV/JSON export for compliance.
|
||||
- **[Important]** Date range filters use raw datetime inputs — inconsistent with dashboard's polished time range pills.
|
||||
|
||||
### Admin — OIDC Config
|
||||
|
||||
- **[Critical]** "Delete OIDC Configuration" is a destructive action without confirmation dialog — could lock out all SSO users.
|
||||
- **[Important]** No inline validation — Issuer URL should validate format on blur, required fields need indicators.
|
||||
- **[Nice-to-have]** No connection test result display area.
|
||||
|
||||
### Admin — Database
|
||||
|
||||
- **[Important]** Visual treatment inconsistent with rest of app — "Connected" status and pool stats use ad-hoc text, not design system components.
|
||||
- **[Important]** Page title "Database Administration" implies actions, but page is read-only — rename to "Database Status" or add operations.
|
||||
- **[Nice-to-have]** Table row counts should be right-aligned for numerical scanning.
|
||||
|
||||
### Admin — OpenSearch
|
||||
|
||||
- **[Critical]** "Disconnected" status displayed as plain text — needs error styling (red text, error badge, or status banner). Infrastructure disconnection is a critical state.
|
||||
- **[Important]** "Yellow" cluster health displayed as plain text with no visual hierarchy — same size/weight as version number and node count.
|
||||
- **[Important]** Indexing pipeline stats use ad-hoc inline format — should use consistent stat-card pattern.
|
||||
- **[Important]** "Disconnected" + "Yellow" health shown simultaneously is contradictory — if disconnected, clarify whether data is stale.
|
||||
|
||||
### Command Palette
|
||||
|
||||
- **[Nice-to-have]** No visible keyboard navigation hint for currently selected item.
|
||||
- **[Nice-to-have]** Empty palette should show recent/frequent items instead of requiring typing.
|
||||
- Overall well-executed — categories, counts, keyboard hints in footer.
|
||||
|
||||
### Dark Mode
|
||||
|
||||
- **[Critical]** `--text-muted` (#7A7068) on `--bg-surface` (#242019) is ~2.9:1 — fails WCAG AA. Affects ALL muted labels across every page.
|
||||
- **[Critical]** `--text-faint` (#4A4238) on `--bg-surface` (#242019) is ~1.4:1 — catastrophically fails WCAG AA. Essentially invisible.
|
||||
- **[Important]** `--amber` (#D4941E) on `--bg-surface` (#242019) is ~3.6:1 — amber links/active text fail AA.
|
||||
- **[Important]** KPI sparkline chart lines are harder to read — thin strokes need increased width or brightness.
|
||||
- **[Important]** Sidebar boundary contrast drops significantly (`--sidebar-bg` #141210 vs `--bg-body` #1A1714 is only ~6 units apart).
|
||||
- **[Important]** Table row alternation contrast near zero in dark mode.
|
||||
- **[Nice-to-have]** Amber accent color shift from #C6820E to #D4941E is well-handled.
|
||||
- **[Nice-to-have]** Semantic colors (success, error, warning) appropriately increase luminance.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Cutting Issues
|
||||
|
||||
### 1. Color Contrast (WCAG AA Failures)
|
||||
|
||||
**Light Mode:**
|
||||
|
||||
| Element | Foreground | Background | Ratio | Required | Verdict |
|
||||
|---------|-----------|------------|-------|----------|---------|
|
||||
| StatCard labels, table meta, section headers | `--text-muted` #9C9184 | #FFFFFF | ~3.0:1 | 4.5:1 | **FAIL** |
|
||||
| Panel meta, overview hints | `--text-faint` #C4BAB0 | #FFFFFF | ~1.9:1 | 4.5:1 | **FAIL** |
|
||||
| Sign-in button text | #FFFFFF | `--amber` #C6820E | ~3.2:1 | 4.5:1 | **FAIL** |
|
||||
| Sidebar muted text | #9C9184 | `--sidebar-bg` #2C2520 | ~3.1:1 | 4.5:1 | **FAIL** |
|
||||
|
||||
**Dark Mode:**
|
||||
|
||||
| Element | Foreground | Background | Ratio | Required | Verdict |
|
||||
|---------|-----------|------------|-------|----------|---------|
|
||||
| All muted labels | #7A7068 | #242019 | ~2.9:1 | 4.5:1 | **FAIL** |
|
||||
| All faint hints | #4A4238 | #242019 | ~1.4:1 | 4.5:1 | **FAIL** |
|
||||
| Amber links/active text | #D4941E | #242019 | ~3.6:1 | 4.5:1 | **FAIL** |
|
||||
|
||||
**Fix:** Change `--text-muted` to **#766A5E** (light) / **#9A9088** (dark). Restrict `--text-faint` to decorative use only or lighten dark variant to #6A6058.
|
||||
|
||||
### 2. Font Size Floor
|
||||
|
||||
10px text is used for: StatCard labels, overview labels, chain labels, section meta, error class names, detail labels, sidebar tree labels. 11px is used for: table meta, error messages, pagination, toggle buttons, chart titles.
|
||||
|
||||
**Fix:** Establish `--font-size-min: 12px` as a design system floor. Update all 10px instances to 12px, all 11px instances to 12px.
|
||||
|
||||
### 3. Number/Unit Formatting
|
||||
|
||||
Inconsistent across pages:
|
||||
- Dashboard: "11.742 ms" (decimal + space)
|
||||
- Routes: "11742ms" (no decimal, no space)
|
||||
- Dashboard: "1.1 msg/s" vs Agent Instance: "0.1/s"
|
||||
|
||||
**Fix:** Create a shared formatting utility enforcing: consistent decimal precision, space before unit, consistent abbreviations.
|
||||
|
||||
### 4. KPI Strip Inconsistency
|
||||
|
||||
Used on Dashboard, Routes, Agents, Agent Instance (consistent). But RBAC uses oversized cards for trivial counts, and Database/OpenSearch use ad-hoc text rendering.
|
||||
|
||||
**Fix:** Admin infra pages should adopt KPI stat strip or a compact-stat component.
|
||||
|
||||
### 5. Empty States
|
||||
|
||||
Inconsistent handling:
|
||||
- Audit Log: "no data" in plain gray
|
||||
- RBAC detail: "Select a user to view details" in gray
|
||||
- No consistent empty state component with icon + message + CTA
|
||||
|
||||
**Fix:** Design system EmptyState component with icon, message, and optional action.
|
||||
|
||||
### 6. Status Indicator Accessibility
|
||||
|
||||
Color-only status encoding throughout:
|
||||
- Duration: green (fast), amber (slow), red (breach) — no icons
|
||||
- Status dots: green (live), amber (stale), gray (dead) — no shapes
|
||||
- Agent dead state uses `--text-muted` instead of `--error`
|
||||
|
||||
**Fix:** Add shape variation (checkmark/triangle/X), increase dot size to 10px minimum, always render text label alongside.
|
||||
|
||||
### 7. Sidebar Structure
|
||||
|
||||
Same apps listed 3x (under Applications, Agents, Routes) — triples sidebar length and scales poorly.
|
||||
|
||||
**Fix:** Unified application-centric tree where expanding an app shows its agents and routes as children.
|
||||
|
||||
---
|
||||
|
||||
## Prioritized Recommendations
|
||||
|
||||
### Critical (fix now)
|
||||
|
||||
| # | Recommendation | Impact |
|
||||
|---|---------------|--------|
|
||||
| 1 | **Bump `--text-muted` to WCAG AA compliance** — #766A5E (light) / #9A9088 (dark). Single highest-impact fix across all pages. | Fixes majority of contrast failures |
|
||||
| 2 | **Establish 12px minimum font size** — update all 10px and 11px instances. Especially StatCard labels, overview labels, table meta. | Readable under stress |
|
||||
| 3 | **Add error highlighting to processor timeline** — red bars, error icons for failed processors. Core debugging view. | Incident response speed |
|
||||
| 4 | **Make Stale/Dead agent states unmistakable** — full card background color (yellow stale, red dead), prominent badge. Change dead from `--text-muted` to `--error`. | Prevents missed outages |
|
||||
| 5 | **Fix OpenSearch "Disconnected" status** — use error badge/banner, add "Reconnect" action, clarify stale data. | Actionable admin page |
|
||||
| 6 | **Add confirmation dialog for OIDC deletion** — type-to-confirm to prevent locking out SSO users. | Prevents lockout |
|
||||
| 7 | **Color Errors KPI card conditionally** — green/neutral at 0, red only when > 0. Prevents false alarm fatigue. | Reduces noise |
|
||||
|
||||
### Important (next sprint)
|
||||
|
||||
| # | Recommendation | Impact |
|
||||
|---|---------------|--------|
|
||||
| 8 | **Add secondary encoding to status indicators** — shapes (checkmark/triangle/X) alongside color dots. Increase dot size to 10px+. | Accessibility compliance |
|
||||
| 9 | **Standardize number/unit formatting** — shared utility for decimals, spacing, unit abbreviations. | Visual consistency |
|
||||
| 10 | **Add per-route error rate to Routes table** — essential for isolating failing routes. | Incident triage |
|
||||
| 11 | **Add visible sort indicators to data tables** — arrows on column headers. | Data exploration |
|
||||
| 12 | **Bring admin infra pages to design system quality** — replace ad-hoc text with KPI strips/stat cards. | Professional polish |
|
||||
| 13 | **Fix login page brand identity** — add camel logo, use correct `--amber` for button, add SSO button when OIDC configured. | First impression |
|
||||
| 14 | **Fix dark mode specifics** — increase sidebar boundary contrast (add 1px border), boost chart stroke width, fix amber link contrast. | Dark mode usability |
|
||||
| 15 | **Widen processor timeline label column** — prevent name truncation, add tooltips for long names. | Core visualization |
|
||||
| 16 | **Add detail panel visual separation** — 2px left border accent. | Layout clarity |
|
||||
| 17 | **Pin Admin/API Docs to sidebar footer** — accessible without scrolling. | Navigation |
|
||||
| 18 | **Audit log improvements** — informative empty state, CSV/JSON export, date picker consistent with dashboard. | Admin usability |
|
||||
| 19 | **OIDC form validation** — inline URL validation, required field indicators, test result display. | Configuration safety |
|
||||
| 20 | **Fix amber button text contrast** — darken button to #8B5A06 or use dark text on amber. | Accessibility |
|
||||
|
||||
### Nice-to-have (backlog)
|
||||
|
||||
| # | Recommendation | Impact |
|
||||
|---|---------------|--------|
|
||||
| 21 | Unify sidebar into single application-centric tree (Applications > agents + routes) | Scalability |
|
||||
| 22 | Truncate Exchange IDs to 8 chars with copy-on-click | Table space |
|
||||
| 23 | Add threshold/alert lines to agent metric charts | Actionable monitoring |
|
||||
| 24 | Link charts to table selection on Routes Metrics | Data exploration |
|
||||
| 25 | Add clickable KPI cards navigating to filtered views | Navigation shortcuts |
|
||||
| 26 | Add `prefers-reduced-motion` support for StatusDot pulse animation | Accessibility |
|
||||
| 27 | Add tooltips to sparkline charts showing value on hover | Data context |
|
||||
| 28 | Replace hardcoded `#5db866` in Dashboard.module.css with `var(--success)` | Token compliance |
|
||||
| 29 | Add keyboard navigation indicators to command palette (selected item highlight) | Power user UX |
|
||||
| 30 | Show recent/frequent items in empty command palette | Discoverability |
|
||||
| 31 | Consolidate duplicated table-header CSS into design system component | Maintainability |
|
||||
| 32 | Login page card shadow for visual lift | Polish |
|
||||
| 33 | Collapsible agent event timeline | Space efficiency |
|
||||
| 34 | Dark mode `--text-faint` increase to #6A6058 for 3:1 minimum | Accessibility |
|
||||
| 35 | Increase DataTable row height to 44px (touch target minimum) | Accessibility |
|
||||
|
||||
---
|
||||
|
||||
## Dark Mode Assessment
|
||||
|
||||
**Grade: Good foundation, specific contrast concerns.**
|
||||
|
||||
**What works well:**
|
||||
- Token system remaps all semantic colors without introducing cold blue-grays — warm brand preserved
|
||||
- Amber accent brightens appropriately (#C6820E → #D4941E)
|
||||
- Error/warning/success colors increase luminance correctly
|
||||
- Shadows shift from warm semi-transparent to opaque — correct for dark backgrounds
|
||||
|
||||
**What needs fixing:**
|
||||
- Sidebar contrast: `--sidebar-bg` #141210 vs `--bg-body` #1A1714 only ~6 units apart (was ~50 in light mode)
|
||||
- Chart line visibility: thin 1-2px strokes need increased width
|
||||
- Table row alternation: near-zero contrast between `--bg-surface` and `--bg-raised`
|
||||
- `--text-faint`: essentially invisible at 1.4:1 contrast
|
||||
- `--text-muted`: 2.9:1 — below AA minimum
|
||||
@@ -5,6 +5,8 @@ import com.cameleer3.server.app.dto.CommandAckRequest;
|
||||
import com.cameleer3.server.app.dto.CommandBroadcastResponse;
|
||||
import com.cameleer3.server.app.dto.CommandRequest;
|
||||
import com.cameleer3.server.app.dto.CommandSingleResponse;
|
||||
import com.cameleer3.server.app.dto.ReplayRequest;
|
||||
import com.cameleer3.server.app.dto.ReplayResponse;
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
@@ -13,6 +15,7 @@ import com.cameleer3.server.core.agent.AgentEventService;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
import com.cameleer3.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer3.server.core.agent.AgentState;
|
||||
import com.cameleer3.server.core.agent.CommandReply;
|
||||
import com.cameleer3.server.core.agent.CommandType;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -32,7 +35,14 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Command push endpoints for sending commands to agents via SSE.
|
||||
@@ -184,6 +194,75 @@ public class AgentCommandController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/replay")
|
||||
@Operation(summary = "Replay an exchange on a specific agent (synchronous)",
|
||||
description = "Sends a replay command and waits for the agent to complete the replay. "
|
||||
+ "Returns the replay result including status, replayExchangeId, and duration.")
|
||||
@ApiResponse(responseCode = "200", description = "Replay completed (check status for success/failure)")
|
||||
@ApiResponse(responseCode = "404", description = "Agent not found or not connected")
|
||||
@ApiResponse(responseCode = "504", description = "Agent did not respond in time")
|
||||
public ResponseEntity<ReplayResponse> replayExchange(@PathVariable String id,
|
||||
@RequestBody ReplayRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
AgentInfo agent = registryService.findById(id);
|
||||
if (agent == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Agent not found: " + id);
|
||||
}
|
||||
|
||||
// Build protocol-compliant replay payload
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("routeId", request.routeId());
|
||||
Map<String, Object> exchange = new LinkedHashMap<>();
|
||||
exchange.put("body", request.body() != null ? request.body() : "");
|
||||
exchange.put("headers", request.headers() != null ? request.headers() : Map.of());
|
||||
payload.put("exchange", exchange);
|
||||
if (request.originalExchangeId() != null) {
|
||||
payload.put("originalExchangeId", request.originalExchangeId());
|
||||
}
|
||||
payload.put("nonce", UUID.randomUUID().toString());
|
||||
|
||||
String payloadJson;
|
||||
try {
|
||||
payloadJson = objectMapper.writeValueAsString(payload);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Failed to serialize replay payload", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new ReplayResponse("FAILURE", "Failed to serialize request", null));
|
||||
}
|
||||
|
||||
CompletableFuture<CommandReply> future = registryService.addCommandWithReply(
|
||||
id, CommandType.REPLAY, payloadJson);
|
||||
|
||||
Map<String, Object> auditDetails = new LinkedHashMap<>();
|
||||
auditDetails.put("routeId", request.routeId());
|
||||
if (request.originalExchangeId() != null) {
|
||||
auditDetails.put("originalExchangeId", request.originalExchangeId());
|
||||
}
|
||||
|
||||
try {
|
||||
CommandReply reply = future.orTimeout(30, TimeUnit.SECONDS).join();
|
||||
auditDetails.put("replyStatus", reply.status());
|
||||
auditDetails.put("replyMessage", reply.message() != null ? reply.message() : "");
|
||||
auditService.log("replay_exchange", AuditCategory.AGENT, id, auditDetails,
|
||||
"SUCCESS".equals(reply.status()) ? AuditResult.SUCCESS : AuditResult.FAILURE, httpRequest);
|
||||
return ResponseEntity.ok(new ReplayResponse(reply.status(), reply.message(), reply.data()));
|
||||
} catch (CompletionException e) {
|
||||
if (e.getCause() instanceof TimeoutException) {
|
||||
auditDetails.put("error", "timeout");
|
||||
auditService.log("replay_exchange", AuditCategory.AGENT, id, auditDetails,
|
||||
AuditResult.FAILURE, httpRequest);
|
||||
return ResponseEntity.status(HttpStatus.GATEWAY_TIMEOUT)
|
||||
.body(new ReplayResponse("FAILURE", "Agent did not respond within 30 seconds", null));
|
||||
}
|
||||
auditDetails.put("error", e.getCause().getMessage());
|
||||
auditService.log("replay_exchange", AuditCategory.AGENT, id, auditDetails,
|
||||
AuditResult.FAILURE, httpRequest);
|
||||
log.error("Error awaiting replay reply from agent {}", id, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new ReplayResponse("FAILURE", "Internal error: " + e.getCause().getMessage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
private CommandType mapCommandType(String typeStr) {
|
||||
return switch (typeStr) {
|
||||
case "config-update" -> CommandType.CONFIG_UPDATE;
|
||||
@@ -191,8 +270,9 @@ public class AgentCommandController {
|
||||
case "replay" -> CommandType.REPLAY;
|
||||
case "set-traced-processors" -> CommandType.SET_TRACED_PROCESSORS;
|
||||
case "test-expression" -> CommandType.TEST_EXPRESSION;
|
||||
case "route-control" -> CommandType.ROUTE_CONTROL;
|
||||
default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
|
||||
"Invalid command type: " + typeStr + ". Valid: config-update, deep-trace, replay, set-traced-processors, test-expression");
|
||||
"Invalid command type: " + typeStr + ". Valid: config-update, deep-trace, replay, set-traced-processors, test-expression, route-control");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ public class ApiExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ResponseEntity<ErrorResponse> handleResponseStatus(ResponseStatusException ex) {
|
||||
String reason = ex.getReason();
|
||||
return ResponseEntity.status(ex.getStatusCode())
|
||||
.body(new ErrorResponse(ex.getReason() != null ? ex.getReason() : "Unknown error"));
|
||||
.body(new ErrorResponse(reason != null ? reason : "Unknown error"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.cameleer3.server.app.controller;
|
||||
|
||||
import com.cameleer3.server.app.dto.AppSettingsRequest;
|
||||
import com.cameleer3.server.core.admin.AppSettings;
|
||||
import com.cameleer3.server.core.admin.AppSettingsRepository;
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/admin/app-settings")
|
||||
@PreAuthorize("hasAnyRole('ADMIN', 'OPERATOR')")
|
||||
@Tag(name = "App Settings", description = "Per-application dashboard settings (ADMIN/OPERATOR)")
|
||||
public class AppSettingsController {
|
||||
|
||||
private final AppSettingsRepository repository;
|
||||
private final AuditService auditService;
|
||||
|
||||
public AppSettingsController(AppSettingsRepository repository, AuditService auditService) {
|
||||
this.repository = repository;
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "List all application settings")
|
||||
public ResponseEntity<List<AppSettings>> getAll() {
|
||||
return ResponseEntity.ok(repository.findAll());
|
||||
}
|
||||
|
||||
@GetMapping("/{appId}")
|
||||
@Operation(summary = "Get settings for a specific application (returns defaults if not configured)")
|
||||
public ResponseEntity<AppSettings> getByAppId(@PathVariable String appId) {
|
||||
AppSettings settings = repository.findByAppId(appId).orElse(AppSettings.defaults(appId));
|
||||
return ResponseEntity.ok(settings);
|
||||
}
|
||||
|
||||
@PutMapping("/{appId}")
|
||||
@Operation(summary = "Create or update settings for an application")
|
||||
public ResponseEntity<AppSettings> update(@PathVariable String appId,
|
||||
@Valid @RequestBody AppSettingsRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
List<String> errors = request.validate();
|
||||
if (!errors.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.join("; ", errors));
|
||||
}
|
||||
|
||||
AppSettings saved = repository.save(request.toSettings(appId));
|
||||
auditService.log("update_app_settings", AuditCategory.CONFIG, appId,
|
||||
Map.of("settings", saved), AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.ok(saved);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{appId}")
|
||||
@Operation(summary = "Delete application settings (reverts to defaults)")
|
||||
public ResponseEntity<Void> delete(@PathVariable String appId, HttpServletRequest httpRequest) {
|
||||
repository.delete(appId);
|
||||
auditService.log("delete_app_settings", AuditCategory.CONFIG, appId,
|
||||
Map.of(), AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,8 @@ public class DatabaseAdminController {
|
||||
String host = extractHost(dataSource);
|
||||
return ResponseEntity.ok(new DatabaseStatusResponse(true, version, host, schema, timescaleDb));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.ok(new DatabaseStatusResponse(false, null, null, null, false));
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.body(new DatabaseStatusResponse(false, null, null, null, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public class DetailController {
|
||||
}
|
||||
|
||||
@GetMapping("/{executionId}/processors/{index}/snapshot")
|
||||
@Operation(summary = "Get exchange snapshot for a specific processor")
|
||||
@Operation(summary = "Get exchange snapshot for a specific processor by index")
|
||||
@ApiResponse(responseCode = "200", description = "Snapshot data")
|
||||
@ApiResponse(responseCode = "404", description = "Snapshot not found")
|
||||
public ResponseEntity<Map<String, String>> getProcessorSnapshot(
|
||||
@@ -69,4 +69,16 @@ public class DetailController {
|
||||
|
||||
return ResponseEntity.ok(snapshot);
|
||||
}
|
||||
|
||||
@GetMapping("/{executionId}/processors/by-id/{processorId}/snapshot")
|
||||
@Operation(summary = "Get exchange snapshot for a specific processor by processorId")
|
||||
@ApiResponse(responseCode = "200", description = "Snapshot data")
|
||||
@ApiResponse(responseCode = "404", description = "Snapshot not found")
|
||||
public ResponseEntity<Map<String, String>> processorSnapshotById(
|
||||
@PathVariable String executionId,
|
||||
@PathVariable String processorId) {
|
||||
return detailService.getProcessorSnapshot(executionId, processorId)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ public class OpenSearchAdminController {
|
||||
health.numberOfNodes(),
|
||||
opensearchUrl));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.ok(new OpenSearchStatusResponse(
|
||||
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.body(new OpenSearchStatusResponse(
|
||||
false, "UNREACHABLE", null, 0, opensearchUrl));
|
||||
}
|
||||
}
|
||||
@@ -149,7 +150,8 @@ public class OpenSearchAdminController {
|
||||
pageItems, totalIndices, totalDocs,
|
||||
humanSize(totalBytes), page, size, totalPages));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.ok(new IndicesPageResponse(
|
||||
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
|
||||
.body(new IndicesPageResponse(
|
||||
List.of(), 0, 0, "0 B", page, size, 0));
|
||||
}
|
||||
}
|
||||
@@ -234,7 +236,8 @@ public class OpenSearchAdminController {
|
||||
searchLatency, indexingLatency,
|
||||
heapUsed, heapMax));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.ok(new PerformanceResponse(0, 0, 0, 0, 0, 0));
|
||||
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
|
||||
.body(new PerformanceResponse(0, 0, 0, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.cameleer3.server.app.controller;
|
||||
import com.cameleer3.server.app.dto.AgentSummary;
|
||||
import com.cameleer3.server.app.dto.AppCatalogEntry;
|
||||
import com.cameleer3.server.app.dto.RouteSummary;
|
||||
import com.cameleer3.common.graph.RouteGraph;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
import com.cameleer3.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer3.server.core.agent.AgentState;
|
||||
import com.cameleer3.server.core.storage.DiagramStore;
|
||||
import com.cameleer3.server.core.storage.StatsStore;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@@ -34,10 +36,14 @@ import java.util.stream.Collectors;
|
||||
public class RouteCatalogController {
|
||||
|
||||
private final AgentRegistryService registryService;
|
||||
private final DiagramStore diagramStore;
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public RouteCatalogController(AgentRegistryService registryService, JdbcTemplate jdbc) {
|
||||
public RouteCatalogController(AgentRegistryService registryService,
|
||||
DiagramStore diagramStore,
|
||||
JdbcTemplate jdbc) {
|
||||
this.registryService = registryService;
|
||||
this.diagramStore = diagramStore;
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@@ -114,12 +120,14 @@ public class RouteCatalogController {
|
||||
|
||||
// Routes
|
||||
Set<String> routeIds = routesByApp.getOrDefault(appId, Set.of());
|
||||
List<String> agentIds = agents.stream().map(AgentInfo::id).toList();
|
||||
List<RouteSummary> routeSummaries = routeIds.stream()
|
||||
.map(routeId -> {
|
||||
String key = appId + "/" + routeId;
|
||||
long count = routeExchangeCounts.getOrDefault(key, 0L);
|
||||
Instant lastSeen = routeLastSeen.get(key);
|
||||
return new RouteSummary(routeId, count, lastSeen);
|
||||
String fromUri = resolveFromEndpointUri(routeId, agentIds);
|
||||
return new RouteSummary(routeId, count, lastSeen, fromUri);
|
||||
})
|
||||
.toList();
|
||||
|
||||
@@ -141,6 +149,15 @@ public class RouteCatalogController {
|
||||
return ResponseEntity.ok(catalog);
|
||||
}
|
||||
|
||||
/** Resolve the from() endpoint URI for a route by looking up its diagram. */
|
||||
private String resolveFromEndpointUri(String routeId, List<String> agentIds) {
|
||||
return diagramStore.findContentHashForRouteByAgents(routeId, agentIds)
|
||||
.flatMap(diagramStore::findByContentHash)
|
||||
.map(RouteGraph::getRoot)
|
||||
.map(root -> root.getEndpointUri())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private String computeWorstHealth(List<AgentInfo> agents) {
|
||||
boolean hasDead = false;
|
||||
boolean hasStale = false;
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.cameleer3.server.app.controller;
|
||||
|
||||
import com.cameleer3.server.app.dto.ProcessorMetrics;
|
||||
import com.cameleer3.server.app.dto.RouteMetrics;
|
||||
import com.cameleer3.server.core.admin.AppSettings;
|
||||
import com.cameleer3.server.core.admin.AppSettingsRepository;
|
||||
import com.cameleer3.server.core.storage.StatsStore;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -18,6 +21,7 @@ import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/routes")
|
||||
@@ -25,9 +29,14 @@ import java.util.List;
|
||||
public class RouteMetricsController {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
private final StatsStore statsStore;
|
||||
private final AppSettingsRepository appSettingsRepository;
|
||||
|
||||
public RouteMetricsController(JdbcTemplate jdbc) {
|
||||
public RouteMetricsController(JdbcTemplate jdbc, StatsStore statsStore,
|
||||
AppSettingsRepository appSettingsRepository) {
|
||||
this.jdbc = jdbc;
|
||||
this.statsStore = statsStore;
|
||||
this.appSettingsRepository = appSettingsRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/metrics")
|
||||
@@ -78,7 +87,7 @@ public class RouteMetricsController {
|
||||
|
||||
routeKeys.add(new RouteKey(applicationName, routeId));
|
||||
return new RouteMetrics(routeId, applicationName, total, successRate,
|
||||
avgDur, p99Dur, errorRate, tps, List.of());
|
||||
avgDur, p99Dur, errorRate, tps, List.of(), -1.0);
|
||||
}, params.toArray());
|
||||
|
||||
// Fetch sparklines (12 buckets over the time window)
|
||||
@@ -100,13 +109,34 @@ public class RouteMetricsController {
|
||||
m.appId(), m.routeId());
|
||||
metrics.set(i, new RouteMetrics(m.routeId(), m.appId(), m.exchangeCount(),
|
||||
m.successRate(), m.avgDurationMs(), m.p99DurationMs(),
|
||||
m.errorRate(), m.throughputPerSec(), sparkline));
|
||||
m.errorRate(), m.throughputPerSec(), sparkline, m.slaCompliance()));
|
||||
} catch (Exception e) {
|
||||
// Leave sparkline empty on error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enrich with SLA compliance per route
|
||||
if (!metrics.isEmpty()) {
|
||||
// Determine SLA threshold (per-app or default)
|
||||
String effectiveAppId = appId != null ? appId : (metrics.isEmpty() ? null : metrics.get(0).appId());
|
||||
int threshold = appSettingsRepository.findByAppId(effectiveAppId != null ? effectiveAppId : "")
|
||||
.map(AppSettings::slaThresholdMs).orElse(300);
|
||||
|
||||
Map<String, long[]> slaCounts = statsStore.slaCountsByRoute(fromInstant, toInstant,
|
||||
effectiveAppId, threshold);
|
||||
|
||||
for (int i = 0; i < metrics.size(); i++) {
|
||||
RouteMetrics m = metrics.get(i);
|
||||
long[] counts = slaCounts.get(m.routeId());
|
||||
double sla = (counts != null && counts[1] > 0)
|
||||
? counts[0] * 100.0 / counts[1] : 100.0;
|
||||
metrics.set(i, new RouteMetrics(m.routeId(), m.appId(), m.exchangeCount(),
|
||||
m.successRate(), m.avgDurationMs(), m.p99DurationMs(),
|
||||
m.errorRate(), m.throughputPerSec(), m.sparkline(), sla));
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(metrics);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.cameleer3.server.app.controller;
|
||||
|
||||
import com.cameleer3.server.core.admin.AppSettings;
|
||||
import com.cameleer3.server.core.admin.AppSettingsRepository;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
import com.cameleer3.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer3.server.core.search.ExecutionStats;
|
||||
@@ -8,6 +10,8 @@ import com.cameleer3.server.core.search.SearchRequest;
|
||||
import com.cameleer3.server.core.search.SearchResult;
|
||||
import com.cameleer3.server.core.search.SearchService;
|
||||
import com.cameleer3.server.core.search.StatsTimeseries;
|
||||
import com.cameleer3.server.core.search.TopError;
|
||||
import com.cameleer3.server.core.storage.StatsStore;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -20,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Search endpoints for querying route executions.
|
||||
@@ -34,10 +39,13 @@ public class SearchController {
|
||||
|
||||
private final SearchService searchService;
|
||||
private final AgentRegistryService registryService;
|
||||
private final AppSettingsRepository appSettingsRepository;
|
||||
|
||||
public SearchController(SearchService searchService, AgentRegistryService registryService) {
|
||||
public SearchController(SearchService searchService, AgentRegistryService registryService,
|
||||
AppSettingsRepository appSettingsRepository) {
|
||||
this.searchService = searchService;
|
||||
this.registryService = registryService;
|
||||
this.appSettingsRepository = appSettingsRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/executions")
|
||||
@@ -87,21 +95,29 @@ public class SearchController {
|
||||
}
|
||||
|
||||
@GetMapping("/stats")
|
||||
@Operation(summary = "Aggregate execution stats (P99 latency, active count)")
|
||||
@Operation(summary = "Aggregate execution stats (P99 latency, active count, SLA compliance)")
|
||||
public ResponseEntity<ExecutionStats> stats(
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(required = false) String routeId,
|
||||
@RequestParam(required = false) String application) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
ExecutionStats stats;
|
||||
if (routeId == null && application == null) {
|
||||
return ResponseEntity.ok(searchService.stats(from, end));
|
||||
stats = searchService.stats(from, end);
|
||||
} else if (routeId == null) {
|
||||
stats = searchService.statsForApp(from, end, application);
|
||||
} else {
|
||||
List<String> agentIds = resolveApplicationToAgentIds(application);
|
||||
stats = searchService.stats(from, end, routeId, agentIds);
|
||||
}
|
||||
if (routeId == null) {
|
||||
return ResponseEntity.ok(searchService.statsForApp(from, end, application));
|
||||
}
|
||||
List<String> agentIds = resolveApplicationToAgentIds(application);
|
||||
return ResponseEntity.ok(searchService.stats(from, end, routeId, agentIds));
|
||||
|
||||
// Enrich with SLA compliance
|
||||
int threshold = appSettingsRepository
|
||||
.findByAppId(application != null ? application : "")
|
||||
.map(AppSettings::slaThresholdMs).orElse(300);
|
||||
double sla = searchService.slaCompliance(from, end, threshold, application, routeId);
|
||||
return ResponseEntity.ok(stats.withSlaCompliance(sla));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/timeseries")
|
||||
@@ -126,6 +142,48 @@ public class SearchController {
|
||||
return ResponseEntity.ok(searchService.timeseries(from, end, buckets, routeId, agentIds));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/timeseries/by-app")
|
||||
@Operation(summary = "Timeseries grouped by application")
|
||||
public ResponseEntity<Map<String, StatsTimeseries>> timeseriesByApp(
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(defaultValue = "24") int buckets) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
return ResponseEntity.ok(searchService.timeseriesGroupedByApp(from, end, buckets));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/timeseries/by-route")
|
||||
@Operation(summary = "Timeseries grouped by route for an application")
|
||||
public ResponseEntity<Map<String, StatsTimeseries>> timeseriesByRoute(
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(defaultValue = "24") int buckets,
|
||||
@RequestParam String application) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
return ResponseEntity.ok(searchService.timeseriesGroupedByRoute(from, end, buckets, application));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/punchcard")
|
||||
@Operation(summary = "Transaction punchcard: weekday x hour grid (rolling 7 days)")
|
||||
public ResponseEntity<List<StatsStore.PunchcardCell>> punchcard(
|
||||
@RequestParam(required = false) String application) {
|
||||
Instant to = Instant.now();
|
||||
Instant from = to.minus(java.time.Duration.ofDays(7));
|
||||
return ResponseEntity.ok(searchService.punchcard(from, to, application));
|
||||
}
|
||||
|
||||
@GetMapping("/errors/top")
|
||||
@Operation(summary = "Top N errors with velocity trend")
|
||||
public ResponseEntity<List<TopError>> topErrors(
|
||||
@RequestParam Instant from,
|
||||
@RequestParam(required = false) Instant to,
|
||||
@RequestParam(required = false) String application,
|
||||
@RequestParam(required = false) String routeId,
|
||||
@RequestParam(defaultValue = "5") int limit) {
|
||||
Instant end = to != null ? to : Instant.now();
|
||||
return ResponseEntity.ok(searchService.topErrors(from, end, application, routeId, limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an application name to agent IDs.
|
||||
* Returns null if application is null/blank (no filtering).
|
||||
|
||||
@@ -9,9 +9,11 @@ import com.cameleer3.server.core.diagram.DiagramRenderer;
|
||||
import com.cameleer3.server.core.diagram.PositionedEdge;
|
||||
import com.cameleer3.server.core.diagram.PositionedNode;
|
||||
import org.eclipse.elk.alg.layered.options.LayeredMetaDataProvider;
|
||||
import org.eclipse.elk.alg.layered.options.NodePlacementStrategy;
|
||||
import org.eclipse.elk.core.RecursiveGraphLayoutEngine;
|
||||
import org.eclipse.elk.core.options.CoreOptions;
|
||||
import org.eclipse.elk.core.options.Direction;
|
||||
import org.eclipse.elk.core.options.EdgeRouting;
|
||||
import org.eclipse.elk.core.options.HierarchyHandling;
|
||||
import org.eclipse.elk.core.util.BasicProgressMonitor;
|
||||
import org.eclipse.elk.graph.ElkBendPoint;
|
||||
@@ -31,6 +33,7 @@ import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -38,21 +41,24 @@ import java.util.Set;
|
||||
/**
|
||||
* ELK + JFreeSVG implementation of {@link DiagramRenderer}.
|
||||
* <p>
|
||||
* Uses Eclipse ELK layered algorithm for top-to-bottom layout computation
|
||||
* Uses Eclipse ELK layered algorithm for layout computation
|
||||
* and JFreeSVG for SVG document generation with color-coded nodes.
|
||||
*/
|
||||
public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
|
||||
// Register ELK provider once (not per-instance)
|
||||
static {
|
||||
org.eclipse.elk.core.data.LayoutMetaDataService.getInstance()
|
||||
.registerLayoutMetaDataProviders(new LayeredMetaDataProvider());
|
||||
}
|
||||
|
||||
private static final int PADDING = 20;
|
||||
private static final int NODE_HEIGHT = 40;
|
||||
private static final int NODE_WIDTH = 160;
|
||||
private static final int MIN_NODE_WIDTH = 80;
|
||||
private static final int CHAR_WIDTH = 8;
|
||||
private static final int LABEL_PADDING = 32;
|
||||
private static final int NODE_HEIGHT = 56;
|
||||
private static final int NODE_WIDTH = 220;
|
||||
private static final int COMPOUND_TOP_PADDING = 30;
|
||||
private static final int COMPOUND_SIDE_PADDING = 10;
|
||||
private static final int CORNER_RADIUS = 8;
|
||||
private static final double NODE_SPACING = 90.0;
|
||||
private static final double NODE_SPACING = 120.0;
|
||||
private static final double EDGE_SPACING = 20.0;
|
||||
|
||||
// Blue: endpoints
|
||||
@@ -103,16 +109,14 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
NodeType.DO_TRY, NodeType.DO_CATCH, NodeType.DO_FINALLY,
|
||||
NodeType.EIP_LOOP, NodeType.EIP_MULTICAST,
|
||||
NodeType.EIP_AGGREGATE, NodeType.ON_EXCEPTION, NodeType.ERROR_HANDLER,
|
||||
NodeType.ON_COMPLETION
|
||||
NodeType.ON_COMPLETION, NodeType.EIP_CIRCUIT_BREAKER,
|
||||
NodeType.EIP_FILTER, NodeType.EIP_IDEMPOTENT_CONSUMER, NodeType.EIP_RECIPIENT_LIST
|
||||
);
|
||||
|
||||
public ElkDiagramRenderer() {
|
||||
// Ensure the layered algorithm meta data provider is registered.
|
||||
// LayoutMetaDataService uses ServiceLoader, but explicit registration
|
||||
// guarantees availability regardless of classpath ordering.
|
||||
org.eclipse.elk.core.data.LayoutMetaDataService.getInstance()
|
||||
.registerLayoutMetaDataProviders(new LayeredMetaDataProvider());
|
||||
}
|
||||
/** Top-level handler types laid out in their own separate ELK graph. */
|
||||
private static final Set<NodeType> HANDLER_SECTION_TYPES = EnumSet.of(
|
||||
NodeType.ON_EXCEPTION, NodeType.ERROR_HANDLER, NodeType.ON_COMPLETION
|
||||
);
|
||||
|
||||
@Override
|
||||
public String renderSvg(RouteGraph graph) {
|
||||
@@ -136,7 +140,17 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
Font labelFont = new Font("SansSerif", Font.PLAIN, 12);
|
||||
g2.setFont(labelFont);
|
||||
|
||||
// Draw compound containers first (background)
|
||||
// Collect IDs of nodes drawn inside compounds (to avoid double-drawing)
|
||||
Set<String> compoundChildIds = new HashSet<>();
|
||||
for (PositionedNode node : allNodes(layout.nodes())) {
|
||||
if (result.compoundInfos.containsKey(node.id()) && node.children() != null) {
|
||||
for (PositionedNode child : node.children()) {
|
||||
collectAllIds(child, compoundChildIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw compound containers first (background + children inside)
|
||||
for (Map.Entry<String, CompoundInfo> entry : result.compoundInfos.entrySet()) {
|
||||
CompoundInfo ci = entry.getValue();
|
||||
PositionedNode pn = findNode(layout.nodes(), ci.nodeId);
|
||||
@@ -145,8 +159,9 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw leaf nodes
|
||||
// Draw leaf nodes (skip compounds and their children — already drawn above)
|
||||
for (PositionedNode node : allNodes(layout.nodes())) {
|
||||
if (compoundChildIds.contains(node.id())) continue;
|
||||
if (!result.compoundInfos.containsKey(node.id()) || node.children().isEmpty()) {
|
||||
drawNode(g2, node, result.nodeColors.getOrDefault(node.id(), PURPLE));
|
||||
}
|
||||
@@ -171,109 +186,249 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
private LayoutResult computeLayout(RouteGraph graph, Direction rootDirection) {
|
||||
ElkGraphFactory factory = ElkGraphFactory.eINSTANCE;
|
||||
// 1. Build node index from root tree (preserves children) with flat-list fallback
|
||||
Map<String, RouteNode> nodeById = buildNodeIndex(graph);
|
||||
LayoutContext ctx = new LayoutContext(ElkGraphFactory.eINSTANCE, nodeById);
|
||||
|
||||
// Create root node
|
||||
ElkNode rootNode = factory.createElkNode();
|
||||
rootNode.setIdentifier("root");
|
||||
rootNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
rootNode.setProperty(CoreOptions.DIRECTION, rootDirection);
|
||||
rootNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING);
|
||||
rootNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING);
|
||||
rootNode.setProperty(CoreOptions.HIERARCHY_HANDLING, HierarchyHandling.INCLUDE_CHILDREN);
|
||||
// 2. Partition graph nodes into main flow vs handler sections
|
||||
List<RouteNode> mainNodes = new ArrayList<>();
|
||||
List<RouteNode> handlerNodes = new ArrayList<>();
|
||||
partitionNodes(graph, nodeById, mainNodes, handlerNodes);
|
||||
|
||||
// Build index of all RouteNodes (flat list from graph + recursive children)
|
||||
Map<String, RouteNode> routeNodeMap = new HashMap<>();
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
indexNodeRecursive(rn, routeNodeMap);
|
||||
// 3. Build ELK graphs — main flow + separate handler roots
|
||||
ElkNode rootNode = createElkRoot("root", rootDirection, 1.0, ctx);
|
||||
for (RouteNode rn : mainNodes) {
|
||||
if (!ctx.elkNodeMap.containsKey(rn.getId())) {
|
||||
createElkNodeRecursive(rn, rootNode, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Track which nodes are children of a compound (at any depth)
|
||||
Set<String> childNodeIds = new HashSet<>();
|
||||
|
||||
// Create ELK nodes recursively — compounds contain their children
|
||||
Map<String, ElkNode> elkNodeMap = new HashMap<>();
|
||||
Map<String, Color> nodeColors = new HashMap<>();
|
||||
Set<String> compoundNodeIds = new HashSet<>();
|
||||
|
||||
// Process top-level nodes from the graph
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
if (!elkNodeMap.containsKey(rn.getId())) {
|
||||
createElkNodeRecursive(rn, rootNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds);
|
||||
}
|
||||
}
|
||||
List<ElkNode> handlerRoots = new ArrayList<>();
|
||||
for (RouteNode rn : handlerNodes) {
|
||||
ElkNode hr = createElkRoot("handler-root-" + rn.getId(), rootDirection, 0.5, ctx);
|
||||
createElkNodeRecursive(rn, hr, ctx);
|
||||
handlerRoots.add(hr);
|
||||
}
|
||||
|
||||
// Create ELK edges
|
||||
if (graph.getEdges() != null) {
|
||||
for (RouteEdge re : graph.getEdges()) {
|
||||
ElkNode sourceElk = elkNodeMap.get(re.getSource());
|
||||
ElkNode targetElk = elkNodeMap.get(re.getTarget());
|
||||
if (sourceElk == null || targetElk == null) {
|
||||
continue;
|
||||
}
|
||||
// 4. Create ELK edges (filtering DO_TRY internals and cross-root edges)
|
||||
createElkEdges(graph, ctx);
|
||||
|
||||
// Determine the containing node for the edge
|
||||
ElkNode containingNode = findCommonParent(sourceElk, targetElk);
|
||||
|
||||
ElkEdge elkEdge = factory.createElkEdge();
|
||||
elkEdge.setContainingNode(containingNode);
|
||||
elkEdge.getSources().add(sourceElk);
|
||||
elkEdge.getTargets().add(targetElk);
|
||||
}
|
||||
}
|
||||
|
||||
// Run layout
|
||||
// 5. Run layout engine
|
||||
RecursiveGraphLayoutEngine engine = new RecursiveGraphLayoutEngine();
|
||||
engine.layout(rootNode, new BasicProgressMonitor());
|
||||
for (ElkNode hr : handlerRoots) {
|
||||
engine.layout(hr, new BasicProgressMonitor());
|
||||
}
|
||||
|
||||
// Extract results — only top-level nodes (children collected recursively)
|
||||
List<PositionedNode> positionedNodes = new ArrayList<>();
|
||||
Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
||||
// 6. Post-process: fix DO_TRY section ordering and widths
|
||||
postProcessDoTrySections(ctx);
|
||||
|
||||
// 7. Extract positioned result
|
||||
return extractLayout(graph, rootNode, handlerRoots, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a node lookup by walking the root tree (which preserves children for
|
||||
* compound nodes). Falls back to the flat nodes list for any nodes not in the
|
||||
* tree (backward compatibility when root is not set).
|
||||
*/
|
||||
private Map<String, RouteNode> buildNodeIndex(RouteGraph graph) {
|
||||
Map<String, RouteNode> nodeById = new HashMap<>();
|
||||
if (graph.getRoot() != null) {
|
||||
collectNodesRecursive(graph.getRoot(), nodeById);
|
||||
}
|
||||
if (graph.getNodes() != null) {
|
||||
for (RouteNode rn : graph.getNodes()) {
|
||||
if (childNodeIds.contains(rn.getId())) {
|
||||
// Skip — collected under its parent compound
|
||||
continue;
|
||||
}
|
||||
ElkNode elkNode = elkNodeMap.get(rn.getId());
|
||||
if (elkNode == null) continue;
|
||||
nodeById.putIfAbsent(rn.getId(), rn);
|
||||
}
|
||||
}
|
||||
return nodeById;
|
||||
}
|
||||
|
||||
positionedNodes.add(extractPositionedNode(rn, elkNode, elkNodeMap,
|
||||
compoundNodeIds, compoundInfos, rootNode));
|
||||
/** Recursively collect RouteNodes from the tree into a map (preserves children). */
|
||||
private void collectNodesRecursive(RouteNode node, Map<String, RouteNode> map) {
|
||||
if (node.getId() != null) {
|
||||
map.put(node.getId(), node);
|
||||
}
|
||||
if (node.getChildren() != null) {
|
||||
for (RouteNode child : node.getChildren()) {
|
||||
collectNodesRecursive(child, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Separate main flow nodes from handler sections by walking FLOW edges
|
||||
* from the graph root. Handler sections (onException, onCompletion, etc.)
|
||||
* are placed in their own list for independent layout.
|
||||
*/
|
||||
private void partitionNodes(RouteGraph graph, Map<String, RouteNode> nodeById,
|
||||
List<RouteNode> mainNodes, List<RouteNode> handlerNodes) {
|
||||
Set<String> mainNodeIds = new HashSet<>();
|
||||
|
||||
if (graph.getRoot() != null && graph.getEdges() != null) {
|
||||
mainNodeIds.add(graph.getRoot().getId());
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (RouteEdge re : graph.getEdges()) {
|
||||
if (re.getEdgeType() == RouteEdge.EdgeType.ERROR) continue;
|
||||
if (mainNodeIds.contains(re.getSource()) && !mainNodeIds.contains(re.getTarget())) {
|
||||
RouteNode target = nodeById.get(re.getTarget());
|
||||
if (target != null && target.getType() != null
|
||||
&& HANDLER_SECTION_TYPES.contains(target.getType())) {
|
||||
continue;
|
||||
}
|
||||
mainNodeIds.add(re.getTarget());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract edges
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (RouteNode rn : nodeById.values()) {
|
||||
if (seen.contains(rn.getId())) continue;
|
||||
seen.add(rn.getId());
|
||||
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty()) {
|
||||
handlerNodes.add(rn);
|
||||
} else if (mainNodeIds.isEmpty() || mainNodeIds.contains(rn.getId())) {
|
||||
mainNodes.add(rn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a configured ELK root node for the layered algorithm. */
|
||||
private ElkNode createElkRoot(String identifier, Direction direction,
|
||||
double spacingScale, LayoutContext ctx) {
|
||||
ElkNode root = ctx.factory.createElkNode();
|
||||
root.setIdentifier(identifier);
|
||||
root.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
root.setProperty(CoreOptions.DIRECTION, direction);
|
||||
root.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * spacingScale);
|
||||
root.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * spacingScale);
|
||||
root.setProperty(CoreOptions.HIERARCHY_HANDLING, HierarchyHandling.INCLUDE_CHILDREN);
|
||||
root.setProperty(CoreOptions.EDGE_ROUTING, EdgeRouting.POLYLINE);
|
||||
root.setProperty(org.eclipse.elk.alg.layered.options.LayeredOptions.NODE_PLACEMENT_STRATEGY,
|
||||
NodePlacementStrategy.NETWORK_SIMPLEX);
|
||||
return root;
|
||||
}
|
||||
|
||||
/** Create ELK edges, skipping structural compound-to-child edges and cross-root edges. */
|
||||
private void createElkEdges(RouteGraph graph, LayoutContext ctx) {
|
||||
if (graph.getEdges() == null) return;
|
||||
for (RouteEdge re : graph.getEdges()) {
|
||||
ElkNode sourceElk = ctx.elkNodeMap.get(re.getSource());
|
||||
ElkNode targetElk = ctx.elkNodeMap.get(re.getTarget());
|
||||
if (sourceElk == null || targetElk == null) continue;
|
||||
|
||||
// Skip edges from any compound node to its own descendants
|
||||
// (these are structural edges, not flow edges)
|
||||
if (ctx.compoundNodeIds.contains(re.getSource()) && isDescendantOf(targetElk, sourceElk)) {
|
||||
continue;
|
||||
}
|
||||
// Skip edges that cross ELK root boundaries — but allow continuation
|
||||
// edges that exit a compound (source inside compound, target outside)
|
||||
if (getElkRoot(sourceElk) != getElkRoot(targetElk)) {
|
||||
// Allow if source and target share the same grandparent (main root)
|
||||
// i.e., one is inside a compound and the other is a sibling
|
||||
ElkNode sourceRoot = getElkRoot(sourceElk);
|
||||
ElkNode targetRoot = getElkRoot(targetElk);
|
||||
boolean sameGrandparent = sourceRoot.getParent() != null
|
||||
&& targetRoot.getParent() != null
|
||||
&& sourceRoot.getParent() == targetRoot.getParent();
|
||||
boolean sourceExitsCompound = sourceRoot.getParent() != null
|
||||
&& targetRoot == sourceRoot.getParent();
|
||||
boolean targetExitsCompound = targetRoot.getParent() != null
|
||||
&& sourceRoot == targetRoot.getParent();
|
||||
if (!sameGrandparent && !sourceExitsCompound && !targetExitsCompound) continue;
|
||||
}
|
||||
|
||||
ElkEdge elkEdge = ctx.factory.createElkEdge();
|
||||
elkEdge.setContainingNode(findCommonParent(sourceElk, targetElk));
|
||||
elkEdge.getSources().add(sourceElk);
|
||||
elkEdge.getTargets().add(targetElk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process DO_TRY compounds after layout: re-stack sections in correct
|
||||
* vertical order (try_body → doFinally → doCatch) and stretch to uniform width.
|
||||
* ELK doesn't reliably order disconnected children within a compound.
|
||||
*/
|
||||
private void postProcessDoTrySections(LayoutContext ctx) {
|
||||
for (Map.Entry<String, List<String>> entry : ctx.doTrySectionOrder.entrySet()) {
|
||||
String doTryId = entry.getKey();
|
||||
List<String> orderedIds = entry.getValue();
|
||||
List<ElkNode> sections = new ArrayList<>();
|
||||
for (String id : orderedIds) {
|
||||
ElkNode section = ctx.elkNodeMap.get(id);
|
||||
if (section != null) sections.add(section);
|
||||
}
|
||||
if (sections.size() < 2) continue;
|
||||
|
||||
// Left-align all sections and stack vertically
|
||||
double startY = sections.stream().mapToDouble(ElkNode::getY).min().orElse(0);
|
||||
double spacing = 20;
|
||||
double currentY = startY;
|
||||
for (ElkNode section : sections) {
|
||||
section.setX(COMPOUND_SIDE_PADDING);
|
||||
section.setY(currentY);
|
||||
currentY += section.getHeight() + spacing;
|
||||
}
|
||||
|
||||
// Uniform width
|
||||
double maxWidth = sections.stream().mapToDouble(ElkNode::getWidth).max().orElse(0);
|
||||
for (ElkNode section : sections) {
|
||||
section.setWidth(maxWidth);
|
||||
}
|
||||
|
||||
// Shrink DO_TRY parent height to fit its content
|
||||
ElkNode doTryNode = ctx.elkNodeMap.get(doTryId);
|
||||
if (doTryNode != null) {
|
||||
double contentBottom = currentY - spacing + COMPOUND_SIDE_PADDING;
|
||||
doTryNode.setHeight(contentBottom);
|
||||
doTryNode.setWidth(maxWidth + 2 * COMPOUND_SIDE_PADDING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Extract positioned nodes, edges, and bounding box from the ELK layout result. */
|
||||
private LayoutResult extractLayout(RouteGraph graph, ElkNode rootNode,
|
||||
List<ElkNode> handlerRoots, LayoutContext ctx) {
|
||||
// Extract positioned nodes (uses tree-aware nodeById for compound children)
|
||||
List<PositionedNode> positionedNodes = new ArrayList<>();
|
||||
for (RouteNode rn : ctx.nodeById.values()) {
|
||||
if (ctx.childNodeIds.contains(rn.getId())) continue;
|
||||
ElkNode elkNode = ctx.elkNodeMap.get(rn.getId());
|
||||
if (elkNode == null) continue;
|
||||
positionedNodes.add(extractPositionedNode(rn, elkNode, getElkRoot(elkNode), ctx));
|
||||
}
|
||||
|
||||
// Extract edges from main root + handler roots
|
||||
List<PositionedEdge> positionedEdges = new ArrayList<>();
|
||||
for (ElkEdge elkEdge : collectAllEdges(rootNode)) {
|
||||
List<ElkEdge> allEdges = collectAllEdges(rootNode);
|
||||
for (ElkNode hr : handlerRoots) {
|
||||
allEdges.addAll(collectAllEdges(hr));
|
||||
}
|
||||
for (ElkEdge elkEdge : allEdges) {
|
||||
String sourceId = elkEdge.getSources().isEmpty() ? "" : elkEdge.getSources().get(0).getIdentifier();
|
||||
String targetId = elkEdge.getTargets().isEmpty() ? "" : elkEdge.getTargets().get(0).getIdentifier();
|
||||
ElkNode containingNode = elkEdge.getContainingNode();
|
||||
ElkNode edgeRoot = containingNode != null ? getElkRoot(containingNode) : null;
|
||||
|
||||
List<double[]> points = new ArrayList<>();
|
||||
for (ElkEdgeSection section : elkEdge.getSections()) {
|
||||
points.add(new double[]{
|
||||
section.getStartX() + getAbsoluteX(elkEdge.getContainingNode(), rootNode),
|
||||
section.getStartY() + getAbsoluteY(elkEdge.getContainingNode(), rootNode)
|
||||
});
|
||||
double cx = containingNode != null ? getAbsoluteX(containingNode, edgeRoot) : 0;
|
||||
double cy = containingNode != null ? getAbsoluteY(containingNode, edgeRoot) : 0;
|
||||
points.add(new double[]{section.getStartX() + cx, section.getStartY() + cy});
|
||||
for (ElkBendPoint bp : section.getBendPoints()) {
|
||||
points.add(new double[]{
|
||||
bp.getX() + getAbsoluteX(elkEdge.getContainingNode(), rootNode),
|
||||
bp.getY() + getAbsoluteY(elkEdge.getContainingNode(), rootNode)
|
||||
});
|
||||
points.add(new double[]{bp.getX() + cx, bp.getY() + cy});
|
||||
}
|
||||
points.add(new double[]{
|
||||
section.getEndX() + getAbsoluteX(elkEdge.getContainingNode(), rootNode),
|
||||
section.getEndY() + getAbsoluteY(elkEdge.getContainingNode(), rootNode)
|
||||
});
|
||||
points.add(new double[]{section.getEndX() + cx, section.getEndY() + cy});
|
||||
}
|
||||
|
||||
// Find label from original edge
|
||||
String label = "";
|
||||
if (graph.getEdges() != null) {
|
||||
for (RouteEdge re : graph.getEdges()) {
|
||||
@@ -283,15 +438,24 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
positionedEdges.add(new PositionedEdge(sourceId, targetId, label, points));
|
||||
}
|
||||
|
||||
double totalWidth = rootNode.getWidth();
|
||||
double totalHeight = rootNode.getHeight();
|
||||
// Compute bounding box
|
||||
double totalWidth = 0, totalHeight = 0;
|
||||
for (PositionedNode pn : allNodes(positionedNodes)) {
|
||||
totalWidth = Math.max(totalWidth, pn.x() + pn.width());
|
||||
totalHeight = Math.max(totalHeight, pn.y() + pn.height());
|
||||
}
|
||||
for (PositionedEdge pe : positionedEdges) {
|
||||
for (double[] pt : pe.points()) {
|
||||
totalWidth = Math.max(totalWidth, pt[0]);
|
||||
totalHeight = Math.max(totalHeight, pt[1]);
|
||||
}
|
||||
}
|
||||
|
||||
DiagramLayout layout = new DiagramLayout(totalWidth, totalHeight, positionedNodes, positionedEdges);
|
||||
return new LayoutResult(layout, nodeColors, compoundInfos);
|
||||
return new LayoutResult(layout, ctx.nodeColors, ctx.compoundInfos);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@@ -316,7 +480,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
|
||||
private void drawCompoundContainer(SVGGraphics2D g2, PositionedNode node, Color color) {
|
||||
// Semi-transparent background
|
||||
Color bg = new Color(color.getRed(), color.getGreen(), color.getBlue(), 38); // ~15% alpha
|
||||
Color bg = new Color(color.getRed(), color.getGreen(), color.getBlue(), 38);
|
||||
g2.setColor(bg);
|
||||
g2.fill(new RoundRectangle2D.Double(
|
||||
node.x(), node.y(), node.width(), node.height(),
|
||||
@@ -331,7 +495,6 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
|
||||
// Label at top
|
||||
g2.setColor(color);
|
||||
FontMetrics fm = g2.getFontMetrics();
|
||||
float labelX = (float) (node.x() + COMPOUND_SIDE_PADDING);
|
||||
float labelY = (float) (node.y() + 18);
|
||||
g2.drawString(node.label(), labelX, labelY);
|
||||
@@ -385,7 +548,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
if (ENDPOINT_TYPES.contains(type)) return BLUE;
|
||||
if (PROCESSOR_TYPES.contains(type)) return GREEN;
|
||||
if (ERROR_TYPES.contains(type)) return RED;
|
||||
if (EIP_TYPES.contains(type)) return EIP_TYPES.contains(type) ? PURPLE : PURPLE;
|
||||
if (EIP_TYPES.contains(type)) return PURPLE;
|
||||
if (CROSS_ROUTE_TYPES.contains(type)) return CYAN;
|
||||
return PURPLE;
|
||||
}
|
||||
@@ -403,34 +566,160 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
// Recursive node building
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/** Index a RouteNode and all its descendants into the map. */
|
||||
private void indexNodeRecursive(RouteNode node, Map<String, RouteNode> map) {
|
||||
map.put(node.getId(), node);
|
||||
if (node.getChildren() != null) {
|
||||
for (RouteNode child : node.getChildren()) {
|
||||
indexNodeRecursive(child, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create ELK nodes. Compound nodes become ELK containers
|
||||
* with their children nested inside. Non-compound nodes become leaf nodes.
|
||||
*/
|
||||
private void createElkNodeRecursive(
|
||||
RouteNode rn, ElkNode parentElk, ElkGraphFactory factory,
|
||||
Map<String, ElkNode> elkNodeMap, Map<String, Color> nodeColors,
|
||||
Set<String> compoundNodeIds, Set<String> childNodeIds) {
|
||||
RouteNode rn, ElkNode parentElk, LayoutContext ctx) {
|
||||
|
||||
boolean isCompound = rn.getType() != null && COMPOUND_TYPES.contains(rn.getType())
|
||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty();
|
||||
|
||||
ElkNode elkNode = factory.createElkNode();
|
||||
ElkNode elkNode = ctx.factory.createElkNode();
|
||||
elkNode.setIdentifier(rn.getId());
|
||||
elkNode.setParent(parentElk);
|
||||
|
||||
if (isCompound) {
|
||||
compoundNodeIds.add(rn.getId());
|
||||
if (isCompound && rn.getType() == NodeType.DO_TRY) {
|
||||
// DO_TRY: vertical container with a virtual _TRY_BODY wrapper for the try body
|
||||
// and DO_CATCH/DO_FINALLY as separate children below
|
||||
ctx.doTryNodeIds.add(rn.getId());
|
||||
ctx.compoundNodeIds.add(rn.getId());
|
||||
elkNode.setWidth(200);
|
||||
elkNode.setHeight(100);
|
||||
elkNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
elkNode.setProperty(CoreOptions.DIRECTION, Direction.DOWN);
|
||||
elkNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.4);
|
||||
elkNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.3);
|
||||
elkNode.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(COMPOUND_TOP_PADDING,
|
||||
COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING));
|
||||
// Separate try body children from handler children
|
||||
List<RouteNode> tryBodyChildren = new ArrayList<>();
|
||||
List<RouteNode> handlerChildren = new ArrayList<>();
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if (child.getType() == NodeType.DO_CATCH || child.getType() == NodeType.DO_FINALLY) {
|
||||
handlerChildren.add(child);
|
||||
} else {
|
||||
tryBodyChildren.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
// Track desired section order: try_body → doFinally → doCatch
|
||||
List<String> sectionOrder = new ArrayList<>();
|
||||
|
||||
// Virtual _TRY_BODY wrapper
|
||||
if (!tryBodyChildren.isEmpty()) {
|
||||
String wrapperId = rn.getId() + "._try_body";
|
||||
ElkNode wrapper = ctx.factory.createElkNode();
|
||||
wrapper.setIdentifier(wrapperId);
|
||||
wrapper.setParent(elkNode);
|
||||
wrapper.setWidth(200);
|
||||
wrapper.setHeight(40);
|
||||
wrapper.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
wrapper.setProperty(CoreOptions.DIRECTION, Direction.RIGHT);
|
||||
wrapper.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(8, 8, 8, 8));
|
||||
ctx.compoundNodeIds.add(wrapperId);
|
||||
ctx.elkNodeMap.put(wrapperId, wrapper);
|
||||
sectionOrder.add(wrapperId);
|
||||
|
||||
for (RouteNode child : tryBodyChildren) {
|
||||
ctx.childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, wrapper, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Handler sections in order: DO_FINALLY (middle), then DO_CATCH (bottom)
|
||||
for (RouteNode handler : orderedHandlerChildren(handlerChildren)) {
|
||||
ctx.childNodeIds.add(handler.getId());
|
||||
createElkNodeRecursive(handler, elkNode, ctx);
|
||||
sectionOrder.add(handler.getId());
|
||||
}
|
||||
|
||||
ctx.doTrySectionOrder.put(rn.getId(), sectionOrder);
|
||||
} else if (isCompound && rn.getType() == NodeType.EIP_CIRCUIT_BREAKER) {
|
||||
// CIRCUIT_BREAKER: vertical container with _CB_MAIN for main path
|
||||
// and onFallback as a compound section below (like DO_TRY pattern)
|
||||
ctx.compoundNodeIds.add(rn.getId());
|
||||
elkNode.setWidth(200);
|
||||
elkNode.setHeight(100);
|
||||
elkNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
elkNode.setProperty(CoreOptions.DIRECTION, Direction.DOWN);
|
||||
elkNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.4);
|
||||
elkNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.3);
|
||||
elkNode.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(COMPOUND_TOP_PADDING,
|
||||
COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING));
|
||||
|
||||
// Separate main path children from onFallback child
|
||||
List<RouteNode> mainChildren = new ArrayList<>();
|
||||
RouteNode fallbackNode = null;
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if ("onFallback".equals(child.getLabel())) {
|
||||
fallbackNode = child;
|
||||
} else {
|
||||
mainChildren.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> sectionOrder = new ArrayList<>();
|
||||
|
||||
// Virtual _CB_MAIN wrapper for main path (horizontal flow)
|
||||
if (!mainChildren.isEmpty()) {
|
||||
String wrapperId = rn.getId() + "._cb_main";
|
||||
ElkNode wrapper = ctx.factory.createElkNode();
|
||||
wrapper.setIdentifier(wrapperId);
|
||||
wrapper.setParent(elkNode);
|
||||
wrapper.setWidth(200);
|
||||
wrapper.setHeight(40);
|
||||
wrapper.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
wrapper.setProperty(CoreOptions.DIRECTION, Direction.RIGHT);
|
||||
wrapper.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(8, 8, 8, 8));
|
||||
ctx.compoundNodeIds.add(wrapperId);
|
||||
ctx.elkNodeMap.put(wrapperId, wrapper);
|
||||
sectionOrder.add(wrapperId);
|
||||
|
||||
for (RouteNode child : mainChildren) {
|
||||
ctx.childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, wrapper, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// onFallback as compound section containing its children
|
||||
if (fallbackNode != null) {
|
||||
ctx.childNodeIds.add(fallbackNode.getId());
|
||||
ElkNode fallbackElk = ctx.factory.createElkNode();
|
||||
fallbackElk.setIdentifier(fallbackNode.getId());
|
||||
fallbackElk.setParent(elkNode);
|
||||
fallbackElk.setWidth(200);
|
||||
fallbackElk.setHeight(40);
|
||||
fallbackElk.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
fallbackElk.setProperty(CoreOptions.DIRECTION, Direction.RIGHT);
|
||||
fallbackElk.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.5);
|
||||
fallbackElk.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
fallbackElk.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(18, 8, 8, 8));
|
||||
ctx.compoundNodeIds.add(fallbackNode.getId());
|
||||
ctx.elkNodeMap.put(fallbackNode.getId(), fallbackElk);
|
||||
sectionOrder.add(fallbackNode.getId());
|
||||
|
||||
if (fallbackNode.getChildren() != null) {
|
||||
for (RouteNode child : fallbackNode.getChildren()) {
|
||||
ctx.childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, fallbackElk, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.doTrySectionOrder.put(rn.getId(), sectionOrder);
|
||||
} else if (isCompound) {
|
||||
ctx.compoundNodeIds.add(rn.getId());
|
||||
elkNode.setWidth(200);
|
||||
elkNode.setHeight(100);
|
||||
elkNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
@@ -441,44 +730,120 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
new org.eclipse.elk.core.math.ElkPadding(COMPOUND_TOP_PADDING,
|
||||
COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING));
|
||||
|
||||
// Recursively create children inside this compound
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, elkNode, factory, elkNodeMap, nodeColors,
|
||||
compoundNodeIds, childNodeIds);
|
||||
ctx.childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, elkNode, ctx);
|
||||
}
|
||||
} else {
|
||||
elkNode.setWidth(NODE_WIDTH);
|
||||
elkNode.setHeight(NODE_HEIGHT);
|
||||
}
|
||||
|
||||
elkNodeMap.put(rn.getId(), elkNode);
|
||||
nodeColors.put(rn.getId(), colorForType(rn.getType()));
|
||||
ctx.elkNodeMap.put(rn.getId(), elkNode);
|
||||
ctx.nodeColors.put(rn.getId(), colorForType(rn.getType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively extract a PositionedNode from the ELK layout result.
|
||||
* Compound nodes include their children with absolute coordinates.
|
||||
* All coordinates are absolute (relative to the ELK root).
|
||||
*/
|
||||
private PositionedNode extractPositionedNode(
|
||||
RouteNode rn, ElkNode elkNode, Map<String, ElkNode> elkNodeMap,
|
||||
Set<String> compoundNodeIds, Map<String, CompoundInfo> compoundInfos,
|
||||
ElkNode rootNode) {
|
||||
RouteNode rn, ElkNode elkNode, ElkNode rootNode, LayoutContext ctx) {
|
||||
|
||||
double absX = getAbsoluteX(elkNode, rootNode);
|
||||
double absY = getAbsoluteY(elkNode, rootNode);
|
||||
|
||||
List<PositionedNode> children = List.of();
|
||||
if (compoundNodeIds.contains(rn.getId()) && rn.getChildren() != null) {
|
||||
if (ctx.compoundNodeIds.contains(rn.getId()) && rn.getChildren() != null) {
|
||||
children = new ArrayList<>();
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
ElkNode childElk = elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
children.add(extractPositionedNode(child, childElk, elkNodeMap,
|
||||
compoundNodeIds, compoundInfos, rootNode));
|
||||
|
||||
if (rn.getType() == NodeType.DO_TRY) {
|
||||
// DO_TRY: extract virtual _TRY_BODY wrapper first, then handler children
|
||||
String wrapperId = rn.getId() + "._try_body";
|
||||
ElkNode wrapperElk = ctx.elkNodeMap.get(wrapperId);
|
||||
if (wrapperElk != null) {
|
||||
List<PositionedNode> wrapperChildren = new ArrayList<>();
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if (child.getType() != NodeType.DO_CATCH && child.getType() != NodeType.DO_FINALLY) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
wrapperChildren.add(extractPositionedNode(child, childElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
children.add(new PositionedNode(
|
||||
wrapperId, "", "_TRY_BODY",
|
||||
getAbsoluteX(wrapperElk, rootNode),
|
||||
getAbsoluteY(wrapperElk, rootNode),
|
||||
wrapperElk.getWidth(), wrapperElk.getHeight(),
|
||||
wrapperChildren, null));
|
||||
ctx.compoundInfos.put(wrapperId, new CompoundInfo(wrapperId, Color.WHITE));
|
||||
}
|
||||
// Handler children in order: DO_FINALLY first, then DO_CATCH
|
||||
for (RouteNode handler : orderedHandlerChildren(rn.getChildren())) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(handler.getId());
|
||||
if (childElk != null) {
|
||||
children.add(extractPositionedNode(handler, childElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
} else if (rn.getType() == NodeType.EIP_CIRCUIT_BREAKER) {
|
||||
// CIRCUIT_BREAKER: extract _CB_MAIN wrapper, then onFallback section
|
||||
String mainWrapperId = rn.getId() + "._cb_main";
|
||||
ElkNode mainWrapperElk = ctx.elkNodeMap.get(mainWrapperId);
|
||||
if (mainWrapperElk != null) {
|
||||
List<PositionedNode> wrapperChildren = new ArrayList<>();
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if (!"onFallback".equals(child.getLabel())) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
wrapperChildren.add(extractPositionedNode(child, childElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
children.add(new PositionedNode(
|
||||
mainWrapperId, "", "_CB_MAIN",
|
||||
getAbsoluteX(mainWrapperElk, rootNode),
|
||||
getAbsoluteY(mainWrapperElk, rootNode),
|
||||
mainWrapperElk.getWidth(), mainWrapperElk.getHeight(),
|
||||
wrapperChildren, null));
|
||||
ctx.compoundInfos.put(mainWrapperId, new CompoundInfo(mainWrapperId, Color.WHITE));
|
||||
}
|
||||
// onFallback section with its children, type overridden to _CB_FALLBACK
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if ("onFallback".equals(child.getLabel())) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
List<PositionedNode> fallbackChildren = new ArrayList<>();
|
||||
if (child.getChildren() != null) {
|
||||
for (RouteNode fc : child.getChildren()) {
|
||||
ElkNode fcElk = ctx.elkNodeMap.get(fc.getId());
|
||||
if (fcElk != null) {
|
||||
fallbackChildren.add(extractPositionedNode(fc, fcElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
children.add(new PositionedNode(
|
||||
child.getId(),
|
||||
child.getLabel() != null ? child.getLabel() : "",
|
||||
"_CB_FALLBACK",
|
||||
getAbsoluteX(childElk, rootNode),
|
||||
getAbsoluteY(childElk, rootNode),
|
||||
childElk.getWidth(), childElk.getHeight(),
|
||||
fallbackChildren, null));
|
||||
ctx.compoundInfos.put(child.getId(),
|
||||
new CompoundInfo(child.getId(), colorForType(rn.getType())));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
children.add(extractPositionedNode(child, childElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
compoundInfos.put(rn.getId(), new CompoundInfo(rn.getId(), colorForType(rn.getType())));
|
||||
ctx.compoundInfos.put(rn.getId(), new CompoundInfo(rn.getId(), colorForType(rn.getType())));
|
||||
}
|
||||
|
||||
return new PositionedNode(
|
||||
@@ -487,7 +852,8 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
rn.getType() != null ? rn.getType().name() : "UNKNOWN",
|
||||
absX, absY,
|
||||
elkNode.getWidth(), elkNode.getHeight(),
|
||||
children
|
||||
children,
|
||||
rn.getEndpointUri()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -495,19 +861,52 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
// ELK graph helpers
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/** Return handler children in section order: DO_FINALLY first, then DO_CATCH. */
|
||||
private static List<RouteNode> orderedHandlerChildren(List<RouteNode> children) {
|
||||
List<RouteNode> ordered = new ArrayList<>();
|
||||
for (RouteNode c : children) {
|
||||
if (c.getType() == NodeType.DO_FINALLY) ordered.add(c);
|
||||
}
|
||||
for (RouteNode c : children) {
|
||||
if (c.getType() == NodeType.DO_CATCH) ordered.add(c);
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
/** Check if 'child' is a descendant of 'ancestor' in the ELK node hierarchy. */
|
||||
private boolean isDescendantOf(ElkNode child, ElkNode ancestor) {
|
||||
ElkNode current = child.getParent();
|
||||
while (current != null) {
|
||||
if (current == ancestor) return true;
|
||||
current = current.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ElkNode getElkRoot(ElkNode node) {
|
||||
ElkNode current = node;
|
||||
while (current.getParent() != null) {
|
||||
current = current.getParent();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/** Proper lowest common ancestor of two ELK nodes. */
|
||||
private ElkNode findCommonParent(ElkNode a, ElkNode b) {
|
||||
if (a.getParent() == b.getParent()) {
|
||||
return a.getParent();
|
||||
Set<ElkNode> ancestorsOfA = new HashSet<>();
|
||||
ElkNode current = a;
|
||||
while (current != null) {
|
||||
ancestorsOfA.add(current);
|
||||
current = current.getParent();
|
||||
}
|
||||
// If one is the parent of the other
|
||||
if (a.getParent() != null && a.getParent() == b) return b;
|
||||
if (b.getParent() != null && b.getParent() == a) return a;
|
||||
// Default: root (grandparent)
|
||||
ElkNode parent = a.getParent();
|
||||
while (parent != null && parent.getParent() != null) {
|
||||
parent = parent.getParent();
|
||||
current = b;
|
||||
while (current != null) {
|
||||
if (ancestorsOfA.contains(current)) {
|
||||
return current;
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
return parent != null ? parent : a.getParent();
|
||||
return getElkRoot(a);
|
||||
}
|
||||
|
||||
private double getAbsoluteX(ElkNode node, ElkNode root) {
|
||||
@@ -538,13 +937,19 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
return edges;
|
||||
}
|
||||
|
||||
/** Recursively find a PositionedNode by ID. */
|
||||
private PositionedNode findNode(List<PositionedNode> nodes, String id) {
|
||||
for (PositionedNode n : nodes) {
|
||||
if (n.id().equals(id)) return n;
|
||||
if (n.children() != null) {
|
||||
PositionedNode found = findNode(n.children(), id);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Recursively flatten a PositionedNode tree. */
|
||||
private List<PositionedNode> allNodes(List<PositionedNode> nodes) {
|
||||
List<PositionedNode> all = new ArrayList<>();
|
||||
for (PositionedNode n : nodes) {
|
||||
@@ -556,6 +961,25 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
return all;
|
||||
}
|
||||
|
||||
/** Collect IDs of all RouteNode descendants (for handler separation). */
|
||||
private void collectDescendantIds(List<RouteNode> nodes, Set<String> ids) {
|
||||
for (RouteNode n : nodes) {
|
||||
ids.add(n.getId());
|
||||
if (n.getChildren() != null) {
|
||||
collectDescendantIds(n.getChildren(), ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectAllIds(PositionedNode node, Set<String> ids) {
|
||||
ids.add(node.id());
|
||||
if (node.children() != null) {
|
||||
for (PositionedNode child : node.children()) {
|
||||
collectAllIds(child, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Internal data classes
|
||||
// ----------------------------------------------------------------
|
||||
@@ -567,4 +991,22 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
) {}
|
||||
|
||||
private record CompoundInfo(String nodeId, Color color) {}
|
||||
|
||||
/** Mutable state accumulated during ELK graph construction and extraction. */
|
||||
private static class LayoutContext {
|
||||
final ElkGraphFactory factory;
|
||||
final Map<String, RouteNode> nodeById;
|
||||
final Map<String, ElkNode> elkNodeMap = new HashMap<>();
|
||||
final Map<String, Color> nodeColors = new HashMap<>();
|
||||
final Set<String> compoundNodeIds = new HashSet<>();
|
||||
final Set<String> childNodeIds = new HashSet<>();
|
||||
final Set<String> doTryNodeIds = new HashSet<>();
|
||||
final Map<String, List<String>> doTrySectionOrder = new LinkedHashMap<>();
|
||||
final Map<String, CompoundInfo> compoundInfos = new HashMap<>();
|
||||
|
||||
LayoutContext(ElkGraphFactory factory, Map<String, RouteNode> nodeById) {
|
||||
this.factory = factory;
|
||||
this.nodeById = nodeById;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.cameleer3.server.app.dto;
|
||||
|
||||
import com.cameleer3.server.core.admin.AppSettings;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "Per-application dashboard settings")
|
||||
public record AppSettingsRequest(
|
||||
@NotNull @Min(1)
|
||||
@Schema(description = "SLA duration threshold in milliseconds")
|
||||
Integer slaThresholdMs,
|
||||
|
||||
@NotNull @Min(0) @Max(100)
|
||||
@Schema(description = "Error rate % threshold for warning (yellow) health dot")
|
||||
Double healthErrorWarn,
|
||||
|
||||
@NotNull @Min(0) @Max(100)
|
||||
@Schema(description = "Error rate % threshold for critical (red) health dot")
|
||||
Double healthErrorCrit,
|
||||
|
||||
@NotNull @Min(0) @Max(100)
|
||||
@Schema(description = "SLA compliance % threshold for warning (yellow) health dot")
|
||||
Double healthSlaWarn,
|
||||
|
||||
@NotNull @Min(0) @Max(100)
|
||||
@Schema(description = "SLA compliance % threshold for critical (red) health dot")
|
||||
Double healthSlaCrit
|
||||
) {
|
||||
|
||||
public AppSettings toSettings(String appId) {
|
||||
Instant now = Instant.now();
|
||||
return new AppSettings(appId, slaThresholdMs, healthErrorWarn, healthErrorCrit,
|
||||
healthSlaWarn, healthSlaCrit, now, now);
|
||||
}
|
||||
|
||||
public List<String> validate() {
|
||||
List<String> errors = new ArrayList<>();
|
||||
if (healthErrorWarn != null && healthErrorCrit != null
|
||||
&& healthErrorWarn > healthErrorCrit) {
|
||||
errors.add("healthErrorWarn must be <= healthErrorCrit");
|
||||
}
|
||||
if (healthSlaWarn != null && healthSlaCrit != null
|
||||
&& healthSlaWarn < healthSlaCrit) {
|
||||
errors.add("healthSlaWarn must be >= healthSlaCrit (higher SLA = healthier)");
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.cameleer3.server.app.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "Request to replay an exchange on an agent")
|
||||
public record ReplayRequest(
|
||||
@NotNull @Schema(description = "Camel route ID to replay on")
|
||||
String routeId,
|
||||
@Schema(description = "Message body for the replayed exchange")
|
||||
String body,
|
||||
@Schema(description = "Message headers for the replayed exchange")
|
||||
Map<String, String> headers,
|
||||
@Schema(description = "Exchange ID of the original execution being replayed (for audit trail)")
|
||||
String originalExchangeId
|
||||
) {}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.cameleer3.server.app.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "Result of a replay command")
|
||||
public record ReplayResponse(
|
||||
@Schema(description = "Replay outcome: SUCCESS or FAILURE")
|
||||
String status,
|
||||
@Schema(description = "Human-readable result message")
|
||||
String message,
|
||||
@Schema(description = "Structured result data from the agent (JSON)")
|
||||
String data
|
||||
) {}
|
||||
@@ -15,5 +15,6 @@ public record RouteMetrics(
|
||||
@NotNull double p99DurationMs,
|
||||
@NotNull double errorRate,
|
||||
@NotNull double throughputPerSec,
|
||||
@NotNull List<Double> sparkline
|
||||
@NotNull List<Double> sparkline,
|
||||
double slaCompliance
|
||||
) {}
|
||||
|
||||
@@ -9,5 +9,7 @@ import java.time.Instant;
|
||||
public record RouteSummary(
|
||||
@NotNull String routeId,
|
||||
@NotNull long exchangeCount,
|
||||
Instant lastSeen
|
||||
Instant lastSeen,
|
||||
@Schema(description = "The from() endpoint URI, e.g. 'direct:processOrder'")
|
||||
String fromEndpointUri
|
||||
) {}
|
||||
|
||||
@@ -179,8 +179,20 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
}
|
||||
|
||||
// Keyword filters (use .keyword sub-field for exact matching on dynamically mapped text fields)
|
||||
if (request.status() != null)
|
||||
filter.add(termQuery("status.keyword", request.status()));
|
||||
if (request.status() != null && !request.status().isBlank()) {
|
||||
String[] statuses = request.status().split(",");
|
||||
if (statuses.length == 1) {
|
||||
filter.add(termQuery("status.keyword", statuses[0].trim()));
|
||||
} else {
|
||||
filter.add(Query.of(q -> q.terms(t -> t
|
||||
.field("status.keyword")
|
||||
.terms(tv -> tv.value(
|
||||
java.util.Arrays.stream(statuses)
|
||||
.map(String::trim)
|
||||
.map(FieldValue::of)
|
||||
.toList())))));
|
||||
}
|
||||
}
|
||||
if (request.routeId() != null)
|
||||
filter.add(termQuery("route_id.keyword", request.routeId()));
|
||||
if (request.agentId() != null)
|
||||
@@ -349,6 +361,8 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
return pm;
|
||||
}).toList());
|
||||
}
|
||||
map.put("has_trace_data", doc.hasTraceData());
|
||||
map.put("is_replay", doc.isReplay());
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -385,7 +399,9 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
(String) src.get("error_message"),
|
||||
null, // diagramContentHash not stored in index
|
||||
extractHighlight(hit),
|
||||
attributes
|
||||
attributes,
|
||||
Boolean.TRUE.equals(src.get("has_trace_data")),
|
||||
Boolean.TRUE.equals(src.get("is_replay"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ public class SecurityConfig {
|
||||
.requestMatchers(HttpMethod.POST, "/api/v1/agents/*/commands").hasAnyRole("OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.POST, "/api/v1/agents/groups/*/commands").hasAnyRole("OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.POST, "/api/v1/agents/commands").hasAnyRole("OPERATOR", "ADMIN")
|
||||
.requestMatchers(HttpMethod.POST, "/api/v1/agents/*/replay").hasAnyRole("OPERATOR", "ADMIN")
|
||||
|
||||
// Search endpoints
|
||||
.requestMatchers(HttpMethod.GET, "/api/v1/search/**").hasAnyRole("VIEWER", "OPERATOR", "ADMIN", "AGENT")
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.cameleer3.server.app.storage;
|
||||
|
||||
import com.cameleer3.server.core.admin.AppSettings;
|
||||
import com.cameleer3.server.core.admin.AppSettingsRepository;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class PostgresAppSettingsRepository implements AppSettingsRepository {
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
private static final RowMapper<AppSettings> ROW_MAPPER = (rs, rowNum) -> new AppSettings(
|
||||
rs.getString("app_id"),
|
||||
rs.getInt("sla_threshold_ms"),
|
||||
rs.getDouble("health_error_warn"),
|
||||
rs.getDouble("health_error_crit"),
|
||||
rs.getDouble("health_sla_warn"),
|
||||
rs.getDouble("health_sla_crit"),
|
||||
rs.getTimestamp("created_at").toInstant(),
|
||||
rs.getTimestamp("updated_at").toInstant());
|
||||
|
||||
public PostgresAppSettingsRepository(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AppSettings> findByAppId(String appId) {
|
||||
List<AppSettings> results = jdbc.query(
|
||||
"SELECT * FROM app_settings WHERE app_id = ?", ROW_MAPPER, appId);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppSettings> findAll() {
|
||||
return jdbc.query("SELECT * FROM app_settings ORDER BY app_id", ROW_MAPPER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppSettings save(AppSettings settings) {
|
||||
jdbc.update("""
|
||||
INSERT INTO app_settings (app_id, sla_threshold_ms, health_error_warn,
|
||||
health_error_crit, health_sla_warn, health_sla_crit, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, now(), now())
|
||||
ON CONFLICT (app_id) DO UPDATE SET
|
||||
sla_threshold_ms = EXCLUDED.sla_threshold_ms,
|
||||
health_error_warn = EXCLUDED.health_error_warn,
|
||||
health_error_crit = EXCLUDED.health_error_crit,
|
||||
health_sla_warn = EXCLUDED.health_sla_warn,
|
||||
health_sla_crit = EXCLUDED.health_sla_crit,
|
||||
updated_at = now()
|
||||
""",
|
||||
settings.appId(), settings.slaThresholdMs(),
|
||||
settings.healthErrorWarn(), settings.healthErrorCrit(),
|
||||
settings.healthSlaWarn(), settings.healthSlaCrit());
|
||||
return findByAppId(settings.appId()).orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String appId) {
|
||||
jdbc.update("DELETE FROM app_settings WHERE app_id = ?", appId);
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,13 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
status, correlation_id, exchange_id, start_time, end_time,
|
||||
duration_ms, error_message, error_stacktrace, diagram_content_hash,
|
||||
engine_level, input_body, output_body, input_headers, output_headers,
|
||||
attributes, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb, now(), now())
|
||||
attributes,
|
||||
error_type, error_category, root_cause_type, root_cause_message,
|
||||
trace_id, span_id,
|
||||
processors_json, has_trace_data, is_replay,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb,
|
||||
?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?, now(), now())
|
||||
ON CONFLICT (execution_id, start_time) DO UPDATE SET
|
||||
status = CASE
|
||||
WHEN EXCLUDED.status IN ('COMPLETED', 'FAILED')
|
||||
@@ -49,6 +54,15 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
input_headers = COALESCE(EXCLUDED.input_headers, executions.input_headers),
|
||||
output_headers = COALESCE(EXCLUDED.output_headers, executions.output_headers),
|
||||
attributes = COALESCE(EXCLUDED.attributes, executions.attributes),
|
||||
error_type = COALESCE(EXCLUDED.error_type, executions.error_type),
|
||||
error_category = COALESCE(EXCLUDED.error_category, executions.error_category),
|
||||
root_cause_type = COALESCE(EXCLUDED.root_cause_type, executions.root_cause_type),
|
||||
root_cause_message = COALESCE(EXCLUDED.root_cause_message, executions.root_cause_message),
|
||||
trace_id = COALESCE(EXCLUDED.trace_id, executions.trace_id),
|
||||
span_id = COALESCE(EXCLUDED.span_id, executions.span_id),
|
||||
processors_json = COALESCE(EXCLUDED.processors_json, executions.processors_json),
|
||||
has_trace_data = EXCLUDED.has_trace_data OR executions.has_trace_data,
|
||||
is_replay = EXCLUDED.is_replay OR executions.is_replay,
|
||||
updated_at = now()
|
||||
""",
|
||||
execution.executionId(), execution.routeId(), execution.agentId(),
|
||||
@@ -61,7 +75,11 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
execution.engineLevel(),
|
||||
execution.inputBody(), execution.outputBody(),
|
||||
execution.inputHeaders(), execution.outputHeaders(),
|
||||
execution.attributes());
|
||||
execution.attributes(),
|
||||
execution.errorType(), execution.errorCategory(),
|
||||
execution.rootCauseType(), execution.rootCauseMessage(),
|
||||
execution.traceId(), execution.spanId(),
|
||||
execution.processorsJson(), execution.hasTraceData(), execution.isReplay());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,8 +90,13 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
INSERT INTO processor_executions (execution_id, processor_id, processor_type,
|
||||
application_name, route_id, depth, parent_processor_id,
|
||||
status, start_time, end_time, duration_ms, error_message, error_stacktrace,
|
||||
input_body, output_body, input_headers, output_headers, attributes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb)
|
||||
input_body, output_body, input_headers, output_headers, attributes,
|
||||
loop_index, loop_size, split_index, split_size, multicast_index,
|
||||
resolved_endpoint_uri,
|
||||
error_type, error_category, root_cause_type, root_cause_message,
|
||||
error_handler_type, circuit_breaker_state, fallback_triggered)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (execution_id, processor_id, start_time) DO UPDATE SET
|
||||
status = EXCLUDED.status,
|
||||
end_time = COALESCE(EXCLUDED.end_time, processor_executions.end_time),
|
||||
@@ -84,7 +107,20 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
output_body = COALESCE(EXCLUDED.output_body, processor_executions.output_body),
|
||||
input_headers = COALESCE(EXCLUDED.input_headers, processor_executions.input_headers),
|
||||
output_headers = COALESCE(EXCLUDED.output_headers, processor_executions.output_headers),
|
||||
attributes = COALESCE(EXCLUDED.attributes, processor_executions.attributes)
|
||||
attributes = COALESCE(EXCLUDED.attributes, processor_executions.attributes),
|
||||
loop_index = COALESCE(EXCLUDED.loop_index, processor_executions.loop_index),
|
||||
loop_size = COALESCE(EXCLUDED.loop_size, processor_executions.loop_size),
|
||||
split_index = COALESCE(EXCLUDED.split_index, processor_executions.split_index),
|
||||
split_size = COALESCE(EXCLUDED.split_size, processor_executions.split_size),
|
||||
multicast_index = COALESCE(EXCLUDED.multicast_index, processor_executions.multicast_index),
|
||||
resolved_endpoint_uri = COALESCE(EXCLUDED.resolved_endpoint_uri, processor_executions.resolved_endpoint_uri),
|
||||
error_type = COALESCE(EXCLUDED.error_type, processor_executions.error_type),
|
||||
error_category = COALESCE(EXCLUDED.error_category, processor_executions.error_category),
|
||||
root_cause_type = COALESCE(EXCLUDED.root_cause_type, processor_executions.root_cause_type),
|
||||
root_cause_message = COALESCE(EXCLUDED.root_cause_message, processor_executions.root_cause_message),
|
||||
error_handler_type = COALESCE(EXCLUDED.error_handler_type, processor_executions.error_handler_type),
|
||||
circuit_breaker_state = COALESCE(EXCLUDED.circuit_breaker_state, processor_executions.circuit_breaker_state),
|
||||
fallback_triggered = COALESCE(EXCLUDED.fallback_triggered, processor_executions.fallback_triggered)
|
||||
""",
|
||||
processors.stream().map(p -> new Object[]{
|
||||
p.executionId(), p.processorId(), p.processorType(),
|
||||
@@ -94,7 +130,14 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
p.endTime() != null ? Timestamp.from(p.endTime()) : null,
|
||||
p.durationMs(), p.errorMessage(), p.errorStacktrace(),
|
||||
p.inputBody(), p.outputBody(), p.inputHeaders(), p.outputHeaders(),
|
||||
p.attributes()
|
||||
p.attributes(),
|
||||
p.loopIndex(), p.loopSize(), p.splitIndex(), p.splitSize(),
|
||||
p.multicastIndex(),
|
||||
p.resolvedEndpointUri(),
|
||||
p.errorType(), p.errorCategory(),
|
||||
p.rootCauseType(), p.rootCauseMessage(),
|
||||
p.errorHandlerType(), p.circuitBreakerState(),
|
||||
p.fallbackTriggered()
|
||||
}).toList());
|
||||
}
|
||||
|
||||
@@ -113,6 +156,13 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
PROCESSOR_MAPPER, executionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ProcessorRecord> findProcessorById(String executionId, String processorId) {
|
||||
String sql = "SELECT * FROM processor_executions WHERE execution_id = ? AND processor_id = ? LIMIT 1";
|
||||
List<ProcessorRecord> results = jdbc.query(sql, PROCESSOR_MAPPER, executionId, processorId);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
}
|
||||
|
||||
private static final RowMapper<ExecutionRecord> EXECUTION_MAPPER = (rs, rowNum) ->
|
||||
new ExecutionRecord(
|
||||
rs.getString("execution_id"), rs.getString("route_id"),
|
||||
@@ -126,7 +176,13 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
rs.getString("engine_level"),
|
||||
rs.getString("input_body"), rs.getString("output_body"),
|
||||
rs.getString("input_headers"), rs.getString("output_headers"),
|
||||
rs.getString("attributes"));
|
||||
rs.getString("attributes"),
|
||||
rs.getString("error_type"), rs.getString("error_category"),
|
||||
rs.getString("root_cause_type"), rs.getString("root_cause_message"),
|
||||
rs.getString("trace_id"), rs.getString("span_id"),
|
||||
rs.getString("processors_json"),
|
||||
rs.getBoolean("has_trace_data"),
|
||||
rs.getBoolean("is_replay"));
|
||||
|
||||
private static final RowMapper<ProcessorRecord> PROCESSOR_MAPPER = (rs, rowNum) ->
|
||||
new ProcessorRecord(
|
||||
@@ -140,7 +196,17 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
rs.getString("error_message"), rs.getString("error_stacktrace"),
|
||||
rs.getString("input_body"), rs.getString("output_body"),
|
||||
rs.getString("input_headers"), rs.getString("output_headers"),
|
||||
rs.getString("attributes"));
|
||||
rs.getString("attributes"),
|
||||
rs.getObject("loop_index") != null ? rs.getInt("loop_index") : null,
|
||||
rs.getObject("loop_size") != null ? rs.getInt("loop_size") : null,
|
||||
rs.getObject("split_index") != null ? rs.getInt("split_index") : null,
|
||||
rs.getObject("split_size") != null ? rs.getInt("split_size") : null,
|
||||
rs.getObject("multicast_index") != null ? rs.getInt("multicast_index") : null,
|
||||
rs.getString("resolved_endpoint_uri"),
|
||||
rs.getString("error_type"), rs.getString("error_category"),
|
||||
rs.getString("root_cause_type"), rs.getString("root_cause_message"),
|
||||
rs.getString("error_handler_type"), rs.getString("circuit_breaker_state"),
|
||||
rs.getObject("fallback_triggered") != null ? rs.getBoolean("fallback_triggered") : null);
|
||||
|
||||
private static Instant toInstant(ResultSet rs, String column) throws SQLException {
|
||||
Timestamp ts = rs.getTimestamp(column);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cameleer3.server.app.storage;
|
||||
import com.cameleer3.server.core.search.ExecutionStats;
|
||||
import com.cameleer3.server.core.search.StatsTimeseries;
|
||||
import com.cameleer3.server.core.search.StatsTimeseries.TimeseriesBucket;
|
||||
import com.cameleer3.server.core.search.TopError;
|
||||
import com.cameleer3.server.core.storage.StatsStore;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -12,7 +13,9 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Repository
|
||||
public class PostgresStatsStore implements StatsStore {
|
||||
@@ -184,4 +187,242 @@ public class PostgresStatsStore implements StatsStore {
|
||||
|
||||
return new StatsTimeseries(buckets);
|
||||
}
|
||||
|
||||
// ── Grouped timeseries ────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount) {
|
||||
return queryGroupedTimeseries("stats_1m_app", "application_name", from, to,
|
||||
bucketCount, List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to,
|
||||
int bucketCount, String applicationName) {
|
||||
return queryGroupedTimeseries("stats_1m_route", "route_id", from, to,
|
||||
bucketCount, List.of(new Filter("application_name", applicationName)));
|
||||
}
|
||||
|
||||
private Map<String, StatsTimeseries> queryGroupedTimeseries(
|
||||
String view, String groupCol, Instant from, Instant to,
|
||||
int bucketCount, List<Filter> filters) {
|
||||
|
||||
long intervalSeconds = Duration.between(from, to).toSeconds() / Math.max(bucketCount, 1);
|
||||
if (intervalSeconds < 60) intervalSeconds = 60;
|
||||
|
||||
String sql = "SELECT time_bucket(? * INTERVAL '1 second', bucket) AS period, " +
|
||||
groupCol + " AS group_key, " +
|
||||
"COALESCE(SUM(total_count), 0) AS total_count, " +
|
||||
"COALESCE(SUM(failed_count), 0) AS failed_count, " +
|
||||
"CASE WHEN SUM(total_count) > 0 THEN SUM(duration_sum) / SUM(total_count) ELSE 0 END AS avg_duration, " +
|
||||
"COALESCE(MAX(p99_duration), 0) AS p99_duration, " +
|
||||
"COALESCE(SUM(running_count), 0) AS active_count " +
|
||||
"FROM " + view + " WHERE bucket >= ? AND bucket < ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(intervalSeconds);
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
for (Filter f : filters) {
|
||||
sql += " AND " + f.column() + " = ?";
|
||||
params.add(f.value());
|
||||
}
|
||||
sql += " GROUP BY period, group_key ORDER BY period, group_key";
|
||||
|
||||
Map<String, List<TimeseriesBucket>> grouped = new LinkedHashMap<>();
|
||||
jdbc.query(sql, (rs) -> {
|
||||
String key = rs.getString("group_key");
|
||||
TimeseriesBucket bucket = new TimeseriesBucket(
|
||||
rs.getTimestamp("period").toInstant(),
|
||||
rs.getLong("total_count"), rs.getLong("failed_count"),
|
||||
rs.getLong("avg_duration"), rs.getLong("p99_duration"),
|
||||
rs.getLong("active_count"));
|
||||
grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(bucket);
|
||||
}, params.toArray());
|
||||
|
||||
Map<String, StatsTimeseries> result = new LinkedHashMap<>();
|
||||
grouped.forEach((key, buckets) -> result.put(key, new StatsTimeseries(buckets)));
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── SLA compliance ────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public double slaCompliance(Instant from, Instant to, int thresholdMs,
|
||||
String applicationName, String routeId) {
|
||||
String sql = "SELECT " +
|
||||
"COUNT(*) FILTER (WHERE duration_ms <= ? AND status != 'RUNNING') AS compliant, " +
|
||||
"COUNT(*) FILTER (WHERE status != 'RUNNING') AS total " +
|
||||
"FROM executions WHERE start_time >= ? AND start_time < ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(thresholdMs);
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (applicationName != null) {
|
||||
sql += " AND application_name = ?";
|
||||
params.add(applicationName);
|
||||
}
|
||||
if (routeId != null) {
|
||||
sql += " AND route_id = ?";
|
||||
params.add(routeId);
|
||||
}
|
||||
|
||||
return jdbc.query(sql, (rs, rowNum) -> {
|
||||
long total = rs.getLong("total");
|
||||
if (total == 0) return 1.0;
|
||||
return rs.getLong("compliant") * 100.0 / total;
|
||||
}, params.toArray()).stream().findFirst().orElse(1.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs) {
|
||||
String sql = "SELECT application_name, " +
|
||||
"COUNT(*) FILTER (WHERE duration_ms <= ? AND status != 'RUNNING') AS compliant, " +
|
||||
"COUNT(*) FILTER (WHERE status != 'RUNNING') AS total " +
|
||||
"FROM executions WHERE start_time >= ? AND start_time < ? " +
|
||||
"GROUP BY application_name";
|
||||
|
||||
Map<String, long[]> result = new LinkedHashMap<>();
|
||||
jdbc.query(sql, (rs) -> {
|
||||
result.put(rs.getString("application_name"),
|
||||
new long[]{rs.getLong("compliant"), rs.getLong("total")});
|
||||
}, defaultThresholdMs, Timestamp.from(from), Timestamp.from(to));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, long[]> slaCountsByRoute(Instant from, Instant to,
|
||||
String applicationName, int thresholdMs) {
|
||||
String sql = "SELECT route_id, " +
|
||||
"COUNT(*) FILTER (WHERE duration_ms <= ? AND status != 'RUNNING') AS compliant, " +
|
||||
"COUNT(*) FILTER (WHERE status != 'RUNNING') AS total " +
|
||||
"FROM executions WHERE start_time >= ? AND start_time < ? " +
|
||||
"AND application_name = ? GROUP BY route_id";
|
||||
|
||||
Map<String, long[]> result = new LinkedHashMap<>();
|
||||
jdbc.query(sql, (rs) -> {
|
||||
result.put(rs.getString("route_id"),
|
||||
new long[]{rs.getLong("compliant"), rs.getLong("total")});
|
||||
}, thresholdMs, Timestamp.from(from), Timestamp.from(to), applicationName);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Top errors ────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public List<TopError> topErrors(Instant from, Instant to, String applicationName,
|
||||
String routeId, int limit) {
|
||||
StringBuilder where = new StringBuilder(
|
||||
"status = 'FAILED' AND start_time >= ? AND start_time < ?");
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (applicationName != null) {
|
||||
where.append(" AND application_name = ?");
|
||||
params.add(applicationName);
|
||||
}
|
||||
|
||||
String table;
|
||||
String groupId;
|
||||
if (routeId != null) {
|
||||
// L3: attribute errors to processors
|
||||
table = "processor_executions";
|
||||
groupId = "processor_id";
|
||||
where.append(" AND route_id = ?");
|
||||
params.add(routeId);
|
||||
} else {
|
||||
// L1/L2: attribute errors to routes
|
||||
table = "executions";
|
||||
groupId = "route_id";
|
||||
}
|
||||
|
||||
Instant fiveMinAgo = Instant.now().minus(5, ChronoUnit.MINUTES);
|
||||
Instant tenMinAgo = Instant.now().minus(10, ChronoUnit.MINUTES);
|
||||
|
||||
String sql = "WITH counted AS (" +
|
||||
" SELECT COALESCE(error_type, LEFT(error_message, 200)) AS error_key, " +
|
||||
" " + groupId + " AS group_id, " +
|
||||
" COUNT(*) AS cnt, MAX(start_time) AS last_seen " +
|
||||
" FROM " + table + " WHERE " + where +
|
||||
" GROUP BY error_key, group_id ORDER BY cnt DESC LIMIT ?" +
|
||||
"), velocity AS (" +
|
||||
" SELECT COALESCE(error_type, LEFT(error_message, 200)) AS error_key, " +
|
||||
" COUNT(*) FILTER (WHERE start_time >= ?) AS recent_5m, " +
|
||||
" COUNT(*) FILTER (WHERE start_time >= ? AND start_time < ?) AS prev_5m " +
|
||||
" FROM " + table + " WHERE " + where +
|
||||
" GROUP BY error_key" +
|
||||
") SELECT c.error_key, c.group_id, c.cnt, c.last_seen, " +
|
||||
" COALESCE(v.recent_5m, 0) / 5.0 AS velocity, " +
|
||||
" CASE " +
|
||||
" WHEN COALESCE(v.recent_5m, 0) > COALESCE(v.prev_5m, 0) * 1.2 THEN 'accelerating' " +
|
||||
" WHEN COALESCE(v.recent_5m, 0) < COALESCE(v.prev_5m, 0) * 0.8 THEN 'decelerating' " +
|
||||
" ELSE 'stable' END AS trend " +
|
||||
"FROM counted c LEFT JOIN velocity v ON c.error_key = v.error_key " +
|
||||
"ORDER BY c.cnt DESC";
|
||||
|
||||
// Build full params: counted-where params + limit + velocity timestamps + velocity-where params
|
||||
List<Object> fullParams = new ArrayList<>(params);
|
||||
fullParams.add(limit);
|
||||
fullParams.add(Timestamp.from(fiveMinAgo));
|
||||
fullParams.add(Timestamp.from(tenMinAgo));
|
||||
fullParams.add(Timestamp.from(fiveMinAgo));
|
||||
fullParams.addAll(params); // same where clause for velocity CTE
|
||||
|
||||
return jdbc.query(sql, (rs, rowNum) -> {
|
||||
String errorKey = rs.getString("error_key");
|
||||
String gid = rs.getString("group_id");
|
||||
return new TopError(
|
||||
errorKey,
|
||||
routeId != null ? routeId : gid, // routeId
|
||||
routeId != null ? gid : null, // processorId (only at L3)
|
||||
rs.getLong("cnt"),
|
||||
rs.getDouble("velocity"),
|
||||
rs.getString("trend"),
|
||||
rs.getTimestamp("last_seen").toInstant());
|
||||
}, fullParams.toArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int activeErrorTypes(Instant from, Instant to, String applicationName) {
|
||||
String sql = "SELECT COUNT(DISTINCT COALESCE(error_type, LEFT(error_message, 200))) " +
|
||||
"FROM executions WHERE status = 'FAILED' AND start_time >= ? AND start_time < ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (applicationName != null) {
|
||||
sql += " AND application_name = ?";
|
||||
params.add(applicationName);
|
||||
}
|
||||
|
||||
Integer count = jdbc.queryForObject(sql, Integer.class, params.toArray());
|
||||
return count != null ? count : 0;
|
||||
}
|
||||
|
||||
// ── Punchcard ─────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public List<PunchcardCell> punchcard(Instant from, Instant to, String applicationName) {
|
||||
String view = applicationName != null ? "stats_1m_app" : "stats_1m_all";
|
||||
String sql = "SELECT EXTRACT(DOW FROM bucket) AS weekday, " +
|
||||
"EXTRACT(HOUR FROM bucket) AS hour, " +
|
||||
"COALESCE(SUM(total_count), 0) AS total_count, " +
|
||||
"COALESCE(SUM(failed_count), 0) AS failed_count " +
|
||||
"FROM " + view + " WHERE bucket >= ? AND bucket < ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(Timestamp.from(from));
|
||||
params.add(Timestamp.from(to));
|
||||
if (applicationName != null) {
|
||||
sql += " AND application_name = ?";
|
||||
params.add(applicationName);
|
||||
}
|
||||
sql += " GROUP BY weekday, hour ORDER BY weekday, hour";
|
||||
|
||||
return jdbc.query(sql, (rs, rowNum) -> new PunchcardCell(
|
||||
rs.getInt("weekday"), rs.getInt("hour"),
|
||||
rs.getLong("total_count"), rs.getLong("failed_count")),
|
||||
params.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ public class SpaForwardController {
|
||||
@GetMapping(value = {
|
||||
"/login",
|
||||
"/executions",
|
||||
"/executions/{path:[^\\.]*}",
|
||||
"/executions/**",
|
||||
"/oidc/callback",
|
||||
"/admin/{path:[^\\.]*}"
|
||||
"/admin/**"
|
||||
})
|
||||
public String forward() {
|
||||
return "forward:/index.html";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
-- executions: store raw processor tree for faithful detail response
|
||||
ALTER TABLE executions ADD COLUMN processors_json JSONB;
|
||||
|
||||
-- executions: error categorization + OTel tracing
|
||||
ALTER TABLE executions ADD COLUMN error_type TEXT;
|
||||
ALTER TABLE executions ADD COLUMN error_category TEXT;
|
||||
ALTER TABLE executions ADD COLUMN root_cause_type TEXT;
|
||||
ALTER TABLE executions ADD COLUMN root_cause_message TEXT;
|
||||
ALTER TABLE executions ADD COLUMN trace_id TEXT;
|
||||
ALTER TABLE executions ADD COLUMN span_id TEXT;
|
||||
|
||||
-- processor_executions: error categorization + circuit breaker
|
||||
ALTER TABLE processor_executions ADD COLUMN error_type TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN error_category TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN root_cause_type TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN root_cause_message TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN error_handler_type TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN circuit_breaker_state TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN fallback_triggered BOOLEAN;
|
||||
|
||||
-- Remove erroneous depth columns from V9
|
||||
ALTER TABLE processor_executions DROP COLUMN IF EXISTS split_depth;
|
||||
ALTER TABLE processor_executions DROP COLUMN IF EXISTS loop_depth;
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Flag indicating whether any processor in this execution captured trace data
|
||||
ALTER TABLE executions ADD COLUMN IF NOT EXISTS has_trace_data BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Backfill: set flag for existing executions that have processor trace data
|
||||
UPDATE executions e SET has_trace_data = TRUE
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM processor_executions pe
|
||||
WHERE pe.execution_id = e.execution_id
|
||||
AND (pe.input_body IS NOT NULL OR pe.output_body IS NOT NULL)
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Per-application dashboard settings (SLA thresholds, health dot thresholds)
|
||||
CREATE TABLE app_settings (
|
||||
app_id TEXT PRIMARY KEY,
|
||||
sla_threshold_ms INTEGER NOT NULL DEFAULT 300,
|
||||
health_error_warn DOUBLE PRECISION NOT NULL DEFAULT 1.0,
|
||||
health_error_crit DOUBLE PRECISION NOT NULL DEFAULT 5.0,
|
||||
health_sla_warn DOUBLE PRECISION NOT NULL DEFAULT 99.0,
|
||||
health_sla_crit DOUBLE PRECISION NOT NULL DEFAULT 95.0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Flag indicating whether this execution is a replayed exchange
|
||||
ALTER TABLE executions ADD COLUMN IF NOT EXISTS is_replay BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Backfill: check inputHeaders JSON for X-Cameleer-Replay header
|
||||
UPDATE executions SET is_replay = TRUE
|
||||
WHERE input_headers IS NOT NULL
|
||||
AND input_headers::jsonb ? 'X-Cameleer-Replay';
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS loop_index INTEGER;
|
||||
ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS loop_size INTEGER;
|
||||
ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS split_index INTEGER;
|
||||
ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS split_size INTEGER;
|
||||
ALTER TABLE processor_executions ADD COLUMN IF NOT EXISTS multicast_index INTEGER;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE processor_executions ADD COLUMN resolved_endpoint_uri TEXT;
|
||||
ALTER TABLE processor_executions ADD COLUMN split_depth INTEGER DEFAULT 0;
|
||||
ALTER TABLE processor_executions ADD COLUMN loop_depth INTEGER DEFAULT 0;
|
||||
@@ -39,7 +39,8 @@ class ElkDiagramRendererTest {
|
||||
RouteNode process = new RouteNode("node-2", NodeType.BEAN, "myProcessor");
|
||||
RouteNode to = new RouteNode("node-3", NodeType.TO, "log:output");
|
||||
|
||||
graph.setNodes(List.of(from, process, to));
|
||||
from.setChildren(List.of(process, to));
|
||||
graph.setRoot(from);
|
||||
graph.setEdges(List.of(
|
||||
new RouteEdge("node-1", "node-2", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-2", "node-3", RouteEdge.EdgeType.FLOW)
|
||||
@@ -62,10 +63,10 @@ class ElkDiagramRendererTest {
|
||||
RouteNode otherwise = new RouteNode("node-4", NodeType.EIP_OTHERWISE, "otherwise");
|
||||
RouteNode to = new RouteNode("node-5", NodeType.TO, "log:result");
|
||||
|
||||
// Set children on the choice node
|
||||
// Build tree: from → [choice, to]; choice → [when, otherwise]
|
||||
choice.setChildren(List.of(when, otherwise));
|
||||
|
||||
graph.setNodes(List.of(from, choice, when, otherwise, to));
|
||||
from.setChildren(List.of(choice, to));
|
||||
graph.setRoot(from);
|
||||
graph.setEdges(List.of(
|
||||
new RouteEdge("node-1", "node-2", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-2", "node-3", RouteEdge.EdgeType.FLOW),
|
||||
@@ -172,6 +173,97 @@ class ElkDiagramRendererTest {
|
||||
assertEquals(2, choiceNode.children().size(), "Choice node should have 2 children (when, otherwise)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a DO_TRY graph: from -> doTry(try: [process, log], doFinally: [cleanup], doCatch: [errorLog]) -> to
|
||||
*/
|
||||
private RouteGraph buildDoTryGraph() {
|
||||
RouteGraph graph = new RouteGraph("try-catch-route");
|
||||
graph.setExtractedAt(Instant.now());
|
||||
graph.setVersion(1);
|
||||
|
||||
RouteNode from = new RouteNode("node-1", NodeType.ENDPOINT, "timer:tick");
|
||||
RouteNode doTry = new RouteNode("node-2", NodeType.DO_TRY, "doTry");
|
||||
RouteNode process = new RouteNode("node-3", NodeType.PROCESSOR, "process");
|
||||
RouteNode log1 = new RouteNode("node-4", NodeType.LOG, "log:tryBody");
|
||||
RouteNode doFinally = new RouteNode("node-5", NodeType.DO_FINALLY, "doFinally");
|
||||
RouteNode cleanup = new RouteNode("node-6", NodeType.LOG, "log:cleanup");
|
||||
RouteNode doCatch = new RouteNode("node-7", NodeType.DO_CATCH, "doCatch");
|
||||
RouteNode errorLog = new RouteNode("node-8", NodeType.LOG, "log:error");
|
||||
RouteNode to = new RouteNode("node-9", NodeType.TO, "log:done");
|
||||
|
||||
doFinally.setChildren(List.of(cleanup));
|
||||
doCatch.setChildren(List.of(errorLog));
|
||||
doTry.setChildren(List.of(process, log1, doFinally, doCatch));
|
||||
from.setChildren(List.of(doTry, to));
|
||||
graph.setRoot(from);
|
||||
graph.setEdges(List.of(
|
||||
new RouteEdge("node-1", "node-2", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-2", "node-3", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-3", "node-4", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-2", "node-5", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-5", "node-6", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-2", "node-7", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-7", "node-8", RouteEdge.EdgeType.FLOW),
|
||||
new RouteEdge("node-2", "node-9", RouteEdge.EdgeType.FLOW)
|
||||
));
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
@Test
|
||||
void layoutJson_doTryGraph_sectionsInCorrectOrder() {
|
||||
DiagramLayout layout = renderer.layoutJson(buildDoTryGraph());
|
||||
|
||||
assertNotNull(layout);
|
||||
|
||||
// Find the DO_TRY compound node
|
||||
PositionedNode doTryNode = layout.nodes().stream()
|
||||
.filter(n -> "node-2".equals(n.id()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("DO_TRY node not found"));
|
||||
|
||||
assertNotNull(doTryNode.children(), "DO_TRY should have children");
|
||||
assertFalse(doTryNode.children().isEmpty(), "DO_TRY should have non-empty children");
|
||||
|
||||
// Find sections by ID pattern
|
||||
PositionedNode tryBody = doTryNode.children().stream()
|
||||
.filter(n -> n.id() != null && n.id().contains("._try_body"))
|
||||
.findFirst().orElse(null);
|
||||
PositionedNode finallySection = doTryNode.children().stream()
|
||||
.filter(n -> "node-5".equals(n.id()))
|
||||
.findFirst().orElse(null);
|
||||
PositionedNode catchSection = doTryNode.children().stream()
|
||||
.filter(n -> "node-7".equals(n.id()))
|
||||
.findFirst().orElse(null);
|
||||
|
||||
assertNotNull(tryBody, "Try body wrapper should exist");
|
||||
assertNotNull(finallySection, "doFinally section should exist");
|
||||
assertNotNull(catchSection, "doCatch section should exist");
|
||||
|
||||
// Verify vertical order: tryBody.y < doFinally.y < doCatch.y
|
||||
assertTrue(tryBody.y() < finallySection.y(),
|
||||
"Try body (y=" + tryBody.y() + ") should be above doFinally (y=" + finallySection.y() + ")");
|
||||
assertTrue(finallySection.y() < catchSection.y(),
|
||||
"doFinally (y=" + finallySection.y() + ") should be above doCatch (y=" + catchSection.y() + ")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void layoutJson_doTryGraph_sectionsHaveSameWidth() {
|
||||
DiagramLayout layout = renderer.layoutJson(buildDoTryGraph());
|
||||
|
||||
PositionedNode doTryNode = layout.nodes().stream()
|
||||
.filter(n -> "node-2".equals(n.id()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("DO_TRY node not found"));
|
||||
|
||||
List<PositionedNode> sections = doTryNode.children();
|
||||
double firstWidth = sections.get(0).width();
|
||||
for (PositionedNode section : sections) {
|
||||
assertEquals(firstWidth, section.width(), 0.1,
|
||||
"All sections should have the same width, but " + section.id() + " differs");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void renderSvg_compoundGraph_producesValidSvg() {
|
||||
String svg = renderer.renderSvg(buildCompoundGraph());
|
||||
|
||||
@@ -36,7 +36,7 @@ class OpenSearchIndexIT extends AbstractPostgresIT {
|
||||
"OrderNotFoundException: order-12345 not found", null,
|
||||
List.of(new ProcessorDoc("proc-1", "log", "COMPLETED",
|
||||
null, null, "request body with customer-99", null, null, null, null)),
|
||||
null);
|
||||
null, false, false);
|
||||
|
||||
searchIndex.index(doc);
|
||||
refreshOpenSearchIndices();
|
||||
@@ -62,7 +62,7 @@ class OpenSearchIndexIT extends AbstractPostgresIT {
|
||||
now, now.plusMillis(50), 50L, null, null,
|
||||
List.of(new ProcessorDoc("proc-1", "bean", "COMPLETED",
|
||||
null, null, "UniquePayloadIdentifier12345", null, null, null, null)),
|
||||
null);
|
||||
null, false, false);
|
||||
|
||||
searchIndex.index(doc);
|
||||
refreshOpenSearchIndices();
|
||||
|
||||
@@ -26,7 +26,8 @@ class PostgresExecutionStoreIT extends AbstractPostgresIT {
|
||||
"COMPLETED", "corr-1", "exchange-1",
|
||||
now, now.plusMillis(100), 100L,
|
||||
null, null, null,
|
||||
"REGULAR", null, null, null, null, null);
|
||||
"REGULAR", null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, false, false);
|
||||
|
||||
executionStore.upsert(record);
|
||||
Optional<ExecutionRecord> found = executionStore.findById("exec-1");
|
||||
@@ -43,11 +44,13 @@ class PostgresExecutionStoreIT extends AbstractPostgresIT {
|
||||
ExecutionRecord first = new ExecutionRecord(
|
||||
"exec-dup", "route-a", "agent-1", "app-1",
|
||||
"RUNNING", null, null, now, null, null, null, null, null,
|
||||
null, null, null, null, null, null);
|
||||
null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, false, false);
|
||||
ExecutionRecord second = new ExecutionRecord(
|
||||
"exec-dup", "route-a", "agent-1", "app-1",
|
||||
"COMPLETED", null, null, now, now.plusMillis(200), 200L, null, null, null,
|
||||
"COMPLETE", null, null, null, null, null);
|
||||
"COMPLETE", null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, false, false);
|
||||
|
||||
executionStore.upsert(first);
|
||||
executionStore.upsert(second);
|
||||
@@ -64,18 +67,23 @@ class PostgresExecutionStoreIT extends AbstractPostgresIT {
|
||||
ExecutionRecord exec = new ExecutionRecord(
|
||||
"exec-proc", "route-a", "agent-1", "app-1",
|
||||
"COMPLETED", null, null, now, now.plusMillis(50), 50L, null, null, null,
|
||||
"COMPLETE", null, null, null, null, null);
|
||||
"COMPLETE", null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, false, false);
|
||||
executionStore.upsert(exec);
|
||||
|
||||
List<ProcessorRecord> processors = List.of(
|
||||
new ProcessorRecord("exec-proc", "proc-1", "log",
|
||||
"app-1", "route-a", 0, null, "COMPLETED",
|
||||
now, now.plusMillis(10), 10L, null, null,
|
||||
"input body", "output body", null, null, null),
|
||||
"input body", "output body", null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null),
|
||||
new ProcessorRecord("exec-proc", "proc-2", "to",
|
||||
"app-1", "route-a", 1, "proc-1", "COMPLETED",
|
||||
now.plusMillis(10), now.plusMillis(30), 20L, null, null,
|
||||
null, null, null, null, null)
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null)
|
||||
);
|
||||
executionStore.upsertProcessors("exec-proc", now, "app-1", "route-a", processors);
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class PostgresStatsStoreIT extends AbstractPostgresIT {
|
||||
id, routeId, "agent-1", applicationName, status, null, null,
|
||||
startTime, startTime.plusMillis(durationMs), durationMs,
|
||||
status.equals("FAILED") ? "error" : null, null, null,
|
||||
null, null, null, null, null, null));
|
||||
null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.cameleer3.server.core.admin;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record AppSettings(
|
||||
String appId,
|
||||
int slaThresholdMs,
|
||||
double healthErrorWarn,
|
||||
double healthErrorCrit,
|
||||
double healthSlaWarn,
|
||||
double healthSlaCrit,
|
||||
Instant createdAt,
|
||||
Instant updatedAt) {
|
||||
|
||||
public static AppSettings defaults(String appId) {
|
||||
Instant now = Instant.now();
|
||||
return new AppSettings(appId, 300, 1.0, 5.0, 99.0, 95.0, now, now);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cameleer3.server.core.admin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AppSettingsRepository {
|
||||
Optional<AppSettings> findByAppId(String appId);
|
||||
List<AppSettings> findAll();
|
||||
AppSettings save(AppSettings settings);
|
||||
void delete(String appId);
|
||||
}
|
||||
@@ -8,5 +8,6 @@ public enum CommandType {
|
||||
DEEP_TRACE,
|
||||
REPLAY,
|
||||
SET_TRACED_PROCESSORS,
|
||||
TEST_EXPRESSION
|
||||
TEST_EXPRESSION,
|
||||
ROUTE_CONTROL
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package com.cameleer3.server.core.detail;
|
||||
|
||||
import com.cameleer3.common.model.ProcessorExecution;
|
||||
import com.cameleer3.server.core.storage.ExecutionStore;
|
||||
import com.cameleer3.server.core.storage.ExecutionStore.ProcessorRecord;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class DetailService {
|
||||
|
||||
private static final ObjectMapper JSON = new ObjectMapper();
|
||||
private static final ObjectMapper JSON = new ObjectMapper()
|
||||
.findAndRegisterModules()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
private static final TypeReference<Map<String, String>> STR_MAP = new TypeReference<>() {};
|
||||
private static final TypeReference<List<ProcessorExecution>> PROCESSOR_EXEC_LIST = new TypeReference<>() {};
|
||||
|
||||
private final ExecutionStore executionStore;
|
||||
|
||||
@@ -21,8 +26,14 @@ public class DetailService {
|
||||
public Optional<ExecutionDetail> getDetail(String executionId) {
|
||||
return executionStore.findById(executionId)
|
||||
.map(exec -> {
|
||||
List<ProcessorRecord> processors = executionStore.findProcessors(executionId);
|
||||
List<ProcessorNode> roots = buildTree(processors);
|
||||
// Prefer the raw processor tree (faithful to agent data) over
|
||||
// flat-record reconstruction (which loses iteration context).
|
||||
List<ProcessorNode> processors = parseProcessorsJson(exec.processorsJson());
|
||||
if (processors == null) {
|
||||
// Fallback for executions ingested before processors_json was added
|
||||
List<ProcessorRecord> records = executionStore.findProcessors(executionId);
|
||||
processors = buildTree(records);
|
||||
}
|
||||
return new ExecutionDetail(
|
||||
exec.executionId(), exec.routeId(), exec.agentId(),
|
||||
exec.applicationName(),
|
||||
@@ -30,25 +41,100 @@ public class DetailService {
|
||||
exec.durationMs() != null ? exec.durationMs() : 0L,
|
||||
exec.correlationId(), exec.exchangeId(),
|
||||
exec.errorMessage(), exec.errorStacktrace(),
|
||||
exec.diagramContentHash(), roots,
|
||||
exec.diagramContentHash(), processors,
|
||||
exec.inputBody(), exec.outputBody(),
|
||||
exec.inputHeaders(), exec.outputHeaders(),
|
||||
parseAttributes(exec.attributes())
|
||||
parseAttributes(exec.attributes()),
|
||||
exec.errorType(), exec.errorCategory(),
|
||||
exec.rootCauseType(), exec.rootCauseMessage(),
|
||||
exec.traceId(), exec.spanId()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public Optional<Map<String, String>> getProcessorSnapshot(String executionId, String processorId) {
|
||||
return executionStore.findProcessorById(executionId, processorId)
|
||||
.map(p -> {
|
||||
Map<String, String> snapshot = new LinkedHashMap<>();
|
||||
if (p.inputBody() != null) snapshot.put("inputBody", p.inputBody());
|
||||
if (p.outputBody() != null) snapshot.put("outputBody", p.outputBody());
|
||||
if (p.inputHeaders() != null) snapshot.put("inputHeaders", p.inputHeaders());
|
||||
if (p.outputHeaders() != null) snapshot.put("outputHeaders", p.outputHeaders());
|
||||
return snapshot;
|
||||
});
|
||||
}
|
||||
|
||||
/** Parse the raw processor tree JSON stored alongside the execution. */
|
||||
private List<ProcessorNode> parseProcessorsJson(String json) {
|
||||
if (json == null || json.isBlank()) return null;
|
||||
try {
|
||||
List<ProcessorExecution> executions = JSON.readValue(json, PROCESSOR_EXEC_LIST);
|
||||
return convertProcessors(executions);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert agent ProcessorExecution tree to detail ProcessorNode tree. */
|
||||
private List<ProcessorNode> convertProcessors(List<ProcessorExecution> executions) {
|
||||
if (executions == null) return List.of();
|
||||
List<ProcessorNode> result = new ArrayList<>();
|
||||
for (ProcessorExecution p : executions) {
|
||||
boolean hasTrace = p.getInputBody() != null || p.getOutputBody() != null
|
||||
|| p.getInputHeaders() != null || p.getOutputHeaders() != null;
|
||||
ProcessorNode node = new ProcessorNode(
|
||||
p.getProcessorId(), p.getProcessorType(),
|
||||
p.getStatus() != null ? p.getStatus().name() : null,
|
||||
p.getStartTime(), p.getEndTime(),
|
||||
p.getDurationMs(),
|
||||
p.getErrorMessage(), p.getErrorStackTrace(),
|
||||
p.getAttributes() != null ? new LinkedHashMap<>(p.getAttributes()) : null,
|
||||
p.getLoopIndex(), p.getLoopSize(),
|
||||
p.getSplitIndex(), p.getSplitSize(),
|
||||
p.getMulticastIndex(),
|
||||
p.getResolvedEndpointUri(),
|
||||
p.getErrorType(), p.getErrorCategory(),
|
||||
p.getRootCauseType(), p.getRootCauseMessage(),
|
||||
p.getErrorHandlerType(), p.getCircuitBreakerState(),
|
||||
p.getFallbackTriggered(),
|
||||
p.getFilterMatched(), p.getDuplicateMessage(),
|
||||
hasTrace
|
||||
);
|
||||
for (ProcessorNode child : convertProcessors(p.getChildren())) {
|
||||
node.addChild(child);
|
||||
}
|
||||
result.add(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback: reconstruct processor tree from flat records.
|
||||
* Note: this loses iteration context for processors with the same ID across iterations.
|
||||
*/
|
||||
List<ProcessorNode> buildTree(List<ProcessorRecord> processors) {
|
||||
if (processors.isEmpty()) return List.of();
|
||||
|
||||
Map<String, ProcessorNode> nodeMap = new LinkedHashMap<>();
|
||||
for (ProcessorRecord p : processors) {
|
||||
boolean hasTrace = p.inputBody() != null || p.outputBody() != null
|
||||
|| p.inputHeaders() != null || p.outputHeaders() != null;
|
||||
nodeMap.put(p.processorId(), new ProcessorNode(
|
||||
p.processorId(), p.processorType(), p.status(),
|
||||
p.startTime(), p.endTime(),
|
||||
p.durationMs() != null ? p.durationMs() : 0L,
|
||||
p.errorMessage(), p.errorStacktrace(),
|
||||
parseAttributes(p.attributes())
|
||||
parseAttributes(p.attributes()),
|
||||
p.loopIndex(), p.loopSize(),
|
||||
p.splitIndex(), p.splitSize(),
|
||||
p.multicastIndex(),
|
||||
p.resolvedEndpointUri(),
|
||||
p.errorType(), p.errorCategory(),
|
||||
p.rootCauseType(), p.rootCauseMessage(),
|
||||
p.errorHandlerType(), p.circuitBreakerState(),
|
||||
p.fallbackTriggered(),
|
||||
null, null, // filterMatched, duplicateMessage (not in flat DB records)
|
||||
hasTrace
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,12 @@ public record ExecutionDetail(
|
||||
String outputBody,
|
||||
String inputHeaders,
|
||||
String outputHeaders,
|
||||
Map<String, String> attributes
|
||||
Map<String, String> attributes,
|
||||
String errorType,
|
||||
String errorCategory,
|
||||
String rootCauseType,
|
||||
String rootCauseMessage,
|
||||
String traceId,
|
||||
String spanId
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -22,12 +22,38 @@ public final class ProcessorNode {
|
||||
private final String errorMessage;
|
||||
private final String errorStackTrace;
|
||||
private final Map<String, String> attributes;
|
||||
private final Integer loopIndex;
|
||||
private final Integer loopSize;
|
||||
private final Integer splitIndex;
|
||||
private final Integer splitSize;
|
||||
private final Integer multicastIndex;
|
||||
private final String resolvedEndpointUri;
|
||||
private final String errorType;
|
||||
private final String errorCategory;
|
||||
private final String rootCauseType;
|
||||
private final String rootCauseMessage;
|
||||
private final String errorHandlerType;
|
||||
private final String circuitBreakerState;
|
||||
private final Boolean fallbackTriggered;
|
||||
private final Boolean filterMatched;
|
||||
private final Boolean duplicateMessage;
|
||||
private final boolean hasTraceData;
|
||||
private final List<ProcessorNode> children;
|
||||
|
||||
public ProcessorNode(String processorId, String processorType, String status,
|
||||
Instant startTime, Instant endTime, long durationMs,
|
||||
String errorMessage, String errorStackTrace,
|
||||
Map<String, String> attributes) {
|
||||
Map<String, String> attributes,
|
||||
Integer loopIndex, Integer loopSize,
|
||||
Integer splitIndex, Integer splitSize,
|
||||
Integer multicastIndex,
|
||||
String resolvedEndpointUri,
|
||||
String errorType, String errorCategory,
|
||||
String rootCauseType, String rootCauseMessage,
|
||||
String errorHandlerType, String circuitBreakerState,
|
||||
Boolean fallbackTriggered,
|
||||
Boolean filterMatched, Boolean duplicateMessage,
|
||||
boolean hasTraceData) {
|
||||
this.processorId = processorId;
|
||||
this.processorType = processorType;
|
||||
this.status = status;
|
||||
@@ -37,6 +63,22 @@ public final class ProcessorNode {
|
||||
this.errorMessage = errorMessage;
|
||||
this.errorStackTrace = errorStackTrace;
|
||||
this.attributes = attributes;
|
||||
this.loopIndex = loopIndex;
|
||||
this.loopSize = loopSize;
|
||||
this.splitIndex = splitIndex;
|
||||
this.splitSize = splitSize;
|
||||
this.multicastIndex = multicastIndex;
|
||||
this.resolvedEndpointUri = resolvedEndpointUri;
|
||||
this.errorType = errorType;
|
||||
this.errorCategory = errorCategory;
|
||||
this.rootCauseType = rootCauseType;
|
||||
this.rootCauseMessage = rootCauseMessage;
|
||||
this.errorHandlerType = errorHandlerType;
|
||||
this.circuitBreakerState = circuitBreakerState;
|
||||
this.fallbackTriggered = fallbackTriggered;
|
||||
this.filterMatched = filterMatched;
|
||||
this.duplicateMessage = duplicateMessage;
|
||||
this.hasTraceData = hasTraceData;
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -53,5 +95,21 @@ public final class ProcessorNode {
|
||||
public String getErrorMessage() { return errorMessage; }
|
||||
public String getErrorStackTrace() { return errorStackTrace; }
|
||||
public Map<String, String> getAttributes() { return attributes; }
|
||||
public Integer getLoopIndex() { return loopIndex; }
|
||||
public Integer getLoopSize() { return loopSize; }
|
||||
public Integer getSplitIndex() { return splitIndex; }
|
||||
public Integer getSplitSize() { return splitSize; }
|
||||
public Integer getMulticastIndex() { return multicastIndex; }
|
||||
public String getResolvedEndpointUri() { return resolvedEndpointUri; }
|
||||
public String getErrorType() { return errorType; }
|
||||
public String getErrorCategory() { return errorCategory; }
|
||||
public String getRootCauseType() { return rootCauseType; }
|
||||
public String getRootCauseMessage() { return rootCauseMessage; }
|
||||
public String getErrorHandlerType() { return errorHandlerType; }
|
||||
public String getCircuitBreakerState() { return circuitBreakerState; }
|
||||
public Boolean getFallbackTriggered() { return fallbackTriggered; }
|
||||
public Boolean getFilterMatched() { return filterMatched; }
|
||||
public Boolean getDuplicateMessage() { return duplicateMessage; }
|
||||
public boolean isHasTraceData() { return hasTraceData; }
|
||||
public List<ProcessorNode> getChildren() { return List.copyOf(children); }
|
||||
}
|
||||
|
||||
@@ -8,14 +8,15 @@ import java.util.List;
|
||||
* For compound nodes (CHOICE, SPLIT, TRY_CATCH, etc.), {@code children}
|
||||
* contains the nested child nodes rendered inside the parent bounds.
|
||||
*
|
||||
* @param id node identifier (matches RouteNode.id)
|
||||
* @param label display label
|
||||
* @param type NodeType name (e.g., "ENDPOINT", "PROCESSOR")
|
||||
* @param x horizontal position
|
||||
* @param y vertical position
|
||||
* @param width node width
|
||||
* @param height node height
|
||||
* @param children nested child nodes for compound/swimlane groups
|
||||
* @param id node identifier (matches RouteNode.id)
|
||||
* @param label display label
|
||||
* @param type NodeType name (e.g., "ENDPOINT", "PROCESSOR")
|
||||
* @param x horizontal position
|
||||
* @param y vertical position
|
||||
* @param width node width
|
||||
* @param height node height
|
||||
* @param children nested child nodes for compound/swimlane groups
|
||||
* @param endpointUri the Camel endpoint URI (e.g., "direct:processOrder"), null for non-endpoint nodes
|
||||
*/
|
||||
public record PositionedNode(
|
||||
String id,
|
||||
@@ -25,6 +26,7 @@ public record PositionedNode(
|
||||
double y,
|
||||
double width,
|
||||
double height,
|
||||
List<PositionedNode> children
|
||||
List<PositionedNode> children,
|
||||
String endpointUri
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class SearchIndexer implements SearchIndexerStats {
|
||||
exec.status(), exec.correlationId(), exec.exchangeId(),
|
||||
exec.startTime(), exec.endTime(), exec.durationMs(),
|
||||
exec.errorMessage(), exec.errorStacktrace(), processorDocs,
|
||||
exec.attributes()));
|
||||
exec.attributes(), exec.hasTraceData(), exec.isReplay()));
|
||||
|
||||
indexedCount.incrementAndGet();
|
||||
lastIndexedAt = Instant.now();
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cameleer3.server.core.ingestion;
|
||||
import com.cameleer3.common.model.ExchangeSnapshot;
|
||||
import com.cameleer3.common.model.ProcessorExecution;
|
||||
import com.cameleer3.common.model.RouteExecution;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.cameleer3.server.core.indexing.ExecutionUpdatedEvent;
|
||||
import com.cameleer3.server.core.storage.DiagramStore;
|
||||
import com.cameleer3.server.core.storage.ExecutionStore;
|
||||
@@ -19,7 +20,9 @@ import java.util.function.Consumer;
|
||||
|
||||
public class IngestionService {
|
||||
|
||||
private static final ObjectMapper JSON = new ObjectMapper();
|
||||
private static final ObjectMapper JSON = new ObjectMapper()
|
||||
.findAndRegisterModules()
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
private final ExecutionStore executionStore;
|
||||
private final DiagramStore diagramStore;
|
||||
@@ -97,6 +100,14 @@ public class IngestionService {
|
||||
outputHeaders = toJson(outputSnapshot.getHeaders());
|
||||
}
|
||||
|
||||
boolean hasTraceData = hasAnyTraceData(exec.getProcessors());
|
||||
|
||||
boolean isReplay = exec.getReplayExchangeId() != null;
|
||||
if (!isReplay && inputSnapshot != null && inputSnapshot.getHeaders() != null) {
|
||||
isReplay = "true".equalsIgnoreCase(
|
||||
String.valueOf(inputSnapshot.getHeaders().get("X-Cameleer-Replay")));
|
||||
}
|
||||
|
||||
return new ExecutionRecord(
|
||||
exec.getExchangeId(), exec.getRouteId(), agentId, applicationName,
|
||||
exec.getStatus() != null ? exec.getStatus().name() : "RUNNING",
|
||||
@@ -107,10 +118,25 @@ public class IngestionService {
|
||||
diagramHash,
|
||||
exec.getEngineLevel(),
|
||||
inputBody, outputBody, inputHeaders, outputHeaders,
|
||||
toJson(exec.getAttributes())
|
||||
toJson(exec.getAttributes()),
|
||||
exec.getErrorType(), exec.getErrorCategory(),
|
||||
exec.getRootCauseType(), exec.getRootCauseMessage(),
|
||||
exec.getTraceId(), exec.getSpanId(),
|
||||
toJsonObject(exec.getProcessors()),
|
||||
hasTraceData,
|
||||
isReplay
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean hasAnyTraceData(List<ProcessorExecution> processors) {
|
||||
if (processors == null) return false;
|
||||
for (ProcessorExecution p : processors) {
|
||||
if (p.getInputBody() != null || p.getOutputBody() != null) return true;
|
||||
if (hasAnyTraceData(p.getChildren())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ProcessorRecord> flattenProcessors(
|
||||
List<ProcessorExecution> processors, String executionId,
|
||||
java.time.Instant execStartTime, String applicationName, String routeId,
|
||||
@@ -128,7 +154,15 @@ public class IngestionService {
|
||||
p.getErrorMessage(), p.getErrorStackTrace(),
|
||||
truncateBody(p.getInputBody()), truncateBody(p.getOutputBody()),
|
||||
toJson(p.getInputHeaders()), toJson(p.getOutputHeaders()),
|
||||
toJson(p.getAttributes())
|
||||
toJson(p.getAttributes()),
|
||||
p.getLoopIndex(), p.getLoopSize(),
|
||||
p.getSplitIndex(), p.getSplitSize(),
|
||||
p.getMulticastIndex(),
|
||||
p.getResolvedEndpointUri(),
|
||||
p.getErrorType(), p.getErrorCategory(),
|
||||
p.getRootCauseType(), p.getRootCauseMessage(),
|
||||
p.getErrorHandlerType(), p.getCircuitBreakerState(),
|
||||
p.getFallbackTriggered()
|
||||
));
|
||||
if (p.getChildren() != null) {
|
||||
flat.addAll(flattenProcessors(
|
||||
@@ -153,4 +187,13 @@ public class IngestionService {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
private static String toJsonObject(Object obj) {
|
||||
if (obj == null) return null;
|
||||
try {
|
||||
return JSON.writeValueAsString(obj);
|
||||
} catch (JsonProcessingException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.cameleer3.server.core.ingestion;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
@@ -16,6 +19,8 @@ import java.util.concurrent.BlockingQueue;
|
||||
*/
|
||||
public class WriteBuffer<T> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WriteBuffer.class);
|
||||
|
||||
private final BlockingQueue<T> queue;
|
||||
private final int capacity;
|
||||
|
||||
@@ -45,7 +50,10 @@ public class WriteBuffer<T> {
|
||||
return false;
|
||||
}
|
||||
for (T item : items) {
|
||||
queue.offer(item);
|
||||
if (!queue.offer(item)) {
|
||||
log.warn("WriteBuffer offer rejected despite capacity check — possible concurrent modification");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -14,4 +14,23 @@ public record ExecutionStats(
|
||||
long prevTotalCount,
|
||||
long prevFailedCount,
|
||||
long prevAvgDurationMs,
|
||||
long prevP99LatencyMs) {}
|
||||
long prevP99LatencyMs,
|
||||
double slaCompliance) {
|
||||
|
||||
/** Constructor without SLA compliance (backward-compatible, sets to -1). */
|
||||
public ExecutionStats(long totalCount, long failedCount, long avgDurationMs,
|
||||
long p99LatencyMs, long activeCount, long totalToday,
|
||||
long prevTotalCount, long prevFailedCount,
|
||||
long prevAvgDurationMs, long prevP99LatencyMs) {
|
||||
this(totalCount, failedCount, avgDurationMs, p99LatencyMs, activeCount,
|
||||
totalToday, prevTotalCount, prevFailedCount, prevAvgDurationMs,
|
||||
prevP99LatencyMs, -1.0);
|
||||
}
|
||||
|
||||
/** Return a copy with the given SLA compliance value. */
|
||||
public ExecutionStats withSlaCompliance(double slaCompliance) {
|
||||
return new ExecutionStats(totalCount, failedCount, avgDurationMs, p99LatencyMs,
|
||||
activeCount, totalToday, prevTotalCount, prevFailedCount,
|
||||
prevAvgDurationMs, prevP99LatencyMs, slaCompliance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public record ExecutionSummary(
|
||||
String errorMessage,
|
||||
String diagramContentHash,
|
||||
String highlight,
|
||||
Map<String, String> attributes
|
||||
Map<String, String> attributes,
|
||||
boolean hasTraceData,
|
||||
boolean isReplay
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.cameleer3.server.core.storage.StatsStore;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SearchService {
|
||||
|
||||
@@ -48,4 +49,42 @@ public class SearchService {
|
||||
String routeId, List<String> agentIds) {
|
||||
return statsStore.timeseriesForRoute(from, to, bucketCount, routeId, agentIds);
|
||||
}
|
||||
|
||||
// ── Dashboard-specific queries ────────────────────────────────────────
|
||||
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount) {
|
||||
return statsStore.timeseriesGroupedByApp(from, to, bucketCount);
|
||||
}
|
||||
|
||||
public Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to,
|
||||
int bucketCount, String applicationName) {
|
||||
return statsStore.timeseriesGroupedByRoute(from, to, bucketCount, applicationName);
|
||||
}
|
||||
|
||||
public double slaCompliance(Instant from, Instant to, int thresholdMs,
|
||||
String applicationName, String routeId) {
|
||||
return statsStore.slaCompliance(from, to, thresholdMs, applicationName, routeId);
|
||||
}
|
||||
|
||||
public Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs) {
|
||||
return statsStore.slaCountsByApp(from, to, defaultThresholdMs);
|
||||
}
|
||||
|
||||
public Map<String, long[]> slaCountsByRoute(Instant from, Instant to,
|
||||
String applicationName, int thresholdMs) {
|
||||
return statsStore.slaCountsByRoute(from, to, applicationName, thresholdMs);
|
||||
}
|
||||
|
||||
public List<TopError> topErrors(Instant from, Instant to, String applicationName,
|
||||
String routeId, int limit) {
|
||||
return statsStore.topErrors(from, to, applicationName, routeId, limit);
|
||||
}
|
||||
|
||||
public int activeErrorTypes(Instant from, Instant to, String applicationName) {
|
||||
return statsStore.activeErrorTypes(from, to, applicationName);
|
||||
}
|
||||
|
||||
public List<StatsStore.PunchcardCell> punchcard(Instant from, Instant to, String applicationName) {
|
||||
return statsStore.punchcard(from, to, applicationName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cameleer3.server.core.search;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record TopError(
|
||||
String errorType,
|
||||
String routeId,
|
||||
String processorId,
|
||||
long count,
|
||||
double velocity,
|
||||
String trend,
|
||||
Instant lastSeen) {}
|
||||
@@ -16,6 +16,8 @@ public interface ExecutionStore {
|
||||
|
||||
List<ProcessorRecord> findProcessors(String executionId);
|
||||
|
||||
Optional<ProcessorRecord> findProcessorById(String executionId, String processorId);
|
||||
|
||||
record ExecutionRecord(
|
||||
String executionId, String routeId, String agentId, String applicationName,
|
||||
String status, String correlationId, String exchangeId,
|
||||
@@ -23,7 +25,13 @@ public interface ExecutionStore {
|
||||
String errorMessage, String errorStacktrace, String diagramContentHash,
|
||||
String engineLevel,
|
||||
String inputBody, String outputBody, String inputHeaders, String outputHeaders,
|
||||
String attributes
|
||||
String attributes,
|
||||
String errorType, String errorCategory,
|
||||
String rootCauseType, String rootCauseMessage,
|
||||
String traceId, String spanId,
|
||||
String processorsJson,
|
||||
boolean hasTraceData,
|
||||
boolean isReplay
|
||||
) {}
|
||||
|
||||
record ProcessorRecord(
|
||||
@@ -33,6 +41,14 @@ public interface ExecutionStore {
|
||||
Instant startTime, Instant endTime, Long durationMs,
|
||||
String errorMessage, String errorStacktrace,
|
||||
String inputBody, String outputBody, String inputHeaders, String outputHeaders,
|
||||
String attributes
|
||||
String attributes,
|
||||
Integer loopIndex, Integer loopSize,
|
||||
Integer splitIndex, Integer splitSize,
|
||||
Integer multicastIndex,
|
||||
String resolvedEndpointUri,
|
||||
String errorType, String errorCategory,
|
||||
String rootCauseType, String rootCauseMessage,
|
||||
String errorHandlerType, String circuitBreakerState,
|
||||
Boolean fallbackTriggered
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.cameleer3.server.core.storage;
|
||||
|
||||
import com.cameleer3.server.core.search.ExecutionStats;
|
||||
import com.cameleer3.server.core.search.StatsTimeseries;
|
||||
import com.cameleer3.server.core.search.TopError;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface StatsStore {
|
||||
|
||||
@@ -33,4 +35,34 @@ public interface StatsStore {
|
||||
// Per-processor timeseries
|
||||
StatsTimeseries timeseriesForProcessor(Instant from, Instant to, int bucketCount,
|
||||
String routeId, String processorType);
|
||||
|
||||
// Grouped timeseries by application (for L1 dashboard charts)
|
||||
Map<String, StatsTimeseries> timeseriesGroupedByApp(Instant from, Instant to, int bucketCount);
|
||||
|
||||
// Grouped timeseries by route within an application (for L2 dashboard charts)
|
||||
Map<String, StatsTimeseries> timeseriesGroupedByRoute(Instant from, Instant to, int bucketCount,
|
||||
String applicationName);
|
||||
|
||||
// SLA compliance: % of completed exchanges with duration <= thresholdMs
|
||||
double slaCompliance(Instant from, Instant to, int thresholdMs,
|
||||
String applicationName, String routeId);
|
||||
|
||||
// Batch SLA counts by app: {appId -> [compliant, total]}
|
||||
Map<String, long[]> slaCountsByApp(Instant from, Instant to, int defaultThresholdMs);
|
||||
|
||||
// Batch SLA counts by route within an app: {routeId -> [compliant, total]}
|
||||
Map<String, long[]> slaCountsByRoute(Instant from, Instant to, String applicationName,
|
||||
int thresholdMs);
|
||||
|
||||
// Top N errors with velocity trend
|
||||
List<TopError> topErrors(Instant from, Instant to, String applicationName,
|
||||
String routeId, int limit);
|
||||
|
||||
// Count of distinct error types in window
|
||||
int activeErrorTypes(Instant from, Instant to, String applicationName);
|
||||
|
||||
// Punchcard: aggregate by weekday (0=Sun..6=Sat) x hour (0-23) over last 7 days
|
||||
List<PunchcardCell> punchcard(Instant from, Instant to, String applicationName);
|
||||
|
||||
record PunchcardCell(int weekday, int hour, long totalCount, long failedCount) {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ public record ExecutionDocument(
|
||||
Instant startTime, Instant endTime, Long durationMs,
|
||||
String errorMessage, String errorStacktrace,
|
||||
List<ProcessorDoc> processors,
|
||||
String attributes
|
||||
String attributes,
|
||||
boolean hasTraceData,
|
||||
boolean isReplay
|
||||
) {
|
||||
public record ProcessorDoc(
|
||||
String processorId, String processorType, String status,
|
||||
|
||||
@@ -27,7 +27,9 @@ class TreeReconstructionTest {
|
||||
"exec-1", id, type,
|
||||
"default", "route1", depth, parentId,
|
||||
status, NOW, NOW, 10L,
|
||||
null, null, null, null, null, null, null
|
||||
null, null, null, null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
402
ci-log.txt
Normal file
@@ -0,0 +1,402 @@
|
||||
2026-03-25T12:05:26.5603617Z my-docker-runner(version:v0.3.0) received task 1104 of job build, be triggered by event: push
|
||||
2026-03-25T12:05:26.5608954Z workflow prepared
|
||||
2026-03-25T12:05:26.5610069Z evaluating expression 'github.event_name != 'delete''
|
||||
2026-03-25T12:05:26.5611215Z expression 'github.event_name != 'delete'' evaluated to 'true'
|
||||
2026-03-25T12:05:26.5611555Z 🚀 Start image=maven:3.9-eclipse-temurin-17
|
||||
2026-03-25T12:05:26.5687706Z 🐳 docker pull image=maven:3.9-eclipse-temurin-17 platform= username= forcePull=true
|
||||
2026-03-25T12:05:26.5688071Z 🐳 docker pull maven:3.9-eclipse-temurin-17
|
||||
2026-03-25T12:05:26.5688457Z pulling image 'docker.io/library/maven:3.9-eclipse-temurin-17' ()
|
||||
2026-03-25T12:05:27.4255898Z Pulling from library/maven :: 3.9-eclipse-temurin-17
|
||||
2026-03-25T12:05:27.4496622Z Digest: sha256:39a5260d49fe20e5f407bf63f63a267d9870965bcd1d114e52e1e50ba1c55a32 ::
|
||||
2026-03-25T12:05:27.4497205Z Status: Image is up to date for maven:3.9-eclipse-temurin-17 ::
|
||||
2026-03-25T12:05:27.4684745Z 🐳 docker create image=maven:3.9-eclipse-temurin-17 platform= entrypoint=["/bin/sleep" "10800"] cmd=[] network="gitea_gitea_default"
|
||||
2026-03-25T12:05:27.4853152Z Custom container.Config from options ==> &{Hostname: Domainname: User: AttachStdin:false AttachStdout:true AttachStderr:true ExposedPorts:map[] Tty:false OpenStdin:false StdinOnce:false Env:[] Cmd:[] Healthcheck:<nil> ArgsEscaped:false Image: Volumes:map[] WorkingDir: Entrypoint:[] NetworkDisabled:false MacAddress: OnBuild:[] Labels:map[] StopSignal: StopTimeout:<nil> Shell:[]}
|
||||
2026-03-25T12:05:27.4854116Z Merged container.Config ==> &{Hostname: Domainname: User: AttachStdin:false AttachStdout:true AttachStderr:true ExposedPorts:map[] Tty:false OpenStdin:false StdinOnce:false Env:[RUNNER_TOOL_CACHE=/opt/hostedtoolcache RUNNER_OS=Linux RUNNER_ARCH=ARM64 RUNNER_TEMP=/tmp LANG=C.UTF-8] Cmd:[] Healthcheck:<nil> ArgsEscaped:false Image:maven:3.9-eclipse-temurin-17 Volumes:map[] WorkingDir:/workspace/***/***3-server Entrypoint:[/bin/sleep 10800] NetworkDisabled:false MacAddress: OnBuild:[] Labels:map[] StopSignal: StopTimeout:<nil> Shell:[]}
|
||||
2026-03-25T12:05:27.4855205Z Custom container.HostConfig from options ==> &{Binds:[] ContainerIDFile: LogConfig:{Type: Config:map[]} NetworkMode:gitea_gitea_default PortBindings:map[] RestartPolicy:{Name:no MaximumRetryCount:0} AutoRemove:false VolumeDriver: VolumesFrom:[] ConsoleSize:[0 0] Annotations:map[] CapAdd:[] CapDrop:[] CgroupnsMode: DNS:[] DNSOptions:[] DNSSearch:[] ExtraHosts:[] GroupAdd:[] IpcMode: Cgroup: Links:[] OomScoreAdj:0 PidMode: Privileged:false PublishAllPorts:false ReadonlyRootfs:false SecurityOpt:[] StorageOpt:map[] Tmpfs:map[] UTSMode: UsernsMode: ShmSize:0 Sysctls:map[] Runtime: Isolation: Resources:{CPUShares:0 Memory:0 NanoCPUs:0 CgroupParent: BlkioWeight:0 BlkioWeightDevice:[] BlkioDeviceReadBps:[] BlkioDeviceWriteBps:[] BlkioDeviceReadIOps:[] BlkioDeviceWriteIOps:[] CPUPeriod:0 CPUQuota:0 CPURealtimePeriod:0 CPURealtimeRuntime:0 CpusetCpus: CpusetMems: Devices:[] DeviceCgroupRules:[] DeviceRequests:[] KernelMemory:0 KernelMemoryTCP:0 MemoryReservation:0 MemorySwap:0 MemorySwappiness:0x228ace413ac8 OomKillDisable:0x228ace4139c3 PidsLimit:0x228ace413b28 Ulimits:[] CPUCount:0 CPUPercent:0 IOMaximumIOps:0 IOMaximumBandwidth:0} Mounts:[] MaskedPaths:[] ReadonlyPaths:[] Init:<nil>}
|
||||
2026-03-25T12:05:27.4856294Z --network and --net in the options will be ignored.
|
||||
2026-03-25T12:05:27.4856928Z Merged container.HostConfig ==> &{Binds:[/var/run/docker.sock:/var/run/docker.sock] ContainerIDFile: LogConfig:{Type: Config:map[]} NetworkMode:gitea_gitea_default PortBindings:map[] RestartPolicy:{Name:no MaximumRetryCount:0} AutoRemove:true VolumeDriver: VolumesFrom:[] ConsoleSize:[0 0] Annotations:map[] CapAdd:[] CapDrop:[] CgroupnsMode: DNS:[] DNSOptions:[] DNSSearch:[] ExtraHosts:[] GroupAdd:[] IpcMode: Cgroup: Links:[] OomScoreAdj:0 PidMode: Privileged:false PublishAllPorts:false ReadonlyRootfs:false SecurityOpt:[] StorageOpt:map[] Tmpfs:map[] UTSMode: UsernsMode: ShmSize:0 Sysctls:map[] Runtime: Isolation: Resources:{CPUShares:0 Memory:0 NanoCPUs:0 CgroupParent: BlkioWeight:0 BlkioWeightDevice:[] BlkioDeviceReadBps:[] BlkioDeviceWriteBps:[] BlkioDeviceReadIOps:[] BlkioDeviceWriteIOps:[] CPUPeriod:0 CPUQuota:0 CPURealtimePeriod:0 CPURealtimeRuntime:0 CpusetCpus: CpusetMems: Devices:[] DeviceCgroupRules:[] DeviceRequests:[] KernelMemory:0 KernelMemoryTCP:0 MemoryReservation:0 MemorySwap:0 MemorySwappiness:0x228ace413ac8 OomKillDisable:0x228ace4139c3 PidsLimit:0x228ace413b28 Ulimits:[] CPUCount:0 CPUPercent:0 IOMaximumIOps:0 IOMaximumBandwidth:0} Mounts:[{Type:volume Source:act-toolcache Target:/opt/hostedtoolcache ReadOnly:false Consistency: BindOptions:<nil> VolumeOptions:<nil> TmpfsOptions:<nil> ClusterOptions:<nil>} {Type:volume Source:GITEA-ACTIONS-TASK-1104_WORKFLOW-CI_JOB-build-env Target:/var/run/act ReadOnly:false Consistency: BindOptions:<nil> VolumeOptions:<nil> TmpfsOptions:<nil> ClusterOptions:<nil>} {Type:volume Source:GITEA-ACTIONS-TASK-1104_WORKFLOW-CI_JOB-build Target:/workspace/***/***3-server ReadOnly:false Consistency: BindOptions:<nil> VolumeOptions:<nil> TmpfsOptions:<nil> ClusterOptions:<nil>}] MaskedPaths:[] ReadonlyPaths:[] Init:<nil>}
|
||||
2026-03-25T12:05:27.5978022Z Created container name=GITEA-ACTIONS-TASK-1104_WORKFLOW-CI_JOB-build id=7218bfea7789106d54e48224d5541c49969b5cca00766ae2ffdcbc415f9c35ae from image maven:3.9-eclipse-temurin-17 (platform: )
|
||||
2026-03-25T12:05:27.5978558Z ENV ==> [RUNNER_TOOL_CACHE=/opt/hostedtoolcache RUNNER_OS=Linux RUNNER_ARCH=ARM64 RUNNER_TEMP=/tmp LANG=C.UTF-8]
|
||||
2026-03-25T12:05:27.5978778Z 🐳 docker run image=maven:3.9-eclipse-temurin-17 platform= entrypoint=["/bin/sleep" "10800"] cmd=[] network="gitea_gitea_default"
|
||||
2026-03-25T12:05:27.5978979Z Starting container: 7218bfea7789106d54e48224d5541c49969b5cca00766ae2ffdcbc415f9c35ae
|
||||
2026-03-25T12:05:27.8074630Z Started container: 7218bfea7789106d54e48224d5541c49969b5cca00766ae2ffdcbc415f9c35ae
|
||||
2026-03-25T12:05:27.9290310Z Writing entry to tarball workflow/event.json len:5028
|
||||
2026-03-25T12:05:27.9290900Z Writing entry to tarball workflow/envs.txt len:0
|
||||
2026-03-25T12:05:27.9291139Z Extracting content to '/var/run/act/'
|
||||
2026-03-25T12:05:27.9477718Z ☁ git clone 'https://github.com/actions/checkout' # ref=v4
|
||||
2026-03-25T12:05:27.9478182Z cloning https://github.com/actions/checkout to /root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab
|
||||
2026-03-25T12:05:28.2648306Z Non-terminating error while running 'git clone': some refs were not updated
|
||||
2026-03-25T12:05:28.2717948Z ☁ git clone 'https://github.com/actions/cache' # ref=v4
|
||||
2026-03-25T12:05:28.2718527Z cloning https://github.com/actions/cache to /root/.cache/act/6b4e4eb40e21c1bd02cb00a273f4d79af7c42205c1390e4e65c594ecd7a3696e
|
||||
2026-03-25T12:05:29.0246163Z Non-terminating error while running 'git clone': some refs were not updated
|
||||
2026-03-25T12:05:29.0434354Z evaluating expression ''
|
||||
2026-03-25T12:05:29.0435137Z expression '' evaluated to 'true'
|
||||
2026-03-25T12:05:29.0435325Z ⭐ Run Main Install Node.js 22
|
||||
2026-03-25T12:05:29.0435581Z Writing entry to tarball workflow/outputcmd.txt len:0
|
||||
2026-03-25T12:05:29.0435798Z Writing entry to tarball workflow/statecmd.txt len:0
|
||||
2026-03-25T12:05:29.0435957Z Writing entry to tarball workflow/pathcmd.txt len:0
|
||||
2026-03-25T12:05:29.0436104Z Writing entry to tarball workflow/envs.txt len:0
|
||||
2026-03-25T12:05:29.0436243Z Writing entry to tarball workflow/SUMMARY.md len:0
|
||||
2026-03-25T12:05:29.0436418Z Extracting content to '/var/run/act'
|
||||
2026-03-25T12:05:29.0641886Z Wrote command \n\napt-get update && apt-get install -y ca-certificates curl gnupg\nmkdir -p /etc/apt/keyrings\ncurl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg\necho "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list\napt-get update && apt-get install -y nodejs\n\n\n to 'workflow/0.sh'
|
||||
2026-03-25T12:05:29.0642653Z Writing entry to tarball workflow/0.sh len:407
|
||||
2026-03-25T12:05:29.0642896Z Extracting content to '/var/run/act'
|
||||
2026-03-25T12:05:29.0664683Z 🐳 docker exec cmd=[sh -e /var/run/act/workflow/0.sh] user= workdir=
|
||||
2026-03-25T12:05:29.0665120Z Exec command '[sh -e /var/run/act/workflow/0.sh]'
|
||||
2026-03-25T12:05:29.0665572Z Working directory '/workspace/***/***3-server'
|
||||
2026-03-25T12:05:29.1930283Z Get:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease [256 kB]
|
||||
2026-03-25T12:05:29.3020283Z Get:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease [126 kB]
|
||||
2026-03-25T12:05:29.3278285Z Get:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease [126 kB]
|
||||
2026-03-25T12:05:29.3533160Z Get:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease [126 kB]
|
||||
2026-03-25T12:05:29.3799993Z Get:5 http://ports.ubuntu.com/ubuntu-ports noble/universe arm64 Packages [19.0 MB]
|
||||
2026-03-25T12:05:29.8392869Z Get:6 http://ports.ubuntu.com/ubuntu-ports noble/restricted arm64 Packages [113 kB]
|
||||
2026-03-25T12:05:29.8393771Z Get:7 http://ports.ubuntu.com/ubuntu-ports noble/multiverse arm64 Packages [274 kB]
|
||||
2026-03-25T12:05:29.8394197Z Get:8 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 Packages [1,776 kB]
|
||||
2026-03-25T12:05:29.8802923Z Get:9 http://ports.ubuntu.com/ubuntu-ports noble-updates/multiverse arm64 Packages [45.7 kB]
|
||||
2026-03-25T12:05:29.8812220Z Get:10 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 Packages [2,451 kB]
|
||||
2026-03-25T12:05:29.9387427Z Get:11 http://ports.ubuntu.com/ubuntu-ports noble-updates/universe arm64 Packages [2,057 kB]
|
||||
2026-03-25T12:05:29.9942591Z Get:12 http://ports.ubuntu.com/ubuntu-ports noble-updates/restricted arm64 Packages [5,019 kB]
|
||||
2026-03-25T12:05:30.1164288Z Get:13 http://ports.ubuntu.com/ubuntu-ports noble-backports/multiverse arm64 Packages [695 B]
|
||||
2026-03-25T12:05:30.1180686Z Get:14 http://ports.ubuntu.com/ubuntu-ports noble-backports/universe arm64 Packages [36.1 kB]
|
||||
2026-03-25T12:05:30.1181823Z Get:15 http://ports.ubuntu.com/ubuntu-ports noble-backports/main arm64 Packages [49.5 kB]
|
||||
2026-03-25T12:05:30.1182817Z Get:16 http://ports.ubuntu.com/ubuntu-ports noble-security/universe arm64 Packages [1,502 kB]
|
||||
2026-03-25T12:05:30.1538385Z Get:17 http://ports.ubuntu.com/ubuntu-ports noble-security/multiverse arm64 Packages [44.1 kB]
|
||||
2026-03-25T12:05:30.1539003Z Get:18 http://ports.ubuntu.com/ubuntu-ports noble-security/restricted arm64 Packages [4,828 kB]
|
||||
2026-03-25T12:05:30.2657962Z Get:19 http://ports.ubuntu.com/ubuntu-ports noble-security/main arm64 Packages [2,087 kB]
|
||||
2026-03-25T12:05:31.5132214Z Fetched 39.9 MB in 2s (16.9 MB/s)
|
||||
2026-03-25T12:05:32.8027349Z Reading package lists...
|
||||
2026-03-25T12:05:34.1413764Z Reading package lists...
|
||||
2026-03-25T12:05:34.4862647Z Building dependency tree...
|
||||
2026-03-25T12:05:34.4864531Z Reading state information...
|
||||
2026-03-25T12:05:35.2536110Z ca-certificates is already the newest version (20240203).
|
||||
2026-03-25T12:05:35.2536691Z curl is already the newest version (8.5.0-2ubuntu10.8).
|
||||
2026-03-25T12:05:35.2536858Z gnupg is already the newest version (2.4.4-2ubuntu17.4).
|
||||
2026-03-25T12:05:35.2537061Z 0 upgraded, 0 newly installed, 0 to remove and 18 not upgraded.
|
||||
2026-03-25T12:05:35.5548236Z Get:1 https://deb.nodesource.com/node_22.x nodistro InRelease [12.1 kB]
|
||||
2026-03-25T12:05:35.7037091Z Hit:2 http://ports.ubuntu.com/ubuntu-ports noble InRelease
|
||||
2026-03-25T12:05:35.7322162Z Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease
|
||||
2026-03-25T12:05:35.7599694Z Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease
|
||||
2026-03-25T12:05:35.7870145Z Hit:5 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease
|
||||
2026-03-25T12:05:35.8551499Z Get:6 https://deb.nodesource.com/node_22.x nodistro/main arm64 Packages [9,040 B]
|
||||
2026-03-25T12:05:35.9067803Z Fetched 21.2 kB in 1s (41.2 kB/s)
|
||||
2026-03-25T12:05:37.2373298Z Reading package lists...
|
||||
2026-03-25T12:05:38.5735995Z Reading package lists...
|
||||
2026-03-25T12:05:38.9087319Z Building dependency tree...
|
||||
2026-03-25T12:05:38.9087899Z Reading state information...
|
||||
2026-03-25T12:05:39.5855767Z The following additional packages will be installed:
|
||||
2026-03-25T12:05:39.5870559Z libpython3-stdlib libpython3.12-minimal libpython3.12-stdlib media-types
|
||||
2026-03-25T12:05:39.5880369Z netbase python3 python3-minimal python3.12 python3.12-minimal
|
||||
2026-03-25T12:05:39.5913231Z Suggested packages:
|
||||
2026-03-25T12:05:39.5913883Z python3-doc python3-tk python3-venv python3.12-venv python3.12-doc
|
||||
2026-03-25T12:05:39.5914038Z binfmt-support
|
||||
2026-03-25T12:05:39.6741455Z The following NEW packages will be installed:
|
||||
2026-03-25T12:05:39.6754401Z libpython3-stdlib libpython3.12-minimal libpython3.12-stdlib media-types
|
||||
2026-03-25T12:05:39.6762410Z netbase nodejs python3 python3-minimal python3.12 python3.12-minimal
|
||||
2026-03-25T12:05:39.7694458Z 0 upgraded, 10 newly installed, 0 to remove and 18 not upgraded.
|
||||
2026-03-25T12:05:39.7694946Z Need to get 42.9 MB of archives.
|
||||
2026-03-25T12:05:39.7695098Z After this operation, 259 MB of additional disk space will be used.
|
||||
2026-03-25T12:05:39.7695287Z Get:1 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 libpython3.12-minimal arm64 3.12.3-1ubuntu0.12 [834 kB]
|
||||
2026-03-25T12:05:39.8854035Z Get:2 https://deb.nodesource.com/node_22.x nodistro/main arm64 nodejs arm64 22.22.1-1nodesource1 [37.0 MB]
|
||||
2026-03-25T12:05:39.9473201Z Get:3 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3.12-minimal arm64 3.12.3-1ubuntu0.12 [2,252 kB]
|
||||
2026-03-25T12:05:40.0068444Z Get:4 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3-minimal arm64 3.12.3-0ubuntu2.1 [27.4 kB]
|
||||
2026-03-25T12:05:40.0073156Z Get:5 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 media-types all 10.1.0 [27.5 kB]
|
||||
2026-03-25T12:05:40.0077139Z Get:6 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 netbase all 6.4 [13.1 kB]
|
||||
2026-03-25T12:05:40.0079975Z Get:7 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 libpython3.12-stdlib arm64 3.12.3-1ubuntu0.12 [2,037 kB]
|
||||
2026-03-25T12:05:40.0421355Z Get:8 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3.12 arm64 3.12.3-1ubuntu0.12 [651 kB]
|
||||
2026-03-25T12:05:40.0818905Z Get:9 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 libpython3-stdlib arm64 3.12.3-0ubuntu2.1 [10.1 kB]
|
||||
2026-03-25T12:05:40.0822085Z Get:10 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3 arm64 3.12.3-0ubuntu2.1 [23.0 kB]
|
||||
2026-03-25T12:05:41.0927970Z debconf: delaying package configuration, since apt-utils is not installed
|
||||
2026-03-25T12:05:41.1546756Z Fetched 42.9 MB in 1s (38.8 MB/s)
|
||||
2026-03-25T12:05:41.2105330Z Selecting previously unselected package libpython3.12-minimal:arm64.
|
||||
2026-03-25T12:05:41.2141434Z (Reading database ...
|
||||
(Reading database ... 5%
|
||||
(Reading database ... 10%
|
||||
(Reading database ... 15%
|
||||
(Reading database ... 20%
|
||||
(Reading database ... 25%
|
||||
(Reading database ... 30%
|
||||
(Reading database ... 35%
|
||||
(Reading database ... 40%
|
||||
(Reading database ... 45%
|
||||
(Reading database ... 50%
|
||||
(Reading database ... 55%
|
||||
(Reading database ... 60%
|
||||
(Reading database ... 65%
|
||||
(Reading database ... 70%
|
||||
(Reading database ... 75%
|
||||
(Reading database ... 80%
|
||||
(Reading database ... 85%
|
||||
(Reading database ... 90%
|
||||
(Reading database ... 95%
|
||||
(Reading database ... 100%
|
||||
(Reading database ... 10203 files and directories currently installed.)
|
||||
2026-03-25T12:05:41.2151206Z Preparing to unpack .../libpython3.12-minimal_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:05:41.2237074Z Unpacking libpython3.12-minimal:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:41.3575893Z Selecting previously unselected package python3.12-minimal.
|
||||
2026-03-25T12:05:41.3590278Z Preparing to unpack .../python3.12-minimal_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:05:41.3695167Z Unpacking python3.12-minimal (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:41.4592573Z Setting up libpython3.12-minimal:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:41.4945507Z Setting up python3.12-minimal (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:42.5868849Z Selecting previously unselected package python3-minimal.
|
||||
2026-03-25T12:05:42.5916327Z (Reading database ...
|
||||
(Reading database ... 5%
|
||||
(Reading database ... 10%
|
||||
(Reading database ... 15%
|
||||
(Reading database ... 20%
|
||||
(Reading database ... 25%
|
||||
(Reading database ... 30%
|
||||
(Reading database ... 35%
|
||||
(Reading database ... 40%
|
||||
(Reading database ... 45%
|
||||
(Reading database ... 50%
|
||||
(Reading database ... 55%
|
||||
(Reading database ... 60%
|
||||
(Reading database ... 65%
|
||||
(Reading database ... 70%
|
||||
(Reading database ... 75%
|
||||
(Reading database ... 80%
|
||||
(Reading database ... 85%
|
||||
(Reading database ... 90%
|
||||
(Reading database ... 95%
|
||||
(Reading database ... 100%
|
||||
(Reading database ... 10514 files and directories currently installed.)
|
||||
2026-03-25T12:05:42.5927206Z Preparing to unpack .../0-python3-minimal_3.12.3-0ubuntu2.1_arm64.deb ...
|
||||
2026-03-25T12:05:42.6014142Z Unpacking python3-minimal (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:05:42.6550947Z Selecting previously unselected package media-types.
|
||||
2026-03-25T12:05:42.6565762Z Preparing to unpack .../1-media-types_10.1.0_all.deb ...
|
||||
2026-03-25T12:05:42.6650215Z Unpacking media-types (10.1.0) ...
|
||||
2026-03-25T12:05:42.7298448Z Selecting previously unselected package netbase.
|
||||
2026-03-25T12:05:42.7308345Z Preparing to unpack .../2-netbase_6.4_all.deb ...
|
||||
2026-03-25T12:05:42.7390454Z Unpacking netbase (6.4) ...
|
||||
2026-03-25T12:05:42.7929628Z Selecting previously unselected package libpython3.12-stdlib:arm64.
|
||||
2026-03-25T12:05:42.7930194Z Preparing to unpack .../3-libpython3.12-stdlib_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:05:42.8009455Z Unpacking libpython3.12-stdlib:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:42.9412131Z Selecting previously unselected package python3.12.
|
||||
2026-03-25T12:05:42.9416942Z Preparing to unpack .../4-python3.12_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:05:42.9542497Z Unpacking python3.12 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:43.0104645Z Selecting previously unselected package libpython3-stdlib:arm64.
|
||||
2026-03-25T12:05:43.0105209Z Preparing to unpack .../5-libpython3-stdlib_3.12.3-0ubuntu2.1_arm64.deb ...
|
||||
2026-03-25T12:05:43.0184920Z Unpacking libpython3-stdlib:arm64 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:05:43.0704590Z Setting up python3-minimal (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:05:43.2911706Z Selecting previously unselected package python3.
|
||||
2026-03-25T12:05:43.2943227Z (Reading database ...
|
||||
(Reading database ... 5%
|
||||
(Reading database ... 10%
|
||||
(Reading database ... 15%
|
||||
(Reading database ... 20%
|
||||
(Reading database ... 25%
|
||||
(Reading database ... 30%
|
||||
(Reading database ... 35%
|
||||
(Reading database ... 40%
|
||||
(Reading database ... 45%
|
||||
(Reading database ... 50%
|
||||
(Reading database ... 55%
|
||||
(Reading database ... 60%
|
||||
(Reading database ... 65%
|
||||
(Reading database ... 70%
|
||||
(Reading database ... 75%
|
||||
(Reading database ... 80%
|
||||
(Reading database ... 85%
|
||||
(Reading database ... 90%
|
||||
(Reading database ... 95%
|
||||
(Reading database ... 100%
|
||||
(Reading database ... 10955 files and directories currently installed.)
|
||||
2026-03-25T12:05:43.2945936Z Preparing to unpack .../python3_3.12.3-0ubuntu2.1_arm64.deb ...
|
||||
2026-03-25T12:05:43.3054683Z Unpacking python3 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:05:43.3791632Z Selecting previously unselected package nodejs.
|
||||
2026-03-25T12:05:43.3792145Z Preparing to unpack .../nodejs_22.22.1-1nodesource1_arm64.deb ...
|
||||
2026-03-25T12:05:43.3883396Z Unpacking nodejs (22.22.1-1nodesource1) ...
|
||||
2026-03-25T12:05:45.1595580Z Setting up media-types (10.1.0) ...
|
||||
2026-03-25T12:05:45.1928178Z Setting up netbase (6.4) ...
|
||||
2026-03-25T12:05:45.2516737Z Setting up libpython3.12-stdlib:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:45.2763302Z Setting up python3.12 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:05:46.4572507Z Setting up libpython3-stdlib:arm64 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:05:46.4827528Z Setting up python3 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:05:46.5029071Z running python rtupdate hooks for python3.12...
|
||||
2026-03-25T12:05:46.5029813Z running python post-rtupdate hooks for python3.12...
|
||||
2026-03-25T12:05:46.6148316Z Setting up nodejs (22.22.1-1nodesource1) ...
|
||||
2026-03-25T12:05:47.1121917Z ::add-matcher::/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/problem-matcher.json
|
||||
2026-03-25T12:05:47.1125470Z Syncing repository: ***/***3-server
|
||||
2026-03-25T12:05:47.1131969Z ::group::Getting Git version info
|
||||
2026-03-25T12:05:47.1168484Z Working directory is '/workspace/***/***3-server'
|
||||
2026-03-25T12:05:47.1181789Z [command]/usr/bin/git version
|
||||
2026-03-25T12:05:47.1229566Z git version 2.43.0
|
||||
2026-03-25T12:05:47.1261791Z ::endgroup::
|
||||
2026-03-25T12:05:47.1293796Z Temporarily overriding HOME='/tmp/5934957e-e423-4d52-bf2d-bec37cc99fdb' before making global git config changes
|
||||
2026-03-25T12:05:47.1295018Z Adding repository directory to the temporary git global config as a safe directory
|
||||
2026-03-25T12:05:47.1305207Z [command]/usr/bin/git config --global --add safe.directory /workspace/***/***3-server
|
||||
2026-03-25T12:05:47.1341779Z Deleting the contents of '/workspace/***/***3-server'
|
||||
2026-03-25T12:05:47.1347568Z ::group::Initializing the repository
|
||||
2026-03-25T12:05:47.1353328Z [command]/usr/bin/git init /workspace/***/***3-server
|
||||
2026-03-25T12:05:47.1387743Z hint: Using 'master' as the name for the initial branch. This default branch name
|
||||
2026-03-25T12:05:47.1388199Z hint: is subject to change. To configure the initial branch name to use in all
|
||||
2026-03-25T12:05:47.1388360Z hint: of your new repositories, which will suppress this warning, call:
|
||||
2026-03-25T12:05:47.1388505Z hint:
|
||||
2026-03-25T12:05:47.1388717Z hint: git config --global init.defaultBranch <name>
|
||||
2026-03-25T12:05:47.1388847Z hint:
|
||||
2026-03-25T12:05:47.1389121Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
|
||||
2026-03-25T12:05:47.1389269Z hint: 'development'. The just-created branch can be renamed via this command:
|
||||
2026-03-25T12:05:47.1389422Z hint:
|
||||
2026-03-25T12:05:47.1389553Z hint: git branch -m <name>
|
||||
2026-03-25T12:05:47.1394082Z Initialized empty Git repository in /workspace/***/***3-server/.git/
|
||||
2026-03-25T12:05:47.1412922Z [command]/usr/bin/git remote add origin https://gitea.siegeln.net/***/***3-server
|
||||
2026-03-25T12:05:47.1454222Z ::endgroup::
|
||||
2026-03-25T12:05:47.1455198Z ::group::Disabling automatic garbage collection
|
||||
2026-03-25T12:05:47.1459932Z [command]/usr/bin/git config --local gc.auto 0
|
||||
2026-03-25T12:05:47.1511236Z ::endgroup::
|
||||
2026-03-25T12:05:47.1511645Z ::group::Setting up auth
|
||||
2026-03-25T12:05:47.1516443Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
|
||||
2026-03-25T12:05:47.1557420Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
|
||||
2026-03-25T12:05:47.1744851Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/gitea\.siegeln\.net\/\.extraheader
|
||||
2026-03-25T12:05:47.1778167Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/gitea\.siegeln\.net\/\.extraheader' && git config --local --unset-all 'http.https://gitea.siegeln.net/.extraheader' || :"
|
||||
2026-03-25T12:05:47.1961322Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
|
||||
2026-03-25T12:05:47.1992646Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
|
||||
2026-03-25T12:05:47.2169231Z [command]/usr/bin/git config --local http.https://gitea.siegeln.net/.extraheader AUTHORIZATION: basic ***
|
||||
2026-03-25T12:05:47.2203274Z ::endgroup::
|
||||
2026-03-25T12:05:47.2203661Z ::group::Fetching the repository
|
||||
2026-03-25T12:05:47.2227031Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +c96fbef5d517a255fbebb231cfe07c32dd7af9a2:refs/remotes/origin/main
|
||||
2026-03-25T12:05:47.5225105Z From https://gitea.siegeln.net/***/***3-server
|
||||
2026-03-25T12:05:47.5225789Z * [new ref] c96fbef5d517a255fbebb231cfe07c32dd7af9a2 -> origin/main
|
||||
2026-03-25T12:05:47.5242588Z ::endgroup::
|
||||
2026-03-25T12:05:47.5242939Z ::group::Determining the checkout info
|
||||
2026-03-25T12:05:47.5245115Z ::endgroup::
|
||||
2026-03-25T12:05:47.5250211Z [command]/usr/bin/git sparse-checkout disable
|
||||
2026-03-25T12:05:47.5286780Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig
|
||||
2026-03-25T12:05:47.5312590Z ::group::Checking out the ref
|
||||
2026-03-25T12:05:47.5317724Z [command]/usr/bin/git checkout --progress --force -B main refs/remotes/origin/main
|
||||
2026-03-25T12:05:47.5620122Z Switched to a new branch 'main'
|
||||
2026-03-25T12:05:47.5620634Z branch 'main' set up to track 'origin/main'.
|
||||
2026-03-25T12:05:47.5626584Z ::endgroup::
|
||||
2026-03-25T12:05:47.5669948Z [command]/usr/bin/git log -1 --format=%H
|
||||
2026-03-25T12:05:47.5692040Z c96fbef5d517a255fbebb231cfe07c32dd7af9a2
|
||||
2026-03-25T12:05:47.5706708Z ::remove-matcher owner=checkout-git::
|
||||
2026-03-25T12:05:48.3567196Z (node:674) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
|
||||
2026-03-25T12:05:48.3567623Z (Use `node --trace-deprecation ...` to show where the warning was created)
|
||||
2026-03-25T12:05:49.1970440Z Cache Size: ~352 MB (368583412 B)
|
||||
2026-03-25T12:05:49.2003055Z [command]/usr/bin/tar -xf /tmp/45f912d1-a147-43aa-9691-70814448aaf7/cache.tgz -P -C /workspace/***/***3-server -z
|
||||
2026-03-25T12:05:52.7483801Z Cache restored successfully
|
||||
2026-03-25T12:05:52.7951677Z Cache restored from key: linux-maven-9dd3df74bd8aba32251bb49c59e820a2aa73582b0f09ca6d6e930542cfb05391
|
||||
2026-03-25T12:05:59.8118885Z
|
||||
2026-03-25T12:05:59.8119616Z added 213 packages, and audited 214 packages in 7s
|
||||
2026-03-25T12:05:59.8120801Z
|
||||
2026-03-25T12:05:59.8121169Z 58 packages are looking for funding
|
||||
2026-03-25T12:05:59.8121449Z run `npm fund` for details
|
||||
2026-03-25T12:05:59.8132812Z
|
||||
2026-03-25T12:05:59.8133050Z found 0 vulnerabilities
|
||||
2026-03-25T12:05:59.9967402Z
|
||||
2026-03-25T12:05:59.9967960Z > ui@0.0.0 build
|
||||
2026-03-25T12:05:59.9968142Z > tsc -p tsconfig.app.json --noEmit && vite build
|
||||
2026-03-25T12:05:59.9968297Z
|
||||
2026-03-25T12:06:06.5319156Z [36mvite v8.0.1 [32mbuilding client environment for production...[36m[39m
|
||||
2026-03-25T12:06:07.1364525Z [2K
|
||||
transforming...✓ 129 modules transformed.
|
||||
2026-03-25T12:06:07.2755293Z rendering chunks...
|
||||
2026-03-25T12:06:07.6308430Z computing gzip size...
|
||||
2026-03-25T12:06:07.6646343Z dist/index.html 0.94 kB │ gzip: 0.41 kB
|
||||
2026-03-25T12:06:07.6646953Z dist/assets/dm-sans-600-Aqo67rzb.woff2 14.14 kB
|
||||
2026-03-25T12:06:07.6647195Z dist/assets/dm-sans-400-CW0RaeGs.woff2 14.20 kB
|
||||
2026-03-25T12:06:07.6647355Z dist/assets/dm-sans-500-B9HHJjqV.woff2 14.30 kB
|
||||
2026-03-25T12:06:07.6647500Z dist/assets/dm-sans-700-DvUfVpUG.woff2 14.34 kB
|
||||
2026-03-25T12:06:07.6647639Z dist/assets/dm-sans-400-italic-DRLHr0TN.woff2 15.14 kB
|
||||
2026-03-25T12:06:07.6647798Z dist/assets/jetbrains-mono-400-V6pRDFza.woff2 21.16 kB
|
||||
2026-03-25T12:06:07.6647949Z dist/assets/jetbrains-mono-500-BWZEU5yA.woff2 21.83 kB
|
||||
2026-03-25T12:06:07.6648100Z dist/assets/jetbrains-mono-600-C8RAYTDA.woff2 21.86 kB
|
||||
2026-03-25T12:06:07.6648237Z dist/assets/OidcConfigPage-Bq9_k9q2.css 0.61 kB │ gzip: 0.31 kB
|
||||
2026-03-25T12:06:07.6648385Z dist/assets/AuditLogPage-wcUcdzwn.css 1.19 kB │ gzip: 0.56 kB
|
||||
2026-03-25T12:06:07.6648541Z dist/assets/RoutesMetrics-DaUBduav.css 1.46 kB │ gzip: 0.62 kB
|
||||
2026-03-25T12:06:07.6648704Z dist/assets/RbacPage-5iGc4gch.css 1.98 kB │ gzip: 0.69 kB
|
||||
2026-03-25T12:06:07.6648857Z dist/assets/AgentInstance--Z0IKMF0.css 2.50 kB │ gzip: 0.75 kB
|
||||
2026-03-25T12:06:07.6649011Z dist/assets/AgentHealth-y0_55tmK.css 3.42 kB │ gzip: 1.04 kB
|
||||
2026-03-25T12:06:07.6649159Z dist/assets/Dashboard-BYYJXEtx.css 3.64 kB │ gzip: 1.13 kB
|
||||
2026-03-25T12:06:07.6649404Z dist/assets/RouteDetail-CjL6IMio.css 4.26 kB │ gzip: 1.09 kB
|
||||
2026-03-25T12:06:07.6649547Z dist/assets/ExchangeDetail-VcItziWb.css 6.78 kB │ gzip: 1.67 kB
|
||||
2026-03-25T12:06:07.6649698Z dist/assets/index-CXegvd7B.css 111.14 kB │ gzip: 18.21 kB
|
||||
2026-03-25T12:06:07.6649862Z dist/assets/SwaggerPage-CH1CJXTy.css 176.70 kB │ gzip: 26.18 kB
|
||||
2026-03-25T12:06:07.6650017Z dist/assets/use-refresh-interval-CBD44ngw.js 0.19 kB │ gzip: 0.15 kB
|
||||
2026-03-25T12:06:07.6650157Z dist/assets/admin-api-Dldh4fYm.js 0.49 kB │ gzip: 0.35 kB
|
||||
2026-03-25T12:06:07.6650297Z dist/assets/AdminLayout-CKg4TLhS.js 0.55 kB │ gzip: 0.34 kB
|
||||
2026-03-25T12:06:07.6650468Z dist/assets/chunk-B3K2TuZy.js 0.55 kB │ gzip: 0.35 kB
|
||||
2026-03-25T12:06:07.6650606Z dist/assets/agent-metrics-DI4HNJNm.js 0.59 kB │ gzip: 0.42 kB
|
||||
2026-03-25T12:06:07.6650776Z dist/assets/SwaggerPage-ZwDCrEj1.js 0.87 kB │ gzip: 0.54 kB
|
||||
2026-03-25T12:06:07.6650931Z dist/assets/diagram-mapping-zsjZZKRP.js 1.33 kB │ gzip: 0.68 kB
|
||||
2026-03-25T12:06:07.6651084Z dist/assets/useMutation-CjGnzIYg.js 2.31 kB │ gzip: 0.97 kB
|
||||
2026-03-25T12:06:07.6651227Z dist/assets/OpenSearchAdminPage-WnM4gikq.js 2.92 kB │ gzip: 1.17 kB
|
||||
2026-03-25T12:06:07.6651367Z dist/assets/DatabaseAdminPage-CclQJ0Ok.js 3.09 kB │ gzip: 1.16 kB
|
||||
2026-03-25T12:06:07.6651574Z dist/assets/AuditLogPage-4V3oK99a.js 4.53 kB │ gzip: 1.70 kB
|
||||
2026-03-25T12:06:07.6651718Z dist/assets/OidcConfigPage-D0YsPv3U.js 5.38 kB │ gzip: 1.90 kB
|
||||
2026-03-25T12:06:07.6651866Z dist/assets/RoutesMetrics-5XMqf33P.js 5.98 kB │ gzip: 2.10 kB
|
||||
2026-03-25T12:06:07.6652008Z dist/assets/jsx-runtime-DdsoV0Vr.js 9.07 kB │ gzip: 3.52 kB
|
||||
2026-03-25T12:06:07.6652178Z dist/assets/AgentInstance-Bzs6lRBr.js 9.86 kB │ gzip: 2.83 kB
|
||||
2026-03-25T12:06:07.6652328Z dist/assets/Dashboard-C23kCbUs.js 10.16 kB │ gzip: 3.28 kB
|
||||
2026-03-25T12:06:07.6652472Z dist/assets/auth-store-DCBo9Ans.js 10.34 kB │ gzip: 3.79 kB
|
||||
2026-03-25T12:06:07.6652628Z dist/assets/AgentHealth-DwOu1dM9.js 11.36 kB │ gzip: 3.28 kB
|
||||
2026-03-25T12:06:07.6652774Z dist/assets/RouteDetail-l02TOYKF.js 12.35 kB │ gzip: 4.07 kB
|
||||
2026-03-25T12:06:07.6652926Z dist/assets/ExchangeDetail-BLpXn9YT.js 14.56 kB │ gzip: 4.25 kB
|
||||
2026-03-25T12:06:07.6653071Z dist/assets/useQuery-oR6RVwVH.js 22.31 kB │ gzip: 7.37 kB
|
||||
2026-03-25T12:06:07.6653215Z dist/assets/RbacPage-oz3jLDxG.js 25.49 kB │ gzip: 5.57 kB
|
||||
2026-03-25T12:06:07.6653360Z dist/assets/index-COBtXOCg.js 205.44 kB │ gzip: 64.73 kB
|
||||
2026-03-25T12:06:07.6653503Z dist/assets/index.es-BideRJQn.js 216.16 kB │ gzip: 66.22 kB
|
||||
2026-03-25T12:06:07.6653661Z dist/assets/swagger-ui-bundle-BoDRmAdn.js 1,371.99 kB │ gzip: 390.77 kB
|
||||
2026-03-25T12:06:07.6653794Z
|
||||
2026-03-25T12:06:07.6657850Z [32m✓ built in 1.13s[39m
|
||||
2026-03-25T12:06:07.6667869Z [33m[plugin builtin:vite-reporter]
|
||||
2026-03-25T12:06:07.6668214Z (!) Some chunks are larger than 500 kB after minification. Consider:
|
||||
2026-03-25T12:06:07.6668402Z - Using dynamic import() to code-split the application
|
||||
2026-03-25T12:06:07.6668544Z - Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
|
||||
2026-03-25T12:06:07.6668727Z - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.[39m
|
||||
2026-03-25T12:06:09.5292704Z [INFO] Scanning for projects...
|
||||
2026-03-25T12:06:09.9635324Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:06:09.9636148Z [INFO] Reactor Build Order:
|
||||
2026-03-25T12:06:09.9636467Z [INFO]
|
||||
2026-03-25T12:06:09.9659474Z [INFO] Cameleer3 Server Parent [pom]
|
||||
2026-03-25T12:06:09.9660313Z [INFO] Cameleer3 Server Core [jar]
|
||||
2026-03-25T12:06:09.9660795Z [INFO] Cameleer3 Server App [jar]
|
||||
2026-03-25T12:06:09.9828555Z [INFO]
|
||||
2026-03-25T12:06:09.9829990Z [INFO] ---------------< com.***3:***3-server-parent >----------------
|
||||
2026-03-25T12:06:09.9830445Z [INFO] Building Cameleer3 Server Parent 1.0-SNAPSHOT [1/3]
|
||||
2026-03-25T12:06:09.9831993Z [INFO] from pom.xml
|
||||
2026-03-25T12:06:09.9832701Z [INFO] --------------------------------[ pom ]---------------------------------
|
||||
2026-03-25T12:06:10.0308726Z [INFO]
|
||||
2026-03-25T12:06:10.0310168Z [INFO] --- clean:3.4.1:clean (default-clean) @ ***3-server-parent ---
|
||||
2026-03-25T12:06:10.1462826Z [INFO]
|
||||
2026-03-25T12:06:10.1463418Z [INFO] ----------------< com.***3:***3-server-core >-----------------
|
||||
2026-03-25T12:06:10.1463669Z [INFO] Building Cameleer3 Server Core 1.0-SNAPSHOT [2/3]
|
||||
2026-03-25T12:06:10.1464039Z [INFO] from ***3-server-core/pom.xml
|
||||
2026-03-25T12:06:10.1464250Z [INFO] --------------------------------[ jar ]---------------------------------
|
||||
2026-03-25T12:06:10.4658491Z [INFO]
|
||||
2026-03-25T12:06:10.4659065Z [INFO] --- clean:3.4.1:clean (default-clean) @ ***3-server-core ---
|
||||
2026-03-25T12:06:10.4666288Z [INFO]
|
||||
2026-03-25T12:06:10.4667848Z [INFO] --- resources:3.3.1:resources (default-resources) @ ***3-server-core ---
|
||||
2026-03-25T12:06:10.6497862Z [INFO] skip non existing resourceDirectory /workspace/***/***3-server/***3-server-core/src/main/resources
|
||||
2026-03-25T12:06:10.6498571Z [INFO] skip non existing resourceDirectory /workspace/***/***3-server/***3-server-core/src/main/resources
|
||||
2026-03-25T12:06:10.6506664Z [INFO]
|
||||
2026-03-25T12:06:10.6508453Z [INFO] --- compiler:3.13.0:compile (default-compile) @ ***3-server-core ---
|
||||
2026-03-25T12:06:10.8563867Z [INFO] Recompiling the module because of changed source code.
|
||||
2026-03-25T12:06:10.8674155Z [INFO] Compiling 63 source files with javac [debug parameters release 17] to target/classes
|
||||
2026-03-25T12:06:12.7604194Z [INFO] -------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7607101Z [ERROR] COMPILATION ERROR :
|
||||
2026-03-25T12:06:12.7610106Z [INFO] -------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7612996Z [ERROR] /workspace/***/***3-server/***3-server-core/src/main/java/com/***3/server/core/logging/LogIndexService.java:[3,34] cannot find symbol
|
||||
2026-03-25T12:06:12.7613413Z symbol: class LogEntry
|
||||
2026-03-25T12:06:12.7614388Z location: package com.***3.common.model
|
||||
2026-03-25T12:06:12.7616233Z [ERROR] /workspace/***/***3-server/***3-server-core/src/main/java/com/***3/server/core/logging/LogIndexService.java:[9,62] cannot find symbol
|
||||
2026-03-25T12:06:12.7616604Z symbol: class LogEntry
|
||||
2026-03-25T12:06:12.7617505Z location: interface com.***3.server.core.logging.LogIndexService
|
||||
2026-03-25T12:06:12.7620033Z [INFO] 2 errors
|
||||
2026-03-25T12:06:12.7622462Z [INFO] -------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7631912Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7640947Z [INFO] Reactor Summary for Cameleer3 Server Parent 1.0-SNAPSHOT:
|
||||
2026-03-25T12:06:12.7641508Z [INFO]
|
||||
2026-03-25T12:06:12.7641764Z [INFO] Cameleer3 Server Parent ............................ SUCCESS [ 0.164 s]
|
||||
2026-03-25T12:06:12.7643553Z [INFO] Cameleer3 Server Core .............................. FAILURE [ 2.617 s]
|
||||
2026-03-25T12:06:12.7646103Z [INFO] Cameleer3 Server App ............................... SKIPPED
|
||||
2026-03-25T12:06:12.7647941Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7649783Z [INFO] BUILD FAILURE
|
||||
2026-03-25T12:06:12.7651509Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7654688Z [INFO] Total time: 3.272 s
|
||||
2026-03-25T12:06:12.7658348Z [INFO] Finished at: 2026-03-25T12:06:12Z
|
||||
2026-03-25T12:06:12.7660448Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:06:12.7673796Z [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project ***3-server-core: Compilation failure: Compilation failure:
|
||||
2026-03-25T12:06:12.7676849Z [ERROR] /workspace/***/***3-server/***3-server-core/src/main/java/com/***3/server/core/logging/LogIndexService.java:[3,34] cannot find symbol
|
||||
2026-03-25T12:06:12.7678889Z [ERROR] symbol: class LogEntry
|
||||
2026-03-25T12:06:12.7680952Z [ERROR] location: package com.***3.common.model
|
||||
488
ci-log2.txt
Normal file
@@ -0,0 +1,488 @@
|
||||
2026-03-25T12:11:15.0673241Z my-docker-runner(version:v0.3.0) received task 1109 of job build, be triggered by event: push
|
||||
2026-03-25T12:11:15.0678286Z workflow prepared
|
||||
2026-03-25T12:11:15.0679524Z evaluating expression 'github.event_name != 'delete''
|
||||
2026-03-25T12:11:15.0680588Z expression 'github.event_name != 'delete'' evaluated to 'true'
|
||||
2026-03-25T12:11:15.0680862Z 🚀 Start image=maven:3.9-eclipse-temurin-17
|
||||
2026-03-25T12:11:15.0765600Z 🐳 docker pull image=maven:3.9-eclipse-temurin-17 platform= username= forcePull=true
|
||||
2026-03-25T12:11:15.0766370Z 🐳 docker pull maven:3.9-eclipse-temurin-17
|
||||
2026-03-25T12:11:15.0766922Z pulling image 'docker.io/library/maven:3.9-eclipse-temurin-17' ()
|
||||
2026-03-25T12:11:15.9297560Z Pulling from library/maven :: 3.9-eclipse-temurin-17
|
||||
2026-03-25T12:11:15.9512271Z Digest: sha256:39a5260d49fe20e5f407bf63f63a267d9870965bcd1d114e52e1e50ba1c55a32 ::
|
||||
2026-03-25T12:11:15.9512734Z Status: Image is up to date for maven:3.9-eclipse-temurin-17 ::
|
||||
2026-03-25T12:11:15.9638746Z 🐳 docker create image=maven:3.9-eclipse-temurin-17 platform= entrypoint=["/bin/sleep" "10800"] cmd=[] network="gitea_gitea_default"
|
||||
2026-03-25T12:11:15.9735982Z Custom container.Config from options ==> &{Hostname: Domainname: User: AttachStdin:false AttachStdout:true AttachStderr:true ExposedPorts:map[] Tty:false OpenStdin:false StdinOnce:false Env:[] Cmd:[] Healthcheck:<nil> ArgsEscaped:false Image: Volumes:map[] WorkingDir: Entrypoint:[] NetworkDisabled:false MacAddress: OnBuild:[] Labels:map[] StopSignal: StopTimeout:<nil> Shell:[]}
|
||||
2026-03-25T12:11:15.9736700Z Merged container.Config ==> &{Hostname: Domainname: User: AttachStdin:false AttachStdout:true AttachStderr:true ExposedPorts:map[] Tty:false OpenStdin:false StdinOnce:false Env:[RUNNER_TOOL_CACHE=/opt/hostedtoolcache RUNNER_OS=Linux RUNNER_ARCH=ARM64 RUNNER_TEMP=/tmp LANG=C.UTF-8] Cmd:[] Healthcheck:<nil> ArgsEscaped:false Image:maven:3.9-eclipse-temurin-17 Volumes:map[] WorkingDir:/workspace/***/***3-server Entrypoint:[/bin/sleep 10800] NetworkDisabled:false MacAddress: OnBuild:[] Labels:map[] StopSignal: StopTimeout:<nil> Shell:[]}
|
||||
2026-03-25T12:11:15.9737364Z Custom container.HostConfig from options ==> &{Binds:[] ContainerIDFile: LogConfig:{Type: Config:map[]} NetworkMode:gitea_gitea_default PortBindings:map[] RestartPolicy:{Name:no MaximumRetryCount:0} AutoRemove:false VolumeDriver: VolumesFrom:[] ConsoleSize:[0 0] Annotations:map[] CapAdd:[] CapDrop:[] CgroupnsMode: DNS:[] DNSOptions:[] DNSSearch:[] ExtraHosts:[] GroupAdd:[] IpcMode: Cgroup: Links:[] OomScoreAdj:0 PidMode: Privileged:false PublishAllPorts:false ReadonlyRootfs:false SecurityOpt:[] StorageOpt:map[] Tmpfs:map[] UTSMode: UsernsMode: ShmSize:0 Sysctls:map[] Runtime: Isolation: Resources:{CPUShares:0 Memory:0 NanoCPUs:0 CgroupParent: BlkioWeight:0 BlkioWeightDevice:[] BlkioDeviceReadBps:[] BlkioDeviceWriteBps:[] BlkioDeviceReadIOps:[] BlkioDeviceWriteIOps:[] CPUPeriod:0 CPUQuota:0 CPURealtimePeriod:0 CPURealtimeRuntime:0 CpusetCpus: CpusetMems: Devices:[] DeviceCgroupRules:[] DeviceRequests:[] KernelMemory:0 KernelMemoryTCP:0 MemoryReservation:0 MemorySwap:0 MemorySwappiness:0x228ace0e23c8 OomKillDisable:0x228ace0e22c3 PidsLimit:0x228ace0e2428 Ulimits:[] CPUCount:0 CPUPercent:0 IOMaximumIOps:0 IOMaximumBandwidth:0} Mounts:[] MaskedPaths:[] ReadonlyPaths:[] Init:<nil>}
|
||||
2026-03-25T12:11:15.9738107Z --network and --net in the options will be ignored.
|
||||
2026-03-25T12:11:15.9738529Z Merged container.HostConfig ==> &{Binds:[/var/run/docker.sock:/var/run/docker.sock] ContainerIDFile: LogConfig:{Type: Config:map[]} NetworkMode:gitea_gitea_default PortBindings:map[] RestartPolicy:{Name:no MaximumRetryCount:0} AutoRemove:true VolumeDriver: VolumesFrom:[] ConsoleSize:[0 0] Annotations:map[] CapAdd:[] CapDrop:[] CgroupnsMode: DNS:[] DNSOptions:[] DNSSearch:[] ExtraHosts:[] GroupAdd:[] IpcMode: Cgroup: Links:[] OomScoreAdj:0 PidMode: Privileged:false PublishAllPorts:false ReadonlyRootfs:false SecurityOpt:[] StorageOpt:map[] Tmpfs:map[] UTSMode: UsernsMode: ShmSize:0 Sysctls:map[] Runtime: Isolation: Resources:{CPUShares:0 Memory:0 NanoCPUs:0 CgroupParent: BlkioWeight:0 BlkioWeightDevice:[] BlkioDeviceReadBps:[] BlkioDeviceWriteBps:[] BlkioDeviceReadIOps:[] BlkioDeviceWriteIOps:[] CPUPeriod:0 CPUQuota:0 CPURealtimePeriod:0 CPURealtimeRuntime:0 CpusetCpus: CpusetMems: Devices:[] DeviceCgroupRules:[] DeviceRequests:[] KernelMemory:0 KernelMemoryTCP:0 MemoryReservation:0 MemorySwap:0 MemorySwappiness:0x228ace0e23c8 OomKillDisable:0x228ace0e22c3 PidsLimit:0x228ace0e2428 Ulimits:[] CPUCount:0 CPUPercent:0 IOMaximumIOps:0 IOMaximumBandwidth:0} Mounts:[{Type:volume Source:act-toolcache Target:/opt/hostedtoolcache ReadOnly:false Consistency: BindOptions:<nil> VolumeOptions:<nil> TmpfsOptions:<nil> ClusterOptions:<nil>} {Type:volume Source:GITEA-ACTIONS-TASK-1109_WORKFLOW-CI_JOB-build-env Target:/var/run/act ReadOnly:false Consistency: BindOptions:<nil> VolumeOptions:<nil> TmpfsOptions:<nil> ClusterOptions:<nil>} {Type:volume Source:GITEA-ACTIONS-TASK-1109_WORKFLOW-CI_JOB-build Target:/workspace/***/***3-server ReadOnly:false Consistency: BindOptions:<nil> VolumeOptions:<nil> TmpfsOptions:<nil> ClusterOptions:<nil>}] MaskedPaths:[] ReadonlyPaths:[] Init:<nil>}
|
||||
2026-03-25T12:11:16.0650880Z Created container name=GITEA-ACTIONS-TASK-1109_WORKFLOW-CI_JOB-build id=81d7988ac62ef23d7fea4defd927f69654288f6f2a5992c3480ab61a65fc57f2 from image maven:3.9-eclipse-temurin-17 (platform: )
|
||||
2026-03-25T12:11:16.0651415Z ENV ==> [RUNNER_TOOL_CACHE=/opt/hostedtoolcache RUNNER_OS=Linux RUNNER_ARCH=ARM64 RUNNER_TEMP=/tmp LANG=C.UTF-8]
|
||||
2026-03-25T12:11:16.0651606Z 🐳 docker run image=maven:3.9-eclipse-temurin-17 platform= entrypoint=["/bin/sleep" "10800"] cmd=[] network="gitea_gitea_default"
|
||||
2026-03-25T12:11:16.0651774Z Starting container: 81d7988ac62ef23d7fea4defd927f69654288f6f2a5992c3480ab61a65fc57f2
|
||||
2026-03-25T12:11:16.2476626Z Started container: 81d7988ac62ef23d7fea4defd927f69654288f6f2a5992c3480ab61a65fc57f2
|
||||
2026-03-25T12:11:16.3385957Z Writing entry to tarball workflow/event.json len:6148
|
||||
2026-03-25T12:11:16.3386642Z Writing entry to tarball workflow/envs.txt len:0
|
||||
2026-03-25T12:11:16.3386938Z Extracting content to '/var/run/act/'
|
||||
2026-03-25T12:11:16.3535771Z ☁ git clone 'https://github.com/actions/checkout' # ref=v4
|
||||
2026-03-25T12:11:16.3536162Z cloning https://github.com/actions/checkout to /root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab
|
||||
2026-03-25T12:11:16.6693512Z Non-terminating error while running 'git clone': some refs were not updated
|
||||
2026-03-25T12:11:16.6760011Z ☁ git clone 'https://github.com/actions/cache' # ref=v4
|
||||
2026-03-25T12:11:16.6760438Z cloning https://github.com/actions/cache to /root/.cache/act/6b4e4eb40e21c1bd02cb00a273f4d79af7c42205c1390e4e65c594ecd7a3696e
|
||||
2026-03-25T12:11:17.5454691Z Non-terminating error while running 'git clone': some refs were not updated
|
||||
2026-03-25T12:11:17.5674244Z evaluating expression ''
|
||||
2026-03-25T12:11:17.5675000Z expression '' evaluated to 'true'
|
||||
2026-03-25T12:11:17.5675244Z ⭐ Run Main Install Node.js 22
|
||||
2026-03-25T12:11:17.5675584Z Writing entry to tarball workflow/outputcmd.txt len:0
|
||||
2026-03-25T12:11:17.5675865Z Writing entry to tarball workflow/statecmd.txt len:0
|
||||
2026-03-25T12:11:17.5676098Z Writing entry to tarball workflow/pathcmd.txt len:0
|
||||
2026-03-25T12:11:17.5676311Z Writing entry to tarball workflow/envs.txt len:0
|
||||
2026-03-25T12:11:17.5676498Z Writing entry to tarball workflow/SUMMARY.md len:0
|
||||
2026-03-25T12:11:17.5676698Z Extracting content to '/var/run/act'
|
||||
2026-03-25T12:11:17.5917925Z Wrote command \n\napt-get update && apt-get install -y ca-certificates curl gnupg\nmkdir -p /etc/apt/keyrings\ncurl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg\necho "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list\napt-get update && apt-get install -y nodejs\n\n\n to 'workflow/0.sh'
|
||||
2026-03-25T12:11:17.5918846Z Writing entry to tarball workflow/0.sh len:407
|
||||
2026-03-25T12:11:17.5919160Z Extracting content to '/var/run/act'
|
||||
2026-03-25T12:11:17.5943770Z 🐳 docker exec cmd=[sh -e /var/run/act/workflow/0.sh] user= workdir=
|
||||
2026-03-25T12:11:17.5944375Z Exec command '[sh -e /var/run/act/workflow/0.sh]'
|
||||
2026-03-25T12:11:17.5944971Z Working directory '/workspace/***/***3-server'
|
||||
2026-03-25T12:11:17.8888948Z Get:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease [256 kB]
|
||||
2026-03-25T12:11:18.4199917Z Get:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease [126 kB]
|
||||
2026-03-25T12:11:18.5517055Z Get:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease [126 kB]
|
||||
2026-03-25T12:11:18.6832646Z Get:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease [126 kB]
|
||||
2026-03-25T12:11:18.8159266Z Get:5 http://ports.ubuntu.com/ubuntu-ports noble/restricted arm64 Packages [113 kB]
|
||||
2026-03-25T12:11:18.8552983Z Get:6 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 Packages [1,776 kB]
|
||||
2026-03-25T12:11:19.1280961Z Get:7 http://ports.ubuntu.com/ubuntu-ports noble/universe arm64 Packages [19.0 MB]
|
||||
2026-03-25T12:11:19.6229227Z Get:8 http://ports.ubuntu.com/ubuntu-ports noble/multiverse arm64 Packages [274 kB]
|
||||
2026-03-25T12:11:19.6298357Z Get:9 http://ports.ubuntu.com/ubuntu-ports noble-updates/restricted arm64 Packages [5,019 kB]
|
||||
2026-03-25T12:11:19.7533806Z Get:10 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 Packages [2,451 kB]
|
||||
2026-03-25T12:11:19.8087840Z Get:11 http://ports.ubuntu.com/ubuntu-ports noble-updates/universe arm64 Packages [2,057 kB]
|
||||
2026-03-25T12:11:19.8574738Z Get:12 http://ports.ubuntu.com/ubuntu-ports noble-updates/multiverse arm64 Packages [45.7 kB]
|
||||
2026-03-25T12:11:19.8583840Z Get:13 http://ports.ubuntu.com/ubuntu-ports noble-backports/universe arm64 Packages [36.1 kB]
|
||||
2026-03-25T12:11:19.8587388Z Get:14 http://ports.ubuntu.com/ubuntu-ports noble-backports/main arm64 Packages [49.5 kB]
|
||||
2026-03-25T12:11:19.8599308Z Get:15 http://ports.ubuntu.com/ubuntu-ports noble-backports/multiverse arm64 Packages [695 B]
|
||||
2026-03-25T12:11:19.8601750Z Get:16 http://ports.ubuntu.com/ubuntu-ports noble-security/main arm64 Packages [2,087 kB]
|
||||
2026-03-25T12:11:19.9076502Z Get:17 http://ports.ubuntu.com/ubuntu-ports noble-security/multiverse arm64 Packages [44.1 kB]
|
||||
2026-03-25T12:11:19.9088113Z Get:18 http://ports.ubuntu.com/ubuntu-ports noble-security/restricted arm64 Packages [4,828 kB]
|
||||
2026-03-25T12:11:20.0209834Z Get:19 http://ports.ubuntu.com/ubuntu-ports noble-security/universe arm64 Packages [1,502 kB]
|
||||
2026-03-25T12:11:21.2001793Z Fetched 39.9 MB in 4s (11.4 MB/s)
|
||||
2026-03-25T12:11:22.4920632Z Reading package lists...
|
||||
2026-03-25T12:11:23.8283529Z Reading package lists...
|
||||
2026-03-25T12:11:24.1797003Z Building dependency tree...
|
||||
2026-03-25T12:11:24.1797885Z Reading state information...
|
||||
2026-03-25T12:11:24.9189505Z ca-certificates is already the newest version (20240203).
|
||||
2026-03-25T12:11:24.9190005Z curl is already the newest version (8.5.0-2ubuntu10.8).
|
||||
2026-03-25T12:11:24.9190221Z gnupg is already the newest version (2.4.4-2ubuntu17.4).
|
||||
2026-03-25T12:11:24.9190409Z 0 upgraded, 0 newly installed, 0 to remove and 18 not upgraded.
|
||||
2026-03-25T12:11:25.2124240Z Get:1 https://deb.nodesource.com/node_22.x nodistro InRelease [12.1 kB]
|
||||
2026-03-25T12:11:25.2904786Z Get:2 https://deb.nodesource.com/node_22.x nodistro/main arm64 Packages [9,040 B]
|
||||
2026-03-25T12:11:34.8063450Z Hit:3 http://ports.ubuntu.com/ubuntu-ports noble InRelease
|
||||
2026-03-25T12:11:34.8884900Z Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease
|
||||
2026-03-25T12:11:34.9713799Z Hit:5 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease
|
||||
2026-03-25T12:11:35.3400058Z Hit:6 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease
|
||||
2026-03-25T12:11:35.3911975Z Fetched 21.2 kB in 10s (2,048 B/s)
|
||||
2026-03-25T12:11:36.6884300Z Reading package lists...
|
||||
2026-03-25T12:11:38.0360973Z Reading package lists...
|
||||
2026-03-25T12:11:38.4220315Z Building dependency tree...
|
||||
2026-03-25T12:11:38.4221340Z Reading state information...
|
||||
2026-03-25T12:11:39.0941013Z The following additional packages will be installed:
|
||||
2026-03-25T12:11:39.0960465Z libpython3-stdlib libpython3.12-minimal libpython3.12-stdlib media-types
|
||||
2026-03-25T12:11:39.0966161Z netbase python3 python3-minimal python3.12 python3.12-minimal
|
||||
2026-03-25T12:11:39.0998340Z Suggested packages:
|
||||
2026-03-25T12:11:39.0998714Z python3-doc python3-tk python3-venv python3.12-venv python3.12-doc
|
||||
2026-03-25T12:11:39.0998888Z binfmt-support
|
||||
2026-03-25T12:11:39.1810008Z The following NEW packages will be installed:
|
||||
2026-03-25T12:11:39.1823006Z libpython3-stdlib libpython3.12-minimal libpython3.12-stdlib media-types
|
||||
2026-03-25T12:11:39.1830570Z netbase nodejs python3 python3-minimal python3.12 python3.12-minimal
|
||||
2026-03-25T12:11:39.2816298Z 0 upgraded, 10 newly installed, 0 to remove and 18 not upgraded.
|
||||
2026-03-25T12:11:39.2816843Z Need to get 42.9 MB of archives.
|
||||
2026-03-25T12:11:39.2817042Z After this operation, 259 MB of additional disk space will be used.
|
||||
2026-03-25T12:11:39.2817215Z Get:1 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 libpython3.12-minimal arm64 3.12.3-1ubuntu0.12 [834 kB]
|
||||
2026-03-25T12:11:39.3903078Z Get:2 https://deb.nodesource.com/node_22.x nodistro/main arm64 nodejs arm64 22.22.1-1nodesource1 [37.0 MB]
|
||||
2026-03-25T12:11:39.4744824Z Get:3 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3.12-minimal arm64 3.12.3-1ubuntu0.12 [2,252 kB]
|
||||
2026-03-25T12:11:39.5305320Z Get:4 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3-minimal arm64 3.12.3-0ubuntu2.1 [27.4 kB]
|
||||
2026-03-25T12:11:39.5309325Z Get:5 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 media-types all 10.1.0 [27.5 kB]
|
||||
2026-03-25T12:11:39.5330600Z Get:6 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 netbase all 6.4 [13.1 kB]
|
||||
2026-03-25T12:11:39.5333740Z Get:7 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 libpython3.12-stdlib arm64 3.12.3-1ubuntu0.12 [2,037 kB]
|
||||
2026-03-25T12:11:39.5758776Z Get:8 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3.12 arm64 3.12.3-1ubuntu0.12 [651 kB]
|
||||
2026-03-25T12:11:39.5835423Z Get:9 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 libpython3-stdlib arm64 3.12.3-0ubuntu2.1 [10.1 kB]
|
||||
2026-03-25T12:11:39.5839250Z Get:10 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 python3 arm64 3.12.3-0ubuntu2.1 [23.0 kB]
|
||||
2026-03-25T12:11:40.4405153Z debconf: delaying package configuration, since apt-utils is not installed
|
||||
2026-03-25T12:11:40.5017505Z Fetched 42.9 MB in 1s (45.9 MB/s)
|
||||
2026-03-25T12:11:40.5588037Z Selecting previously unselected package libpython3.12-minimal:arm64.
|
||||
2026-03-25T12:11:40.5615337Z (Reading database ...
|
||||
(Reading database ... 5%
|
||||
(Reading database ... 10%
|
||||
(Reading database ... 15%
|
||||
(Reading database ... 20%
|
||||
(Reading database ... 25%
|
||||
(Reading database ... 30%
|
||||
(Reading database ... 35%
|
||||
(Reading database ... 40%
|
||||
(Reading database ... 45%
|
||||
(Reading database ... 50%
|
||||
(Reading database ... 55%
|
||||
(Reading database ... 60%
|
||||
(Reading database ... 65%
|
||||
(Reading database ... 70%
|
||||
(Reading database ... 75%
|
||||
(Reading database ... 80%
|
||||
(Reading database ... 85%
|
||||
(Reading database ... 90%
|
||||
(Reading database ... 95%
|
||||
(Reading database ... 100%
|
||||
(Reading database ... 10203 files and directories currently installed.)
|
||||
2026-03-25T12:11:40.5626546Z Preparing to unpack .../libpython3.12-minimal_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:11:40.5712786Z Unpacking libpython3.12-minimal:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:40.7085040Z Selecting previously unselected package python3.12-minimal.
|
||||
2026-03-25T12:11:40.7096226Z Preparing to unpack .../python3.12-minimal_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:11:40.7201720Z Unpacking python3.12-minimal (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:40.8107975Z Setting up libpython3.12-minimal:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:40.8653673Z Setting up python3.12-minimal (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:41.9358111Z Selecting previously unselected package python3-minimal.
|
||||
2026-03-25T12:11:41.9403560Z (Reading database ...
|
||||
(Reading database ... 5%
|
||||
(Reading database ... 10%
|
||||
(Reading database ... 15%
|
||||
(Reading database ... 20%
|
||||
(Reading database ... 25%
|
||||
(Reading database ... 30%
|
||||
(Reading database ... 35%
|
||||
(Reading database ... 40%
|
||||
(Reading database ... 45%
|
||||
(Reading database ... 50%
|
||||
(Reading database ... 55%
|
||||
(Reading database ... 60%
|
||||
(Reading database ... 65%
|
||||
(Reading database ... 70%
|
||||
(Reading database ... 75%
|
||||
(Reading database ... 80%
|
||||
(Reading database ... 85%
|
||||
(Reading database ... 90%
|
||||
(Reading database ... 95%
|
||||
(Reading database ... 100%
|
||||
(Reading database ... 10514 files and directories currently installed.)
|
||||
2026-03-25T12:11:41.9413961Z Preparing to unpack .../0-python3-minimal_3.12.3-0ubuntu2.1_arm64.deb ...
|
||||
2026-03-25T12:11:41.9559244Z Unpacking python3-minimal (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:11:42.0307879Z Selecting previously unselected package media-types.
|
||||
2026-03-25T12:11:42.0325837Z Preparing to unpack .../1-media-types_10.1.0_all.deb ...
|
||||
2026-03-25T12:11:42.0457110Z Unpacking media-types (10.1.0) ...
|
||||
2026-03-25T12:11:42.1145168Z Selecting previously unselected package netbase.
|
||||
2026-03-25T12:11:42.1159169Z Preparing to unpack .../2-netbase_6.4_all.deb ...
|
||||
2026-03-25T12:11:42.1242901Z Unpacking netbase (6.4) ...
|
||||
2026-03-25T12:11:42.1792726Z Selecting previously unselected package libpython3.12-stdlib:arm64.
|
||||
2026-03-25T12:11:42.1793740Z Preparing to unpack .../3-libpython3.12-stdlib_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:11:42.1880816Z Unpacking libpython3.12-stdlib:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:42.3101823Z Selecting previously unselected package python3.12.
|
||||
2026-03-25T12:11:42.3113846Z Preparing to unpack .../4-python3.12_3.12.3-1ubuntu0.12_arm64.deb ...
|
||||
2026-03-25T12:11:42.3201402Z Unpacking python3.12 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:42.3772802Z Selecting previously unselected package libpython3-stdlib:arm64.
|
||||
2026-03-25T12:11:42.3773312Z Preparing to unpack .../5-libpython3-stdlib_3.12.3-0ubuntu2.1_arm64.deb ...
|
||||
2026-03-25T12:11:42.3858444Z Unpacking libpython3-stdlib:arm64 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:11:42.4387555Z Setting up python3-minimal (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:11:42.6614832Z Selecting previously unselected package python3.
|
||||
2026-03-25T12:11:42.6659520Z (Reading database ...
|
||||
(Reading database ... 5%
|
||||
(Reading database ... 10%
|
||||
(Reading database ... 15%
|
||||
(Reading database ... 20%
|
||||
(Reading database ... 25%
|
||||
(Reading database ... 30%
|
||||
(Reading database ... 35%
|
||||
(Reading database ... 40%
|
||||
(Reading database ... 45%
|
||||
(Reading database ... 50%
|
||||
(Reading database ... 55%
|
||||
(Reading database ... 60%
|
||||
(Reading database ... 65%
|
||||
(Reading database ... 70%
|
||||
(Reading database ... 75%
|
||||
(Reading database ... 80%
|
||||
(Reading database ... 85%
|
||||
(Reading database ... 90%
|
||||
(Reading database ... 95%
|
||||
(Reading database ... 100%
|
||||
(Reading database ... 10955 files and directories currently installed.)
|
||||
2026-03-25T12:11:42.6670209Z Preparing to unpack .../python3_3.12.3-0ubuntu2.1_arm64.deb ...
|
||||
2026-03-25T12:11:42.6782622Z Unpacking python3 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:11:42.7536426Z Selecting previously unselected package nodejs.
|
||||
2026-03-25T12:11:42.7543156Z Preparing to unpack .../nodejs_22.22.1-1nodesource1_arm64.deb ...
|
||||
2026-03-25T12:11:42.7640439Z Unpacking nodejs (22.22.1-1nodesource1) ...
|
||||
2026-03-25T12:11:44.5533574Z Setting up media-types (10.1.0) ...
|
||||
2026-03-25T12:11:44.6116920Z Setting up netbase (6.4) ...
|
||||
2026-03-25T12:11:44.7126932Z Setting up libpython3.12-stdlib:arm64 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:44.7537181Z Setting up python3.12 (3.12.3-1ubuntu0.12) ...
|
||||
2026-03-25T12:11:45.9736127Z Setting up libpython3-stdlib:arm64 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:11:45.9995094Z Setting up python3 (3.12.3-0ubuntu2.1) ...
|
||||
2026-03-25T12:11:46.0202105Z running python rtupdate hooks for python3.12...
|
||||
2026-03-25T12:11:46.0202749Z running python post-rtupdate hooks for python3.12...
|
||||
2026-03-25T12:11:46.1647503Z Setting up nodejs (22.22.1-1nodesource1) ...
|
||||
2026-03-25T12:11:46.6406508Z ::add-matcher::/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/problem-matcher.json
|
||||
2026-03-25T12:11:46.6410499Z Syncing repository: ***/***3-server
|
||||
2026-03-25T12:11:46.6414140Z ::group::Getting Git version info
|
||||
2026-03-25T12:11:46.6414740Z Working directory is '/workspace/***/***3-server'
|
||||
2026-03-25T12:11:46.6451296Z [command]/usr/bin/git version
|
||||
2026-03-25T12:11:46.6498415Z git version 2.43.0
|
||||
2026-03-25T12:11:46.6528997Z ::endgroup::
|
||||
2026-03-25T12:11:46.6559915Z Temporarily overriding HOME='/tmp/b1eaf53a-eeb8-4afe-bb1b-963811cdef44' before making global git config changes
|
||||
2026-03-25T12:11:46.6560661Z Adding repository directory to the temporary git global config as a safe directory
|
||||
2026-03-25T12:11:46.6569698Z [command]/usr/bin/git config --global --add safe.directory /workspace/***/***3-server
|
||||
2026-03-25T12:11:46.6604482Z Deleting the contents of '/workspace/***/***3-server'
|
||||
2026-03-25T12:11:46.6609411Z ::group::Initializing the repository
|
||||
2026-03-25T12:11:46.6614132Z [command]/usr/bin/git init /workspace/***/***3-server
|
||||
2026-03-25T12:11:46.6647197Z hint: Using 'master' as the name for the initial branch. This default branch name
|
||||
2026-03-25T12:11:46.6647668Z hint: is subject to change. To configure the initial branch name to use in all
|
||||
2026-03-25T12:11:46.6647876Z hint: of your new repositories, which will suppress this warning, call:
|
||||
2026-03-25T12:11:46.6648031Z hint:
|
||||
2026-03-25T12:11:46.6648344Z hint: git config --global init.defaultBranch <name>
|
||||
2026-03-25T12:11:46.6648485Z hint:
|
||||
2026-03-25T12:11:46.6648606Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
|
||||
2026-03-25T12:11:46.6648745Z hint: 'development'. The just-created branch can be renamed via this command:
|
||||
2026-03-25T12:11:46.6648895Z hint:
|
||||
2026-03-25T12:11:46.6649012Z hint: git branch -m <name>
|
||||
2026-03-25T12:11:46.6655581Z Initialized empty Git repository in /workspace/***/***3-server/.git/
|
||||
2026-03-25T12:11:46.6673484Z [command]/usr/bin/git remote add origin https://gitea.siegeln.net/***/***3-server
|
||||
2026-03-25T12:11:46.6705963Z ::endgroup::
|
||||
2026-03-25T12:11:46.6706313Z ::group::Disabling automatic garbage collection
|
||||
2026-03-25T12:11:46.6711562Z [command]/usr/bin/git config --local gc.auto 0
|
||||
2026-03-25T12:11:46.6742385Z ::endgroup::
|
||||
2026-03-25T12:11:46.6742714Z ::group::Setting up auth
|
||||
2026-03-25T12:11:46.6753437Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand
|
||||
2026-03-25T12:11:46.6785919Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
|
||||
2026-03-25T12:11:46.6964779Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/gitea\.siegeln\.net\/\.extraheader
|
||||
2026-03-25T12:11:46.6996612Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/gitea\.siegeln\.net\/\.extraheader' && git config --local --unset-all 'http.https://gitea.siegeln.net/.extraheader' || :"
|
||||
2026-03-25T12:11:46.7177068Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir:
|
||||
2026-03-25T12:11:46.7209537Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url
|
||||
2026-03-25T12:11:46.7386392Z [command]/usr/bin/git config --local http.https://gitea.siegeln.net/.extraheader AUTHORIZATION: basic ***
|
||||
2026-03-25T12:11:46.7420093Z ::endgroup::
|
||||
2026-03-25T12:11:46.7420425Z ::group::Fetching the repository
|
||||
2026-03-25T12:11:46.7444311Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +7fd55ea8ba92830ddad59dd3fe3d9a0516254946:refs/remotes/origin/main
|
||||
2026-03-25T12:11:47.0408153Z From https://gitea.siegeln.net/***/***3-server
|
||||
2026-03-25T12:11:47.0408743Z * [new ref] 7fd55ea8ba92830ddad59dd3fe3d9a0516254946 -> origin/main
|
||||
2026-03-25T12:11:47.0425449Z ::endgroup::
|
||||
2026-03-25T12:11:47.0425869Z ::group::Determining the checkout info
|
||||
2026-03-25T12:11:47.0428330Z ::endgroup::
|
||||
2026-03-25T12:11:47.0434686Z [command]/usr/bin/git sparse-checkout disable
|
||||
2026-03-25T12:11:47.0468001Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig
|
||||
2026-03-25T12:11:47.0494380Z ::group::Checking out the ref
|
||||
2026-03-25T12:11:47.0499182Z [command]/usr/bin/git checkout --progress --force -B main refs/remotes/origin/main
|
||||
2026-03-25T12:11:47.0799235Z Switched to a new branch 'main'
|
||||
2026-03-25T12:11:47.0801974Z branch 'main' set up to track 'origin/main'.
|
||||
2026-03-25T12:11:47.0807273Z ::endgroup::
|
||||
2026-03-25T12:11:47.0851193Z [command]/usr/bin/git log -1 --format=%H
|
||||
2026-03-25T12:11:47.0873738Z 7fd55ea8ba92830ddad59dd3fe3d9a0516254946
|
||||
2026-03-25T12:11:47.0889884Z ::remove-matcher owner=checkout-git::
|
||||
2026-03-25T12:11:47.9031005Z (node:674) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
|
||||
2026-03-25T12:11:47.9031552Z (Use `node --trace-deprecation ...` to show where the warning was created)
|
||||
2026-03-25T12:11:48.7715362Z Cache Size: ~352 MB (368583412 B)
|
||||
2026-03-25T12:11:48.7746030Z [command]/usr/bin/tar -xf /tmp/0d28713b-77ea-40d9-ac8d-19c5adfc0c5d/cache.tgz -P -C /workspace/***/***3-server -z
|
||||
2026-03-25T12:11:52.3545912Z Cache restored successfully
|
||||
2026-03-25T12:11:52.3981691Z Cache restored from key: linux-maven-9dd3df74bd8aba32251bb49c59e820a2aa73582b0f09ca6d6e930542cfb05391
|
||||
2026-03-25T12:11:59.0472225Z
|
||||
2026-03-25T12:11:59.0473793Z added 213 packages, and audited 214 packages in 6s
|
||||
2026-03-25T12:11:59.0474257Z
|
||||
2026-03-25T12:11:59.0474791Z 58 packages are looking for funding
|
||||
2026-03-25T12:11:59.0474965Z run `npm fund` for details
|
||||
2026-03-25T12:11:59.0493731Z
|
||||
2026-03-25T12:11:59.0494280Z found 0 vulnerabilities
|
||||
2026-03-25T12:11:59.2308378Z
|
||||
2026-03-25T12:11:59.2309072Z > ui@0.0.0 build
|
||||
2026-03-25T12:11:59.2309247Z > tsc -p tsconfig.app.json --noEmit && vite build
|
||||
2026-03-25T12:11:59.2309401Z
|
||||
2026-03-25T12:12:05.6744989Z [36mvite v8.0.1 [32mbuilding client environment for production...[36m[39m
|
||||
2026-03-25T12:12:06.3075689Z [2K
|
||||
transforming...✓ 129 modules transformed.
|
||||
2026-03-25T12:12:06.4496363Z rendering chunks...
|
||||
2026-03-25T12:12:06.8014982Z computing gzip size...
|
||||
2026-03-25T12:12:06.8346038Z dist/index.html 0.94 kB │ gzip: 0.41 kB
|
||||
2026-03-25T12:12:06.8346660Z dist/assets/dm-sans-600-Aqo67rzb.woff2 14.14 kB
|
||||
2026-03-25T12:12:06.8346848Z dist/assets/dm-sans-400-CW0RaeGs.woff2 14.20 kB
|
||||
2026-03-25T12:12:06.8347005Z dist/assets/dm-sans-500-B9HHJjqV.woff2 14.30 kB
|
||||
2026-03-25T12:12:06.8347232Z dist/assets/dm-sans-700-DvUfVpUG.woff2 14.34 kB
|
||||
2026-03-25T12:12:06.8347378Z dist/assets/dm-sans-400-italic-DRLHr0TN.woff2 15.14 kB
|
||||
2026-03-25T12:12:06.8347520Z dist/assets/jetbrains-mono-400-V6pRDFza.woff2 21.16 kB
|
||||
2026-03-25T12:12:06.8347661Z dist/assets/jetbrains-mono-500-BWZEU5yA.woff2 21.83 kB
|
||||
2026-03-25T12:12:06.8347799Z dist/assets/jetbrains-mono-600-C8RAYTDA.woff2 21.86 kB
|
||||
2026-03-25T12:12:06.8347942Z dist/assets/OidcConfigPage-Bq9_k9q2.css 0.61 kB │ gzip: 0.31 kB
|
||||
2026-03-25T12:12:06.8348093Z dist/assets/AuditLogPage-wcUcdzwn.css 1.19 kB │ gzip: 0.56 kB
|
||||
2026-03-25T12:12:06.8348237Z dist/assets/RoutesMetrics-DaUBduav.css 1.46 kB │ gzip: 0.62 kB
|
||||
2026-03-25T12:12:06.8348390Z dist/assets/RbacPage-5iGc4gch.css 1.98 kB │ gzip: 0.69 kB
|
||||
2026-03-25T12:12:06.8348537Z dist/assets/AgentInstance--Z0IKMF0.css 2.50 kB │ gzip: 0.75 kB
|
||||
2026-03-25T12:12:06.8348713Z dist/assets/AgentHealth-y0_55tmK.css 3.42 kB │ gzip: 1.04 kB
|
||||
2026-03-25T12:12:06.8348861Z dist/assets/Dashboard-BYYJXEtx.css 3.64 kB │ gzip: 1.13 kB
|
||||
2026-03-25T12:12:06.8349210Z dist/assets/RouteDetail-CjL6IMio.css 4.26 kB │ gzip: 1.09 kB
|
||||
2026-03-25T12:12:06.8349349Z dist/assets/ExchangeDetail-VcItziWb.css 6.78 kB │ gzip: 1.67 kB
|
||||
2026-03-25T12:12:06.8349503Z dist/assets/index-CXegvd7B.css 111.14 kB │ gzip: 18.21 kB
|
||||
2026-03-25T12:12:06.8349645Z dist/assets/SwaggerPage-CH1CJXTy.css 176.70 kB │ gzip: 26.18 kB
|
||||
2026-03-25T12:12:06.8349786Z dist/assets/use-refresh-interval-CBD44ngw.js 0.19 kB │ gzip: 0.15 kB
|
||||
2026-03-25T12:12:06.8349931Z dist/assets/admin-api-Dldh4fYm.js 0.49 kB │ gzip: 0.35 kB
|
||||
2026-03-25T12:12:06.8350083Z dist/assets/AdminLayout-CKg4TLhS.js 0.55 kB │ gzip: 0.34 kB
|
||||
2026-03-25T12:12:06.8350288Z dist/assets/chunk-B3K2TuZy.js 0.55 kB │ gzip: 0.35 kB
|
||||
2026-03-25T12:12:06.8350433Z dist/assets/agent-metrics-DI4HNJNm.js 0.59 kB │ gzip: 0.42 kB
|
||||
2026-03-25T12:12:06.8350571Z dist/assets/SwaggerPage-ZwDCrEj1.js 0.87 kB │ gzip: 0.54 kB
|
||||
2026-03-25T12:12:06.8350710Z dist/assets/diagram-mapping-zsjZZKRP.js 1.33 kB │ gzip: 0.68 kB
|
||||
2026-03-25T12:12:06.8350879Z dist/assets/useMutation-CjGnzIYg.js 2.31 kB │ gzip: 0.97 kB
|
||||
2026-03-25T12:12:06.8351033Z dist/assets/OpenSearchAdminPage-WnM4gikq.js 2.92 kB │ gzip: 1.17 kB
|
||||
2026-03-25T12:12:06.8351173Z dist/assets/DatabaseAdminPage-CclQJ0Ok.js 3.09 kB │ gzip: 1.16 kB
|
||||
2026-03-25T12:12:06.8351328Z dist/assets/AuditLogPage-4V3oK99a.js 4.53 kB │ gzip: 1.70 kB
|
||||
2026-03-25T12:12:06.8351477Z dist/assets/OidcConfigPage-D0YsPv3U.js 5.38 kB │ gzip: 1.90 kB
|
||||
2026-03-25T12:12:06.8351642Z dist/assets/RoutesMetrics-5XMqf33P.js 5.98 kB │ gzip: 2.10 kB
|
||||
2026-03-25T12:12:06.8351804Z dist/assets/jsx-runtime-DdsoV0Vr.js 9.07 kB │ gzip: 3.52 kB
|
||||
2026-03-25T12:12:06.8351939Z dist/assets/AgentInstance-Bzs6lRBr.js 9.86 kB │ gzip: 2.83 kB
|
||||
2026-03-25T12:12:06.8352077Z dist/assets/Dashboard-C23kCbUs.js 10.16 kB │ gzip: 3.28 kB
|
||||
2026-03-25T12:12:06.8352236Z dist/assets/auth-store-DCBo9Ans.js 10.34 kB │ gzip: 3.79 kB
|
||||
2026-03-25T12:12:06.8352399Z dist/assets/AgentHealth-DwOu1dM9.js 11.36 kB │ gzip: 3.28 kB
|
||||
2026-03-25T12:12:06.8352543Z dist/assets/RouteDetail-l02TOYKF.js 12.35 kB │ gzip: 4.07 kB
|
||||
2026-03-25T12:12:06.8352685Z dist/assets/ExchangeDetail-BLpXn9YT.js 14.56 kB │ gzip: 4.25 kB
|
||||
2026-03-25T12:12:06.8352827Z dist/assets/useQuery-oR6RVwVH.js 22.31 kB │ gzip: 7.37 kB
|
||||
2026-03-25T12:12:06.8352968Z dist/assets/RbacPage-oz3jLDxG.js 25.49 kB │ gzip: 5.57 kB
|
||||
2026-03-25T12:12:06.8353149Z dist/assets/index-COBtXOCg.js 205.44 kB │ gzip: 64.73 kB
|
||||
2026-03-25T12:12:06.8353297Z dist/assets/index.es-BideRJQn.js 216.16 kB │ gzip: 66.22 kB
|
||||
2026-03-25T12:12:06.8353439Z dist/assets/swagger-ui-bundle-BoDRmAdn.js 1,371.99 kB │ gzip: 390.77 kB
|
||||
2026-03-25T12:12:06.8353572Z
|
||||
2026-03-25T12:12:06.8353740Z [32m✓ built in 1.16s[39m
|
||||
2026-03-25T12:12:06.8356107Z [33m[plugin builtin:vite-reporter]
|
||||
2026-03-25T12:12:06.8356389Z (!) Some chunks are larger than 500 kB after minification. Consider:
|
||||
2026-03-25T12:12:06.8356581Z - Using dynamic import() to code-split the application
|
||||
2026-03-25T12:12:06.8356726Z - Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
|
||||
2026-03-25T12:12:06.8356917Z - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.[39m
|
||||
2026-03-25T12:12:08.8592085Z [INFO] Scanning for projects...
|
||||
2026-03-25T12:12:09.3994902Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:12:09.3995662Z [INFO] Reactor Build Order:
|
||||
2026-03-25T12:12:09.3995968Z [INFO]
|
||||
2026-03-25T12:12:09.4015044Z [INFO] Cameleer3 Server Parent [pom]
|
||||
2026-03-25T12:12:09.4016943Z [INFO] Cameleer3 Server Core [jar]
|
||||
2026-03-25T12:12:09.4020267Z [INFO] Cameleer3 Server App [jar]
|
||||
2026-03-25T12:12:09.4204897Z [INFO]
|
||||
2026-03-25T12:12:09.4206974Z [INFO] ---------------< com.***3:***3-server-parent >----------------
|
||||
2026-03-25T12:12:09.4209665Z [INFO] Building Cameleer3 Server Parent 1.0-SNAPSHOT [1/3]
|
||||
2026-03-25T12:12:09.4213444Z [INFO] from pom.xml
|
||||
2026-03-25T12:12:09.4215425Z [INFO] --------------------------------[ pom ]---------------------------------
|
||||
2026-03-25T12:12:09.4758836Z [INFO]
|
||||
2026-03-25T12:12:09.4761722Z [INFO] --- clean:3.4.1:clean (default-clean) @ ***3-server-parent ---
|
||||
2026-03-25T12:12:09.5985349Z [INFO]
|
||||
2026-03-25T12:12:09.5998691Z [INFO] ----------------< com.***3:***3-server-core >-----------------
|
||||
2026-03-25T12:12:09.5999367Z [INFO] Building Cameleer3 Server Core 1.0-SNAPSHOT [2/3]
|
||||
2026-03-25T12:12:09.5999608Z [INFO] from ***3-server-core/pom.xml
|
||||
2026-03-25T12:12:09.5999870Z [INFO] --------------------------------[ jar ]---------------------------------
|
||||
2026-03-25T12:12:09.9264378Z [INFO]
|
||||
2026-03-25T12:12:09.9265012Z [INFO] --- clean:3.4.1:clean (default-clean) @ ***3-server-core ---
|
||||
2026-03-25T12:12:09.9294155Z [INFO]
|
||||
2026-03-25T12:12:09.9294802Z [INFO] --- resources:3.3.1:resources (default-resources) @ ***3-server-core ---
|
||||
2026-03-25T12:12:10.1415949Z [INFO] skip non existing resourceDirectory /workspace/***/***3-server/***3-server-core/src/main/resources
|
||||
2026-03-25T12:12:10.1416720Z [INFO] skip non existing resourceDirectory /workspace/***/***3-server/***3-server-core/src/main/resources
|
||||
2026-03-25T12:12:10.1433945Z [INFO]
|
||||
2026-03-25T12:12:10.1434657Z [INFO] --- compiler:3.13.0:compile (default-compile) @ ***3-server-core ---
|
||||
2026-03-25T12:12:10.3640938Z [INFO] Recompiling the module because of changed source code.
|
||||
2026-03-25T12:12:10.3728671Z [INFO] Compiling 62 source files with javac [debug parameters release 17] to target/classes
|
||||
2026-03-25T12:12:12.5455820Z [INFO]
|
||||
2026-03-25T12:12:12.5458698Z [INFO] --- resources:3.3.1:testResources (default-testResources) @ ***3-server-core ---
|
||||
2026-03-25T12:12:12.5545314Z [INFO] skip non existing resourceDirectory /workspace/***/***3-server/***3-server-core/src/test/resources
|
||||
2026-03-25T12:12:12.5551351Z [INFO]
|
||||
2026-03-25T12:12:12.5554792Z [INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ ***3-server-core ---
|
||||
2026-03-25T12:12:12.5684758Z [INFO] Recompiling the module because of changed dependency.
|
||||
2026-03-25T12:12:12.5695664Z [INFO] Compiling 3 source files with javac [debug parameters release 17] to target/test-classes
|
||||
2026-03-25T12:12:13.8627365Z [INFO]
|
||||
2026-03-25T12:12:13.8629352Z [INFO] --- surefire:3.5.2:test (default-test) @ ***3-server-core ---
|
||||
2026-03-25T12:12:14.1009777Z [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
|
||||
2026-03-25T12:12:14.1865594Z [INFO]
|
||||
2026-03-25T12:12:14.1866264Z [INFO] -------------------------------------------------------
|
||||
2026-03-25T12:12:14.1866544Z [INFO] T E S T S
|
||||
2026-03-25T12:12:14.1867788Z [INFO] -------------------------------------------------------
|
||||
2026-03-25T12:12:15.2551472Z [INFO] Running com.***3.server.core.detail.TreeReconstructionTest
|
||||
2026-03-25T12:12:15.5724418Z OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
|
||||
2026-03-25T12:12:16.6140778Z [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.313 s -- in com.***3.server.core.detail.TreeReconstructionTest
|
||||
2026-03-25T12:12:16.6183288Z [INFO] Running com.***3.server.core.agent.AgentRegistryServiceTest
|
||||
2026-03-25T12:12:16.6190775Z [INFO] Running com.***3.server.core.agent.AgentRegistryServiceTest$Commands
|
||||
2026-03-25T12:12:16.6263438Z SLF4J(W): No SLF4J providers were found.
|
||||
2026-03-25T12:12:16.6264169Z SLF4J(W): Defaulting to no-operation (NOP) logger implementation
|
||||
2026-03-25T12:12:16.6264406Z SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
|
||||
2026-03-25T12:12:16.6999152Z [INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.104 s -- in com.***3.server.core.agent.AgentRegistryServiceTest$Commands
|
||||
2026-03-25T12:12:16.7024457Z [INFO] Running com.***3.server.core.agent.AgentRegistryServiceTest$Queries
|
||||
2026-03-25T12:12:16.7186966Z [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.028 s -- in com.***3.server.core.agent.AgentRegistryServiceTest$Queries
|
||||
2026-03-25T12:12:16.7189398Z [INFO] Running com.***3.server.core.agent.AgentRegistryServiceTest$LifecycleTransitions
|
||||
2026-03-25T12:12:16.7933600Z [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.066 s -- in com.***3.server.core.agent.AgentRegistryServiceTest$LifecycleTransitions
|
||||
2026-03-25T12:12:16.7944637Z [INFO] Running com.***3.server.core.agent.AgentRegistryServiceTest$Heartbeat
|
||||
2026-03-25T12:12:16.8157231Z [INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 s -- in com.***3.server.core.agent.AgentRegistryServiceTest$Heartbeat
|
||||
2026-03-25T12:12:16.8190493Z [INFO] Running com.***3.server.core.agent.AgentRegistryServiceTest$Registration
|
||||
2026-03-25T12:12:16.8289762Z [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.020 s -- in com.***3.server.core.agent.AgentRegistryServiceTest$Registration
|
||||
2026-03-25T12:12:16.8405658Z [INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.254 s -- in com.***3.server.core.agent.AgentRegistryServiceTest
|
||||
2026-03-25T12:12:16.8433351Z [INFO] Running com.***3.server.core.ingestion.WriteBufferTest
|
||||
2026-03-25T12:12:16.8748334Z [INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.041 s -- in com.***3.server.core.ingestion.WriteBufferTest
|
||||
2026-03-25T12:12:16.8990584Z [INFO]
|
||||
2026-03-25T12:12:16.8991202Z [INFO] Results:
|
||||
2026-03-25T12:12:16.8991549Z [INFO]
|
||||
2026-03-25T12:12:16.8996481Z [INFO] Tests run: 37, Failures: 0, Errors: 0, Skipped: 0
|
||||
2026-03-25T12:12:16.8997041Z [INFO]
|
||||
2026-03-25T12:12:16.9036244Z [INFO]
|
||||
2026-03-25T12:12:16.9036930Z [INFO] --- jar:3.4.2:jar (default-jar) @ ***3-server-core ---
|
||||
2026-03-25T12:12:17.3553610Z [INFO] Building jar: /workspace/***/***3-server/***3-server-core/target/***3-server-core-1.0-SNAPSHOT.jar
|
||||
2026-03-25T12:12:17.4343059Z [INFO]
|
||||
2026-03-25T12:12:17.4343761Z [INFO] -----------------< com.***3:***3-server-app >-----------------
|
||||
2026-03-25T12:12:17.4344006Z [INFO] Building Cameleer3 Server App 1.0-SNAPSHOT [3/3]
|
||||
2026-03-25T12:12:17.4346242Z [INFO] from ***3-server-app/pom.xml
|
||||
2026-03-25T12:12:17.4347591Z [INFO] --------------------------------[ jar ]---------------------------------
|
||||
2026-03-25T12:12:18.7737910Z [INFO]
|
||||
2026-03-25T12:12:18.7738533Z [INFO] --- clean:3.4.1:clean (default-clean) @ ***3-server-app ---
|
||||
2026-03-25T12:12:18.7770445Z [INFO]
|
||||
2026-03-25T12:12:18.7771175Z [INFO] --- resources:3.3.1:copy-resources (copy-ui-dist) @ ***3-server-app ---
|
||||
2026-03-25T12:12:18.7925285Z [INFO] Copying 48 resources from ../ui/dist to target/classes/static
|
||||
2026-03-25T12:12:18.8155069Z [INFO]
|
||||
2026-03-25T12:12:18.8156631Z [INFO] --- resources:3.3.1:resources (default-resources) @ ***3-server-app ---
|
||||
2026-03-25T12:12:18.8197148Z [INFO] Copying 1 resource from src/main/resources to target/classes
|
||||
2026-03-25T12:12:18.8639638Z [INFO] Copying 4 resources from src/main/resources to target/classes
|
||||
2026-03-25T12:12:18.8640395Z [INFO]
|
||||
2026-03-25T12:12:18.8640613Z [INFO] --- compiler:3.13.0:compile (default-compile) @ ***3-server-app ---
|
||||
2026-03-25T12:12:18.8844470Z [INFO] Recompiling the module because of changed dependency.
|
||||
2026-03-25T12:12:18.8848893Z [INFO] Compiling 104 source files with javac [debug parameters release 17] to target/classes
|
||||
2026-03-25T12:12:21.8486343Z [INFO] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/security/OidcTokenExchanger.java: /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/security/OidcTokenExchanger.java uses or overrides a deprecated API.
|
||||
2026-03-25T12:12:21.8487241Z [INFO] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/security/OidcTokenExchanger.java: Recompile with -Xlint:deprecation for details.
|
||||
2026-03-25T12:12:21.8487541Z [INFO] -------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8487894Z [ERROR] COMPILATION ERROR :
|
||||
2026-03-25T12:12:21.8488157Z [INFO] -------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8488337Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/controller/LogIngestionController.java:[3,34] cannot find symbol
|
||||
2026-03-25T12:12:21.8488560Z symbol: class LogBatch
|
||||
2026-03-25T12:12:21.8488701Z location: package com.***3.common.model
|
||||
2026-03-25T12:12:21.8488975Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[3,34] cannot find symbol
|
||||
2026-03-25T12:12:21.8489229Z symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8489369Z location: package com.***3.common.model
|
||||
2026-03-25T12:12:21.8489585Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/controller/LogIngestionController.java:[40,57] cannot find symbol
|
||||
2026-03-25T12:12:21.8489887Z symbol: class LogBatch
|
||||
2026-03-25T12:12:21.8490086Z location: class com.***3.server.app.controller.LogIngestionController
|
||||
2026-03-25T12:12:21.8490358Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[94,69] cannot find symbol
|
||||
2026-03-25T12:12:21.8490581Z symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8490739Z location: class com.***3.server.app.search.OpenSearchLogIndex
|
||||
2026-03-25T12:12:21.8490944Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[135,39] cannot find symbol
|
||||
2026-03-25T12:12:21.8491180Z symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8491349Z location: class com.***3.server.app.search.OpenSearchLogIndex
|
||||
2026-03-25T12:12:21.8491573Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[102,18] cannot find symbol
|
||||
2026-03-25T12:12:21.8491783Z symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8491947Z location: class com.***3.server.app.search.OpenSearchLogIndex
|
||||
2026-03-25T12:12:21.8492176Z [INFO] 6 errors
|
||||
2026-03-25T12:12:21.8492385Z [INFO] -------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8502503Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8503274Z [INFO] Reactor Summary for Cameleer3 Server Parent 1.0-SNAPSHOT:
|
||||
2026-03-25T12:12:21.8503850Z [INFO]
|
||||
2026-03-25T12:12:21.8524175Z [INFO] Cameleer3 Server Parent ............................ SUCCESS [ 0.178 s]
|
||||
2026-03-25T12:12:21.8524906Z [INFO] Cameleer3 Server Core .............................. SUCCESS [ 7.835 s]
|
||||
2026-03-25T12:12:21.8525142Z [INFO] Cameleer3 Server App ............................... FAILURE [ 4.416 s]
|
||||
2026-03-25T12:12:21.8525315Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8525608Z [INFO] BUILD FAILURE
|
||||
2026-03-25T12:12:21.8525797Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8526046Z [INFO] Total time: 13.033 s
|
||||
2026-03-25T12:12:21.8526239Z [INFO] Finished at: 2026-03-25T12:12:21Z
|
||||
2026-03-25T12:12:21.8526470Z [INFO] ------------------------------------------------------------------------
|
||||
2026-03-25T12:12:21.8539577Z [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project ***3-server-app: Compilation failure: Compilation failure:
|
||||
2026-03-25T12:12:21.8540261Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/controller/LogIngestionController.java:[3,34] cannot find symbol
|
||||
2026-03-25T12:12:21.8540576Z [ERROR] symbol: class LogBatch
|
||||
2026-03-25T12:12:21.8540792Z [ERROR] location: package com.***3.common.model
|
||||
2026-03-25T12:12:21.8541144Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[3,34] cannot find symbol
|
||||
2026-03-25T12:12:21.8541428Z [ERROR] symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8541596Z [ERROR] location: package com.***3.common.model
|
||||
2026-03-25T12:12:21.8541880Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/controller/LogIngestionController.java:[40,57] cannot find symbol
|
||||
2026-03-25T12:12:21.8542178Z [ERROR] symbol: class LogBatch
|
||||
2026-03-25T12:12:21.8542402Z [ERROR] location: class com.***3.server.app.controller.LogIngestionController
|
||||
2026-03-25T12:12:21.8542619Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[94,69] cannot find symbol
|
||||
2026-03-25T12:12:21.8542855Z [ERROR] symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8543160Z [ERROR] location: class com.***3.server.app.search.OpenSearchLogIndex
|
||||
2026-03-25T12:12:21.8543969Z [ERROR] /workspace/***/***3-server/***3-server-app/src/main/java/com/***3/server/app/search/OpenSearchLogIndex.java:[135,39] cannot find symbol
|
||||
2026-03-25T12:12:21.8544642Z [ERROR] symbol: class LogEntry
|
||||
2026-03-25T12:12:21.8544881Z [ERROR] location: class com.***3.server.app.search.OpenSearchLogIndex
|
||||
11
docs/design-system-update-instructions.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Design System Update Instructions
|
||||
|
||||
Status: Both changes below were implemented in `@cameleer/design-system` v0.1.19 and consumed by cameleer3-server.
|
||||
|
||||
## 1. Sidebar: `onNavigate` callback prop — DONE (v0.1.19)
|
||||
|
||||
Sidebar now accepts `onNavigate?: (path: string) => void`. When provided, sidebar calls `onNavigate(path)` instead of using internal `<Link>` navigation. The server UI translates paths to its own URL structure.
|
||||
|
||||
## 2. DataTable: `fillHeight` prop — DONE (v0.1.19)
|
||||
|
||||
DataTable now accepts `fillHeight?: boolean`. When true, the table fills remaining vertical space with a scrollable body, sticky header, and pinned pagination footer.
|
||||
1121
docs/superpowers/plans/2026-03-27-execution-overlay.md
Normal file
1301
docs/superpowers/plans/2026-03-28-navigation-redesign.md
Normal file
417
docs/superpowers/specs/2026-03-27-execution-overlay-design.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# Execution Overlay & Debugger — Design Spec
|
||||
|
||||
**Sub-project:** 2 of 3 (Component → **Execution Overlay** → Page Integration)
|
||||
**Scope:** Overlay real execution data onto the ProcessDiagram component from sub-project 1. Adds node status visualization, per-compound iteration stepping, a tabbed detail panel, and error navigation. Does NOT include page integration — that is sub-project 3.
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
The ProcessDiagram from sub-project 1 shows route topology but cannot display what actually happened during an exchange's execution. Users investigating failures must cross-reference between the diagram and separate execution detail views. There is no way to see which processors were hit, which were skipped, where errors occurred, or what the message looked like at each step.
|
||||
|
||||
## Goal
|
||||
|
||||
Build an `ExecutionDiagram` wrapper component that overlays execution data onto ProcessDiagram, turning it into an "after-the-fact debugger." Users can see the execution path at a glance (green = OK, red = failed, dimmed = skipped), step through loop/split iterations independently, and inspect processor-level details (input/output body, headers, errors, timing) in a tabbed detail panel below the diagram.
|
||||
|
||||
---
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Architecture | Wrapper component (`ExecutionDiagram`) composing `ProcessDiagram` | Keeps topology component pure; execution concerns isolated |
|
||||
| Layout | Top/bottom IDE split (diagram top, detail panel bottom) | Left-to-right diagram needs full width; familiar IDE pattern |
|
||||
| Node status | Tinted backgrounds + status badges | Green tint + checkmark for OK, red tint + ! for failed, dimmed for skipped — scannable at a glance |
|
||||
| Duration display | Badge on each executed node (bottom-right) | Quick bottleneck identification without opening detail panel |
|
||||
| Iteration stepping | Per-compound stepper in header bar | Independent stepping at each nesting level; contextually placed |
|
||||
| Error navigation | Passive highlighting + "Jump to Error" action | Red border + ! badge on failed node; jump action drills into sub-routes if needed |
|
||||
| Cross-route errors | Red border + drill-down arrow on calling node | Communicates failure exists here; arrow signals root cause is deeper |
|
||||
| Detail panel tabs | Info, Headers, Input, Output, Error, Config, Timeline | Comprehensive debugging context |
|
||||
| Error tab visibility | Always visible, grayed out when no error | No layout shift; consistent tab bar |
|
||||
| Reusability | Component usable standalone and embedded | Immediately replaces ExchangeDetail flow view; usable elsewhere |
|
||||
|
||||
---
|
||||
|
||||
## 0. Backend Prerequisites
|
||||
|
||||
### Iteration fields on ProcessorNode
|
||||
|
||||
The `ProcessorExecution` model in `cameleer3-common` has iteration tracking fields (`loopIndex`, `loopSize`, `splitIndex`, `splitSize`, `multicastIndex`), but the server's storage layer and API response model do not surface them. The following changes are needed:
|
||||
|
||||
**Storage:**
|
||||
- Add columns to `processor_records` table: `loop_index`, `loop_size`, `split_index`, `split_size`, `multicast_index` (all nullable integers)
|
||||
- Flyway migration to add columns
|
||||
- Update `ExecutionStore` to persist and read these fields
|
||||
|
||||
**Detail model:**
|
||||
- Add fields to `ProcessorNode.java`: `loopIndex`, `loopSize`, `splitIndex`, `splitSize`, `multicastIndex`
|
||||
- Update `DetailService.buildTree()` to populate them from storage
|
||||
|
||||
**API:**
|
||||
- Regenerate `openapi.json` and `schema.d.ts` to include the new fields
|
||||
|
||||
### Snapshot endpoint: accept processorId
|
||||
|
||||
The current snapshot endpoint `GET /executions/{id}/processors/{index}/snapshot` uses a positional index into the flat processor list. This is fragile when the tree structure changes. Add an alternative parameter:
|
||||
|
||||
- `GET /executions/{id}/processors/by-id/{processorId}/snapshot` — fetches snapshot by processor ID
|
||||
- Add corresponding `useProcessorSnapshotById(executionId, processorId)` hook on the frontend
|
||||
|
||||
### Diagram loading by content hash
|
||||
|
||||
`ExecutionDetail` includes `diagramContentHash` linking to the diagram version active during the execution. The existing `useDiagramLayout(contentHash, direction)` hook already supports loading by content hash. The `ExecutionDiagram` wrapper uses this path instead of `useDiagramByRoute(application, routeId)`.
|
||||
|
||||
---
|
||||
|
||||
## 1. ExecutionDiagram Wrapper Component
|
||||
|
||||
### Location
|
||||
|
||||
```
|
||||
ui/src/components/ExecutionDiagram/
|
||||
├── ExecutionDiagram.tsx # Root: top/bottom split, orchestrates overlay + detail panel
|
||||
├── ExecutionDiagram.module.css # Layout styles (splitter, exchange bar, panel)
|
||||
├── useExecutionOverlay.ts # Hook: maps execution data → node overlay state
|
||||
├── useIterationState.ts # Hook: per-compound iteration tracking
|
||||
├── ExecutionContext.tsx # React context: shares execution data + iteration state
|
||||
├── DetailPanel.tsx # Bottom panel: tabs container
|
||||
├── tabs/InfoTab.tsx # Processor metadata + attributes
|
||||
├── tabs/HeadersTab.tsx # Input/output headers side-by-side
|
||||
├── tabs/BodyTab.tsx # Shared: formatted message body (used by Input + Output)
|
||||
├── tabs/ErrorTab.tsx # Exception details + stack trace
|
||||
├── tabs/ConfigTab.tsx # Processor configuration (TODO: agent data)
|
||||
├── tabs/TimelineTab.tsx # Gantt-style processor duration chart
|
||||
├── types.ts # Overlay-specific types
|
||||
└── index.ts # Public exports
|
||||
```
|
||||
|
||||
### Props API
|
||||
|
||||
```typescript
|
||||
interface ExecutionDiagramProps {
|
||||
/** Execution to overlay — fetched externally or by executionId */
|
||||
executionId: string;
|
||||
/** Optional: pre-fetched execution detail (skips internal fetch) */
|
||||
executionDetail?: ExecutionDetail;
|
||||
/** Diagram direction */
|
||||
direction?: 'LR' | 'TB';
|
||||
/** Known route IDs for drill-down resolution */
|
||||
knownRouteIds?: Set<string>;
|
||||
/** Called when user triggers node actions (trace toggle, tap config) */
|
||||
onNodeAction?: (nodeId: string, action: NodeAction) => void;
|
||||
/** Active node configs (trace/tap badges) */
|
||||
nodeConfigs?: Map<string, NodeConfig>;
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
1. Fetches `ExecutionDetail` via `useExecutionDetail(executionId)` (or uses pre-fetched prop)
|
||||
2. Extracts the `diagramContentHash` from the execution to load the correct diagram version
|
||||
3. Maps processor execution tree to diagram node IDs (processor IDs match diagram node IDs)
|
||||
4. Passes overlay data to ProcessDiagram via new overlay props
|
||||
5. Manages selected node state, detail panel content, and iteration stepping
|
||||
|
||||
---
|
||||
|
||||
## 2. ProcessDiagram Overlay Props Extension
|
||||
|
||||
The existing `ProcessDiagramProps` gains optional overlay props. When absent, the diagram renders in topology-only mode (sub-project 1 behavior). When present, nodes render with execution state.
|
||||
|
||||
```typescript
|
||||
interface ProcessDiagramProps {
|
||||
// ... existing props from sub-project 1 ...
|
||||
|
||||
/** Execution overlay: maps diagram node ID → execution state */
|
||||
executionOverlay?: Map<string, NodeExecutionState>;
|
||||
/** Per-compound iteration state: maps compound node ID → current iteration index */
|
||||
iterationState?: Map<string, number>;
|
||||
/** Called when user changes iteration on a compound stepper */
|
||||
onIterationChange?: (compoundNodeId: string, iterationIndex: number) => void;
|
||||
}
|
||||
|
||||
interface NodeExecutionState {
|
||||
status: 'COMPLETED' | 'FAILED';
|
||||
durationMs: number;
|
||||
/** True if this node's target sub-route failed (for DIRECT/SEDA nodes) */
|
||||
subRouteFailed?: boolean;
|
||||
/** True if trace data (input/output body) is available */
|
||||
hasTraceData?: boolean;
|
||||
/** Loop/split iteration info for the compound containing this node */
|
||||
iterationIndex?: number;
|
||||
iterationCount?: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Node Visual States
|
||||
|
||||
### Executed — Completed
|
||||
|
||||
- Background: green tint (`#F0F9F1`)
|
||||
- Border: 1.5px solid `--success` (`#3D7C47`) + 4px green left accent
|
||||
- Badge: green circle with white checkmark (top-right corner, 16px diameter)
|
||||
- Duration: green text bottom-right (e.g., "5ms")
|
||||
|
||||
### Executed — Failed
|
||||
|
||||
- Background: red tint (`#FDF2F0`)
|
||||
- Border: 2px solid `--error` (`#C0392B`)
|
||||
- Badge: red circle with white `!` (top-right corner, 16px diameter)
|
||||
- Duration: red text bottom-right
|
||||
- Label text turns red, subtitle shows "FAILED"
|
||||
|
||||
### Sub-Route Failure (DIRECT/SEDA node whose target route failed)
|
||||
|
||||
- Same visual as Failed (red tint, red border, red ! badge)
|
||||
- Additional: drill-down arrow icon (bottom-left corner)
|
||||
- "Jump to Error" action on this node auto-drills into the sub-route
|
||||
|
||||
### Not Executed (Skipped)
|
||||
|
||||
- Opacity: 35%
|
||||
- No status badge, no duration badge
|
||||
- Original topology styling (no tint)
|
||||
|
||||
### Compound Node Status
|
||||
|
||||
Compound nodes (CHOICE, LOOP, SPLIT, etc.) derive their status from their children:
|
||||
- If any child failed → compound shows as COMPLETED (the compound itself executed) but the failed child shows individually
|
||||
- The compound does not get its own status badge — only leaf processors do
|
||||
- Compound background tint: subtle green if all children OK, no tint if mixed results
|
||||
|
||||
### RUNNING Executions
|
||||
|
||||
RUNNING executions are out of scope for overlay (see Non-Goals). If the `ExecutionDetail.status` is `RUNNING`, the ExecutionDiagram shows the overlay for processors that have completed so far — completed processors get green/red treatment, processors not yet reached are dimmed. No special "in-progress" visual is needed.
|
||||
|
||||
### Edge States
|
||||
|
||||
- **Traversed edge:** solid, `--success` green (`#3D7C47`), 1.5px stroke
|
||||
- **Not traversed edge:** dashed, `#9CA3AF` gray, 1px stroke
|
||||
|
||||
---
|
||||
|
||||
## 4. Per-Compound Iteration Stepper
|
||||
|
||||
### Placement
|
||||
|
||||
Small control widget embedded in the compound node's header bar (right-aligned). Rendered as part of the `CompoundNode` component when overlay data includes iteration info.
|
||||
|
||||
### Visual
|
||||
|
||||
Semi-transparent background pill inside the purple/colored header:
|
||||
```
|
||||
LOOP [< 3 / 5 >]
|
||||
```
|
||||
Prev/next buttons with the current iteration and total count.
|
||||
|
||||
### Behavior
|
||||
|
||||
- Each compound (LOOP, SPLIT, MULTICAST) tracks its iteration independently via `iterationState` map
|
||||
- Changing iteration updates the overlay data for all children of that compound
|
||||
- Nested compounds: outer loop at iteration 2, inner split at branch 1 — independent
|
||||
- CHOICE compounds: no stepper. The taken branch renders with execution state; untaken branches are dimmed
|
||||
- Keyboard: left/right arrow keys step when compound is hovered
|
||||
- Detail panel syncs: selecting a processor inside a loop shows that iteration's snapshot data
|
||||
|
||||
### Data Flow
|
||||
|
||||
The `useIterationState` hook maintains a `Map<compoundNodeId, currentIndex>`. When an iteration changes:
|
||||
1. The hook recalculates which `ProcessorExecution` children correspond to the selected iteration (using `loopIndex`, `splitIndex`, or `multicastIndex` fields)
|
||||
2. Rebuilds the `executionOverlay` map for that compound's children
|
||||
3. ProcessDiagram re-renders with updated overlay
|
||||
|
||||
---
|
||||
|
||||
## 5. Exchange Summary Bar
|
||||
|
||||
A thin bar above the diagram showing exchange-level information:
|
||||
|
||||
- Exchange ID (monospace, copyable)
|
||||
- Status badge (COMPLETED green, FAILED red)
|
||||
- Application / route ID
|
||||
- Total duration
|
||||
- "Jump to Error" button (only for FAILED exchanges) — scrolls diagram to failed node, drills into sub-route if needed
|
||||
|
||||
---
|
||||
|
||||
## 6. Detail Panel
|
||||
|
||||
### Layout
|
||||
|
||||
Below the diagram, separated by a resizable splitter. Default split: 60% diagram / 40% panel. Minimum panel height: 120px. The panel can be collapsed by dragging the splitter to the bottom.
|
||||
|
||||
The panel has:
|
||||
1. **Processor header:** selected processor name, status badge, processor ID, duration
|
||||
2. **Tab bar:** Info | Headers | Input | Output | Error | Config | Timeline
|
||||
3. **Tab content area:** scrollable
|
||||
|
||||
When no processor is selected, the panel shows exchange-level data:
|
||||
- **Info tab:** exchange metadata (exchangeId, correlationId, route, application, total duration, engine level, route-level attributes)
|
||||
- **Headers tab:** route-level input/output headers
|
||||
- **Input tab:** route-level input body
|
||||
- **Output tab:** route-level output body
|
||||
- **Error tab:** route-level error (if failed)
|
||||
- **Config tab:** grayed out (not applicable at exchange level)
|
||||
- **Timeline tab:** Gantt chart of all processors (always available)
|
||||
|
||||
### Tab: Info
|
||||
|
||||
Grid layout showing processor metadata:
|
||||
- Processor ID, Type, Status
|
||||
- Start time, End time, Duration
|
||||
- Endpoint URI, Resolved Endpoint URI
|
||||
- Attributes section: tap-extracted attributes as pill badges
|
||||
|
||||
### Tab: Headers
|
||||
|
||||
Side-by-side layout:
|
||||
- Left: Input headers (key/value table)
|
||||
- Right: Output headers (key/value table)
|
||||
- New/changed headers highlighted in green
|
||||
|
||||
Data source: `useProcessorSnapshotById(executionId, processorId)` → `inputHeaders`, `outputHeaders`
|
||||
|
||||
### Tab: Input
|
||||
|
||||
Formatted message body at processor entry:
|
||||
- Auto-detect format (JSON, XML, plain text)
|
||||
- Syntax-highlighted code block (dark theme)
|
||||
- Copy button
|
||||
- Byte size indicator
|
||||
|
||||
Data source: `useProcessorSnapshotById(executionId, processorId)` → `inputBody`
|
||||
|
||||
### Tab: Output
|
||||
|
||||
Same layout as Input tab, showing processor exit body.
|
||||
|
||||
Data source: `useProcessorSnapshotById(executionId, processorId)` → `outputBody`
|
||||
|
||||
### Tab: Error
|
||||
|
||||
Shown for all processors but grayed out when the selected processor has no error.
|
||||
|
||||
When error exists:
|
||||
- Exception type (class name)
|
||||
- Error message
|
||||
- Root cause type + message
|
||||
- Stack trace in monospace block
|
||||
|
||||
Data source: `ProcessorNode.errorMessage`, `ProcessorNode.errorStackTrace` from the execution detail tree
|
||||
|
||||
### Tab: Config
|
||||
|
||||
Processor configuration from the route definition. **TODO:** Requires agent-side work to capture and expose processor configuration metadata on `RouteNode`. Initially shows a placeholder indicating config data is not yet available.
|
||||
|
||||
### Tab: Timeline
|
||||
|
||||
Gantt-style horizontal bar chart showing executed processors' relative durations:
|
||||
- One row per processor from the `ProcessorNode` execution tree (flattened in execution order) — only executed processors, not all diagram nodes
|
||||
- Bar width proportional to duration relative to total route duration
|
||||
- Green bars for completed, red for failed
|
||||
- Clicking a bar selects that processor in the diagram and scrolls to it
|
||||
- Duration label on the right of each row
|
||||
- When inside a loop/split compound, shows the current iteration's processors
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Flow
|
||||
|
||||
```
|
||||
ExecutionDiagram
|
||||
├── useExecutionDetail(executionId)
|
||||
│ → ExecutionDetail { processors: ProcessorNode[], diagramContentHash, ... }
|
||||
│
|
||||
├── useExecutionOverlay(executionDetail, iterationState)
|
||||
│ → Maps ProcessorNode tree → Map<diagramNodeId, NodeExecutionState>
|
||||
│ → Handles iteration filtering (loopIndex, splitIndex matching)
|
||||
│ → Detects sub-route failures on DIRECT/SEDA nodes
|
||||
│
|
||||
├── useIterationState()
|
||||
│ → Map<compoundNodeId, currentIterationIndex>
|
||||
│ → onIterationChange(compoundId, index) callback
|
||||
│
|
||||
├── ProcessDiagram
|
||||
│ props: { application, routeId, executionOverlay, iterationState, onIterationChange, ... }
|
||||
│ Renders nodes with overlay visual states
|
||||
│
|
||||
└── DetailPanel
|
||||
├── useProcessorSnapshotById(executionId, selectedProcessorId)
|
||||
│ → { inputBody, outputBody, inputHeaders, outputHeaders }
|
||||
└── Tabs render from ProcessorNode + snapshot data
|
||||
```
|
||||
|
||||
### Processor-to-Node Mapping
|
||||
|
||||
The `processorId` field on `ProcessorNode` is the same value as the `id` field on diagram `PositionedNode`. The agent uses diagram node IDs as processor IDs during route model extraction, so no separate mapping or `diagramNodeId` field is needed. The `useExecutionOverlay` hook builds its map by walking the `ProcessorNode` tree and keying on `processorId`, which directly matches diagram node IDs.
|
||||
|
||||
### Snapshot Loading
|
||||
|
||||
Per-processor body/header data is fetched lazily via `useProcessorSnapshotById(executionId, processorId)` when a processor is selected and the user switches to Input/Output/Headers tabs. This avoids loading all snapshot data upfront for routes with many processors. The snapshot endpoint accepts `processorId` (see Backend Prerequisites, Section 0).
|
||||
|
||||
---
|
||||
|
||||
## 8. Jump to Error
|
||||
|
||||
When the user clicks "Jump to Error":
|
||||
|
||||
1. Find the first `ProcessorNode` with `status === 'FAILED'` in the execution tree
|
||||
2. If the failed processor is a DIRECT/SEDA node with `subRouteFailed: true`:
|
||||
a. Drill down into the target route (same as double-click drill-down from sub-project 1)
|
||||
b. Recursively find the failed processor in the sub-route's execution
|
||||
3. Select the failed processor node
|
||||
4. Pan/zoom the diagram to center the failed node
|
||||
5. Show the Error tab in the detail panel
|
||||
|
||||
This handles arbitrarily deep cross-route error chains (route A calls direct:B which calls direct:C where the actual failure is).
|
||||
|
||||
---
|
||||
|
||||
## 9. Integration with ExchangeDetail Page
|
||||
|
||||
The `ExecutionDiagram` component replaces the existing "Flow" view tab on the `ExchangeDetail` page. The page passes `executionId` and the component handles everything internally.
|
||||
|
||||
```typescript
|
||||
// In ExchangeDetail page
|
||||
<ExecutionDiagram
|
||||
executionId={executionId}
|
||||
knownRouteIds={knownRouteIds}
|
||||
onNodeAction={handleNodeAction}
|
||||
nodeConfigs={nodeConfigs}
|
||||
/>
|
||||
```
|
||||
|
||||
The existing Gantt timeline view on ExchangeDetail can be removed or kept as an alternative view — the Timeline tab inside the detail panel provides the same functionality.
|
||||
|
||||
---
|
||||
|
||||
## Non-Goals (Sub-project 3)
|
||||
|
||||
- Replacing RouteFlow on the Dashboard or RouteDetail pages
|
||||
- Aggregate execution heatmaps (showing hot processors across many exchanges)
|
||||
- Live execution tracking (watching a RUNNING exchange in real-time)
|
||||
- Diff between two executions
|
||||
- Export/share execution view
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
1. `npx tsc -p tsconfig.app.json --noEmit` passes
|
||||
2. ExecutionDiagram renders on ExchangeDetail page for a known failed exchange
|
||||
3. Completed nodes show green tint + checkmark + duration badge
|
||||
4. Failed nodes show red tint + ! badge + red duration
|
||||
5. Skipped nodes are dimmed to 35% opacity
|
||||
6. Edges between executed nodes turn green; edges to skipped nodes are dashed gray
|
||||
7. Loop/split compounds show iteration stepper; stepping updates child overlay
|
||||
8. CHOICE compounds highlight taken branch, dim untaken branches
|
||||
9. Nested loops step independently
|
||||
10. Clicking a node shows its data in the detail panel
|
||||
11. Detail panel tabs: Info shows metadata + attributes, Headers shows side-by-side, Input/Output show formatted body, Error shows exception + stack trace, Timeline shows Gantt chart
|
||||
12. "Jump to Error" navigates to and selects the failed processor, drilling into sub-routes if needed
|
||||
13. Error tab grayed out for non-failed processors
|
||||
14. Config tab shows placeholder (TODO)
|
||||
15. Resizable splitter between diagram and detail panel works
|
||||
@@ -333,3 +333,27 @@ These are explicitly out of scope for sub-project 1:
|
||||
8. **Compound nodes:** Route with CHOICE renders children inside dashed container
|
||||
9. **Keyboard (required):** Escape deselects, +/- zooms, 0 fits to view
|
||||
10. **Direction:** `?direction=TB` renders top-to-bottom layout
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes (post-spec additions)
|
||||
|
||||
The following features were added during implementation beyond the original spec:
|
||||
|
||||
### Recursive compound nesting
|
||||
EIP_WHEN, EIP_OTHERWISE, DO_CATCH, DO_FINALLY added to COMPOUND_TYPES on both backend and frontend. CompoundNode recursively renders children that are themselves compound (e.g., CHOICE → WHEN → processors).
|
||||
|
||||
### Edge z-ordering
|
||||
Edges are distributed to their containing compound and rendered inside the compound's SVG group (after background, before children). Top-level edges stay in the main edges group. This prevents compound backgrounds from hiding edges.
|
||||
|
||||
### ON_COMPLETION handler sections
|
||||
ON_COMPLETION nodes render as teal-tinted sections between the main flow and error handler sections. Structurally parallel to ON_EXCEPTION.
|
||||
|
||||
### Drill-down navigation
|
||||
Double-click on DIRECT or SEDA nodes navigates into the target route's diagram. A breadcrumb bar shows the route stack and supports clicking back to any level. Escape key goes back one level. Route ID resolution handles camelCase endpoint URIs → kebab-case route IDs using the catalog's known route IDs.
|
||||
|
||||
### Zoom via CSS transform
|
||||
The original spec proposed SVG viewBox manipulation. Implementation uses CSS `transform: translate() scale()` on the content `<g>` element instead, which is simpler and more predictable. Default zoom is 100%.
|
||||
|
||||
### Toolbar as HTML overlay
|
||||
The original spec proposed SVG `<foreignObject>`. Implementation renders the toolbar as an absolute-positioned HTML div outside the SVG, so it maintains fixed size regardless of zoom level. Styled with design system tokens.
|
||||
|
||||
141
docs/superpowers/specs/2026-03-28-navigation-redesign-design.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Navigation Redesign: Persona-Driven Layout
|
||||
|
||||
## Status: Implemented (2026-03-28)
|
||||
|
||||
## Context
|
||||
|
||||
The Cameleer3 UI was redesigned from page-based routing to a scope-based model with three content tabs. The Process Diagram is the major USP for the Apache Camel community.
|
||||
|
||||
## Core Model
|
||||
|
||||
**Sidebar scopes WHERE** (app -> route hierarchy), **content tabs switch WHAT** (Exchanges | Dashboard | Runtime).
|
||||
|
||||
## Layout Shell
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ TopBar: [All > order-svc > route-a] [Cmd+K] [filters] [user] │
|
||||
├───────────┬──────────────────────────────────────────────────────────────────┤
|
||||
│ Sidebar │ [ Exchanges | Dashboard | Runtime ] TOTAL 443↑ ERR% 0.0%↓ │
|
||||
│ ├──────────────────────────────────────────────────────────────────┤
|
||||
│ Apps │ │
|
||||
│ ● app-1 │ Content area (layout varies by tab + selection) │
|
||||
│ ◐ app-2 │ │
|
||||
│ ▾ app-3 │ │
|
||||
│ route-a │ │
|
||||
│ route-b │ │
|
||||
│ │ │
|
||||
│ ⚙ Admin │ │
|
||||
└───────────┴──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Sidebar
|
||||
|
||||
Simplified to **app -> route** hierarchy only. No agents (accessible via Runtime tab). Sidebar clicks act as scope filters via event interception (`display: contents` wrapper intercepts Link clicks and re-routes through current tab).
|
||||
|
||||
## Scope Trail
|
||||
|
||||
Integrated into the TopBar breadcrumb (not a separate component). Shows: `All Applications > appId > routeId`.
|
||||
|
||||
## Tab Bar with Inline KPIs
|
||||
|
||||
Content tabs (Exchanges | Dashboard | Runtime) rendered using design system `Tabs` component. Right-aligned compact KPI metrics show: Total, Err%, Avg, P99 — each with trend arrow (green=good, red=bad) computed from `useExecutionStats`. KPIs are scope-aware.
|
||||
|
||||
## Tab: Exchanges (Primary USP)
|
||||
|
||||
### Full-width mode (no exchange selected)
|
||||
|
||||
Exchange table (DataTable from design system) with search, sort, filter. No KPI strip (metrics are in the tab bar). Clicking a row selects the exchange via local state (not URL navigation — preserves search scope).
|
||||
|
||||
### Split mode (exchange selected)
|
||||
|
||||
Draggable 50:50 split (20%-80% range, amber highlight on drag):
|
||||
|
||||
```
|
||||
┌──────────────────────┬───────────────────────────────────────┐
|
||||
│ Exchange Table │ Exchange Header │
|
||||
│ (same DataTable, │ status | attrs | app route | agent │
|
||||
│ search preserved) │ Correlated Exchanges (chain) │
|
||||
│ ├───────────────────────────────────────┤
|
||||
│ │ Process Diagram (execution overlay) │
|
||||
│ │ + minimap (bottom-right, above zoom) │
|
||||
│ ├───────────────────────────────────────┤
|
||||
│ │ Detail Panel (tabs) │
|
||||
│ │ Info|Headers|Input|Output|Error| │
|
||||
│ │ Config|Timeline|Log │
|
||||
└──────────────────────┴───────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Exchange Header** features:
|
||||
- Status dot + badge + tap-collected attribute badges (consistent color via value hash)
|
||||
- Clickable app name (resets to app scope), route name + GitBranch icon (resets to route scope)
|
||||
- Clickable agent name + ID + Server icon (colored by agent state: green/yellow/red) -> agent pages
|
||||
- Correlation chain with StatusDot per node, route name + duration, arrow connectors, total duration (far right)
|
||||
- Clicking a correlated exchange switches the diagram via local state
|
||||
- Clicking app/route clears the selection (returns to full-width table)
|
||||
|
||||
**Browser history integration:**
|
||||
- Each exchange selection pushes a history entry with the selection in `location.state`
|
||||
- Back/Forward restores the exact selection state (split view reappears)
|
||||
- Navigating to agent details and pressing Back returns to the diagram
|
||||
|
||||
**Detail Panel tabs:** Info, Headers (sorted alphabetically), Input, Output, Error, Config, Timeline, **Log** (new — shows exchange logs filtered by processor when selected).
|
||||
|
||||
## Tab: Dashboard
|
||||
|
||||
Wraps existing pages: RoutesMetrics (no route scope) or RouteDetail (route scoped).
|
||||
|
||||
## Tab: Runtime
|
||||
|
||||
Wraps existing pages: AgentHealth (no agent scope) or AgentInstance (agent selected).
|
||||
|
||||
## Admin
|
||||
|
||||
Gear icon in sidebar footer. Unchanged tab-based layout (RBAC, Audit, OIDC, AppConfig, DB, OpenSearch).
|
||||
|
||||
## URL Structure
|
||||
|
||||
```
|
||||
/exchanges -> Exchanges tab, no scope
|
||||
/exchanges/:appId -> Exchanges tab, app-scoped
|
||||
/exchanges/:appId/:routeId -> Exchanges tab, route-scoped
|
||||
/dashboard -> Dashboard tab
|
||||
/dashboard/:appId -> Dashboard tab, app-scoped
|
||||
/dashboard/:appId/:routeId -> Dashboard tab, route-scoped
|
||||
/runtime -> Runtime tab
|
||||
/runtime/:appId -> Runtime tab, app-scoped
|
||||
/runtime/:appId/:instanceId -> Runtime tab, agent detail
|
||||
/admin/* -> Admin (unchanged)
|
||||
/apps/*, /agents/* -> Legacy redirects to new paths
|
||||
```
|
||||
|
||||
Default `/` redirects to `/exchanges`.
|
||||
|
||||
## Key Implementation Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `ui/src/hooks/useScope.ts` | URL scope parsing + navigation helpers |
|
||||
| `ui/src/components/ContentTabs.tsx` | Tab bar with inline KPIs |
|
||||
| `ui/src/components/TabKpis.tsx` | Compact KPI metrics (Total, Err%, Avg, P99) |
|
||||
| `ui/src/components/LayoutShell.tsx` | Shell with sidebar interception, scope trail, tabs |
|
||||
| `ui/src/pages/Exchanges/ExchangesPage.tsx` | Full-width / split orchestrator with history state |
|
||||
| `ui/src/pages/Exchanges/ExchangeHeader.tsx` | Exchange info bar + correlation chain |
|
||||
| `ui/src/pages/Dashboard/Dashboard.tsx` | Exchange table (accepts `onExchangeSelect` callback) |
|
||||
| `ui/src/pages/RuntimeTab/RuntimePage.tsx` | AgentHealth / AgentInstance wrapper |
|
||||
| `ui/src/pages/DashboardTab/DashboardPage.tsx` | RoutesMetrics / RouteDetail wrapper |
|
||||
| `ui/src/utils/attribute-color.ts` | Deterministic badge color from value hash |
|
||||
| `ui/src/components/ExecutionDiagram/tabs/LogTab.tsx` | Log tab for detail panel |
|
||||
| `ui/src/router.tsx` | Tab-based routes + legacy redirects |
|
||||
|
||||
## What Differentiates from njams
|
||||
|
||||
1. **Three-tab model** — njams interleaves everything; Cameleer3 separates concerns
|
||||
2. **Inline KPI metrics** — compact stats in the tab bar with trend indicators
|
||||
3. **State-based selection** — exchange selection via local state, not URL navigation (preserves search)
|
||||
4. **Browser history integration** — Back/Forward restores exchange selection
|
||||
5. **Scope-based sidebar** — filters rather than navigates
|
||||
6. **Runtime tab** — dedicated agent monitoring
|
||||
7. **Draggable split** — resizable divider between table and diagram
|
||||
8. **Correlation chain** — arrow-connected nodes with route names, durations, total duration
|
||||
9. **Attribute badges** — consistent colors across all views via value hash
|
||||
197
docs/superpowers/specs/2026-03-29-dashboard-design.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Dashboard Design Spec
|
||||
|
||||
## Goal
|
||||
|
||||
Redesign the Dashboard tab as a progressive drill-down dashboard for Apache Camel application monitoring. Follows the RED method (Rate, Errors, Duration) plus saturation (inflight exchanges). The sidebar drives scope: all applications → single application → single route. Each level answers a focused question with increasing detail.
|
||||
|
||||
Business/support users are the primary audience — the dashboard focuses on exchange health, throughput, error rates, and SLA compliance. Ops/infrastructure monitoring stays on the Runtime tab (agent health, JVM metrics). Power users needing custom analysis will use Grafana; this dashboard targets the 80% sweet spot.
|
||||
|
||||
## Architecture
|
||||
|
||||
The Dashboard tab renders one of three views based on the current sidebar selection:
|
||||
|
||||
| Sidebar state | Dashboard level | Question answered |
|
||||
|---|---|---|
|
||||
| No selection | Level 1: All Applications | Is my landscape healthy? Which app needs attention? |
|
||||
| Application selected | Level 2: Application | How is this app performing? Which route is the problem? |
|
||||
| Route selected | Level 3: Route | What's happening in this route? Where's the bottleneck? |
|
||||
|
||||
All levels share the same time range (controlled by the top bar's time selector) and auto-refresh behavior (LIVE mode with 30s refresh).
|
||||
|
||||
## Level 1: All Applications Overview
|
||||
|
||||
### KPI Strip (4 metrics)
|
||||
|
||||
| Metric | Source | Trend |
|
||||
|---|---|---|
|
||||
| Total throughput (msg/s) | `stats_1m_all` aggregate | vs previous period |
|
||||
| Error rate (%) | failed / total | vs previous period |
|
||||
| P99 latency (ms) | `approx_percentile(0.99)` | vs previous period |
|
||||
| Inflight exchanges | running count | current value |
|
||||
|
||||
Uses the existing `useExecutionStats()` hook with no application/route filter.
|
||||
|
||||
### Application Health Table
|
||||
|
||||
Columns: App name, Status dot, Throughput (msg/s), Error Rate (%), P99 (ms), Active Routes, Sparkline (12-bucket trend).
|
||||
|
||||
**Status dot derivation:**
|
||||
- Green: error rate < 1% AND P99 < SLA threshold (300ms)
|
||||
- Yellow: error rate 1-5% OR P99 between 200-300ms
|
||||
- Red: error rate > 5% OR P99 > SLA threshold
|
||||
|
||||
Row click navigates to that application in the sidebar (transitions to Level 2).
|
||||
|
||||
Data source: `useRouteMetrics()` aggregated by application. The route-level metrics are grouped by `appId` and aggregated to produce application-level rows.
|
||||
|
||||
### Charts (2, side-by-side)
|
||||
|
||||
1. **Throughput over time** — Stacked area chart, one series per application. Shows relative volume and total.
|
||||
2. **Error rate over time** — Line chart, one line per application. Highlights which app is misbehaving.
|
||||
|
||||
Data source: `useStatsTimeseries()` with no application filter, extended to support per-app breakdown. This requires a new API parameter or a new endpoint that returns timeseries grouped by application.
|
||||
|
||||
### New API endpoint needed
|
||||
|
||||
`GET /api/v1/search/stats/timeseries/by-app` — Returns timeseries buckets grouped by application name. Query: `stats_1m_app` materialized view.
|
||||
|
||||
Response shape:
|
||||
```json
|
||||
{
|
||||
"apps": {
|
||||
"order-service": [ { "time": "...", "totalCount": 42, "failedCount": 1, ... } ],
|
||||
"payment-gateway": [ ... ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Level 2: Single Application
|
||||
|
||||
### KPI Strip (4 metrics)
|
||||
|
||||
Same four metrics as Level 1 but scoped to the selected application. Uses `useExecutionStats({ application })`.
|
||||
|
||||
### Route Performance Table
|
||||
|
||||
Columns: Route ID, Throughput (msg/s), Success %, Avg Duration (ms), P99 Duration (ms), Error Rate (%), Sparkline.
|
||||
|
||||
Sortable by any column. Row click navigates to that route in the sidebar (transitions to Level 3).
|
||||
|
||||
Data source: `useRouteMetrics({ appId })` — already exists and returns per-route data filtered by application.
|
||||
|
||||
### Charts (2, side-by-side)
|
||||
|
||||
1. **Throughput over time** — Stacked area by route.
|
||||
2. **Latency percentiles over time** — P50, P95, P99 lines with SLA threshold (300ms horizontal dashed line).
|
||||
|
||||
Data source: `useStatsTimeseries({ application })` for the aggregate latency chart. For per-route throughput breakdown, needs the same by-app pattern extended to by-route:
|
||||
|
||||
### New API endpoint needed
|
||||
|
||||
`GET /api/v1/search/stats/timeseries/by-route` — Returns timeseries buckets grouped by route ID, filtered by application. Query: `stats_1m_route` materialized view.
|
||||
|
||||
Response shape:
|
||||
```json
|
||||
{
|
||||
"routes": {
|
||||
"process-order": [ { "time": "...", "totalCount": 42, ... } ],
|
||||
"validate-payment": [ ... ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Top 5 Errors
|
||||
|
||||
Compact table: Error Type, Route, Count, Last Seen.
|
||||
|
||||
Click navigates to the Exchanges tab with the error type pre-filled as a search filter. Section hidden when there are zero errors in the time window.
|
||||
|
||||
Data source: PostgreSQL query on `executions` table, aggregating by `error_type` column for the selected application and time range.
|
||||
|
||||
### New API endpoint needed
|
||||
|
||||
`GET /api/v1/search/errors/top` — Returns top N errors grouped by error type.
|
||||
|
||||
Parameters: `application` (optional), `routeId` (optional), `from`, `to`, `limit` (default 5).
|
||||
|
||||
Response shape:
|
||||
```json
|
||||
[
|
||||
{ "errorType": "ConnectTimeoutException", "routeId": "validate-payment", "count": 47, "lastSeen": "2026-03-29T15:58:00Z" }
|
||||
]
|
||||
```
|
||||
|
||||
Source: PostgreSQL query on `executions` table. Group by `error_type` column (added in V10 migration). Filter to `status = 'FAILED'` within the time range. Order by count descending, limit to N.
|
||||
|
||||
## Level 3: Single Route
|
||||
|
||||
### KPI Strip (4 metrics)
|
||||
|
||||
Same four metrics scoped to the selected route. Uses `useExecutionStats({ application, routeId })`.
|
||||
|
||||
### Charts (3, in a row)
|
||||
|
||||
1. **Throughput over time** — Area chart, single series.
|
||||
2. **Latency percentiles over time** — P50, P95, P99 lines with SLA threshold.
|
||||
3. **Error rate over time** — Area chart, red-tinted.
|
||||
|
||||
Data source: `useStatsTimeseries({ application, routeId })` — already exists.
|
||||
|
||||
### Compact Process Diagram with Heatmap
|
||||
|
||||
Reuses the `ProcessDiagram` component but with a **latency heatmap overlay** instead of the execution overlay. Processor nodes are colored by aggregate performance:
|
||||
|
||||
- Color scale: green (fast) → yellow (moderate) → red (slow), computed relative to the route's own processors (not absolute thresholds). The slowest processor in the route gets the warmest color.
|
||||
- Data source: `useProcessorMetrics({ routeId, appId })` — already exists. Uses `avgDurationMs` or `p99DurationMs` for the color mapping.
|
||||
- No click interactions beyond visual identification. Clicking a node scrolls to its row in the processor table below.
|
||||
- Compact sizing: fixed height (~250px), the diagram fits-to-view within that space.
|
||||
|
||||
Implementation: a new `heatmapOverlay` prop on `ProcessDiagram` (or a wrapper component) that takes a `Map<processorId, { avgMs, p99Ms }>` and colors nodes accordingly. Reuses the existing diagram layout and rendering — only the fill color logic changes.
|
||||
|
||||
### Processor Metrics Table
|
||||
|
||||
Columns: Processor ID, Type, Invocation Count, Avg Duration (ms), P99 Duration (ms), Error Rate (%).
|
||||
|
||||
Default sort: P99 descending (slowest processor first — highlights bottlenecks).
|
||||
|
||||
Data source: `useProcessorMetrics({ routeId, appId })` — already exists.
|
||||
|
||||
### Top 5 Errors
|
||||
|
||||
Same format as Level 2 but scoped to this route. Uses the same `top-errors` endpoint with `routeId` parameter.
|
||||
|
||||
## Data Requirements Summary
|
||||
|
||||
### Existing endpoints (no backend changes)
|
||||
|
||||
| Endpoint | Used at |
|
||||
|---|---|
|
||||
| `GET /search/stats` | All levels (KPI strip) |
|
||||
| `GET /search/stats/timeseries` | Level 2, Level 3 (charts) |
|
||||
| `GET /routes/metrics` | Level 1 (app table, aggregated), Level 2 (route table) |
|
||||
| `GET /routes/metrics/processors` | Level 3 (processor table + heatmap) |
|
||||
|
||||
### New endpoints needed
|
||||
|
||||
| Endpoint | Used at | Source |
|
||||
|---|---|---|
|
||||
| `GET /search/stats/timeseries/by-app` | Level 1 (charts) | `stats_1m_app` view |
|
||||
| `GET /search/stats/timeseries/by-route` | Level 2 (throughput chart) | `stats_1m_route` view |
|
||||
| `GET /search/errors/top` | Level 2, Level 3 (top errors) | `executions` table |
|
||||
|
||||
### Existing frontend components reused
|
||||
|
||||
- `KpiStrip` / `TabKpis` — KPI display with trends
|
||||
- `DataTable` — sortable tables
|
||||
- `AreaChart`, `LineChart` — time-series charts
|
||||
- `Sparkline` — compact trend in table cells
|
||||
- `StatusDot` — health indicators
|
||||
- `ProcessDiagram` — route visualization (extended with heatmap)
|
||||
|
||||
## Scope Exclusions
|
||||
|
||||
- No user-customizable panels or drag-and-drop layout (power users use Grafana)
|
||||
- No JVM/infrastructure metrics on Dashboard tab (that's Runtime tab)
|
||||
- No alerting or threshold configuration (out of scope)
|
||||
- No comparison mode (e.g., "this week vs last week" side-by-side)
|
||||
- No export/PDF functionality
|
||||
1
openapi.json
Normal file
BIN
org/eclipse/elk/alg/layered/options/.LayeredOptions.java._trace
Normal file
2661
org/eclipse/elk/core/options/CoreOptions.java
Normal file
BIN
screenshots/01-dashboard.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
screenshots/02-dashboard-detail-panel.png
Normal file
|
After Width: | Height: | Size: 232 KiB |