First release of open core

This commit is contained in:
t
2026-04-02 10:57:36 -04:00
parent 1c94f12d1c
commit 084c1321fc
101 changed files with 8812 additions and 17 deletions

View File

@@ -0,0 +1,101 @@
{{define "content"}}
<style>
.builder-layout { display: flex; gap: 20px; align-items: stretch; margin-top: 20px; }
.builder-lhs { flex: 1.5; display: flex; flex-direction: column; overflow: hidden; }
.builder-rhs { flex: 1; display: flex; flex-direction: column; }
.builder-group { margin-bottom: 15px; }
.builder-label { display: block; margin-bottom: 5px; font-weight: 600; font-size: 0.9rem; color: var(--text-muted); }
.builder-input, .builder-select {
width: 100%; padding: 10px; border: 1px solid var(--border);
border-radius: 4px; font-family: inherit; box-sizing: border-box;
background: var(--card-bg); color: var(--text-main);
}
.builder-input:focus, .builder-select:focus { outline: none; border-color: var(--primary); }
.mapping-table td { vertical-align: middle; }
</style>
<div class="container">
<header style="padding: 0; box-shadow: none; border: none; margin-bottom: 10px; background: transparent;">
<h2>🔧 Adapter Builder: <span style="color: var(--text-muted); font-weight: normal;">{{.Filename}}</span></h2>
</header>
<div class="builder-layout">
<div class="card builder-lhs" style="padding: 0;">
<div class="toolbar">
<h3 style="font-size: 1.1rem; margin: 0;">1. Data Preview</h3>
<label for="local-file" class="btn btn-secondary" style="margin: 0;">
Load {{.Filename}}
</label>
<input type="file" id="local-file" accept=".csv,.json" style="display: none;">
</div>
<div class="scroll-container" style="padding: 20px; overflow-x: auto; max-height: 65vh; overflow-y: auto;">
<div id="preview-placeholder" style="text-align: center; color: var(--text-muted); margin-top: 40px;">
<p>Click "Load" to generate a data preview and extract column headers.</p>
</div>
<div id="preview-table-container"></div>
</div>
</div>
<div class="card builder-rhs">
<h3 style="font-size: 1.1rem; margin-top: 0; margin-bottom: 20px; color: var(--primary);">2. Schema Mapping</h3>
<form id="adapter-form" style="display: flex; flex-direction: column; height: 100%;">
<div style="display: flex; gap: 15px;">
<div class="builder-group" style="flex: 1;">
<label class="builder-label">Adapter Name</label>
<input type="text" id="name" class="builder-input" placeholder="e.g. AcmeScan" required>
</div>
<div class="builder-group" style="flex: 1;">
<label class="builder-label">Source Name</label>
<input type="text" id="source_name" class="builder-input" placeholder="e.g. Acme" required>
</div>
</div>
<div class="builder-group">
<label class="builder-label">Findings JSON Path <span style="font-weight: normal; font-size: 0.8rem;">('.' for CSV, 'data.alerts' for JSON)</span></label>
<input type="text" id="findings_path" class="builder-input" value="." required>
</div>
<hr style="border: 0; border-top: 1px solid var(--border); margin: 20px 0;">
<h4 style="margin-bottom: 15px; font-size: 1rem;">Map to Core Fields</h4>
<table class="mapping-table" style="margin-top: 0;">
<tbody>
<tr>
<td width="35%"><strong>Title <span style="color: var(--critical);">*</span></strong></td>
<td><select id="mapping_title" class="builder-select source-header" required><option value="">-- Load file --</option></select></td>
</tr>
<tr>
<td><strong>Asset <span style="color: var(--critical);">*</span></strong></td>
<td><select id="mapping_asset" class="builder-select source-header" required><option value="">-- Load file --</option></select></td>
</tr>
<tr>
<td><strong>Severity <span style="color: var(--critical);">*</span></strong></td>
<td><select id="mapping_severity" class="builder-select source-header" required><option value="">-- Load file --</option></select></td>
</tr>
<tr>
<td><strong>Description</strong></td>
<td><select id="mapping_description" class="builder-select source-header"><option value="">-- Optional --</option></select></td>
</tr>
<tr>
<td><strong>Remediation</strong></td>
<td><select id="mapping_remediation" class="builder-select source-header"><option value="">-- Optional --</option></select></td>
</tr>
</tbody>
</table>
<div style="margin-top: auto; padding-top: 20px;">
<button type="submit" class="btn disabled" id="save-btn" style="width: 100%; background: var(--primary); color: white; border: none; padding: 12px;">Save & Enable Adapter</button>
</div>
<div style="display: flex; gap: 15px; margin-top: 20px;">
<button class="btn" style="background: #2563eb; color: white;" onclick="saveAdapter()">💾 Save to Database</button>
<button class="btn btn-secondary" onclick="exportAdapterJSON()">⬇️ Export JSON Profile</button>
</div>
</form>
</div>
</div>
</div>
<script src="/static/builder.js"></script>
{{end}}

28
ui/templates/admin.gohtml Normal file
View File

@@ -0,0 +1,28 @@
{{define "content"}}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<div>
<h1 style="margin: 0; color: #0f172a;">The Sheriff's Office</h1>
<p style="color: #64748b; margin-top: 5px;">Strategic Command, Personnel, & Operations</p>
</div>
</div>
<div class="tab-nav">
<button class="tab-btn active" onclick="switchTab('tab-metrics', this)">📊 Metrics</button>
<button class="tab-btn" onclick="switchTab('tab-performance', this)">📡 Performance</button>
<button class="tab-btn" style="color: #94a3b8; cursor: not-allowed;" title="Available in RiskRancher Pro">
🔒 Risk Reviews (Pro)
</button>
<button class="tab-btn" onclick="switchTab('tab-config', this)">⚙️ Configuration</button>
<button class="tab-btn" onclick="switchTab('tab-feed', this)">📻 System Logs</button>
</div>
{{template "admin_metrics" .}}
{{template "admin_performance" .}}
{{template "admin_config" .}}
{{template "admin_feed" .}}
{{template "admin_modals" .}}
<script src="/static/admin.js"></script>
{{end}}

View File

@@ -0,0 +1,56 @@
{{define "content"}}
<div class="navbar-strategic" style="margin: -20px -20px 20px -20px; border-radius: 8px 8px 0 0;">
<div style="font-size: 1.2rem; font-weight: bold; letter-spacing: 1px;">RISK RANCHER</div>
<div>
<a href="/dashboard">The Corral (Tactical)</a>
<a href="/assets" style="color: white; border-bottom: 2px solid #3b82f6; padding-bottom: 5px;">Sheriff's Office (Strategic)</a>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 20px;">
<div>
<h1 style="margin: 0; color: #0f172a;">Asset Risk Rollup</h1>
<p style="margin: 5px 0 0 0; color: #64748b;">Tracking {{.TotalCount}} vulnerable assets across the ranch.</p>
</div>
</div>
<div class="card">
<table>
<thead>
<tr>
<th>Asset Identifier</th>
<th style="text-align: center;">Total Active</th>
<th style="text-align: center;">Critical</th>
<th style="text-align: center;">High</th>
<th style="text-align: center;">Medium</th>
<th style="text-align: center;">Low</th>
</tr>
</thead>
<tbody>
{{range .Assets}}
<tr>
<td>
<a href="/dashboard?asset={{.AssetIdentifier}}" style="font-family: monospace; font-size: 1.05rem; color: #2563eb; text-decoration: none; font-weight: bold;">
{{.AssetIdentifier}}
</a>
</td>
<td style="text-align: center; font-weight: bold; color: #475569;">{{.TotalActive}}</td>
<td style="text-align: center;"><span class="count-badge {{if gt .Critical 0}}bg-critical{{else}}bg-zero{{end}}">{{.Critical}}</span></td>
<td style="text-align: center;"><span class="count-badge {{if gt .High 0}}bg-high{{else}}bg-zero{{end}}">{{.High}}</span></td>
<td style="text-align: center;"><span class="count-badge {{if gt .Medium 0}}bg-medium{{else}}bg-zero{{end}}">{{.Medium}}</span></td>
<td style="text-align: center;"><span class="count-badge {{if gt .Low 0}}bg-low{{else}}bg-zero{{end}}">{{.Low}}</span></td>
</tr>
{{else}}
<tr>
<td colspan="6" style="text-align: center; padding: 40px; color: #64748b;">
No vulnerable assets found. The ranch is secure!
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
{{end}}

