fix(web/dashboard): wrap long kv-chips instead of blowing out the EVENT column
Key:value chips in the live-feed event cell used the default .chip
style, which is white-space: nowrap + inline-flex. A long cmd: value
(attacker-controlled shell strings, URLs, base64 payloads) stretched
the chip horizontally past the column, pushing the whole table into
horizontal scroll and clipping subsequent columns off-screen.
Add a chip-kv variant that allows the value to wrap inside a
max-width: 100% chip (word-break: break-word, overflow-wrap: anywhere
for dense strings with no natural break). The key-label stays on the
first line via flex-shrink: 0. Short values (uid: 0, user: root)
stay tight; long ones wrap onto multiple lines inside the chip.
Also set minWidth: 0 on the EVENT td + nested flex containers so
flex children honour the column width instead of growing to fit
content. Added title={k: v} on each chip for full-value hover in
case the wrap is still clipped.
This commit is contained in:
@@ -71,6 +71,22 @@
|
||||
background: rgba(255, 65, 65, 0.1);
|
||||
}
|
||||
|
||||
/* Key-value chips in the live-feed event column. Values are unbounded
|
||||
(attacker-supplied command strings, URLs, base64 payloads), so these
|
||||
must wrap inside the chip rather than inherit the default nowrap —
|
||||
otherwise a single `cmd: echo <2KB>` blows out the table width. */
|
||||
.chip.chip-kv {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
max-width: 100%;
|
||||
align-items: flex-start;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.chip.chip-kv > .dim {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Breach banner */
|
||||
.breach-banner {
|
||||
background: rgba(255, 65, 65, 0.1);
|
||||
|
||||
@@ -359,8 +359,8 @@ const Dashboard: React.FC<DashboardProps> = ({ searchQuery }) => {
|
||||
<td className="violet-accent">{log.decky}</td>
|
||||
<td><span className="chip dim-chip">{log.service}</span></td>
|
||||
<td className="matrix-text">{log.attacker_ip}</td>
|
||||
<td>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<td style={{ minWidth: 0, maxWidth: 0, width: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: '0.78rem', color: 'var(--text-color)' }}>
|
||||
{(() => {
|
||||
const et = log.event_type && log.event_type !== '-' ? log.event_type : null;
|
||||
@@ -378,7 +378,7 @@ const Dashboard: React.FC<DashboardProps> = ({ searchQuery }) => {
|
||||
})()}
|
||||
</div>
|
||||
{Object.keys(parsedFields).length > 0 && (
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', minWidth: 0 }}>
|
||||
{parsedFields.stored_as != null && (
|
||||
<button
|
||||
onClick={() => setArtifact({
|
||||
@@ -404,12 +404,20 @@ const Dashboard: React.FC<DashboardProps> = ({ searchQuery }) => {
|
||||
)}
|
||||
{Object.entries(parsedFields)
|
||||
.filter(([k]) => k !== 'meta_json_b64' && k !== 'stored_as')
|
||||
.map(([k, v]) => (
|
||||
<span key={k} className="chip matrix" style={{ fontSize: '0.62rem' }}>
|
||||
<span className="dim" style={{ marginRight: 3 }}>{k}:</span>
|
||||
{typeof v === 'object' ? JSON.stringify(v) : String(v)}
|
||||
</span>
|
||||
))}
|
||||
.map(([k, v]) => {
|
||||
const rendered = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
||||
return (
|
||||
<span
|
||||
key={k}
|
||||
className="chip matrix chip-kv"
|
||||
style={{ fontSize: '0.62rem' }}
|
||||
title={`${k}: ${rendered}`}
|
||||
>
|
||||
<span className="dim" style={{ marginRight: 3 }}>{k}:</span>
|
||||
{rendered}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user