Files
edgeguard-native/management-ui/src/styles/enterprise.css
Debian ca03e69637 feat: Network/IP-Verwaltung + Mailguard-Design-Übernahme
Backend:
* Migration 0009_networks: network_interfaces (ethernet|vlan|bond|
  bridge|wireguard, role wan|lan|dmz|mgmt|cluster, parent + vlan_id
  für VLANs) + ip_addresses (interface_id FK, address+prefix, is_vip
  + vip_priority für Cluster-Failover-VIPs).
* Repos services/networkifs + services/ipaddresses + Models +
  Handler /api/v1/network-interfaces (CRUD + /:id/ip-addresses)
  und /api/v1/ip-addresses (CRUD).
* /api/v1/system/interfaces refactored auf Go-natives net.Interfaces()
  statt `ip -j addr show` shell-out — die systemd-Sandbox blockt
  AF_NETLINK auch für Go's runtime, deswegen edgeguard-api.service
  RestrictAddressFamilies um AF_NETLINK ergänzt. Output-Shape
  bleibt identisch (ifindex, ifname, flags[], mtu, link_type,
  address, addr_info[]) — Frontend muss nicht angepasst werden.

Frontend:
* Networks-Page (/networks): "System-discovered Interfaces"
  read-only Tags-Card oben, deklarierte Interfaces unten als
  Tabelle mit Modal-CRUD; Type-Switch zeigt parent+vlan_id-Felder
  bei type=vlan; Role-Tags farbig (wan blau, lan grün, dmz orange,
  mgmt purple, cluster magenta).
* IPAddresses-Page (/ip-addresses): Tabelle pro Interface, VIP-
  Toggle blendet vip_priority-Eingabe ein. Goldenes VIP-Tag in der
  Liste.
* Sidebar erweitert um Networks + IP-Adressen + section-grouping.

Design 1:1 von mail-gateway/management-ui/ übernommen:
* enterprise.css verbatim (Inter-Font via Google CDN statt local
  woff2), Sidebar 240px dunkler Gradient #0B1426→#101D33→#0D1829,
  branding-accent #1677ff für Active-State, abgerundete Cards mit
  shadow-Token, Header weiß mit subtilem backdrop-filter.
* AntD-Theme-Tokens: colorPrimary #0EA5E9, fontSize 13, fontFamily
  'Inter', controlHeight 34, borderRadius 6.
* Layout-Komponenten neu strukturiert: AppLayout/Sidebar/Header
  matchen mailguard-Klassen-Naming (.app-layout, .main-content,
  .sidebar-section, .sidebar-menu-item.active, .header-left, …).
* Sidebar mit 4 Sektionen (Übersicht / Routing / Netzwerk / System)
  + Logo-Header + Versions-Footer.

Live-deployed auf 89.163.205.6: Networks-Endpoint listet eth0
(89.163.205.6/24, MAC bc:24:11:64:29:e8) + lo, frontend zeigt sie
als System-Tags in der Networks-Page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 16:08:44 +02:00