36
ui/templates/base.gohtml Normal file
View File

@@ -0,0 +1,36 @@
{{define "base"}}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RiskRancher OSS</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header style="display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background: white; border-bottom: 1px solid #e2e8f0;">
<div class="logo">
<h2 style="margin: 0;"><a href="/dashboard" style="color: #0f172a; text-decoration: none;">🐴 RiskRancher</a></h2>
</div>
<nav style="display: flex; align-items: center; gap: 15px;">
<span style="color: #475569; font-size: 0.9rem; font-family: monospace; padding-left: 15px; margin-left: 5px;">Community Edition</span>
<button onclick="logout()" style="background: #f1f5f9; color: #dc2626; border: 1px solid #e2e8f0; padding: 6px 12px; border-radius: 4px; font-weight: bold; cursor: pointer; font-size: 0.85rem;">Log Out</button>
</nav>
</header>
<script>
async function logout() {
try {
await fetch('/api/auth/logout', { method: 'POST' });
window.location.href = '/login';
} catch (e) { alert("Failed to log out."); }
}
</script>
<main class="container">
{{template "content" .}}
</main>
<footer style="text-align: center; padding: 20px; margin-top: 40px; border-top: 1px solid #e2e8f0; color: #94a3b8; font-size: 0.85rem;">
🐴 RiskRancher Core Edition | Version: <strong>{{.Version}}</strong> | Build: <strong>{{.Commit}}</strong>
</footer>
</body>
</html>
{{end}}

View File

