Generated by Spark: Github build status information on dashboard - https://github.com/johndoe6345789/low-code-react-app-b/actions

This commit is contained in:
2026-01-17 09:56:58 +00:00
committed by GitHub
parent f6108cd08e
commit d159c5e8c7
8 changed files with 294 additions and 3 deletions

View File

@@ -176,6 +176,7 @@ function App() {
storybookStories={storybookStories}
unitTests={unitTests}
flaskConfig={flaskConfig}
nextjsConfig={nextjsConfig}
/>
</TabsContent>

View File

@@ -116,7 +116,7 @@ function App() {
<PageHeader activeTab={activeTab} />
<div className="flex-1 overflow-hidden">
<TabsContent value="dashboard" className="h-full m-0">
<ProjectDashboard files={files} models={models} components={components} theme={theme} playwrightTests={playwrightTests} storybookStories={storybookStories} unitTests={unitTests} flaskConfig={flaskConfig} />
<ProjectDashboard files={files} models={models} components={components} theme={theme} playwrightTests={playwrightTests} storybookStories={storybookStories} unitTests={unitTests} flaskConfig={flaskConfig} nextjsConfig={nextjsConfig} />
</TabsContent>
{featureToggles.codeEditor && (
<TabsContent value="code" className="h-full m-0">

View File

@@ -13,8 +13,9 @@ import {
CheckCircle,
Warning
} from '@phosphor-icons/react'
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig } from '@/types/project'
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig, NextJsConfig } from '@/types/project'
import { SeedDataStatus } from '@/components/atoms'
import { GitHubBuildStatus } from '@/components/molecules/GitHubBuildStatus'
interface ProjectDashboardProps {
files: ProjectFile[]
@@ -25,6 +26,7 @@ interface ProjectDashboardProps {
storybookStories: StorybookStory[]
unitTests: UnitTest[]
flaskConfig: FlaskConfig
nextjsConfig: NextJsConfig
}
export function ProjectDashboard({
@@ -36,6 +38,7 @@ export function ProjectDashboard({
storybookStories,
unitTests,
flaskConfig,
nextjsConfig,
}: ProjectDashboardProps) {
const totalFiles = files.length
const totalModels = models.length
@@ -134,6 +137,13 @@ export function ProjectDashboard({
<SeedDataStatus />
{nextjsConfig?.githubRepo && (
<GitHubBuildStatus
owner={nextjsConfig.githubRepo.owner}
repo={nextjsConfig.githubRepo.repo}
/>
)}
<Card>
<CardHeader>
<CardTitle>Project Details</CardTitle>

View File

@@ -0,0 +1,271 @@
import { useState, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
GitBranch,
CheckCircle,
XCircle,
Clock,
ArrowSquareOut,
Warning
} from '@phosphor-icons/react'
import { Skeleton } from '@/components/ui/skeleton'
interface WorkflowRun {
id: number
name: string
status: string
conclusion: string | null
created_at: string
updated_at: string
html_url: string
head_branch: string
head_sha: string
event: string
}
interface GitHubBuildStatusProps {
owner: string
repo: string
}
export function GitHubBuildStatus({ owner, repo }: GitHubBuildStatusProps) {
const [workflows, setWorkflows] = useState<WorkflowRun[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetchWorkflowRuns()
}, [owner, repo])
const fetchWorkflowRuns = async () => {
try {
setLoading(true)
setError(null)
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/actions/runs?per_page=5`,
{
headers: {
'Accept': 'application/vnd.github.v3+json',
},
}
)
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`)
}
const data = await response.json()
setWorkflows(data.workflow_runs || [])
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch workflows')
} finally {
setLoading(false)
}
}
const getStatusIcon = (status: string, conclusion: string | null) => {
if (status === 'completed') {
if (conclusion === 'success') {
return <CheckCircle size={20} weight="fill" className="text-green-500" />
}
if (conclusion === 'failure') {
return <XCircle size={20} weight="fill" className="text-red-500" />
}
if (conclusion === 'cancelled') {
return <Warning size={20} weight="fill" className="text-yellow-500" />
}
}
return <Clock size={20} weight="duotone" className="text-blue-500 animate-pulse" />
}
const getStatusBadge = (status: string, conclusion: string | null) => {
if (status === 'completed') {
if (conclusion === 'success') {
return <Badge className="bg-green-500/10 text-green-500 border-green-500/20">Success</Badge>
}
if (conclusion === 'failure') {
return <Badge variant="destructive">Failed</Badge>
}
if (conclusion === 'cancelled') {
return <Badge variant="secondary">Cancelled</Badge>
}
}
return <Badge variant="outline" className="border-blue-500/50 text-blue-500">Running</Badge>
}
const formatTime = (dateString: string) => {
const date = new Date(dateString)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / 60000)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
if (days > 0) return `${days}d ago`
if (hours > 0) return `${hours}h ago`
if (minutes > 0) return `${minutes}m ago`
return 'just now'
}
if (loading) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<GitBranch size={24} weight="duotone" />
GitHub Actions
</CardTitle>
<CardDescription>Recent workflow runs</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="flex items-center justify-between p-3 border border-border rounded-lg">
<div className="flex items-center gap-3 flex-1">
<Skeleton className="w-5 h-5 rounded-full" />
<div className="space-y-2 flex-1">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-48" />
</div>
</div>
<Skeleton className="w-16 h-5 rounded" />
</div>
))}
</CardContent>
</Card>
)
}
if (error) {
return (
<Card className="bg-red-500/10 border-red-500/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<GitBranch size={24} weight="duotone" />
GitHub Actions
</CardTitle>
<CardDescription>Unable to fetch workflow status</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-start gap-3">
<XCircle size={20} weight="fill" className="text-red-500 mt-0.5" />
<div className="flex-1 space-y-2">
<p className="text-sm text-red-500">{error}</p>
<Button
size="sm"
variant="outline"
onClick={fetchWorkflowRuns}
className="text-xs"
>
Try Again
</Button>
</div>
</div>
</CardContent>
</Card>
)
}
if (workflows.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<GitBranch size={24} weight="duotone" />
GitHub Actions
</CardTitle>
<CardDescription>No workflow runs found</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
No GitHub Actions workflows have been run yet.
</p>
</CardContent>
</Card>
)
}
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<GitBranch size={24} weight="duotone" />
GitHub Actions
</CardTitle>
<CardDescription>Recent workflow runs from {owner}/{repo}</CardDescription>
</div>
<Button
size="sm"
variant="ghost"
onClick={fetchWorkflowRuns}
className="text-xs"
>
Refresh
</Button>
</div>
</CardHeader>
<CardContent className="space-y-3">
{workflows.map((workflow) => (
<div
key={workflow.id}
className="flex items-center justify-between p-3 border border-border rounded-lg hover:bg-accent/50 transition-colors"
>
<div className="flex items-center gap-3 flex-1 min-w-0">
{getStatusIcon(workflow.status, workflow.conclusion)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<p className="text-sm font-medium truncate">
{workflow.name}
</p>
{getStatusBadge(workflow.status, workflow.conclusion)}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-1">
<span className="truncate">{workflow.head_branch}</span>
<span></span>
<span>{formatTime(workflow.updated_at)}</span>
<span></span>
<span className="truncate">{workflow.event}</span>
</div>
</div>
</div>
<Button
size="sm"
variant="ghost"
asChild
className="ml-2"
>
<a
href={workflow.html_url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1"
>
<ArrowSquareOut size={16} />
</a>
</Button>
</div>
))}
<Button
size="sm"
variant="outline"
asChild
className="w-full text-xs"
>
<a
href={`https://github.com/${owner}/${repo}/actions`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2"
>
View All Workflows
<ArrowSquareOut size={14} />
</a>
</Button>
</CardContent>
</Card>
)
}

View File

@@ -5,6 +5,7 @@ export { EditorToolbar } from './EditorToolbar'
export { EmptyEditorState } from './EmptyEditorState'
export { EmptyState } from './EmptyState'
export { FileTabs } from './FileTabs'
export { GitHubBuildStatus } from './GitHubBuildStatus'
export { LabelWithBadge } from './LabelWithBadge'
export { LazyInlineMonacoEditor } from './LazyInlineMonacoEditor'
export { LazyMonacoEditor, preloadMonacoEditor } from './LazyMonacoEditor'

View File

@@ -9,7 +9,7 @@
"shortcut": "ctrl+1",
"order": 1,
"props": {
"state": ["files", "models", "components", "theme", "playwrightTests", "storybookStories", "unitTests", "flaskConfig"]
"state": ["files", "models", "components", "theme", "playwrightTests", "storybookStories", "unitTests", "flaskConfig", "nextjsConfig"]
}
},
{

View File

@@ -33,6 +33,10 @@ const DEFAULT_NEXTJS_CONFIG: NextJsConfig = {
appRouter: true,
importAlias: '@/*',
turbopack: false,
githubRepo: {
owner: 'johndoe6345789',
repo: 'low-code-react-app-b',
},
}
const DEFAULT_NPM_SETTINGS: NpmSettings = {

View File

@@ -169,6 +169,10 @@ export interface NextJsConfig {
appRouter: boolean
importAlias: string
turbopack?: boolean
githubRepo?: {
owner: string
repo: string
}
}
export interface NpmPackage {