feat: upgrade design system to v0.1.19, use onNavigate/fillHeight, add SonarQube workflow
- Use Sidebar onNavigate callback instead of display:contents click interception - Use DataTable fillHeight prop instead of manual scroll wrapper divs - Fix DataTable scroll/pagination by adding overflow:hidden to content container - Fix left panel in split view to use flex column instead of overflow:auto - Make error tab stack trace scrollable for large traces - Add nightly SonarQube workflow with manual trigger support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
.gitea/workflows/sonarqube.yml
Normal file
57
.gitea/workflows/sonarqube.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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 with coverage
|
||||||
|
run: mvn clean verify -DskipITs -U --batch-mode
|
||||||
|
|
||||||
|
- name: SonarQube Analysis
|
||||||
|
run: |
|
||||||
|
mvn sonar:sonar --batch-mode \
|
||||||
|
-Dsonar.host.url="$SONAR_HOST_URL" \
|
||||||
|
-Dsonar.token="$SONAR_TOKEN" \
|
||||||
|
-Dsonar.projectKey=cameleer3-server \
|
||||||
|
-Dsonar.projectName="Cameleer3 Server"
|
||||||
|
env:
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.18",
|
"@cameleer/design-system": "^0.1.19",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
@@ -277,9 +277,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cameleer/design-system": {
|
"node_modules/@cameleer/design-system": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.18/design-system-0.1.18.tgz",
|
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.19/design-system-0.1.19.tgz",
|
||||||
"integrity": "sha512-uvGr4PFw6Eya+h9DSD0wBnzjIXhZpcndR2dDJX2tMvQqgy+32WTTTQ8BZZWZjOKLSv63UpBN/fwVSXtkA4dnqA==",
|
"integrity": "sha512-YpYJysWycqRiTMco3Fco8AIatJz/IU7EecTmUQLUrkcBUGfHOWzCTMYm47jRvNAjQrANoanYnXPKXRHg91NS2w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.18",
|
"@cameleer/design-system": "^0.1.19",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
|
|||||||
@@ -447,6 +447,11 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.errorStackWrap pre {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.errorStackLabel {
|
.errorStackLabel {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ export function ErrorTab({ processor, executionDetail }: ErrorTabProps) {
|
|||||||
{errorStackTrace && (
|
{errorStackTrace && (
|
||||||
<>
|
<>
|
||||||
<div className={styles.errorStackLabel}>Stack Trace</div>
|
<div className={styles.errorStackLabel}>Stack Trace</div>
|
||||||
|
<div className={styles.errorStackWrap}>
|
||||||
<CodeBlock content={errorStackTrace} copyable />
|
<CodeBlock content={errorStackTrace} copyable />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -222,36 +222,31 @@ function LayoutContent() {
|
|||||||
navigate(`${baseParts.join('/')}?text=${encodeURIComponent(query)}`);
|
navigate(`${baseParts.join('/')}?text=${encodeURIComponent(query)}`);
|
||||||
}, [navigate, scope.appId, scope.routeId]);
|
}, [navigate, scope.appId, scope.routeId]);
|
||||||
|
|
||||||
// Intercept Sidebar's internal <Link> navigation to re-route through current tab
|
// Translate Sidebar's internal paths to our URL structure
|
||||||
const handleSidebarClick = useCallback((e: React.MouseEvent) => {
|
const handleSidebarNavigate = useCallback((path: string) => {
|
||||||
const anchor = (e.target as HTMLElement).closest('a[href]');
|
// /apps/:appId and /apps/:appId/:routeId → current tab
|
||||||
if (!anchor) return;
|
const appMatch = path.match(/^\/apps\/([^/]+)(?:\/(.+))?$/);
|
||||||
const href = anchor.getAttribute('href') || '';
|
|
||||||
|
|
||||||
// Intercept /apps/:appId and /apps/:appId/:routeId links
|
|
||||||
const appMatch = href.match(/^\/apps\/([^/]+)(?:\/(.+))?$/);
|
|
||||||
if (appMatch) {
|
if (appMatch) {
|
||||||
e.preventDefault();
|
|
||||||
const [, sAppId, sRouteId] = appMatch;
|
const [, sAppId, sRouteId] = appMatch;
|
||||||
navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`);
|
navigate(sRouteId ? `/${scope.tab}/${sAppId}/${sRouteId}` : `/${scope.tab}/${sAppId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intercept /agents/* links — redirect to runtime tab
|
// /agents/:appId/:instanceId → runtime tab
|
||||||
const agentMatch = href.match(/^\/agents\/([^/]+)(?:\/(.+))?$/);
|
const agentMatch = path.match(/^\/agents\/([^/]+)(?:\/(.+))?$/);
|
||||||
if (agentMatch) {
|
if (agentMatch) {
|
||||||
e.preventDefault();
|
|
||||||
const [, sAppId, sInstanceId] = agentMatch;
|
const [, sAppId, sInstanceId] = agentMatch;
|
||||||
navigate(sInstanceId ? `/runtime/${sAppId}/${sInstanceId}` : `/runtime/${sAppId}`);
|
navigate(sInstanceId ? `/runtime/${sAppId}/${sInstanceId}` : `/runtime/${sAppId}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigate(path);
|
||||||
}, [navigate, scope.tab]);
|
}, [navigate, scope.tab]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
sidebar={
|
sidebar={
|
||||||
<div onClick={handleSidebarClick} style={{ display: 'contents' }}>
|
<Sidebar apps={sidebarApps} onNavigate={handleSidebarNavigate} />
|
||||||
<Sidebar apps={sidebarApps} />
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TopBar
|
<TopBar
|
||||||
|
|||||||
@@ -5,25 +5,8 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
background: var(--bg-body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table section — stretches to fill and scrolls internally */
|
|
||||||
|
|
||||||
|
|
||||||
.tableSection {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
background: var(--bg-surface);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
background: var(--bg-body);
|
||||||
|
|
||||||
.tableScroll {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tableHeader {
|
.tableHeader {
|
||||||
|
|||||||
@@ -236,11 +236,7 @@ export default function Dashboard({ onExchangeSelect }: DashboardProps = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{/* Scrollable content */}
|
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{/* Exchanges table */}
|
|
||||||
<div className={styles.tableSection}>
|
|
||||||
<div className={styles.tableHeader}>
|
<div className={styles.tableHeader}>
|
||||||
<span className={styles.tableTitle}>
|
<span className={styles.tableTitle}>
|
||||||
{textFilter ? (
|
{textFilter ? (
|
||||||
@@ -265,7 +261,6 @@ export default function Dashboard({ onExchangeSelect }: DashboardProps = {}) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.tableScroll}>
|
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={rows}
|
data={rows}
|
||||||
@@ -273,6 +268,7 @@ export default function Dashboard({ onExchangeSelect }: DashboardProps = {}) {
|
|||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
sortable
|
sortable
|
||||||
flush
|
flush
|
||||||
|
fillHeight
|
||||||
onSortChange={handleSortChange}
|
onSortChange={handleSortChange}
|
||||||
rowAccent={handleRowAccent}
|
rowAccent={handleRowAccent}
|
||||||
expandedContent={(row: Row) =>
|
expandedContent={(row: Row) =>
|
||||||
@@ -288,8 +284,5 @@ export default function Dashboard({ onExchangeSelect }: DashboardProps = {}) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.leftPanel {
|
.leftPanel {
|
||||||
overflow: auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user