fix(frontend): layout viewport selector, credentials tab order, Attackers.css shared import
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user