refactor(ui): drawer CSS module + narrow LogsPanel memo deps
Extract 14 inline style blocks from CheckpointDetailDrawer index.tsx and LogsPanel.tsx into a shared CSS module using DS CSS variables throughout. Narrow the LogsPanel useMemo dep array from the full deployment object to deployment.id + deployment.replicaStates to prevent spurious query invalidation on every TanStack Query poll. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
/* index.tsx */
|
||||||
|
.titleRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleJar {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusPill {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--bg-inset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metaLine {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabContent {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerRow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerHint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LogsPanel.tsx */
|
||||||
|
.filterBar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyState {
|
||||||
|
padding: 16px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logRow {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logTimestamp {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
|
|||||||
import { useInfiniteApplicationLogs } from '../../../../api/queries/logs';
|
import { useInfiniteApplicationLogs } from '../../../../api/queries/logs';
|
||||||
import type { Deployment } from '../../../../api/queries/admin/apps';
|
import type { Deployment } from '../../../../api/queries/admin/apps';
|
||||||
import { instanceIdsFor } from './instance-id';
|
import { instanceIdsFor } from './instance-id';
|
||||||
|
import styles from './CheckpointDetailDrawer.module.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
deployment: Deployment;
|
deployment: Deployment;
|
||||||
@@ -12,7 +13,8 @@ interface Props {
|
|||||||
export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
|
export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
|
||||||
const allInstanceIds = useMemo(
|
const allInstanceIds = useMemo(
|
||||||
() => instanceIdsFor(deployment, envSlug, appSlug),
|
() => instanceIdsFor(deployment, envSlug, appSlug),
|
||||||
[deployment, envSlug, appSlug]
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[deployment.id, deployment.replicaStates, envSlug, appSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [replicaFilter, setReplicaFilter] = useState<'all' | number>('all');
|
const [replicaFilter, setReplicaFilter] = useState<'all' | number>('all');
|
||||||
@@ -28,7 +30,7 @@ export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', padding: '8px 0', fontSize: 12 }}>
|
<div className={styles.filterBar}>
|
||||||
<label>
|
<label>
|
||||||
Replica:
|
Replica:
|
||||||
<select
|
<select
|
||||||
@@ -46,11 +48,11 @@ export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{logs.items.length === 0 && !logs.isLoading && (
|
{logs.items.length === 0 && !logs.isLoading && (
|
||||||
<div style={{ padding: 16, color: 'var(--text-muted)' }}>No logs for this deployment.</div>
|
<div className={styles.emptyState}>No logs for this deployment.</div>
|
||||||
)}
|
)}
|
||||||
{logs.items.map((entry, i) => (
|
{logs.items.map((entry, i) => (
|
||||||
<div key={i} style={{ fontFamily: 'monospace', fontSize: 11, padding: '2px 0' }}>
|
<div key={i} className={styles.logRow}>
|
||||||
<span style={{ color: 'var(--text-muted)' }}>{entry.timestamp}</span>{' '}
|
<span className={styles.logTimestamp}>{entry.timestamp}</span>{' '}
|
||||||
<span>[{entry.level}]</span>{' '}
|
<span>[{entry.level}]</span>{' '}
|
||||||
<span>{entry.message}</span>
|
<span>{entry.message}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { Deployment, AppVersion } from '../../../../api/queries/admin/apps'
|
|||||||
import { LogsPanel } from './LogsPanel';
|
import { LogsPanel } from './LogsPanel';
|
||||||
import { ConfigPanel } from './ConfigPanel';
|
import { ConfigPanel } from './ConfigPanel';
|
||||||
import { timeAgo } from '../../../../utils/format-utils';
|
import { timeAgo } from '../../../../utils/format-utils';
|
||||||
|
import styles from './CheckpointDetailDrawer.module.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -25,20 +26,20 @@ export function CheckpointDetailDrawer({
|
|||||||
const archived = !version;
|
const archived = !version;
|
||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div className={styles.titleRow}>
|
||||||
<Badge label={version ? `v${version.version}` : '?'} color="auto" />
|
<Badge label={version ? `v${version.version}` : '?'} color="auto" />
|
||||||
<span style={{ fontFamily: 'monospace', fontSize: 13 }}>
|
<span className={styles.titleJar}>
|
||||||
{version?.jarFilename ?? 'JAR pruned'}
|
{version?.jarFilename ?? 'JAR pruned'}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 11, padding: '2px 8px', borderRadius: 3, background: 'var(--bg-inset)' }}>
|
<span className={styles.statusPill}>
|
||||||
{deployment.status}
|
{deployment.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const footer = (
|
const footer = (
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
|
<div className={styles.footerRow}>
|
||||||
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
|
<span className={styles.footerHint}>
|
||||||
Restoring hydrates the form — you'll still need to Redeploy.
|
Restoring hydrates the form — you'll still need to Redeploy.
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -54,7 +55,7 @@ export function CheckpointDetailDrawer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SideDrawer open={open} onClose={onClose} title={title} size="lg" footer={footer}>
|
<SideDrawer open={open} onClose={onClose} title={title} size="lg" footer={footer}>
|
||||||
<div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5, marginBottom: 12 }}>
|
<div className={styles.metaLine}>
|
||||||
Deployed by <b>{deployment.createdBy ?? '—'}</b>
|
Deployed by <b>{deployment.createdBy ?? '—'}</b>
|
||||||
{deployment.deployedAt && <> · {timeAgo(deployment.deployedAt)} ({deployment.deployedAt})</>}
|
{deployment.deployedAt && <> · {timeAgo(deployment.deployedAt)} ({deployment.deployedAt})</>}
|
||||||
{' · '}Strategy: {deployment.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling'}
|
{' · '}Strategy: {deployment.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling'}
|
||||||
@@ -68,7 +69,7 @@ export function CheckpointDetailDrawer({
|
|||||||
{ value: 'config', label: 'Config' },
|
{ value: 'config', label: 'Config' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div style={{ marginTop: 12 }}>
|
<div className={styles.tabContent}>
|
||||||
{tab === 'logs' && (
|
{tab === 'logs' && (
|
||||||
<LogsPanel deployment={deployment} appSlug={appSlug} envSlug={envSlug} />
|
<LogsPanel deployment={deployment} appSlug={appSlug} envSlug={envSlug} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user