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:
hsiegeln
2026-04-23 13:30:48 +02:00
parent b0995d84bc
commit d1150e5dd8
3 changed files with 80 additions and 12 deletions

View File

@@ -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);
}

View File

@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import { useInfiniteApplicationLogs } from '../../../../api/queries/logs';
import type { Deployment } from '../../../../api/queries/admin/apps';
import { instanceIdsFor } from './instance-id';
import styles from './CheckpointDetailDrawer.module.css';
interface Props {
deployment: Deployment;
@@ -12,7 +13,8 @@ interface Props {
export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
const allInstanceIds = useMemo(
() => 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');
@@ -28,7 +30,7 @@ export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
return (
<div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center', padding: '8px 0', fontSize: 12 }}>
<div className={styles.filterBar}>
<label>
Replica:&nbsp;
<select
@@ -46,11 +48,11 @@ export function LogsPanel({ deployment, appSlug, envSlug }: Props) {
</label>
</div>
{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) => (
<div key={i} style={{ fontFamily: 'monospace', fontSize: 11, padding: '2px 0' }}>
<span style={{ color: 'var(--text-muted)' }}>{entry.timestamp}</span>{' '}
<div key={i} className={styles.logRow}>
<span className={styles.logTimestamp}>{entry.timestamp}</span>{' '}
<span>[{entry.level}]</span>{' '}
<span>{entry.message}</span>
</div>

View File

@@ -5,6 +5,7 @@ import type { Deployment, AppVersion } from '../../../../api/queries/admin/apps'
import { LogsPanel } from './LogsPanel';
import { ConfigPanel } from './ConfigPanel';
import { timeAgo } from '../../../../utils/format-utils';
import styles from './CheckpointDetailDrawer.module.css';
interface Props {
open: boolean;
@@ -25,20 +26,20 @@ export function CheckpointDetailDrawer({
const archived = !version;
const title = (
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div className={styles.titleRow}>
<Badge label={version ? `v${version.version}` : '?'} color="auto" />
<span style={{ fontFamily: 'monospace', fontSize: 13 }}>
<span className={styles.titleJar}>
{version?.jarFilename ?? 'JAR pruned'}
</span>
<span style={{ fontSize: 11, padding: '2px 8px', borderRadius: 3, background: 'var(--bg-inset)' }}>
<span className={styles.statusPill}>
{deployment.status}
</span>
</div>
);
const footer = (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 }}>
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
<div className={styles.footerRow}>
<span className={styles.footerHint}>
Restoring hydrates the form you'll still need to Redeploy.
</span>
<Button
@@ -54,7 +55,7 @@ export function CheckpointDetailDrawer({
return (
<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>
{deployment.deployedAt && <> · {timeAgo(deployment.deployedAt)} ({deployment.deployedAt})</>}
{' · '}Strategy: {deployment.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling'}
@@ -68,7 +69,7 @@ export function CheckpointDetailDrawer({
{ value: 'config', label: 'Config' },
]}
/>
<div style={{ marginTop: 12 }}>
<div className={styles.tabContent}>
{tab === 'logs' && (
<LogsPanel deployment={deployment} appSlug={appSlug} envSlug={envSlug} />
)}