Skip to content

Commit d20bb05

Browse files
feat: marketplace-style Integrations page + fix tests and OG image
- Redesign IntegrationsPage as a card grid with brand colours, status icons (checkmark/warning), filter tabs, and per-tool detail pages - Connected tools navigate to Token Monitoring with the right assistant pre-selected (via initialTool prop on BoardView) - Non-connected tools open a full-width detail view with back button, numbered setup steps, and documentation links - Only tools with real board panels (claude-code, codex, devin, copilot) navigate away; others open the detail view to avoid blank screens - Fix integration badge count: use Set to count distinct reading tools instead of Math.max capping at 1 - Fall back to assistants array when integrations data is unavailable - Replace text-only og.png with real dashboard screenshot - Fix leaderboard tests: handle cloud-configured openBoard flow, re-stub API on reload, update nav item count assertions Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 6f22ab0 commit d20bb05

8 files changed

Lines changed: 616 additions & 98 deletions

File tree

site/public/og-text.png

40.9 KB
Loading

site/public/og.png

201 KB
Loading

site/src/components/BoardView.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import { profilesOf, rankBoard } from '../lib/leaderboard'
33
import { TipProvider } from './board/charts'
44
import {
5-
CodexDays, CodexLimits, CodexModels, CodexPolicy, CodexProjects, CodexSessions, CodexSplit, CodexTiles,
5+
CodexDays, CodexLimits, CodexModels, CodexPolicy, CodexProjects, CodexSessions, CodexSplit, CodexTiles,
66
} from './board/codex'
77
import { AddMachineModal, MachinesPanel, UpgradeModal } from './board/enroll'
88
import { ExtensionsCard, governanceBadges, McpCard, PermissionsCard, ToolCallsCard } from './board/governance'
99
import Leaderboard from './board/leaderboard'
1010
import {
11-
ActivityCard, Card, DriversCard, EndedFeed, HostsFeed, HoursCard, IntegrationsCard, LiveCard,
12-
ModelsTable, Offboard, ProjectsFeed, PromptsFeed, RateCard, SessionsTable, SpendCard, Tiles,
13-
TokensCard, UsageWindows,
11+
ActivityCard, Card, DriversCard, EndedFeed, HostsFeed, HoursCard, IntegrationsCard, LiveCard,
12+
ModelsTable, Offboard, ProjectsFeed, PromptsFeed, RateCard, SessionsTable, SpendCard, Tiles,
13+
TokensCard, UsageWindows,
1414
} from './board/panels'
1515
import Rail, { Ic } from './board/Rail'
1616
import { compact, usdShort } from './board/util'
@@ -67,9 +67,10 @@ export default function BoardView({
6767
theme, onToggleTheme,
6868
embedded, onBoardState,
6969
publicBoard,
70+
initialTool,
7071
}) {
7172
const [picked, setPicked] = useState(null) /* which machine */
72-
const [toolSel, setToolSel] = useState('claude-code')
73+
const [toolSel, setToolSel] = useState(initialTool || 'claude-code')
7374
const [railOpen, setRailOpen] = useState(false)
7475
const [active, setActive] = useState(null) /* which nav row is lit */
7576
const [addOpen, setAddOpen] = useState(false) /* the add-machine modal */

site/src/components/Portal.jsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { fleetOf, rankBoard } from '../lib/leaderboard'
66
import { buildOverview } from '../lib/overview'
77
import AdminSidebar from './admin/AdminSidebar'
88
import AdminTopbar from './admin/AdminTopbar'
9+
import IntegrationsPage from './admin/IntegrationsPage'
910
import LeaderboardPage, { LEADERBOARD_KEYS, LEADERBOARD_PAGES } from './admin/LeaderboardPage'
1011
import RootRail, { SECTIONS } from './admin/RootRail'
1112
import SectionRail from './admin/SectionRail'
@@ -60,6 +61,7 @@ export default function Portal({ onClose, user, onUser, onSelfHost }) {
6061
const [rootMini, setRootMini] = useState(() => load(SK_ROOTNAV, '') === '1')
6162
const [collapsed, setCollapsed] = useState(() => load(SK_SUBNAV, '') === '1')
6263
const [boardState, setBoardState] = useState(null)
64+
const [pendingTool, setPendingTool] = useState(null)
6365

6466
/* Every fetch this component has in flight, so a sign-out or a close does
6567
not land a response into an unmounted tree. */
@@ -220,7 +222,10 @@ export default function Portal({ onClose, user, onUser, onSelfHost }) {
220222
const toggleRoot = useCallback(() => {
221223
setRootMini(c => { save(SK_ROOTNAV, c ? '' : '1'); return !c })
222224
}, [])
223-
const goto = useCallback(key => { setSection(key); save(SK_SECTION, key) }, [])
225+
const goto = useCallback(key => {
226+
if (key !== 'monitoring') setPendingTool(null)
227+
setSection(key); save(SK_SECTION, key)
228+
}, [])
224229
const gotoLb = useCallback(key => { setLbPage(key); save(SK_LBPAGE, key) }, [])
225230

226231
/* The fleet in the shape the Leaderboard pages expect. Computed here rather
@@ -246,17 +251,23 @@ export default function Portal({ onClose, user, onUser, onSelfHost }) {
246251
return row ? row.rank : null
247252
}, [profiles, meId])
248253

249-
/* Integration summary across all snapshots for the badge. */
254+
/* Integration summary across all snapshots for the badge. Falls back to
255+
the assistants array when the agent hasn't been upgraded yet. */
250256
const intSummary = useMemo(() => {
251257
const snaps = data?.latest || []
252258
let reading = 0, known = 0
253259
const seen = new Set()
260+
const readingIds = new Set()
254261
for (const s of snaps) {
255262
const sum = s.metrics?.integrationSummary
256263
if (sum) { reading = Math.max(reading, sum.reading || 0); known = Math.max(known, sum.known || 0) }
257-
for (const r of (s.metrics?.integrations || [])) seen.add(r.id)
264+
const rows = s.metrics?.integrations || s.metrics?.assistants || []
265+
for (const r of rows) {
266+
seen.add(r.id)
267+
if (r.state === 'reading' || r.hasData) readingIds.add(r.id)
268+
}
258269
}
259-
return { reading, known: known || seen.size }
270+
return { reading: Math.max(reading, readingIds.size), known: known || seen.size }
260271
}, [data])
261272

262273
const hasSubNav = section === 'monitoring' || section === 'leaderboard'
@@ -350,6 +361,7 @@ export default function Portal({ onClose, user, onUser, onSelfHost }) {
350361
publicBoard={publicBoard}
351362
embedded
352363
onBoardState={setBoardState}
364+
initialTool={pendingTool}
353365
/>
354366
)}
355367
{section === 'leaderboard' && (
@@ -361,7 +373,12 @@ export default function Portal({ onClose, user, onUser, onSelfHost }) {
361373
/>
362374
)}
363375
{section === 'integrations' && (
364-
<IntegrationsPage snapshots={data?.latest || []} />
376+
<IntegrationsPage snapshots={data?.latest || []}
377+
onNavigate={(toolId) => {
378+
setPendingTool(toolId)
379+
goto('monitoring')
380+
}}
381+
/>
365382
)}
366383
{section === 'settings' && (
367384
<div className="adm-page" style={{ padding: 'var(--space-xl) var(--page-gutter)' }}>

site/src/components/SelfHost.jsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import { fleetOf, rankBoard } from '../lib/leaderboard'
33
import AdminSidebar from './admin/AdminSidebar'
44
import AdminTopbar from './admin/AdminTopbar'
5+
import IntegrationsPage from './admin/IntegrationsPage'
56
import LeaderboardPage, { LEADERBOARD_KEYS, LEADERBOARD_PAGES } from './admin/LeaderboardPage'
67
import RootRail, { SECTIONS } from './admin/RootRail'
78
import SectionRail from './admin/SectionRail'
@@ -335,6 +336,7 @@ export default function SelfHost({ onClose }) {
335336
const [nonce, setNonce] = useState(0)
336337
const [probeSeq, setProbeSeq] = useState(0)
337338
const [boardState, setBoardState] = useState(null)
339+
const [pendingTool, setPendingTool] = useState(null)
338340
const esRef = useRef(null)
339341
const [latestRelease, setLatestRelease] = useState(null)
340342

@@ -517,7 +519,10 @@ export default function SelfHost({ onClose }) {
517519
const toggleRoot = useCallback(() => {
518520
setRootMini(c => { save(SK_ROOTNAV, c ? '' : '1'); return !c })
519521
}, [])
520-
const goto = useCallback(key => { setSection(key); save(SK_SECTION, key) }, [])
522+
const goto = useCallback(key => {
523+
if (key !== 'monitoring') setPendingTool(null)
524+
setSection(key); save(SK_SECTION, key)
525+
}, [])
521526
const gotoLb = useCallback(key => { setLbPage(key); save(SK_LBPAGE, key) }, [])
522527

523528
/* The board wants `id` on a machine row; the server calls it `installId`.
@@ -564,17 +569,23 @@ export default function SelfHost({ onClose }) {
564569
return row ? row.rank : null
565570
}, [profiles, meId])
566571

567-
/* Integration summary across all snapshots for the badge. */
572+
/* Integration summary across all snapshots for the badge. Falls back to
573+
the assistants array when the agent hasn't been upgraded yet. */
568574
const intSummary = useMemo(() => {
569575
const snaps = shaped?.latest || []
570576
let reading = 0, known = 0
571577
const seen = new Set()
578+
const readingIds = new Set()
572579
for (const s of snaps) {
573580
const sum = s.metrics?.integrationSummary
574581
if (sum) { reading = Math.max(reading, sum.reading || 0); known = Math.max(known, sum.known || 0) }
575-
for (const r of (s.metrics?.integrations || [])) seen.add(r.id)
582+
const rows = s.metrics?.integrations || s.metrics?.assistants || []
583+
for (const r of rows) {
584+
seen.add(r.id)
585+
if (r.state === 'reading' || r.hasData) readingIds.add(r.id)
586+
}
576587
}
577-
return { reading, known: known || seen.size }
588+
return { reading: Math.max(reading, readingIds.size), known: known || seen.size }
578589
}, [shaped])
579590

580591
/* ── routing: probing and offline don't get the admin shell ── */
@@ -693,6 +704,7 @@ export default function SelfHost({ onClose }) {
693704
onToggleTheme={toggleTheme}
694705
embedded
695706
onBoardState={setBoardState}
707+
initialTool={pendingTool}
696708
/>
697709
)}
698710
{section === 'leaderboard' && (
@@ -707,7 +719,12 @@ export default function SelfHost({ onClose }) {
707719
/>
708720
)}
709721
{section === 'integrations' && (
710-
<IntegrationsPage snapshots={shaped?.latest || []} />
722+
<IntegrationsPage snapshots={shaped?.latest || []}
723+
onNavigate={(toolId) => {
724+
setPendingTool(toolId)
725+
goto('monitoring')
726+
}}
727+
/>
711728
)}
712729
{section === 'settings' && (
713730
<Settings

0 commit comments

Comments
 (0)