2711 lines
75 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* EdgeGuard — Enterprise Light Theme (übernommen 1:1 aus mail-gateway). */
/* Inter via Google Fonts CDN. mail-gateway hostet woff2 lokal in
* public/fonts/; EdgeGuard nutzt CDN um keine Font-Binaries zu shippen. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
/* ── Design tokens ──────────────────────────────────────────────────────────── */
:root {
--branding-primary: #1677ff;
--branding-accent: #1677ff;
--radius: 6px;
--radius-md: 8px;
--radius-lg: 10px;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.05);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-weight: 400;
background: #F8FAFC;
color: #334155;
font-size: 13px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
letter-spacing: -0.01em;
}
/* Global typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600 !important;
color: #0F172A !important;
letter-spacing: -0.02em;
}
.ant-typography { color: #334155; }
.ant-modal-title { font-weight: 600 !important; }
.ant-card-head-title { font-weight: 600 !important; }
.ant-tabs-tab { font-weight: 500 !important; }
.ant-btn { font-weight: 500 !important; letter-spacing: -0.01em; }
/* === LAYOUT === */
.app-layout {
display: flex;
min-height: 100vh;
}
/* === SIDEBAR === */
.sidebar {
width: 240px;
min-height: 100vh;
background: linear-gradient(180deg, #0B1426 0%, #101D33 50%, #0D1829 100%);
display: flex;
flex-direction: column;
position: fixed;
left: 0;
top: 0;
bottom: 0;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255,255,255,0.1) transparent;
z-index: 100;
}
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
.sidebar-logo {
padding: 20px 16px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.sidebar-logo-icon {
width: 32px;
height: 32px;
background: linear-gradient(135deg, #1677ff, #1677ff);
border-radius: 8px;
box-shadow: 0 0 12px rgba(0, 212, 170, 0.4);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: 700;
color: white;
}
.sidebar-logo-text {
font-size: 16px;
font-weight: 600;
color: #ffffff;
}
.sidebar-section {
padding: 16px 16px 4px;
}
.sidebar-section-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #475569;
margin-bottom: 4px;
}
.sidebar-menu {
list-style: none;
padding: 4px 8px;
}
.sidebar-menu-item {
position: relative;
border-radius: 6px;
margin-bottom: 1px;
}
.sidebar-menu-item a,
.sidebar-menu-item button {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
font-size: 14px;
/* CSS-Variable --nmg-sidebar-text wird vom Sidebar-Komponente
gesetzt wenn das Branding eine eigene Textfarbe hat. Fallback:
#94A3B8 (Light-Slate, ursprünglicher Hardcode). */
color: var(--nmg-sidebar-text, #94A3B8);
text-decoration: none;
border-radius: 6px;
transition: all 0.15s ease;
background: none;
border: none;
cursor: pointer;
width: 100%;
}
.sidebar-menu-item a:hover,
.sidebar-menu-item button:hover {
background: rgba(255,255,255,0.05);
color: #CBD5E1;
}
.sidebar-menu-item.active::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 20px;
background: var(--branding-accent, #1677ff);
border-radius: 0 2px 2px 0;
box-shadow: 0 0 8px color-mix(in srgb, var(--branding-accent, #1677ff) 40%, transparent);
}
.sidebar-menu-item.active a,
.sidebar-menu-item.active button {
background: color-mix(in srgb, var(--branding-accent, #1677ff) 10%, transparent);
color: var(--branding-accent, #1677ff);
}
.sidebar-version {
margin-top: auto;
padding: 16px;
font-size: 11px;
color: #475569;
text-align: center;
border-top: 1px solid rgba(255,255,255,0.06);
}
.sidebar-logo-img {
height: 28px;
width: auto;
flex-shrink: 0;
}
/* Mobile: sidebar slides in, overlay dims the main content. */
.sidebar-overlay {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.5);
z-index: 99;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.header-menu-toggle {
display: none;
}
.header-user {
color: #334155;
font-size: 13px;
}
@media (max-width: 900px) {
.sidebar {
transform: translateX(-100%);
transition: transform 0.2s ease;
}
.sidebar.open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.header-menu-toggle {
display: inline-flex;
}
.header-user {
display: none;
}
}
/* === MAIN CONTENT === */
.main-content {
margin-left: 240px;
flex: 1;
display: flex;
flex-direction: column;
min-height: 100vh;
overflow-x: hidden;
min-width: 0;
}
/* === HEADER === */
.header {
height: 56px;
background: #ffffff;
border-bottom: 1px solid #E5E7EB;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: blur(8px);
background: rgba(255,255,255,0.95);
}
.header-title {
font-size: 15px;
font-weight: 500;
color: #1E293B;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
/* === CONTENT AREA === */
.content-area {
padding: 16px;
flex: 1;
overflow-x: hidden;
min-width: 0;
}
.content-card {
background: #ffffff;
border: 1px solid #E2E8F0;
border-radius: 12px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.02), 0 1px 2px rgba(0,0,0,0.03);
margin-bottom: 16px;
}
/* === TABS (Pill-Style) === */
.tab-bar {
display: flex;
gap: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.tab-item {
padding: 6px 14px;
font-size: 13px;
font-weight: 500;
border: 1.5px solid #E2E8F0;
border-radius: 8px;
color: #64748B;
background: #ffffff;
cursor: pointer;
transition: all 0.15s ease;
}
.tab-item:hover {
border-color: #CBD5E1;
color: #475569;
}
.tab-item.active {
border-color: #1677ff;
background: #F0F9FF;
color: #1d4ed8;
}
/* === STATUS DOTS === */
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
display: inline-block;
}
.status-dot.online {
background: #1677ff;
box-shadow: 0 0 8px rgba(0, 212, 170, 0.5);
}
.status-dot.offline {
background: #EF4444;
box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
}
/* === BADGES === */
.badge {
padding: 2px 8px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
}
.badge-success { background: rgba(0, 212, 170, 0.1); color: #1677ff; }
.badge-danger { background: rgba(239, 68, 68, 0.1); color: #EF4444; }
.badge-info { background: rgba(14, 165, 233, 0.1); color: #1677ff; }
.badge-warning { background: rgba(245, 158, 11, 0.1); color: #D97706; }
/* ═══════════════════════════════════════════════════════════════════════════════
PREMIUM ANT DESIGN OVERRIDES — High-End Enterprise Look
═══════════════════════════════════════════════════════════════════════════════ */
/* ── Tables — shadcn-clean ───────────────────────────────────────────────────── */
.ant-table {
border-radius: 8px !important;
overflow: hidden;
}
.ant-table table {
table-layout: auto !important;
}
.ant-table-thead > tr > th {
white-space: nowrap !important;
}
.ant-table-tbody > tr > td {
/* overflow-wrap statt word-break: nur LANGE Strings (URLs,
Mailadressen ohne Spaces) brechen, kurze formatierte Strings
wie Timestamps bleiben in einer Zeile. word-break:break-word hat
22.12.2025 12:30 in „22.1 2.20 25 12: 30" gehackt — Beobachtung
1.5.61 auf der Sandbox-Page. */
overflow-wrap: anywhere;
word-break: normal;
}
.ant-table-thead > tr > th,
.ant-table-thead > tr > td {
background: #F8FAFC !important;
color: #64748B !important;
font-weight: 500 !important;
font-size: 12px !important;
text-transform: uppercase !important;
letter-spacing: 0.4px !important;
border-bottom: 1px solid #E2E8F0 !important;
padding: 10px 16px !important;
}
.ant-table-tbody > tr > td {
padding: 12px 16px !important;
border-bottom: 1px solid #F1F5F9 !important;
font-size: 13px !important;
color: #334155 !important;
transition: background 0.1s ease !important;
}
.ant-table-tbody > tr:hover > td {
background: #F8FAFC !important;
}
.ant-table-tbody > tr:last-child > td {
border-bottom: none !important;
}
/* Scrollable tables on small screens */
.ant-table-wrapper {
border-radius: 8px !important;
border: 1px solid #E2E8F0 !important;
overflow: hidden !important;
}
/* ── Modals ──────────────────────────────────────────────────────────────────── */
.ant-modal {
max-width: calc(100vw - 32px) !important;
}
.ant-modal .ant-modal-content {
border-radius: 10px !important;
box-shadow: 0 8px 32px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.05) !important;
overflow: hidden !important;
border: 1px solid #E2E8F0 !important;
}
.ant-modal .ant-modal-header {
background: #FFFFFF !important;
border-bottom: 1px solid #E2E8F0 !important;
padding: 16px 24px !important;
margin: 0 !important;
}
.ant-modal .ant-modal-title {
color: #0F172A !important;
font-weight: 600 !important;
font-size: 15px !important;
}
.ant-modal .ant-modal-close {
color: #64748B !important;
top: 12px !important;
}
.ant-modal .ant-modal-close:hover {
color: #0F172A !important;
background: #F1F5F9 !important;
border-radius: 6px !important;
}
.ant-modal .ant-modal-body {
padding: 20px 24px !important;
}
.ant-modal .ant-modal-footer {
border-top: 1px solid #F1F5F9 !important;
padding: 12px 24px !important;
background: #FAFAFA !important;
}
/* ── Drawers ─────────────────────────────────────────────────────────────────── */
.ant-drawer .ant-drawer-header {
background: #FFFFFF !important;
border-bottom: 1px solid #E2E8F0 !important;
padding: 16px 24px !important;
}
.ant-drawer .ant-drawer-title {
color: #0F172A !important;
font-weight: 600 !important;
font-size: 15px !important;
}
.ant-drawer .ant-drawer-close {
color: #64748B !important;
}
.ant-drawer .ant-drawer-close:hover {
color: #0F172A !important;
background: #F1F5F9 !important;
border-radius: 6px !important;
}
.ant-drawer .ant-drawer-body {
padding: 20px 24px !important;
background: #FFFFFF !important;
}
.ant-drawer .ant-drawer-extra .ant-btn-primary {
background: var(--branding-primary, #1677ff) !important;
border: none !important;
}
/* ── Buttons — flat, clean, shadcn-style ─────────────────────────────────────── */
.ant-btn-primary {
background: var(--branding-primary, #1677ff) !important;
border: none !important;
border-radius: 6px !important;
font-weight: 500 !important;
color: #fff !important;
box-shadow: none !important;
transition: opacity 0.15s ease, background 0.15s ease !important;
}
.ant-btn-primary:hover {
background: color-mix(in srgb, var(--branding-primary, #1677ff) 85%, #000) !important;
box-shadow: none !important;
color: #fff !important;
}
.ant-btn-primary:active {
background: color-mix(in srgb, var(--branding-primary, #1677ff) 75%, #000) !important;
}
/* Ghost buttons */
.ant-btn-primary.ant-btn-background-ghost,
.ant-btn.ant-btn-primary[class*="ghost"] {
background: transparent !important;
color: var(--branding-primary, #1677ff) !important;
border: 1px solid var(--branding-primary, #1677ff) !important;
box-shadow: none !important;
}
.ant-btn-primary.ant-btn-background-ghost:hover,
.ant-btn.ant-btn-primary[class*="ghost"]:hover {
background: rgba(14,165,233,0.05) !important;
color: var(--branding-primary, #1677ff) !important;
}
/* Danger buttons */
.ant-btn-primary.ant-btn-dangerous {
background: #EF4444 !important;
color: #fff !important;
box-shadow: none !important;
}
.ant-btn-primary.ant-btn-dangerous:hover {
background: #DC2626 !important;
color: #fff !important;
box-shadow: none !important;
}
/* Default / secondary buttons */
.ant-btn-default {
border-radius: 6px !important;
border-color: #E2E8F0 !important;
color: #334155 !important;
font-weight: 500 !important;
background: #FFFFFF !important;
box-shadow: none !important;
transition: background 0.15s ease, border-color 0.15s ease !important;
}
.ant-btn-default:hover {
border-color: #CBD5E1 !important;
background: #F8FAFC !important;
color: #0F172A !important;
box-shadow: none !important;
}
.ant-btn-default.ant-btn-dangerous {
color: #EF4444 !important;
border-color: #FCA5A5 !important;
}
.ant-btn-default.ant-btn-dangerous:hover {
border-color: #EF4444 !important;
background: #FFF5F5 !important;
}
/* Link buttons */
.ant-btn-link {
color: var(--branding-primary, #1677ff) !important;
font-weight: 500 !important;
}
.ant-btn-link:hover {
color: color-mix(in srgb, var(--branding-primary, #1677ff) 75%, #000) !important;
background: rgba(14,165,233,0.05) !important;
}
/* Text buttons */
.ant-btn-text {
color: #64748B !important;
}
.ant-btn-text:hover {
background: #F1F5F9 !important;
color: #334155 !important;
}
/* Ensure button content color */
.ant-btn-primary span,
.ant-btn-primary .anticon {
color: inherit !important;
}
/* ── Tags ────────────────────────────────────────────────────────────────────── */
.ant-tag {
border-radius: 4px !important;
font-weight: 500 !important;
font-size: 11px !important;
padding: 2px 8px !important;
line-height: 18px !important;
margin-inline-end: 4px !important;
}
/* ── Cards (Ant Design) ──────────────────────────────────────────────────────── */
.ant-card {
border-radius: 10px !important;
border-color: #E2E8F0 !important;
box-shadow: 0 1px 2px rgba(0,0,0,0.04) !important;
transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
}
.ant-card:hover {
border-color: #CBD5E1 !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.06) !important;
}
.ant-card .ant-card-head {
border-bottom: 1px solid #F1F5F9 !important;
padding: 14px 20px !important;
min-height: auto !important;
background: #FFFFFF !important;
}
.ant-card .ant-card-head-title {
font-size: 14px !important;
font-weight: 600 !important;
color: #0F172A !important;
padding: 0 !important;
}
.ant-card .ant-card-body {
padding: 16px 20px !important;
}
/* ── Form Inputs — shadcn style ──────────────────────────────────────────────── */
.ant-input,
.ant-input-affix-wrapper,
.ant-input-number,
.ant-input-number-affix-wrapper,
.ant-select-selector,
.ant-picker {
border-radius: 6px !important;
border-color: #E2E8F0 !important;
background: #FFFFFF !important;
transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
}
.ant-input::placeholder,
.ant-input-number-input::placeholder {
color: #94A3B8 !important;
}
.ant-input:hover,
.ant-input-affix-wrapper:hover,
.ant-input-number:hover,
.ant-select:not(.ant-select-disabled):hover .ant-select-selector,
.ant-picker:hover {
border-color: #CBD5E1 !important;
}
.ant-input:focus,
.ant-input-focused,
.ant-input-affix-wrapper-focused,
.ant-input-number-focused,
.ant-select-focused .ant-select-selector,
.ant-picker-focused {
border-color: var(--branding-primary, #1677ff) !important;
box-shadow: 0 0 0 2px rgba(14,165,233,0.12) !important;
}
.ant-form-item-label > label {
font-weight: 500 !important;
font-size: 13px !important;
color: #374151 !important;
}
.ant-form-item-explain-error {
font-size: 12px !important;
margin-top: 4px !important;
}
/* ── Tabs (Ant Design) ───────────────────────────────────────────────────────── */
.ant-tabs .ant-tabs-tab {
font-weight: 500 !important;
color: #64748B !important;
transition: color 0.15s ease !important;
padding: 10px 16px !important;
}
.ant-tabs .ant-tabs-tab:hover {
color: #334155 !important;
}
.ant-tabs .ant-tabs-tab-active .ant-tabs-tab-btn {
color: var(--branding-primary, #1677ff) !important;
font-weight: 600 !important;
}
.ant-tabs .ant-tabs-ink-bar {
background: var(--branding-primary, #1677ff) !important;
height: 2px !important;
border-radius: 2px !important;
}
.ant-tabs-content-holder {
padding-top: 16px !important;
}
/* ── Pagination ──────────────────────────────────────────────────────────────── */
.ant-pagination .ant-pagination-item {
border-radius: 6px !important;
border-color: #E2E8F0 !important;
transition: border-color 0.15s, background 0.15s !important;
}
.ant-pagination .ant-pagination-item:hover {
border-color: var(--branding-primary, #1677ff) !important;
background: #F0F9FF !important;
}
.ant-pagination .ant-pagination-item-active {
background: var(--branding-primary, #1677ff) !important;
border-color: var(--branding-primary, #1677ff) !important;
}
.ant-pagination .ant-pagination-item-active a {
color: #fff !important;
}
/* ── Switch ──────────────────────────────────────────────────────────────────── */
.ant-switch-checked {
background: #10B981 !important;
}
.ant-switch-checked:hover {
background: #059669 !important;
}
/* ── Progress Bars ───────────────────────────────────────────────────────────── */
.ant-progress-bg {
border-radius: 4px !important;
}
.ant-progress-inner {
border-radius: 4px !important;
}
/* ── Popconfirm ──────────────────────────────────────────────────────────────── */
.ant-popconfirm .ant-btn-primary {
border-radius: 6px !important;
}
/* ── Select Dropdown ─────────────────────────────────────────────────────────── */
.ant-select-dropdown {
border-radius: 10px !important;
box-shadow: 0 8px 32px rgba(0,0,0,0.12) !important;
border: 1px solid #E2E8F0 !important;
padding: 4px !important;
}
.ant-select-item-option {
border-radius: 6px !important;
margin: 1px 0 !important;
}
.ant-select-item-option-active {
background: #F0F9FF !important;
}
.ant-select-item-option-selected {
background: #F0F9FF !important;
font-weight: 600 !important;
}
/* ── Tooltip ─────────────────────────────────────────────────────────────────── */
.ant-tooltip-inner {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 500 !important;
}
/* ── Message ─────────────────────────────────────────────────────────────────── */
.ant-message-notice-content {
border-radius: 10px !important;
box-shadow: 0 8px 24px rgba(0,0,0,0.1) !important;
padding: 10px 16px !important;
font-weight: 500 !important;
}
/* ── Divider ─────────────────────────────────────────────────────────────────── */
.ant-divider-inner-text {
font-size: 12px !important;
font-weight: 600 !important;
color: #64748B !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
}
/* ── Badge ───────────────────────────────────────────────────────────────────── */
.ant-badge-status-dot {
width: 8px !important;
height: 8px !important;
}
/* ── Alert ───────────────────────────────────────────────────────────────────── */
.ant-alert {
border-radius: 10px !important;
}
/* ═══════════════════════════════════════════════════════════════════════════════
LAYOUT UTILITIES — replace inline styles in page components
═══════════════════════════════════════════════════════════════════════════════ */
/* Flex helpers */
.flex { display: flex; }
.flex-center { display: flex; align-items: center; }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.flex-col { display: flex; flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.gap-4 { gap: 4px; }
.gap-8 { gap: 8px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }
/* Spacing */
.mb-0 { margin-bottom: 0; }
.mb-8 { margin-bottom: 8px; }
.mb-12 { margin-bottom: 12px; }
.mb-16 { margin-bottom: 16px; }
.mb-20 { margin-bottom: 20px; }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
/* Text utilities */
.text-muted { color: #64748B; }
.text-subtle { color: #94A3B8; }
.text-dark { color: #0F172A; }
.text-xxs { font-size: 10px; }
.text-xs { font-size: 11px; }
.text-sm { font-size: 12px; }
.text-base { font-size: 13px; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.font-mono { font-variant-numeric: tabular-nums; font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; }
.text-upper { text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; font-weight: 600; color: #64748B; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-nowrap { white-space: nowrap; }
.text-wrap { white-space: normal; word-break: break-word; }
.text-right { text-align: right; }
.text-left { text-align: left; }
.flex-shrink-0 { flex-shrink: 0; }
/* ── Table-Cell utilities (1.5.58) ─────────────────────────────────────────────
Engere Tabellen-Variante für Live-Listen mit vielen Spalten (Mail-Logs,
Training, MailQueue). Ant-Default ist 12px×16px, das frisst auf
Desktop-Breite mit ≥6 Spalten zu viel horizontalen Platz und sprengt
die Tabelle aus dem Viewport. .tbl-compact reduziert padding auf
6px×10px und text auf 12px (statt 13px).
Wrap-Modus (.tbl-wrap-cells) verhindert dass nowrap-Cells die
Tabellen-Breite ins Unendliche ziehen. */
.tbl-compact .ant-table-tbody > tr > td,
.tbl-compact .ant-table-thead > tr > th {
padding: 6px 10px !important;
font-size: 12px !important;
}
.tbl-compact .ant-table-thead > tr > th {
font-size: 11px !important;
}
.tbl-wrap-cells .ant-table-tbody > tr > td {
white-space: normal !important;
word-break: break-word !important;
}
.tbl-cell-nowrap {
white-space: nowrap;
word-break: normal;
}
.tbl-tag-xs .ant-tag {
font-size: 10px !important;
line-height: 16px !important;
padding: 0 6px !important;
}
/* ── Detail-Panel-Utilities ────────────────────────────────────────────────────
Für die expandable-row + Quarantäne-Preview-Modal: Symbol-Listen,
Score-Highlights, kleine Mono-Fonts. */
.detail-row { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px solid #fafafa; gap: 4px; }
.detail-row .ant-typography { font-size: 11px; }
.detail-section-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.4px; color: #94A3B8; font-weight: 600; }
.score-large { font-size: 22px; font-weight: 700; line-height: 1.2; }
.score-required { font-size: 10px; color: #8c8c8c; }
.score-pill { font-size: 13px !important; padding: 2px 12px !important; }
/* ── Symbol-list rows (rspamd-Detail) ──────────────────────────────────────────
ellipsis name + tag-with-score, beide im selben flex-row. */
.symbol-row { display: flex; justify-content: space-between; align-items: center; padding: 3px 0; border-bottom: 1px solid #fafafa; gap: 6px; }
.symbol-row .symbol-name { font-size: 11px; font-family: 'JetBrains Mono', monospace; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.symbol-row .ant-tag { font-size: 10px !important; flex-shrink: 0; margin: 0; }
/* ── Responsive table fallback ─────────────────────────────────────────────────
Auf Desktop ≤960 px kommen viel-spaltige Tabellen ins Stocken; das
built-in `scroll: { x: ... }` der Ant-Table greift dann. Damit der
horizontale Scroll subtil bleibt: Scrollbar dünner, kein Border-Box. */
@media (max-width: 960px) {
.ant-table-wrapper .ant-table-content { overflow-x: auto !important; }
.tbl-compact .ant-table-tbody > tr > td,
.tbl-compact .ant-table-thead > tr > th { padding: 5px 8px !important; font-size: 11px !important; }
}
/* Score-Color-Klassen — komplementär zu mailLogsScoreColor() in JS,
damit datengetriebene-color als style={{ color: ... }} bleiben kann
ODER alternative semantische Klassen genutzt werden. */
.score-clean { color: #10B981; }
.score-warn { color: #F59E0B; }
.score-spam { color: #EF4444; }
.score-virus { color: #7C2D12; }
/* MailLogs-Detail-Row: action-tag + relay/IP links, host-tag rechts. */
.maillog-detail { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.maillog-detail-left { display: flex; flex-wrap: nowrap; gap: 6px; align-items: center; overflow: hidden; min-width: 0; }
.maillog-detail-left > * { flex-shrink: 0; }
/* MailLogs Expandable-Detail: Pill-Header + Dark-Terminal-Block */
.maillog-expand { padding: 8px 0; }
.maillog-pill-header { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px; background: #fafafa; border-radius: 6px; margin-bottom: 8px; }
.maillog-pill-header .ant-tag { font-size: 10px; }
.maillog-subject-trunc { max-width: 250px; display: inline-block; vertical-align: bottom; }
.maillog-score-pill { padding: 2px 8px; border-radius: 12px; font-weight: 600; font-size: 11px; color: #fff; }
.maillog-terminal { background: #1f1f1f; border: 1px solid #303030; border-radius: 6px; padding: 12px 16px; font-family: 'JetBrains Mono', monospace; font-size: 11px; line-height: 1.7; color: #d9d9d9; max-height: 320px; overflow-y: auto; }
.maillog-terminal-empty { color: #bfbfbf; }
.maillog-step { margin-bottom: 4px; white-space: pre-wrap; }
.maillog-step-ts { color: #888; margin-right: 8px; }
.maillog-step-action { color: #fa8c16; margin-right: 8px; }
.maillog-step-relay { color: #73d13d; margin-right: 8px; }
.maillog-step-source { margin-right: 8px; }
/* Training-action utilities */
.btn-ham { color: #52c41a; }
.mr-4 { margin-right: 4px; }
/* Training-Intro-Block: lead + bullet-list + quarantine-hint footer */
.training-intro { font-size: 13px; line-height: 1.6; }
.training-intro p { margin: 0 0 8px; }
.training-intro ul { margin: 0 0 8px; padding-left: 20px; }
.training-intro-foot { margin: 0 !important; color: #595959; }
/* Symbol-row neutral (informative without score-tag) */
.symbol-row-neutral { padding: 2px 0; }
/* ── Sandbox-Detail (1.5.65) ──────────────────────────────────────────────────
Aufklappbarer Report-Block in der Sandbox-Tabelle. Zeigt alle 13 Stages
strukturiert in Cards. Verdict-Hero oben prominent, Sub-Cards Grid darunter. */
.sandbox-detail { padding: 8px 0; }
/* Verdict-Hero: Score-Big-Number + Verdict-Tag + Confidence + Duration */
.sandbox-verdict-hero {
display: flex; align-items: center; gap: 24px;
padding: 16px 20px; margin-bottom: 12px;
background: linear-gradient(180deg, #F8FAFC 0%, #FFFFFF 100%);
border: 1px solid #E2E8F0; border-radius: 10px;
}
.sandbox-verdict-score { display: flex; align-items: baseline; gap: 4px; }
.sandbox-verdict-score-num { font-size: 42px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.sandbox-verdict-score-suffix { font-size: 14px; color: #94A3B8; font-weight: 500; }
.sandbox-verdict-meta { display: flex; flex-direction: column; gap: 4px; }
/* Reasons-Liste (Bullet-Punkte unter Hero) */
.sandbox-section { padding: 8px 12px; background: #FFFBEB; border-radius: 6px; border: 1px solid #FDE68A; margin-bottom: 8px; }
.sandbox-reasons { margin: 4px 0 0 0; padding-left: 18px; font-size: 12px; color: #92400E; }
.sandbox-reasons li { margin-bottom: 2px; }
/* Key-Value-Grid für IP/RDNS/ASN/Country/MIME-Stats etc. */
.kv-grid {
display: grid; grid-template-columns: max-content 1fr;
gap: 4px 12px; align-items: baseline;
font-size: 12px;
}
.kv-key { font-size: 11px; color: #64748B; font-weight: 500; }
/* Received-Chain Liste, leicht eingerückt + monospace */
.sandbox-recv-chain { margin-top: 4px; padding: 6px 10px; background: #F8FAFC; border-radius: 4px; max-height: 200px; overflow-y: auto; }
/* Attachment- und URL-Listen */
.sandbox-attach-list, .sandbox-url-list { display: flex; flex-direction: column; gap: 8px; }
.sandbox-attach-item, .sandbox-url-item {
padding: 8px 10px; border: 1px solid #F1F5F9; border-radius: 6px; background: #FAFAFA;
}
.sandbox-attach-head, .sandbox-url-head {
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
}
.sandbox-attach-head strong { font-size: 12px; }
.sandbox-url-head code { word-break: break-all; min-width: 0; }
/* ── Dashboard: Enterprise KPI-Layout (1.5.59) ─────────────────────────────
Großer, klarer Blick auf die wichtigsten Zahlen. Hierarchie:
1. Hero-Row mit den 4 kritischen KPIs (Mails, Spam, Rejected, Quarantäne)
mit großen Zahlen + Trend-Sparkline daneben
2. Sekundäre KPIs in kleineren Tiles
3. Listen + Charts darunter
Ant-Statistic-Komponente wird per .kpi-* übersteuert. */
.dashboard-hero .ant-card-body {
padding: 20px 24px !important;
}
.kpi-tile {
display: flex;
flex-direction: column;
gap: 4px;
padding: 14px 16px;
background: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 10px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
min-width: 0;
}
.kpi-tile:hover {
border-color: #CBD5E1;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
}
.kpi-tile.kpi-primary { background: #F8FAFC; border-color: #E2E8F0; }
.kpi-tile.kpi-success { background: #F0FDF4; border-color: #BBF7D0; }
.kpi-tile.kpi-warning { background: #FFFBEB; border-color: #FDE68A; }
.kpi-tile.kpi-danger { background: #FEF2F2; border-color: #FECACA; }
.kpi-tile.kpi-info { background: #EFF6FF; border-color: #BFDBFE; }
.kpi-tile-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #64748B;
font-weight: 600;
}
.kpi-tile-value {
font-size: 28px;
font-weight: 700;
line-height: 1.1;
color: #0F172A;
font-variant-numeric: tabular-nums;
}
.kpi-tile-value.kpi-success { color: #10B981; }
.kpi-tile-value.kpi-warning { color: #F59E0B; }
.kpi-tile-value.kpi-danger { color: #EF4444; }
.kpi-tile-value.kpi-info { color: #0EA5E9; }
/* Score-Boost-Tags (Lookalike-Watcher etc.) — drei Tier-Farben.
Wird per Klasse statt inline-style gesetzt damit das Theme global
tunable bleibt. */
.tag-score-high.ant-tag {
background: #FEE2E2;
border-color: #FCA5A5;
color: #B91C1C;
}
.tag-score-mid.ant-tag {
background: #FEF3C7;
border-color: #FCD34D;
color: #B45309;
}
/* Threat-Radar-Tile-Akzent: bei > 0 Hits farbiger Border-Left + voller
Wert; bei 0 Hits halb-transparent. Klassen statt inline-style. */
.threat-tile.threat-tile--hit { opacity: 1; border-left: 3px solid #EF4444; }
.threat-tile.threat-tile--warn { opacity: 1; border-left: 3px solid #F59E0B; }
.threat-tile.threat-tile--crit { opacity: 1; border-left: 3px solid #DC2626; }
.threat-tile.threat-tile--idle { opacity: 0.55; border-left: 3px solid transparent; }
.threat-tile-value--hit { color: #EF4444; }
.threat-tile-value--warn { color: #F59E0B; }
.threat-tile-value--crit { color: #DC2626; }
.kpi-tile-link { display: block; text-decoration: none; cursor: pointer; }
.kpi-tile-link:hover .kpi-tile-compact { background: #F8FAFC; }
/* MailTrace eml-Eingabe: monospace fixed-width für raw-mail-Snippets. */
.eml-textarea textarea {
font-family: Menlo, Consolas, monospace !important;
font-size: 12px !important;
}
/* MailTrace Stage-Card-Layout (Status-Tag + Summary + collapsible JSON). */
.trace-stage-row {
display: flex;
align-items: center;
gap: 12px;
}
.trace-stage-tag.ant-tag {
min-width: 60px;
text-align: center;
}
.trace-stage-detail {
margin-top: 8px;
font-size: 11px;
background: #F6F8FA;
padding: 8px;
border-radius: 4px;
overflow: auto;
max-height: 240px;
}
.full-width-stack { width: 100%; }
.stats-window-label {
display: block;
margin-bottom: 8px;
font-size: 12px;
font-style: italic;
}
/* Inline-Style-Cleanup-Sweep (1.7.14): Utility-Klassen für die
Patterns die in /pages noch als style={{...}} drinhingen.
Memory-Regel: style={{}} nur für datengetriebene Werte (Score-
Color, Progress-Width per pct), Layout = Klasse. */
.row-flex-end { display: flex; justify-content: flex-end; }
.list-item-flat { padding: 4px 0; border: 0; }
.tag-xs.ant-tag { font-size: 10px; }
.tag-xxs.ant-tag { font-size: 9px; }
.code-xs { font-size: 12px; }
.text-xxs { font-size: 11px; }
.api-key-input { width: calc(100% - 44px); }
/* ScoreTuning-Tab — sticky filter header + dashed-row separator. */
.scoretuning-sticky-header {
position: sticky;
top: 0;
z-index: 5;
padding: 8px 0;
background: #fff;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 16px;
}
.scoretuning-row {
padding: 6px 0;
border-bottom: 1px dashed #f0f0f0;
}
/* Setup-Wizard: ordered list mit reduziertem padding-left + pre mit
wrap. */
.setup-list-tight { padding-left: 18px; }
.setup-pre-wrap { white-space: pre-wrap; }
/* SuspiciousTLDs-Spam-Ratio-Cell: Farbe wird datengetrieben gesetzt
(color je nach pct), aber das font-weight gehört in eine Klasse. */
.tld-ratio-cell { font-weight: 500; }
/* Generische Icon-Farb-Tokens — drei Tier-Farben für SuspiciousTLDs
Lock-Cell + ähnliche „operator-locked"/„not-locked"-Indikatoren. */
.text-warning-icon { color: #F59E0B; }
.text-muted-icon { color: #94A3B8; }
/* MailBodyPreview — sandboxed iframe + pre für Plain-Text. Höhe 60vh
damit auch lange Newsletter komplett sichtbar sind, scroll im iframe. */
.mail-body-preview-headers .ant-descriptions-item-label {
width: 110px;
font-weight: 500;
}
.mail-body-preview-frame {
width: 100%;
min-height: 50vh;
height: 50vh;
border: 0;
border-radius: 4px;
background: #fff;
}
.mail-body-preview-pre {
margin: 0;
padding: 8px;
background: #F8FAFC;
border-radius: 4px;
font-family: Menlo, Consolas, monospace;
font-size: 12px;
white-space: pre-wrap;
word-break: break-word;
max-height: 50vh;
overflow: auto;
}
.kpi-tile-sub {
font-size: 11px;
color: #94A3B8;
display: flex;
align-items: center;
gap: 4px;
}
.kpi-tile-icon {
font-size: 16px;
color: #94A3B8;
}
/* SystemHealthCard — pro Mountpoint eine Zeile mit Pfad + Progress.
Pfad bleibt in fixer Breite damit die Bars vertikal alignen. */
.kpi-tile-value-sm {
font-size: 13px;
font-weight: 600;
color: #0F172A;
line-height: 1.3;
}
.sys-disk-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.sys-disk-row {
font-size: 11px;
color: #475569;
}
.sys-disk-row .ant-progress {
margin-bottom: 0;
}
.sys-disk-path {
display: inline-block;
min-width: 96px;
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
color: #64748B;
font-size: 11px;
margin-right: 8px;
}
/* Mobile-Layout für SystemHealthCard auf < 768 px Viewports.
Statt Tabelle ein Card-Stack pro Node — jede Karte hat Header
(Hostname + Lizenz-Tag) und vertikal gestapelte Werte (Load,
Uptime, Memory, Disks). 320 px breite Smartphones rendern damit
ohne Horizontal-Scroll. */
.sys-mobile-stack {
display: flex;
flex-direction: column;
gap: 12px;
}
.sys-mobile-card {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
border: 1px solid #E2E8F0;
border-radius: 8px;
background: #F8FAFC;
}
.sys-mobile-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
}
.sys-mobile-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.sys-mobile-block {
margin-top: 4px;
}
/* Branding-Settings Logo-Preview: zentriert das Logo in einer
80x80-Box mit hellem Border, damit Admin sofort sieht wie das
Logo skaliert. object-fit:contain erhält das Aspect-Ratio. */
.branding-logo-preview {
display: inline-flex;
align-items: center;
justify-content: center;
width: 96px;
height: 96px;
padding: 8px;
border: 1px solid #E2E8F0;
border-radius: 8px;
background: #FFFFFF;
}
.branding-logo-preview img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
/* Login-BG-Preview: 16:9-Box, das Bild wird cover-gerendert wie es
später im Portal aussehen wird, damit der Operator vor dem Speichern
sieht ob Crop / Fokus passen. */
.branding-loginbg-preview {
display: inline-flex;
width: 240px;
height: 135px;
border: 1px solid #E2E8F0;
border-radius: 8px;
overflow: hidden;
background: #F8FAFC;
}
.branding-loginbg-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Section heading inside Card body */
.section-title {
font-size: 11px !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
color: #64748B !important;
font-weight: 600 !important;
margin: 0 0 12px 0 !important;
}
/* Compact row of secondary KPIs (4-up). Tighter than primary tiles. */
.kpi-tile-compact {
padding: 10px 14px;
background: #FFFFFF;
border: 1px solid #F1F5F9;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.kpi-tile-compact .kpi-tile-value {
font-size: 20px;
font-weight: 600;
}
/* Cluster-status pill: shows N/M nodes online in one chip */
.cluster-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: #F0FDF4;
border: 1px solid #BBF7D0;
color: #047857;
font-size: 11px;
font-weight: 600;
}
.cluster-pill.degraded {
background: #FEF2F2;
border-color: #FECACA;
color: #B91C1C;
}
.cluster-pill .anticon {
font-size: 10px;
}
.maillog-symbols-section { margin-top: 12px; }
.maillog-symbols-list { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px; }
.maillog-postscreen-toggle .ant-typography { font-size: 12px; }
/* Quarantäne-Preview-Body (Modal): pre-formatted RFC822-Body. */
.quarantine-preview-body {
max-height: 420px; overflow: auto;
background: #F8FAFC; padding: 12px; border-radius: 6px;
border: 1px solid #E2E8F0; font-size: 12px; line-height: 1.45;
white-space: pre-wrap; word-break: break-word;
}
.ml-12 { margin-left: 12px; }
.mt-6 { margin-top: 6px; }
/* Margin-zero override für Tags die direkt neben anderen Inhalten stehen
und keine extra-Lücke zum nächsten Element brauchen sollen. */
.tag-no-mr { margin-right: 0; }
.tag-no-margin { margin: 0; }
/* Symbol-Lists in Training/Detail (rspamd-Symbol-Block).
Trennlinie zwischen Symbol-Gruppen. */
.symbol-divider { margin: 6px 0; }
/* Additional utilities for Settings + global use */
.mb-24 { margin-bottom: 24px; }
.mt-0 { margin-top: 0; }
.mt-4 { margin-top: 4px; }
.mr-6 { margin-right: 6px; }
.p-24 { padding: 24px; }
.d-block { display: block; }
.w-full { width: 100%; }
.w-200 { width: 200px; }
.max-w-480 { max-width: 480px; }
.max-w-520 { max-width: 520px; }
.max-w-560 { max-width: 560px; }
.text-center { text-align: center; }
.text-primary { color: #1677ff; }
.text-label { font-size: 13px; font-weight: 600; color: #1E293B; display: block; }
.mono-sm { font-family: monospace; font-size: 12px; }
.mono-xs { font-family: monospace; font-size: 11px; }
.mono-base { font-family: monospace; font-size: 13px; }
.card-bordered { border: 1px solid #E5E7EB; border-radius: 8px; }
.card-bordered--lg { border: 1px solid #E5E7EB; border-radius: 10px; }
.btn-primary-blue { background: #1677ff; border-color: #1677ff; }
.spinner-center { text-align: center; padding: 24px; }
.loader-center { min-height: 40vh; display: grid; place-items: center; }
/* Form-field width helpers — replace inline style={{ width: N }} on
Form.Item / InputNumber / Input so mail-config, domain-form, filters
can share a single set of standardised column widths. */
.field-w-100 { width: 100px; }
.field-w-120 { width: 120px; }
.field-w-140 { width: 140px; }
.field-w-160 { width: 160px; }
.field-w-180 { width: 180px; }
.field-w-200 { width: 200px; }
.field-w-220 { width: 220px; }
.field-w-240 { width: 240px; }
.field-w-260 { width: 260px; }
.max-w-220 { max-width: 220px; }
.max-w-240 { max-width: 240px; }
.max-w-260 { max-width: 260px; }
/* Inline helpers for small label+value rows. */
.text-hint-sm { font-size: 12px; margin-top: -4px; }
.title-suffix { font-size: 16px; font-weight: 400; margin-left: 12px; }
/* Semantic status icon colors — shared by Settings (upgrade timeline),
Dashboard, Cluster status cells and anywhere else a state icon needs
a consistent palette. */
.icon-success { color: #52c41a; }
.icon-info { color: #1677ff; }
.icon-danger { color: #ff4d4f; }
.icon-muted { color: #8c8c8c; }
.icon-warning { color: #faad14; }
.inline-elapsed { margin-left: 12px; font-size: 12px; }
.upgrade-timeline { margin-top: 16px; padding-left: 8px; }
.auto-update-hint { margin-top: 12px; font-size: 12px; margin-bottom: 0; }
.setup-wrapper-done { max-width: 520px; margin: 80px auto; padding: 24px; }
.setup-wrapper-wizard { max-width: 720px; margin: 60px auto; padding: 24px; }
/* Score cell — font-weight shared; color is data-driven so it stays inline. */
.score-cell { font-weight: 500; }
.hidden { display: none !important; }
.training-dropzone { min-height: 180px; }
/* Ham button — green counterpart to AntD's danger (spam). Used in the
Training / Quarantine / Mail-History pages so "learn as ham" is
visually consistent with "learn as spam". AntD has no built-in
success variant for Button size=small, so we overlay our own. */
.btn-ham.ant-btn,
.btn-ham.ant-btn:focus {
color: #10B981 !important;
border-color: #86EFAC !important;
background: #FFFFFF !important;
}
.btn-ham.ant-btn:hover {
color: #059669 !important;
border-color: #10B981 !important;
background: #F0FDF4 !important;
}
.btn-ham.ant-btn:disabled {
color: #94A3B8 !important;
border-color: #E2E8F0 !important;
background: #F8FAFC !important;
}
/* Toolbar row used by page headers that mix title + actions buttons. */
.page-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
gap: 12px;
flex-wrap: wrap;
}
.page-toolbar h1,
.page-toolbar h2,
.page-toolbar h3,
.page-toolbar h4,
.page-toolbar h5 {
margin: 0;
}
/* Inline icon+text combos */
.icon-label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
}
/* Muted icon box (used in section headers, stat cards) */
.icon-box {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: #F0F9FF;
border-radius: 6px;
font-size: 14px;
color: var(--branding-primary, #1677ff);
flex-shrink: 0;
}
.icon-box--sm {
width: 22px;
height: 22px;
font-size: 12px;
border-radius: 5px;
}
.icon-box--lg {
width: 36px;
height: 36px;
font-size: 17px;
border-radius: 8px;
}
/* Color variants */
.icon-box--green { background: #F0FDF4; color: #10B981; }
.icon-box--red { background: #FFF5F5; color: #EF4444; }
.icon-box--orange { background: #FFFBEB; color: #F59E0B; }
.icon-box--purple { background: #F5F3FF; color: #8B5CF6; }
.icon-box--teal { background: #EFF6FF; color: #1677ff; }
.icon-box--slate { background: #F8FAFC; color: #64748B; }
/* Mobile card items (for ProTable mobile views) */
.mobile-card {
background: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 10px;
padding: 14px 16px;
margin-bottom: 8px;
}
.mobile-card:last-child {
margin-bottom: 0;
}
.mobile-card__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.mobile-card__title {
font-size: 13px;
font-weight: 600;
color: #0F172A;
}
.mobile-card__meta {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #64748B;
}
.mobile-card__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.mobile-card__actions {
border-top: 1px solid #F1F5F9;
padding-top: 8px;
}
/* Inline status dot */
.dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
}
.dot--green { background: #10B981; }
.dot--red { background: #EF4444; }
.dot--orange { background: #F59E0B; }
.dot--blue { background: #3B82F6; }
.dot--gray { background: #94A3B8; }
/* Top-list rows on the dashboard. Three-line structure:
row 1 rank + label (left) total (right, prominent)
row 2 progress bar (full width)
row 3 inline counters with .dot- prefixes
Layout uses CSS-grid so wrapping never collapses the right-side
total below the label — the previous flex+space-between version
broke at narrow widths and produced unreadable stacks. */
.toplist-row {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
}
.toplist-head {
display: grid;
grid-template-columns: 1fr auto;
align-items: baseline;
gap: 12px;
}
.toplist-rank {
display: inline-block;
min-width: 22px;
font-size: 11px;
font-weight: 600;
color: #94A3B8;
letter-spacing: 0.4px;
}
.toplist-label {
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
font-size: 13px;
font-weight: 500;
color: #0F172A;
}
.toplist-sub {
margin-left: 8px;
color: #94A3B8;
font-size: 11px;
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
}
.toplist-total {
font-variant-numeric: tabular-nums;
font-size: 14px;
font-weight: 700;
color: #1E293B;
}
.toplist-bar {
height: 4px;
background: #F1F5F9;
border-radius: 2px;
overflow: hidden;
}
.toplist-bar-fill {
height: 100%;
border-radius: 2px;
}
.toplist-bar-fill--blue { background: #0EA5E9; }
.toplist-bar-fill--red { background: #EF4444; }
.toplist-counters {
display: flex;
flex-wrap: wrap;
gap: 12px;
font-size: 11px;
font-variant-numeric: tabular-nums;
color: #475569;
align-items: center;
}
.toplist-counter {
display: inline-flex;
align-items: center;
gap: 5px;
}
/* Empty state */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
text-align: center;
gap: 8px;
color: #94A3B8;
}
.empty-state__icon {
font-size: 36px;
margin-bottom: 8px;
opacity: 0.5;
}
.empty-state__title {
font-size: 14px;
font-weight: 600;
color: #64748B;
}
.empty-state__desc {
font-size: 13px;
color: #94A3B8;
max-width: 300px;
}
/* ── AntD Table inside DataCard (flush/no-padding) ───────────────────────────── */
/* Remove double borders when table is inside data-card */
.data-card .ant-table-wrapper {
border: none !important;
border-radius: 0 !important;
}
.data-card .ant-table {
border-radius: 0 !important;
}
/* ═══════════════════════════════════════════════════════════════════════════════
RESPONSIVE — Mobile Optimization
═══════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
.ant-drawer-content-wrapper {
width: 100% !important;
max-width: 100vw !important;
}
}
@media (max-width: 992px) {
.sidebar {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.sidebar.open {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.header {
height: 52px;
padding: 0 12px;
}
.header-menu-toggle {
display: flex !important;
}
.sidebar-overlay {
display: block !important;
}
.content-area {
padding: 8px;
}
.content-card {
padding: 14px;
border-radius: 10px;
}
.impersonation-banner {
flex-wrap: wrap;
gap: 8px;
padding: 8px 12px !important;
font-size: 12px !important;
}
.ant-table-wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.ant-modal .ant-modal-body {
padding: 16px !important;
}
.ant-modal .ant-modal-header {
padding: 14px 16px !important;
}
.ant-modal .ant-modal-footer {
padding: 12px 16px !important;
}
}
@media (max-width: 768px) {
.header-title {
font-size: 13px;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header-actions {
gap: 6px;
}
.header-user-email {
display: none !important;
}
.header-lang-text {
display: none !important;
}
.content-area {
padding: 6px;
}
.content-card {
padding: 12px;
border-radius: 8px;
}
.page-header-icon {
width: 32px !important;
height: 32px !important;
font-size: 15px !important;
}
.tab-bar {
gap: 4px;
}
.tab-item {
padding: 5px 10px;
font-size: 12px;
}
.stat-card {
padding: 12px !important;
}
.ant-modal {
top: 16px !important;
padding-bottom: 16px !important;
}
.ant-modal .ant-modal-header {
padding: 12px 16px !important;
}
.ant-modal .ant-modal-title {
font-size: 14px !important;
}
}
@media (max-width: 480px) {
.header {
height: 48px;
padding: 0 8px;
}
.header-title {
max-width: 100px;
font-size: 12px;
}
.content-area {
padding: 4px;
}
.content-card {
padding: 8px;
border-radius: 6px;
margin-bottom: 8px;
}
}
/* ── Auth pages (Login, ForgotPassword, ResetPassword) ───────────────────────── */
.auth-page {
min-height: 100vh;
background: #F8FAFC;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
.auth-card {
background: #ffffff;
border: 1px solid #E5E7EB;
border-radius: 10px;
padding: 32px;
width: 100%;
max-width: 400px;
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
}
.auth-logo-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 28px;
}
.auth-logo-title {
font-size: 16px;
font-weight: 700;
color: #1E293B;
line-height: 1.2;
}
.auth-logo-subtitle {
font-size: 12px;
color: #94A3B8;
margin-top: 2px;
}
.auth-back-link {
margin-top: 20px;
text-align: center;
font-size: 13px;
}
.auth-hint {
font-size: 13px;
color: #64748B;
margin-bottom: 20px;
line-height: 1.6;
}
/* ─── Update Changelog ──────────────────────────────────────────────────────── */
.changelog-collapse {
margin-top: 8px;
border: 1px solid #FED7AA !important;
border-radius: 8px !important;
background: #FFFBEB !important;
}
.changelog-collapse-label {
font-weight: 500;
color: #1677ff;
}
.changelog-body {
margin: 0;
font-family: var(--font-mono, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace);
font-size: 13px;
line-height: 1.7;
white-space: pre-wrap;
word-break: break-word;
color: #1E293B;
}
.changelog-table .ant-table {
background: transparent;
}
.changelog-table .ant-table-cell {
padding: 4px 8px !important;
border-bottom: 1px solid #FDE68A !important;
vertical-align: top;
}
.changelog-version-tag {
font-family: var(--font-mono, 'SFMono-Regular', Consolas, monospace);
font-size: 11px;
color: #64748B;
white-space: nowrap;
}
.changelog-type-tag {
font-family: var(--font-mono, 'SFMono-Regular', Consolas, monospace);
font-size: 11px;
}
.flex-1 { flex: 1; }
.text-red { color: #EF4444; }
.text-green { color: #10B981; }
.ml-6 { margin-left: 6px; }
.ml-8 { margin-left: 8px; }
/* ═══════════════════════════════════════════════════════════════════════════════
SHARED ENTERPRISE COMPONENTS — Reusable across all pages
═══════════════════════════════════════════════════════════════════════════════ */
/* ── Settings Panel (light gray section with title) ────────────────────────── */
.ent-settings-panel {
background: #F8FAFC;
border: 1px solid #E5E7EB;
border-radius: 8px;
padding: 16px;
}
.ent-settings-panel__title {
display: block;
margin-bottom: 12px;
font-size: 13px;
font-weight: 600;
color: #1E293B;
}
.ent-settings-panel__title .anticon {
margin-right: 6px;
}
/* ── Info Field (monospace value box with label) ───────────────────────────── */
.ent-field-label {
display: block;
margin-bottom: 4px;
font-size: 12px;
color: #64748B;
font-weight: 500;
}
.ent-field-value {
display: block;
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
font-size: 13px;
background: #F8FAFC;
padding: 6px 10px;
border-radius: 6px;
border: 1px solid #E2E8F0;
color: #1E293B;
word-break: break-all;
}
.ent-field-value--success {
background: #F0FDF4;
border-color: #BBF7D0;
color: #15803D;
}
/* ── Warning / Info Alert Box ──────────────────────────────────────────────── */
.ent-alert-box {
border-radius: 8px;
padding: 12px 16px;
font-size: 13px;
}
.ent-alert-box--warning {
background: #FFF7ED;
border: 1px solid #FED7AA;
color: #92400E;
}
.ent-alert-box--info {
background: #F0F9FF;
border: 1px solid #BAE6FD;
color: #1d4ed8;
}
.ent-alert-box--success {
background: #F0FDF4;
border: 1px solid #BBF7D0;
color: #15803D;
}
.ent-alert-box--danger {
background: #FEF2F2;
border: 1px solid #FECACA;
color: #991B1B;
}
/* ── Inline Card (white bordered card used outside DataCard) ───────────────── */
.ent-card {
background: #fff;
border: 1px solid #E5E7EB;
border-radius: 10px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
}
.ent-card--compact {
padding: 16px;
}
.ent-card--flush {
padding: 0;
}
/* ── Selectable Card (for type selectors, e.g. restore type) ───────────────── */
.ent-select-card {
flex: 1 1 calc(50% - 4px);
min-width: 120px;
border: 2px solid #E5E7EB;
border-radius: 8px;
padding: 10px 14px;
cursor: pointer;
background: #FAFAFA;
transition: all 0.15s ease;
display: flex;
align-items: flex-start;
gap: 10px;
}
.ent-select-card:hover {
border-color: #CBD5E1;
}
.ent-select-card--active {
border-color: var(--branding-primary, #1677ff);
background: rgba(22, 119, 255, 0.06);
}
.ent-select-card__icon {
font-size: 20px;
margin-top: 1px;
flex-shrink: 0;
}
.ent-select-card__title {
font-size: 13px;
font-weight: 600;
color: #1E293B;
line-height: 1.3;
}
/* ── Code Block (dark terminal-style output) ───────────────────────────────── */
.ent-code-block {
background: #0B1426;
color: #E2E8F0;
padding: 14px 16px;
border-radius: 8px;
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
font-size: 12px;
line-height: 1.7;
max-height: 400px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
/* ── DS Record / Code Snippet Row ──────────────────────────────────────────── */
.ent-code-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.ent-code-row:last-child {
margin-bottom: 0;
}
.ent-code-row__code {
flex: 1;
font-size: 11px;
font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
background: #E5E7EB;
padding: 2px 6px;
border-radius: 4px;
word-break: break-all;
}
/* ── Mini Stat Chip (inline stat badge used in active tasks, etc.) ─────────── */
.ent-stat-chip {
background: #F0F9FF;
border: 1px solid #BAE6FD;
border-radius: 8px;
padding: 8px 14px;
display: flex;
align-items: center;
gap: 8px;
}
.ent-stat-chip--green {
background: #F0FDF4;
border-color: #BBF7D0;
}
.ent-stat-chip--orange {
background: #FFF7ED;
border-color: #FED7AA;
}
.ent-stat-chip__icon {
font-size: 16px;
flex-shrink: 0;
}
.ent-stat-chip__icon--blue { color: #1677ff; }
.ent-stat-chip__icon--green { color: #22C55E; }
.ent-stat-chip__icon--orange { color: #F59E0B; }
/* ── Divider thin ──────────────────────────────────────────────────────────── */
.ent-divider-sm { margin: 12px 0; }
/* ── Update Modal — Mission Control ────────────────────────────────────────
* Full-screen overlay shown during a self-upgrade. Pattern 1:1 from
* netcell-webpanel/management-ui/src/pages/Dashboard/Dashboard.css.
* The orbit uses two counter-rotating rings + two dots + a pulsing
* center icon, plus a four-step progress list and a large seconds
* timer. Classes are namespaced with `update-modal` so they don't
* clash with AntD Modal internals. */
/* Update-Banner Mobile-Layout (1.6.92+). Auf engen Viewports kollidiert
der „Update verfügbar"-Text mit den beiden Aktion-Buttons (Check +
Update Now), weil AntD-Alert beide horizontal nebeneinander rendert.
Unter 640 px kippen wir das Layout in column-flex, action-Bereich
landet unter der Message + die Buttons stretchen auf 100% Breite. */
@media (max-width: 640px) {
.update-banner-alert.ant-alert {
flex-direction: column;
align-items: stretch;
}
.update-banner-alert .ant-alert-content {
margin-right: 0;
}
.update-banner-alert .ant-alert-action {
margin-left: 0;
margin-top: 8px;
}
.update-banner-alert .ant-alert-action .ant-space {
width: 100%;
display: flex;
}
.update-banner-alert .ant-alert-action .ant-space > .ant-space-item {
flex: 1;
}
.update-banner-alert .ant-alert-action button {
width: 100%;
}
}
/* Popconfirm der hinter "Update jetzt installieren?" steckt. Default
ist sehr breit weil die Description die ganze "Rolling-Upgrade über
alle N Knoten ..."-Zeile in EINER Zeile rendert. Wir kappen die
Breite und lassen den Text umbrechen — schmaler aber höher. */
.update-popconfirm.ant-popover {
max-width: 380px;
}
.update-popconfirm .ant-popconfirm-description {
white-space: normal;
line-height: 1.45;
font-size: 13px;
}
.update-popconfirm .ant-popconfirm-message-title {
font-size: 14px;
font-weight: 600;
}
.update-modal-overlay {
position: fixed;
inset: 0;
z-index: 9999;
background: rgba(3, 7, 18, 0.92);
backdrop-filter: blur(12px);
display: flex;
align-items: center;
justify-content: center;
animation: updateFadeIn 0.4s ease;
}
@keyframes updateFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.update-modal {
text-align: center;
color: #E2E8F0;
max-width: 400px;
padding: 40px;
}
.update-modal__orbit {
position: relative;
width: 140px;
height: 140px;
margin: 0 auto 32px;
}
.update-modal__ring {
position: absolute;
inset: 0;
border: 2px solid rgba(22, 119, 255, 0.15);
border-radius: 50%;
border-top-color: #1677ff;
animation: updateSpin 2s linear infinite;
}
.update-modal__ring--2 {
inset: 12px;
border-color: rgba(22, 119, 255, 0.08);
border-top-color: rgba(22, 119, 255, 0.4);
animation-duration: 3s;
animation-direction: reverse;
}
@keyframes updateSpin {
to { transform: rotate(360deg); }
}
.update-modal__dot {
position: absolute;
width: 8px;
height: 8px;
background: #1677ff;
border-radius: 50%;
top: -4px;
left: 50%;
margin-left: -4px;
box-shadow: 0 0 12px rgba(22, 119, 255, 0.8);
animation: updateSpin 2s linear infinite;
transform-origin: 4px 74px;
}
.update-modal__dot--2 {
background: #00D4AA;
box-shadow: 0 0 12px rgba(0, 212, 170, 0.8);
animation-duration: 3s;
animation-direction: reverse;
transform-origin: 4px 62px;
top: 8px;
}
.update-modal__center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 56px;
height: 56px;
background: linear-gradient(135deg, #0B1426 0%, #1E293B 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 30px rgba(22, 119, 255, 0.2);
}
.update-modal__icon {
font-size: 24px;
color: #1677ff;
animation: updatePulse 1.5s ease-in-out infinite;
}
@keyframes updatePulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.1); }
}
.update-modal__title {
font-size: 20px;
font-weight: 700;
color: #FFFFFF;
margin-bottom: 8px;
letter-spacing: -0.02em;
}
.update-modal__version {
font-size: 14px;
color: #64748B;
margin-bottom: 32px;
font-family: 'JetBrains Mono', monospace;
}
.update-modal__steps {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 32px;
text-align: left;
}
.update-modal__step {
display: flex;
align-items: center;
gap: 12px;
font-size: 13px;
color: #475569;
transition: color 0.3s;
}
.update-modal__step--active { color: #E2E8F0; }
.update-modal__step--done { color: #10B981; }
.update-modal__step-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #334155;
flex-shrink: 0;
transition: all 0.3s;
}
.update-modal__step--active .update-modal__step-dot {
background: #1677ff;
box-shadow: 0 0 8px rgba(22, 119, 255, 0.6);
animation: updateDotPulse 1s ease-in-out infinite;
}
.update-modal__step--done .update-modal__step-dot {
background: #10B981;
box-shadow: 0 0 8px rgba(16, 185, 129, 0.6);
animation: none;
}
@keyframes updateDotPulse {
0%, 100% { box-shadow: 0 0 4px rgba(22, 119, 255, 0.3); }
50% { box-shadow: 0 0 12px rgba(22, 119, 255, 0.8); }
}
.update-modal__timer {
font-size: 36px;
font-weight: 800;
color: #1677ff;
font-family: 'JetBrains Mono', monospace;
margin-bottom: 8px;
letter-spacing: -0.02em;
}
.update-modal__hint {
font-size: 12px;
color: #475569;
}
/* EnvelopeFrom — zwei-zeilige Anzeige in den Mail-Logs/Training/
Sandbox-Tabellen. Envelope steht oben, Header-From in Klammern
darunter wenn er sich unterscheidet. Mobile-platzsparend: kompakte
Schriftgrößen, beide Zeilen mit overflow-wrap:anywhere damit lange
Adressen umbrechen statt die Spalte zu sprengen. */
.ef-stack {
display: flex;
flex-direction: column;
line-height: 1.25;
min-width: 0;
}
.ef-line {
display: block;
overflow-wrap: anywhere;
word-break: normal;
font-variant-numeric: tabular-nums;
}
.ef-line-envelope {
font-size: 13px;
color: #0F172A;
font-weight: 500;
}
.ef-line-header {
font-size: 11px;
color: #64748B;
}
.ef-tooltip {
margin: 0;
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
font-size: 11px;
white-space: pre-wrap;
}
@media (max-width: 575px) {
.ef-line-envelope { font-size: 12px; }
.ef-line-header { font-size: 10px; }
}
/* GDPR/DSGVO masked rendering — addresses + subjects masked by
default; the operator clicks to confirm a reveal-reason which is
audited before the plaintext appears. The clickable variant uses a
subtle dotted underline so the cell looks like a button without
adding background colour to a dense table. */
.masked-address,
.masked-subject {
display: inline-block;
min-width: 0;
max-width: 100%;
/* Adressen wie 010f019dd…@mailer.members.netflix.com haben oft kein
Whitespace und würden ohne Bruch das Layout sprengen — wir lassen
den Browser an jeder Stelle umbrechen (overflow-wrap:anywhere) und
nehmen einen zweiten Zeilenumbruch in Kauf statt Ellipsis. Der
User-Befund 2026-04-29: 'die anadressen sind teilweise noch zu
breit z.B. die von Amazon, da muss ein Zeilenumbruch rein sonst
ist das ganze Layout kaputt'. Truncation per ellipsis war hier
verkehrt — der Operator will den Volltext sehen, auch wenn er
dafür auf zwei Zeilen muss. */
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
vertical-align: middle;
}
/* Im Two-Line-Layout (envelope + Header-From) erbt die ef-stack-Box
das Wrap-Verhalten — beide Zeilen umbrechen unabhängig, kein
Ellipsis. */
.masked-address .ef-stack {
max-width: 100%;
}
.masked-address .ef-line {
display: block;
overflow-wrap: anywhere;
word-break: break-word;
white-space: normal;
}
.masked-clickable {
cursor: pointer;
border-bottom: 1px dotted #94A3B8;
border-radius: 2px;
}
.masked-clickable:hover {
color: #0EA5E9;
border-bottom-color: #0EA5E9;
}
.masked-clickable:focus-visible {
outline: 2px solid #0EA5E9;
outline-offset: 2px;
}
/* Quarantine-Tabelle: Subject-Spalte hart auf eine Zeile limitieren
damit lange Spam-Betreffe das Layout nicht zerlegen. AntD's
ellipsis: true greift bei sehr langen ungefluschten Strings nicht
immer — diese expliziten max-width + Truncation sind robuster. */
.quar-subject-cell {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
/* Reporting-Page — Stacked-Bar-Chart pro Tag.
Pro-Bar Stack-Reihenfolge bottom→top: clean, other, spam, rejected.
Höhe relativ zum Cluster-weiten Tages-Maximum, damit ruhige Tage
nicht abflachen. */
.reporting-chart {
display: flex;
align-items: flex-end;
gap: 2px;
height: 220px;
padding: 8px 4px;
border-bottom: 1px solid #E2E8F0;
border-left: 1px solid #E2E8F0;
background: linear-gradient(to top, #F8FAFC 0%, transparent 60%);
}
.reporting-bar-col {
flex: 1 1 0;
display: flex;
align-items: flex-end;
height: 100%;
min-width: 4px;
}
.reporting-bar-stack {
display: flex;
flex-direction: column-reverse;
width: 100%;
height: 100%;
border-radius: 2px 2px 0 0;
overflow: hidden;
transition: opacity 0.15s ease;
}
.reporting-bar-col:hover .reporting-bar-stack { opacity: 0.85; }
.reporting-bar { width: 100%; }
.reporting-bar--clean { background: #10B981; }
.reporting-bar--other { background: #94A3B8; }
.reporting-bar--spam { background: #F59E0B; }
.reporting-bar--rej { background: #EF4444; }
.reporting-chart-legend {
display: flex;
gap: 18px;
flex-wrap: wrap;
margin-top: 12px;
font-size: 12px;
color: #475569;
}
.reporting-chart-legend .legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.reporting-chart-legend .legend-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 2px;
}
.legend-dot--clean { background: #10B981; }
.legend-dot--spam { background: #F59E0B; }
.legend-dot--rej { background: #EF4444; }
.legend-dot--other { background: #94A3B8; }
/* ── Dashboard v2 KPI-Tiles ────────────────────────────────────────── */
.kpi-tile-card { transition: box-shadow 0.15s ease; }
.kpi-tile-card:hover { box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06); }
.kpi-tile-row { display: flex; justify-content: space-between; }
.kpi-tile-label {
font-size: 11px;
font-weight: 500;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.kpi-tile-value-row {
display: flex;
align-items: baseline;
gap: 8px;
margin-top: 6px;
}
.kpi-tile-value { margin: 0 !important; line-height: 1.1 !important; }
.kpi-tile-suffix { font-size: 13px; color: #64748B; margin-left: 2px; }
.kpi-tile-delta { font-size: 12px; font-weight: 500; }
.kpi-tile-subline { font-size: 11px; display: block; margin-top: 4px; }
.kpi-tile-spark { margin-top: 12px; }
/* ── Dashboard v2 DistributionBar ──────────────────────────────────── */
.distribution-bar {
display: flex;
width: 100%;
border-radius: 3px;
overflow: hidden;
background: #F1F5F9;
}
.distribution-bar-empty {
width: 100%;
border-radius: 3px;
background: #F1F5F9;
}
.distribution-bar-seg {
display: block;
height: 100%;
transition: width 0.2s ease;
}
/* ── Dashboard v2 Sections ─────────────────────────────────────────── */
.dashboard-section { margin-bottom: 20px; }
.dashboard-section-head {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 10px;
gap: 12px;
flex-wrap: wrap;
}
.dashboard-section-title { margin: 0 !important; }
.dashboard-empty-chart {
height: 240px;
display: flex;
align-items: center;
justify-content: center;
}
/* ── Dashboard v2 ThreatRadar ──────────────────────────────────────── */
.threat-list-section-label {
display: block;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
margin-bottom: 8px;
}
.threat-active-list { display: flex; flex-direction: column; gap: 6px; }
.threat-active-row {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
}
.threat-active-bar {
width: 4px;
height: 30px;
border-radius: 2px;
flex-shrink: 0;
}
.threat-active-text { display: flex; flex-direction: column; flex: 1 1 auto; }
.threat-active-count {
font-size: 18px;
min-width: 36px;
text-align: right;
}
/* ── Dashboard v2 TopRecipients ────────────────────────────────────── */
.topr-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #F1F5F9;
}
.topr-domain-cell { display: flex; flex-direction: column; }
.topr-domain-name { font-size: 13px; font-weight: 500; }
.topr-domain-subtitle {
font-size: 11px;
color: #94A3B8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 320px;
}
.topr-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 16px;
border-top: 1px solid #F1F5F9;
flex-wrap: wrap;
gap: 10px;
}
.topr-legend { font-size: 11px; }
/* ── Dashboard v2 Page-Root ────────────────────────────────────────── */
.dashboard-v2-page { padding: 4px; }
.dashboard-v2-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
flex-wrap: wrap;
gap: 12px;
}
.dashboard-v2-title { margin: 0 !important; }
/* LearningStats — Enterprise-Card mit Donut + Metric-Bars (1.8.88) */
.learning-card .ant-card-body { padding: 20px 24px; }
.learning-donut-wrap {
display: flex; align-items: center; justify-content: center;
min-height: 140px;
position: relative;
}
.learning-donut-empty {
display: flex; flex-direction: column; align-items: center;
justify-content: center;
width: 140px; height: 140px;
border: 2px dashed #E2E8F0; border-radius: 50%;
text-align: center;
}
/* 1.10.5: Donut-Center-Label zog frueher per Pie-annotations bei
y='62%' rein und schnitt unten in den Ring; der Annotations-Pfad
ist nicht zuverlaessig zentriert weil chart-padding mitspielt.
Jetzt absolute Overlay zentriert auf 50/50, bleibt sicher in der
inneren Hole (innerRadius=0.7 → 70% Hohlraum) und wraps nicht. */
.learning-donut-center {
position: absolute; inset: 0;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
pointer-events: none;
text-align: center;
line-height: 1.05;
}
.learning-donut-center-value {
font-size: 22px; font-weight: 700; color: #0F172A;
}
.learning-donut-center-label {
font-size: 9px; color: #94A3B8;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-top: 2px;
}
.learning-metric-row {
display: flex; align-items: center; gap: 10px;
padding: 4px 0;
}
.learning-metric-bar {
width: 4px; height: 28px; border-radius: 2px; flex-shrink: 0;
}
.learning-metric-text {
display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0;
}
.learning-metric-text .ant-typography {
line-height: 1.15;
}
.learning-metric-value { font-size: 16px !important; }
.learning-metric-share {
font-variant-numeric: tabular-nums;
min-width: 40px; text-align: right;
}
.learning-neural-profile {
padding: 10px 0;
border-bottom: 1px solid #F1F5F9;
}
.learning-neural-profile:last-child { border-bottom: 0; padding-bottom: 0; }
.learning-neural-profile:first-child { padding-top: 0; }
.learning-neural-head {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 8px;
gap: 8px;
}
.learning-neural-name { font-family: 'JetBrains Mono', monospace; }