@@ -0,0 +1,112 @@
{{define "admin_config"}}
<div id="tab-config" class="tab-pane">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3 style="margin: 0;">🤠 Personnel</h3>
<button id="openUserModal" class="btn" style="padding: 6px 12px; font-size: 0.85rem;">+ Add User</button>
</div>
<table style="font-size: 0.9rem; width: 100%;">
<thead><tr><th style="text-align: left;">Name</th><th style="text-align: left;">Role</th><th style="text-align: right;">Actions</th></tr></thead>
<tbody>
{{range .Users}}
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 8px 0;"><strong>{{.FullName}}</strong><br><span style="font-family: monospace; font-size: 0.8rem; color: #64748b;">{{.Email}}</span></td>
<td><span class="badge" style="background: #1e293b;">{{.GlobalRole}}</span></td>
<td style="text-align: right;">
<button class="btn btn-secondary" style="padding: 2px 6px; font-size: 0.75rem;" onclick="resetPassword({{.ID}})" title="Reset Password">🔑</button>
<button class="btn btn-secondary" style="padding: 2px 6px; font-size: 0.75rem;" onclick="editRole({{.ID}}, '{{.GlobalRole}}')" title="Change Role">🛡️</button>
<button class="btn btn-secondary" style="padding: 2px 6px; font-size: 0.75rem; color: #dc2626; border-color: #fca5a5;" onclick="deleteUser({{.ID}})" title="Deactivate">🗑️</button>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px;">
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; background: #f8fafc;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3 style="margin: 0; color: #64748b;">🛤️ Source Routing</h3>
<button class="btn" disabled style="padding: 6px 12px; font-size: 0.85rem; background: #cbd5e1; color: white; cursor: not-allowed;">🔒 Pro Feature</button>
</div>
<div style="text-align: center; padding: 20px; border: 1px dashed #cbd5e1; border-radius: 6px; background: white;">
<p style="color: #475569; font-size: 0.9rem; margin-bottom: 10px;">Automate ticket assignment and triage based on asset tags or CVEs.</p>
<a href="https://RiskRancher.com/pro" target="_blank" style="color: #8b5cf6; text-decoration: none; font-weight: bold; font-size: 0.85rem;">Learn about RiskRancher Pro &rarr;</a>
</div>
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 20px;">
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; position: relative;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div>
<h3 style="margin: 0;">⏱️ SLA Policies & System Time</h3>
<p style="margin: 2px 0 0 0; font-size: 0.8rem; color: #64748b;">Locked to Standard FedRAMP/NIST Default Timeframes</p>
</div>
<button class="btn" style="padding: 6px 12px; font-size: 0.85rem; background: #f8fafc; color: #64748b; border: 1px solid #cbd5e1;" onclick="showUpsell('Custom SLA Timers & Business Hours')">🔒 Customize (Pro)</button>
</div>
<div style="display: flex; gap: 30px; opacity: 0.7; pointer-events: none;">
<div style="flex: 1; padding: 15px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0;">
<h4 style="margin-top: 0;">Base Configuration</h4>
<label>System Timezone:</label>
<select disabled style="width: 100%; padding: 6px; margin-bottom: 10px; background: #e2e8f0;">
<option selected>UTC (Universal)</option>
</select>
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<div style="flex: 1;"><label>Biz Start:</label><input type="text" disabled value="09:00" style="width: 100%; padding: 6px; background: #e2e8f0;"></div>
<div style="flex: 1;"><label>Biz End:</label><input type="text" disabled value="17:00" style="width: 100%; padding: 6px; background: #e2e8f0;"></div>
</div>
</div>
<div style="flex: 2;">
<div style="margin-bottom: 10px; display: flex; justify-content: space-between;">
<h4 style="margin: 0;">SLA Matrix (Days to Patch)</h4>
</div>
<table style="font-size: 0.85rem; width: 100%; text-align: left;">
<thead><tr><th style="padding-bottom: 5px;">Severity</th><th>Triage</th><th>Patch</th></tr></thead>
<tbody>
<tr class="sla-row"><td style="padding: 4px 0;"><span class="badge critical">Critical</span></td><td>1</td><td>3</td></tr>
<tr class="sla-row"><td style="padding: 4px 0;"><span class="badge high">High</span></td><td>3</td><td>14</td></tr>
<tr class="sla-row"><td style="padding: 4px 0;"><span class="badge medium">Medium</span></td><td>7</td><td>30</td></tr>
<tr class="sla-row"><td style="padding: 4px 0;"><span class="badge low">Low</span></td><td>14</td><td>90</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; display: flex; flex-direction: column;">
<h3 style="margin: 0 0 15px 0;">⚙️ Operations</h3>
<div style="margin-bottom: 20px;">
<label style="font-weight: bold; display: flex; justify-content: space-between;">
Automated Backups
<span style="font-size: 0.75rem; color: #8b5cf6; font-weight: normal;">Pro Feature</span>
</label>
<div style="display: flex; gap: 10px; margin-top: 5px;">
<select disabled style="flex: 1; padding: 6px; background: #f1f5f9; color: #94a3b8; cursor: not-allowed; border: 1px solid #cbd5e1;">
<option>Manual Only (Free Core)</option>
<option>Daily Automated</option>
<option>Weekly Automated</option>
</select>
<button class="btn btn-secondary" style="color: #94a3b8; border-color: #cbd5e1;" onclick="showUpsell('Automated DB Backups')">🔒 Apply</button>
</div>
</div>
<div style="margin-bottom: 20px;">
<label style="font-weight: bold;">Data Portability</label>
<a href="/api/admin/export" class="btn btn-secondary" style="display: block; text-align: center; margin-top: 5px; background: #e0e7ff; color: #3730a3; border-color: #c7d2fe;">⬇️ Export JSON State</a>
</div>
<div style="margin-top: auto; padding-top: 15px; border-top: 1px solid #e2e8f0;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.85rem; color: #475569;">Core Engine: <strong>{{.Version}} ({{.Commit}})</strong></span>
<button class="btn" style="padding: 4px 10px; font-size: 0.8rem; color: white; background: #0f172a;" onclick="checkUpdates()">Updates</button>
</div>
</div>
</div>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,33 @@
{{define "admin_feed"}}
<div id="tab-feed" class="tab-pane">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<div>
<h3 style="margin: 0;">📻 System Logs</h3>
<p style="font-size: 0.85rem; color: #64748b; margin: 0;">Real-time tamper-evident system audit log.</p>
</div>
<div>
<select id="logFilter" style="padding: 8px; border-radius: 4px; border: 1px solid #cbd5e1; font-size: 0.9rem;">
<option value="All">All Activity</option>
<option value="status_change">Status Changes</option>
<option value="risk_request">Risk Requests</option>
<option value="magistrate_review">Magistrate Reviews</option>
<option value="assigned_to">Assignments</option>
<option value="comment">Comments</option>
<option value="read_receipt">Read Receipts</option>
</select>
</div>
</div>
<div id="logContainer" style="min-height: 400px; padding-right: 5px; font-size: 0.95rem;">
<div style="text-align: center; color: #94a3b8; padding: 40px;">Loading logs...</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #e2e8f0;">
<span id="logPageInfo" style="font-size: 0.85rem; color: #64748b;">Showing 0 of 0</span>
<div>
<button id="logPrevBtn" class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" disabled>Previous</button>
<button id="logNextBtn" class="btn btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;" disabled>Next</button>
</div>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,80 @@
{{define "admin_metrics"}}
<div id="tab-metrics" class="tab-pane active">
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 20px;">
<a href="/dashboard?filter=critical" class="kpi-card" style="text-align: center; border-top: 4px solid #dc2626;">
<h4 style="margin: 0; color: #64748b; font-size: 0.85rem; text-transform: uppercase;">Active CISA KEVs</h4>
<p style="font-size: 2rem; margin: 10px 0 0 0; font-weight: bold; color: {{if gt .Analytics.ActiveKEVs 0}}#dc2626{{else}}#10b981{{end}};">{{.Analytics.ActiveKEVs}}</p>
</a>
<a href="/dashboard?filter=critical" class="kpi-card" style="text-align: center; border-top: 4px solid #ea580c;">
<h4 style="margin: 0; color: #64748b; font-size: 0.85rem; text-transform: uppercase;">Open Criticals</h4>
<p style="font-size: 2rem; margin: 10px 0 0 0; font-weight: bold; color: #ea580c;">{{.Analytics.OpenCriticals}}</p>
</a>
<a href="/dashboard?filter=overdue" class="kpi-card" style="text-align: center; border-top: 4px solid #eab308;">
<h4 style="margin: 0; color: #64748b; font-size: 0.85rem; text-transform: uppercase;">SLA Breaches</h4>
<p style="font-size: 2rem; margin: 10px 0 0 0; font-weight: bold; color: #eab308;">{{.Analytics.TotalOverdue}}</p>
</a>
<div class="kpi-card" style="text-align: center; border-top: 4px solid #3b82f6; cursor: default;">
<h4 style="margin: 0; color: #64748b; font-size: 0.85rem; text-transform: uppercase;">Global MTTR (Days)</h4>
<p style="font-size: 2rem; margin: 10px 0 0 0; font-weight: bold; color: #3b82f6;">{{.Analytics.GlobalMTTRDays}}</p>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 20px;">
<div class="card" style="display: flex; flex-direction: column; justify-content: center; background: white; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin: 0 0 15px 0;">🔴 Open Risk Profile ({{.Analytics.Severity.Total}})</h3>
<div style="display: flex; height: 24px; border-radius: 12px; overflow: hidden; margin-bottom: 20px; background: #f1f5f9;">
{{if gt .Analytics.Severity.Total 0}}
<div style="width: {{.Analytics.Severity.CritPct}}%; background: #dc2626;" title="Critical: {{.Analytics.Severity.Critical}}"></div>
<div style="width: {{.Analytics.Severity.HighPct}}%; background: #ea580c;" title="High: {{.Analytics.Severity.High}}"></div>
<div style="width: {{.Analytics.Severity.MedPct}}%; background: #eab308;" title="Medium: {{.Analytics.Severity.Medium}}"></div>
<div style="width: {{.Analytics.Severity.LowPct}}%; background: #3b82f6;" title="Low: {{.Analytics.Severity.Low}}"></div>
<div style="width: {{.Analytics.Severity.InfoPct}}%; background: #94a3b8;" title="Info: {{.Analytics.Severity.Info}}"></div>
{{else}}
<div style="width: 100%; text-align: center; color: #94a3b8; font-size: 0.8rem; line-height: 24px;">No Open Findings</div>
{{end}}
</div>
<div style="display: flex; justify-content: space-between; font-size: 0.8rem; font-weight: bold; text-align: center;">
<div style="color: #dc2626; flex: 1;">Crit<br><span style="font-size: 1.1rem;">{{.Analytics.Severity.Critical}}</span></div>
<div style="color: #ea580c; flex: 1;">High<br><span style="font-size: 1.1rem;">{{.Analytics.Severity.High}}</span></div>
<div style="color: #eab308; flex: 1;">Med<br><span style="font-size: 1.1rem;">{{.Analytics.Severity.Medium}}</span></div>
<div style="color: #3b82f6; flex: 1;">Low<br><span style="font-size: 1.1rem;">{{.Analytics.Severity.Low}}</span></div>
</div>
</div>
<div class="card" style="display: flex; flex-direction: column; justify-content: center; background: white; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin: 0 0 15px 0;">🟢 Resolution Profile (Closed) ({{.Analytics.Resolution.Total}})</h3>
<div style="display: flex; height: 24px; border-radius: 12px; overflow: hidden; margin-bottom: 20px; background: #f1f5f9;">
{{if gt .Analytics.Resolution.Total 0}}
<div style="width: {{.Analytics.Resolution.PatchedPct}}%; background: #10b981;" title="Patched: {{.Analytics.Resolution.Patched}}"></div>
<div style="width: {{.Analytics.Resolution.RiskAccPct}}%; background: #8b5cf6;" title="Risk Accepted: {{.Analytics.Resolution.RiskAccepted}}"></div>
<div style="width: {{.Analytics.Resolution.FalsePosPct}}%; background: #64748b;" title="False Positive: {{.Analytics.Resolution.FalsePositive}}"></div>
{{else}}
<div style="width: 100%; text-align: center; color: #94a3b8; font-size: 0.8rem; line-height: 24px;">No Closed Findings</div>
{{end}}
</div>
<div style="display: flex; justify-content: space-between; font-size: 0.8rem; font-weight: bold; text-align: center;">
<div style="color: #10b981; flex: 1;">Patched<br><span style="font-size: 1.1rem;">{{.Analytics.Resolution.Patched}}</span></div>
<div style="color: #8b5cf6; flex: 1;">Accepted<br><span style="font-size: 1.1rem;">{{.Analytics.Resolution.RiskAccepted}}</span></div>
<div style="color: #64748b; flex: 1;">False Pos<br><span style="font-size: 1.1rem;">{{.Analytics.Resolution.FalsePositive}}</span></div>
</div>
</div>
<div class="card" style="background: white; padding: 20px; border-radius: 8px; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin: 0 0 15px 0;">🎯 Top Vulnerable Assets</h3>
{{range .Analytics.TopAssets}}
<a href="/dashboard?asset={{.Asset}}" style="display: block; text-decoration: none; color: inherit; margin-bottom: 12px; padding: 4px; border-radius: 4px; transition: background 0.2s;" onmouseover="this.style.background='#f8fafc'" onmouseout="this.style.background='transparent'">
<div style="display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 4px;">
<span style="font-family: monospace; font-weight: bold; color: #3b82f6;">{{.Asset}}</span>
<span style="color: #64748b; font-weight: bold;">{{.Count}} Findings</span>
</div>
<div style="background: #e2e8f0; height: 8px; border-radius: 4px; overflow: hidden;">
<div style="width: {{.Percentage}}%; background: #0f172a; height: 100%; border-radius: 4px;"></div>
</div>
</a>
{{else}}
<p style="color: #94a3b8; text-align: center; font-size: 0.9rem; padding: 20px;">No vulnerable assets found.</p>
{{end}}
</div>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,25 @@
{{define "admin_modals"}}
<div id="userModal" class="modal-overlay" style="display: none;">
<div class="modal-content" style="width: 400px;">
<h3>Add New Personnel</h3>
<label>Full Name:</label>
<input type="text" id="newUserName" style="width: 100%; padding: 8px; margin-bottom: 15px;" placeholder="e.g. Alice Cloud">
<label>Email Address:</label>
<input type="email" id="newUserEmail" style="width: 100%; padding: 8px; margin-bottom: 15px;" placeholder="alice@ranch.com">
<label>Temporary Password:</label>
<input type="password" id="newUserPassword" style="width: 100%; padding: 8px; margin-bottom: 15px;" placeholder="••••••••">
<label>Global Role:</label>
<select id="newUserRole" style="width: 100%; padding: 8px; margin-bottom: 20px;">
<option value="RangeHand">RangeHand (Analyst)</option>
<option value="Wrangler">Wrangler (IT / Submitter)</option>
<option value="Magistrate">Magistrate (Risk Approver)</option>
<option value="Sheriff">Sheriff (Global Admin)</option>
</select>
<div class="modal-actions">
<button id="cancelUser" class="btn btn-secondary">Cancel</button>
<button id="submitUser" class="btn">Create User</button>
</div>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,114 @@
{{define "admin_performance"}}
<div id="tab-performance" class="tab-pane">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<div>
<h3 style="margin: 0;">📡 Integration Performance</h3>
<p style="font-size: 0.85rem; color: #64748b; margin: 0;">Diagnostic breakdown of global KPIs, bottlenecks, and SLA tracking by scanner source.</p>
</div>
</div>
<table style="font-size: 0.9rem; width: 100%; text-align: left; border-collapse: collapse;">
<thead>
<tr style="border-bottom: 2px solid #e2e8f0;">
<th style="padding-bottom: 12px; width: 15%;">Integration</th>
<th style="padding-bottom: 12px; text-align: center; width: 20%;">🔥 High Risk Drivers<br><span style="font-size: 0.75rem; color: #94a3b8; font-weight: normal;">(Ties to KPI Metrics)</span></th>
<th style="padding-bottom: 12px; text-align: center; width: 15%;">🕒 Analyst Backlog<br><span style="font-size: 0.75rem; color: #94a3b8; font-weight: normal;">(Triage Phase)</span></th>
<th style="padding-bottom: 12px; text-align: center; width: 15%;">⏳ IT Bottlenecks<br><span style="font-size: 0.75rem; color: #94a3b8; font-weight: normal;">(Patch Phase)</span></th>
<th style="padding-bottom: 12px; text-align: center; width: 15%;">🛡️ Resolution Hygiene<br><span style="font-size: 0.75rem; color: #94a3b8; font-weight: normal;">(Closed Profile)</span></th>
<th style="padding-bottom: 12px; width: 20%;">Strategic Insight</th>
</tr>
</thead>
<tbody>
{{range .Analytics.SourceHealth}}
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 16px 0;">
<strong style="font-size: 1rem; color: #0f172a;">{{.Source}}</strong><br>
<span style="font-size: 0.8rem; color: #64748b;">{{.TotalOpen}} Total Open</span>
</td>
<td style="text-align: center;">
<div style="display: flex; gap: 8px; justify-content: center;">
{{if gt .Criticals 0}}<span title="Open Criticals" style="background: #fff7ed; color: #ea580c; border: 1px solid #fdba74; padding: 2px 8px; border-radius: 4px; font-weight: bold; font-size: 0.8rem;">{{.Criticals}} CRIT</span>{{end}}
{{if gt .CisaKEVs 0}}<span title="Active CISA KEVs" style="background: #fee2e2; color: #dc2626; border: 1px solid #fca5a5; padding: 2px 8px; border-radius: 4px; font-weight: bold; font-size: 0.8rem;">{{.CisaKEVs}} KEV</span>{{end}}
{{if and (eq .Criticals 0) (eq .CisaKEVs 0)}}<span style="color: #94a3b8; font-size: 0.85rem;">-</span>{{end}}
</div>
</td>
<td style="text-align: center;">
{{if gt .Untriaged 0}}
<span style="background: #f1f5f9; color: #475569; padding: 4px 10px; border-radius: 12px; font-weight: bold; font-size: 0.8rem;">{{.Untriaged}} Pending</span>
{{else}}
<span style="color: #10b981; font-size: 0.85rem; font-weight: bold;">✓ Clear</span>
{{end}}
</td>
<td style="text-align: center;">
{{if gt .PatchOverdue 0}}
<span style="background: #fee2e2; color: #dc2626; padding: 4px 10px; border-radius: 12px; font-weight: bold; font-size: 0.8rem; box-shadow: 0 0 5px rgba(220, 38, 38, 0.3);">{{.PatchOverdue}} Overdue</span>
{{else if gt .PendingRisk 0}}
<span style="background: #f3e8ff; color: #7e22ce; padding: 4px 10px; border-radius: 12px; font-weight: bold; font-size: 0.8rem;" title="Waiting on Risk Reviews Tab">{{.PendingRisk}} Excepted</span>
{{else}}
<span style="color: #10b981; font-size: 0.85rem; font-weight: bold;">✓ Met</span>
{{end}}
</td>
<td style="text-align: center;">
<div style="font-size: 0.8rem; font-weight: bold; color: #0f172a; margin-bottom: 4px;">{{.TotalClosed}} Closed</div>
<div style="display: flex; gap: 6px; justify-content: center; font-size: 0.75rem;">
{{if gt .Patched 0}}<span style="color: #10b981;" title="Patched">✓ {{.Patched}}</span>{{end}}
{{if gt .RiskAccepted 0}}<span style="color: #8b5cf6;" title="Risk Accepted">⚖️ {{.RiskAccepted}}</span>{{end}}
{{if gt .FalsePositive 0}}<span style="color: #64748b;" title="False Positive">🚫 {{.FalsePositive}}</span>{{end}}
{{if eq .TotalClosed 0}}<span style="color: #94a3b8;">-</span>{{end}}
</div>
</td>
<td>
<div style="font-weight: bold; color: {{if or (eq .StrategicNote "✅ Routine Processing") (eq .StrategicNote "✅ Healthy Resolution Velocity")}}#10b981{{else}}#0f172a{{end}}; font-size: 0.85rem;">{{.StrategicNote}}</div>
<div style="font-family: monospace; font-size: 0.75rem; color: #64748b; margin-top: 4px;">Lead: {{.TopAssignee}}</div>
</td>
</tr>
{{else}}
<tr><td colspan="6" style="text-align: center; padding: 40px; color: #94a3b8;">No active sources found.</td></tr>
{{end}}
</tbody>
</table>
<div style="margin-top: 40px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div>
<h3 style="margin: 0;">🔌 Recent Sync History</h3>
<p style="font-size: 0.85rem; color: #64748b; margin: 0;">Operational ledger of all API pushes, webhooks, and CSV uploads.</p>
</div>
</div>
<table style="font-size: 0.85rem; width: 100%; text-align: left; border-collapse: collapse; background: white; border: 1px solid #e2e8f0; border-radius: 8px;">
<thead>
<tr style="background: #f8fafc; border-bottom: 2px solid #e2e8f0;">
<th style="padding: 10px 15px;">Timestamp</th>
<th style="padding: 10px 15px;">Source</th>
<th style="padding: 10px 15px; text-align: center;">Status</th>
<th style="padding: 10px 15px; text-align: center;">Records Processed</th>
<th style="padding: 10px 15px;">Diagnostics</th>
</tr>
</thead>
<tbody>
{{range .SyncLogs}}
<tr style="border-bottom: 1px solid #f1f5f9;">
<td style="padding: 10px 15px; color: #64748b; font-family: monospace;">{{.CreatedAt}}</td>
<td style="padding: 10px 15px; font-weight: bold; color: #0f172a;">{{.Source}}</td>
<td style="padding: 10px 15px; text-align: center;">
{{if eq .Status "Success"}}
<span style="background: #dcfce7; color: #166534; padding: 2px 8px; border-radius: 12px; font-weight: bold;">✓ Success</span>
{{else}}
<span style="background: #fee2e2; color: #dc2626; padding: 2px 8px; border-radius: 12px; font-weight: bold;">✗ Failed</span>
{{end}}
</td>
<td style="padding: 10px 15px; text-align: center; font-weight: bold; color: #475569;">{{.RecordsProcessed}}</td>
<td style="padding: 10px 15px; color: #dc2626; font-family: monospace; font-size: 0.8rem;">{{.ErrorMessage}}</td>
</tr>
{{else}}
<tr><td colspan="5" style="text-align: center; padding: 20px; color: #94a3b8;">No syncs recorded yet.</td></tr>
{{end}}
</tbody>
</table>
</div>
{{end}}

