ui(deploy): DeploymentTab + flex-grow StartupLogPanel
DeploymentTab composes StatusCard, DeploymentProgress, StartupLogPanel, and HistoryDisclosure for the latest deployment. StartupLogPanel gains an optional className prop, drops the fixed maxHeight, and its .panel rule uses flex-column + min-height:0 so a parent can drive its height. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,9 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ interface StartupLogPanelProps {
|
|||||||
deployment: Deployment;
|
deployment: Deployment;
|
||||||
appSlug: string;
|
appSlug: string;
|
||||||
envSlug: string;
|
envSlug: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StartupLogPanel({ deployment, appSlug, envSlug }: StartupLogPanelProps) {
|
export function StartupLogPanel({ deployment, appSlug, envSlug, className }: StartupLogPanelProps) {
|
||||||
const isStarting = deployment.status === 'STARTING';
|
const isStarting = deployment.status === 'STARTING';
|
||||||
const isFailed = deployment.status === 'FAILED';
|
const isFailed = deployment.status === 'FAILED';
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ export function StartupLogPanel({ deployment, appSlug, envSlug }: StartupLogPane
|
|||||||
if (entries.length === 0 && !isStarting) return null;
|
if (entries.length === 0 && !isStarting) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.panel}>
|
<div className={`${styles.panel}${className ? ` ${className}` : ''}`}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.headerLeft}>
|
<div className={styles.headerLeft}>
|
||||||
<span className={styles.title}>Startup Logs</span>
|
<span className={styles.title}>Startup Logs</span>
|
||||||
@@ -38,7 +39,7 @@ export function StartupLogPanel({ deployment, appSlug, envSlug }: StartupLogPane
|
|||||||
<span className={styles.lineCount}>{entries.length} lines</span>
|
<span className={styles.lineCount}>{entries.length} lines</span>
|
||||||
</div>
|
</div>
|
||||||
{entries.length > 0 ? (
|
{entries.length > 0 ? (
|
||||||
<LogViewer entries={entries as unknown as LogEntry[]} maxHeight={300} />
|
<LogViewer entries={entries as unknown as LogEntry[]} />
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.empty}>Waiting for container output...</div>
|
<div className={styles.empty}>Waiting for container output...</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import type { Deployment, AppVersion } from '../../../../api/queries/admin/apps';
|
||||||
|
import { DeploymentProgress } from '../../../../components/DeploymentProgress';
|
||||||
|
import { StartupLogPanel } from '../../../../components/StartupLogPanel';
|
||||||
|
import { EmptyState } from '@cameleer/design-system';
|
||||||
|
import { StatusCard } from './StatusCard';
|
||||||
|
import { HistoryDisclosure } from './HistoryDisclosure';
|
||||||
|
import styles from '../AppDeploymentPage.module.css';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
deployments: Deployment[];
|
||||||
|
versions: AppVersion[];
|
||||||
|
appSlug: string;
|
||||||
|
envSlug: string;
|
||||||
|
externalUrl: string;
|
||||||
|
onStop: (deploymentId: string) => void;
|
||||||
|
onStart: (deploymentId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeploymentTab({ deployments, versions, appSlug, envSlug, externalUrl, onStop, onStart }: Props) {
|
||||||
|
const latest = deployments
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? ''))[0] ?? null;
|
||||||
|
|
||||||
|
if (!latest) {
|
||||||
|
return <EmptyState title="No deployments yet" description="Save your configuration and click Redeploy to launch." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = versions.find((v) => v.id === latest.appVersionId) ?? null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.deploymentTab}>
|
||||||
|
<StatusCard
|
||||||
|
deployment={latest}
|
||||||
|
version={version}
|
||||||
|
externalUrl={externalUrl}
|
||||||
|
onStop={() => onStop(latest.id)}
|
||||||
|
onStart={() => onStart(latest.id)}
|
||||||
|
/>
|
||||||
|
{latest.status === 'STARTING' && (
|
||||||
|
<DeploymentProgress currentStage={latest.deployStage} failed={false} />
|
||||||
|
)}
|
||||||
|
{latest.status === 'FAILED' && (
|
||||||
|
<DeploymentProgress currentStage={latest.deployStage} failed />
|
||||||
|
)}
|
||||||
|
<StartupLogPanel deployment={latest} appSlug={appSlug} envSlug={envSlug}
|
||||||
|
className={styles.logFill} />
|
||||||
|
<HistoryDisclosure deployments={deployments} versions={versions}
|
||||||
|
appSlug={appSlug} envSlug={envSlug} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user