Compare commits
159 Commits
085c4e395b
...
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 |
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 }}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,11 +9,12 @@ 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.alg.layered.options.NodePlacementStrategy;
|
||||
import org.eclipse.elk.core.util.BasicProgressMonitor;
|
||||
import org.eclipse.elk.graph.ElkBendPoint;
|
||||
import org.eclipse.elk.graph.ElkEdge;
|
||||
@@ -32,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;
|
||||
@@ -39,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
|
||||
@@ -104,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) {
|
||||
@@ -137,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);
|
||||
@@ -146,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));
|
||||
}
|
||||
@@ -172,111 +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);
|
||||
rootNode.setProperty(org.eclipse.elk.alg.layered.options.LayeredOptions.NODE_PLACEMENT_STRATEGY,
|
||||
NodePlacementStrategy.LINEAR_SEGMENTS);
|
||||
// 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()) {
|
||||
@@ -286,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);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@@ -319,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(),
|
||||
@@ -334,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);
|
||||
@@ -388,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;
|
||||
}
|
||||
@@ -406,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");
|
||||
@@ -444,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(
|
||||
@@ -490,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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -498,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) {
|
||||
@@ -541,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) {
|
||||
@@ -559,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
|
||||
// ----------------------------------------------------------------
|
||||
@@ -570,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
|
||||
@@ -73,8 +91,12 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
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,
|
||||
loop_index, loop_size, split_index, split_size, multicast_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb, ?, ?, ?, ?, ?)
|
||||
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),
|
||||
@@ -90,7 +112,15 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
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)
|
||||
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(),
|
||||
@@ -102,7 +132,12 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
p.inputBody(), p.outputBody(), p.inputHeaders(), p.outputHeaders(),
|
||||
p.attributes(),
|
||||
p.loopIndex(), p.loopSize(), p.splitIndex(), p.splitSize(),
|
||||
p.multicastIndex()
|
||||
p.multicastIndex(),
|
||||
p.resolvedEndpointUri(),
|
||||
p.errorType(), p.errorCategory(),
|
||||
p.rootCauseType(), p.rootCauseMessage(),
|
||||
p.errorHandlerType(), p.circuitBreakerState(),
|
||||
p.fallbackTriggered()
|
||||
}).toList());
|
||||
}
|
||||
|
||||
@@ -141,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(
|
||||
@@ -160,7 +201,12 @@ public class PostgresExecutionStore implements ExecutionStore {
|
||||
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.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,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,10 +41,13 @@ 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()
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -50,11 +64,61 @@ public class DetailService {
|
||||
});
|
||||
}
|
||||
|
||||
/** 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(),
|
||||
@@ -63,7 +127,14 @@ public class DetailService {
|
||||
parseAttributes(p.attributes()),
|
||||
p.loopIndex(), p.loopSize(),
|
||||
p.splitIndex(), p.splitSize(),
|
||||
p.multicastIndex()
|
||||
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
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -27,6 +27,17 @@ public final class ProcessorNode {
|
||||
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,
|
||||
@@ -35,7 +46,14 @@ public final class ProcessorNode {
|
||||
Map<String, String> attributes,
|
||||
Integer loopIndex, Integer loopSize,
|
||||
Integer splitIndex, Integer splitSize,
|
||||
Integer multicastIndex) {
|
||||
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;
|
||||
@@ -50,6 +68,17 @@ public final class ProcessorNode {
|
||||
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<>();
|
||||
}
|
||||
|
||||
@@ -71,5 +100,16 @@ public final class ProcessorNode {
|
||||
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,
|
||||
@@ -131,7 +157,12 @@ public class IngestionService {
|
||||
toJson(p.getAttributes()),
|
||||
p.getLoopIndex(), p.getLoopSize(),
|
||||
p.getSplitIndex(), p.getSplitSize(),
|
||||
p.getMulticastIndex()
|
||||
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(
|
||||
@@ -156,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) {}
|
||||
@@ -25,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(
|
||||
@@ -38,6 +44,11 @@ public interface ExecutionStore {
|
||||
String attributes,
|
||||
Integer loopIndex, Integer loopSize,
|
||||
Integer splitIndex, Integer splitSize,
|
||||
Integer multicastIndex
|
||||
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.
|
||||
1301
docs/superpowers/plans/2026-03-28-navigation-redesign.md
Normal file
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 |
BIN
screenshots/03-exchange-detail.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
screenshots/04-routes-metrics.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
screenshots/05-agents.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
screenshots/06-agent-instance.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
screenshots/07-admin-rbac.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
screenshots/08-admin-audit.png
Normal file
|
After Width: | Height: | Size: 74 KiB |