View File

@@ -0,0 +1,126 @@
{{define "dash_modals"}}
<div id="upsellModal" class="modal-overlay" style="display: none;">
<div class="modal-content" style="width: 400px; text-align: center;">
<div style="font-size: 3rem; margin-bottom: 10px;">🚀</div>
<h3 style="margin-top: 0; color: #0f172a;">RiskRancher Pro Required</h3>
<p style="color: #64748b; font-size: 0.95rem; margin-bottom: 20px;">The <strong id="upsellFeatureName">feature</strong> is available in the Pro edition. Scale your operations with automation, advanced RBAC, and branded reports.</p>
<div style="display: flex; gap: 10px; justify-content: center;">
<button class="btn btn-secondary" onclick="document.getElementById('upsellModal').style.display='none'">Close</button>
<a href="https://RiskRancher.com/pro" target="_blank" class="btn" style="background: #8b5cf6; color: white; text-decoration: none;">View Pro Plans</a>
</div>
</div>
</div>
<div id="newTicketModal" class="modal-overlay" style="display: none;">
<div class="modal-content" style="width: 500px; text-align: left;">
<h3 style="margin-top: 0; color: #0f172a; border-bottom: 1px solid #e2e8f0; padding-bottom: 10px;">+ Log Manual Finding</h3>
<div style="margin-bottom: 15px;">
<label style="font-weight: bold; display: block; margin-bottom: 5px; font-size: 0.9rem;">Title:</label>
<input type="text" id="newTicketTitle" placeholder="e.g. Open S3 Bucket found" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
<div>
<label style="font-weight: bold; display: block; margin-bottom: 5px; font-size: 0.9rem;">Asset Identifier:</label>
<input type="text" id="newTicketAsset" placeholder="e.g. aws-prod-bucket" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px; box-sizing: border-box;">
</div>
<div>
<label style="font-weight: bold; display: block; margin-bottom: 5px; font-size: 0.9rem;">Severity:</label>
<select id="newTicketSeverity" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px;">
<option value="Critical">Critical</option>
<option value="High" selected>High</option>
<option value="Medium">Medium</option>
<option value="Low">Low</option>
<option value="Info">Info</option>
</select>
</div>
</div>
<div style="margin-bottom: 20px;">
<label style="font-weight: bold; display: block; margin-bottom: 5px; font-size: 0.9rem;">Description (Markdown supported):</label>
<textarea id="newTicketDesc" style="width: 100%; padding: 8px; border: 1px solid #cbd5e1; border-radius: 4px; height: 100px; resize: vertical; box-sizing: border-box;"></textarea>
</div>
<div style="display: flex; justify-content: flex-end; gap: 10px;">
<button class="btn btn-secondary" onclick="document.getElementById('newTicketModal').style.display='none'">Cancel</button>
<button class="btn" style="background: #10b981; color: white; border: none;" onclick="submitNewTicket()">Create Finding</button>
</div>
</div>
</div>
<div id="drawerOverlay" class="drawer-overlay" onclick="closeDrawer()"></div>
<div id="ticketDrawer" class="drawer">
<div class="drawer-header">
<div>
<span id="drawerBadge" class="badge" style="margin-bottom: 8px; display: inline-block;"></span>
<h3 id="drawerTitle" style="margin: 0; color: #0f172a; font-size: 1.25rem;"></h3>
<div id="drawerAsset" style="font-family: monospace; color: #475569; font-size: 0.9rem; margin-top: 8px; background: #e2e8f0; padding: 2px 6px; border-radius: 4px; display: inline-block;"></div>
</div>
<button onclick="closeDrawer()" style="background: none; border: none; font-size: 1.8rem; cursor: pointer; color: #94a3b8;">&times;</button>
</div>
<div class="drawer-body" style="max-height: 75vh; overflow-y: auto; padding-right: 10px;">
<input type="hidden" id="drawerTicketID">
<div id="drawerEvidenceBlock" style="display: none; margin-bottom: 20px; background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 6px; padding: 15px; border-left: 4px solid #166534;">
<label style="font-weight: bold; color: #14532d; display: block; margin-bottom: 5px;">IT Wrangler Evidence:</label>
<div id="drawerEvidenceText" style="font-family: monospace; font-size: 0.9rem; color: #166534; white-space: pre-wrap; word-break: break-word;"></div>
</div>
<div id="drawerReturnedBlock" style="display: none; margin-bottom: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 6px; padding: 15px; border-left: 4px solid #991b1b;">
<label style="font-weight: bold; color: #7f1d1d; display: block; margin-bottom: 5px;">🔄 Reason for Return:</label>
<div id="drawerReturnedText" style="font-size: 0.9rem; color: #991b1b; white-space: pre-wrap; word-break: break-word;"></div>
</div>
<div style="margin-bottom: 20px;">
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Description (Live Preview):</label>
<div id="drawerDescPreview" style="background: #f8fafc; border: 1px solid #cbd5e1; border-radius: 6px; padding: 15px; font-size: 0.95rem; color: #1e293b; overflow-x: auto;">
</div>
</div>
<div id="drawerEditControls">
<div style="margin-bottom: 20px;">
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Edit Description (Markdown):</label>
<textarea id="drawerDescEdit" onkeyup="updateDrawerPreview()" onchange="updateDrawerPreview()" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.9rem; font-family: monospace; resize: vertical; min-height: 120px;"></textarea>
</div>
<div style="margin-bottom: 20px;">
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Edit Recommended Remediation:</label>
<textarea id="drawerRemEdit" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.9rem; resize: vertical; min-height: 80px;"></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px;">
<div>
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Adjust Severity:</label>
<select id="drawerSeverity" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.95rem;">
<option value="Critical">Critical</option>
<option value="High">High</option>
<option value="Medium">Medium</option>
<option value="Low">Low</option>
<option value="Info">Info</option>
</select>
</div>
<div>
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Assign to IT (Email):</label>
<input type="text" id="drawerAssignee" placeholder="e.g. sysadmin@company.com" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.95rem; box-sizing: border-box;">
</div>
</div>
<div style="background: #eff6ff; padding: 15px; border-radius: 6px; border-left: 4px solid #3b82f6;">
<label style="font-weight: bold; color: #1e3a8a; display: block; margin-bottom: 5px;">Audit Trail Comment (Required if altering):</label>
<input type="text" id="drawerComment" style="width: 100%; padding: 10px; border: 1px solid #bfdbfe; border-radius: 6px; box-sizing: border-box; font-size: 0.95rem;" placeholder="e.g., Updated remediation steps and severity...">
</div>
</div>
</div>
<div class="drawer-footer" style="display: flex; justify-content: space-between; width: 100%;">
<button class="btn btn-secondary" onclick="closeDrawer()">Discard</button>
<div id="drawerStandardActions" style="display: flex; gap: 10px;">
<button class="btn btn-secondary" style="color: #ea580c; border-color: #fdba74; background: #fffbeb;" onclick="markFalsePositive()">🚫 Mark False Positive</button>
<button class="btn" id="drawerSubmitBtn" style="background: #2563eb; color: white;">Save & Dispatch</button>
</div>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,108 @@
{{define "dash_table"}}
<div class="unified-card">
<div class="toolbar">
<div style="display: flex; align-items: center; gap: 15px;">
<h3 style="margin: 0; color: #0f172a;">Asset Triage Queue</h3>
</div>
{{if eq .CurrentTab "archives"}}
<select id="statusFilter" class="filter-dropdown" onchange="window.location.href='/dashboard?tab=archives&filter=' + this.value">
<option value="all" {{if eq .CurrentFilter "all"}}selected{{end}}>All Archived</option>
<option value="Patched" {{if eq .CurrentFilter "Patched"}}selected{{end}}>✅ Patched</option>
<option value="False Positive" {{if eq .CurrentFilter "False Positive"}}selected{{end}}>👻 False Positives</option>
</select>
{{end}}
<div style="display: flex; gap: 10px;">
<button class="btn" style="background: #10b981; color: white; border: none; padding: 6px 14px; font-size: 0.85rem; font-weight: bold; cursor: pointer;" onclick="openNewTicketModal()">+ New Finding</button>
<a href="/ingest" class="btn" style="background: #2563eb; color: white; text-decoration: none; padding: 6px 14px; font-size: 0.85rem; display: flex; align-items: center;">📥 Import Data</a>
<button class="btn" style="font-size: 0.85rem; padding: 6px 14px; background: #f8fafc; color: #64748b; border: 1px solid #cbd5e1;" onclick="showUpsell('Analyst God-Mode (Bulk Actions)')">🔒 Bulk Actions</button>
</div>
</div>
{{if .CurrentAsset}}
<div style="background: #eff6ff; border-bottom: 1px solid #bfdbfe; color: #1e3a8a; padding: 10px 20px; font-size: 0.9rem; display: flex; justify-content: space-between; align-items: center;">
<div><strong>Asset Filter Active:</strong> Showing tickets for <code style="background: white; padding: 2px 6px; border-radius: 4px;">{{.CurrentAsset}}</code></div>
<a href="/dashboard?tab={{.CurrentTab}}" style="background: #2563eb; color: white; padding: 4px 10px; border-radius: 4px; text-decoration: none; font-size: 0.8rem;">Clear Filter</a>
</div>
{{end}}
<table style="width: 100%; border-collapse: collapse; text-align: left; margin: 0;">
<thead id="mainTableHeader" style="display: none;">
<tr style="border-bottom: 2px solid #e2e8f0;">
<th style="width: 40px; padding: 12px 20px;"><input type="checkbox" id="selectAll"></th>
<th style="padding: 12px;">Severity</th>
<th style="padding: 12px;">Source</th>
<th style="padding: 12px;">Finding</th>
<th style="padding: 12px;">IT Assignee</th>
<th style="padding: 12px;">{{if eq .CurrentTab "holding_pen"}}⏳ Time to Triage{{else}}SLA Status{{end}}</th>
{{if ne .CurrentTab "holding_pen"}}<th style="padding: 12px; text-align: right; padding-right: 20px;">Action</th>{{end}}
</tr>
</thead>
<tbody id="ticketTableBody">
{{range .Tickets}}
<tr class="ticket-row {{if eq $.CurrentTab "archives"}}archived-row{{end}}" data-asset="{{.AssetIdentifier}}">
<td style="width: 40px;"><input type="checkbox" class="ticket-cb" name="ticket_ids" value="{{.ID}}"></td>
<td style="width: 120px;"><span class="badge {{.Severity | lower}}">{{.Severity}}</span></td>
<td style="width: 100px; font-size: 0.85rem; color: #475569;">{{.Source}}</td>
<td>
<textarea id="desc-{{.ID}}" style="display:none;">{{.Description}}</textarea>
<textarea id="rem-{{.ID}}" style="display:none;">{{.RecommendedRemediation}}</textarea>
<textarea id="ev-{{.ID}}" style="display:none;">{{.PatchEvidence}}</textarea>
<input type="hidden" id="status-{{.ID}}" value="{{.Status}}">
<textarea id="comment-{{.ID}}" style="display:none;">{{.LatestComment}}</textarea>
<input type="hidden" id="assignee-{{.ID}}" value="{{.Assignee}}">
<a href="javascript:void(0)" style="font-weight: bold; color: #2563eb; text-decoration: none;" onclick="openDrawer('{{.ID}}', '{{.Title}}', '{{.AssetIdentifier}}', '{{.Severity}}')">{{.Title}}</a>
{{if eq .Status "Returned to Security"}}
<div style="margin-top: 8px; background: #fef2f2; border-left: 3px solid #991b1b; padding: 6px 10px; font-size: 0.8rem; color: #7f1d1d; border-radius: 0 4px 4px 0;">
<strong>🔄 Returned by IT:</strong> {{.LatestComment}}
</div>
{{end}}
</td>
<td style="width: 150px;">
{{if eq .Assignee "Unassigned"}}
<span style="color: #94a3b8; font-style: italic; font-size: 0.85rem;">Unassigned</span>
{{else}}
<span style="background: #e0e7ff; color: #3730a3; padding: 2px 8px; border-radius: 12px; font-size: 0.85rem; font-weight: bold;">{{.Assignee}}</span>
{{end}}
</td>
<td style="width: 150px;">
{{if eq $.CurrentTab "holding_pen"}}
<span class="triage-timer" data-due="{{.TriageDueDate.Format "2006-01-02T15:04:05Z07:00"}}"></span>
{{else}}
{{if .IsOverdue}}<span style="color: #dc2626; font-weight: bold;">{{.SLAString}}</span>{{else}}<span style="color: #10b981; font-weight: bold;">{{.SLAString}}</span>{{end}}
{{end}}
</td>
{{if eq $.CurrentTab "holding_pen"}}
{{else if eq $.CurrentTab "chute"}}
<td style="text-align: right;"><button class="btn btn-secondary" style="padding: 4px 8px; font-size: 0.8rem; color: #94a3b8; border-color: #e2e8f0;" onclick="showUpsell('Passwordless Magic Links')">🔒 Share Asset Link</button></td>
{{else if eq $.CurrentTab "archives"}}
<td style="text-align: right; vertical-align: middle;">
{{if eq .Status "Patched"}}
<span class="archive-badge" style="background: #dcfce7; color: #166534; border: 1px solid #bbf7d0;">✅ RESOLVED</span>
{{else if eq .Status "False Positive"}}
<span class="archive-badge" style="background: #fff7ed; color: #9a3412; border: 1px solid #ffedd5;">👻 IGNORED</span>
{{end}}
<div style="font-size: 0.75rem; color: #94a3b8; margin-top: 4px;">
Archived {{.UpdatedAt.Format "Jan 02"}} (Took {{.SLAString}})
</div>
</td>
{{end}}
</tr>
{{end}}
</tbody>
</table>
<div class="pagination" style="padding: 20px;">
<span style="color: var(--text-muted); font-size: 0.9rem;">Page <strong>{{.CurrentPage}}</strong> of <strong>{{.TotalPages}}</strong></span>
<div style="display: flex; gap: 10px;">
<a href="/dashboard?tab={{.CurrentTab}}&page={{if .HasPrev}}{{.PrevPage}}{{else}}1{{end}}" class="btn {{if not .HasPrev}}disabled{{end}}">&larr; Previous</a>
<a href="/dashboard?tab={{.CurrentTab}}&page={{if .HasNext}}{{.NextPage}}{{else}}{{.TotalPages}}{{end}}" class="btn {{if not .HasNext}}disabled{{end}}">Next &rarr;</a>
</div>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,19 @@
{{define "dash_tabs"}}
<div class="tab-nav">
<a href="/dashboard?tab=holding_pen" class="tab-btn {{if eq .CurrentTab "holding_pen"}}active{{end}}">
📩 Holding Pen
{{if gt .ReturnedCount 0}}
<span class="notification-bubble">{{.ReturnedCount}}</span>
{{end}}
</a>
<a href="/dashboard?tab=chute" class="tab-btn {{if eq .CurrentTab "chute"}}active{{end}}">🤠 The Chute (Assigned)</a>
<a href="javascript:void(0)" onclick="showUpsell('E2E Exception & Verification Pipeline')" class="tab-btn" style="color: #94a3b8;">
🔒 Pending Verification (Pro)
</a>
<a href="/dashboard?tab=archives" class="tab-btn {{if eq .CurrentTab "archives"}}active{{end}}">
🗄️ The Archives
</a>
</div>
{{end}}

