mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 15:22:47 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03a4806d45 | ||
|
|
e6baead486 | ||
|
|
3618f9784d | ||
|
|
02c7da91da | ||
|
|
18a4dc54e7 |
60
cmd/cmd.go
60
cmd/cmd.go
@@ -1,8 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
kongyaml "github.com/alecthomas/kong-yaml"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
@@ -11,8 +9,6 @@ import (
|
||||
"github.com/SecurityBrewery/catalyst"
|
||||
"github.com/SecurityBrewery/catalyst/bus"
|
||||
"github.com/SecurityBrewery/catalyst/database"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
"github.com/SecurityBrewery/catalyst/generated/pointer"
|
||||
"github.com/SecurityBrewery/catalyst/role"
|
||||
"github.com/SecurityBrewery/catalyst/storage"
|
||||
)
|
||||
@@ -47,9 +43,7 @@ type CLI struct {
|
||||
EmitterIOHost string `env:"EMITTER_IO_HOST" default:"tcp://emitter:8080"`
|
||||
EmitterIORKey string `env:"EMITTER_IO_KEY" required:""`
|
||||
|
||||
Timeformat string `env:"TIMEFORMAT" default:"yyyy-MM-dd HH:mm:ss" help:""`
|
||||
ArtifactStates []map[string]string `env:"ARTIFACT_STATES"`
|
||||
InitialAPIKey string `env:"INITIAL_API_KEY"`
|
||||
InitialAPIKey string `env:"INITIAL_API_KEY"`
|
||||
}
|
||||
|
||||
func ParseCatalystConfig() (*catalyst.Config, error) {
|
||||
@@ -68,19 +62,6 @@ func MapConfig(cli CLI) (*catalyst.Config, error) {
|
||||
roles = append(roles, role.Explodes(cli.AuthDefaultRoles)...)
|
||||
roles = role.Explodes(role.Strings(roles))
|
||||
|
||||
artifactStates, err := toTypes(cli.ArtifactStates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(artifactStates) == 0 {
|
||||
artifactStates = []*model.Type{
|
||||
{Icon: "mdi-help-circle-outline", ID: "unknown", Name: "Unknown", Color: pointer.String(model.TypeColorInfo)},
|
||||
{Icon: "mdi-skull", ID: "malicious", Name: "Malicious", Color: pointer.String(model.TypeColorError)},
|
||||
{Icon: "mdi-check", ID: "clean", Name: "Clean", Color: pointer.String(model.TypeColorSuccess)},
|
||||
}
|
||||
}
|
||||
|
||||
scopes := unique(append([]string{oidc.ScopeOpenID, "profile", "email"}, cli.OIDCScopes...))
|
||||
config := &catalyst.Config{
|
||||
IndexPath: cli.IndexPath,
|
||||
@@ -99,49 +80,12 @@ func MapConfig(cli CLI) (*catalyst.Config, error) {
|
||||
AuthDefaultRoles: roles,
|
||||
AuthAdminUsers: cli.AuthAdminUsers,
|
||||
},
|
||||
Bus: &bus.Config{Host: cli.EmitterIOHost, Key: cli.EmitterIORKey, APIUrl: cli.CatalystAddress + "/api"},
|
||||
UISettings: &model.Settings{
|
||||
ArtifactStates: artifactStates,
|
||||
Timeformat: cli.Timeformat,
|
||||
Version: catalyst.GetVersion(),
|
||||
Tier: model.SettingsTierCommunity,
|
||||
},
|
||||
Bus: &bus.Config{Host: cli.EmitterIOHost, Key: cli.EmitterIORKey, APIUrl: cli.CatalystAddress + "/api"},
|
||||
InitialAPIKey: cli.InitialAPIKey,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func toTypes(params []map[string]string) ([]*model.Type, error) {
|
||||
var types []*model.Type
|
||||
for _, param := range params {
|
||||
t := &model.Type{}
|
||||
|
||||
icon, iconOK := param["icon"]
|
||||
if iconOK {
|
||||
t.Icon = icon
|
||||
}
|
||||
id, idOK := param["id"]
|
||||
if idOK {
|
||||
t.ID = id
|
||||
}
|
||||
name, nameOK := param["name"]
|
||||
if nameOK {
|
||||
t.Name = name
|
||||
}
|
||||
color, ok := param["color"]
|
||||
if ok {
|
||||
t.Color = pointer.String(color)
|
||||
}
|
||||
|
||||
if iconOK && idOK && nameOK {
|
||||
types = append(types, t)
|
||||
} else {
|
||||
return nil, fmt.Errorf("incomplete type: icon, id and name need to be provided (%s)", params)
|
||||
}
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func unique(l []string) []string {
|
||||
keys := make(map[string]bool)
|
||||
var list []string
|
||||
|
||||
117
database/dashboard.go
Normal file
117
database/dashboard.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
"github.com/iancoleman/strcase"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/caql"
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
)
|
||||
|
||||
func toDashboardResponse(key string, doc *model.Dashboard) *model.DashboardResponse {
|
||||
return &model.DashboardResponse{
|
||||
ID: key,
|
||||
Name: doc.Name,
|
||||
Widgets: doc.Widgets,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) DashboardCreate(ctx context.Context, dashboard *model.Dashboard) (*model.DashboardResponse, error) {
|
||||
if dashboard == nil {
|
||||
return nil, errors.New("requires dashboard")
|
||||
}
|
||||
if dashboard.Name == "" {
|
||||
return nil, errors.New("requires dashboard name")
|
||||
}
|
||||
|
||||
if err := db.parseWidgets(dashboard); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var doc model.Dashboard
|
||||
newctx := driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
meta, err := db.dashboardCollection.CreateDocument(ctx, newctx, strcase.ToKebab(dashboard.Name), dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toDashboardResponse(meta.Key, &doc), nil
|
||||
}
|
||||
|
||||
func (db *Database) DashboardGet(ctx context.Context, id string) (*model.DashboardResponse, error) {
|
||||
var doc model.Dashboard
|
||||
meta, err := db.dashboardCollection.ReadDocument(ctx, id, &doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toDashboardResponse(meta.Key, &doc), nil
|
||||
}
|
||||
|
||||
func (db *Database) DashboardUpdate(ctx context.Context, id string, dashboard *model.Dashboard) (*model.DashboardResponse, error) {
|
||||
if err := db.parseWidgets(dashboard); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var doc model.Dashboard
|
||||
ctx = driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
meta, err := db.dashboardCollection.ReplaceDocument(ctx, id, dashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toDashboardResponse(meta.Key, &doc), nil
|
||||
}
|
||||
|
||||
func (db *Database) DashboardDelete(ctx context.Context, id string) error {
|
||||
_, err := db.dashboardCollection.RemoveDocument(ctx, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) DashboardList(ctx context.Context) ([]*model.DashboardResponse, error) {
|
||||
query := "FOR d IN @@collection RETURN d"
|
||||
cursor, _, err := db.Query(ctx, query, map[string]interface{}{"@collection": DashboardCollectionName}, busdb.ReadOperation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close()
|
||||
var docs []*model.DashboardResponse
|
||||
for {
|
||||
var doc model.Dashboard
|
||||
meta, err := cursor.ReadDocument(ctx, &doc)
|
||||
if driver.IsNoMoreDocuments(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs = append(docs, toDashboardResponse(meta.Key, &doc))
|
||||
}
|
||||
|
||||
return docs, err
|
||||
}
|
||||
|
||||
func (db *Database) parseWidgets(dashboard *model.Dashboard) error {
|
||||
for _, widget := range dashboard.Widgets {
|
||||
parser := &caql.Parser{Searcher: db.Index, Prefix: "d."}
|
||||
|
||||
_, err := parser.Parse(widget.Aggregation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid aggregation query (%s): syntax error\n", widget.Aggregation)
|
||||
}
|
||||
|
||||
if widget.Filter != nil {
|
||||
_, err := parser.Parse(*widget.Filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid filter query (%s): syntax error\n", *widget.Filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -25,6 +25,8 @@ const (
|
||||
UserCollectionName = "users"
|
||||
TicketTypeCollectionName = "tickettypes"
|
||||
JobCollectionName = "jobs"
|
||||
SettingsCollectionName = "settings"
|
||||
DashboardCollectionName = "dashboards"
|
||||
|
||||
TicketArtifactsGraphName = "Graph"
|
||||
RelatedTicketsCollectionName = "related"
|
||||
@@ -44,6 +46,8 @@ type Database struct {
|
||||
userCollection *busdb.Collection
|
||||
tickettypeCollection *busdb.Collection
|
||||
jobCollection *busdb.Collection
|
||||
settingsCollection *busdb.Collection
|
||||
dashboardCollection *busdb.Collection
|
||||
|
||||
relatedCollection *busdb.Collection
|
||||
// containsCollection *busdb.Collection
|
||||
@@ -122,6 +126,14 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
settingsCollection, err := arangoDB.Collection(ctx, SettingsCollectionName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dashboardCollection, err := arangoDB.Collection(ctx, DashboardCollectionName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hookedDB, err := busdb.NewDatabase(ctx, arangoDB, bus)
|
||||
if err != nil {
|
||||
@@ -142,6 +154,8 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo
|
||||
userCollection: busdb.NewCollection(userCollection, hookedDB),
|
||||
tickettypeCollection: busdb.NewCollection(tickettypeCollection, hookedDB),
|
||||
jobCollection: busdb.NewCollection(jobCollection, hookedDB),
|
||||
settingsCollection: busdb.NewCollection(settingsCollection, hookedDB),
|
||||
dashboardCollection: busdb.NewCollection(dashboardCollection, hookedDB),
|
||||
}
|
||||
|
||||
return db, nil
|
||||
@@ -189,5 +203,7 @@ func (db *Database) Truncate(ctx context.Context) {
|
||||
db.tickettypeCollection.Truncate(ctx)
|
||||
db.jobCollection.Truncate(ctx)
|
||||
db.relatedCollection.Truncate(ctx)
|
||||
db.settingsCollection.Truncate(ctx)
|
||||
db.dashboardCollection.Truncate(ctx)
|
||||
// db.containsCollection.Truncate(ctx)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,15 @@ func generateMigrations() ([]Migration, error) {
|
||||
&createCollection{ID: "create-job-collection", Name: "jobs", DataType: "job", Schema: `{"properties":{"automation":{"type":"string"},"log":{"type":"string"},"payload":{},"origin":{"properties":{"artifact_origin":{"properties":{"artifact":{"type":"string"},"ticket_id":{"format":"int64","type":"integer"}},"required":["artifact","ticket_id"],"type":"object"},"task_origin":{"properties":{"playbook_id":{"type":"string"},"task_id":{"type":"string"},"ticket_id":{"format":"int64","type":"integer"}},"required":["playbook_id","task_id","ticket_id"],"type":"object"}},"type":"object"},"output":{"properties":{},"type":"object"},"running":{"type":"boolean"},"status":{"type":"string"}},"required":["automation","running","status"],"type":"object"}`},
|
||||
|
||||
&createDocument{ID: "create-playbook-simple", Collection: "playbooks", Document: &busdb.Keyed{Key: "simple", Doc: model.PlaybookTemplate{Name: "Simple", Yaml: SimplePlaybook}}},
|
||||
|
||||
&createCollection{ID: "create-settings-collection", Name: "settings", DataType: "settings", Schema: `{"type":"object","properties":{"artifactStates":{"title":"Artifact States","items":{"type":"object","properties":{"color":{"title":"Color","type":"string","enum":["error","info","success","warning"]},"icon":{"title":"Icon (https://materialdesignicons.com)","type":"string"},"id":{"title":"ID","type":"string"},"name":{"title":"Name","type":"string"}},"required":["id","name","icon"]},"type":"array"},"artifactKinds":{"title":"Artifact Kinds","items":{"type":"object","properties":{"color":{"title":"Color","type":"string","enum":["error","info","success","warning"]},"icon":{"title":"Icon (https://materialdesignicons.com)","type":"string"},"id":{"title":"ID","type":"string"},"name":{"title":"Name","type":"string"}},"required":["id","name","icon"]},"type":"array"},"timeformat":{"title":"Time Format","type":"string"}},"required":["timeformat","artifactKinds","artifactStates"]}`},
|
||||
&createDocument{ID: "create-settings-global", Collection: "settings", Document: &busdb.Keyed{Key: "global", Doc: model.Settings{ArtifactStates: []*model.Type{{Icon: "mdi-help-circle-outline", ID: "unknown", Name: "Unknown", Color: pointer.String(model.TypeColorInfo)}, {Icon: "mdi-skull", ID: "malicious", Name: "Malicious", Color: pointer.String(model.TypeColorError)}, {Icon: "mdi-check", ID: "clean", Name: "Clean", Color: pointer.String(model.TypeColorSuccess)}}, ArtifactKinds: []*model.Type{{Icon: "mdi-server", ID: "asset", Name: "Asset"}, {Icon: "mdi-bullseye", ID: "ioc", Name: "IOC"}}, Timeformat: "YYYY-MM-DDThh:mm:ss"}}},
|
||||
|
||||
&updateSchema{ID: "update-ticket-collection", Name: "tickets", DataType: "ticket", Schema: `{"properties":{"artifacts":{"items":{"properties":{"enrichments":{"additionalProperties":{"properties":{"created":{"format":"date-time","type":"string"},"data":{"example":{"hash":"b7a067a742c20d07a7456646de89bc2d408a1153"},"properties":{},"type":"object"},"name":{"example":"hash.sha1","type":"string"}},"required":["created","data","name"],"type":"object"},"type":"object"},"name":{"example":"2.2.2.2","type":"string"},"status":{"example":"Unknown","type":"string"},"type":{"type":"string"},"kind":{"type":"string"}},"required":["name"],"type":"object"},"type":"array"},"comments":{"items":{"properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"}},"required":["created","creator","message"],"type":"object"},"type":"array"},"created":{"format":"date-time","type":"string"},"details":{"example":{"description":"my little incident"},"properties":{},"type":"object"},"files":{"items":{"properties":{"key":{"example":"myfile","type":"string"},"name":{"example":"notes.docx","type":"string"}},"required":["key","name"],"type":"object"},"type":"array"},"modified":{"format":"date-time","type":"string"},"name":{"example":"WannyCry","type":"string"},"owner":{"example":"bob","type":"string"},"playbooks":{"additionalProperties":{"properties":{"name":{"example":"Phishing","type":"string"},"tasks":{"additionalProperties":{"properties":{"automation":{"type":"string"},"closed":{"format":"date-time","type":"string"},"created":{"format":"date-time","type":"string"},"data":{"properties":{},"type":"object"},"done":{"type":"boolean"},"join":{"example":false,"type":"boolean"},"payload":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"example":"Inform user","type":"string"},"next":{"additionalProperties":{"type":"string"},"type":"object"},"owner":{"type":"string"},"schema":{"properties":{},"type":"object"},"type":{"enum":["task","input","automation"],"example":"task","type":"string"}},"required":["created","done","name","type"],"type":"object"},"type":"object"}},"required":["name","tasks"],"type":"object"},"type":"object"},"read":{"example":["bob"],"items":{"type":"string"},"type":"array"},"references":{"items":{"properties":{"href":{"example":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2017-0144","type":"string"},"name":{"example":"CVE-2017-0144","type":"string"}},"required":["href","name"],"type":"object"},"type":"array"},"schema":{"example":"{}","type":"string"},"status":{"example":"open","type":"string"},"type":{"example":"incident","type":"string"},"write":{"example":["alice"],"items":{"type":"string"},"type":"array"}},"required":["created","modified","name","schema","status","type"],"type":"object"}`},
|
||||
|
||||
&createCollection{ID: "create-dashboard-collection", Name: "dashboards", DataType: "dashboards", Schema: `{"type":"object","properties":{"name":{"type":"string"},"widgets":{"items":{"type":"object","properties":{"aggregation":{"type":"string"},"filter":{"type":"string"},"name":{"type":"string"},"type":{"enum":[ "bar", "line", "pie" ]},"width": { "type": "integer", "minimum": 1, "maximum": 12 }},"required":["name","aggregation", "type", "width"]},"type":"array"}},"required":["name","widgets"]}`},
|
||||
|
||||
&updateDocument{ID: "update-settings-global-1", Collection: "settings", Key: "global", Document: &model.Settings{ArtifactStates: []*model.Type{{Icon: "mdi-help-circle-outline", ID: "unknown", Name: "Unknown", Color: pointer.String(model.TypeColorInfo)}, {Icon: "mdi-skull", ID: "malicious", Name: "Malicious", Color: pointer.String(model.TypeColorError)}, {Icon: "mdi-check", ID: "clean", Name: "Clean", Color: pointer.String(model.TypeColorSuccess)}}, ArtifactKinds: []*model.Type{{Icon: "mdi-server", ID: "asset", Name: "Asset"}, {Icon: "mdi-bullseye", ID: "ioc", Name: "IOC"}}, Timeformat: "yyyy-MM-dd hh:mm:ss"}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,84 +2,34 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
)
|
||||
|
||||
func toUserDataResponse(key string, doc *model.UserData) *model.UserDataResponse {
|
||||
return &model.UserDataResponse{
|
||||
Email: doc.Email,
|
||||
ID: key,
|
||||
Image: doc.Image,
|
||||
Name: doc.Name,
|
||||
Timeformat: doc.Timeformat,
|
||||
func (db *Database) Settings(ctx context.Context) (*model.Settings, error) {
|
||||
settings := &model.Settings{}
|
||||
if _, err := db.settingsCollection.ReadDocument(ctx, "global", settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func (db *Database) UserDataCreate(ctx context.Context, id string, userdata *model.UserData) error {
|
||||
if userdata == nil {
|
||||
return errors.New("requires setting")
|
||||
}
|
||||
if id == "" {
|
||||
return errors.New("requires username")
|
||||
}
|
||||
|
||||
_, err := db.userdataCollection.CreateDocument(ctx, ctx, id, userdata)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) UserDataGetOrCreate(ctx context.Context, id string, newUserData *model.UserData) (*model.UserDataResponse, error) {
|
||||
setting, err := db.UserDataGet(ctx, id)
|
||||
if err != nil {
|
||||
return toUserDataResponse(id, newUserData), db.UserDataCreate(ctx, id, newUserData)
|
||||
}
|
||||
return setting, nil
|
||||
}
|
||||
|
||||
func (db *Database) UserDataGet(ctx context.Context, id string) (*model.UserDataResponse, error) {
|
||||
var doc model.UserData
|
||||
meta, err := db.userdataCollection.ReadDocument(ctx, id, &doc)
|
||||
func (db *Database) SaveSettings(ctx context.Context, settings *model.Settings) (*model.Settings, error) {
|
||||
exists, err := db.settingsCollection.DocumentExists(ctx, "global")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUserDataResponse(meta.Key, &doc), err
|
||||
}
|
||||
|
||||
func (db *Database) UserDataList(ctx context.Context) ([]*model.UserDataResponse, error) {
|
||||
query := "FOR d IN @@collection SORT d.username ASC RETURN d"
|
||||
cursor, _, err := db.Query(ctx, query, map[string]interface{}{"@collection": UserDataCollectionName}, busdb.ReadOperation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close()
|
||||
var docs []*model.UserDataResponse
|
||||
for {
|
||||
var doc model.UserData
|
||||
meta, err := cursor.ReadDocument(ctx, &doc)
|
||||
if driver.IsNoMoreDocuments(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
if exists {
|
||||
if _, err := db.settingsCollection.ReplaceDocument(ctx, "global", settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if _, err := db.settingsCollection.CreateDocument(ctx, ctx, "global", settings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs = append(docs, toUserDataResponse(meta.Key, &doc))
|
||||
}
|
||||
|
||||
return docs, err
|
||||
}
|
||||
|
||||
func (db *Database) UserDataUpdate(ctx context.Context, id string, userdata *model.UserData) (*model.UserDataResponse, error) {
|
||||
var doc model.UserData
|
||||
ctx = driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
meta, err := db.userdataCollection.ReplaceDocument(ctx, id, userdata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUserDataResponse(meta.Key, &doc), nil
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/caql"
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
)
|
||||
@@ -41,3 +43,49 @@ func (db *Database) Statistics(ctx context.Context) (*model.Statistics, error) {
|
||||
|
||||
return &statistics, nil
|
||||
}
|
||||
|
||||
func (db *Database) WidgetData(ctx context.Context, aggregation string, filter *string) (map[string]interface{}, error) {
|
||||
parser := &caql.Parser{Searcher: db.Index, Prefix: "d."}
|
||||
|
||||
queryTree, err := parser.Parse(aggregation)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid aggregation query (%s): syntax error\n", aggregation)
|
||||
}
|
||||
aggregationString, err := queryTree.String()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid widget aggregation query (%s): %w", aggregation, err)
|
||||
}
|
||||
aggregation = aggregationString
|
||||
|
||||
filterQ := ""
|
||||
if filter != nil && *filter != "" {
|
||||
queryTree, err := parser.Parse(*filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter query (%s): syntax error\n", *filter)
|
||||
}
|
||||
filterString, err := queryTree.String()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid widget filter query (%s): %w", *filter, err)
|
||||
}
|
||||
|
||||
filterQ = "FILTER " + filterString
|
||||
}
|
||||
|
||||
query := `RETURN MERGE(FOR d in tickets
|
||||
` + filterQ + `
|
||||
COLLECT field = ` + aggregation + ` WITH COUNT INTO count
|
||||
RETURN ZIP([field], [count]))`
|
||||
|
||||
cur, _, err := db.Query(ctx, query, nil, busdb.ReadOperation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cur.Close()
|
||||
|
||||
statistics := map[string]interface{}{}
|
||||
if _, err := cur.ReadDocument(ctx, &statistics); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return statistics, nil
|
||||
}
|
||||
|
||||
@@ -449,8 +449,6 @@ func (db *Database) TicketDelete(ctx context.Context, ticketID int64) error {
|
||||
func (db *Database) TicketList(ctx context.Context, ticketType string, query string, sorts []string, desc []bool, offset, count int64) (*model.TicketList, error) {
|
||||
binVars := map[string]interface{}{}
|
||||
|
||||
parser := &caql.Parser{Searcher: db.Index, Prefix: "d."}
|
||||
|
||||
var typeString = ""
|
||||
if ticketType != "" {
|
||||
typeString = "FILTER d.type == @type "
|
||||
@@ -459,6 +457,7 @@ func (db *Database) TicketList(ctx context.Context, ticketType string, query str
|
||||
|
||||
var filterString = ""
|
||||
if query != "" {
|
||||
parser := &caql.Parser{Searcher: db.Index, Prefix: "d."}
|
||||
queryTree, err := parser.Parse(query)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid filter query: syntax error")
|
||||
|
||||
85
database/userdata.go
Normal file
85
database/userdata.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
)
|
||||
|
||||
func toUserDataResponse(key string, doc *model.UserData) *model.UserDataResponse {
|
||||
return &model.UserDataResponse{
|
||||
Email: doc.Email,
|
||||
ID: key,
|
||||
Image: doc.Image,
|
||||
Name: doc.Name,
|
||||
Timeformat: doc.Timeformat,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) UserDataCreate(ctx context.Context, id string, userdata *model.UserData) error {
|
||||
if userdata == nil {
|
||||
return errors.New("requires setting")
|
||||
}
|
||||
if id == "" {
|
||||
return errors.New("requires username")
|
||||
}
|
||||
|
||||
_, err := db.userdataCollection.CreateDocument(ctx, ctx, id, userdata)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) UserDataGetOrCreate(ctx context.Context, id string, newUserData *model.UserData) (*model.UserDataResponse, error) {
|
||||
setting, err := db.UserDataGet(ctx, id)
|
||||
if err != nil {
|
||||
return toUserDataResponse(id, newUserData), db.UserDataCreate(ctx, id, newUserData)
|
||||
}
|
||||
return setting, nil
|
||||
}
|
||||
|
||||
func (db *Database) UserDataGet(ctx context.Context, id string) (*model.UserDataResponse, error) {
|
||||
var doc model.UserData
|
||||
meta, err := db.userdataCollection.ReadDocument(ctx, id, &doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUserDataResponse(meta.Key, &doc), err
|
||||
}
|
||||
|
||||
func (db *Database) UserDataList(ctx context.Context) ([]*model.UserDataResponse, error) {
|
||||
query := "FOR d IN @@collection SORT d.username ASC RETURN d"
|
||||
cursor, _, err := db.Query(ctx, query, map[string]interface{}{"@collection": UserDataCollectionName}, busdb.ReadOperation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close()
|
||||
var docs []*model.UserDataResponse
|
||||
for {
|
||||
var doc model.UserData
|
||||
meta, err := cursor.ReadDocument(ctx, &doc)
|
||||
if driver.IsNoMoreDocuments(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs = append(docs, toUserDataResponse(meta.Key, &doc))
|
||||
}
|
||||
|
||||
return docs, err
|
||||
}
|
||||
|
||||
func (db *Database) UserDataUpdate(ctx context.Context, id string, userdata *model.UserData) (*model.UserDataResponse, error) {
|
||||
var doc model.UserData
|
||||
ctx = driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
meta, err := db.userdataCollection.ReplaceDocument(ctx, id, userdata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toUserDataResponse(meta.Key, &doc), nil
|
||||
}
|
||||
@@ -10,6 +10,7 @@ definitions:
|
||||
properties:
|
||||
name: { type: string, example: "2.2.2.2" }
|
||||
type: { type: string }
|
||||
kind: { type: string }
|
||||
status: { type: string, example: "Unknown" }
|
||||
enrichments: { type: object, additionalProperties: { $ref: "#/definitions/Enrichment" } }
|
||||
|
||||
|
||||
167
definition/dashboards.yaml
Normal file
167
definition/dashboards.yaml
Normal file
@@ -0,0 +1,167 @@
|
||||
swagger: "2.0"
|
||||
info: { version: "", title: "" }
|
||||
|
||||
paths:
|
||||
/statistics:
|
||||
get:
|
||||
tags: [ "statistics" ]
|
||||
summary: "Get statistics"
|
||||
operationId: "getStatistics"
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: '#/definitions/Statistics' }
|
||||
examples:
|
||||
test:
|
||||
unassigned: 0
|
||||
open_tickets_per_user: { }
|
||||
tickets_per_week: { "2021-39": 3 }
|
||||
tickets_per_type: { "alert": 2, "incident": 1 }
|
||||
security: [ { roles: [ "ticket:read" ] } ]
|
||||
|
||||
/dashboards:
|
||||
get:
|
||||
tags: [ "dashboards" ]
|
||||
summary: "List dashboards"
|
||||
operationId: "listDashboards"
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { type: array, items: { $ref: "#/definitions/DashboardResponse" } }
|
||||
examples:
|
||||
test:
|
||||
- id: simple
|
||||
name: Simple
|
||||
widgets:
|
||||
- name: "open_tickets_per_user"
|
||||
aggregation: "owner"
|
||||
filter: 'status == "open"'
|
||||
type: "bar"
|
||||
width: 4
|
||||
- name: "tickets_per_week"
|
||||
aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) < 10 ? "0" : "", DATE_ISOWEEK(created))'
|
||||
type: "line"
|
||||
width: 8
|
||||
security: [ { roles: [ "dashboard:read" ] } ]
|
||||
post:
|
||||
tags: [ "dashboards" ]
|
||||
summary: "Create a new dashboard"
|
||||
operationId: "createDashboard"
|
||||
parameters:
|
||||
- { name: "template", in: "body", description: "New template", required: true, schema: { $ref: "#/definitions/Dashboard" }, x-example: { name: "My Dashboard", widgets: [ ] } }
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/DashboardResponse" }
|
||||
examples:
|
||||
test:
|
||||
id: "my-dashboard"
|
||||
name: "My Dashboard"
|
||||
widgets: []
|
||||
security: [ { roles: [ "dashboard:write" ] } ]
|
||||
|
||||
/dashboards/{id}:
|
||||
get:
|
||||
tags: [ "dashboards" ]
|
||||
summary: "Get a single dashboard"
|
||||
operationId: "getDashboard"
|
||||
parameters:
|
||||
- { name: "id", in: "path", description: "Dashboard ID", required: true, type: string, x-example: "simple" }
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/DashboardResponse" }
|
||||
examples:
|
||||
test:
|
||||
id: simple
|
||||
name: Simple
|
||||
widgets:
|
||||
- name: "open_tickets_per_user"
|
||||
aggregation: "owner"
|
||||
filter: 'status == "open"'
|
||||
type: "bar"
|
||||
width: 4
|
||||
- name: "tickets_per_week"
|
||||
aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) < 10 ? "0" : "", DATE_ISOWEEK(created))'
|
||||
type: "line"
|
||||
width: 8
|
||||
security: [ { roles: [ "dashboard:read" ] } ]
|
||||
put:
|
||||
tags: [ "dashboards" ]
|
||||
summary: "Update an existing dashboard"
|
||||
operationId: "updateDashboard"
|
||||
parameters:
|
||||
- { name: "id", in: "path", description: "Dashboard ID", required: true, type: string, x-example: "simple" }
|
||||
- { name: "dashboard", in: "body", description: "Dashboard object that needs to be added", required: true, schema: { $ref: "#/definitions/Dashboard" }, x-example: { name: "Simple", widgets: [] } }
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/DashboardResponse" }
|
||||
examples:
|
||||
test:
|
||||
id: simple
|
||||
name: Simple
|
||||
widgets: []
|
||||
security: [ { roles: [ "dashboard:write" ] } ]
|
||||
delete:
|
||||
tags: [ "dashboards" ]
|
||||
summary: "Delete a dashboard"
|
||||
operationId: "deleteDashboard"
|
||||
parameters:
|
||||
- { name: "id", in: "path", description: "Dashboard ID", required: true, type: string, x-example: "simple" }
|
||||
responses:
|
||||
"204": { description: "successful operation" }
|
||||
security: [ { roles: [ "dashboard:write" ] } ]
|
||||
|
||||
/dashboard/data:
|
||||
get:
|
||||
tags: [ "dashboards" ]
|
||||
summary: "Get widget data"
|
||||
operationId: "dashboardData"
|
||||
parameters:
|
||||
- { name: "aggregation", in: "query", description: "Aggregation", required: true, type: string, x-example: "type" }
|
||||
- { name: "filter", in: "query", description: "Filter", type: string, x-example: 'status == "closed"' }
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { type: object }
|
||||
examples:
|
||||
test:
|
||||
alert: 2
|
||||
incident: 1
|
||||
security: [ { roles: [ "dashboard:read" ] } ]
|
||||
|
||||
definitions:
|
||||
Statistics:
|
||||
type: object
|
||||
required: [ unassigned, open_tickets_per_user, tickets_per_week, tickets_per_type ]
|
||||
properties:
|
||||
unassigned: { type: integer }
|
||||
open_tickets_per_user: { type: object, additionalProperties: { type: integer } }
|
||||
tickets_per_week: { type: object, additionalProperties: { type: integer } }
|
||||
tickets_per_type: { type: object, additionalProperties: { type: integer } }
|
||||
|
||||
Dashboard:
|
||||
type: object
|
||||
required: [ name, widgets ]
|
||||
properties:
|
||||
name: { type: string }
|
||||
widgets: { type: array, items: { $ref: "#/definitions/Widget" } }
|
||||
|
||||
DashboardResponse:
|
||||
type: object
|
||||
required: [ id, name, widgets ]
|
||||
properties:
|
||||
id: { type: string }
|
||||
name: { type: string }
|
||||
widgets: { type: array, items: { $ref: "#/definitions/Widget" } }
|
||||
|
||||
Widget:
|
||||
type: object
|
||||
required: [ name, type, aggregation, width ]
|
||||
properties:
|
||||
name: { type: string }
|
||||
type: { type: string, enum: [ "bar", "line", "pie" ] }
|
||||
filter: { type: string }
|
||||
aggregation: { type: string }
|
||||
width: { type: integer, minimum: 1, maximum: 12 }
|
||||
@@ -10,42 +10,92 @@ paths:
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/Settings" }
|
||||
schema: { $ref: "#/definitions/SettingsResponse" }
|
||||
examples:
|
||||
test:
|
||||
version: "0.0.0-test"
|
||||
tier: community
|
||||
timeformat: "YYYY-MM-DDThh:mm:ss"
|
||||
timeformat: "yyyy-MM-dd hh:mm:ss"
|
||||
ticketTypes:
|
||||
- { icon: "mdi-alert", id: "alert", name: "Alerts", default_template: "default", default_playbooks: [ ] }
|
||||
- { icon: "mdi-radioactive", id: "incident", name: "Incidents", default_template: "default", default_playbooks: [ ] }
|
||||
- { icon: "mdi-fingerprint", id: "investigation", name: "Forensic Investigations", default_template: "default", default_playbooks: [ ] }
|
||||
- { icon: "mdi-target", id: "hunt", name: "Threat Hunting", default_template: "default", default_playbooks: [ ] }
|
||||
artifactKinds:
|
||||
- { icon: "mdi-server", id: "asset", name: "Asset" }
|
||||
- { icon: "mdi-bullseye", id: "ioc", name: "IOC" }
|
||||
artifactStates:
|
||||
- { icon: "mdi-help-circle-outline", id: "unknown", name: "Unknown", color: "info" }
|
||||
- { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" }
|
||||
- { icon: "mdi-check", id: "clean", name: "Clean", color: "success" }
|
||||
roles: [
|
||||
"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write",
|
||||
"admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read",
|
||||
"admin:userdata:write", "analyst:automation:read",
|
||||
"analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read",
|
||||
"analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read",
|
||||
"analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write",
|
||||
"analyst:tickettype:read", "analyst:user:read", "engineer:automation:write",
|
||||
"engineer:playbook:write", "engineer:rule:write", "engineer:template:write",
|
||||
"engineer:tickettype:write" ]
|
||||
"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write",
|
||||
"admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read",
|
||||
"admin:userdata:write", "analyst:automation:read",
|
||||
"analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read",
|
||||
"analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read",
|
||||
"analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write",
|
||||
"analyst:tickettype:read", "analyst:user:read", "engineer:automation:write",
|
||||
"engineer:playbook:write", "engineer:rule:write", "engineer:template:write",
|
||||
"engineer:tickettype:write" ]
|
||||
security: [ { roles: [ "settings:read" ] } ]
|
||||
post:
|
||||
tags: [ "settings" ]
|
||||
summary: "Save settings"
|
||||
operationId: "saveSettings"
|
||||
parameters:
|
||||
- { name: "settings", in: "body", description: "Save settings", required: true, schema: { $ref: "#/definitions/Settings" }, x-example: { timeformat: "yyyy-MM-dd hh:mm:ss", artifactKinds: [ { icon: "mdi-server", id: "asset", name: "Asset" }, { icon: "mdi-bullseye", id: "ioc", name: "IOC" } ], artifactStates: [ { icon: "mdi-help-circle-outline", id: "unknown", name: "Unknown", color: "info" },{ icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" },{ icon: "mdi-check", id: "clean", name: "Clean", color: "success" } ] } }
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/SettingsResponse" }
|
||||
examples:
|
||||
test:
|
||||
version: "0.0.0-test"
|
||||
tier: community
|
||||
timeformat: "yyyy-MM-dd hh:mm:ss"
|
||||
ticketTypes:
|
||||
- { icon: "mdi-alert", id: "alert", name: "Alerts", default_template: "default", default_playbooks: [ ] }
|
||||
- { icon: "mdi-radioactive", id: "incident", name: "Incidents", default_template: "default", default_playbooks: [ ] }
|
||||
- { icon: "mdi-fingerprint", id: "investigation", name: "Forensic Investigations", default_template: "default", default_playbooks: [ ] }
|
||||
- { icon: "mdi-target", id: "hunt", name: "Threat Hunting", default_template: "default", default_playbooks: [ ] }
|
||||
artifactKinds:
|
||||
- { icon: "mdi-server", id: "asset", name: "Asset" }
|
||||
- { icon: "mdi-bullseye", id: "ioc", name: "IOC" }
|
||||
artifactStates:
|
||||
- { icon: "mdi-help-circle-outline", id: "unknown", name: "Unknown", color: "info" }
|
||||
- { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" }
|
||||
- { icon: "mdi-check", id: "clean", name: "Clean", color: "success" }
|
||||
roles: [
|
||||
"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write",
|
||||
"admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read",
|
||||
"admin:userdata:write", "analyst:automation:read",
|
||||
"analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read",
|
||||
"analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read",
|
||||
"analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write",
|
||||
"analyst:tickettype:read", "analyst:user:read", "engineer:automation:write",
|
||||
"engineer:playbook:write", "engineer:rule:write", "engineer:template:write",
|
||||
"engineer:tickettype:write" ]
|
||||
security: [ { roles: [ "settings:write" ] } ]
|
||||
|
||||
definitions:
|
||||
Settings:
|
||||
type: object
|
||||
required: [ version, tier, timeformat, ticketTypes, artifactStates ]
|
||||
required: [ timeformat, artifactKinds, artifactStates ]
|
||||
properties:
|
||||
timeformat: { title: "Time Format", type: string }
|
||||
artifactKinds: { title: "Artifact Kinds", type: array, items: { $ref: "#/definitions/Type" } }
|
||||
artifactStates: { title: "Artifact States", type: array, items: { $ref: "#/definitions/Type" } }
|
||||
|
||||
SettingsResponse:
|
||||
type: object
|
||||
required: [ version, tier, timeformat, ticketTypes, artifactKinds, artifactStates ]
|
||||
properties:
|
||||
version: { title: "Version", type: string }
|
||||
tier: { title: "Tier", type: string, enum: [ "community", "enterprise" ] }
|
||||
timeformat: { title: "Time Format", type: string }
|
||||
ticketTypes: { title: "Ticket Types", type: array, items: { $ref: "#/definitions/TicketTypeResponse" } }
|
||||
artifactKinds: { title: "Artifact Kinds", type: array, items: { $ref: "#/definitions/Type" } }
|
||||
artifactStates: { title: "Artifact States", type: array, items: { $ref: "#/definitions/Type" } }
|
||||
roles: { title: "Roles", type: array, items: { type: string } }
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
swagger: "2.0"
|
||||
info: { version: "", title: "" }
|
||||
|
||||
paths:
|
||||
/statistics:
|
||||
get:
|
||||
tags: [ "statistics" ]
|
||||
summary: "Get statistics"
|
||||
operationId: "getStatistics"
|
||||
responses:
|
||||
"200":
|
||||
description: "successful operation"
|
||||
schema: { $ref: '#/definitions/Statistics' }
|
||||
examples:
|
||||
test:
|
||||
unassigned: 0
|
||||
open_tickets_per_user: { }
|
||||
tickets_per_week: { "2021-39": 3 }
|
||||
tickets_per_type: { "alert": 2, "incident": 1 }
|
||||
security: [ { roles: [ "ticket:read" ] } ]
|
||||
|
||||
definitions:
|
||||
|
||||
Statistics:
|
||||
type: object
|
||||
required: [ unassigned, open_tickets_per_user, tickets_per_week, tickets_per_type ]
|
||||
properties:
|
||||
unassigned: { type: integer }
|
||||
open_tickets_per_user: { type: object, additionalProperties: { type: integer } }
|
||||
tickets_per_week: { type: object, additionalProperties: { type: integer } }
|
||||
tickets_per_type: { type: object, additionalProperties: { type: integer } }
|
||||
@@ -12,7 +12,7 @@ paths:
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/UserResponse" }
|
||||
examples:
|
||||
test: { id: bob, roles: [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: false }
|
||||
test: { id: bob, roles: [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read","analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: false }
|
||||
security: [ { roles: [ "currentuser:read" ] } ]
|
||||
|
||||
/users:
|
||||
@@ -26,8 +26,8 @@ paths:
|
||||
schema: { type: array, items: { $ref: "#/definitions/UserResponse" } }
|
||||
examples:
|
||||
test:
|
||||
- { id: bob, blocked: false, roles: [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], apikey: false }
|
||||
- { id: script, roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true }
|
||||
- { id: bob, blocked: false, roles: [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], apikey: false }
|
||||
- { id: script, roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true }
|
||||
security: [ { roles: [ "user:read" ] } ]
|
||||
post:
|
||||
tags: [ "users" ]
|
||||
@@ -40,7 +40,7 @@ paths:
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/NewUserResponse" }
|
||||
examples:
|
||||
test: { id: "syncscript", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], secret: "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH", blocked: false }
|
||||
test: { id: "syncscript", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], secret: "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH", blocked: false }
|
||||
security: [ { roles: [ "user:write" ] } ]
|
||||
/users/{id}:
|
||||
get:
|
||||
@@ -54,7 +54,7 @@ paths:
|
||||
description: "successful operation"
|
||||
schema: { $ref: "#/definitions/UserResponse" }
|
||||
examples:
|
||||
test: { id: "script", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true }
|
||||
test: { id: "script", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true }
|
||||
security: [ { roles: [ "user:read" ] } ]
|
||||
put:
|
||||
tags: [ "users" ]
|
||||
@@ -70,7 +70,7 @@ paths:
|
||||
examples:
|
||||
test:
|
||||
id: bob
|
||||
roles: [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
roles: [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
apikey: false
|
||||
blocked: false
|
||||
security: [ { roles: [ "user:write" ] } ]
|
||||
|
||||
@@ -19,6 +19,12 @@ type Service interface {
|
||||
CurrentUser(context.Context) (*model.UserResponse, error)
|
||||
CurrentUserData(context.Context) (*model.UserDataResponse, error)
|
||||
UpdateCurrentUserData(context.Context, *model.UserData) (*model.UserDataResponse, error)
|
||||
DashboardData(context.Context, string, *string) (map[string]interface{}, error)
|
||||
ListDashboards(context.Context) ([]*model.DashboardResponse, error)
|
||||
CreateDashboard(context.Context, *model.Dashboard) (*model.DashboardResponse, error)
|
||||
GetDashboard(context.Context, string) (*model.DashboardResponse, error)
|
||||
UpdateDashboard(context.Context, string, *model.Dashboard) (*model.DashboardResponse, error)
|
||||
DeleteDashboard(context.Context, string) error
|
||||
ListJobs(context.Context) ([]*model.JobResponse, error)
|
||||
RunJob(context.Context, *model.JobForm) (*model.JobResponse, error)
|
||||
GetJob(context.Context, string) (*model.JobResponse, error)
|
||||
@@ -29,7 +35,8 @@ type Service interface {
|
||||
GetPlaybook(context.Context, string) (*model.PlaybookTemplateResponse, error)
|
||||
UpdatePlaybook(context.Context, string, *model.PlaybookTemplateForm) (*model.PlaybookTemplateResponse, error)
|
||||
DeletePlaybook(context.Context, string) error
|
||||
GetSettings(context.Context) (*model.Settings, error)
|
||||
GetSettings(context.Context) (*model.SettingsResponse, error)
|
||||
SaveSettings(context.Context, *model.Settings) (*model.SettingsResponse, error)
|
||||
GetStatistics(context.Context) (*model.Statistics, error)
|
||||
ListTasks(context.Context) ([]*model.TaskWithContext, error)
|
||||
ListTemplates(context.Context) ([]*model.TicketTemplateResponse, error)
|
||||
@@ -90,6 +97,12 @@ func NewServer(service Service, roleAuth func([]string) func(http.Handler) http.
|
||||
r.With(roleAuth([]string{"currentuser:read"})).Get("/currentuser", s.currentUserHandler)
|
||||
r.With(roleAuth([]string{"currentuserdata:read"})).Get("/currentuserdata", s.currentUserDataHandler)
|
||||
r.With(roleAuth([]string{"currentuserdata:write"})).Put("/currentuserdata", s.updateCurrentUserDataHandler)
|
||||
r.With(roleAuth([]string{"dashboard:read"})).Get("/dashboard/data", s.dashboardDataHandler)
|
||||
r.With(roleAuth([]string{"dashboard:read"})).Get("/dashboards", s.listDashboardsHandler)
|
||||
r.With(roleAuth([]string{"dashboard:write"})).Post("/dashboards", s.createDashboardHandler)
|
||||
r.With(roleAuth([]string{"dashboard:read"})).Get("/dashboards/{id}", s.getDashboardHandler)
|
||||
r.With(roleAuth([]string{"dashboard:write"})).Put("/dashboards/{id}", s.updateDashboardHandler)
|
||||
r.With(roleAuth([]string{"dashboard:write"})).Delete("/dashboards/{id}", s.deleteDashboardHandler)
|
||||
r.With(roleAuth([]string{"job:read"})).Get("/jobs", s.listJobsHandler)
|
||||
r.With(roleAuth([]string{"job:write"})).Post("/jobs", s.runJobHandler)
|
||||
r.With(roleAuth([]string{"job:read"})).Get("/jobs/{id}", s.getJobHandler)
|
||||
@@ -101,6 +114,7 @@ func NewServer(service Service, roleAuth func([]string) func(http.Handler) http.
|
||||
r.With(roleAuth([]string{"playbook:write"})).Put("/playbooks/{id}", s.updatePlaybookHandler)
|
||||
r.With(roleAuth([]string{"playbook:write"})).Delete("/playbooks/{id}", s.deletePlaybookHandler)
|
||||
r.With(roleAuth([]string{"settings:read"})).Get("/settings", s.getSettingsHandler)
|
||||
r.With(roleAuth([]string{"settings:write"})).Post("/settings", s.saveSettingsHandler)
|
||||
r.With(roleAuth([]string{"ticket:read"})).Get("/statistics", s.getStatisticsHandler)
|
||||
r.With(roleAuth([]string{"ticket:read"})).Get("/tasks", s.listTasksHandler)
|
||||
r.With(roleAuth([]string{"template:read"})).Get("/templates", s.listTemplatesHandler)
|
||||
@@ -245,6 +259,77 @@ func (s *server) updateCurrentUserDataHandler(w http.ResponseWriter, r *http.Req
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) dashboardDataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
aggregationP := r.URL.Query().Get("aggregation")
|
||||
|
||||
filterP := r.URL.Query().Get("filter")
|
||||
|
||||
result, err := s.service.DashboardData(r.Context(), aggregationP, &filterP)
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) listDashboardsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := s.service.ListDashboards(r.Context())
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) createDashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
JSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if validateSchema(body, model.DashboardSchema, w) {
|
||||
return
|
||||
}
|
||||
|
||||
var templateP *model.Dashboard
|
||||
if err := parseBody(body, &templateP); err != nil {
|
||||
JSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := s.service.CreateDashboard(r.Context(), templateP)
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) getDashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
idP := chi.URLParam(r, "id")
|
||||
|
||||
result, err := s.service.GetDashboard(r.Context(), idP)
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) updateDashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
idP := chi.URLParam(r, "id")
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
JSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if validateSchema(body, model.DashboardSchema, w) {
|
||||
return
|
||||
}
|
||||
|
||||
var dashboardP *model.Dashboard
|
||||
if err := parseBody(body, &dashboardP); err != nil {
|
||||
JSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := s.service.UpdateDashboard(r.Context(), idP, dashboardP)
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) deleteDashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
idP := chi.URLParam(r, "id")
|
||||
|
||||
response(w, nil, s.service.DeleteDashboard(r.Context(), idP))
|
||||
}
|
||||
|
||||
func (s *server) listJobsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := s.service.ListJobs(r.Context())
|
||||
response(w, result, err)
|
||||
@@ -375,6 +460,27 @@ func (s *server) getSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
JSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if validateSchema(body, model.SettingsSchema, w) {
|
||||
return
|
||||
}
|
||||
|
||||
var settingsP *model.Settings
|
||||
if err := parseBody(body, &settingsP); err != nil {
|
||||
JSONError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := s.service.SaveSettings(r.Context(), settingsP)
|
||||
response(w, result, err)
|
||||
}
|
||||
|
||||
func (s *server) getStatisticsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
result, err := s.service.GetStatistics(r.Context())
|
||||
response(w, result, err)
|
||||
|
||||
@@ -5,8 +5,23 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func VueStatic(fsys fs.FS) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := http.FileServer(http.FS(fsys))
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/static/") {
|
||||
handler = http.StripPrefix("/static/", handler)
|
||||
} else {
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func Static(fsys fs.FS) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
http.FileServer(http.FS(fsys)).ServeHTTP(w, r)
|
||||
|
||||
@@ -68,7 +68,7 @@ var Tests = []struct {
|
||||
Args: Args{Method: "Get", URL: "/currentuser"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}},
|
||||
Body: map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -90,6 +90,60 @@ var Tests = []struct {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "DashboardData",
|
||||
Args: Args{Method: "Get", URL: "/dashboard/data?aggregation=type&filter=status+%3D%3D+%22closed%22"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"alert": 2, "incident": 1},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "ListDashboards",
|
||||
Args: Args{Method: "Get", URL: "/dashboards"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: []interface{}{map[string]interface{}{"id": "simple", "name": "Simple", "widgets": []interface{}{map[string]interface{}{"aggregation": "owner", "filter": "status == \"open\"", "name": "open_tickets_per_user", "type": "bar", "width": 4}, map[string]interface{}{"aggregation": "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))", "name": "tickets_per_week", "type": "line", "width": 8}}}},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "CreateDashboard",
|
||||
Args: Args{Method: "Post", URL: "/dashboards", Data: map[string]interface{}{"name": "My Dashboard", "widgets": []interface{}{}}},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"id": "my-dashboard", "name": "My Dashboard", "widgets": []interface{}{}},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "GetDashboard",
|
||||
Args: Args{Method: "Get", URL: "/dashboards/simple"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"id": "simple", "name": "Simple", "widgets": []interface{}{map[string]interface{}{"aggregation": "owner", "filter": "status == \"open\"", "name": "open_tickets_per_user", "type": "bar", "width": 4}, map[string]interface{}{"aggregation": "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))", "name": "tickets_per_week", "type": "line", "width": 8}}},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "UpdateDashboard",
|
||||
Args: Args{Method: "Put", URL: "/dashboards/simple", Data: map[string]interface{}{"name": "Simple", "widgets": []interface{}{}}},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"id": "simple", "name": "Simple", "widgets": []interface{}{}},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "DeleteDashboard",
|
||||
Args: Args{Method: "Delete", URL: "/dashboards/simple"},
|
||||
Want: Want{
|
||||
Status: 204,
|
||||
Body: nil,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "ListJobs",
|
||||
Args: Args{Method: "Get", URL: "/jobs"},
|
||||
@@ -185,7 +239,16 @@ var Tests = []struct {
|
||||
Args: Args{Method: "Get", URL: "/settings"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"artifactStates": []interface{}{map[string]interface{}{"color": "info", "icon": "mdi-help-circle-outline", "id": "unknown", "name": "Unknown"}, map[string]interface{}{"color": "error", "icon": "mdi-skull", "id": "malicious", "name": "Malicious"}, map[string]interface{}{"color": "success", "icon": "mdi-check", "id": "clean", "name": "Clean"}}, "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}, "ticketTypes": []interface{}{map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-alert", "id": "alert", "name": "Alerts"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-radioactive", "id": "incident", "name": "Incidents"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-fingerprint", "id": "investigation", "name": "Forensic Investigations"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-target", "id": "hunt", "name": "Threat Hunting"}}, "tier": "community", "timeformat": "YYYY-MM-DDThh:mm:ss", "version": "0.0.0-test"},
|
||||
Body: map[string]interface{}{"artifactKinds": []interface{}{map[string]interface{}{"icon": "mdi-server", "id": "asset", "name": "Asset"}, map[string]interface{}{"icon": "mdi-bullseye", "id": "ioc", "name": "IOC"}}, "artifactStates": []interface{}{map[string]interface{}{"color": "info", "icon": "mdi-help-circle-outline", "id": "unknown", "name": "Unknown"}, map[string]interface{}{"color": "error", "icon": "mdi-skull", "id": "malicious", "name": "Malicious"}, map[string]interface{}{"color": "success", "icon": "mdi-check", "id": "clean", "name": "Clean"}}, "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}, "ticketTypes": []interface{}{map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-alert", "id": "alert", "name": "Alerts"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-radioactive", "id": "incident", "name": "Incidents"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-fingerprint", "id": "investigation", "name": "Forensic Investigations"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-target", "id": "hunt", "name": "Threat Hunting"}}, "tier": "community", "timeformat": "yyyy-MM-dd hh:mm:ss", "version": "0.0.0-test"},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "SaveSettings",
|
||||
Args: Args{Method: "Post", URL: "/settings", Data: map[string]interface{}{"artifactKinds": []interface{}{map[string]interface{}{"icon": "mdi-server", "id": "asset", "name": "Asset"}, map[string]interface{}{"icon": "mdi-bullseye", "id": "ioc", "name": "IOC"}}, "artifactStates": []interface{}{map[string]interface{}{"color": "info", "icon": "mdi-help-circle-outline", "id": "unknown", "name": "Unknown"}, map[string]interface{}{"color": "error", "icon": "mdi-skull", "id": "malicious", "name": "Malicious"}, map[string]interface{}{"color": "success", "icon": "mdi-check", "id": "clean", "name": "Clean"}}, "timeformat": "yyyy-MM-dd hh:mm:ss"}},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"artifactKinds": []interface{}{map[string]interface{}{"icon": "mdi-server", "id": "asset", "name": "Asset"}, map[string]interface{}{"icon": "mdi-bullseye", "id": "ioc", "name": "IOC"}}, "artifactStates": []interface{}{map[string]interface{}{"color": "info", "icon": "mdi-help-circle-outline", "id": "unknown", "name": "Unknown"}, map[string]interface{}{"color": "error", "icon": "mdi-skull", "id": "malicious", "name": "Malicious"}, map[string]interface{}{"color": "success", "icon": "mdi-check", "id": "clean", "name": "Clean"}}, "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}, "ticketTypes": []interface{}{map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-alert", "id": "alert", "name": "Alerts"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-radioactive", "id": "incident", "name": "Incidents"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-fingerprint", "id": "investigation", "name": "Forensic Investigations"}, map[string]interface{}{"default_playbooks": []interface{}{}, "default_template": "default", "icon": "mdi-target", "id": "hunt", "name": "Threat Hunting"}}, "tier": "community", "timeformat": "yyyy-MM-dd hh:mm:ss", "version": "0.0.0-test"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -545,7 +608,7 @@ var Tests = []struct {
|
||||
Args: Args{Method: "Get", URL: "/users"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: []interface{}{map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}}, map[string]interface{}{"apikey": true, "blocked": false, "id": "script", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}}},
|
||||
Body: []interface{}{map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}}, map[string]interface{}{"apikey": true, "blocked": false, "id": "script", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}}},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -554,7 +617,7 @@ var Tests = []struct {
|
||||
Args: Args{Method: "Post", URL: "/users", Data: map[string]interface{}{"apikey": true, "blocked": false, "id": "syncscript", "roles": []interface{}{"analyst"}}},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"blocked": false, "id": "syncscript", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read"}, "secret": "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"},
|
||||
Body: map[string]interface{}{"blocked": false, "id": "syncscript", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read"}, "secret": "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -563,7 +626,7 @@ var Tests = []struct {
|
||||
Args: Args{Method: "Get", URL: "/users/script"},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"apikey": true, "blocked": false, "id": "script", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}},
|
||||
Body: map[string]interface{}{"apikey": true, "blocked": false, "id": "script", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -572,7 +635,7 @@ var Tests = []struct {
|
||||
Args: Args{Method: "Put", URL: "/users/bob", Data: map[string]interface{}{"apikey": false, "blocked": false, "id": "syncscript", "roles": []interface{}{"analyst", "admin"}}},
|
||||
Want: Want{
|
||||
Status: 200,
|
||||
Body: map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}},
|
||||
Body: map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
"apikey" : false,
|
||||
"blocked" : false,
|
||||
"id" : "bob",
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -307,6 +307,257 @@
|
||||
"x-codegen-request-body-name" : "userdata"
|
||||
}
|
||||
},
|
||||
"/dashboard/data" : {
|
||||
"get" : {
|
||||
"operationId" : "dashboardData",
|
||||
"parameters" : [ {
|
||||
"description" : "Aggregation",
|
||||
"example" : "type",
|
||||
"in" : "query",
|
||||
"name" : "aggregation",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
}, {
|
||||
"description" : "Filter",
|
||||
"example" : "status == \"closed\"",
|
||||
"in" : "query",
|
||||
"name" : "filter",
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"type" : "object"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"alert" : 2,
|
||||
"incident" : 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:read" ]
|
||||
} ],
|
||||
"summary" : "Get widget data",
|
||||
"tags" : [ "dashboards" ]
|
||||
}
|
||||
},
|
||||
"/dashboards" : {
|
||||
"get" : {
|
||||
"operationId" : "listDashboards",
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
},
|
||||
"type" : "array"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : [ {
|
||||
"id" : "simple",
|
||||
"name" : "Simple",
|
||||
"widgets" : [ {
|
||||
"aggregation" : "owner",
|
||||
"filter" : "status == \"open\"",
|
||||
"name" : "open_tickets_per_user",
|
||||
"type" : "bar",
|
||||
"width" : 4
|
||||
}, {
|
||||
"aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))",
|
||||
"name" : "tickets_per_week",
|
||||
"type" : "line",
|
||||
"width" : 8
|
||||
} ]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:read" ]
|
||||
} ],
|
||||
"summary" : "List dashboards",
|
||||
"tags" : [ "dashboards" ]
|
||||
},
|
||||
"post" : {
|
||||
"operationId" : "createDashboard",
|
||||
"requestBody" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Dashboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "New template",
|
||||
"required" : true
|
||||
},
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"id" : "my-dashboard",
|
||||
"name" : "My Dashboard",
|
||||
"widgets" : [ ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:write" ]
|
||||
} ],
|
||||
"summary" : "Create a new dashboard",
|
||||
"tags" : [ "dashboards" ],
|
||||
"x-codegen-request-body-name" : "template"
|
||||
}
|
||||
},
|
||||
"/dashboards/{id}" : {
|
||||
"delete" : {
|
||||
"operationId" : "deleteDashboard",
|
||||
"parameters" : [ {
|
||||
"description" : "Dashboard ID",
|
||||
"example" : "simple",
|
||||
"in" : "path",
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"responses" : {
|
||||
"204" : {
|
||||
"content" : { },
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:write" ]
|
||||
} ],
|
||||
"summary" : "Delete a dashboard",
|
||||
"tags" : [ "dashboards" ]
|
||||
},
|
||||
"get" : {
|
||||
"operationId" : "getDashboard",
|
||||
"parameters" : [ {
|
||||
"description" : "Dashboard ID",
|
||||
"example" : "simple",
|
||||
"in" : "path",
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"id" : "simple",
|
||||
"name" : "Simple",
|
||||
"widgets" : [ {
|
||||
"aggregation" : "owner",
|
||||
"filter" : "status == \"open\"",
|
||||
"name" : "open_tickets_per_user",
|
||||
"type" : "bar",
|
||||
"width" : 4
|
||||
}, {
|
||||
"aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))",
|
||||
"name" : "tickets_per_week",
|
||||
"type" : "line",
|
||||
"width" : 8
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:read" ]
|
||||
} ],
|
||||
"summary" : "Get a single dashboard",
|
||||
"tags" : [ "dashboards" ]
|
||||
},
|
||||
"put" : {
|
||||
"operationId" : "updateDashboard",
|
||||
"parameters" : [ {
|
||||
"description" : "Dashboard ID",
|
||||
"example" : "simple",
|
||||
"in" : "path",
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"requestBody" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Dashboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "Dashboard object that needs to be added",
|
||||
"required" : true
|
||||
},
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"id" : "simple",
|
||||
"name" : "Simple",
|
||||
"widgets" : [ ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:write" ]
|
||||
} ],
|
||||
"summary" : "Update an existing dashboard",
|
||||
"tags" : [ "dashboards" ],
|
||||
"x-codegen-request-body-name" : "dashboard"
|
||||
}
|
||||
},
|
||||
"/graph/{col}/{id}" : {
|
||||
"get" : {
|
||||
"operationId" : "graph",
|
||||
@@ -1143,11 +1394,20 @@
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Settings"
|
||||
"$ref" : "#/components/schemas/SettingsResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"artifactKinds" : [ {
|
||||
"icon" : "mdi-server",
|
||||
"id" : "asset",
|
||||
"name" : "Asset"
|
||||
}, {
|
||||
"icon" : "mdi-bullseye",
|
||||
"id" : "ioc",
|
||||
"name" : "IOC"
|
||||
} ],
|
||||
"artifactStates" : [ {
|
||||
"color" : "info",
|
||||
"icon" : "mdi-help-circle-outline",
|
||||
@@ -1164,7 +1424,7 @@
|
||||
"id" : "clean",
|
||||
"name" : "Clean"
|
||||
} ],
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ],
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ],
|
||||
"ticketTypes" : [ {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
@@ -1191,7 +1451,7 @@
|
||||
"name" : "Threat Hunting"
|
||||
} ],
|
||||
"tier" : "community",
|
||||
"timeformat" : "YYYY-MM-DDThh:mm:ss",
|
||||
"timeformat" : "yyyy-MM-dd hh:mm:ss",
|
||||
"version" : "0.0.0-test"
|
||||
}
|
||||
}
|
||||
@@ -1204,6 +1464,96 @@
|
||||
} ],
|
||||
"summary" : "Get settings",
|
||||
"tags" : [ "settings" ]
|
||||
},
|
||||
"post" : {
|
||||
"operationId" : "saveSettings",
|
||||
"requestBody" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Settings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "Save settings",
|
||||
"required" : true
|
||||
},
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/SettingsResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"artifactKinds" : [ {
|
||||
"icon" : "mdi-server",
|
||||
"id" : "asset",
|
||||
"name" : "Asset"
|
||||
}, {
|
||||
"icon" : "mdi-bullseye",
|
||||
"id" : "ioc",
|
||||
"name" : "IOC"
|
||||
} ],
|
||||
"artifactStates" : [ {
|
||||
"color" : "info",
|
||||
"icon" : "mdi-help-circle-outline",
|
||||
"id" : "unknown",
|
||||
"name" : "Unknown"
|
||||
}, {
|
||||
"color" : "error",
|
||||
"icon" : "mdi-skull",
|
||||
"id" : "malicious",
|
||||
"name" : "Malicious"
|
||||
}, {
|
||||
"color" : "success",
|
||||
"icon" : "mdi-check",
|
||||
"id" : "clean",
|
||||
"name" : "Clean"
|
||||
} ],
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ],
|
||||
"ticketTypes" : [ {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-alert",
|
||||
"id" : "alert",
|
||||
"name" : "Alerts"
|
||||
}, {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-radioactive",
|
||||
"id" : "incident",
|
||||
"name" : "Incidents"
|
||||
}, {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-fingerprint",
|
||||
"id" : "investigation",
|
||||
"name" : "Forensic Investigations"
|
||||
}, {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-target",
|
||||
"id" : "hunt",
|
||||
"name" : "Threat Hunting"
|
||||
} ],
|
||||
"tier" : "community",
|
||||
"timeformat" : "yyyy-MM-dd hh:mm:ss",
|
||||
"version" : "0.0.0-test"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "settings:write" ]
|
||||
} ],
|
||||
"summary" : "Save settings",
|
||||
"tags" : [ "settings" ],
|
||||
"x-codegen-request-body-name" : "settings"
|
||||
}
|
||||
},
|
||||
"/statistics" : {
|
||||
@@ -4993,12 +5343,12 @@
|
||||
"apikey" : false,
|
||||
"blocked" : false,
|
||||
"id" : "bob",
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}, {
|
||||
"apikey" : true,
|
||||
"blocked" : false,
|
||||
"id" : "script",
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@@ -5036,7 +5386,7 @@
|
||||
"example" : {
|
||||
"blocked" : false,
|
||||
"id" : "syncscript",
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ],
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ],
|
||||
"secret" : "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"
|
||||
}
|
||||
}
|
||||
@@ -5102,7 +5452,7 @@
|
||||
"apikey" : true,
|
||||
"blocked" : false,
|
||||
"id" : "script",
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5151,7 +5501,7 @@
|
||||
"apikey" : false,
|
||||
"blocked" : false,
|
||||
"id" : "bob",
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5177,6 +5527,9 @@
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"kind" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"example" : "2.2.2.2",
|
||||
"type" : "string"
|
||||
@@ -5329,6 +5682,39 @@
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Dashboard" : {
|
||||
"properties" : {
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"widgets" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Widget"
|
||||
},
|
||||
"type" : "array"
|
||||
}
|
||||
},
|
||||
"required" : [ "name", "widgets" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"DashboardResponse" : {
|
||||
"properties" : {
|
||||
"id" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"widgets" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Widget"
|
||||
},
|
||||
"type" : "array"
|
||||
}
|
||||
},
|
||||
"required" : [ "id", "name", "widgets" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"Enrichment" : {
|
||||
"properties" : {
|
||||
"created" : {
|
||||
@@ -5793,6 +6179,37 @@
|
||||
},
|
||||
"Settings" : {
|
||||
"properties" : {
|
||||
"artifactKinds" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
},
|
||||
"title" : "Artifact Kinds",
|
||||
"type" : "array"
|
||||
},
|
||||
"artifactStates" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
},
|
||||
"title" : "Artifact States",
|
||||
"type" : "array"
|
||||
},
|
||||
"timeformat" : {
|
||||
"title" : "Time Format",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "artifactKinds", "artifactStates", "timeformat" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"SettingsResponse" : {
|
||||
"properties" : {
|
||||
"artifactKinds" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
},
|
||||
"title" : "Artifact Kinds",
|
||||
"type" : "array"
|
||||
},
|
||||
"artifactStates" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
@@ -5828,7 +6245,7 @@
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "artifactStates", "ticketTypes", "tier", "timeformat", "version" ],
|
||||
"required" : [ "artifactKinds", "artifactStates", "ticketTypes", "tier", "timeformat", "version" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"Statistics" : {
|
||||
@@ -6762,6 +7179,30 @@
|
||||
},
|
||||
"required" : [ "apikey", "blocked", "id", "roles" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"Widget" : {
|
||||
"properties" : {
|
||||
"aggregation" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"filter" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"type" : {
|
||||
"enum" : [ "bar", "line", "pie" ],
|
||||
"type" : "string"
|
||||
},
|
||||
"width" : {
|
||||
"maximum" : 12,
|
||||
"minimum" : 1,
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"required" : [ "aggregation", "name", "type", "width" ],
|
||||
"type" : "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,6 +8,8 @@ definitions:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/Enrichment'
|
||||
type: object
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
example: 2.2.2.2
|
||||
type: string
|
||||
@@ -139,6 +141,33 @@ definitions:
|
||||
ticket:
|
||||
$ref: '#/definitions/TicketResponse'
|
||||
type: object
|
||||
Dashboard:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
widgets:
|
||||
items:
|
||||
$ref: '#/definitions/Widget'
|
||||
type: array
|
||||
required:
|
||||
- name
|
||||
- widgets
|
||||
type: object
|
||||
DashboardResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
widgets:
|
||||
items:
|
||||
$ref: '#/definitions/Widget'
|
||||
type: array
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- widgets
|
||||
type: object
|
||||
Enrichment:
|
||||
properties:
|
||||
created:
|
||||
@@ -501,6 +530,31 @@ definitions:
|
||||
type: object
|
||||
Settings:
|
||||
properties:
|
||||
artifactKinds:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
title: Artifact Kinds
|
||||
type: array
|
||||
artifactStates:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
title: Artifact States
|
||||
type: array
|
||||
timeformat:
|
||||
title: Time Format
|
||||
type: string
|
||||
required:
|
||||
- timeformat
|
||||
- artifactKinds
|
||||
- artifactStates
|
||||
type: object
|
||||
SettingsResponse:
|
||||
properties:
|
||||
artifactKinds:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
title: Artifact Kinds
|
||||
type: array
|
||||
artifactStates:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
@@ -533,6 +587,7 @@ definitions:
|
||||
- tier
|
||||
- timeformat
|
||||
- ticketTypes
|
||||
- artifactKinds
|
||||
- artifactStates
|
||||
type: object
|
||||
Statistics:
|
||||
@@ -1307,6 +1362,30 @@ definitions:
|
||||
- roles
|
||||
- apikey
|
||||
type: object
|
||||
Widget:
|
||||
properties:
|
||||
aggregation:
|
||||
type: string
|
||||
filter:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
enum:
|
||||
- bar
|
||||
- line
|
||||
- pie
|
||||
type: string
|
||||
width:
|
||||
maximum: 12
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- aggregation
|
||||
- width
|
||||
type: object
|
||||
host: .
|
||||
info:
|
||||
description: API for the catalyst incident response platform.
|
||||
@@ -1548,10 +1627,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -1560,6 +1641,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -1630,6 +1712,183 @@ paths:
|
||||
summary: Update current user data
|
||||
tags:
|
||||
- userdata
|
||||
/dashboard/data:
|
||||
get:
|
||||
operationId: dashboardData
|
||||
parameters:
|
||||
- description: Aggregation
|
||||
in: query
|
||||
name: aggregation
|
||||
required: true
|
||||
type: string
|
||||
x-example: type
|
||||
- description: Filter
|
||||
in: query
|
||||
name: filter
|
||||
type: string
|
||||
x-example: status == "closed"
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
alert: 2
|
||||
incident: 1
|
||||
schema:
|
||||
type: object
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:read
|
||||
summary: Get widget data
|
||||
tags:
|
||||
- dashboards
|
||||
/dashboards:
|
||||
get:
|
||||
operationId: listDashboards
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
- id: simple
|
||||
name: Simple
|
||||
widgets:
|
||||
- aggregation: owner
|
||||
filter: status == "open"
|
||||
name: open_tickets_per_user
|
||||
type: bar
|
||||
width: 4
|
||||
- aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created)
|
||||
< 10 ? "0" : "", DATE_ISOWEEK(created))'
|
||||
name: tickets_per_week
|
||||
type: line
|
||||
width: 8
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
type: array
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:read
|
||||
summary: List dashboards
|
||||
tags:
|
||||
- dashboards
|
||||
post:
|
||||
operationId: createDashboard
|
||||
parameters:
|
||||
- description: New template
|
||||
in: body
|
||||
name: template
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Dashboard'
|
||||
x-example:
|
||||
name: My Dashboard
|
||||
widgets: []
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
id: my-dashboard
|
||||
name: My Dashboard
|
||||
widgets: []
|
||||
schema:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:write
|
||||
summary: Create a new dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
/dashboards/{id}:
|
||||
delete:
|
||||
operationId: deleteDashboard
|
||||
parameters:
|
||||
- description: Dashboard ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
x-example: simple
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:write
|
||||
summary: Delete a dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
get:
|
||||
operationId: getDashboard
|
||||
parameters:
|
||||
- description: Dashboard ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
x-example: simple
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
id: simple
|
||||
name: Simple
|
||||
widgets:
|
||||
- aggregation: owner
|
||||
filter: status == "open"
|
||||
name: open_tickets_per_user
|
||||
type: bar
|
||||
width: 4
|
||||
- aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created)
|
||||
< 10 ? "0" : "", DATE_ISOWEEK(created))'
|
||||
name: tickets_per_week
|
||||
type: line
|
||||
width: 8
|
||||
schema:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:read
|
||||
summary: Get a single dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
put:
|
||||
operationId: updateDashboard
|
||||
parameters:
|
||||
- description: Dashboard ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
x-example: simple
|
||||
- description: Dashboard object that needs to be added
|
||||
in: body
|
||||
name: dashboard
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Dashboard'
|
||||
x-example:
|
||||
name: Simple
|
||||
widgets: []
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
id: simple
|
||||
name: Simple
|
||||
widgets: []
|
||||
schema:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:write
|
||||
summary: Update an existing dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
/graph/{col}/{id}:
|
||||
get:
|
||||
operationId: graph
|
||||
@@ -2549,6 +2808,13 @@ paths:
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
artifactKinds:
|
||||
- icon: mdi-server
|
||||
id: asset
|
||||
name: Asset
|
||||
- icon: mdi-bullseye
|
||||
id: ioc
|
||||
name: IOC
|
||||
artifactStates:
|
||||
- color: info
|
||||
icon: mdi-help-circle-outline
|
||||
@@ -2565,10 +2831,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -2577,6 +2845,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -2614,16 +2883,137 @@ paths:
|
||||
id: hunt
|
||||
name: Threat Hunting
|
||||
tier: community
|
||||
timeformat: YYYY-MM-DDThh:mm:ss
|
||||
timeformat: yyyy-MM-dd hh:mm:ss
|
||||
version: 0.0.0-test
|
||||
schema:
|
||||
$ref: '#/definitions/Settings'
|
||||
$ref: '#/definitions/SettingsResponse'
|
||||
security:
|
||||
- roles:
|
||||
- settings:read
|
||||
summary: Get settings
|
||||
tags:
|
||||
- settings
|
||||
post:
|
||||
operationId: saveSettings
|
||||
parameters:
|
||||
- description: Save settings
|
||||
in: body
|
||||
name: settings
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Settings'
|
||||
x-example:
|
||||
artifactKinds:
|
||||
- icon: mdi-server
|
||||
id: asset
|
||||
name: Asset
|
||||
- icon: mdi-bullseye
|
||||
id: ioc
|
||||
name: IOC
|
||||
artifactStates:
|
||||
- color: info
|
||||
icon: mdi-help-circle-outline
|
||||
id: unknown
|
||||
name: Unknown
|
||||
- color: error
|
||||
icon: mdi-skull
|
||||
id: malicious
|
||||
name: Malicious
|
||||
- color: success
|
||||
icon: mdi-check
|
||||
id: clean
|
||||
name: Clean
|
||||
timeformat: yyyy-MM-dd hh:mm:ss
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
artifactKinds:
|
||||
- icon: mdi-server
|
||||
id: asset
|
||||
name: Asset
|
||||
- icon: mdi-bullseye
|
||||
id: ioc
|
||||
name: IOC
|
||||
artifactStates:
|
||||
- color: info
|
||||
icon: mdi-help-circle-outline
|
||||
id: unknown
|
||||
name: Unknown
|
||||
- color: error
|
||||
icon: mdi-skull
|
||||
id: malicious
|
||||
name: Malicious
|
||||
- color: success
|
||||
icon: mdi-check
|
||||
id: clean
|
||||
name: Clean
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
- admin:userdata:write
|
||||
- analyst:automation:read
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
- analyst:rule:read
|
||||
- analyst:settings:read
|
||||
- analyst:template:read
|
||||
- analyst:ticket:read
|
||||
- analyst:ticket:write
|
||||
- analyst:tickettype:read
|
||||
- analyst:user:read
|
||||
- engineer:automation:write
|
||||
- engineer:playbook:write
|
||||
- engineer:rule:write
|
||||
- engineer:template:write
|
||||
- engineer:tickettype:write
|
||||
ticketTypes:
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-alert
|
||||
id: alert
|
||||
name: Alerts
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-radioactive
|
||||
id: incident
|
||||
name: Incidents
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-fingerprint
|
||||
id: investigation
|
||||
name: Forensic Investigations
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-target
|
||||
id: hunt
|
||||
name: Threat Hunting
|
||||
tier: community
|
||||
timeformat: yyyy-MM-dd hh:mm:ss
|
||||
version: 0.0.0-test
|
||||
schema:
|
||||
$ref: '#/definitions/SettingsResponse'
|
||||
security:
|
||||
- roles:
|
||||
- settings:write
|
||||
summary: Save settings
|
||||
tags:
|
||||
- settings
|
||||
/statistics:
|
||||
get:
|
||||
operationId: getStatistics
|
||||
@@ -6966,10 +7356,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -6978,6 +7370,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -7001,6 +7394,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -7053,6 +7447,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -7113,6 +7508,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -7169,10 +7565,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -7181,6 +7579,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
"apikey" : false,
|
||||
"blocked" : false,
|
||||
"id" : "bob",
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -307,6 +307,257 @@
|
||||
"x-codegen-request-body-name" : "userdata"
|
||||
}
|
||||
},
|
||||
"/dashboard/data" : {
|
||||
"get" : {
|
||||
"operationId" : "dashboardData",
|
||||
"parameters" : [ {
|
||||
"description" : "Aggregation",
|
||||
"example" : "type",
|
||||
"in" : "query",
|
||||
"name" : "aggregation",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
}, {
|
||||
"description" : "Filter",
|
||||
"example" : "status == \"closed\"",
|
||||
"in" : "query",
|
||||
"name" : "filter",
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"type" : "object"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"alert" : 2,
|
||||
"incident" : 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:read" ]
|
||||
} ],
|
||||
"summary" : "Get widget data",
|
||||
"tags" : [ "dashboards" ]
|
||||
}
|
||||
},
|
||||
"/dashboards" : {
|
||||
"get" : {
|
||||
"operationId" : "listDashboards",
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
},
|
||||
"type" : "array"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : [ {
|
||||
"id" : "simple",
|
||||
"name" : "Simple",
|
||||
"widgets" : [ {
|
||||
"aggregation" : "owner",
|
||||
"filter" : "status == \"open\"",
|
||||
"name" : "open_tickets_per_user",
|
||||
"type" : "bar",
|
||||
"width" : 4
|
||||
}, {
|
||||
"aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))",
|
||||
"name" : "tickets_per_week",
|
||||
"type" : "line",
|
||||
"width" : 8
|
||||
} ]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:read" ]
|
||||
} ],
|
||||
"summary" : "List dashboards",
|
||||
"tags" : [ "dashboards" ]
|
||||
},
|
||||
"post" : {
|
||||
"operationId" : "createDashboard",
|
||||
"requestBody" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Dashboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "New template",
|
||||
"required" : true
|
||||
},
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"id" : "my-dashboard",
|
||||
"name" : "My Dashboard",
|
||||
"widgets" : [ ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:write" ]
|
||||
} ],
|
||||
"summary" : "Create a new dashboard",
|
||||
"tags" : [ "dashboards" ],
|
||||
"x-codegen-request-body-name" : "template"
|
||||
}
|
||||
},
|
||||
"/dashboards/{id}" : {
|
||||
"delete" : {
|
||||
"operationId" : "deleteDashboard",
|
||||
"parameters" : [ {
|
||||
"description" : "Dashboard ID",
|
||||
"example" : "simple",
|
||||
"in" : "path",
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"responses" : {
|
||||
"204" : {
|
||||
"content" : { },
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:write" ]
|
||||
} ],
|
||||
"summary" : "Delete a dashboard",
|
||||
"tags" : [ "dashboards" ]
|
||||
},
|
||||
"get" : {
|
||||
"operationId" : "getDashboard",
|
||||
"parameters" : [ {
|
||||
"description" : "Dashboard ID",
|
||||
"example" : "simple",
|
||||
"in" : "path",
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"id" : "simple",
|
||||
"name" : "Simple",
|
||||
"widgets" : [ {
|
||||
"aggregation" : "owner",
|
||||
"filter" : "status == \"open\"",
|
||||
"name" : "open_tickets_per_user",
|
||||
"type" : "bar",
|
||||
"width" : 4
|
||||
}, {
|
||||
"aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))",
|
||||
"name" : "tickets_per_week",
|
||||
"type" : "line",
|
||||
"width" : 8
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:read" ]
|
||||
} ],
|
||||
"summary" : "Get a single dashboard",
|
||||
"tags" : [ "dashboards" ]
|
||||
},
|
||||
"put" : {
|
||||
"operationId" : "updateDashboard",
|
||||
"parameters" : [ {
|
||||
"description" : "Dashboard ID",
|
||||
"example" : "simple",
|
||||
"in" : "path",
|
||||
"name" : "id",
|
||||
"required" : true,
|
||||
"schema" : {
|
||||
"type" : "string"
|
||||
}
|
||||
} ],
|
||||
"requestBody" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Dashboard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "Dashboard object that needs to be added",
|
||||
"required" : true
|
||||
},
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/DashboardResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"id" : "simple",
|
||||
"name" : "Simple",
|
||||
"widgets" : [ ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "dashboard:write" ]
|
||||
} ],
|
||||
"summary" : "Update an existing dashboard",
|
||||
"tags" : [ "dashboards" ],
|
||||
"x-codegen-request-body-name" : "dashboard"
|
||||
}
|
||||
},
|
||||
"/jobs" : {
|
||||
"get" : {
|
||||
"operationId" : "listJobs",
|
||||
@@ -713,11 +964,20 @@
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Settings"
|
||||
"$ref" : "#/components/schemas/SettingsResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"artifactKinds" : [ {
|
||||
"icon" : "mdi-server",
|
||||
"id" : "asset",
|
||||
"name" : "Asset"
|
||||
}, {
|
||||
"icon" : "mdi-bullseye",
|
||||
"id" : "ioc",
|
||||
"name" : "IOC"
|
||||
} ],
|
||||
"artifactStates" : [ {
|
||||
"color" : "info",
|
||||
"icon" : "mdi-help-circle-outline",
|
||||
@@ -734,7 +994,7 @@
|
||||
"id" : "clean",
|
||||
"name" : "Clean"
|
||||
} ],
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ],
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ],
|
||||
"ticketTypes" : [ {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
@@ -761,7 +1021,7 @@
|
||||
"name" : "Threat Hunting"
|
||||
} ],
|
||||
"tier" : "community",
|
||||
"timeformat" : "YYYY-MM-DDThh:mm:ss",
|
||||
"timeformat" : "yyyy-MM-dd hh:mm:ss",
|
||||
"version" : "0.0.0-test"
|
||||
}
|
||||
}
|
||||
@@ -774,6 +1034,96 @@
|
||||
} ],
|
||||
"summary" : "Get settings",
|
||||
"tags" : [ "settings" ]
|
||||
},
|
||||
"post" : {
|
||||
"operationId" : "saveSettings",
|
||||
"requestBody" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Settings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "Save settings",
|
||||
"required" : true
|
||||
},
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/SettingsResponse"
|
||||
}
|
||||
},
|
||||
"test" : {
|
||||
"example" : {
|
||||
"artifactKinds" : [ {
|
||||
"icon" : "mdi-server",
|
||||
"id" : "asset",
|
||||
"name" : "Asset"
|
||||
}, {
|
||||
"icon" : "mdi-bullseye",
|
||||
"id" : "ioc",
|
||||
"name" : "IOC"
|
||||
} ],
|
||||
"artifactStates" : [ {
|
||||
"color" : "info",
|
||||
"icon" : "mdi-help-circle-outline",
|
||||
"id" : "unknown",
|
||||
"name" : "Unknown"
|
||||
}, {
|
||||
"color" : "error",
|
||||
"icon" : "mdi-skull",
|
||||
"id" : "malicious",
|
||||
"name" : "Malicious"
|
||||
}, {
|
||||
"color" : "success",
|
||||
"icon" : "mdi-check",
|
||||
"id" : "clean",
|
||||
"name" : "Clean"
|
||||
} ],
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ],
|
||||
"ticketTypes" : [ {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-alert",
|
||||
"id" : "alert",
|
||||
"name" : "Alerts"
|
||||
}, {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-radioactive",
|
||||
"id" : "incident",
|
||||
"name" : "Incidents"
|
||||
}, {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-fingerprint",
|
||||
"id" : "investigation",
|
||||
"name" : "Forensic Investigations"
|
||||
}, {
|
||||
"default_playbooks" : [ ],
|
||||
"default_template" : "default",
|
||||
"icon" : "mdi-target",
|
||||
"id" : "hunt",
|
||||
"name" : "Threat Hunting"
|
||||
} ],
|
||||
"tier" : "community",
|
||||
"timeformat" : "yyyy-MM-dd hh:mm:ss",
|
||||
"version" : "0.0.0-test"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "successful operation"
|
||||
}
|
||||
},
|
||||
"security" : [ {
|
||||
"roles" : [ "settings:write" ]
|
||||
} ],
|
||||
"summary" : "Save settings",
|
||||
"tags" : [ "settings" ],
|
||||
"x-codegen-request-body-name" : "settings"
|
||||
}
|
||||
},
|
||||
"/statistics" : {
|
||||
@@ -4563,12 +4913,12 @@
|
||||
"apikey" : false,
|
||||
"blocked" : false,
|
||||
"id" : "bob",
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}, {
|
||||
"apikey" : true,
|
||||
"blocked" : false,
|
||||
"id" : "script",
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@@ -4606,7 +4956,7 @@
|
||||
"example" : {
|
||||
"blocked" : false,
|
||||
"id" : "syncscript",
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ],
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ],
|
||||
"secret" : "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"
|
||||
}
|
||||
}
|
||||
@@ -4672,7 +5022,7 @@
|
||||
"apikey" : true,
|
||||
"blocked" : false,
|
||||
"id" : "script",
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4721,7 +5071,7 @@
|
||||
"apikey" : false,
|
||||
"blocked" : false,
|
||||
"id" : "bob",
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
"roles" : [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4747,6 +5097,9 @@
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"kind" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"example" : "2.2.2.2",
|
||||
"type" : "string"
|
||||
@@ -4899,6 +5252,39 @@
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Dashboard" : {
|
||||
"properties" : {
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"widgets" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Widget"
|
||||
},
|
||||
"type" : "array"
|
||||
}
|
||||
},
|
||||
"required" : [ "name", "widgets" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"DashboardResponse" : {
|
||||
"properties" : {
|
||||
"id" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"widgets" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Widget"
|
||||
},
|
||||
"type" : "array"
|
||||
}
|
||||
},
|
||||
"required" : [ "id", "name", "widgets" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"Enrichment" : {
|
||||
"properties" : {
|
||||
"created" : {
|
||||
@@ -5214,6 +5600,37 @@
|
||||
},
|
||||
"Settings" : {
|
||||
"properties" : {
|
||||
"artifactKinds" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
},
|
||||
"title" : "Artifact Kinds",
|
||||
"type" : "array"
|
||||
},
|
||||
"artifactStates" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
},
|
||||
"title" : "Artifact States",
|
||||
"type" : "array"
|
||||
},
|
||||
"timeformat" : {
|
||||
"title" : "Time Format",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "artifactKinds", "artifactStates", "timeformat" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"SettingsResponse" : {
|
||||
"properties" : {
|
||||
"artifactKinds" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
},
|
||||
"title" : "Artifact Kinds",
|
||||
"type" : "array"
|
||||
},
|
||||
"artifactStates" : {
|
||||
"items" : {
|
||||
"$ref" : "#/components/schemas/Type"
|
||||
@@ -5249,7 +5666,7 @@
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "artifactStates", "ticketTypes", "tier", "timeformat", "version" ],
|
||||
"required" : [ "artifactKinds", "artifactStates", "ticketTypes", "tier", "timeformat", "version" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"Statistics" : {
|
||||
@@ -6183,6 +6600,30 @@
|
||||
},
|
||||
"required" : [ "apikey", "blocked", "id", "roles" ],
|
||||
"type" : "object"
|
||||
},
|
||||
"Widget" : {
|
||||
"properties" : {
|
||||
"aggregation" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"filter" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"type" : {
|
||||
"enum" : [ "bar", "line", "pie" ],
|
||||
"type" : "string"
|
||||
},
|
||||
"width" : {
|
||||
"maximum" : 12,
|
||||
"minimum" : 1,
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"required" : [ "aggregation", "name", "type", "width" ],
|
||||
"type" : "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,6 +8,8 @@ definitions:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/Enrichment'
|
||||
type: object
|
||||
kind:
|
||||
type: string
|
||||
name:
|
||||
example: 2.2.2.2
|
||||
type: string
|
||||
@@ -139,6 +141,33 @@ definitions:
|
||||
ticket:
|
||||
$ref: '#/definitions/TicketResponse'
|
||||
type: object
|
||||
Dashboard:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
widgets:
|
||||
items:
|
||||
$ref: '#/definitions/Widget'
|
||||
type: array
|
||||
required:
|
||||
- name
|
||||
- widgets
|
||||
type: object
|
||||
DashboardResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
widgets:
|
||||
items:
|
||||
$ref: '#/definitions/Widget'
|
||||
type: array
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- widgets
|
||||
type: object
|
||||
Enrichment:
|
||||
properties:
|
||||
created:
|
||||
@@ -382,6 +411,31 @@ definitions:
|
||||
type: array
|
||||
Settings:
|
||||
properties:
|
||||
artifactKinds:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
title: Artifact Kinds
|
||||
type: array
|
||||
artifactStates:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
title: Artifact States
|
||||
type: array
|
||||
timeformat:
|
||||
title: Time Format
|
||||
type: string
|
||||
required:
|
||||
- timeformat
|
||||
- artifactKinds
|
||||
- artifactStates
|
||||
type: object
|
||||
SettingsResponse:
|
||||
properties:
|
||||
artifactKinds:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
title: Artifact Kinds
|
||||
type: array
|
||||
artifactStates:
|
||||
items:
|
||||
$ref: '#/definitions/Type'
|
||||
@@ -414,6 +468,7 @@ definitions:
|
||||
- tier
|
||||
- timeformat
|
||||
- ticketTypes
|
||||
- artifactKinds
|
||||
- artifactStates
|
||||
type: object
|
||||
Statistics:
|
||||
@@ -1188,6 +1243,30 @@ definitions:
|
||||
- roles
|
||||
- apikey
|
||||
type: object
|
||||
Widget:
|
||||
properties:
|
||||
aggregation:
|
||||
type: string
|
||||
filter:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
enum:
|
||||
- bar
|
||||
- line
|
||||
- pie
|
||||
type: string
|
||||
width:
|
||||
maximum: 12
|
||||
minimum: 1
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- aggregation
|
||||
- width
|
||||
type: object
|
||||
host: .
|
||||
info:
|
||||
description: API for the catalyst incident response platform.
|
||||
@@ -1429,10 +1508,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -1441,6 +1522,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -1511,6 +1593,183 @@ paths:
|
||||
summary: Update current user data
|
||||
tags:
|
||||
- userdata
|
||||
/dashboard/data:
|
||||
get:
|
||||
operationId: dashboardData
|
||||
parameters:
|
||||
- description: Aggregation
|
||||
in: query
|
||||
name: aggregation
|
||||
required: true
|
||||
type: string
|
||||
x-example: type
|
||||
- description: Filter
|
||||
in: query
|
||||
name: filter
|
||||
type: string
|
||||
x-example: status == "closed"
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
alert: 2
|
||||
incident: 1
|
||||
schema:
|
||||
type: object
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:read
|
||||
summary: Get widget data
|
||||
tags:
|
||||
- dashboards
|
||||
/dashboards:
|
||||
get:
|
||||
operationId: listDashboards
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
- id: simple
|
||||
name: Simple
|
||||
widgets:
|
||||
- aggregation: owner
|
||||
filter: status == "open"
|
||||
name: open_tickets_per_user
|
||||
type: bar
|
||||
width: 4
|
||||
- aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created)
|
||||
< 10 ? "0" : "", DATE_ISOWEEK(created))'
|
||||
name: tickets_per_week
|
||||
type: line
|
||||
width: 8
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
type: array
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:read
|
||||
summary: List dashboards
|
||||
tags:
|
||||
- dashboards
|
||||
post:
|
||||
operationId: createDashboard
|
||||
parameters:
|
||||
- description: New template
|
||||
in: body
|
||||
name: template
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Dashboard'
|
||||
x-example:
|
||||
name: My Dashboard
|
||||
widgets: []
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
id: my-dashboard
|
||||
name: My Dashboard
|
||||
widgets: []
|
||||
schema:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:write
|
||||
summary: Create a new dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
/dashboards/{id}:
|
||||
delete:
|
||||
operationId: deleteDashboard
|
||||
parameters:
|
||||
- description: Dashboard ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
x-example: simple
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:write
|
||||
summary: Delete a dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
get:
|
||||
operationId: getDashboard
|
||||
parameters:
|
||||
- description: Dashboard ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
x-example: simple
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
id: simple
|
||||
name: Simple
|
||||
widgets:
|
||||
- aggregation: owner
|
||||
filter: status == "open"
|
||||
name: open_tickets_per_user
|
||||
type: bar
|
||||
width: 4
|
||||
- aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created)
|
||||
< 10 ? "0" : "", DATE_ISOWEEK(created))'
|
||||
name: tickets_per_week
|
||||
type: line
|
||||
width: 8
|
||||
schema:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:read
|
||||
summary: Get a single dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
put:
|
||||
operationId: updateDashboard
|
||||
parameters:
|
||||
- description: Dashboard ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
x-example: simple
|
||||
- description: Dashboard object that needs to be added
|
||||
in: body
|
||||
name: dashboard
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Dashboard'
|
||||
x-example:
|
||||
name: Simple
|
||||
widgets: []
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
id: simple
|
||||
name: Simple
|
||||
widgets: []
|
||||
schema:
|
||||
$ref: '#/definitions/DashboardResponse'
|
||||
security:
|
||||
- roles:
|
||||
- dashboard:write
|
||||
summary: Update an existing dashboard
|
||||
tags:
|
||||
- dashboards
|
||||
/jobs:
|
||||
get:
|
||||
operationId: listJobs
|
||||
@@ -2137,6 +2396,13 @@ paths:
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
artifactKinds:
|
||||
- icon: mdi-server
|
||||
id: asset
|
||||
name: Asset
|
||||
- icon: mdi-bullseye
|
||||
id: ioc
|
||||
name: IOC
|
||||
artifactStates:
|
||||
- color: info
|
||||
icon: mdi-help-circle-outline
|
||||
@@ -2153,10 +2419,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -2165,6 +2433,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -2202,16 +2471,137 @@ paths:
|
||||
id: hunt
|
||||
name: Threat Hunting
|
||||
tier: community
|
||||
timeformat: YYYY-MM-DDThh:mm:ss
|
||||
timeformat: yyyy-MM-dd hh:mm:ss
|
||||
version: 0.0.0-test
|
||||
schema:
|
||||
$ref: '#/definitions/Settings'
|
||||
$ref: '#/definitions/SettingsResponse'
|
||||
security:
|
||||
- roles:
|
||||
- settings:read
|
||||
summary: Get settings
|
||||
tags:
|
||||
- settings
|
||||
post:
|
||||
operationId: saveSettings
|
||||
parameters:
|
||||
- description: Save settings
|
||||
in: body
|
||||
name: settings
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Settings'
|
||||
x-example:
|
||||
artifactKinds:
|
||||
- icon: mdi-server
|
||||
id: asset
|
||||
name: Asset
|
||||
- icon: mdi-bullseye
|
||||
id: ioc
|
||||
name: IOC
|
||||
artifactStates:
|
||||
- color: info
|
||||
icon: mdi-help-circle-outline
|
||||
id: unknown
|
||||
name: Unknown
|
||||
- color: error
|
||||
icon: mdi-skull
|
||||
id: malicious
|
||||
name: Malicious
|
||||
- color: success
|
||||
icon: mdi-check
|
||||
id: clean
|
||||
name: Clean
|
||||
timeformat: yyyy-MM-dd hh:mm:ss
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
examples:
|
||||
test:
|
||||
artifactKinds:
|
||||
- icon: mdi-server
|
||||
id: asset
|
||||
name: Asset
|
||||
- icon: mdi-bullseye
|
||||
id: ioc
|
||||
name: IOC
|
||||
artifactStates:
|
||||
- color: info
|
||||
icon: mdi-help-circle-outline
|
||||
id: unknown
|
||||
name: Unknown
|
||||
- color: error
|
||||
icon: mdi-skull
|
||||
id: malicious
|
||||
name: Malicious
|
||||
- color: success
|
||||
icon: mdi-check
|
||||
id: clean
|
||||
name: Clean
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
- admin:userdata:write
|
||||
- analyst:automation:read
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
- analyst:rule:read
|
||||
- analyst:settings:read
|
||||
- analyst:template:read
|
||||
- analyst:ticket:read
|
||||
- analyst:ticket:write
|
||||
- analyst:tickettype:read
|
||||
- analyst:user:read
|
||||
- engineer:automation:write
|
||||
- engineer:playbook:write
|
||||
- engineer:rule:write
|
||||
- engineer:template:write
|
||||
- engineer:tickettype:write
|
||||
ticketTypes:
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-alert
|
||||
id: alert
|
||||
name: Alerts
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-radioactive
|
||||
id: incident
|
||||
name: Incidents
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-fingerprint
|
||||
id: investigation
|
||||
name: Forensic Investigations
|
||||
- default_playbooks: []
|
||||
default_template: default
|
||||
icon: mdi-target
|
||||
id: hunt
|
||||
name: Threat Hunting
|
||||
tier: community
|
||||
timeformat: yyyy-MM-dd hh:mm:ss
|
||||
version: 0.0.0-test
|
||||
schema:
|
||||
$ref: '#/definitions/SettingsResponse'
|
||||
security:
|
||||
- roles:
|
||||
- settings:write
|
||||
summary: Save settings
|
||||
tags:
|
||||
- settings
|
||||
/statistics:
|
||||
get:
|
||||
operationId: getStatistics
|
||||
@@ -6554,10 +6944,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -6566,6 +6958,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -6589,6 +6982,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -6641,6 +7035,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -6701,6 +7096,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
@@ -6757,10 +7153,12 @@ paths:
|
||||
roles:
|
||||
- admin:backup:read
|
||||
- admin:backup:restore
|
||||
- admin:dashboard:write
|
||||
- admin:group:write
|
||||
- admin:job:read
|
||||
- admin:job:write
|
||||
- admin:log:read
|
||||
- admin:settings:write
|
||||
- admin:ticket:delete
|
||||
- admin:user:write
|
||||
- admin:userdata:read
|
||||
@@ -6769,6 +7167,7 @@ paths:
|
||||
- analyst:currentsettings:write
|
||||
- analyst:currentuser:read
|
||||
- analyst:currentuserdata:read
|
||||
- analyst:dashboard:read
|
||||
- analyst:file
|
||||
- analyst:group:read
|
||||
- analyst:playbook:read
|
||||
|
||||
@@ -16,6 +16,8 @@ var (
|
||||
CommentSchema = new(gojsonschema.Schema)
|
||||
CommentFormSchema = new(gojsonschema.Schema)
|
||||
ContextSchema = new(gojsonschema.Schema)
|
||||
DashboardSchema = new(gojsonschema.Schema)
|
||||
DashboardResponseSchema = new(gojsonschema.Schema)
|
||||
EnrichmentSchema = new(gojsonschema.Schema)
|
||||
EnrichmentFormSchema = new(gojsonschema.Schema)
|
||||
FileSchema = new(gojsonschema.Schema)
|
||||
@@ -35,6 +37,7 @@ var (
|
||||
ReferenceSchema = new(gojsonschema.Schema)
|
||||
ReferenceArraySchema = new(gojsonschema.Schema)
|
||||
SettingsSchema = new(gojsonschema.Schema)
|
||||
SettingsResponseSchema = new(gojsonschema.Schema)
|
||||
StatisticsSchema = new(gojsonschema.Schema)
|
||||
TaskSchema = new(gojsonschema.Schema)
|
||||
TaskOriginSchema = new(gojsonschema.Schema)
|
||||
@@ -59,11 +62,12 @@ var (
|
||||
UserDataResponseSchema = new(gojsonschema.Schema)
|
||||
UserFormSchema = new(gojsonschema.Schema)
|
||||
UserResponseSchema = new(gojsonschema.Schema)
|
||||
WidgetSchema = new(gojsonschema.Schema)
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := schemaLoader.AddSchemas(
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"enrichments":{"type":"object","additionalProperties":{"$ref":"#/definitions/Enrichment"}},"name":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"}},"required":["name"],"$id":"#/definitions/Artifact"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"enrichments":{"type":"object","additionalProperties":{"$ref":"#/definitions/Enrichment"}},"kind":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"type":{"type":"string"}},"required":["name"],"$id":"#/definitions/Artifact"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifact":{"type":"string"},"ticket_id":{"format":"int64","type":"integer"}},"required":["ticket_id","artifact"],"$id":"#/definitions/ArtifactOrigin"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"image":{"type":"string"},"schema":{"type":"string"},"script":{"type":"string"},"type":{"items":{"type":"string","enum":["artifact","playbook","global"]},"type":"array"}},"required":["image","script","type"],"$id":"#/definitions/Automation"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"id":{"type":"string"},"image":{"type":"string"},"schema":{"type":"string"},"script":{"type":"string"},"type":{"items":{"type":"string","enum":["artifact","playbook","global"]},"type":"array"}},"required":["id","image","script","type"],"$id":"#/definitions/AutomationForm"}`),
|
||||
@@ -71,6 +75,8 @@ func init() {
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"}},"required":["creator","created","message"],"$id":"#/definitions/Comment"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"}},"required":["message"],"$id":"#/definitions/CommentForm"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifact":{"$ref":"#/definitions/Artifact"},"playbook":{"$ref":"#/definitions/PlaybookResponse"},"task":{"$ref":"#/definitions/TaskResponse"},"ticket":{"$ref":"#/definitions/TicketResponse"}},"$id":"#/definitions/Context"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"name":{"type":"string"},"widgets":{"items":{"$ref":"#/definitions/Widget"},"type":"array"}},"required":["name","widgets"],"$id":"#/definitions/Dashboard"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"widgets":{"items":{"$ref":"#/definitions/Widget"},"type":"array"}},"required":["id","name","widgets"],"$id":"#/definitions/DashboardResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"created":{"format":"date-time","type":"string"},"data":{"type":"object"},"name":{"type":"string"}},"required":["name","data","created"],"$id":"#/definitions/Enrichment"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"data":{"type":"object"},"name":{"type":"string"}},"required":["name","data"],"$id":"#/definitions/EnrichmentForm"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"],"$id":"#/definitions/File"}`),
|
||||
@@ -89,7 +95,8 @@ func init() {
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"yaml":{"type":"string"}},"required":["id","name","yaml"],"$id":"#/definitions/PlaybookTemplateResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"href":{"type":"string"},"name":{"type":"string"}},"required":["name","href"],"$id":"#/definitions/Reference"}`),
|
||||
gojsonschema.NewStringLoader(`{"items":{"$ref":"#/definitions/Reference"},"type":"array","$id":"#/definitions/ReferenceArray"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifactStates":{"title":"Artifact States","items":{"$ref":"#/definitions/Type"},"type":"array"},"roles":{"title":"Roles","items":{"type":"string"},"type":"array"},"ticketTypes":{"title":"Ticket Types","items":{"$ref":"#/definitions/TicketTypeResponse"},"type":"array"},"tier":{"title":"Tier","type":"string","enum":["community","enterprise"]},"timeformat":{"title":"Time Format","type":"string"},"version":{"title":"Version","type":"string"}},"required":["version","tier","timeformat","ticketTypes","artifactStates"],"$id":"#/definitions/Settings"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifactKinds":{"title":"Artifact Kinds","items":{"$ref":"#/definitions/Type"},"type":"array"},"artifactStates":{"title":"Artifact States","items":{"$ref":"#/definitions/Type"},"type":"array"},"timeformat":{"title":"Time Format","type":"string"}},"required":["timeformat","artifactKinds","artifactStates"],"$id":"#/definitions/Settings"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifactKinds":{"title":"Artifact Kinds","items":{"$ref":"#/definitions/Type"},"type":"array"},"artifactStates":{"title":"Artifact States","items":{"$ref":"#/definitions/Type"},"type":"array"},"roles":{"title":"Roles","items":{"type":"string"},"type":"array"},"ticketTypes":{"title":"Ticket Types","items":{"$ref":"#/definitions/TicketTypeResponse"},"type":"array"},"tier":{"title":"Tier","type":"string","enum":["community","enterprise"]},"timeformat":{"title":"Time Format","type":"string"},"version":{"title":"Version","type":"string"}},"required":["version","tier","timeformat","ticketTypes","artifactKinds","artifactStates"],"$id":"#/definitions/SettingsResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"open_tickets_per_user":{"type":"object","additionalProperties":{"type":"integer"}},"tickets_per_type":{"type":"object","additionalProperties":{"type":"integer"}},"tickets_per_week":{"type":"object","additionalProperties":{"type":"integer"}},"unassigned":{"type":"integer"}},"required":["unassigned","open_tickets_per_user","tickets_per_week","tickets_per_type"],"$id":"#/definitions/Statistics"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"automation":{"type":"string"},"closed":{"format":"date-time","type":"string"},"created":{"format":"date-time","type":"string"},"data":{"type":"object"},"done":{"type":"boolean"},"join":{"type":"boolean"},"name":{"type":"string"},"next":{"type":"object","additionalProperties":{"type":"string"}},"owner":{"type":"string"},"payload":{"type":"object","additionalProperties":{"type":"string"}},"schema":{"type":"object"},"type":{"type":"string","enum":["task","input","automation"]}},"required":["name","type","done","created"],"$id":"#/definitions/Task"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"playbook_id":{"type":"string"},"task_id":{"type":"string"},"ticket_id":{"format":"int64","type":"integer"}},"required":["ticket_id","playbook_id","task_id"],"$id":"#/definitions/TaskOrigin"}`),
|
||||
@@ -114,6 +121,7 @@ func init() {
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"email":{"type":"string"},"id":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"timeformat":{"title":"Time Format (https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens)","type":"string"}},"required":["id"],"$id":"#/definitions/UserDataResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserForm"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"aggregation":{"type":"string"},"filter":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["bar","line","pie"]},"width":{"maximum":12,"type":"integer"}},"required":["name","type","aggregation","width"],"$id":"#/definitions/Widget"}`),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -127,6 +135,8 @@ func init() {
|
||||
CommentSchema = mustCompile(`#/definitions/Comment`)
|
||||
CommentFormSchema = mustCompile(`#/definitions/CommentForm`)
|
||||
ContextSchema = mustCompile(`#/definitions/Context`)
|
||||
DashboardSchema = mustCompile(`#/definitions/Dashboard`)
|
||||
DashboardResponseSchema = mustCompile(`#/definitions/DashboardResponse`)
|
||||
EnrichmentSchema = mustCompile(`#/definitions/Enrichment`)
|
||||
EnrichmentFormSchema = mustCompile(`#/definitions/EnrichmentForm`)
|
||||
FileSchema = mustCompile(`#/definitions/File`)
|
||||
@@ -146,6 +156,7 @@ func init() {
|
||||
ReferenceSchema = mustCompile(`#/definitions/Reference`)
|
||||
ReferenceArraySchema = mustCompile(`#/definitions/ReferenceArray`)
|
||||
SettingsSchema = mustCompile(`#/definitions/Settings`)
|
||||
SettingsResponseSchema = mustCompile(`#/definitions/SettingsResponse`)
|
||||
StatisticsSchema = mustCompile(`#/definitions/Statistics`)
|
||||
TaskSchema = mustCompile(`#/definitions/Task`)
|
||||
TaskOriginSchema = mustCompile(`#/definitions/TaskOrigin`)
|
||||
@@ -170,10 +181,12 @@ func init() {
|
||||
UserDataResponseSchema = mustCompile(`#/definitions/UserDataResponse`)
|
||||
UserFormSchema = mustCompile(`#/definitions/UserForm`)
|
||||
UserResponseSchema = mustCompile(`#/definitions/UserResponse`)
|
||||
WidgetSchema = mustCompile(`#/definitions/Widget`)
|
||||
}
|
||||
|
||||
type Artifact struct {
|
||||
Enrichments map[string]*Enrichment `json:"enrichments,omitempty"`
|
||||
Kind *string `json:"kind,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
@@ -226,6 +239,17 @@ type Context struct {
|
||||
Ticket *TicketResponse `json:"ticket,omitempty"`
|
||||
}
|
||||
|
||||
type Dashboard struct {
|
||||
Name string `json:"name"`
|
||||
Widgets []*Widget `json:"widgets"`
|
||||
}
|
||||
|
||||
type DashboardResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Widgets []*Widget `json:"widgets"`
|
||||
}
|
||||
|
||||
type Enrichment struct {
|
||||
Created time.Time `json:"created"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
@@ -338,6 +362,13 @@ type Reference struct {
|
||||
type ReferenceArray []*Reference
|
||||
|
||||
type Settings struct {
|
||||
ArtifactKinds []*Type `json:"artifactKinds"`
|
||||
ArtifactStates []*Type `json:"artifactStates"`
|
||||
Timeformat string `json:"timeformat"`
|
||||
}
|
||||
|
||||
type SettingsResponse struct {
|
||||
ArtifactKinds []*Type `json:"artifactKinds"`
|
||||
ArtifactStates []*Type `json:"artifactStates"`
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
TicketTypes []*TicketTypeResponse `json:"ticketTypes"`
|
||||
@@ -589,6 +620,14 @@ type UserResponse struct {
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
type Widget struct {
|
||||
Aggregation string `json:"aggregation"`
|
||||
Filter *string `json:"filter,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Width int `json:"width"`
|
||||
}
|
||||
|
||||
func mustCompile(uri string) *gojsonschema.Schema {
|
||||
s, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(uri))
|
||||
if err != nil {
|
||||
@@ -598,9 +637,9 @@ func mustCompile(uri string) *gojsonschema.Schema {
|
||||
}
|
||||
|
||||
const (
|
||||
SettingsTierCommunity = "community"
|
||||
SettingsResponseTierCommunity = "community"
|
||||
|
||||
SettingsTierEnterprise = "enterprise"
|
||||
SettingsResponseTierEnterprise = "enterprise"
|
||||
|
||||
TaskTypeTask = "task"
|
||||
|
||||
@@ -621,4 +660,10 @@ const (
|
||||
TypeColorSuccess = "success"
|
||||
|
||||
TypeColorWarning = "warning"
|
||||
|
||||
WidgetTypeBar = "bar"
|
||||
|
||||
WidgetTypeLine = "line"
|
||||
|
||||
WidgetTypePie = "pie"
|
||||
)
|
||||
|
||||
@@ -78,3 +78,7 @@ func (i *Index) Truncate() error {
|
||||
i.internal = index
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) Close() error {
|
||||
return i.internal.Close()
|
||||
}
|
||||
|
||||
28
role/role.go
28
role/role.go
@@ -16,9 +16,10 @@ const (
|
||||
Admin string = "admin"
|
||||
|
||||
AutomationRead Role = "analyst:automation:read"
|
||||
CurrentuserRead Role = "analyst:currentuser:read"
|
||||
CurrentuserdataRead Role = "analyst:currentuserdata:read"
|
||||
CurrentuserdataWrite Role = "analyst:currentsettings:write"
|
||||
CurrentuserRead Role = "analyst:currentuser:read"
|
||||
DashboardRead Role = "analyst:dashboard:read"
|
||||
FileReadWrite Role = "analyst:file"
|
||||
GroupRead Role = "analyst:group:read"
|
||||
PlaybookRead Role = "analyst:playbook:read"
|
||||
@@ -26,8 +27,8 @@ const (
|
||||
SettingsRead Role = "analyst:settings:read"
|
||||
TemplateRead Role = "analyst:template:read"
|
||||
TicketRead Role = "analyst:ticket:read"
|
||||
TickettypeRead Role = "analyst:tickettype:read"
|
||||
TicketWrite Role = "analyst:ticket:write"
|
||||
TickettypeRead Role = "analyst:tickettype:read"
|
||||
UserRead Role = "analyst:user:read"
|
||||
|
||||
AutomationWrite Role = "engineer:automation:write"
|
||||
@@ -36,16 +37,18 @@ const (
|
||||
TemplateWrite Role = "engineer:template:write"
|
||||
TickettypeWrite Role = "engineer:tickettype:write"
|
||||
|
||||
BackupRead Role = "admin:backup:read"
|
||||
BackupRestore Role = "admin:backup:restore"
|
||||
GroupWrite Role = "admin:group:write"
|
||||
JobWrite Role = "admin:job:write"
|
||||
JobRead Role = "admin:job:read"
|
||||
LogRead Role = "admin:log:read"
|
||||
UserdataRead Role = "admin:userdata:read"
|
||||
UserdataWrite Role = "admin:userdata:write"
|
||||
TicketDelete Role = "admin:ticket:delete"
|
||||
UserWrite Role = "admin:user:write"
|
||||
BackupRead Role = "admin:backup:read"
|
||||
BackupRestore Role = "admin:backup:restore"
|
||||
DashboardWrite Role = "admin:dashboard:write"
|
||||
GroupWrite Role = "admin:group:write"
|
||||
JobRead Role = "admin:job:read"
|
||||
JobWrite Role = "admin:job:write"
|
||||
LogRead Role = "admin:log:read"
|
||||
SettingsWrite Role = "admin:settings:write"
|
||||
TicketDelete Role = "admin:ticket:delete"
|
||||
UserWrite Role = "admin:user:write"
|
||||
UserdataRead Role = "admin:userdata:read"
|
||||
UserdataWrite Role = "admin:userdata:write"
|
||||
)
|
||||
|
||||
func (p Role) String() string {
|
||||
@@ -145,6 +148,7 @@ func List() []Role {
|
||||
TicketWrite, UserRead, AutomationWrite, PlaybookWrite, RuleWrite,
|
||||
TemplateWrite, TickettypeWrite, BackupRead, BackupRestore, GroupWrite,
|
||||
LogRead, UserdataWrite, TicketDelete, UserWrite, JobRead, JobWrite,
|
||||
SettingsWrite, DashboardRead, DashboardWrite,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
38
server.go
38
server.go
@@ -3,7 +3,6 @@ package catalyst
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -31,7 +30,6 @@ type Config struct {
|
||||
Storage *storage.Config
|
||||
Bus *bus.Config
|
||||
|
||||
UISettings *model.Settings
|
||||
Secret []byte
|
||||
Auth *AuthConfig
|
||||
ExternalAddress string
|
||||
@@ -82,7 +80,7 @@ func New(hooks *hooks.Hooks, config *Config) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
catalystService, err := service.New(catalystBus, catalystDatabase, catalystStorage, config.UISettings)
|
||||
catalystService, err := service.New(catalystBus, catalystDatabase, catalystStorage, GetVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -117,35 +115,33 @@ func New(hooks *hooks.Hooks, config *Config) (*Server, error) {
|
||||
}
|
||||
|
||||
func setupAPI(catalystService *service.Service, catalystStorage *storage.Storage, catalystDatabase *database.Database, dbConfig *database.Config, bus *bus.Bus, config *Config) (chi.Router, error) {
|
||||
// create server
|
||||
allowAll := cors.AllowAll().Handler
|
||||
apiServer := api.NewServer(
|
||||
catalystService,
|
||||
AuthorizeRole,
|
||||
allowAll, Authenticate(catalystDatabase, config.Auth), AuthorizeBlockedUser(),
|
||||
)
|
||||
middlewares := []func(next http.Handler) http.Handler{Authenticate(catalystDatabase, config.Auth), AuthorizeBlockedUser()}
|
||||
|
||||
apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Head("/files/{ticketID}/tusd/{id}", tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress))
|
||||
apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Patch("/files/{ticketID}/tusd/{id}", tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress))
|
||||
apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Post("/files/{ticketID}/tusd", tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress))
|
||||
apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Post("/files/{ticketID}/upload", upload(catalystDatabase, catalystStorage.S3(), catalystStorage.Uploader()))
|
||||
apiServer.With(AuthorizeRole([]string{role.FileReadWrite.String()})).Get("/files/{ticketID}/download/{key}", download(catalystStorage.Downloader()))
|
||||
// create server
|
||||
apiServerMiddleware := []func(next http.Handler) http.Handler{cors.AllowAll().Handler}
|
||||
apiServerMiddleware = append(apiServerMiddleware, middlewares...)
|
||||
apiServer := api.NewServer(catalystService, AuthorizeRole, apiServerMiddleware...)
|
||||
|
||||
fileReadWrite := AuthorizeRole([]string{role.FileReadWrite.String()})
|
||||
tudHandler := tusdUpload(catalystDatabase, bus, catalystStorage.S3(), config.ExternalAddress)
|
||||
apiServer.With(fileReadWrite).Head("/files/{ticketID}/tusd/{id}", tudHandler)
|
||||
apiServer.With(fileReadWrite).Patch("/files/{ticketID}/tusd/{id}", tudHandler)
|
||||
apiServer.With(fileReadWrite).Post("/files/{ticketID}/tusd", tudHandler)
|
||||
apiServer.With(fileReadWrite).Post("/files/{ticketID}/upload", upload(catalystDatabase, catalystStorage.S3(), catalystStorage.Uploader()))
|
||||
apiServer.With(fileReadWrite).Get("/files/{ticketID}/download/{key}", download(catalystStorage.Downloader()))
|
||||
|
||||
apiServer.With(AuthorizeRole([]string{role.BackupRead.String()})).Get("/backup/create", backupHandler(catalystStorage, dbConfig))
|
||||
apiServer.With(AuthorizeRole([]string{role.BackupRestore.String()})).Post("/backup/restore", restoreHandler(catalystStorage, catalystDatabase, dbConfig))
|
||||
|
||||
server := chi.NewRouter()
|
||||
server.Use(middleware.RequestID, middleware.RealIP, middleware.Logger, middleware.Recoverer, allowAll)
|
||||
server.Use(middleware.RequestID, middleware.RealIP, middleware.Logger, middleware.Recoverer, cors.AllowAll().Handler)
|
||||
server.Mount("/api", apiServer)
|
||||
|
||||
server.Get("/callback", callback(config.Auth))
|
||||
server.With(Authenticate(catalystDatabase, config.Auth), AuthorizeBlockedUser()).Handle("/wss", handleWebSocket(bus))
|
||||
server.With(middlewares...).Handle("/wss", handleWebSocket(bus))
|
||||
|
||||
fsys, _ := fs.Sub(ui.UI, "dist")
|
||||
server.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("not found", r.URL.RawPath)
|
||||
Authenticate(catalystDatabase, config.Auth)(AuthorizeBlockedUser()(http.HandlerFunc(api.Static(fsys)))).ServeHTTP(w, r)
|
||||
})
|
||||
server.With(middlewares...).NotFound(api.VueStatic(fsys))
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
49
service/dashboard.go
Normal file
49
service/dashboard.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/arangodb/go-driver"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/database"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
)
|
||||
|
||||
func dashboardResponseID(doc *model.DashboardResponse) []driver.DocumentID {
|
||||
if doc == nil {
|
||||
return nil
|
||||
}
|
||||
return templateID(doc.ID)
|
||||
}
|
||||
|
||||
func dashboardID(id string) []driver.DocumentID {
|
||||
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.DashboardCollectionName, id))}
|
||||
}
|
||||
|
||||
func (s *Service) ListDashboards(ctx context.Context) ([]*model.DashboardResponse, error) {
|
||||
return s.database.DashboardList(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) CreateDashboard(ctx context.Context, dashboard *model.Dashboard) (doc *model.DashboardResponse, err error) {
|
||||
defer s.publishRequest(ctx, err, "CreateDashboard", dashboardResponseID(doc))
|
||||
return s.database.DashboardCreate(ctx, dashboard)
|
||||
}
|
||||
|
||||
func (s *Service) GetDashboard(ctx context.Context, id string) (*model.DashboardResponse, error) {
|
||||
return s.database.DashboardGet(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateDashboard(ctx context.Context, id string, form *model.Dashboard) (doc *model.DashboardResponse, err error) {
|
||||
defer s.publishRequest(ctx, err, "UpdateDashboard", dashboardResponseID(doc))
|
||||
return s.database.DashboardUpdate(ctx, id, form)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteDashboard(ctx context.Context, id string) (err error) {
|
||||
defer s.publishRequest(ctx, err, "DeleteDashboard", dashboardID(id))
|
||||
return s.database.DashboardDelete(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) DashboardData(ctx context.Context, aggregation string, filter *string) (map[string]interface{}, error) {
|
||||
return s.database.WidgetData(ctx, aggregation, filter)
|
||||
}
|
||||
@@ -8,19 +8,18 @@ import (
|
||||
"github.com/SecurityBrewery/catalyst/bus"
|
||||
"github.com/SecurityBrewery/catalyst/database"
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
"github.com/SecurityBrewery/catalyst/storage"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
bus *bus.Bus
|
||||
database *database.Database
|
||||
settings *model.Settings
|
||||
storage *storage.Storage
|
||||
version string
|
||||
}
|
||||
|
||||
func New(bus *bus.Bus, database *database.Database, storage *storage.Storage, settings *model.Settings) (*Service, error) {
|
||||
return &Service{database: database, bus: bus, settings: settings, storage: storage}, nil
|
||||
func New(bus *bus.Bus, database *database.Database, storage *storage.Storage, version string) (*Service, error) {
|
||||
return &Service{database: database, bus: bus, storage: storage, version: version}, nil
|
||||
}
|
||||
|
||||
func (s *Service) publishRequest(ctx context.Context, err error, function string, ids []driver.DocumentID) {
|
||||
|
||||
62
service/settings.go
Normal file
62
service/settings.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
"github.com/SecurityBrewery/catalyst/role"
|
||||
)
|
||||
|
||||
func (s *Service) GetSettings(ctx context.Context) (*model.SettingsResponse, error) {
|
||||
globalSettings, err := s.database.Settings(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.settings(ctx, globalSettings)
|
||||
}
|
||||
|
||||
func (s *Service) SaveSettings(ctx context.Context, settings *model.Settings) (*model.SettingsResponse, error) {
|
||||
globalSettings, err := s.database.SaveSettings(ctx, settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.settings(ctx, globalSettings)
|
||||
}
|
||||
|
||||
func (s *Service) settings(ctx context.Context, globalSettings *model.Settings) (*model.SettingsResponse, error) {
|
||||
user, ok := busdb.UserFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("no user in context")
|
||||
}
|
||||
|
||||
userData, err := s.database.UserDataGet(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ticketTypeList, err := s.database.TicketTypeList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userData.Timeformat != nil {
|
||||
globalSettings.Timeformat = *userData.Timeformat
|
||||
}
|
||||
roles := role.Strings(role.List())
|
||||
sort.Strings(roles)
|
||||
|
||||
return &model.SettingsResponse{
|
||||
Tier: model.SettingsResponseTierCommunity,
|
||||
Version: s.version,
|
||||
Roles: roles,
|
||||
TicketTypes: ticketTypeList,
|
||||
ArtifactStates: globalSettings.ArtifactStates,
|
||||
ArtifactKinds: globalSettings.ArtifactKinds,
|
||||
Timeformat: globalSettings.Timeformat,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
"github.com/SecurityBrewery/catalyst/role"
|
||||
)
|
||||
|
||||
func (s *Service) GetSettings(ctx context.Context) (*model.Settings, error) {
|
||||
user, ok := busdb.UserFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("no user in context")
|
||||
}
|
||||
|
||||
setting, err := s.database.UserDataGet(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings := mergeSettings(s.settings, setting)
|
||||
|
||||
ticketTypeList, err := s.database.TicketTypeList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings.TicketTypes = ticketTypeList
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func mergeSettings(globalSettings *model.Settings, user *model.UserDataResponse) *model.Settings {
|
||||
if user.Timeformat != nil {
|
||||
globalSettings.Timeformat = *user.Timeformat
|
||||
}
|
||||
roles := role.Strings(role.List())
|
||||
sort.Strings(roles)
|
||||
globalSettings.Roles = roles
|
||||
|
||||
return globalSettings
|
||||
}
|
||||
21
test/data.go
21
test/data.go
@@ -78,6 +78,27 @@ func SetupTestData(ctx context.Context, db *database.Database) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.DashboardCreate(ctx, &model.Dashboard{
|
||||
Name: "Simple",
|
||||
Widgets: []*model.Widget{
|
||||
{
|
||||
Name: "open_tickets_per_user",
|
||||
Type: model.WidgetTypeBar,
|
||||
Aggregation: "owner",
|
||||
Filter: pointer.String(`status == "open"`),
|
||||
Width: 4,
|
||||
},
|
||||
{
|
||||
Name: "tickets_per_week",
|
||||
Type: model.WidgetTypeLine,
|
||||
Aggregation: `CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) < 10 ? "0" : "", DATE_ISOWEEK(created))`,
|
||||
Width: 8,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
21
test/test.go
21
test/test.go
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/api"
|
||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||
"github.com/SecurityBrewery/catalyst/generated/pointer"
|
||||
"github.com/SecurityBrewery/catalyst/hooks"
|
||||
"github.com/SecurityBrewery/catalyst/index"
|
||||
"github.com/SecurityBrewery/catalyst/service"
|
||||
@@ -51,22 +50,6 @@ func Config(ctx context.Context) (*catalyst.Config, error) {
|
||||
Key: "A9RysEsPJni8RaHeg_K0FKXQNfBrUyw-",
|
||||
APIUrl: "http://localhost:8002/api",
|
||||
},
|
||||
UISettings: &model.Settings{
|
||||
ArtifactStates: []*model.Type{
|
||||
{Icon: "mdi-help-circle-outline", ID: "unknown", Name: "Unknown", Color: pointer.String(model.TypeColorInfo)},
|
||||
{Icon: "mdi-skull", ID: "malicious", Name: "Malicious", Color: pointer.String(model.TypeColorError)},
|
||||
{Icon: "mdi-check", ID: "clean", Name: "Clean", Color: pointer.String(model.TypeColorSuccess)},
|
||||
},
|
||||
TicketTypes: []*model.TicketTypeResponse{
|
||||
{ID: "alert", Icon: "mdi-alert", Name: "Alerts"},
|
||||
{ID: "incident", Icon: "mdi-radioactive", Name: "Incidents"},
|
||||
{ID: "investigation", Icon: "mdi-fingerprint", Name: "Forensic Investigations"},
|
||||
{ID: "hunt", Icon: "mdi-target", Name: "Threat Hunting"},
|
||||
},
|
||||
Version: "0.0.0-test",
|
||||
Tier: model.SettingsTierCommunity,
|
||||
Timeformat: "YYYY-MM-DDThh:mm:ss",
|
||||
},
|
||||
Secret: []byte("4ef5b29539b70233dd40c02a1799d25079595565e05a193b09da2c3e60ada1cd"),
|
||||
Auth: &catalyst.AuthConfig{
|
||||
OIDCIssuer: "http://localhost:9002/auth/realms/catalyst",
|
||||
@@ -101,7 +84,7 @@ func Index(t *testing.T) (*index.Index, func(), error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return catalystIndex, func() { os.RemoveAll(dir) }, nil
|
||||
return catalystIndex, func() { catalystIndex.Close(); os.RemoveAll(dir) }, nil
|
||||
}
|
||||
|
||||
func Bus(t *testing.T) (context.Context, *catalyst.Config, *bus.Bus, error) {
|
||||
@@ -168,7 +151,7 @@ func Service(t *testing.T) (context.Context, *catalyst.Config, *bus.Bus, *index.
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
catalystService, err := service.New(rbus, db, catalystStorage, config.UISettings)
|
||||
catalystService, err := service.New(rbus, db, catalystStorage, "0.0.0-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="/flask.png?v=1">
|
||||
<title>Catalyst</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -12,8 +11,5 @@
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script>let global = globalThis;</script>
|
||||
<script type="module" src="./src/main.ts"></script>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<v-app class="background">
|
||||
<v-navigation-drawer dark permanent :mini-variant="mini" :expand-on-hover="mini" app color="statusbar">
|
||||
<v-list>
|
||||
<v-list-item class="px-2" :to="{ name: 'Dashboard' }">
|
||||
<v-list-item class="px-2" :to="{ name: 'Home' }">
|
||||
<v-list-item-avatar rounded="0">
|
||||
<v-img src="/flask_white.svg" :width="40"></v-img>
|
||||
<v-img src="/static/flask_white.svg" :width="40"></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="title">
|
||||
@@ -132,9 +132,7 @@
|
||||
</v-btn>
|
||||
|
||||
</v-app-bar>
|
||||
<div>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<router-view></router-view>
|
||||
<v-snackbar v-model="snackbar" :color="$store.state.alert.type" :timeout="$store.state.alert.type === 'error' ? -1 : 5000" outlined>
|
||||
<b style="display: block">{{ $store.state.alert.name | capitalize }}</b>
|
||||
{{ $store.state.alert.detail }}
|
||||
@@ -164,6 +162,7 @@ export default Vue.extend({
|
||||
{ icon: "mdi-account-group", name: "Groups", to: "GroupList", role: "admin:group:write", tier: "enterprise" },
|
||||
{ icon: "mdi-cogs", name: "User Data", to: "UserDataList", role: "admin:userdata:write" },
|
||||
{ icon: "mdi-format-list-checks", name: "Jobs", to: "JobList", role: "admin:job:write" },
|
||||
{ icon: "mdi-cog", name: "Settings", to: "Settings", role: "admin:settings:write" },
|
||||
],
|
||||
mini: true,
|
||||
goto: "",
|
||||
@@ -181,6 +180,7 @@ export default Vue.extend({
|
||||
},
|
||||
internal: function (): Array<any> {
|
||||
return [
|
||||
{ icon: "mdi-view-dashboard", name: "Dashboards", to: "DashboardList", role: "analyst:dashboard:read" },
|
||||
{ icon: "mdi-check-bold", name: "Open Tasks", to: "TaskList", count: this.$store.state.task_count },
|
||||
]
|
||||
},
|
||||
@@ -188,6 +188,8 @@ export default Vue.extend({
|
||||
return this.$store.state.showAlert
|
||||
},
|
||||
crumbs: function() {
|
||||
this.$route.name
|
||||
|
||||
let pathArray = this.$route.path.split("/")
|
||||
pathArray.shift()
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ export interface Artifact {
|
||||
* @memberof Artifact
|
||||
*/
|
||||
'enrichments'?: { [key: string]: Enrichment; };
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Artifact
|
||||
*/
|
||||
'kind'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@@ -284,6 +290,50 @@ export interface Context {
|
||||
*/
|
||||
'ticket'?: TicketResponse;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface Dashboard
|
||||
*/
|
||||
export interface Dashboard {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Dashboard
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Widget>}
|
||||
* @memberof Dashboard
|
||||
*/
|
||||
'widgets': Array<Widget>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface DashboardResponse
|
||||
*/
|
||||
export interface DashboardResponse {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof DashboardResponse
|
||||
*/
|
||||
'id': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof DashboardResponse
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Widget>}
|
||||
* @memberof DashboardResponse
|
||||
*/
|
||||
'widgets': Array<Widget>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -976,50 +1026,81 @@ export interface RuleResponse {
|
||||
* @interface Settings
|
||||
*/
|
||||
export interface Settings {
|
||||
/**
|
||||
*
|
||||
* @type {Array<Type>}
|
||||
* @memberof Settings
|
||||
*/
|
||||
'artifactKinds': Array<Type>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Type>}
|
||||
* @memberof Settings
|
||||
*/
|
||||
'artifactStates': Array<Type>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof Settings
|
||||
*/
|
||||
'roles'?: Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<TicketTypeResponse>}
|
||||
* @memberof Settings
|
||||
*/
|
||||
'ticketTypes': Array<TicketTypeResponse>;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Settings
|
||||
*/
|
||||
'tier': SettingsTierEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Settings
|
||||
*/
|
||||
'timeformat': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface SettingsResponse
|
||||
*/
|
||||
export interface SettingsResponse {
|
||||
/**
|
||||
*
|
||||
* @type {Array<Type>}
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'artifactKinds': Array<Type>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Type>}
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'artifactStates': Array<Type>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'roles'?: Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<TicketTypeResponse>}
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'ticketTypes': Array<TicketTypeResponse>;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Settings
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'tier': SettingsResponseTierEnum;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'timeformat': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SettingsResponse
|
||||
*/
|
||||
'version': string;
|
||||
}
|
||||
|
||||
export const SettingsTierEnum = {
|
||||
export const SettingsResponseTierEnum = {
|
||||
Community: 'community',
|
||||
Enterprise: 'enterprise'
|
||||
} as const;
|
||||
|
||||
export type SettingsTierEnum = typeof SettingsTierEnum[keyof typeof SettingsTierEnum];
|
||||
export type SettingsResponseTierEnum = typeof SettingsResponseTierEnum[keyof typeof SettingsResponseTierEnum];
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -2242,6 +2323,52 @@ export interface UserResponse {
|
||||
*/
|
||||
'roles': Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface Widget
|
||||
*/
|
||||
export interface Widget {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'aggregation': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'filter'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'type': WidgetTypeEnum;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'width': number;
|
||||
}
|
||||
|
||||
export const WidgetTypeEnum = {
|
||||
Bar: 'bar',
|
||||
Line: 'line',
|
||||
Pie: 'pie'
|
||||
} as const;
|
||||
|
||||
export type WidgetTypeEnum = typeof WidgetTypeEnum[keyof typeof WidgetTypeEnum];
|
||||
|
||||
|
||||
/**
|
||||
* AutomationsApi - axios parameter creator
|
||||
@@ -2620,6 +2747,461 @@ export class AutomationsApi extends BaseAPI {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DashboardsApi - axios parameter creator
|
||||
* @export
|
||||
*/
|
||||
export const DashboardsApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createDashboard: async (template: Dashboard, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'template' is not null or undefined
|
||||
assertParamExists('createDashboard', 'template', template)
|
||||
const localVarPath = `/dashboards`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(template, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
dashboardData: async (aggregation: string, filter?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'aggregation' is not null or undefined
|
||||
assertParamExists('dashboardData', 'aggregation', aggregation)
|
||||
const localVarPath = `/dashboard/data`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
if (aggregation !== undefined) {
|
||||
localVarQueryParameter['aggregation'] = aggregation;
|
||||
}
|
||||
|
||||
if (filter !== undefined) {
|
||||
localVarQueryParameter['filter'] = filter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteDashboard: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('deleteDashboard', 'id', id)
|
||||
const localVarPath = `/dashboards/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getDashboard: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('getDashboard', 'id', id)
|
||||
const localVarPath = `/dashboards/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listDashboards: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/dashboards`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateDashboard: async (id: string, dashboard: Dashboard, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('updateDashboard', 'id', id)
|
||||
// verify required parameter 'dashboard' is not null or undefined
|
||||
assertParamExists('updateDashboard', 'dashboard', dashboard)
|
||||
const localVarPath = `/dashboards/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(dashboard, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardsApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const DashboardsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = DashboardsApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async createDashboard(template: Dashboard, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DashboardResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createDashboard(template, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async dashboardData(aggregation: string, filter?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.dashboardData(aggregation, filter, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteDashboard(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteDashboard(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getDashboard(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DashboardResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getDashboard(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listDashboards(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<DashboardResponse>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listDashboards(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateDashboard(id: string, dashboard: Dashboard, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DashboardResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateDashboard(id, dashboard, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardsApi - factory interface
|
||||
* @export
|
||||
*/
|
||||
export const DashboardsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = DashboardsApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createDashboard(template: Dashboard, options?: any): AxiosPromise<DashboardResponse> {
|
||||
return localVarFp.createDashboard(template, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
dashboardData(aggregation: string, filter?: string, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.dashboardData(aggregation, filter, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteDashboard(id: string, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.deleteDashboard(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getDashboard(id: string, options?: any): AxiosPromise<DashboardResponse> {
|
||||
return localVarFp.getDashboard(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listDashboards(options?: any): AxiosPromise<Array<DashboardResponse>> {
|
||||
return localVarFp.listDashboards(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateDashboard(id: string, dashboard: Dashboard, options?: any): AxiosPromise<DashboardResponse> {
|
||||
return localVarFp.updateDashboard(id, dashboard, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardsApi - object-oriented interface
|
||||
* @export
|
||||
* @class DashboardsApi
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class DashboardsApi extends BaseAPI {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public createDashboard(template: Dashboard, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).createDashboard(template, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public dashboardData(aggregation: string, filter?: string, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).dashboardData(aggregation, filter, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public deleteDashboard(id: string, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).deleteDashboard(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public getDashboard(id: string, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).getDashboard(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public listDashboards(options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).listDashboards(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public updateDashboard(id: string, dashboard: Dashboard, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).updateDashboard(id, dashboard, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GraphApi - axios parameter creator
|
||||
* @export
|
||||
@@ -4319,6 +4901,42 @@ export const SettingsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Save settings
|
||||
* @param {Settings} settings Save settings
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
saveSettings: async (settings: Settings, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'settings' is not null or undefined
|
||||
assertParamExists('saveSettings', 'settings', settings)
|
||||
const localVarPath = `/settings`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(settings, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
@@ -4340,10 +4958,21 @@ export const SettingsApiFp = function(configuration?: Configuration) {
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getSettings(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Settings>> {
|
||||
async getSettings(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SettingsResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getSettings(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Save settings
|
||||
* @param {Settings} settings Save settings
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async saveSettings(settings: Settings, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SettingsResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.saveSettings(settings, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4360,9 +4989,19 @@ export const SettingsApiFactory = function (configuration?: Configuration, baseP
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getSettings(options?: any): AxiosPromise<Settings> {
|
||||
getSettings(options?: any): AxiosPromise<SettingsResponse> {
|
||||
return localVarFp.getSettings(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Save settings
|
||||
* @param {Settings} settings Save settings
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
saveSettings(settings: Settings, options?: any): AxiosPromise<SettingsResponse> {
|
||||
return localVarFp.saveSettings(settings, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4383,6 +5022,18 @@ export class SettingsApi extends BaseAPI {
|
||||
public getSettings(options?: AxiosRequestConfig) {
|
||||
return SettingsApiFp(this.configuration).getSettings(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Save settings
|
||||
* @param {Settings} settings Save settings
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof SettingsApi
|
||||
*/
|
||||
public saveSettings(settings: Settings, options?: AxiosRequestConfig) {
|
||||
return SettingsApiFp(this.configuration).saveSettings(settings, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
<v-icon small class="mr-1" :color="statusColor">{{ statusIcon }}</v-icon>
|
||||
<span :class="statusColor + '--text'">{{ artifact.status | capitalize }}</span>
|
||||
|
||||
<v-icon small class="mx-1" :color="kindColor">{{ kindIcon }}</v-icon>
|
||||
<span :class="kindColor + '--text'">{{ artifact.kind | capitalize }}</span>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-icon small class="mr-1">mdi-information</v-icon>
|
||||
<span class="mr-1">{{ artifact.enrichments ? lodash.size(artifact.enrichments) : 0 }}</span>
|
||||
@@ -47,6 +50,24 @@ export default Vue.extend({
|
||||
}
|
||||
})
|
||||
return color;
|
||||
},
|
||||
kindIcon: function () {
|
||||
let icon = "mdi-help";
|
||||
this.lodash.forEach(this.$store.state.settings.artifactKinds, (state: Type) => {
|
||||
if (this.artifact.kind === state.id) {
|
||||
icon = state.icon;
|
||||
}
|
||||
})
|
||||
return icon;
|
||||
},
|
||||
kindColor: function () {
|
||||
let color = TypeColorEnum.Info as TypeColorEnum;
|
||||
this.lodash.forEach(this.$store.state.settings.artifactKinds, (state: Type) => {
|
||||
if (this.artifact.kind === state.id && state.color) {
|
||||
color = state.color;
|
||||
}
|
||||
})
|
||||
return color;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -68,19 +68,26 @@ const v = new Vue({
|
||||
}).$mount("#app");
|
||||
|
||||
axios.interceptors.response.use(
|
||||
response => response,
|
||||
// response => response,
|
||||
response => {
|
||||
lodash.unset(response.data, 'notoast');
|
||||
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
error => {
|
||||
console.log(error)
|
||||
if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) {
|
||||
const problem = error.response.data as Problem;
|
||||
v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail });
|
||||
return Promise.reject(error);
|
||||
if (!lodash.has(error.response.data, 'notoast')) {
|
||||
if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) {
|
||||
const problem = error.response.data as Problem;
|
||||
v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
if (error.response.data && 'error' in error.response.data) {
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: error.response.data.error });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: JSON.stringify(error.response.data) });
|
||||
}
|
||||
if (error.response.data && 'error' in error.response.data) {
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: error.response.data.error });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: JSON.stringify(error.response.data) });
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -15,17 +15,21 @@ import Rule from "../views/Rule.vue";
|
||||
import RuleList from "../views/RuleList.vue";
|
||||
import Template from "../views/Template.vue";
|
||||
import TemplateList from "../views/TemplateList.vue";
|
||||
import Dashboard from "../views/Dashboard.vue";
|
||||
import DashboardList from "../views/DashboardList.vue";
|
||||
import API from "../views/API.vue";
|
||||
import User from '../views/User.vue';
|
||||
import UserList from "@/views/UserList.vue";
|
||||
import Job from '../views/Job.vue';
|
||||
import JobList from "@/views/JobList.vue";
|
||||
import GroupList from "@/views/GroupList.vue";
|
||||
import Dashboard from "@/views/Dashboard.vue";
|
||||
import Home from "@/views/Home.vue";
|
||||
import Group from "@/views/Group.vue";
|
||||
import TicketType from '../views/TicketType.vue';
|
||||
import TicketTypeList from "@/views/TicketTypeList.vue";
|
||||
import TaskList from "@/views/TaskList.vue";
|
||||
import Settings from "@/views/Settings.vue";
|
||||
import NotFound from "@/views/NotFound.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -54,14 +58,14 @@ const routes: Array<RouteConfig> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Catalyst",
|
||||
redirect: { name: "Dashboard" },
|
||||
redirect: { name: "Home" },
|
||||
},
|
||||
|
||||
{
|
||||
path: "/dashboard",
|
||||
name: "Dashboard",
|
||||
component: Dashboard,
|
||||
meta: { title: "Dashboard" },
|
||||
path: "/home",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
meta: { title: "Home" },
|
||||
},
|
||||
|
||||
{
|
||||
@@ -226,6 +230,27 @@ const routes: Array<RouteConfig> = [
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: "/dashboards",
|
||||
name: "DashboardList",
|
||||
component: DashboardList,
|
||||
meta: { title: "Dashboards" },
|
||||
children: [
|
||||
{
|
||||
path: ":id",
|
||||
name: "Dashboard",
|
||||
component: Dashboard,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: "/settings",
|
||||
name: "Settings",
|
||||
component: Settings,
|
||||
meta: { title: "Settings" },
|
||||
},
|
||||
|
||||
{
|
||||
path: "/apidocs",
|
||||
name: "API",
|
||||
@@ -239,6 +264,13 @@ const routes: Array<RouteConfig> = [
|
||||
component: Graph,
|
||||
meta: { title: "Graph" },
|
||||
},
|
||||
|
||||
{
|
||||
path: '*',
|
||||
name: "Not Found",
|
||||
component: NotFound,
|
||||
meta: { title: "Not Found" },
|
||||
}
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
SettingsApi,
|
||||
SettingsApiFactory,
|
||||
JobsApi,
|
||||
JobsApiFactory,
|
||||
JobsApiFactory, DashboardsApiFactory, DashboardsApi,
|
||||
} from "@/client";
|
||||
|
||||
const config = new Configuration({
|
||||
@@ -56,7 +56,8 @@ export const API: TicketsApi &
|
||||
SettingsApi &
|
||||
TickettypesApi &
|
||||
JobsApi &
|
||||
TasksApi = Object.assign(
|
||||
TasksApi &
|
||||
DashboardsApi = Object.assign(
|
||||
{},
|
||||
TicketsApiFactory(config),
|
||||
PlaybooksApiFactory(config),
|
||||
@@ -74,5 +75,6 @@ export const API: TicketsApi &
|
||||
TickettypesApiFactory(config),
|
||||
TasksApiFactory(config),
|
||||
SettingsApiFactory(config),
|
||||
JobsApiFactory(config)
|
||||
JobsApiFactory(config),
|
||||
DashboardsApiFactory(config)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from "vue";
|
||||
import Vuex, {ActionContext} from "vuex";
|
||||
import {API} from "@/services/api";
|
||||
import {UserData, TicketList, Settings, UserResponse} from "@/client";
|
||||
import {UserData, TicketList, UserResponse, SettingsResponse} from "@/client";
|
||||
import {AxiosResponse} from "axios";
|
||||
import {Alert} from "@/types/types";
|
||||
import {templateStore} from "./modules/templates";
|
||||
@@ -19,7 +19,7 @@ export default new Vuex.Store({
|
||||
counts: {} as Record<string, number>,
|
||||
task_count: 0 as number,
|
||||
|
||||
settings: {} as Settings,
|
||||
settings: {} as SettingsResponse,
|
||||
userdata: {} as UserData,
|
||||
|
||||
alert: {} as Alert,
|
||||
@@ -46,7 +46,7 @@ export default new Vuex.Store({
|
||||
setUserData (state, msg: UserData) {
|
||||
state.userdata = msg
|
||||
},
|
||||
setSettings (state, msg: Settings) {
|
||||
setSettings (state, msg: SettingsResponse) {
|
||||
state.settings = msg
|
||||
},
|
||||
setAlert (state, msg: Alert) {
|
||||
@@ -68,7 +68,7 @@ export default new Vuex.Store({
|
||||
})
|
||||
},
|
||||
getSettings (context: ActionContext<any, any>) {
|
||||
API.getSettings().then((response: AxiosResponse<Settings>) => {
|
||||
API.getSettings().then((response: AxiosResponse<SettingsResponse>) => {
|
||||
context.commit("setSettings", response.data);
|
||||
context.dispatch("fetchCount");
|
||||
})
|
||||
|
||||
@@ -22,6 +22,23 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
·
|
||||
Kind:
|
||||
<v-menu offset-y class="mr-2">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<span v-bind="attrs" v-on="on">
|
||||
<v-icon small class="mr-1" :color="kindColor(artifact.kind)">{{ kindIcon(artifact.kind) }}</v-icon>
|
||||
<span :class="kindColor(artifact.kind) + '--text'">{{ artifact.kind | capitalize }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item dense link v-for="state in otherKinds" :key="state.id" @click="setKind(state.id)">
|
||||
<v-list-item-title>
|
||||
Set kind to <v-icon small>{{ kindIcon(state.id) }}</v-icon> {{ state.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
·
|
||||
Type:
|
||||
<v-menu
|
||||
:close-on-content-click="false"
|
||||
@@ -148,6 +165,14 @@ export default Vue.extend({
|
||||
return state.id !== this.artifact.status;
|
||||
})
|
||||
},
|
||||
otherKinds: function (): Array<Type> {
|
||||
return this.lodash.filter(this.$store.state.settings.artifactKinds, (state: Type) => {
|
||||
if (!this.artifact || !this.artifact.status) {
|
||||
return true;
|
||||
}
|
||||
return state.id !== this.artifact.status;
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setArtifactType() {
|
||||
@@ -229,6 +254,42 @@ export default Vue.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
kindIcon: function (kind: string): string {
|
||||
let icon = "mdi-help";
|
||||
this.lodash.forEach(this.$store.state.settings.artifactKinds, (state: Type) => {
|
||||
if (kind === state.id) {
|
||||
icon = state.icon;
|
||||
}
|
||||
})
|
||||
return icon;
|
||||
},
|
||||
kindColor: function (kind: string) {
|
||||
let color = TypeColorEnum.Info as TypeColorEnum;
|
||||
this.lodash.forEach(this.$store.state.settings.artifactKinds, (state: Type) => {
|
||||
if (kind === state.id && state.color) {
|
||||
color = state.color
|
||||
}
|
||||
})
|
||||
return color;
|
||||
},
|
||||
setKind(kind: string) {
|
||||
if (!this.artifact || !this.artifact.name || this.ticketID === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let artifact = this.artifact
|
||||
artifact.kind = kind
|
||||
API.setArtifact(this.ticketID, this.artifact.name, artifact).then((response) => {
|
||||
this.$store.dispatch("alertSuccess", { name: "Artifact kind changed", type: "success" })
|
||||
if (response.data.artifacts) {
|
||||
this.lodash.forEach(response.data.artifacts, (artifact) => {
|
||||
if (artifact.name == this.name) {
|
||||
this.artifact = artifact;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
load() {
|
||||
this.loadArtifact(this.ticketID, this.name);
|
||||
}
|
||||
|
||||
@@ -1,110 +1,123 @@
|
||||
<template>
|
||||
<v-main>
|
||||
<div v-if="dashboard">
|
||||
<h2 class="d-flex">
|
||||
<span v-if="!editmode">{{ dashboard.name }}</span>
|
||||
<v-text-field v-else v-model="dashboard.name" outlined dense class="mb-0" hide-details></v-text-field>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="editmode" small outlined @click="addWidget" class="mr-1">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
Add Widget
|
||||
</v-btn>
|
||||
<v-btn v-if="editmode" small outlined @click="save" class="mr-1">
|
||||
<v-icon>mdi-content-save</v-icon>
|
||||
Save
|
||||
</v-btn>
|
||||
<v-btn v-if="editmode && $route.params.id !== 'new'" small outlined @click="cancel">
|
||||
<v-icon>mdi-cancel</v-icon>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn v-if="!editmode" small outlined @click="edit">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</h2>
|
||||
|
||||
<v-row>
|
||||
<v-col v-if="statistics" cols="12" lg="7">
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-subheader>Unassigned tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND !owner' }
|
||||
}">
|
||||
{{ statistics.unassigned }}
|
||||
</router-link>
|
||||
</span>
|
||||
<v-subheader>Your tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND owner == \'' + $store.state.user.id + '\'' }
|
||||
}">
|
||||
{{ $store.state.user.id in statistics.open_tickets_per_user ? statistics.open_tickets_per_user[$store.state.user.id] : 0 }}
|
||||
</router-link>
|
||||
</span>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<v-subheader>Open tickets per owner</v-subheader>
|
||||
<bar-chart
|
||||
v-if="open_tickets_per_user"
|
||||
:chart-data="open_tickets_per_user"
|
||||
:styles="{
|
||||
width: '100%',
|
||||
'max-height': '400px',
|
||||
position: 'relative'
|
||||
}"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { xAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] },
|
||||
onClick: clickUser,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
></bar-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="7">
|
||||
<v-subheader>Tickets created per week</v-subheader>
|
||||
<v-col v-for="(widget, index) in dashboard.widgets" :key="index" :cols="widget.width">
|
||||
<v-card class="mb-2">
|
||||
<v-card-title>
|
||||
<span v-if="!editmode">{{ widget.name }}</span>
|
||||
<v-text-field v-else outlined dense hide-details v-model="widget.name" class="mr-1"></v-text-field>
|
||||
<v-btn v-if="editmode" outlined @click="removeWidget(index)">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
Remove
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text v-if="editmode">
|
||||
<v-row>
|
||||
<v-col cols="8">
|
||||
<v-select label="Type" v-model="widget.type" :items="['line', 'bar', 'pie']"></v-select>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-text-field label="Width" type="number" v-model="widget.width"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-text-field label="Aggregation" v-model="widget.aggregation"></v-text-field>
|
||||
<v-text-field label="Filter" v-model="widget.filter" clearable></v-text-field>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text v-if="data[index] === null">
|
||||
{{ widgetErrors[index] }}
|
||||
</v-card-text>
|
||||
<div v-else>
|
||||
<line-chart
|
||||
v-if="tickets_per_week"
|
||||
:chart-data="tickets_per_week"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
|
||||
}"
|
||||
v-if="widget.type === 'line' && data[index]"
|
||||
:chart-data="data[index]"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: false,
|
||||
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
|
||||
}"
|
||||
>
|
||||
</line-chart>
|
||||
</v-col>
|
||||
<v-col cols="5">
|
||||
<v-subheader>Ticket Types</v-subheader>
|
||||
|
||||
<pie-chart
|
||||
v-if="tickets_per_type"
|
||||
:chart-data="tickets_per_type"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
onClick: clickPie,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
v-if="widget.type === 'pie' && data[index]"
|
||||
:chart-data="data[index]"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}"
|
||||
>
|
||||
</pie-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="5">
|
||||
<TicketList :type="this.$route.params.type" @click="open"></TicketList>
|
||||
|
||||
<bar-chart
|
||||
v-if="widget.type === 'bar' && data[index]"
|
||||
:chart-data="data[index]"
|
||||
:styles="{
|
||||
width: '100%',
|
||||
'max-height': '400px',
|
||||
position: 'relative'
|
||||
}"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: false,
|
||||
scales: { xAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] },
|
||||
}"
|
||||
></bar-chart>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import {DashboardResponse, Widget} from "@/client";
|
||||
import { API } from "@/services/api";
|
||||
import {createHash} from "crypto";
|
||||
import {colors} from "@/plugins/vuetify";
|
||||
import LineChart from "../components/charts/Line";
|
||||
import BarChart from "../components/charts/Bar";
|
||||
import PieChart from "../components/charts/Doughnut";
|
||||
import { API } from "@/services/api";
|
||||
import {Statistics, TicketResponse} from "@/client";
|
||||
import {DateTime} from "luxon";
|
||||
import { colors } from "@/plugins/vuetify";
|
||||
import TicketList from "@/components/TicketList.vue";
|
||||
import { createHash } from "crypto";
|
||||
import {ChartData} from "chart.js";
|
||||
import {AxiosError, AxiosTransformer} from "axios";
|
||||
|
||||
interface State {
|
||||
dashboard?: DashboardResponse;
|
||||
undodashboard?: DashboardResponse;
|
||||
data: Record<string, any>;
|
||||
editmode: boolean;
|
||||
widgetErrors: Record<number, string>;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Dashboard",
|
||||
@@ -112,108 +125,130 @@ export default Vue.extend({
|
||||
LineChart,
|
||||
BarChart,
|
||||
PieChart,
|
||||
TicketList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statistics: (undefined as unknown) as Statistics
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tickets_per_type: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.tickets_per_type, (count, type) => {
|
||||
data.labels.push(type);
|
||||
data.datasets[0].data.push(count);
|
||||
|
||||
data.datasets[0].backgroundColor.push(this.color(type));
|
||||
})
|
||||
return data
|
||||
data: (): State => ({
|
||||
dashboard: undefined,
|
||||
undodashboard: undefined,
|
||||
data: {},
|
||||
editmode: false,
|
||||
widgetErrors: {},
|
||||
}),
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.loadDashboard();
|
||||
},
|
||||
open_tickets_per_user: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.open_tickets_per_user, (count, user) => {
|
||||
if (!user) {
|
||||
data.labels.push("unassigned");
|
||||
} else {
|
||||
data.labels.push(user);
|
||||
}
|
||||
data.datasets[0].data.push(count);
|
||||
data.datasets[0].backgroundColor.push(this.color(user));
|
||||
})
|
||||
return data
|
||||
},
|
||||
tickets_per_week: function () {
|
||||
let data = {labels: [] as Array<string>, datasets: [{backgroundColor: [] as Array<string>, data: [] as Array<number> }]}
|
||||
this.lodash.forEach(this.weeks(), (week) => {
|
||||
data.labels.push(week);
|
||||
if (week in this.statistics.tickets_per_week) {
|
||||
data.datasets[0].data.push(this.statistics.tickets_per_week[week]);
|
||||
} else {
|
||||
data.datasets[0].data.push(0);
|
||||
}
|
||||
data.datasets[0].backgroundColor.push("#607d8b");
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open: function (ticket: TicketResponse) {
|
||||
if (ticket.id === undefined) {
|
||||
return;
|
||||
edit: function () {
|
||||
this.undodashboard = this.lodash.cloneDeep(this.dashboard);
|
||||
this.editmode = true;
|
||||
},
|
||||
save: function () {
|
||||
if (!this.dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "Ticket",
|
||||
params: {type: '-', id: ticket.id.toString()}
|
||||
});
|
||||
},
|
||||
clickUser: function (evt, elem) {
|
||||
let owner = this.open_tickets_per_user.labels[elem[0]._index];
|
||||
let query = 'status == \'open\' AND owner == \'' + owner + '\'';
|
||||
let widgets = [] as Array<Widget>;
|
||||
this.lodash.forEach(this.dashboard.widgets, (widget) => {
|
||||
widget.width = this.lodash.toInteger(widget.width);
|
||||
if (!widget.filter) {
|
||||
this.lodash.unset(widget, "filter")
|
||||
}
|
||||
widgets.push(widget);
|
||||
})
|
||||
this.dashboard.widgets = widgets;
|
||||
|
||||
if (owner == 'unassigned') {
|
||||
query = 'status == \'open\' AND !owner';
|
||||
if (this.$route.params.id === 'new') {
|
||||
API.createDashboard(this.dashboard).then((response) => {
|
||||
this.loadWidgetData(response.data.widgets);
|
||||
|
||||
this.dashboard = response.data;
|
||||
this.editmode = false;
|
||||
|
||||
this.$router.push({ name: "Dashboard", params: { id: response.data.id }})
|
||||
})
|
||||
} else {
|
||||
API.updateDashboard(this.dashboard.id, this.dashboard).then((response) => {
|
||||
this.loadWidgetData(response.data.widgets);
|
||||
|
||||
this.dashboard = response.data;
|
||||
this.editmode = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
cancel: function () {
|
||||
this.dashboard = this.lodash.cloneDeep(this.undodashboard);
|
||||
this.editmode = false;
|
||||
},
|
||||
addWidget: function () {
|
||||
if (!this.dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {query: query}
|
||||
});
|
||||
this.dashboard.widgets.push({name: "new widget", width: 6, aggregation: "", type: "line"})
|
||||
},
|
||||
clickPie: function (evt, elem) {
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {type: this.tickets_per_type.labels[elem[0]._index]}
|
||||
});
|
||||
removeWidget: function (id: number) {
|
||||
if (!this.dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(id);
|
||||
let widgets = this.lodash.cloneDeep(this.dashboard.widgets);
|
||||
this.lodash.pullAt(widgets, [id]);
|
||||
Vue.set(this.dashboard, "widgets", widgets);
|
||||
},
|
||||
loadDashboard: function () {
|
||||
if (this.$route.params.id === 'new') {
|
||||
this.dashboard = {
|
||||
name: "New dashboard",
|
||||
widgets: [{name: "new widget", width: 6, aggregation: "", type: "line"}],
|
||||
} as DashboardResponse
|
||||
this.editmode = true;
|
||||
} else {
|
||||
API.getDashboard(this.$route.params.id).then((response) => {
|
||||
this.loadWidgetData(response.data.widgets);
|
||||
|
||||
this.dashboard = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
loadWidgetData: function (widgets: Array<Widget>) {
|
||||
this.lodash.forEach(widgets, (widget: Widget, index: number) => {
|
||||
let widgetErrors = {};
|
||||
let defaultTransformers = this.axios.defaults.transformResponse as AxiosTransformer[]
|
||||
let transformResponse = defaultTransformers.concat((data) => {
|
||||
data.notoast = true;
|
||||
return data
|
||||
});
|
||||
API.dashboardData(widget.aggregation, widget.filter, {transformResponse: transformResponse}).then((response) => {
|
||||
let d = { labels: [], datasets: [{data: [], backgroundColor: []}] } as ChartData;
|
||||
this.lodash.forEach(response.data, (v: any, k: string) => {
|
||||
// @ts-expect-error T2532
|
||||
d.labels.push(k)
|
||||
// @ts-expect-error T2532
|
||||
d.datasets[0].data.push(v)
|
||||
|
||||
if (widget.type !== 'line') {
|
||||
// @ts-expect-error T2532
|
||||
d.datasets[0].backgroundColor.push(this.color(this.lodash.toString(v)));
|
||||
}
|
||||
})
|
||||
|
||||
Vue.set(this.data, index, d);
|
||||
}).catch((err: AxiosError) => {
|
||||
widgetErrors[index] = this.lodash.toString(err.response?.data.error);
|
||||
Vue.set(this.data, index, null);
|
||||
})
|
||||
Vue.set(this, 'widgetErrors', widgetErrors);
|
||||
})
|
||||
},
|
||||
color: function (s: string): string {
|
||||
let pos = createHash('md5').update(s).digest().readUInt32BE(0) % colors.length;
|
||||
return colors[pos];
|
||||
},
|
||||
fillData() {
|
||||
API.getStatistics().then(response => {
|
||||
this.statistics = response.data;
|
||||
});
|
||||
},
|
||||
weeks: function () {
|
||||
let w = [] as Array<string>;
|
||||
for (let i = 0; i < 53; i++) {
|
||||
w.push(DateTime.utc().minus({ weeks: i }).toFormat("kkkk-WW"))
|
||||
}
|
||||
this.lodash.reverse(w);
|
||||
return w
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fillData();
|
||||
this.loadDashboard();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: relative !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
64
ui/src/views/DashboardList.vue
Normal file
64
ui/src/views/DashboardList.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<v-main style="min-height: 100vh;">
|
||||
<List
|
||||
:items="dashboards"
|
||||
routername="Dashboard"
|
||||
itemid="id"
|
||||
itemname="name"
|
||||
singular="Dashboard"
|
||||
plural="Dashboards"
|
||||
writepermission="admin:dashboard:write"
|
||||
@delete="deleteDashboard"
|
||||
></List>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
|
||||
import {Dashboard} from "@/client";
|
||||
import {API} from "@/services/api";
|
||||
import List from "../components/List.vue";
|
||||
|
||||
interface State {
|
||||
dashboards: Array<Dashboard>;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: "DashboardList",
|
||||
components: {List},
|
||||
data: (): State => ({
|
||||
dashboards: [],
|
||||
}),
|
||||
methods: {
|
||||
loadDashboards() {
|
||||
API.listDashboards().then((response) => {
|
||||
this.dashboards = response.data;
|
||||
});
|
||||
},
|
||||
deleteDashboard(id: string) {
|
||||
API.deleteDashboard(id).then(() => {
|
||||
this.loadDashboards();
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadDashboards();
|
||||
|
||||
this.$store.subscribeAction((action, state) => {
|
||||
if (!action.payload || !(this.lodash.has(action.payload, "ids")) || !action.payload["ids"]) {
|
||||
return
|
||||
}
|
||||
let reload = false;
|
||||
Vue.lodash.forEach(action.payload["ids"], (id) => {
|
||||
if (this.lodash.startsWith(id, "dashboard/")) {
|
||||
reload = true;
|
||||
}
|
||||
});
|
||||
if (reload) {
|
||||
this.loadDashboards()
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
</script>
|
||||
219
ui/src/views/Home.vue
Normal file
219
ui/src/views/Home.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<v-main>
|
||||
<v-row>
|
||||
<v-col v-if="statistics" cols="12" lg="7">
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-subheader>Unassigned tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND !owner' }
|
||||
}">
|
||||
{{ statistics.unassigned }}
|
||||
</router-link>
|
||||
</span>
|
||||
<v-subheader>Your tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND owner == \'' + $store.state.user.id + '\'' }
|
||||
}">
|
||||
{{ $store.state.user.id in statistics.open_tickets_per_user ? statistics.open_tickets_per_user[$store.state.user.id] : 0 }}
|
||||
</router-link>
|
||||
</span>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<v-subheader>Open tickets per owner</v-subheader>
|
||||
<bar-chart
|
||||
v-if="open_tickets_per_user"
|
||||
:chart-data="open_tickets_per_user"
|
||||
:styles="{
|
||||
width: '100%',
|
||||
'max-height': '400px',
|
||||
position: 'relative'
|
||||
}"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { xAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] },
|
||||
onClick: clickUser,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
></bar-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="7">
|
||||
<v-subheader>Tickets created per week</v-subheader>
|
||||
<line-chart
|
||||
v-if="tickets_per_week"
|
||||
:chart-data="tickets_per_week"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
|
||||
}"
|
||||
>
|
||||
</line-chart>
|
||||
</v-col>
|
||||
<v-col cols="5">
|
||||
<v-subheader>Ticket Types</v-subheader>
|
||||
<pie-chart
|
||||
v-if="tickets_per_type"
|
||||
:chart-data="tickets_per_type"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
onClick: clickPie,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
</pie-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="5">
|
||||
<TicketList :type="this.$route.params.type" @click="open"></TicketList>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import LineChart from "../components/charts/Line";
|
||||
import BarChart from "../components/charts/Bar";
|
||||
import PieChart from "../components/charts/Doughnut";
|
||||
import { API } from "@/services/api";
|
||||
import {Statistics, TicketResponse} from "@/client";
|
||||
import {DateTime} from "luxon";
|
||||
import { colors } from "@/plugins/vuetify";
|
||||
import TicketList from "@/components/TicketList.vue";
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Home",
|
||||
components: {
|
||||
LineChart,
|
||||
BarChart,
|
||||
PieChart,
|
||||
TicketList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statistics: (undefined as unknown) as Statistics
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tickets_per_type: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.tickets_per_type, (count, type) => {
|
||||
data.labels.push(type);
|
||||
data.datasets[0].data.push(count);
|
||||
|
||||
data.datasets[0].backgroundColor.push(this.color(type));
|
||||
})
|
||||
return data
|
||||
},
|
||||
open_tickets_per_user: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.open_tickets_per_user, (count, user) => {
|
||||
if (!user) {
|
||||
data.labels.push("unassigned");
|
||||
} else {
|
||||
data.labels.push(user);
|
||||
}
|
||||
data.datasets[0].data.push(count);
|
||||
data.datasets[0].backgroundColor.push(this.color(user));
|
||||
})
|
||||
return data
|
||||
},
|
||||
tickets_per_week: function () {
|
||||
let data = {labels: [] as Array<string>, datasets: [{backgroundColor: [] as Array<string>, data: [] as Array<number> }]}
|
||||
this.lodash.forEach(this.weeks(), (week) => {
|
||||
data.labels.push(week);
|
||||
if (week in this.statistics.tickets_per_week) {
|
||||
data.datasets[0].data.push(this.statistics.tickets_per_week[week]);
|
||||
} else {
|
||||
data.datasets[0].data.push(0);
|
||||
}
|
||||
data.datasets[0].backgroundColor.push("#607d8b");
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open: function (ticket: TicketResponse) {
|
||||
if (ticket.id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "Ticket",
|
||||
params: {type: '-', id: ticket.id.toString()}
|
||||
});
|
||||
},
|
||||
clickUser: function (evt, elem) {
|
||||
let owner = this.open_tickets_per_user.labels[elem[0]._index];
|
||||
let query = 'status == \'open\' AND owner == \'' + owner + '\'';
|
||||
|
||||
if (owner == 'unassigned') {
|
||||
query = 'status == \'open\' AND !owner';
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {query: query}
|
||||
});
|
||||
},
|
||||
clickPie: function (evt, elem) {
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {type: this.tickets_per_type.labels[elem[0]._index]}
|
||||
});
|
||||
},
|
||||
color: function (s: string): string {
|
||||
let pos = createHash('md5').update(s).digest().readUInt32BE(0) % colors.length;
|
||||
return colors[pos];
|
||||
},
|
||||
fillData() {
|
||||
API.getStatistics().then(response => {
|
||||
this.statistics = response.data;
|
||||
});
|
||||
},
|
||||
weeks: function () {
|
||||
let w = [] as Array<string>;
|
||||
for (let i = 0; i < 53; i++) {
|
||||
w.push(DateTime.utc().minus({ weeks: i }).toFormat("kkkk-WW"))
|
||||
}
|
||||
this.lodash.reverse(w);
|
||||
return w
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fillData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: relative !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
13
ui/src/views/NotFound.vue
Normal file
13
ui/src/views/NotFound.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-main>
|
||||
<div class="fill-height d-flex flex-row align-center justify-center">
|
||||
<h1>Page not found :(</h1>
|
||||
</div>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "NotFound"
|
||||
}
|
||||
</script>
|
||||
177
ui/src/views/Settings.vue
Normal file
177
ui/src/views/Settings.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<v-main class="ma-4">
|
||||
<div v-if="settings !== undefined">
|
||||
<v-text-field label="Time Format" v-model="settings.timeformat"></v-text-field>
|
||||
|
||||
<v-subheader class="mx-0 px-0">Artifact States</v-subheader>
|
||||
<v-card v-for="state in settings.artifactStates" :key="state.id" class="d-flex mb-2">
|
||||
<v-row class="px-4 pt-2" dense>
|
||||
<v-col>
|
||||
<v-text-field label="ID" v-model="state.id"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Name" v-model="state.name"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Icon" v-model="state.icon"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-select label="Color" v-model="state.color" :items="['info', 'error', 'success', 'warning']" clearable></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-btn icon class="mt-6 mr-4" @click="removeState(state.id)"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card>
|
||||
<v-card class="d-flex mb-2">
|
||||
<v-row class="px-4 pt-2" dense>
|
||||
<v-col>
|
||||
<v-text-field label="ID" v-model="newState.id"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Name" v-model="newState.name"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Icon" v-model="newState.icon"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-select label="Color" v-model="newState.color" :items="['info', 'error', 'success', 'warning']" clearable></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-btn icon class="mt-6 mr-4" @click="addState"><v-icon>mdi-plus</v-icon></v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-subheader class="mx-0 px-0">Artifact Types</v-subheader>
|
||||
<v-card v-for="state in settings.artifactKinds" :key="state.id" class="d-flex mb-2">
|
||||
<v-row class="px-4 pt-2" dense>
|
||||
<v-col>
|
||||
<v-text-field label="ID" v-model="state.id"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Name" v-model="state.name"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Icon" v-model="state.icon"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-select label="Color" v-model="state.color" :items="['info', 'error', 'success', 'warning']" clearable></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-btn icon class="mt-6 mr-4" @click="removeKind(state.id)"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-card>
|
||||
<v-card class="d-flex mb-2">
|
||||
<v-row class="px-4 pt-2" dense>
|
||||
<v-col>
|
||||
<v-text-field label="ID" v-model="newKind.id"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Name" v-model="newKind.name"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field label="Icon" v-model="newKind.icon"></v-text-field>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-select label="Color" v-model="newKind.color" :items="['info', 'error', 'success', 'warning']" clearable></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-btn icon class="mt-6 mr-4" @click="addKind"><v-icon>mdi-plus</v-icon></v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-btn color="success" @click="save" outlined class="mt-2">
|
||||
<v-icon>mdi-content-save</v-icon>
|
||||
Save
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {DateTime} from "luxon";
|
||||
import Vue from "vue";
|
||||
import {Settings, SettingsResponse, Type} from "@/client";
|
||||
import {API} from "@/services/api";
|
||||
import {AxiosResponse} from "axios";
|
||||
|
||||
interface State {
|
||||
valid: boolean;
|
||||
settings?: Settings;
|
||||
newState: Type;
|
||||
newKind: Type;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Settings",
|
||||
data: (): State => ({
|
||||
valid: true,
|
||||
settings: undefined,
|
||||
newState: {} as Type,
|
||||
newKind: {} as Type,
|
||||
}),
|
||||
methods: {
|
||||
save: function () {
|
||||
if (this.settings === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
API.saveSettings(this.settings).then((response) => {
|
||||
this.settings = response.data;
|
||||
this.$store.dispatch("getSettings");
|
||||
})
|
||||
},
|
||||
addState: function () {
|
||||
if (this.settings === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.settings.artifactStates.push(this.newState);
|
||||
this.newState = {} as Type;
|
||||
},
|
||||
removeState: function (id: string) {
|
||||
if (this.settings === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.settings.artifactStates = this.lodash.filter(this.settings.artifactStates, function (t) { return t.id !== id });
|
||||
},
|
||||
addKind: function () {
|
||||
if (this.settings === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.settings.artifactKinds.push(this.newKind);
|
||||
this.newKind = {} as Type;
|
||||
},
|
||||
removeKind: function (id: string) {
|
||||
if (this.settings === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
this.settings.artifactKinds = this.lodash.filter(this.settings.artifactKinds, function (t) { return t.id !== id });
|
||||
},
|
||||
timeformat: function (s: string) {
|
||||
let format = this.$store.state.settings.timeformat;
|
||||
if (!format) {
|
||||
return DateTime.fromISO(s).toLocaleString(DateTime.DATETIME_SHORT);
|
||||
}
|
||||
return DateTime.fromISO(s).toFormat(format);
|
||||
},
|
||||
dateformat: function (s: string) {
|
||||
let format = this.$store.state.settings.timeformat;
|
||||
if (!format) {
|
||||
return DateTime.fromISO(s).toLocaleString(DateTime.DATETIME_SHORT);
|
||||
}
|
||||
return DateTime.fromISO(s).toFormat(format);
|
||||
},
|
||||
datetimeformat: function (s: string) {
|
||||
let format = this.$store.state.settings.timeformat;
|
||||
if (!format) {
|
||||
return DateTime.fromISO(s).toLocaleString(DateTime.DATETIME_SHORT);
|
||||
}
|
||||
return DateTime.fromISO(s).toFormat(format);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
API.getSettings().then((response: AxiosResponse<SettingsResponse>) => {
|
||||
this.settings = response.data;
|
||||
})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -181,7 +181,7 @@
|
||||
<v-jsf
|
||||
v-model="ticket.details"
|
||||
:schema="schema"
|
||||
:options="{ initialValidation: 'all', formats: { time: timeformat, date: dateformat, 'date-time': datetimeformat } }"
|
||||
:options="{ initialValidation: 'all', formats: { time: timeformat, date: dateformat, 'date-time': datetimeformat }, editMode: 'inline' }"
|
||||
/>
|
||||
</v-form>
|
||||
<v-btn small class="float-right mb-2" color="card" @click="saveTicket" outlined>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
publicPath: "/static/",
|
||||
transpileDependencies: ["vuetify", "@koumoul/vjsf"],
|
||||
pwa: {
|
||||
name: "Catalyst",
|
||||
|
||||
Reference in New Issue
Block a user