mirror of
https://github.com/mblanke/GooseStrike.git
synced 2026-03-01 05:50:22 -05:00
Add roadmap API and mock dashboard
This commit is contained in:
16
web/static/goose_flag_logo.svg
Normal file
16
web/static/goose_flag_logo.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200" role="img" aria-label="GooseStrike mark">
|
||||
<defs>
|
||||
<style>
|
||||
.goose-fill { fill: #0b0c0d; }
|
||||
.accent-red { fill: #d90429; }
|
||||
.leaf-fill { fill: #d90429; }
|
||||
.type { font-family: 'Oswald', 'Arial Black', sans-serif; font-size: 32px; font-weight: 700; letter-spacing: 2px; fill: #0b0c0d; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect width="300" height="200" fill="none" />
|
||||
<path class="goose-fill" d="M206 33c-32.7-14.7-78-16.4-104.1 8.4-12.9 12.4-19.7 29.6-16.5 48.5 2.7 16 11.8 29.6 24.8 38.8-11.9 6.4-19.4 17.5-19.4 29.6 0 22 21.5 39.1 56.7 39.1 20.6 0 37.6-4.8 50.7-13.7l-9.5-15c-8.9 6.3-22 10.6-37.5 10.6-19.6 0-32.8-8.2-32.8-20 0-7.7 8.1-15.5 22.8-17.7l26.6-4c36.4-5.5 63.3-28.2 63.3-66.1-.1-28.4-17.1-51.7-45.1-62.5zm-12.6 101.8-24.9 3.7c-15.5-9.4-26-25.8-26-43.4 0-32.7 34.2-52 68.5-43.9 23.3 5.5 36.7 22.5 36.7 43.6 0 27.3-21.3 34.4-54.3 40z"/>
|
||||
<path class="goose-fill" d="M210.7 48.4c9.9 2.7 19.1 8.3 25.6 16.1 4.6-6.5 5.2-14.8 1.2-20.8-5.3-7.8-17.5-11.9-30-10.4-5.7.7-10.5 2.5-14 5 6.9 1.2 12.8 3.4 17.2 6.1z"/>
|
||||
<path class="accent-red" d="M227.4 60.4c-2.9 3.8-7.5 6.7-13.2 8.2 4 4 9.2 6.6 14.1 7.3 6.3.9 12.5-1 16.4-5.3 1.9-2.1 3.1-4.6 3.3-7.2-6.2 1.6-13.1 1.2-20.6-3z"/>
|
||||
<polygon class="leaf-fill" points="122 88 132 88 135 72 142 88 152 88 144 101 154 112 140 113 135 128 128 112 116 114 125 101"/>
|
||||
<text class="type" x="40" y="182">GOOSE STRIKE</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
368
web/static/styles.css
Normal file
368
web/static/styles.css
Normal file
@@ -0,0 +1,368 @@
|
||||
:root {
|
||||
--black: #0b0c0d;
|
||||
--grey: #4b4f58;
|
||||
--white: #f5f5f5;
|
||||
--red: #d90429;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: var(--black);
|
||||
color: var(--white);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, var(--black), var(--grey));
|
||||
border-bottom: 4px solid var(--red);
|
||||
}
|
||||
|
||||
section.hero-panel {
|
||||
background: linear-gradient(120deg, rgba(0, 0, 0, 0.95), rgba(11, 12, 13, 0.85), rgba(75, 79, 88, 0.85));
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 2rem;
|
||||
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.hero-text h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.hero-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0 0 1.5rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.hero-list li {
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.hero-meta h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.hero-meta table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.hero-meta th,
|
||||
.hero-meta td {
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding: 0.45rem 0.6rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hero-meta th {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.hero-visual {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-visual-inner {
|
||||
width: min(320px, 100%);
|
||||
border-radius: 18px;
|
||||
background: radial-gradient(circle at top, rgba(255, 255, 255, 0.12), rgba(0, 0, 0, 0.75));
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.hero-visual img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canadian-flag {
|
||||
width: 80px;
|
||||
height: 48px;
|
||||
border: 2px solid var(--white);
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(90deg, var(--red) 0 25%, var(--white) 25% 75%, var(--red) 75% 100%);
|
||||
box-shadow: 0 0 12px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.goose-logo {
|
||||
margin-left: auto;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.85);
|
||||
background: linear-gradient(145deg, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.45));
|
||||
padding: 0.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.goose-logo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.75));
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--grey);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
#assets article {
|
||||
border: 1px solid var(--grey);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
#assets .meta {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
#scans,
|
||||
#mitre {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#scans article,
|
||||
#mitre article {
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
#mitre h3,
|
||||
#scans h3 {
|
||||
margin-top: 0;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.service-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vuln-list {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.45rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.severity-critical {
|
||||
background: var(--red);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.severity-high {
|
||||
background: #ff6b6b;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.severity-medium {
|
||||
background: #ffba08;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.severity-low,
|
||||
.severity-info,
|
||||
.severity-unknown {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.scan-card .meta,
|
||||
#mitre .meta {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
#assets h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
#assets ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#assets li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#assets li strong {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background: var(--grey);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
form.card-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: var(--white);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background: var(--red);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
padding: 0.6rem 1.25rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #ff4d4d;
|
||||
}
|
||||
|
||||
.helper {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.helper.error {
|
||||
color: #ff8c8c;
|
||||
}
|
||||
|
||||
#tasks {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.task-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.status-running {
|
||||
background: #ffba08;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background: #06d6a0;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.status-failed,
|
||||
.status-error {
|
||||
background: #ef476f;
|
||||
color: var(--white);
|
||||
}
|
||||
0
web/static/uploads/.gitkeep
Normal file
0
web/static/uploads/.gitkeep
Normal file
42
web/static/uploads/official_goosestrike_logo.svg
Normal file
42
web/static/uploads/official_goosestrike_logo.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GooseStrike official crest">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#101112" />
|
||||
<stop offset="55%" stop-color="#1c1e20" />
|
||||
<stop offset="100%" stop-color="#2b2f36" />
|
||||
</linearGradient>
|
||||
<linearGradient id="beak" x1="0%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#e43d3d" />
|
||||
<stop offset="100%" stop-color="#a10b0b" />
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="12" stdDeviation="12" flood-color="#000" flood-opacity="0.65" />
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="640" height="640" rx="64" fill="url(#bg)" />
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M365 120c-88 0-162 78-162 174 0 73 43 122 96 138-4 18-38 58-68 76 0 0 120-18 152-142 44-1 112-44 125-112 20-106-82-134-143-134z" fill="#f7f7f2"/>
|
||||
<path d="M274 178c-12 8-18 18-18 30 0 20 22 36 54 36 38 0 94-24 142-78-40 8-78-2-106-10-34-10-58-4-72 22z" fill="#050505"/>
|
||||
<path d="M360 210c-18 0-32 10-32 24s14 24 32 24 32-10 32-24-14-24-32-24z" fill="#f7f7f2"/>
|
||||
<path d="M418 204c48-22 90-22 122 0-6-54-58-88-128-88-42 0-92 10-142 32 42 26 82 34 148 56z" fill="#050505"/>
|
||||
<path d="M468 232c-16 14-20 34-10 46 12 16 38 14 58-4 22-20 22-46 2-64-18-16-44-18-64-4-10 8-10 16-4 26 10-8 22-8 34 0 14 10 12 24-4 36-12 10-26 12-36 2 0-6 2-12 10-18z" fill="#050505"/>
|
||||
<path d="M270 374c-26 62-70 104-120 126 72 10 142-12 186-66 32-40 46-94 46-124-36 26-78 44-112 64z" fill="#050505"/>
|
||||
<path d="M322 330l-42 60 62-28c28-14 50-30 66-54-34 10-64 16-86 22z" fill="#050505"/>
|
||||
<path d="M256 426 320 512l60-86z" fill="#050505"/>
|
||||
<path d="M320 500l-80 32 20 32 88-12z" fill="#050505" opacity="0.8"/>
|
||||
<path d="M320 328 208 448l116-56 100 50z" fill="#050505" opacity="0.85"/>
|
||||
<path d="M326 360c-30 0-56 24-56 52s26 52 56 52 56-24 56-52-26-52-56-52z" fill="#050505" opacity="0.9"/>
|
||||
<path d="M346 370c-18 0-32 12-32 28s14 28 32 28 32-12 32-28-14-28-32-28z" fill="#f5f5f5"/>
|
||||
<path d="M320 260c-58 58-88 118-88 178l62-32 26-76 24 76 68 30c0-96-26-160-92-176z" fill="#d90429"/>
|
||||
<path d="M320 272c-42 44-62 90-62 138l44-22 18-50 16 50 46 20c0-68-18-114-62-136z" fill="#fdf7f7"/>
|
||||
<path d="M448 132c-10 0-20 4-28 12 28 0 52 10 70 24 10 8 18 18 24 28 4-4 6-10 6-18 0-26-32-46-72-46z" fill="#050505"/>
|
||||
<path d="M480 188c-6 0-12 2-18 6 10 8 18 18 22 32 8-2 12-6 12-14 0-12-8-24-16-24z" fill="#050505"/>
|
||||
<path d="M430 220c-10 0-18 6-18 14 0 8 8 14 18 14s18-6 18-14c0-8-8-14-18-14z" fill="#fefefe"/>
|
||||
<path d="M512 248c-6 0-10 2-14 6 6 4 10 12 10 20 8-2 12-6 12-12 0-8-4-14-8-14z" fill="#fefefe"/>
|
||||
<path d="M392 190c-10 0-18 4-18 10s8 10 18 10 18-4 18-10-8-10-18-10z" fill="#fefefe"/>
|
||||
<path d="M360 144c-10 0-18 6-18 12s8 12 18 12 18-6 18-12-8-12-18-12z" fill="#fefefe"/>
|
||||
<path d="M428 144c-6 0-12 2-16 6 12 4 22 10 30 18 2-2 2-4 2-6 0-10-8-18-16-18z" fill="#be1e2d"/>
|
||||
<path d="M448 176c-6 0-12 2-16 6 10 6 18 14 22 22 4-2 6-6 6-10 0-10-6-18-12-18z" fill="url(#beak)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
426
web/templates/index.html
Normal file
426
web/templates/index.html
Normal file
@@ -0,0 +1,426 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>GooseStrike Command Deck</title>
|
||||
<link rel="stylesheet" href="/static/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="canadian-flag" role="img" aria-label="Canadian flag motif"></div>
|
||||
<div>
|
||||
<h1>GooseStrike</h1>
|
||||
<p>Canadian-themed, AI-assisted offensive security toolbox for authorized testing.</p>
|
||||
</div>
|
||||
<div class="goose-logo" role="img" aria-label="Uploaded GooseStrike logo">
|
||||
<img src="{{ logo_url }}" alt="Uploaded GooseStrike logo" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="wide hero-panel" aria-label="GooseStrike Core overview">
|
||||
<div class="hero-text">
|
||||
<h2>GooseStrike Core (Docker-ready)</h2>
|
||||
<ul class="hero-list">
|
||||
<li>🔧 Nmap, Metasploit, SQLMap, Hydra, ZAP</li>
|
||||
<li>🧠 AI exploit assistant (Claude, HackGPT-ready)</li>
|
||||
<li>📚 Offline CVE mirroring with <code>update_cve.sh</code></li>
|
||||
<li>🗂 ASCII banner, logo, branding kit (PDF)</li>
|
||||
<li>📜 CVE scan + JSON match script</li>
|
||||
<li>📦 <strong>goosestrike-cve-enabled.zip</strong> (download link)</li>
|
||||
<li>🧠 <strong>hackgpt-ai-stack.zip</strong> with README + architecture</li>
|
||||
</ul>
|
||||
<div class="hero-meta">
|
||||
<h3>Coming next (roadmap you requested)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Task</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>🐳 Build <code>docker-compose.goosestrike-full.yml</code></td>
|
||||
<td>⏳ In progress</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🧠 HackGPT API container (linked to n8n)</td>
|
||||
<td>⏳ Next up</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🌐 Local CVE API server</td>
|
||||
<td>Pending</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🧬 Claude + HackGPT fallback system</td>
|
||||
<td>Pending</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🔄 n8n workflow <code>.json</code> import</td>
|
||||
<td>Pending</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🎯 Target "prioritizer" AI agent</td>
|
||||
<td>Pending</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🧭 SVG architecture diagram</td>
|
||||
<td>Pending</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🖥 Dashboard frontend (Armitage-style)</td>
|
||||
<td>Optional</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🔐 C2 bridging to Mythic/Sliver</td>
|
||||
<td>Optional</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-visual" role="img" aria-label="GooseStrike crest render">
|
||||
<div class="hero-visual-inner">
|
||||
<img src="{{ spotlight_logo_url }}" alt="GooseStrike crest" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Assets & Vulnerabilities</h2>
|
||||
<div id="assets"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Scan History</h2>
|
||||
<div id="scans"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>MITRE ATT&CK Suggestions</h2>
|
||||
<div id="mitre"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Task Queue</h2>
|
||||
<div id="tasks"></div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Alerts</h2>
|
||||
<pre id="alerts">Waiting for n8n webhooks...</pre>
|
||||
</section>
|
||||
<section class="wide">
|
||||
<h2>Queue Tool Task</h2>
|
||||
<form id="task-form" class="card-form">
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
Tool
|
||||
<input type="text" id="task-tool" list="tool-presets" placeholder="sqlmap" required />
|
||||
<datalist id="tool-presets">
|
||||
<option value="metasploit"></option>
|
||||
<option value="sqlmap"></option>
|
||||
<option value="hydra"></option>
|
||||
<option value="zap"></option>
|
||||
<option value="password_cracker"></option>
|
||||
<option value="hashcat"></option>
|
||||
<option value="john"></option>
|
||||
<option value="rainbow"></option>
|
||||
</datalist>
|
||||
</label>
|
||||
<label>
|
||||
Target / Notes
|
||||
<input type="text" id="task-target" placeholder="10.0.0.5:80" />
|
||||
</label>
|
||||
</div>
|
||||
<label>
|
||||
Params (JSON)
|
||||
<textarea id="task-params" rows="4" placeholder='{"level": 3, "risk": 1}'></textarea>
|
||||
</label>
|
||||
<div class="form-actions">
|
||||
<button type="submit">Queue Task</button>
|
||||
<span class="helper">Tasks land in db/tasks.db and runners pick them up.</span>
|
||||
</div>
|
||||
<div id="task-message" class="helper"></div>
|
||||
</form>
|
||||
</section>
|
||||
<section class="wide">
|
||||
<h2>Password Cracking Helper</h2>
|
||||
<form id="password-form" class="card-form">
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
Cracking Tool
|
||||
<select id="password-tool">
|
||||
<option value="hashcat">Hashcat</option>
|
||||
<option value="john">John the Ripper</option>
|
||||
<option value="rainbow">Rainbow (rcrack)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Target Label
|
||||
<input type="text" id="password-target" placeholder="lab-hashes" />
|
||||
</label>
|
||||
</div>
|
||||
<label>
|
||||
Hash / Tables file
|
||||
<input type="text" id="password-file" placeholder="/data/hashes.txt" required />
|
||||
</label>
|
||||
<label>
|
||||
Wordlist / Mask / Hash value
|
||||
<input type="text" id="password-wordlist" placeholder="/usr/share/wordlists/rockyou.txt or ?l?d?d?d" />
|
||||
</label>
|
||||
<label>
|
||||
Extra options (JSON)
|
||||
<textarea id="password-extra" rows="3" placeholder='{"mode": 0, "attack_mode": 0}'></textarea>
|
||||
</label>
|
||||
<div class="form-actions">
|
||||
<button type="submit">Queue Password Crack</button>
|
||||
<span class="helper">Hashcat/John/Rainbow jobs reuse the password_cracker runner.</span>
|
||||
</div>
|
||||
<div id="password-message" class="helper"></div>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Built for red, grey, black, and white team operations. Use responsibly.
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
async function fetchAssets() {
|
||||
const response = await fetch('/assets');
|
||||
const container = document.getElementById('assets');
|
||||
if (!response.ok) {
|
||||
container.innerText = 'Unable to load assets yet.';
|
||||
return;
|
||||
}
|
||||
const assets = await response.json();
|
||||
if (!assets.length) {
|
||||
container.innerText = 'No assets ingested yet.';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
assets.forEach(asset => {
|
||||
const card = document.createElement('article');
|
||||
const macLine = asset.mac_address ? `<div class="meta">MAC: ${asset.mac_address} ${asset.mac_vendor ? '(' + asset.mac_vendor + ')' : ''}</div>` : '';
|
||||
card.innerHTML = `
|
||||
<h3>${asset.ip} ${asset.hostname ? '(' + asset.hostname + ')' : ''}</h3>
|
||||
${macLine}
|
||||
<ul>
|
||||
${asset.services.map(service => `
|
||||
<li>
|
||||
<div class="service-line">
|
||||
<strong>${service.proto}/${service.port}</strong>
|
||||
<span>${service.product || 'unknown'} ${service.version || ''}</span>
|
||||
</div>
|
||||
${service.vulnerabilities && service.vulnerabilities.length ? `
|
||||
<div class="vuln-list">
|
||||
${service.vulnerabilities.map(vuln => `
|
||||
<span class="badge severity-${(vuln.severity || 'unknown').toLowerCase()}">${vuln.cve_id}${vuln.severity ? ' · ' + vuln.severity : ''}</span>
|
||||
`).join(' ')}
|
||||
</div>
|
||||
` : '<span class="badge severity-info">No CVEs linked</span>'}
|
||||
</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchScans() {
|
||||
const response = await fetch('/scans');
|
||||
const container = document.getElementById('scans');
|
||||
if (!response.ok) {
|
||||
container.innerText = 'Unable to load scan history.';
|
||||
return;
|
||||
}
|
||||
const scans = await response.json();
|
||||
if (!scans.length) {
|
||||
container.innerText = 'No scans stored yet.';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
scans.forEach(scan => {
|
||||
const card = document.createElement('article');
|
||||
card.classList.add('scan-card');
|
||||
card.innerHTML = `
|
||||
<h3>${scan.asset_ip}</h3>
|
||||
<div class="meta">Scan ${scan.scan_id || scan.id} via ${scan.scanner || 'unknown'} (${scan.mode || 'standard'})</div>
|
||||
<div class="meta">${scan.started_at || 'unknown start'} → ${scan.completed_at || 'unknown finish'}</div>
|
||||
<div class="meta">Services: ${scan.services.length}</div>
|
||||
`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchMitre() {
|
||||
const response = await fetch('/attack_suggestions');
|
||||
const container = document.getElementById('mitre');
|
||||
if (!response.ok) {
|
||||
container.innerText = 'Unable to load ATT&CK guidance.';
|
||||
return;
|
||||
}
|
||||
const suggestions = await response.json();
|
||||
if (!suggestions.length) {
|
||||
container.innerText = 'Suggestions populate after a scan.';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
suggestions.forEach(suggestion => {
|
||||
const card = document.createElement('article');
|
||||
card.innerHTML = `
|
||||
<h3>${suggestion.technique_id} · ${suggestion.name}</h3>
|
||||
<p>${suggestion.description}</p>
|
||||
<div class="meta">${suggestion.tactic} | Severity: ${suggestion.severity || 'info'}</div>
|
||||
${suggestion.related_cve ? `<div class="badge severity-critical">Linked CVE ${suggestion.related_cve}</div>` : ''}
|
||||
`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAll() {
|
||||
fetchAssets();
|
||||
fetchScans();
|
||||
fetchMitre();
|
||||
fetchTasks();
|
||||
}
|
||||
|
||||
refreshAll();
|
||||
setInterval(refreshAll, 15000);
|
||||
|
||||
async function fetchTasks() {
|
||||
const response = await fetch('/tasks');
|
||||
const container = document.getElementById('tasks');
|
||||
if (!response.ok) {
|
||||
container.innerText = 'Unable to load tasks.';
|
||||
return;
|
||||
}
|
||||
const tasks = await response.json();
|
||||
if (!tasks.length) {
|
||||
container.innerText = 'Queue a task to populate this view.';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
tasks.forEach(task => {
|
||||
const card = document.createElement('article');
|
||||
card.classList.add('task-card');
|
||||
const params = JSON.stringify(task.params || {}, null, 2);
|
||||
card.innerHTML = `
|
||||
<div class="task-line">
|
||||
<strong>${task.tool}</strong>
|
||||
<span class="badge status-pill status-${task.status.toLowerCase()}">${task.status}</span>
|
||||
</div>
|
||||
<div class="meta">Target: ${task.target || 'n/a'}</div>
|
||||
<div class="meta">Created: ${task.created_at}</div>
|
||||
${task.started_at ? `<div class="meta">Started: ${task.started_at}</div>` : ''}
|
||||
${task.finished_at ? `<div class="meta">Finished: ${task.finished_at}</div>` : ''}
|
||||
<details>
|
||||
<summary>Parameters</summary>
|
||||
<pre>${params}</pre>
|
||||
</details>
|
||||
${task.result ? `<details><summary>Result</summary><pre>${JSON.stringify(task.result, null, 2)}</pre></details>` : ''}
|
||||
`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
async function queueTask(payload, messageEl) {
|
||||
try {
|
||||
const response = await fetch('/tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(text || 'Unable to queue task');
|
||||
}
|
||||
const data = await response.json();
|
||||
messageEl.textContent = `Queued task #${data.id}`;
|
||||
messageEl.classList.remove('error');
|
||||
fetchTasks();
|
||||
} catch (err) {
|
||||
messageEl.textContent = err.message;
|
||||
messageEl.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
const taskForm = document.getElementById('task-form');
|
||||
if (taskForm) {
|
||||
taskForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const toolInput = document.getElementById('task-tool');
|
||||
const targetInput = document.getElementById('task-target');
|
||||
const paramsInput = document.getElementById('task-params');
|
||||
const messageEl = document.getElementById('task-message');
|
||||
let params = {};
|
||||
if (paramsInput.value.trim()) {
|
||||
try {
|
||||
params = JSON.parse(paramsInput.value);
|
||||
} catch (err) {
|
||||
messageEl.textContent = 'Params must be valid JSON.';
|
||||
messageEl.classList.add('error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
await queueTask({
|
||||
tool: toolInput.value.trim(),
|
||||
target: targetInput.value.trim() || null,
|
||||
params
|
||||
}, messageEl);
|
||||
taskForm.reset();
|
||||
});
|
||||
}
|
||||
|
||||
const passwordForm = document.getElementById('password-form');
|
||||
if (passwordForm) {
|
||||
passwordForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const messageEl = document.getElementById('password-message');
|
||||
const tool = document.getElementById('password-tool').value;
|
||||
const target = document.getElementById('password-target').value;
|
||||
const hashFile = document.getElementById('password-file').value;
|
||||
const wordlist = document.getElementById('password-wordlist').value;
|
||||
const extraField = document.getElementById('password-extra');
|
||||
let extra = {};
|
||||
if (extraField.value.trim()) {
|
||||
try {
|
||||
extra = JSON.parse(extraField.value);
|
||||
} catch (err) {
|
||||
messageEl.textContent = 'Extra options must be valid JSON.';
|
||||
messageEl.classList.add('error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const params = {
|
||||
crack_tool: tool,
|
||||
...extra
|
||||
};
|
||||
if (tool === 'rainbow') {
|
||||
params.tables_path = hashFile;
|
||||
if (wordlist) {
|
||||
if (wordlist.includes('/') || wordlist.endsWith('.txt')) {
|
||||
params.hash_file = wordlist;
|
||||
} else {
|
||||
params.hash_value = wordlist;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.hash_file = hashFile;
|
||||
if (wordlist) {
|
||||
if (tool === 'hashcat' && wordlist.includes('?') && !wordlist.includes('/')) {
|
||||
params.mask = wordlist;
|
||||
} else {
|
||||
params.wordlist = wordlist;
|
||||
}
|
||||
}
|
||||
}
|
||||
await queueTask({
|
||||
tool: 'password_cracker',
|
||||
target: target || hashFile,
|
||||
params
|
||||
}, messageEl);
|
||||
passwordForm.reset();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
161
web/templates/mock_dashboard.html
Normal file
161
web/templates/mock_dashboard.html
Normal file
@@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>GooseStrike Mock Dashboard</title>
|
||||
<link rel="stylesheet" href="/static/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="canadian-flag" role="img" aria-label="Canadian flag motif"></div>
|
||||
<div>
|
||||
<h1>GooseStrike Mock Dashboard</h1>
|
||||
<p>Pre-filled sample data so you can preview the UI without running scans.</p>
|
||||
</div>
|
||||
<div class="goose-logo" role="img" aria-label="Uploaded GooseStrike logo">
|
||||
<img src="{{ logo_url }}" alt="Uploaded GooseStrike logo" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="wide hero-panel" aria-label="Mock GooseStrike Core overview">
|
||||
<div class="hero-text">
|
||||
<h2>{{ mock.core_snapshot.title }}</h2>
|
||||
<ul class="hero-list">
|
||||
{% for bullet in mock.core_snapshot.bullets %}
|
||||
<li>{{ bullet }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h3>Artifact drops</h3>
|
||||
<ul class="hero-list">
|
||||
{% for download in mock.core_snapshot.downloads %}
|
||||
<li>{{ download }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="hero-meta">
|
||||
<h3>Coming next (roadmap you requested)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Task</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in mock.roadmap %}
|
||||
<tr>
|
||||
<td>{{ item.task | safe }}</td>
|
||||
<td>{{ item.status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-visual" role="img" aria-label="GooseStrike crest mockup">
|
||||
<div class="hero-visual-inner">
|
||||
<img src="{{ spotlight_logo_url }}" alt="GooseStrike crest" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Assets (Sample)</h2>
|
||||
<div class="scan-list">
|
||||
{% for asset in mock.assets %}
|
||||
<article class="scan-card">
|
||||
<h3>{{ asset.ip }}{% if asset.hostname %} · {{ asset.hostname }}{% endif %}</h3>
|
||||
<p class="meta">MAC {{ asset.mac_address }} · {{ asset.mac_vendor }}</p>
|
||||
<ul>
|
||||
{% for service in asset.services %}
|
||||
<li>
|
||||
<strong>{{ service.port }}/{{ service.proto }}</strong>
|
||||
<span>{{ service.product }} {{ service.version }}</span>
|
||||
<div>
|
||||
{% for vuln in service.vulnerabilities %}
|
||||
<span class="severity-pill severity-{{ vuln.severity | lower if vuln.severity else 'unknown' }}">
|
||||
{{ vuln.cve_id }} ({{ vuln.severity or 'Unknown' }})
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Scan History (Sample)</h2>
|
||||
<div class="scan-list">
|
||||
{% for scan in mock.scans %}
|
||||
<article class="scan-card">
|
||||
<h3>{{ scan.asset_ip }} · {{ scan.mode }} ({{ scan.scan_id }})</h3>
|
||||
<p class="meta">{{ scan.started_at }} → {{ scan.completed_at }} · {{ scan.notes }}</p>
|
||||
<ul>
|
||||
{% for svc in scan.services %}
|
||||
<li>
|
||||
<strong>{{ svc.port }}/{{ svc.proto }}</strong> — {{ svc.product }} {{ svc.version }}
|
||||
<div>
|
||||
{% for cve in svc.cves %}
|
||||
<span class="severity-pill severity-info">{{ cve }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>MITRE ATT&CK Suggestions (Sample)</h2>
|
||||
<div id="mitre">
|
||||
{% for suggestion in mock.attack_suggestions %}
|
||||
<article class="scan-card">
|
||||
<h3>{{ suggestion.technique_id }} · {{ suggestion.name }}</h3>
|
||||
<p class="meta">{{ suggestion.tactic }} · Related to {{ suggestion.related_cve }}</p>
|
||||
<p>{{ suggestion.description }}</p>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Task Queue (Sample)</h2>
|
||||
<div id="tasks">
|
||||
{% for task in mock.tasks %}
|
||||
<article class="task-card">
|
||||
<div class="task-line">
|
||||
<strong>{{ task.tool }}</strong>
|
||||
<span class="severity-pill status-{{ task.status }}">{{ task.status }}</span>
|
||||
</div>
|
||||
<p>{{ task.target }}</p>
|
||||
<pre>{{ task.params | tojson(indent=2) }}</pre>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Alerts (Sample)</h2>
|
||||
<div id="alerts">
|
||||
{% for alert in mock.alerts %}
|
||||
<article class="scan-card">
|
||||
<h3>{{ alert.source }}</h3>
|
||||
<p class="meta">{{ alert.created_at }}</p>
|
||||
<pre>{{ alert.payload | tojson(indent=2) }}</pre>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Mock data only — run the real dashboard at <code>/</code> once you have live scans.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user