Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5184ad2a35 | |||
| bda39c40f6 | |||
| 7fecb4905e | |||
| 2a01e0fc08 | |||
| ead3c9043d | |||
| 317dd9aedf | |||
| 7f22302859 | |||
| dc0ae8eb96 |
@@ -13,7 +13,7 @@ Just drop the binary on a server and start triaging.
|
|||||||
|
|
||||||
### Option A: Download the Binary
|
### Option A: Download the Binary
|
||||||
|
|
||||||
1. Go to the [Releases](https://epigas.gitea.cloud/RiskRancher/core/releases) tab and download the compiled executable for your OS (Windows/macOS/Linux).
|
1. Go to the [Releases](https://code.riskrancher.com/RiskRancher/core/releases) tab and download the compiled executable for your OS (Windows/macOS/Linux).
|
||||||
2. Place the binary in a dedicated directory and execute it.
|
2. Place the binary in a dedicated directory and execute it.
|
||||||
3. Visit `http://localhost:8080` in your browser.
|
3. Visit `http://localhost:8080` in your browser.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Just drop the binary on a server and start triaging.
|
|||||||
Ensure you have **Go 1.26+** installed (*CGO is required for the native `mattn/go-sqlite3` driver*).
|
Ensure you have **Go 1.26+** installed (*CGO is required for the native `mattn/go-sqlite3` driver*).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://epigas.gitea.cloud/RiskRancher/core
|
git clone https://code.riskrancher.com/RiskRancher/core
|
||||||
cd core
|
cd core
|
||||||
go build -o rr ./cmd/rr/main.go
|
go build -o rr ./cmd/rr/main.go
|
||||||
./rr
|
./rr
|
||||||
|
|||||||
+3
-3
@@ -4,9 +4,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/server"
|
"code.riskrancher.com/RiskRancher/core/pkg/server"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/ui"
|
"code.riskrancher.com/RiskRancher/core/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/auth"
|
"code.riskrancher.com/RiskRancher/core/pkg/auth"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module epigas.gitea.cloud/RiskRancher/core
|
module code.riskrancher.com/RiskRancher/core
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
domain2 "epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
domain2 "code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handler) HandleGetAdapters(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleGetAdapters(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestAdapters(t *testing.T) (*Handler, *sql.DB) {
|
func setupTestAdapters(t *testing.T) (*Handler, *sql.DB) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package adapters
|
package adapters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/auth"
|
"code.riskrancher.com/RiskRancher/core/pkg/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PasswordResetRequest is the expected JSON payload
|
// PasswordResetRequest is the expected JSON payload
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetGlobalConfig(t *testing.T) {
|
func TestGetGlobalConfig(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExportSystemState(t *testing.T) {
|
func TestExportSystemState(t *testing.T) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler encapsulates all Admin and Sheriff HTTP logic
|
// Handler encapsulates all Admin and Sheriff HTTP logic
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setupTestAdmin returns the clean Admin Handler and the raw DB
|
// setupTestAdmin returns the clean Admin Handler and the raw DB
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestAnalytics(t *testing.T) (*Handler, *sql.DB) {
|
func setupTestAnalytics(t *testing.T) (*Handler, *sql.DB) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package analytics
|
package analytics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestAuth(t *testing.T) (*Handler, *sql.DB) {
|
func setupTestAuth(t *testing.T) (*Handler, *sql.DB) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNotFound is a standard error we can use across our handlers
|
// ErrNotFound is a standard error we can use across our handlers
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
_ "modernc.org/sqlite" // We need the SQLite driver for the test
|
_ "modernc.org/sqlite" // We need the SQLite driver for the test
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ CREATE TABLE IF NOT EXISTS sla_policies (
|
|||||||
);
|
);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO sla_policies (domain, severity, days_to_triage, days_to_remediate, max_extensions) VALUES
|
INSERT OR IGNORE INTO sla_policies (domain, severity, days_to_triage, days_to_remediate, max_extensions) VALUES
|
||||||
('Vulnerability', 'Critical', 3, 14, 1), ('Vulnerability', 'High', 3, 30, 2),
|
('Vulnerability', 'Critical', 3, 14, 1),
|
||||||
('Privacy', 'Critical', 3, 3, 0), ('Privacy', 'High', 3, 7, 1),
|
('Vulnerability', 'High', 3, 30, 2),
|
||||||
|
('Vulnerability', 'Medium', 7, 60, 2),
|
||||||
|
('Vulnerability', 'Low', 14, 90, 3),
|
||||||
|
('Vulnerability', 'Info', 30, 180, 5),
|
||||||
|
('Privacy', 'Critical', 3, 3, 0),
|
||||||
|
('Privacy', 'High', 3, 7, 1),
|
||||||
('Incident', 'Critical', 3, 1, 0);
|
('Incident', 'Critical', 3, 1, 0);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
@@ -50,7 +55,6 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|||||||
expires_at DATETIME NOT NULL,
|
expires_at DATETIME NOT NULL,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tickets (
|
CREATE TABLE IF NOT EXISTS tickets (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
domain TEXT NOT NULL DEFAULT 'Vulnerability',
|
domain TEXT NOT NULL DEFAULT 'Vulnerability',
|
||||||
@@ -70,7 +74,10 @@ CREATE TABLE IF NOT EXISTS tickets (
|
|||||||
'Triaged',
|
'Triaged',
|
||||||
'Assigned Out',
|
'Assigned Out',
|
||||||
'Patched',
|
'Patched',
|
||||||
'False Positive'
|
'False Positive',
|
||||||
|
'Pending Risk Approval',
|
||||||
|
'Risk Accepted',
|
||||||
|
'Pending Verification'
|
||||||
)),
|
)),
|
||||||
dedupe_hash TEXT UNIQUE NOT NULL,
|
dedupe_hash TEXT UNIQUE NOT NULL,
|
||||||
patch_evidence TEXT,
|
patch_evidence TEXT,
|
||||||
@@ -78,6 +85,16 @@ CREATE TABLE IF NOT EXISTS tickets (
|
|||||||
assignee TEXT DEFAULT 'Unassigned',
|
assignee TEXT DEFAULT 'Unassigned',
|
||||||
latest_comment TEXT DEFAULT '',
|
latest_comment TEXT DEFAULT '',
|
||||||
|
|
||||||
|
-- 🚀 RE-ADDED: The missing Enterprise Risk & CISA tracking fields!
|
||||||
|
is_cisa_kev BOOLEAN DEFAULT 0,
|
||||||
|
verification_requested_at DATETIME,
|
||||||
|
extension_count INTEGER DEFAULT 0,
|
||||||
|
risk_rationale TEXT,
|
||||||
|
risk_evidence TEXT,
|
||||||
|
risk_approved_by TEXT,
|
||||||
|
risk_approved_at DATETIME,
|
||||||
|
exception_expires_at DATETIME,
|
||||||
|
|
||||||
assigned_at DATETIME,
|
assigned_at DATETIME,
|
||||||
owner_viewed_at DATETIME,
|
owner_viewed_at DATETIME,
|
||||||
triage_due_date DATETIME,
|
triage_due_date DATETIME,
|
||||||
@@ -87,7 +104,6 @@ CREATE TABLE IF NOT EXISTS tickets (
|
|||||||
patched_at DATETIME,
|
patched_at DATETIME,
|
||||||
FOREIGN KEY(domain) REFERENCES domains(name) ON DELETE SET DEFAULT
|
FOREIGN KEY(domain) REFERENCES domains(name) ON DELETE SET DEFAULT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status);
|
CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status);
|
||||||
CREATE INDEX IF NOT EXISTS idx_tickets_severity ON tickets(severity);
|
CREATE INDEX IF NOT EXISTS idx_tickets_severity ON tickets(severity);
|
||||||
CREATE INDEX IF NOT EXISTS idx_tickets_domain ON tickets(domain);
|
CREATE INDEX IF NOT EXISTS idx_tickets_domain ON tickets(domain);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package datastore
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SQLiteStore struct {
|
type SQLiteStore struct {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
domain2 "epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
domain2 "code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SQLiteStore) UpdateAppConfig(ctx context.Context, config domain2.AppConfig) error {
|
func (s *SQLiteStore) UpdateAppConfig(ctx context.Context, config domain2.AppConfig) error {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
domain2 "epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
domain2 "code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SQLiteStore) GetSheriffAnalytics(ctx context.Context) (domain2.SheriffAnalytics, error) {
|
func (s *SQLiteStore) GetSheriffAnalytics(ctx context.Context) (domain2.SheriffAnalytics, error) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
domain2 "epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
domain2 "code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SQLiteStore) SaveDraft(ctx context.Context, d domain2.DraftTicket) error {
|
func (s *SQLiteStore) SaveDraft(ctx context.Context, d domain2.DraftTicket) error {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
domain2 "epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
domain2 "code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SQLiteStore) IngestTickets(ctx context.Context, tickets []domain2.Ticket) error {
|
func (s *SQLiteStore) IngestTickets(ctx context.Context, tickets []domain2.Ticket) error {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SQLiteStore) GetTickets(ctx context.Context) ([]domain.Ticket, error) {
|
func (s *SQLiteStore) GetTickets(ctx context.Context) ([]domain.Ticket, error) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package ingest
|
package ingest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handler) HandleIngest(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleIngest(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -76,14 +75,15 @@ func (h *Handler) HandleCSVIngest(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
adapterIDStr := r.FormValue("adapter_id")
|
// 1. Grab the adapter_name sent by the frontend JS
|
||||||
adapterID, err := strconv.Atoi(adapterIDStr)
|
adapterName := r.FormValue("adapter_name")
|
||||||
if err != nil {
|
if adapterName == "" {
|
||||||
http.Error(w, "Invalid adapter_id", http.StatusBadRequest)
|
http.Error(w, "Missing adapter_name", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter, err := h.Store.GetAdapterByID(r.Context(), adapterID)
|
// 2. Look up the adapter by Name instead of ID
|
||||||
|
adapter, err := h.Store.GetAdapterByName(r.Context(), adapterName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Adapter mapping not found", http.StatusNotFound)
|
http.Error(w, "Adapter mapping not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestIngest(t *testing.T) (*Handler, *sql.DB) {
|
func setupTestIngest(t *testing.T) (*Handler, *sql.DB) {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/auth"
|
"code.riskrancher.com/RiskRancher/core/pkg/auth"
|
||||||
domain2 "epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
domain2 "code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handler) HandleSaveDraft(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) HandleSaveDraft(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package report
|
package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestReport(t *testing.T) (*Handler, *sql.DB) {
|
func setupTestReport(t *testing.T) (*Handler, *sql.DB) {
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@ package server
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/sla"
|
"code.riskrancher.com/RiskRancher/core/pkg/sla"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
|||||||
+10
-11
@@ -3,14 +3,14 @@ package server
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/adapters"
|
"code.riskrancher.com/RiskRancher/core/pkg/adapters"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/admin"
|
"code.riskrancher.com/RiskRancher/core/pkg/admin"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/analytics"
|
"code.riskrancher.com/RiskRancher/core/pkg/analytics"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/auth"
|
"code.riskrancher.com/RiskRancher/core/pkg/auth"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/ingest"
|
"code.riskrancher.com/RiskRancher/core/pkg/ingest"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/report"
|
"code.riskrancher.com/RiskRancher/core/pkg/report"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/tickets"
|
"code.riskrancher.com/RiskRancher/core/pkg/tickets"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/ui"
|
"code.riskrancher.com/RiskRancher/core/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterRoutes(app *App) {
|
func RegisterRoutes(app *App) {
|
||||||
@@ -66,6 +66,8 @@ func RegisterRoutes(app *App) {
|
|||||||
// Adapters & Configuration
|
// Adapters & Configuration
|
||||||
app.Router.Handle("GET /api/adapters", protected(adapterH.HandleGetAdapters))
|
app.Router.Handle("GET /api/adapters", protected(adapterH.HandleGetAdapters))
|
||||||
app.Router.Handle("GET /api/config", protected(adminH.HandleGetConfig))
|
app.Router.Handle("GET /api/config", protected(adminH.HandleGetConfig))
|
||||||
|
app.Router.Handle("POST /api/adapters", protected(adapterH.HandleCreateAdapter))
|
||||||
|
app.Router.Handle("DELETE /api/adapters/{id}", protected(adapterH.HandleDeleteAdapter))
|
||||||
|
|
||||||
// Analytics
|
// Analytics
|
||||||
app.Router.Handle("GET /api/analytics/summary", protected(analyticsH.HandleGetAnalyticsSummary))
|
app.Router.Handle("GET /api/analytics/summary", protected(analyticsH.HandleGetAnalyticsSummary))
|
||||||
@@ -83,9 +85,6 @@ func RegisterRoutes(app *App) {
|
|||||||
|
|
||||||
app.Router.Handle("GET /admin", sheriffOnly(ui.HandleAdminDashboard(app.Store)))
|
app.Router.Handle("GET /admin", sheriffOnly(ui.HandleAdminDashboard(app.Store)))
|
||||||
|
|
||||||
app.Router.Handle("POST /api/adapters", adminOnly(adapterH.HandleCreateAdapter))
|
|
||||||
app.Router.Handle("DELETE /api/adapters/{id}", adminOnly(adapterH.HandleDeleteAdapter))
|
|
||||||
|
|
||||||
app.Router.Handle("GET /api/admin/export", sheriffOnly(adminH.HandleExportState))
|
app.Router.Handle("GET /api/admin/export", sheriffOnly(adminH.HandleExportState))
|
||||||
app.Router.Handle("GET /api/admin/check-updates", sheriffOnly(adminH.HandleCheckUpdates))
|
app.Router.Handle("GET /api/admin/check-updates", sheriffOnly(adminH.HandleCheckUpdates))
|
||||||
app.Router.Handle("POST /api/admin/shutdown", sheriffOnly(adminH.HandleShutdown))
|
app.Router.Handle("POST /api/admin/shutdown", sheriffOnly(adminH.HandleShutdown))
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultSLACalculator implements the SLACalculator interface
|
// DefaultSLACalculator implements the SLACalculator interface
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package tickets
|
package tickets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler encapsulates all Ticket-related HTTP logic
|
// Handler encapsulates all Ticket-related HTTP logic
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/datastore"
|
"code.riskrancher.com/RiskRancher/core/pkg/datastore"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestTickets(t *testing.T) (*Handler, *sql.DB) {
|
func setupTestTickets(t *testing.T) (*Handler, *sql.DB) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InlineUpdateRequest struct {
|
type InlineUpdateRequest struct {
|
||||||
|
|||||||
+26
-12
@@ -62,7 +62,6 @@ function autoDetectArrayPath(obj) {
|
|||||||
return bestPath || ".";
|
return bestPath || ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function processPreview() {
|
function processPreview() {
|
||||||
let headers = [];
|
let headers = [];
|
||||||
let rows = [];
|
let rows = [];
|
||||||
@@ -72,7 +71,6 @@ function processPreview() {
|
|||||||
const parsed = JSON.parse(currentRawData);
|
const parsed = JSON.parse(currentRawData);
|
||||||
const findings = getNestedValue(parsed, pathInput.value);
|
const findings = getNestedValue(parsed, pathInput.value);
|
||||||
|
|
||||||
|
|
||||||
if (!Array.isArray(findings) || findings.length === 0) {
|
if (!Array.isArray(findings) || findings.length === 0) {
|
||||||
const rawPreview = JSON.stringify(parsed, null, 2).substring(0, 1500) + "\n\n... (file truncated for preview)";
|
const rawPreview = JSON.stringify(parsed, null, 2).substring(0, 1500) + "\n\n... (file truncated for preview)";
|
||||||
|
|
||||||
@@ -140,8 +138,8 @@ function populateDropdowns(headers) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('adapter-form').onsubmit = async (e) => {
|
// Reusable save function mapped to the API
|
||||||
e.preventDefault();
|
async function saveAdapterToAPI() {
|
||||||
const data = {
|
const data = {
|
||||||
name: document.getElementById('name').value,
|
name: document.getElementById('name').value,
|
||||||
source_name: document.getElementById('source_name').value,
|
source_name: document.getElementById('source_name').value,
|
||||||
@@ -165,12 +163,28 @@ document.getElementById('adapter-form').onsubmit = async (e) => {
|
|||||||
} else {
|
} else {
|
||||||
alert("Failed to save adapter: " + await resp.text());
|
alert("Failed to save adapter: " + await resp.text());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bound to the primary "Save & Enable Adapter" submit button
|
||||||
|
document.getElementById('adapter-form').onsubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await saveAdapterToAPI();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bound to the secondary "Save to Database" button
|
||||||
|
window.saveAdapter = async function() {
|
||||||
|
const form = document.getElementById('adapter-form');
|
||||||
|
// Ensure HTML validations (like 'required') are checked before saving
|
||||||
|
if (form.reportValidity()) {
|
||||||
|
await saveAdapterToAPI();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.exportAdapterJSON = function() {
|
window.exportAdapterJSON = function() {
|
||||||
const name = document.getElementById("adapterName").value.trim();
|
// Fixed mismatched Element IDs
|
||||||
const sourceName = document.getElementById("sourceName").value.trim();
|
const name = document.getElementById("name").value.trim();
|
||||||
const rootPath = document.getElementById("rootPath").value.trim();
|
const sourceName = document.getElementById("source_name").value.trim();
|
||||||
|
const rootPath = document.getElementById("findings_path").value.trim();
|
||||||
|
|
||||||
if (!name || !sourceName) {
|
if (!name || !sourceName) {
|
||||||
return alert("Adapter Name and Source Name are required to export.");
|
return alert("Adapter Name and Source Name are required to export.");
|
||||||
@@ -180,11 +194,11 @@ window.exportAdapterJSON = function() {
|
|||||||
name: name,
|
name: name,
|
||||||
source_name: sourceName,
|
source_name: sourceName,
|
||||||
findings_path: rootPath,
|
findings_path: rootPath,
|
||||||
mapping_title: document.getElementById("mapTitle").value.trim(),
|
mapping_title: document.getElementById("mapping_title").value.trim(),
|
||||||
mapping_asset: document.getElementById("mapAsset").value.trim(),
|
mapping_asset: document.getElementById("mapping_asset").value.trim(),
|
||||||
mapping_severity: document.getElementById("mapSeverity").value.trim(),
|
mapping_severity: document.getElementById("mapping_severity").value.trim(),
|
||||||
mapping_description: document.getElementById("mapDesc").value.trim(),
|
mapping_description: document.getElementById("mapping_description").value.trim(),
|
||||||
mapping_remediation: document.getElementById("mapRem").value.trim()
|
mapping_remediation: document.getElementById("mapping_remediation").value.trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a downloadable JSON blob
|
// Create a downloadable JSON blob
|
||||||
|
|||||||
+11
-12
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
window.showUpsell = function(featureName) {
|
window.showUpsell = function(featureName) {
|
||||||
const featureNameEl = document.getElementById('upsellFeatureName');
|
const featureNameEl = document.getElementById('upsellFeatureName');
|
||||||
const modalEl = document.getElementById('upsellModal');
|
const modalEl = document.getElementById('upsellModal');
|
||||||
@@ -10,7 +9,6 @@ window.showUpsell = function(featureName) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
window.renderMarkdown = function(text) {
|
window.renderMarkdown = function(text) {
|
||||||
if (!text) return "<i style='color:#94a3b8;'>No description provided.</i>";
|
if (!text) return "<i style='color:#94a3b8;'>No description provided.</i>";
|
||||||
let html = text.replace(/!\[.*?\]\((.*?)\)/g, '<br><img src="$1" style="max-width: 100%; max-height: 400px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; margin: 10px 0; display: block; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);"><br>');
|
let html = text.replace(/!\[.*?\]\((.*?)\)/g, '<br><img src="$1" style="max-width: 100%; max-height: 400px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; margin: 10px 0; display: block; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);"><br>');
|
||||||
@@ -18,13 +16,11 @@ window.renderMarkdown = function(text) {
|
|||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
window.updateDrawerPreview = function() {
|
window.updateDrawerPreview = function() {
|
||||||
const rawDesc = document.getElementById('drawerDescEdit').value;
|
const rawDesc = document.getElementById('drawerDescEdit').value;
|
||||||
document.getElementById('drawerDescPreview').innerHTML = renderMarkdown(rawDesc);
|
document.getElementById('drawerDescPreview').innerHTML = renderMarkdown(rawDesc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
window.openDrawer = function(id, title, asset, severity) {
|
window.openDrawer = function(id, title, asset, severity) {
|
||||||
document.getElementById('drawerTicketID').value = id;
|
document.getElementById('drawerTicketID').value = id;
|
||||||
document.getElementById('drawerTitle').innerText = title;
|
document.getElementById('drawerTitle').innerText = title;
|
||||||
@@ -34,9 +30,7 @@ window.openDrawer = function(id, title, asset, severity) {
|
|||||||
badge.innerText = severity;
|
badge.innerText = severity;
|
||||||
badge.className = `badge ${severity.toLowerCase()}`;
|
badge.className = `badge ${severity.toLowerCase()}`;
|
||||||
|
|
||||||
document.getElementById('drawerSeverity').value = severity;
|
// Read hidden inputs from the table row
|
||||||
document.getElementById('drawerComment').value = "";
|
|
||||||
|
|
||||||
const rawDesc = document.getElementById('desc-' + id) ? document.getElementById('desc-' + id).value : "";
|
const rawDesc = document.getElementById('desc-' + id) ? document.getElementById('desc-' + id).value : "";
|
||||||
const rawRem = document.getElementById('rem-' + id) ? document.getElementById('rem-' + id).value : "";
|
const rawRem = document.getElementById('rem-' + id) ? document.getElementById('rem-' + id).value : "";
|
||||||
const rawEv = document.getElementById('ev-' + id) ? document.getElementById('ev-' + id).value : "";
|
const rawEv = document.getElementById('ev-' + id) ? document.getElementById('ev-' + id).value : "";
|
||||||
@@ -44,6 +38,10 @@ window.openDrawer = function(id, title, asset, severity) {
|
|||||||
const rawComment = document.getElementById('comment-' + id) ? document.getElementById('comment-' + id).value : "";
|
const rawComment = document.getElementById('comment-' + id) ? document.getElementById('comment-' + id).value : "";
|
||||||
const assignee = document.getElementById('assignee-' + id) ? document.getElementById('assignee-' + id).value : "";
|
const assignee = document.getElementById('assignee-' + id) ? document.getElementById('assignee-' + id).value : "";
|
||||||
|
|
||||||
|
// Set initial values in the drawer
|
||||||
|
document.getElementById('drawerSeverity').value = severity;
|
||||||
|
document.getElementById('drawerStatus').value = status; // Pre-select current status
|
||||||
|
document.getElementById('drawerComment').value = "";
|
||||||
document.getElementById('drawerDescEdit').value = rawDesc;
|
document.getElementById('drawerDescEdit').value = rawDesc;
|
||||||
document.getElementById('drawerRemEdit').value = rawRem;
|
document.getElementById('drawerRemEdit').value = rawRem;
|
||||||
|
|
||||||
@@ -307,13 +305,14 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
|
|
||||||
const assigneeInput = document.getElementById("drawerAssignee");
|
const assigneeInput = document.getElementById("drawerAssignee");
|
||||||
const newAssignee = assigneeInput ? assigneeInput.value.trim() : "";
|
const newAssignee = assigneeInput ? assigneeInput.value.trim() : "";
|
||||||
const currentStatus = document.getElementById("status-" + id).value;
|
|
||||||
|
|
||||||
let newStatus = currentStatus;
|
// Explicitly grab the selected status from the new dropdown
|
||||||
if (newAssignee !== "" && newAssignee !== "Unassigned") {
|
let explicitStatus = document.getElementById("drawerStatus").value;
|
||||||
|
let newStatus = explicitStatus;
|
||||||
|
|
||||||
|
// Helpful UX: If they typed an email but left the status as "Waiting", auto-assign it
|
||||||
|
if (newAssignee !== "" && newAssignee !== "Unassigned" && explicitStatus === "Waiting to be Triaged") {
|
||||||
newStatus = "Assigned Out";
|
newStatus = "Assigned Out";
|
||||||
} else if (currentStatus === "Returned to Security") {
|
|
||||||
newStatus = "Waiting to be Triaged";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!comment.trim()) return alert("An audit trail comment is strictly required when modifying a finding.");
|
if (!comment.trim()) return alert("An audit trail comment is strictly required when modifying a finding.");
|
||||||
|
|||||||
+45
-33
@@ -1,50 +1,47 @@
|
|||||||
const dropZone = document.getElementById('drop-zone');
|
async function uploadScan() {
|
||||||
const fileInput = document.getElementById('file-input');
|
const fileInput = document.getElementById('scanFile');
|
||||||
|
const adapterSelect = document.getElementById('adapterSelect');
|
||||||
|
const resultDiv = document.getElementById('ingestResult');
|
||||||
|
|
||||||
async function processFile(file) {
|
const file = fileInput.files[0];
|
||||||
const statusText = document.getElementById('status-text');
|
const adapterName = adapterSelect.value;
|
||||||
document.getElementById('status-area').classList.remove('d-none');
|
|
||||||
|
|
||||||
const adapterSelect = document.getElementById('adapter-select');
|
if (!file) {
|
||||||
const adapterId = adapterSelect.value;
|
showResult("Please select a file to upload.", false);
|
||||||
|
return;
|
||||||
let adapterName = "";
|
|
||||||
if (adapterSelect.selectedIndex > 0) {
|
|
||||||
adapterName = adapterSelect.options[adapterSelect.selectedIndex].getAttribute('data-name');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!adapterName) {
|
||||||
|
showResult("Please select an adapter.", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show processing state
|
||||||
|
showResult("Processing...", true, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
|
// Route appropriately based on file extension
|
||||||
if (file.name.toLowerCase().endsWith('.json')) {
|
if (file.name.toLowerCase().endsWith('.json')) {
|
||||||
if (!adapterName) {
|
|
||||||
statusText.innerText = "Unknown JSON format. Redirecting to Adapter Builder...";
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = `/admin/adapters/new?filename=${encodeURIComponent(file.name)}`;
|
|
||||||
}, 1200);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawText = await file.text();
|
const rawText = await file.text();
|
||||||
response = await fetch(`/api/ingest/${encodeURIComponent(adapterName)}`, {
|
response = await fetch(`/api/ingest/${encodeURIComponent(adapterName)}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: rawText
|
body: rawText
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
if (adapterId) {
|
formData.append('adapter_name', adapterName); // Pass adapter name for CSVs
|
||||||
formData.append('adapter_id', adapterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
response = await fetch('/api/ingest/csv', { method: 'POST', body: formData });
|
response = await fetch('/api/ingest/csv', { method: 'POST', body: formData });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
// Auto-redirect to builder if the adapter doesn't match the file structure
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
statusText.innerText = "Format not recognized. Redirecting to Adapter Builder...";
|
showResult("Format not recognized. Redirecting to Adapter Builder...", false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = `/admin/adapters/new?filename=${encodeURIComponent(file.name)}`;
|
window.location.href = `/admin/adapters/new?filename=${encodeURIComponent(file.name)}`;
|
||||||
}, 1200);
|
}, 1200);
|
||||||
@@ -53,16 +50,31 @@ async function processFile(file) {
|
|||||||
throw new Error(errText);
|
throw new Error(errText);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
statusText.innerText = "Yeehaw! Tickets corralled successfully.";
|
showResult("Yeehaw! Tickets corralled successfully.", true);
|
||||||
setTimeout(() => window.location.href = "/dashboard", 800);
|
setTimeout(() => window.location.href = "/dashboard", 1000);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
statusText.innerText = "Stampede! Error: " + err.message;
|
showResult("Stampede! Error: " + err.message, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dropZone.onclick = () => fileInput.click();
|
// Helper function to handle status messages nicely
|
||||||
fileInput.onchange = (e) => processFile(e.target.files[0]);
|
function showResult(msg, isSuccess, isInfo = false) {
|
||||||
dropZone.ondragover = (e) => { e.preventDefault(); dropZone.style.background = "#e1f5fe"; };
|
const div = document.getElementById('ingestResult');
|
||||||
dropZone.ondragleave = () => dropZone.style.background = "#f8f9fa";
|
div.style.display = 'block';
|
||||||
dropZone.ondrop = (e) => { e.preventDefault(); processFile(e.dataTransfer.files[0]); };
|
div.innerText = msg;
|
||||||
|
|
||||||
|
if (isInfo) {
|
||||||
|
div.style.backgroundColor = '#e0f2fe';
|
||||||
|
div.style.color = '#0369a1';
|
||||||
|
div.style.border = '1px solid #bae6fd';
|
||||||
|
} else if (isSuccess) {
|
||||||
|
div.style.backgroundColor = '#dcfce7';
|
||||||
|
div.style.color = '#166534';
|
||||||
|
div.style.border = '1px solid #bbf7d0';
|
||||||
|
} else {
|
||||||
|
div.style.backgroundColor = '#fee2e2';
|
||||||
|
div.style.color = '#991b1b';
|
||||||
|
div.style.border = '1px solid #fecaca';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,8 +90,8 @@
|
|||||||
<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>
|
<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>
|
||||||
<div style="display: flex; gap: 15px; margin-top: 20px;">
|
<div style="display: flex; gap: 15px; margin-top: 20px;">
|
||||||
<button class="btn" style="background: #2563eb; color: white;" onclick="saveAdapter()">💾 Save to Database</button>
|
<button type="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>
|
<button type="button" class="btn btn-secondary" onclick="exportAdapterJSON()">⬇️ Export JSON Profile</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,16 +3,22 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>RiskRancher OSS</title>
|
<title>RiskRancher {{if isProActive}}PRO{{else}}OSS{{end}}</title>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header style="display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background: white; border-bottom: 1px solid #e2e8f0;">
|
<header style="display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background: white; border-bottom: 1px solid #e2e8f0;">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<h2 style="margin: 0;"><a href="/dashboard" style="color: #0f172a; text-decoration: none;">🐴 RiskRancher</a></h2>
|
<h2 style="margin: 0;">
|
||||||
|
<a href="/dashboard" style="color: #0f172a; text-decoration: none;">
|
||||||
|
🐴 RiskRancher{{if isProActive}}{{with getCompanyName}} | {{.}}{{end}}{{end}}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<nav style="display: flex; align-items: center; gap: 15px;">
|
<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>
|
<span style="color: {{if isProActive}}#059669{{else}}#475569{{end}}; font-size: 0.9rem; font-family: monospace; padding-left: 15px; margin-left: 5px; font-weight: {{if isProActive}}bold{{else}}normal{{end}};">
|
||||||
|
{{if isProActive}}PRO Edition{{else}}Community Edition{{end}}
|
||||||
|
</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>
|
<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>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
@@ -29,7 +35,7 @@
|
|||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
</main>
|
</main>
|
||||||
<footer style="text-align: center; padding: 20px; margin-top: 40px; border-top: 1px solid #e2e8f0; color: #94a3b8; font-size: 0.85rem;">
|
<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>
|
🐴 RiskRancher {{if isProActive}}PRO Edition{{else}}Core Edition{{end}} | Version: <strong>{{.Version}}</strong> | Build: <strong>{{.Commit}}</strong>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -83,20 +83,6 @@
|
|||||||
<h3 style="margin: 0 0 15px 0;">⚙️ Operations</h3>
|
<h3 style="margin: 0 0 15px 0;">⚙️ Operations</h3>
|
||||||
|
|
||||||
{{block "pro_backups" .}}
|
{{block "pro_backups" .}}
|
||||||
<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>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
<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>
|
<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>
|
||||||
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px;">
|
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px;">
|
||||||
<div>
|
<div>
|
||||||
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Adjust Severity:</label>
|
<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;">
|
<select id="drawerSeverity" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.95rem;">
|
||||||
@@ -101,6 +101,18 @@
|
|||||||
<option value="Info">Info</option>
|
<option value="Info">Info</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Adjust Status:</label>
|
||||||
|
<select id="drawerStatus" style="width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.95rem;">
|
||||||
|
<option value="Waiting to be Triaged">Waiting to be Triaged</option>
|
||||||
|
<option value="Assigned Out">Assigned Out</option>
|
||||||
|
<option value="Returned to Security">Returned to Security</option>
|
||||||
|
<option value="Patched">Remediated</option>
|
||||||
|
<option value="False Positive">False Positive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label style="font-weight: bold; color: #334155; display: block; margin-bottom: 5px;">Assign to IT (Email):</label>
|
<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;">
|
<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;">
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/auth"
|
"code.riskrancher.com/RiskRancher/core/pkg/auth"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
"code.riskrancher.com/RiskRancher/core/pkg/domain"
|
||||||
"epigas.gitea.cloud/RiskRancher/core/pkg/report"
|
"code.riskrancher.com/RiskRancher/core/pkg/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/* templates/components/* static/*
|
//go:embed templates/* templates/components/* static/*
|
||||||
@@ -36,7 +36,11 @@ func SetVersionInfo(version, commit string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
funcMap := template.FuncMap{"lower": strings.ToLower}
|
funcMap := template.FuncMap{
|
||||||
|
"lower": strings.ToLower,
|
||||||
|
"isProActive": func() bool { return false },
|
||||||
|
"getCompanyName": func() string { return "" },
|
||||||
|
}
|
||||||
Pages = make(map[string]*template.Template)
|
Pages = make(map[string]*template.Template)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
Reference in New Issue
Block a user