View File

@@ -0,0 +1,17 @@
{{define "content"}}
<script>
window.CurrentTab = "{{.CurrentTab}}";
</script>
{{template "dash_tabs" .}}
<div class="tab-pane active">
{{template "dash_table" .}}
</div>
{{template "dash_modals" .}}
<script src="/static/dashboard.js"></script>
{{end}}

View File

@@ -0,0 +1,41 @@
{{define "content"}}
<div style="max-width: 900px; margin: 0 auto; padding: 20px;">
<h2 style="color: #0f172a; margin-bottom: 5px;">📥 Data Ingestion & Parsers</h2>
<p style="color: #64748b; margin-bottom: 30px;">Bring your findings into the ranch.</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;">
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<div style="font-size: 2rem; margin-bottom: 10px;">📝</div>
<h3 style="margin-top: 0;">Pentest Report Parser</h3>
<p style="color: #475569; font-size: 0.9rem; margin-bottom: 20px; min-height: 40px;">Upload a Word (DOCX) or PDF penetration test report. We'll extract the findings and map them to tickets.</p>
<a href="/reports/upload" class="btn" style="background: #2563eb; color: white; text-decoration: none; display: inline-block;">Upload Report</a>
</div>
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<div style="font-size: 2rem; margin-bottom: 10px;">🛠️</div>
<h3 style="margin-top: 0;">Custom Adapter Builder</h3>
<p style="color: #475569; font-size: 0.9rem; margin-bottom: 20px; min-height: 40px;">Using a proprietary scanner? Build a visual JSON mapping to seamlessly ingest its outputs.</p>
<a href="/admin/adapters/new" class="btn btn-secondary" style="text-decoration: none; display: inline-block;">Build New Adapter</a>
</div>
</div>
<div style="border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin-top: 0;">📡 Standard Scanner Ingestion</h3>
<form id="ingestForm">
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #334155;">1. Select Configured Adapter:</label>
<select id="adapterSelect" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; margin-bottom: 15px;">
{{range .Adapters}}
<option value="{{.Name}}">{{.Name}} (Source: {{.SourceName}})</option>
{{end}}
</select>
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #334155;">2. Upload Scan Results (JSON/CSV):</label>
<input type="file" id="scanFile" style="width: 100%; padding: 10px; border: 1px dashed #cbd5e1; border-radius: 6px; margin-bottom: 20px; background: #f8fafc;">
<button type="button" class="btn" style="background: #10b981; color: white; width: 100%; font-size: 1.05rem;" onclick="uploadScan()">Run Ingestion Process</button>
</form>
<div id="ingestResult" style="margin-top: 15px; padding: 10px; display: none; border-radius: 6px;"></div>
</div>
</div>
<script src="/static/ingest.js"></script>
{{end}}

