diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index a1cd0cc5501..60ad14b86b2 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -760,6 +760,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param "pgtrigger", "pghistory", "single_session", + "dojo.watercolor.apps.WatercolorConfig", ) # ------------------------------------------------------------------------------ diff --git a/dojo/static/dojo/css/watercolor.css b/dojo/static/dojo/css/watercolor.css new file mode 100644 index 00000000000..a5843bef4b6 --- /dev/null +++ b/dojo/static/dojo/css/watercolor.css @@ -0,0 +1,787 @@ +/* Copyright (c) Tactivos / Mural. Licensed under BSD-3-Clause (see LICENSE). */ +/* + * Watercolor FE redesign — all tokens and component styles scoped under + * .theme-watercolor so they cannot leak into the rest of DefectDojo. + */ + +body.theme-watercolor { + /* Palette (keep the `--g-*` names from the old forest-green system) */ + --g-50: #f7fafc; + --g-100: #eef3f7; + --g-200: #dde6ee; + --g-300: #c2d2df; + --g-400: #9bb2c3; + --g-500: #6f8b9f; + --g-600: #4f6b80; + --g-700: #3a5366; + --g-800: #283e4f; + --g-900: #182a38; + + --brand: #5a9ec4; + --brand-hover: #346a8b; + + /* Surfaces */ + --bg-app: #f5f0e8; + --bg-surface: #ffffff; + --bg-tint: #f1ece2; + --bg-cream: #fbf5ea; + + /* Text */ + --text: #1f2a33; + --text-soft: #4a5b6b; + --text-mute: #7d8b97; + --line: rgba(31, 42, 51, 0.08); + + /* Severity washes (pastels) */ + --sev-critical: #f7d6d2; + --sev-high: #f7e3c8; + --sev-medium: #f5edc0; + --sev-low: #d8ead4; + --sev-info: #d5e3ee; + + /* Category gradients */ + --cat-web: linear-gradient(135deg, #dbeaf3, #b9d4e6); + --cat-cloud: linear-gradient(135deg, #f1e3d2, #e9d2b4); + --cat-supply: linear-gradient(135deg, #f0d8d3, #e2b8af); + --cat-identity: linear-gradient(135deg, #e3dded, #cbc1de); + --cat-data: linear-gradient(135deg, #d8e8eb, #b3d2d8); + --cat-config: linear-gradient(135deg, #dfead8, #c2d8b8); + + /* Hero gradient */ + --hero-gradient: linear-gradient(135deg, #f5e8d6 0%, #e9d6e2 45%, #d8e6ec 100%); + + /* Wash families for report cards */ + --wash-sky: linear-gradient(135deg, #dceaf3, #b9d4e6); + --wash-mint: linear-gradient(135deg, #dfead8, #b9d6b3); + --wash-lilac: linear-gradient(135deg, #e3dded, #c3b6dd); + --wash-rose: linear-gradient(135deg, #f7d6d2, #e6b5b0); + + /* Radii */ + --r-sm: 8px; + --r-md: 14px; + --r-lg: 20px; + --r-xl: 28px; + + /* Shadows */ + --shadow-card: 0 6px 18px rgba(24, 42, 56, 0.08); + --shadow-lift: 0 12px 32px rgba(24, 42, 56, 0.14); + --shadow-pop: 0 24px 60px rgba(24, 42, 56, 0.22); + + /* Motion */ + --easing: cubic-bezier(0.4, 0, 0.2, 1); + --d-fast: 140ms; + --d-base: 240ms; + --d-slow: 420ms; + + /* Type */ + --font-display: Georgia, "Times New Roman", serif; + --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + --h1: 48px; + --h2: 26px; + + background: var(--bg-app); + color: var(--text); + font-family: var(--font-ui); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +body.theme-watercolor #wrapper, +body.theme-watercolor #content-wrapper { + background: transparent; + padding: 0; +} + +body.theme-watercolor .wc-shell { + max-width: 1320px; + margin: 0 auto; + padding: 24px 32px 96px; + animation: wc-fade-in var(--d-slow) var(--easing) both; +} + +@keyframes wc-fade-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ---------- Top nav ---------- */ +body.theme-watercolor .wc-topnav { + background: var(--bg-surface); + border-bottom: 1px solid var(--line); + padding: 14px 32px 0; + position: sticky; + top: 0; + z-index: 50; +} +body.theme-watercolor .wc-topnav__row { + display: flex; + align-items: center; + gap: 24px; +} +body.theme-watercolor .wc-brand { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + color: var(--text); +} +body.theme-watercolor .wc-brand__mark { + width: 36px; height: 36px; + display: inline-flex; align-items: center; justify-content: center; + border-radius: var(--r-sm); + background: linear-gradient(135deg, var(--brand), var(--brand-hover)); + color: #fff; + font-family: var(--font-display); + font-weight: 700; + font-size: 18px; +} +body.theme-watercolor .wc-brand__title { display: block; font-family: var(--font-display); font-size: 18px; } +body.theme-watercolor .wc-brand__sub { display: block; font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-mute); } + +body.theme-watercolor .wc-search { + flex: 1 1 auto; + max-width: 520px; + background: rgba(245, 240, 232, 0.6); + backdrop-filter: blur(8px); + border: 1px solid var(--line); + border-radius: 999px; + padding: 8px 16px; + display: flex; align-items: center; gap: 10px; +} +body.theme-watercolor .wc-search__input { + flex: 1; border: 0; background: transparent; outline: none; + font-size: 14px; color: var(--text); +} +body.theme-watercolor .wc-search__kbd { + font-family: var(--font-ui); + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--line); + border-radius: 6px; + background: #fff; + color: var(--text-mute); +} + +body.theme-watercolor .wc-actions { display: flex; align-items: center; gap: 12px; } +body.theme-watercolor .wc-action-btn { + position: relative; + background: transparent; + border: 1px solid var(--line); + border-radius: 999px; + width: 38px; height: 38px; + cursor: pointer; +} +body.theme-watercolor .wc-dot { + width: 8px; height: 8px; border-radius: 999px; + display: inline-block; vertical-align: middle; +} +body.theme-watercolor .wc-dot--red { position: absolute; top: 8px; right: 8px; background: #d76661; } +body.theme-watercolor .wc-dot--critical { background: #c25a55; } +body.theme-watercolor .wc-dot--high { background: #d8954e; } +body.theme-watercolor .wc-dot--medium { background: #c5b251; } +body.theme-watercolor .wc-dot--low { background: #6a9a6a; } +body.theme-watercolor .wc-dot--info { background: #6b8aa6; } +body.theme-watercolor .wc-dot--sla { background: #b06a92; } +body.theme-watercolor .wc-dot--fixed { background: #6a9a6a; } + +body.theme-watercolor .wc-badge { + position: absolute; + top: -4px; right: -4px; + background: var(--brand); + color: #fff; + font-size: 11px; + border-radius: 999px; + padding: 2px 6px; + min-width: 18px; + text-align: center; +} + +body.theme-watercolor .wc-avatar-pill { + display: flex; align-items: center; gap: 8px; + background: var(--bg-tint); + border-radius: 999px; + padding: 4px 12px 4px 4px; +} +body.theme-watercolor .wc-avatar-pill__initials { + width: 30px; height: 30px; + border-radius: 999px; + background: var(--brand); + color: #fff; + display: inline-flex; align-items: center; justify-content: center; + font-size: 12px; +} +body.theme-watercolor .wc-avatar-pill__name { display: block; font-size: 13px; } +body.theme-watercolor .wc-avatar-pill__role { display: block; font-size: 11px; color: var(--text-mute); } + +body.theme-watercolor .wc-catbar { + display: flex; gap: 24px; padding: 14px 0; + overflow-x: auto; +} +body.theme-watercolor .wc-catbar__item { + color: var(--text-soft); + text-decoration: none; + font-size: 13px; + padding-bottom: 6px; + border-bottom: 2px solid transparent; + white-space: nowrap; +} +body.theme-watercolor .wc-catbar__item.is-active { + color: var(--text); + border-bottom-color: var(--brand); +} + +/* ---------- Hero ---------- */ +body.theme-watercolor .wc-hero { + display: grid; + grid-template-columns: 1.5fr 1fr; + gap: 32px; + background: var(--hero-gradient); + border-radius: var(--r-xl); + padding: 48px; + margin-bottom: 32px; + box-shadow: var(--shadow-card); +} +body.theme-watercolor .wc-eyebrow { + display: inline-block; + background: rgba(255,255,255,0.6); + padding: 4px 12px; + border-radius: 999px; + font-size: 11px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text-soft); +} +body.theme-watercolor .wc-h1 { + font-family: var(--font-display); + font-size: var(--h1); + line-height: 1.1; + margin: 16px 0 12px; + color: var(--text); +} +body.theme-watercolor .wc-h1__em { + font-style: italic; + background: linear-gradient(135deg, #d86a6a, #a274c2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +body.theme-watercolor .wc-h2 { + font-family: var(--font-display); + font-size: var(--h2); + margin: 0 0 16px; + color: var(--text); +} +body.theme-watercolor .wc-hero__body { font-size: 16px; color: var(--text-soft); max-width: 540px; } +body.theme-watercolor .wc-hero__ctas { display: flex; gap: 12px; margin-top: 24px; } + +body.theme-watercolor .wc-btn { + display: inline-flex; align-items: center; justify-content: center; + border-radius: 999px; + padding: 10px 22px; + font-size: 14px; + cursor: pointer; + border: 1px solid transparent; + text-decoration: none; + transition: background var(--d-fast) var(--easing), color var(--d-fast) var(--easing); +} +body.theme-watercolor .wc-btn--primary { background: var(--g-800); color: #fff; } +body.theme-watercolor .wc-btn--primary:hover { background: var(--g-900); } +body.theme-watercolor .wc-btn--ghost { background: rgba(255,255,255,0.6); color: var(--text); border-color: var(--line); } +body.theme-watercolor .wc-btn--ghost:hover { background: #fff; } +body.theme-watercolor .wc-btn--block { width: 100%; } + +body.theme-watercolor .wc-counter-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} +body.theme-watercolor .wc-counter-cell { + background: rgba(255,255,255,0.7); + border-radius: var(--r-md); + padding: 16px; +} +body.theme-watercolor .wc-counter { + font-family: var(--font-display); + font-size: 36px; + color: var(--text); + display: block; +} +body.theme-watercolor .wc-counter-cell__label { + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-mute); +} + +/* ---------- Assurance row ---------- */ +body.theme-watercolor .wc-assurance { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0; + background: var(--bg-surface); + border-radius: var(--r-md); + padding: 20px; + box-shadow: var(--shadow-card); + margin-bottom: 40px; +} +body.theme-watercolor .wc-assurance__cell { + font-size: 13px; + color: var(--text-soft); + padding: 0 16px; + border-right: 1px solid var(--line); +} +body.theme-watercolor .wc-assurance__cell:last-child { border-right: 0; } +body.theme-watercolor .wc-assurance__cell strong { color: var(--text); } + +/* ---------- Sections ---------- */ +body.theme-watercolor .wc-section { margin: 40px 0; } + +body.theme-watercolor .wc-cat-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 16px; +} +@media (max-width: 1024px) { + body.theme-watercolor .wc-cat-grid { grid-template-columns: repeat(3, 1fr); } + body.theme-watercolor .wc-hero { grid-template-columns: 1fr; padding: 32px; } +} + +body.theme-watercolor .wc-cat-tile { + display: flex; flex-direction: column; + background: var(--bg-surface); + border-radius: var(--r-md); + overflow: hidden; + box-shadow: var(--shadow-card); + text-decoration: none; + color: var(--text); + transition: transform var(--d-base) var(--easing), box-shadow var(--d-base) var(--easing); +} +body.theme-watercolor .wc-cat-tile:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); } +body.theme-watercolor .wc-cat-tile__header { + height: 60px; + position: relative; + display: flex; justify-content: flex-end; align-items: center; + padding: 0 12px; +} +body.theme-watercolor .wc-cat-tile.cat-web .wc-cat-tile__header { background: var(--cat-web); } +body.theme-watercolor .wc-cat-tile.cat-cloud .wc-cat-tile__header { background: var(--cat-cloud); } +body.theme-watercolor .wc-cat-tile.cat-supply .wc-cat-tile__header { background: var(--cat-supply); } +body.theme-watercolor .wc-cat-tile.cat-identity .wc-cat-tile__header { background: var(--cat-identity); } +body.theme-watercolor .wc-cat-tile.cat-data .wc-cat-tile__header { background: var(--cat-data); } +body.theme-watercolor .wc-cat-tile.cat-config .wc-cat-tile__header { background: var(--cat-config); } +body.theme-watercolor .wc-cat-tile__icon { width: 40px; height: 40px; opacity: 0.85; } +body.theme-watercolor .wc-cat-tile__body { padding: 14px 16px 18px; } +body.theme-watercolor .wc-cat-tile__name { display: block; font-size: 14px; font-weight: 600; } +body.theme-watercolor .wc-cat-tile__count { display: block; font-size: 12px; color: var(--text-mute); margin-top: 2px; } + +/* ---------- Card grids ---------- */ +body.theme-watercolor .wc-card-grid { + display: grid; + gap: 20px; +} +body.theme-watercolor .wc-card-grid--4up { grid-template-columns: repeat(4, 1fr); } +body.theme-watercolor .wc-card-grid--browse { grid-template-columns: repeat(3, 1fr); } +@media (max-width: 1024px) { + body.theme-watercolor .wc-card-grid--4up, + body.theme-watercolor .wc-card-grid--browse { grid-template-columns: repeat(2, 1fr); } +} +body.theme-watercolor .wc-card-scroller { + grid-auto-flow: column; + grid-auto-columns: 260px; + overflow-x: auto; + padding-bottom: 12px; +} + +/* ---------- Finding card ---------- */ +body.theme-watercolor .wc-card { + position: relative; + background: var(--bg-surface); + border-radius: var(--r-md); + overflow: hidden; + box-shadow: var(--shadow-card); + cursor: pointer; + transition: transform var(--d-base) var(--easing), box-shadow var(--d-base) var(--easing), opacity var(--d-base) var(--easing); + opacity: 0; + animation: wc-card-in var(--d-slow) var(--easing) forwards; +} +body.theme-watercolor .wc-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); } +body.theme-watercolor .wc-card__cover { + aspect-ratio: 16 / 7; + background-size: cover; + background-position: center; + background-color: var(--bg-tint); +} +@keyframes wc-card-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } + +body.theme-watercolor .wc-card.severity-critical .wc-card__cover { background-color: var(--sev-critical); } +body.theme-watercolor .wc-card.severity-high .wc-card__cover { background-color: var(--sev-high); } +body.theme-watercolor .wc-card.severity-medium .wc-card__cover { background-color: var(--sev-medium); } +body.theme-watercolor .wc-card.severity-low .wc-card__cover { background-color: var(--sev-low); } +body.theme-watercolor .wc-card.severity-info .wc-card__cover { background-color: var(--sev-info); } + +body.theme-watercolor .wc-heart { + position: absolute; + top: 12px; right: 12px; + width: 36px; height: 36px; + border-radius: 999px; + background: rgba(255,255,255,0.85); + border: 0; + cursor: pointer; + display: inline-flex; align-items: center; justify-content: center; + color: var(--text-mute); + transition: color var(--d-fast) var(--easing), transform var(--d-fast) var(--easing); +} +body.theme-watercolor .wc-heart.is-saved { color: #d76661; } +body.theme-watercolor .wc-heart:hover { transform: scale(1.08); } +body.theme-watercolor .wc-heart__glyph { font-size: 18px; line-height: 1; } + +body.theme-watercolor .wc-card__body { padding: 16px 18px 20px; } +body.theme-watercolor .wc-chip { + display: inline-block; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text); +} +body.theme-watercolor .wc-card.severity-critical .wc-chip--severity { background: var(--sev-critical); } +body.theme-watercolor .wc-card.severity-high .wc-chip--severity { background: var(--sev-high); } +body.theme-watercolor .wc-card.severity-medium .wc-chip--severity { background: var(--sev-medium); } +body.theme-watercolor .wc-card.severity-low .wc-chip--severity { background: var(--sev-low); } +body.theme-watercolor .wc-card.severity-info .wc-chip--severity { background: var(--sev-info); } + +body.theme-watercolor .wc-card__title { + font-family: var(--font-display); + font-size: 17px; + margin: 10px 0 8px; + line-height: 1.3; + color: var(--text); +} +body.theme-watercolor .wc-card__meta { + display: flex; gap: 12px; + color: var(--text-mute); + font-size: 12px; + align-items: baseline; +} +body.theme-watercolor .wc-cvss { + font-family: var(--font-display); + font-size: 22px; + color: var(--text); +} + +/* ---------- Browse ---------- */ +body.theme-watercolor .wc-browse { + display: grid; + grid-template-columns: 260px 1fr; + gap: 32px; +} +@media (max-width: 1024px) { + body.theme-watercolor .wc-browse { grid-template-columns: 1fr; } +} +body.theme-watercolor .wc-filters { + position: sticky; + top: 132px; + align-self: flex-start; + background: var(--bg-surface); + border-radius: var(--r-md); + padding: 20px; + box-shadow: var(--shadow-card); + max-height: calc(100vh - 160px); + overflow: auto; +} +body.theme-watercolor .wc-filter-group { + border: 0; + margin: 0 0 20px; + padding: 0; +} +body.theme-watercolor .wc-filter-group legend { + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-mute); + margin-bottom: 8px; +} +body.theme-watercolor .wc-check, body.theme-watercolor .wc-radio { + display: flex; align-items: center; gap: 8px; + font-size: 13px; + margin: 4px 0; +} +body.theme-watercolor .wc-pill-toggle { + display: inline-flex; align-items: center; gap: 6px; + margin-right: 8px; + font-size: 12px; +} + +body.theme-watercolor .wc-results__head { + display: flex; align-items: center; gap: 16px; + margin-bottom: 16px; +} +body.theme-watercolor .wc-results__count { + font-size: 20px; + color: var(--text-mute); +} +body.theme-watercolor .wc-sort { + margin-left: auto; + display: flex; align-items: center; gap: 8px; + font-size: 13px; +} +body.theme-watercolor .wc-chip-row { + display: flex; flex-wrap: wrap; gap: 8px; + list-style: none; + padding: 0; + margin: 0 0 16px; +} +body.theme-watercolor .wc-chip--active { + background: var(--bg-tint); + border: 1px solid var(--line); +} + +body.theme-watercolor .wc-pagination { + display: flex; gap: 12px; align-items: center; + margin-top: 24px; + justify-content: center; +} +body.theme-watercolor .wc-pagination__link { + background: var(--bg-surface); + border: 1px solid var(--line); + border-radius: 999px; + padding: 6px 14px; + text-decoration: none; + color: var(--text); +} + +/* ---------- Queue ---------- */ +body.theme-watercolor .wc-queue__head { + display: flex; align-items: center; gap: 16px; + margin-bottom: 24px; +} +body.theme-watercolor .wc-queue__count { + font-size: 24px; + color: var(--text-mute); +} +body.theme-watercolor .wc-empty { + background: var(--bg-surface); + border: 1px dashed var(--line); + border-radius: var(--r-md); + padding: 32px; + text-align: center; + color: var(--text-mute); +} +body.theme-watercolor .wc-empty--state { + background: var(--bg-cream); + font-family: var(--font-display); + font-size: 18px; + color: var(--text-soft); +} + +/* ---------- Reports ---------- */ +body.theme-watercolor .wc-reports__head { margin-bottom: 24px; } +body.theme-watercolor .wc-reports__sub { color: var(--text-soft); } +body.theme-watercolor .wc-report-card { + background: var(--bg-surface); + border-radius: var(--r-md); + overflow: hidden; + box-shadow: var(--shadow-card); + display: flex; flex-direction: column; +} +body.theme-watercolor .wc-report-card__cover { + aspect-ratio: 16 / 9; + background: var(--cat-web); +} +body.theme-watercolor .wc-report-card.wash-sky .wc-report-card__cover { background: var(--wash-sky); } +body.theme-watercolor .wc-report-card.wash-mint .wc-report-card__cover { background: var(--wash-mint); } +body.theme-watercolor .wc-report-card.wash-lilac .wc-report-card__cover { background: var(--wash-lilac); } +body.theme-watercolor .wc-report-card.wash-rose .wc-report-card__cover { background: var(--wash-rose); } +body.theme-watercolor .wc-report-card__body { padding: 20px 22px 24px; } +body.theme-watercolor .wc-report-card__cat { + display: inline-block; font-size: 11px; letter-spacing: 0.1em; + text-transform: uppercase; color: var(--text-mute); +} +body.theme-watercolor .wc-report-card__title { + font-family: var(--font-display); + font-size: 20px; + margin: 6px 0 10px; +} + +/* ---------- Drawer ---------- */ +body.theme-watercolor .wc-drawer { + position: fixed; + top: 0; right: 0; bottom: 0; + width: 640px; + max-width: 92vw; + background: var(--bg-surface); + box-shadow: var(--shadow-pop); + transform: translateX(110%); + transition: transform var(--d-base) var(--easing); + z-index: 100; + overflow: hidden; +} +body.theme-watercolor .wc-drawer.is-open { transform: translateX(0); } +body.theme-watercolor .wc-drawer__inner { + height: 100%; + overflow: auto; +} +body.theme-watercolor .wc-drawer__head { + position: sticky; + top: 0; + padding: 16px 24px; + display: flex; align-items: center; justify-content: space-between; + background: var(--bg-surface); + border-bottom: 1px solid var(--line); + z-index: 1; +} +body.theme-watercolor .wc-drawer__actions { display: flex; gap: 8px; } +body.theme-watercolor .wc-drawer__icon { + background: transparent; border: 1px solid var(--line); + border-radius: 999px; + width: 34px; height: 34px; + cursor: pointer; + display: inline-flex; align-items: center; justify-content: center; +} +body.theme-watercolor .wc-drawer__cover { + aspect-ratio: 16 / 7; + background-size: cover; + background-position: center; +} +body.theme-watercolor .wc-drawer__thumbs { + display: flex; gap: 8px; padding: 12px 24px; +} +body.theme-watercolor .wc-drawer__thumb { + width: 72px; height: 40px; + border-radius: var(--r-sm); + background-size: cover; background-position: center; + opacity: 0.6; + border: 2px solid transparent; +} +body.theme-watercolor .wc-drawer__thumb.is-active { opacity: 1; border-color: var(--brand); } +body.theme-watercolor .wc-drawer__body { padding: 8px 24px 48px; } +body.theme-watercolor .wc-drawer__title-row { + display: flex; align-items: baseline; gap: 12px; + flex-wrap: wrap; +} +body.theme-watercolor .wc-drawer__title { + font-family: var(--font-display); + font-size: 26px; + margin: 12px 0 4px; +} +body.theme-watercolor .wc-pill { + display: inline-block; + padding: 2px 10px; + border-radius: 999px; + background: var(--bg-tint); + font-size: 11px; + color: var(--text-soft); +} +body.theme-watercolor .wc-drawer__sub { color: var(--text-soft); margin: 0 0 14px; } +body.theme-watercolor .wc-drawer__cvss { + display: inline-flex; align-items: baseline; gap: 8px; + background: var(--bg-cream); + padding: 10px 16px; + border-radius: var(--r-md); + margin: 8px 0 16px; +} +body.theme-watercolor .wc-drawer__cvss-label { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-mute); } +body.theme-watercolor .wc-drawer__cvss-value { font-family: var(--font-display); font-size: 28px; } +body.theme-watercolor .wc-drawer__cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 24px; } +body.theme-watercolor .wc-drawer__section { margin: 20px 0; } +body.theme-watercolor .wc-drawer__h { + font-family: var(--font-display); + font-size: 16px; + color: var(--text-soft); + margin: 0 0 8px; +} +body.theme-watercolor .wc-drawer__proof { + background: var(--g-50); + border: 1px solid var(--line); + border-radius: var(--r-sm); + padding: 12px; + font-size: 12px; + overflow: auto; + color: var(--text); +} +body.theme-watercolor .wc-drawer__details { + display: grid; + grid-template-columns: 120px 1fr; + gap: 6px 16px; + font-size: 13px; +} +body.theme-watercolor .wc-drawer__details dt { color: var(--text-mute); } +body.theme-watercolor .wc-tags, body.theme-watercolor .wc-avatars { + display: flex; flex-wrap: wrap; gap: 6px; + list-style: none; padding: 0; margin: 0; +} +body.theme-watercolor .wc-tag { + background: var(--bg-tint); + padding: 4px 10px; + border-radius: 999px; + font-size: 12px; + color: var(--text-soft); +} +body.theme-watercolor .wc-avatar { + width: 28px; height: 28px; + border-radius: 999px; + background: var(--brand); + color: #fff; + display: inline-flex; align-items: center; justify-content: center; + font-size: 11px; +} + +/* ---------- Toast ---------- */ +body.theme-watercolor .wc-toast { + position: fixed; + bottom: 24px; left: 50%; + transform: translateX(-50%) translateY(40px); + background: var(--g-800); + color: #fff; + padding: 10px 20px; + border-radius: 999px; + font-size: 13px; + opacity: 0; + transition: opacity var(--d-base) var(--easing), transform var(--d-base) var(--easing); + z-index: 200; +} +body.theme-watercolor .wc-toast.is-visible { + opacity: 1; + transform: translateX(-50%) translateY(0); +} + +/* ---------- Command palette ---------- */ +body.theme-watercolor .wc-palette { + position: fixed; inset: 0; + background: rgba(24, 42, 56, 0.36); + display: none; + align-items: flex-start; + justify-content: center; + padding-top: 14vh; + z-index: 300; +} +body.theme-watercolor .wc-palette.is-open { display: flex; } +body.theme-watercolor .wc-palette__card { + width: min(540px, 92vw); + background: var(--bg-surface); + border-radius: var(--r-lg); + box-shadow: var(--shadow-pop); + overflow: hidden; +} +body.theme-watercolor .wc-palette__input { + width: 100%; + padding: 16px 20px; + border: 0; + font-size: 15px; + outline: none; +} +body.theme-watercolor .wc-palette__hint { + padding: 4px 20px 8px; + font-size: 11px; + color: var(--text-mute); +} +body.theme-watercolor .wc-palette__list { + margin: 0; padding: 8px 0 16px; + list-style: none; +} +body.theme-watercolor .wc-palette__item { + padding: 8px 20px; + font-size: 13px; + color: var(--text-mute); +} diff --git a/dojo/static/dojo/img/watercolor/category/cloud.svg b/dojo/static/dojo/img/watercolor/category/cloud.svg new file mode 100644 index 00000000000..bda0dc86e32 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/category/cloud.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/category/config.svg b/dojo/static/dojo/img/watercolor/category/config.svg new file mode 100644 index 00000000000..ce2971d8845 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/category/config.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/category/data.svg b/dojo/static/dojo/img/watercolor/category/data.svg new file mode 100644 index 00000000000..8561cb33c71 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/category/data.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/category/identity.svg b/dojo/static/dojo/img/watercolor/category/identity.svg new file mode 100644 index 00000000000..e09db0cb4f0 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/category/identity.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/category/supply.svg b/dojo/static/dojo/img/watercolor/category/supply.svg new file mode 100644 index 00000000000..81e2eb07b73 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/category/supply.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/category/web.svg b/dojo/static/dojo/img/watercolor/category/web.svg new file mode 100644 index 00000000000..7af33059998 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/category/web.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/critical-1.svg b/dojo/static/dojo/img/watercolor/covers/critical-1.svg new file mode 100644 index 00000000000..b25dd9d88ff --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/critical-1.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/critical-2.svg b/dojo/static/dojo/img/watercolor/covers/critical-2.svg new file mode 100644 index 00000000000..44f7f8a0ba6 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/critical-2.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/critical-3.svg b/dojo/static/dojo/img/watercolor/covers/critical-3.svg new file mode 100644 index 00000000000..f3232ba87e4 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/critical-3.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/critical-4.svg b/dojo/static/dojo/img/watercolor/covers/critical-4.svg new file mode 100644 index 00000000000..4b8def38128 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/critical-4.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/critical-5.svg b/dojo/static/dojo/img/watercolor/covers/critical-5.svg new file mode 100644 index 00000000000..ff779034661 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/critical-5.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/high-1.svg b/dojo/static/dojo/img/watercolor/covers/high-1.svg new file mode 100644 index 00000000000..70bdd9f32a6 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/high-1.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/high-2.svg b/dojo/static/dojo/img/watercolor/covers/high-2.svg new file mode 100644 index 00000000000..c66fe689eea --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/high-2.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/high-3.svg b/dojo/static/dojo/img/watercolor/covers/high-3.svg new file mode 100644 index 00000000000..d18b6b6bfb5 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/high-3.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/high-4.svg b/dojo/static/dojo/img/watercolor/covers/high-4.svg new file mode 100644 index 00000000000..57098370914 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/high-4.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/high-5.svg b/dojo/static/dojo/img/watercolor/covers/high-5.svg new file mode 100644 index 00000000000..d25de5e948b --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/high-5.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/info-1.svg b/dojo/static/dojo/img/watercolor/covers/info-1.svg new file mode 100644 index 00000000000..f0d05ce6ef3 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/info-1.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/info-2.svg b/dojo/static/dojo/img/watercolor/covers/info-2.svg new file mode 100644 index 00000000000..a6dca2b4c22 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/info-2.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/info-3.svg b/dojo/static/dojo/img/watercolor/covers/info-3.svg new file mode 100644 index 00000000000..da6a9592ad2 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/info-3.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/info-4.svg b/dojo/static/dojo/img/watercolor/covers/info-4.svg new file mode 100644 index 00000000000..e3b921a5660 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/info-4.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/info-5.svg b/dojo/static/dojo/img/watercolor/covers/info-5.svg new file mode 100644 index 00000000000..6a735fc72b7 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/info-5.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/low-1.svg b/dojo/static/dojo/img/watercolor/covers/low-1.svg new file mode 100644 index 00000000000..826ef224dd9 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/low-1.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/low-2.svg b/dojo/static/dojo/img/watercolor/covers/low-2.svg new file mode 100644 index 00000000000..04f03379b84 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/low-2.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/low-3.svg b/dojo/static/dojo/img/watercolor/covers/low-3.svg new file mode 100644 index 00000000000..4e6f9048afb --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/low-3.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/low-4.svg b/dojo/static/dojo/img/watercolor/covers/low-4.svg new file mode 100644 index 00000000000..b17d76e53ee --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/low-4.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/low-5.svg b/dojo/static/dojo/img/watercolor/covers/low-5.svg new file mode 100644 index 00000000000..05b13424492 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/low-5.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/medium-1.svg b/dojo/static/dojo/img/watercolor/covers/medium-1.svg new file mode 100644 index 00000000000..e6f8832dd4b --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/medium-1.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/medium-2.svg b/dojo/static/dojo/img/watercolor/covers/medium-2.svg new file mode 100644 index 00000000000..f5dbdd3892c --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/medium-2.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/medium-3.svg b/dojo/static/dojo/img/watercolor/covers/medium-3.svg new file mode 100644 index 00000000000..3394d79e791 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/medium-3.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/medium-4.svg b/dojo/static/dojo/img/watercolor/covers/medium-4.svg new file mode 100644 index 00000000000..5a6c0cc9259 --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/medium-4.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/img/watercolor/covers/medium-5.svg b/dojo/static/dojo/img/watercolor/covers/medium-5.svg new file mode 100644 index 00000000000..516b2be36ba --- /dev/null +++ b/dojo/static/dojo/img/watercolor/covers/medium-5.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/dojo/static/dojo/js/watercolor.js b/dojo/static/dojo/js/watercolor.js new file mode 100644 index 00000000000..ae5755a7f84 --- /dev/null +++ b/dojo/static/dojo/js/watercolor.js @@ -0,0 +1,208 @@ +// Copyright (c) Tactivos / Mural. Licensed under BSD-3-Clause (see LICENSE). +// Watercolor FE behaviors: counters, bookmarks, drawer, command palette, stagger. +(function () { + "use strict"; + + var STAGGER_CAP = 6; + var STAGGER_STEP_MS = 35; + var COUNTER_DURATION_MS = 800; + + function getCookie(name) { + var match = document.cookie.match(new RegExp("(^|; )" + name + "=([^;]+)")); + return match ? decodeURIComponent(match[2]) : ""; + } + + function easeOutCubic(t) { + return 1 - Math.pow(1 - t, 3); + } + + function animateCount(el, target, duration) { + var start = null; + target = Math.max(0, parseInt(target, 10) || 0); + function step(ts) { + if (start === null) { start = ts; } + var pct = Math.min(1, (ts - start) / duration); + var v = Math.round(easeOutCubic(pct) * target); + el.textContent = v; + if (pct < 1) { + requestAnimationFrame(step); + } else { + el.textContent = target; + } + } + requestAnimationFrame(step); + } + + function showToast(msg, isError) { + var t = document.getElementById("wc-toast"); + if (!t) { return; } + t.textContent = msg; + t.classList.toggle("wc-toast--error", !!isError); + t.classList.add("is-visible"); + setTimeout(function () { t.classList.remove("is-visible"); }, 2200); + } + + function refreshBadge() { + fetch("/api/v2/bookmarks/count/", { + credentials: "same-origin", + headers: { "Accept": "application/json" }, + }).then(function (r) { + return r.ok ? r.json() : null; + }).then(function (data) { + if (data && typeof data.count !== "undefined") { + var badge = document.getElementById("wc-bookmark-count"); + if (badge) { badge.textContent = data.count; } + } + }).catch(function () { /* swallow */ }); + } + + function initCounters() { + var nodes = document.querySelectorAll(".wc-counter[data-value]"); + nodes.forEach(function (el) { + var target = el.getAttribute("data-value"); + animateCount(el, target, COUNTER_DURATION_MS); + }); + } + + function initBookmarks() { + document.body.addEventListener("click", function (e) { + var btn = e.target.closest(".wc-heart"); + if (!btn) { return; } + e.preventDefault(); + e.stopPropagation(); + var fid = btn.getAttribute("data-finding-id"); + if (!fid) { return; } + var saved = btn.classList.contains("is-saved"); + var url = saved ? "/api/v2/bookmarks/" + fid + "/" : "/api/v2/bookmarks/"; + var opts = { + method: saved ? "DELETE" : "POST", + credentials: "same-origin", + headers: { + "X-CSRFToken": getCookie("csrftoken"), + "Content-Type": "application/json", + "Accept": "application/json", + }, + }; + if (!saved) { opts.body = JSON.stringify({ finding: parseInt(fid, 10) }); } + + // Optimistic toggle. + btn.classList.toggle("is-saved"); + btn.setAttribute("aria-pressed", saved ? "false" : "true"); + + fetch(url, opts).then(function (r) { + if (!r.ok) { throw new Error("bookmark request failed"); } + showToast(saved ? "Removed from queue." : "Saved to your queue."); + refreshBadge(); + }).catch(function () { + // Roll back optimistic state. + btn.classList.toggle("is-saved"); + btn.setAttribute("aria-pressed", saved ? "true" : "false"); + showToast("Could not update bookmark.", true); + }); + }); + } + + function openDrawer(html) { + var drawer = document.getElementById("wc-drawer"); + var body = document.getElementById("wc-drawer-body"); + if (!drawer || !body) { return; } + body.innerHTML = html; + drawer.classList.add("is-open"); + drawer.setAttribute("aria-hidden", "false"); + var close = document.getElementById("wc-drawer-close"); + if (close) { close.focus(); } + } + + function closeDrawer() { + var drawer = document.getElementById("wc-drawer"); + if (!drawer) { return; } + drawer.classList.remove("is-open"); + drawer.setAttribute("aria-hidden", "true"); + } + + function initDrawer() { + document.body.addEventListener("click", function (e) { + if (e.target.closest(".wc-heart")) { return; } + if (e.target.closest("#wc-drawer-close")) { closeDrawer(); return; } + var card = e.target.closest(".wc-card"); + if (!card) { return; } + var fid = card.getAttribute("data-finding-id"); + if (!fid) { return; } + e.preventDefault(); + fetch("/watercolor/finding/" + fid + "/drawer/", { + credentials: "same-origin", + headers: { "Accept": "text/html" }, + }).then(function (r) { + if (!r.ok) { throw new Error("drawer fetch failed"); } + return r.text(); + }).then(openDrawer) + .catch(function () { showToast("Could not load finding.", true); }); + }); + + document.addEventListener("keydown", function (e) { + if (e.key === "Escape") { closeDrawer(); closePalette(); } + // Tab-trap inside the drawer when open. + var drawer = document.getElementById("wc-drawer"); + if (e.key === "Tab" && drawer && drawer.classList.contains("is-open")) { + var focusables = drawer.querySelectorAll( + "button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])" + ); + if (focusables.length === 0) { return; } + var first = focusables[0]; + var last = focusables[focusables.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); first.focus(); + } + } + }); + } + + function openPalette() { + var p = document.getElementById("wc-palette"); + if (!p) { return; } + p.classList.add("is-open"); + p.setAttribute("aria-hidden", "false"); + var input = p.querySelector(".wc-palette__input"); + if (input) { input.focus(); } + } + + function closePalette() { + var p = document.getElementById("wc-palette"); + if (!p) { return; } + p.classList.remove("is-open"); + p.setAttribute("aria-hidden", "true"); + } + + function initPalette() { + document.addEventListener("keydown", function (e) { + if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) { + e.preventDefault(); + openPalette(); + } + }); + } + + function initStagger() { + var grids = document.querySelectorAll(".wc-card-grid"); + grids.forEach(function (grid) { + var children = grid.children; + for (var i = 0; i < children.length; i++) { + var idx = Math.min(i, STAGGER_CAP); + children[i].style.animationDelay = (idx * STAGGER_STEP_MS) + "ms"; + children[i].style.transitionDelay = (idx * STAGGER_STEP_MS) + "ms"; + } + }); + } + + function initAll() { + initCounters(); + initBookmarks(); + initDrawer(); + initPalette(); + initStagger(); + } + + window.WaterColor = { initAll: initAll, openPalette: openPalette, closePalette: closePalette }; +})(); diff --git a/dojo/templates/dojo/watercolor/_base.html b/dojo/templates/dojo/watercolor/_base.html new file mode 100644 index 00000000000..e5092166e7f --- /dev/null +++ b/dojo/templates/dojo/watercolor/_base.html @@ -0,0 +1,40 @@ + +{% extends "base.html" %} +{% load static %} +{% load watercolor %} + +{% block add_css %} + {{ block.super }} + +{% endblock %} + +{% block navigation %} + {# Replace the upstream side-nav with the watercolor top bar. #} + {% include "dojo/watercolor/_partials/_top_nav.html" %} +{% endblock %} + +{% block pre_wrapper %} + {# Apply the theme class via a JS shim since base.html bakes body class. #} + +{% endblock %} + +{% block content %} +
+ found in + {% if product %}{{ product.name }}{% else %}unknown product{% endif %} +
+{{ finding.steps_to_reproduce|default:"No proof captured yet." }}
+ No findings match these filters.
+ {% endfor %} +A calmer surface for the findings that actually need you today. Saved, assigned, and SLA-breach work, surfaced first.
+Nothing trending this week.
+ {% endfor %} +No findings are currently assigned to you.
+ {% endfor %} +No findings yet.
+ {% endfor %} +{{ empty_copy }}
+ {% endif %} +Discoverability shell over the existing DefectDojo report engine.
+