fix(frontend): layout viewport selector, credentials tab order, Attackers.css shared import

This commit is contained in:
2026-04-30 22:16:46 -04:00
parent eb34d0b1ea
commit 3cb0203d07
8 changed files with 55 additions and 34 deletions

View File

@@ -1,7 +1,7 @@
.attackers-root { .attackers-root {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 20px;
} }
.ak-filter-row { .ak-filter-row {

View File

@@ -8,6 +8,7 @@ import EmptyState from './EmptyState/EmptyState';
import { useFocusSearch } from '../hooks/useFocusSearch'; import { useFocusSearch } from '../hooks/useFocusSearch';
import { useCampaignStream } from './useCampaignStream'; import { useCampaignStream } from './useCampaignStream';
import './Dashboard.css'; import './Dashboard.css';
import './Attackers.css';
interface CampaignEntry { interface CampaignEntry {
uuid: string; uuid: string;

View File

@@ -224,23 +224,6 @@ const Credentials: React.FC = () => {
</div> </div>
</div> </div>
<div className="seg-group" role="tablist" style={{ marginBottom: 12 }}>
<button
type="button"
className={tab === 'creds' ? 'active' : ''}
onClick={() => setTab('creds')}
>
CREDS
</button>
<button
type="button"
className={tab === 'reuse' ? 'active' : ''}
onClick={() => setTab('reuse')}
>
REUSE
</button>
</div>
{tab === 'creds' && ( {tab === 'creds' && (
<form className="controls-row" onSubmit={handleSearch}> <form className="controls-row" onSubmit={handleSearch}>
<div className="search-container"> <div className="search-container">
@@ -275,6 +258,23 @@ const Credentials: React.FC = () => {
</form> </form>
)} )}
<div className="seg-group" role="tablist" style={{ marginBottom: 12 }}>
<button
type="button"
className={tab === 'creds' ? 'active' : ''}
onClick={() => setTab('creds')}
>
CREDS
</button>
<button
type="button"
className={tab === 'reuse' ? 'active' : ''}
onClick={() => setTab('reuse')}
>
REUSE
</button>
</div>
<div className="logs-section"> <div className="logs-section">
<div className="section-header"> <div className="section-header">
<div className="section-title"> <div className="section-title">

View File

@@ -5,6 +5,12 @@
height: 100%; height: 100%;
} }
/* Detail/scroll pages: grow with content instead of locking to viewport */
.dashboard.page-scroll {
height: auto;
min-height: 0;
}
/* Page header */ /* Page header */
.page-header { .page-header {
display: flex; display: flex;
@@ -275,6 +281,7 @@
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
min-height: 0; min-height: 0;
max-height: none;
} }
.dash-side { .dash-side {

View File

@@ -8,6 +8,7 @@ import EmptyState from './EmptyState/EmptyState';
import { useFocusSearch } from '../hooks/useFocusSearch'; import { useFocusSearch } from '../hooks/useFocusSearch';
import { useIdentityStream } from './useIdentityStream'; import { useIdentityStream } from './useIdentityStream';
import './Dashboard.css'; import './Dashboard.css';
import './Attackers.css';
interface IdentityEntry { interface IdentityEntry {
uuid: string; uuid: string;

View File

@@ -348,6 +348,6 @@
} }
/* Dashboard fills the viewport without scrolling — panels scroll internally */ /* Dashboard fills the viewport without scrolling — panels scroll internally */
.content-viewport:has(> .dashboard) { .content-viewport:has(> .dashboard > .dash-grid) {
overflow: hidden; overflow: hidden;
} }

View File

@@ -66,7 +66,9 @@ const Layout: React.FC<LayoutProps> = ({
alertCount: alertCountProp = 0, alertCount: alertCountProp = 0,
build = 'v0.1', build = 'v0.1',
}) => { }) => {
const [sidebarOpen, setSidebarOpen] = useState(true); const [sidebarOpen, setSidebarOpen] = useState(() => {
try { return localStorage.getItem('decnet_sidebar_open') !== 'false'; } catch { return true; }
});
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [systemActive, setSystemActive] = useState(false); const [systemActive, setSystemActive] = useState(false);
const [clockTime, setClockTime] = useState(() => formatClock(new Date())); const [clockTime, setClockTime] = useState(() => formatClock(new Date()));
@@ -106,7 +108,11 @@ const Layout: React.FC<LayoutProps> = ({
<div className="sidebar-header"> <div className="sidebar-header">
<Activity size={24} className="violet-accent" /> <Activity size={24} className="violet-accent" />
{sidebarOpen && <span className="logo-text">DECNET</span>} {sidebarOpen && <span className="logo-text">DECNET</span>}
<button className="toggle-btn" onClick={() => setSidebarOpen(!sidebarOpen)}> <button className="toggle-btn" onClick={() => setSidebarOpen(v => {
const next = !v;
try { localStorage.setItem('decnet_sidebar_open', String(next)); } catch {}
return next;
})}>
{sidebarOpen ? <X size={20} /> : <Menu size={20} />} {sidebarOpen ? <X size={20} /> : <Menu size={20} />}
</button> </button>
</div> </div>
@@ -255,13 +261,21 @@ interface NavGroupProps {
} }
const NavGroup: React.FC<NavGroupProps> = ({ label, icon, open, children }) => { const NavGroup: React.FC<NavGroupProps> = ({ label, icon, open, children }) => {
const [expanded, setExpanded] = useState(true); const storageKey = `decnet_navgroup_${label.toLowerCase()}`;
const [expanded, setExpanded] = useState(() => {
try { return localStorage.getItem(storageKey) === 'true'; } catch { return false; }
});
const toggle = () => setExpanded(v => {
const next = !v;
try { localStorage.setItem(storageKey, String(next)); } catch {}
return next;
});
return ( return (
<div className="nav-group"> <div className="nav-group">
<button <button
type="button" type="button"
className="nav-item nav-group-toggle" className="nav-item nav-group-toggle"
onClick={() => setExpanded((v) => !v)} onClick={toggle}
> >
{icon} {icon}
{open && ( {open && (

View File

@@ -274,17 +274,15 @@ const LiveLogs: React.FC = () => {
</div> </div>
<div className="section-actions"> <div className="section-actions">
<span>SHOWING {filteredLogs.length} OF {totalLogs.toLocaleString()}</span> <span>SHOWING {filteredLogs.length} OF {totalLogs.toLocaleString()}</span>
{!streaming && ( <div className="pager" style={{ marginLeft: 16 }}>
<div className="pager" style={{ marginLeft: 16 }}> <span className="dim">Page {page} of {totalPages}</span>
<span className="dim">Page {page} of {totalPages}</span> <button disabled={page === 1} onClick={() => changePage(page - 1)} aria-label="Previous page">
<button disabled={page === 1} onClick={() => changePage(page - 1)} aria-label="Previous page"> <ChevronLeft size={14} />
<ChevronLeft size={14} /> </button>
</button> <button disabled={page >= totalPages} onClick={() => changePage(page + 1)} aria-label="Next page">
<button disabled={page >= totalPages} onClick={() => changePage(page + 1)} aria-label="Next page"> <ChevronRight size={14} />
<ChevronRight size={14} /> </button>
</button> </div>
</div>
)}
</div> </div>
</div> </div>