37
ui/templates/login.gohtml Normal file
View File

@@ -0,0 +1,37 @@
{{define "login"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RiskRancher - Login</title>
<link rel="stylesheet" href="/static/style.css">
<style>body { display: flex; align-items: center; justify-content: center; height: 100vh; background-color: #f8fafc; margin: 0; }</style>
</head>
<body>
<div class="auth-card">
<h2 style="margin-top: 0;">🐴 RiskRancher</h2>
<p style="color: #64748b; margin-bottom: 20px;">Sign in to your SOC Dashboard</p>
<div id="errorMsg" class="error"></div>
<form id="loginForm">
<div style="text-align: left;">
<label style="font-weight: 600; font-size: 0.9rem;">Email Address</label>
<input type="email" id="email" required placeholder="tim@ranch.com">
<label style="font-weight: 600; font-size: 0.9rem;">Password</label>
<input type="password" id="password" required placeholder="••••••••">
</div>
<button type="submit" class="btn" id="submitBtn">Sign In</button>
</form>
<div style="margin-top: 20px; font-size: 0.85rem;">
First time setup? <a href="/register" style="color: #2563eb;">Initialize System</a>
</div>
</div>
<script src="/static/auth.js"></script>
</body>
</html>
{{end}}

View File

@@ -0,0 +1,42 @@
{{define "register"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RiskRancher - System Initialization</title>
<link rel="stylesheet" href="/static/style.css">
<style>body { display: flex; align-items: center; justify-content: center; height: 100vh; background-color: #0f172a; color: white; margin: 0; }</style>
</head>
<body>
<div class="auth-card dark">
<h2 style="margin-top: 0; color: #10b981;">🛡️ Initialize System</h2>
<p style="color: #94a3b8; margin-bottom: 25px; font-size: 0.9rem;">
Welcome to RiskRancher. The first user to register will automatically be granted the <strong>Sheriff (Global Admin)</strong> role.
</p>
<div id="errorMsg" class="error dark"></div>
<form id="registerForm">
<div style="text-align: left;">
<label style="font-weight: 600; font-size: 0.9rem; color: #cbd5e1;">Full Name</label>
<input type="text" id="fullname" required placeholder="Wyatt Earp">
<label style="font-weight: 600; font-size: 0.9rem; color: #cbd5e1;">Email Address</label>
<input type="email" id="email" required placeholder="admin@ranch.com">
<label style="font-weight: 600; font-size: 0.9rem; color: #cbd5e1;">Secure Password</label>
<input type="password" id="password" required placeholder="••••••••">
</div>
<button type="submit" class="btn" id="submitBtn" style="background-color: #10b981;">Claim Sheriff Access</button>
</form>
<div style="margin-top: 20px; font-size: 0.85rem; color: #64748b;">
System already initialized? <a href="/login" style="color: #3b82f6;">Login here</a>
</div>
</div>
<script src="/static/auth.js"></script>
</body>
</html>
{{end}}

View File

@@ -0,0 +1,37 @@
{{define "content"}}
<div class="container-fluid" style="padding: 20px; max-width: 1500px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<div>
<h2 style="margin: 0; color: #0f172a;">📝 Manual Pentest Parser</h2>
<p style="color: #64748b; margin-top: 5px;">Highlight text in the DOCX viewer to extract vulnerabilities.</p>
</div>
</div>
<div style="display: flex; gap: 20px; align-items: stretch; height: 75vh;">
<div class="card" style="flex: 1.5; padding: 0; display: flex; flex-direction: column; overflow: hidden;">
<div class="toolbar" style="background: #f8fafc; border-bottom: 1px solid #e2e8f0; padding: 15px 20px;">
<h3 style="margin: 0; font-size: 1.1rem; color: #0f172a;">📄 Document Viewer</h3>
</div>
<div id="document-viewer" style="padding: 40px; overflow-y: auto; flex: 1; background: #ffffff; line-height: 1.8; font-size: 1.05rem; color: #334155; border-radius: 0 0 8px 8px;">
{{.RenderedHTML}}
</div>
</div>
<div class="card" style="flex: 1; padding: 0; display: flex; flex-direction: column; background: #f8fafc;">
<div class="toolbar" style="background: #0f172a; color: white; border-bottom: 1px solid #e2e8f0; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; border-radius: 8px 8px 0 0;">
<h3 style="margin: 0; font-size: 1.1rem; color: white;">📋 Draft Findings</h3>
<button class="btn" style="background: #10b981; color: white; border: none; font-size: 0.85rem; padding: 6px 12px; font-weight: bold;" onclick="promoteAllDrafts()">Promote All to Tickets</button>
</div>
<div id="draft-list" class="scroll-container" style="padding: 20px; overflow-y: auto; flex: 1;">
<div style="text-align: center; color: #94a3b8; margin-top: 40px; font-weight: bold;">
No drafts yet.<br><br>Highlight text on the left to begin clipping.
</div>
</div>
</div>
</div>
</div>
<button id="clip-btn" class="btn" style="position: absolute; display: none; z-index: 1000; background: #2563eb; color: white; border: none; box-shadow: 0 4px 10px rgba(0,0,0,0.2); border-radius: 6px; padding: 6px 12px; font-weight: bold; font-size: 0.85rem;">✂️ Clip as Finding</button>
<script src="/static/parser.js"></script>
{{end}}

View File

@@ -0,0 +1,56 @@
{{define "content"}}
<div class="container mt-5" style="display: flex; justify-content: center; padding-top: 50px;">
<div class="card" style="width: 100%; max-width: 600px; padding: 40px; text-align: center; box-shadow: 0 4px 6px rgba(0,0,0,0.05);">
<h2 style="color: #0f172a; margin-top: 0;">📄 Upload Pentest Report</h2>
<p style="color: #64748b;">Upload a .docx manual assessment to enter the clipping parser.</p>
<div id="drop-zone" style="border: 3px dashed #3b82f6; padding: 50px 20px; border-radius: 8px; cursor: pointer; margin-top: 30px; background: #f8fafc; transition: background 0.2s;">
<h4 style="margin: 0; color: #1d4ed8;">Drag & Drop DOCX Here</h4>
<p style="margin-top: 10px; color: #94a3b8; font-size: 0.9rem;">or click to browse your computer</p>
<input type="file" id="file-input" hidden accept=".docx">
</div>
<div id="status-area" style="display: none; margin-top: 25px;">
<span style="font-weight: bold; color: #2563eb; padding: 10px 20px; background: #eff6ff; border-radius: 6px;">⏳ Processing Document...</span>
</div>
</div>
</div>
<script>
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const statusArea = document.getElementById('status-area');
dropZone.onclick = () => fileInput.click();
fileInput.onchange = (e) => processFile(e.target.files[0]);
dropZone.ondragover = (e) => { e.preventDefault(); dropZone.style.background = "#e0e7ff"; };
dropZone.ondragleave = () => dropZone.style.background = "#f8fafc";
dropZone.ondrop = (e) => {
e.preventDefault();
dropZone.style.background = "#f8fafc";
processFile(e.dataTransfer.files[0]);
};
async function processFile(file) {
if (!file.name.toLowerCase().endsWith('.docx')) {
alert("Whoops! Please upload a .docx file.");
return;
}
statusArea.style.display = 'block';
let formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/reports/upload', { method: 'POST', body: formData });
if (!res.ok) throw new Error(await res.text());
const data = await res.json();
window.location.href = '/reports/parser/' + data.file_id;
} catch (err) {
alert("Upload failed: " + err.message);
statusArea.style.display = 'none';
}
}
</script>
{{end}}