Add Dashboards (#41)

This commit is contained in:
Jonas Plum
2022-03-14 00:23:29 +01:00
committed by GitHub
parent 18a4dc54e7
commit 02c7da91da
30 changed files with 2824 additions and 279 deletions

117
database/dashboard.go Normal file
View 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
}

View File

@@ -26,6 +26,7 @@ const (
TicketTypeCollectionName = "tickettypes" TicketTypeCollectionName = "tickettypes"
JobCollectionName = "jobs" JobCollectionName = "jobs"
SettingsCollectionName = "settings" SettingsCollectionName = "settings"
DashboardCollectionName = "dashboards"
TicketArtifactsGraphName = "Graph" TicketArtifactsGraphName = "Graph"
RelatedTicketsCollectionName = "related" RelatedTicketsCollectionName = "related"
@@ -46,6 +47,7 @@ type Database struct {
tickettypeCollection *busdb.Collection tickettypeCollection *busdb.Collection
jobCollection *busdb.Collection jobCollection *busdb.Collection
settingsCollection *busdb.Collection settingsCollection *busdb.Collection
dashboardCollection *busdb.Collection
relatedCollection *busdb.Collection relatedCollection *busdb.Collection
// containsCollection *busdb.Collection // containsCollection *busdb.Collection
@@ -128,6 +130,10 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo
if err != nil { if err != nil {
return nil, err return nil, err
} }
dashboardCollection, err := arangoDB.Collection(ctx, DashboardCollectionName)
if err != nil {
return nil, err
}
hookedDB, err := busdb.NewDatabase(ctx, arangoDB, bus) hookedDB, err := busdb.NewDatabase(ctx, arangoDB, bus)
if err != nil { if err != nil {
@@ -149,6 +155,7 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo
tickettypeCollection: busdb.NewCollection(tickettypeCollection, hookedDB), tickettypeCollection: busdb.NewCollection(tickettypeCollection, hookedDB),
jobCollection: busdb.NewCollection(jobCollection, hookedDB), jobCollection: busdb.NewCollection(jobCollection, hookedDB),
settingsCollection: busdb.NewCollection(settingsCollection, hookedDB), settingsCollection: busdb.NewCollection(settingsCollection, hookedDB),
dashboardCollection: busdb.NewCollection(dashboardCollection, hookedDB),
} }
return db, nil return db, nil
@@ -197,5 +204,6 @@ func (db *Database) Truncate(ctx context.Context) {
db.jobCollection.Truncate(ctx) db.jobCollection.Truncate(ctx)
db.relatedCollection.Truncate(ctx) db.relatedCollection.Truncate(ctx)
db.settingsCollection.Truncate(ctx) db.settingsCollection.Truncate(ctx)
db.dashboardCollection.Truncate(ctx)
// db.containsCollection.Truncate(ctx) // db.containsCollection.Truncate(ctx)
} }

View File

@@ -56,6 +56,8 @@ func generateMigrations() ([]Migration, error) {
&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"}}}, &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"}`}, &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"]}`},
}, nil }, nil
} }

View File

@@ -2,7 +2,9 @@ package database
import ( import (
"context" "context"
"fmt"
"github.com/SecurityBrewery/catalyst/caql"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/model" "github.com/SecurityBrewery/catalyst/generated/model"
) )
@@ -41,3 +43,49 @@ func (db *Database) Statistics(ctx context.Context) (*model.Statistics, error) {
return &statistics, nil 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
}

View File

@@ -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) { 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{}{} binVars := map[string]interface{}{}
parser := &caql.Parser{Searcher: db.Index, Prefix: "d."}
var typeString = "" var typeString = ""
if ticketType != "" { if ticketType != "" {
typeString = "FILTER d.type == @type " typeString = "FILTER d.type == @type "
@@ -459,6 +457,7 @@ func (db *Database) TicketList(ctx context.Context, ticketType string, query str
var filterString = "" var filterString = ""
if query != "" { if query != "" {
parser := &caql.Parser{Searcher: db.Index, Prefix: "d."}
queryTree, err := parser.Parse(query) queryTree, err := parser.Parse(query)
if err != nil { if err != nil {
return nil, errors.New("invalid filter query: syntax error") return nil, errors.New("invalid filter query: syntax error")

167
definition/dashboards.yaml Normal file
View 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 }

View File

@@ -29,10 +29,10 @@ paths:
- { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" } - { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" }
- { icon: "mdi-check", id: "clean", name: "Clean", color: "success" } - { icon: "mdi-check", id: "clean", name: "Clean", color: "success" }
roles: [ roles: [
"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job: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:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read",
"admin:userdata:write", "analyst:automation:read", "admin:userdata:write", "analyst:automation:read",
"analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata: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:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read",
"analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write",
"analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write",
@@ -67,10 +67,10 @@ paths:
- { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" } - { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" }
- { icon: "mdi-check", id: "clean", name: "Clean", color: "success" } - { icon: "mdi-check", id: "clean", name: "Clean", color: "success" }
roles: [ roles: [
"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job: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:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read",
"admin:userdata:write", "analyst:automation:read", "admin:userdata:write", "analyst:automation:read",
"analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata: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:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read",
"analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write",
"analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write",

View File

@@ -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 } }

View File

@@ -12,7 +12,7 @@ paths:
description: "successful operation" description: "successful operation"
schema: { $ref: "#/definitions/UserResponse" } schema: { $ref: "#/definitions/UserResponse" }
examples: examples:
test: { id: bob, roles: [ "admin:backup:read", "admin:backup:restore", "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: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" ] } ] security: [ { roles: [ "currentuser:read" ] } ]
/users: /users:
@@ -26,8 +26,8 @@ paths:
schema: { type: array, items: { $ref: "#/definitions/UserResponse" } } schema: { type: array, items: { $ref: "#/definitions/UserResponse" } }
examples: examples:
test: 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: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: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: 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: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: 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" ] } ] security: [ { roles: [ "user:read" ] } ]
post: post:
tags: [ "users" ] tags: [ "users" ]
@@ -40,7 +40,7 @@ paths:
description: "successful operation" description: "successful operation"
schema: { $ref: "#/definitions/NewUserResponse" } schema: { $ref: "#/definitions/NewUserResponse" }
examples: 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" ] } ] security: [ { roles: [ "user:write" ] } ]
/users/{id}: /users/{id}:
get: get:
@@ -54,7 +54,7 @@ paths:
description: "successful operation" description: "successful operation"
schema: { $ref: "#/definitions/UserResponse" } schema: { $ref: "#/definitions/UserResponse" }
examples: 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" ] } ] security: [ { roles: [ "user:read" ] } ]
put: put:
tags: [ "users" ] tags: [ "users" ]
@@ -70,7 +70,7 @@ paths:
examples: examples:
test: test:
id: bob id: bob
roles: [ "admin:backup:read", "admin:backup:restore", "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: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 apikey: false
blocked: false blocked: false
security: [ { roles: [ "user:write" ] } ] security: [ { roles: [ "user:write" ] } ]

View File

@@ -19,6 +19,12 @@ type Service interface {
CurrentUser(context.Context) (*model.UserResponse, error) CurrentUser(context.Context) (*model.UserResponse, error)
CurrentUserData(context.Context) (*model.UserDataResponse, error) CurrentUserData(context.Context) (*model.UserDataResponse, error)
UpdateCurrentUserData(context.Context, *model.UserData) (*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) ListJobs(context.Context) ([]*model.JobResponse, error)
RunJob(context.Context, *model.JobForm) (*model.JobResponse, error) RunJob(context.Context, *model.JobForm) (*model.JobResponse, error)
GetJob(context.Context, string) (*model.JobResponse, error) GetJob(context.Context, string) (*model.JobResponse, error)
@@ -91,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{"currentuser:read"})).Get("/currentuser", s.currentUserHandler)
r.With(roleAuth([]string{"currentuserdata:read"})).Get("/currentuserdata", s.currentUserDataHandler) r.With(roleAuth([]string{"currentuserdata:read"})).Get("/currentuserdata", s.currentUserDataHandler)
r.With(roleAuth([]string{"currentuserdata:write"})).Put("/currentuserdata", s.updateCurrentUserDataHandler) 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:read"})).Get("/jobs", s.listJobsHandler)
r.With(roleAuth([]string{"job:write"})).Post("/jobs", s.runJobHandler) r.With(roleAuth([]string{"job:write"})).Post("/jobs", s.runJobHandler)
r.With(roleAuth([]string{"job:read"})).Get("/jobs/{id}", s.getJobHandler) r.With(roleAuth([]string{"job:read"})).Get("/jobs/{id}", s.getJobHandler)
@@ -247,6 +259,77 @@ func (s *server) updateCurrentUserDataHandler(w http.ResponseWriter, r *http.Req
response(w, result, err) 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) { func (s *server) listJobsHandler(w http.ResponseWriter, r *http.Request) {
result, err := s.service.ListJobs(r.Context()) result, err := s.service.ListJobs(r.Context())
response(w, result, err) response(w, result, err)

View File

@@ -68,7 +68,7 @@ var Tests = []struct {
Args: Args{Method: "Get", URL: "/currentuser"}, Args: Args{Method: "Get", URL: "/currentuser"},
Want: Want{ Want: Want{
Status: 200, 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: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: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", Name: "ListJobs",
Args: Args{Method: "Get", URL: "/jobs"}, Args: Args{Method: "Get", URL: "/jobs"},
@@ -185,7 +239,7 @@ var Tests = []struct {
Args: Args{Method: "Get", URL: "/settings"}, Args: Args{Method: "Get", URL: "/settings"},
Want: Want{ Want: Want{
Status: 200, 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: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: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-DDThh:mm:ss", "version": "0.0.0-test"},
}, },
}, },
@@ -194,7 +248,7 @@ var Tests = []struct {
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-DDThh:mm:ss"}}, 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-DDThh:mm:ss"}},
Want: Want{ Want: Want{
Status: 200, 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: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: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-DDThh:mm:ss", "version": "0.0.0-test"},
}, },
}, },
@@ -554,7 +608,7 @@ var Tests = []struct {
Args: Args{Method: "Get", URL: "/users"}, Args: Args{Method: "Get", URL: "/users"},
Want: Want{ Want: Want{
Status: 200, 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: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: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"}}},
}, },
}, },
@@ -563,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"}}}, Args: Args{Method: "Post", URL: "/users", Data: map[string]interface{}{"apikey": true, "blocked": false, "id": "syncscript", "roles": []interface{}{"analyst"}}},
Want: Want{ Want: Want{
Status: 200, 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"},
}, },
}, },
@@ -572,7 +626,7 @@ var Tests = []struct {
Args: Args{Method: "Get", URL: "/users/script"}, Args: Args{Method: "Get", URL: "/users/script"},
Want: Want{ Want: Want{
Status: 200, 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"}},
}, },
}, },
@@ -581,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"}}}, Args: Args{Method: "Put", URL: "/users/bob", Data: map[string]interface{}{"apikey": false, "blocked": false, "id": "syncscript", "roles": []interface{}{"analyst", "admin"}}},
Want: Want{ Want: Want{
Status: 200, 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: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: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"}},
}, },
}, },

View File

@@ -225,7 +225,7 @@
"apikey" : false, "apikey" : false,
"blocked" : false, "blocked" : false,
"id" : "bob", "id" : "bob",
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" "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}" : { "/graph/{col}/{id}" : {
"get" : { "get" : {
"operationId" : "graph", "operationId" : "graph",
@@ -1173,7 +1424,7 @@
"id" : "clean", "id" : "clean",
"name" : "Clean" "name" : "Clean"
} ], } ],
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" : [ { "ticketTypes" : [ {
"default_playbooks" : [ ], "default_playbooks" : [ ],
"default_template" : "default", "default_template" : "default",
@@ -1262,7 +1513,7 @@
"id" : "clean", "id" : "clean",
"name" : "Clean" "name" : "Clean"
} ], } ],
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" : [ { "ticketTypes" : [ {
"default_playbooks" : [ ], "default_playbooks" : [ ],
"default_template" : "default", "default_template" : "default",
@@ -5092,12 +5343,12 @@
"apikey" : false, "apikey" : false,
"blocked" : false, "blocked" : false,
"id" : "bob", "id" : "bob",
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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, "apikey" : true,
"blocked" : false, "blocked" : false,
"id" : "script", "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" ]
} ] } ]
} }
}, },
@@ -5135,7 +5386,7 @@
"example" : { "example" : {
"blocked" : false, "blocked" : false,
"id" : "syncscript", "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" "secret" : "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"
} }
} }
@@ -5201,7 +5452,7 @@
"apikey" : true, "apikey" : true,
"blocked" : false, "blocked" : false,
"id" : "script", "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" ]
} }
} }
}, },
@@ -5250,7 +5501,7 @@
"apikey" : false, "apikey" : false,
"blocked" : false, "blocked" : false,
"id" : "bob", "id" : "bob",
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" ]
} }
} }
}, },
@@ -5431,6 +5682,39 @@
}, },
"type" : "object" "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" : { "Enrichment" : {
"properties" : { "properties" : {
"created" : { "created" : {
@@ -6895,6 +7179,30 @@
}, },
"required" : [ "apikey", "blocked", "id", "roles" ], "required" : [ "apikey", "blocked", "id", "roles" ],
"type" : "object" "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"
} }
} }
}, },

View File

@@ -141,6 +141,33 @@ definitions:
ticket: ticket:
$ref: '#/definitions/TicketResponse' $ref: '#/definitions/TicketResponse'
type: object 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: Enrichment:
properties: properties:
created: created:
@@ -1335,6 +1362,30 @@ definitions:
- roles - roles
- apikey - apikey
type: object 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: . host: .
info: info:
description: API for the catalyst incident response platform. description: API for the catalyst incident response platform.
@@ -1576,6 +1627,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -1589,6 +1641,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -1659,6 +1712,183 @@ paths:
summary: Update current user data summary: Update current user data
tags: tags:
- userdata - 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}: /graph/{col}/{id}:
get: get:
operationId: graph operationId: graph
@@ -2601,6 +2831,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -2614,6 +2845,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -2720,6 +2952,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -2733,6 +2966,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -7122,6 +7356,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -7135,6 +7370,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -7158,6 +7394,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -7210,6 +7447,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -7270,6 +7508,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -7326,6 +7565,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -7339,6 +7579,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read

View File

@@ -225,7 +225,7 @@
"apikey" : false, "apikey" : false,
"blocked" : false, "blocked" : false,
"id" : "bob", "id" : "bob",
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" "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" : { "/jobs" : {
"get" : { "get" : {
"operationId" : "listJobs", "operationId" : "listJobs",
@@ -743,7 +994,7 @@
"id" : "clean", "id" : "clean",
"name" : "Clean" "name" : "Clean"
} ], } ],
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" : [ { "ticketTypes" : [ {
"default_playbooks" : [ ], "default_playbooks" : [ ],
"default_template" : "default", "default_template" : "default",
@@ -832,7 +1083,7 @@
"id" : "clean", "id" : "clean",
"name" : "Clean" "name" : "Clean"
} ], } ],
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" : [ { "ticketTypes" : [ {
"default_playbooks" : [ ], "default_playbooks" : [ ],
"default_template" : "default", "default_template" : "default",
@@ -4662,12 +4913,12 @@
"apikey" : false, "apikey" : false,
"blocked" : false, "blocked" : false,
"id" : "bob", "id" : "bob",
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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, "apikey" : true,
"blocked" : false, "blocked" : false,
"id" : "script", "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" ]
} ] } ]
} }
}, },
@@ -4705,7 +4956,7 @@
"example" : { "example" : {
"blocked" : false, "blocked" : false,
"id" : "syncscript", "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" "secret" : "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"
} }
} }
@@ -4771,7 +5022,7 @@
"apikey" : true, "apikey" : true,
"blocked" : false, "blocked" : false,
"id" : "script", "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" ]
} }
} }
}, },
@@ -4820,7 +5071,7 @@
"apikey" : false, "apikey" : false,
"blocked" : false, "blocked" : false,
"id" : "bob", "id" : "bob",
"roles" : [ "admin:backup:read", "admin:backup:restore", "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: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" ]
} }
} }
}, },
@@ -5001,6 +5252,39 @@
}, },
"type" : "object" "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" : { "Enrichment" : {
"properties" : { "properties" : {
"created" : { "created" : {
@@ -6316,6 +6600,30 @@
}, },
"required" : [ "apikey", "blocked", "id", "roles" ], "required" : [ "apikey", "blocked", "id", "roles" ],
"type" : "object" "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"
} }
} }
}, },

View File

@@ -141,6 +141,33 @@ definitions:
ticket: ticket:
$ref: '#/definitions/TicketResponse' $ref: '#/definitions/TicketResponse'
type: object 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: Enrichment:
properties: properties:
created: created:
@@ -1216,6 +1243,30 @@ definitions:
- roles - roles
- apikey - apikey
type: object 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: . host: .
info: info:
description: API for the catalyst incident response platform. description: API for the catalyst incident response platform.
@@ -1457,6 +1508,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -1470,6 +1522,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -1540,6 +1593,183 @@ paths:
summary: Update current user data summary: Update current user data
tags: tags:
- userdata - 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: /jobs:
get: get:
operationId: listJobs operationId: listJobs
@@ -2189,6 +2419,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -2202,6 +2433,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -2308,6 +2540,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -2321,6 +2554,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -6710,6 +6944,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -6723,6 +6958,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -6746,6 +6982,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -6798,6 +7035,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -6858,6 +7096,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read
@@ -6914,6 +7153,7 @@ paths:
roles: roles:
- admin:backup:read - admin:backup:read
- admin:backup:restore - admin:backup:restore
- admin:dashboard:write
- admin:group:write - admin:group:write
- admin:job:read - admin:job:read
- admin:job:write - admin:job:write
@@ -6927,6 +7167,7 @@ paths:
- analyst:currentsettings:write - analyst:currentsettings:write
- analyst:currentuser:read - analyst:currentuser:read
- analyst:currentuserdata:read - analyst:currentuserdata:read
- analyst:dashboard:read
- analyst:file - analyst:file
- analyst:group:read - analyst:group:read
- analyst:playbook:read - analyst:playbook:read

View File

@@ -16,6 +16,8 @@ var (
CommentSchema = new(gojsonschema.Schema) CommentSchema = new(gojsonschema.Schema)
CommentFormSchema = new(gojsonschema.Schema) CommentFormSchema = new(gojsonschema.Schema)
ContextSchema = new(gojsonschema.Schema) ContextSchema = new(gojsonschema.Schema)
DashboardSchema = new(gojsonschema.Schema)
DashboardResponseSchema = new(gojsonschema.Schema)
EnrichmentSchema = new(gojsonschema.Schema) EnrichmentSchema = new(gojsonschema.Schema)
EnrichmentFormSchema = new(gojsonschema.Schema) EnrichmentFormSchema = new(gojsonschema.Schema)
FileSchema = new(gojsonschema.Schema) FileSchema = new(gojsonschema.Schema)
@@ -60,6 +62,7 @@ var (
UserDataResponseSchema = new(gojsonschema.Schema) UserDataResponseSchema = new(gojsonschema.Schema)
UserFormSchema = new(gojsonschema.Schema) UserFormSchema = new(gojsonschema.Schema)
UserResponseSchema = new(gojsonschema.Schema) UserResponseSchema = new(gojsonschema.Schema)
WidgetSchema = new(gojsonschema.Schema)
) )
func init() { func init() {
@@ -72,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":["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":{"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":{"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":{"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":{"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"}`), gojsonschema.NewStringLoader(`{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"],"$id":"#/definitions/File"}`),
@@ -116,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":{"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/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":{"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 { if err != nil {
panic(err) panic(err)
@@ -129,6 +135,8 @@ func init() {
CommentSchema = mustCompile(`#/definitions/Comment`) CommentSchema = mustCompile(`#/definitions/Comment`)
CommentFormSchema = mustCompile(`#/definitions/CommentForm`) CommentFormSchema = mustCompile(`#/definitions/CommentForm`)
ContextSchema = mustCompile(`#/definitions/Context`) ContextSchema = mustCompile(`#/definitions/Context`)
DashboardSchema = mustCompile(`#/definitions/Dashboard`)
DashboardResponseSchema = mustCompile(`#/definitions/DashboardResponse`)
EnrichmentSchema = mustCompile(`#/definitions/Enrichment`) EnrichmentSchema = mustCompile(`#/definitions/Enrichment`)
EnrichmentFormSchema = mustCompile(`#/definitions/EnrichmentForm`) EnrichmentFormSchema = mustCompile(`#/definitions/EnrichmentForm`)
FileSchema = mustCompile(`#/definitions/File`) FileSchema = mustCompile(`#/definitions/File`)
@@ -173,6 +181,7 @@ func init() {
UserDataResponseSchema = mustCompile(`#/definitions/UserDataResponse`) UserDataResponseSchema = mustCompile(`#/definitions/UserDataResponse`)
UserFormSchema = mustCompile(`#/definitions/UserForm`) UserFormSchema = mustCompile(`#/definitions/UserForm`)
UserResponseSchema = mustCompile(`#/definitions/UserResponse`) UserResponseSchema = mustCompile(`#/definitions/UserResponse`)
WidgetSchema = mustCompile(`#/definitions/Widget`)
} }
type Artifact struct { type Artifact struct {
@@ -230,6 +239,17 @@ type Context struct {
Ticket *TicketResponse `json:"ticket,omitempty"` 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 { type Enrichment struct {
Created time.Time `json:"created"` Created time.Time `json:"created"`
Data map[string]interface{} `json:"data"` Data map[string]interface{} `json:"data"`
@@ -600,6 +620,14 @@ type UserResponse struct {
Roles []string `json:"roles"` 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 { func mustCompile(uri string) *gojsonschema.Schema {
s, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(uri)) s, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(uri))
if err != nil { if err != nil {
@@ -632,4 +660,10 @@ const (
TypeColorSuccess = "success" TypeColorSuccess = "success"
TypeColorWarning = "warning" TypeColorWarning = "warning"
WidgetTypeBar = "bar"
WidgetTypeLine = "line"
WidgetTypePie = "pie"
) )

View File

@@ -78,3 +78,7 @@ func (i *Index) Truncate() error {
i.internal = index i.internal = index
return nil return nil
} }
func (i *Index) Close() error {
return i.internal.Close()
}

View File

@@ -16,9 +16,10 @@ const (
Admin string = "admin" Admin string = "admin"
AutomationRead Role = "analyst:automation:read" AutomationRead Role = "analyst:automation:read"
CurrentuserRead Role = "analyst:currentuser:read"
CurrentuserdataRead Role = "analyst:currentuserdata:read" CurrentuserdataRead Role = "analyst:currentuserdata:read"
CurrentuserdataWrite Role = "analyst:currentsettings:write" CurrentuserdataWrite Role = "analyst:currentsettings:write"
CurrentuserRead Role = "analyst:currentuser:read" DashboardRead Role = "analyst:dashboard:read"
FileReadWrite Role = "analyst:file" FileReadWrite Role = "analyst:file"
GroupRead Role = "analyst:group:read" GroupRead Role = "analyst:group:read"
PlaybookRead Role = "analyst:playbook:read" PlaybookRead Role = "analyst:playbook:read"
@@ -26,8 +27,8 @@ const (
SettingsRead Role = "analyst:settings:read" SettingsRead Role = "analyst:settings:read"
TemplateRead Role = "analyst:template:read" TemplateRead Role = "analyst:template:read"
TicketRead Role = "analyst:ticket:read" TicketRead Role = "analyst:ticket:read"
TickettypeRead Role = "analyst:tickettype:read"
TicketWrite Role = "analyst:ticket:write" TicketWrite Role = "analyst:ticket:write"
TickettypeRead Role = "analyst:tickettype:read"
UserRead Role = "analyst:user:read" UserRead Role = "analyst:user:read"
AutomationWrite Role = "engineer:automation:write" AutomationWrite Role = "engineer:automation:write"
@@ -36,17 +37,18 @@ const (
TemplateWrite Role = "engineer:template:write" TemplateWrite Role = "engineer:template:write"
TickettypeWrite Role = "engineer:tickettype:write" TickettypeWrite Role = "engineer:tickettype:write"
BackupRead Role = "admin:backup:read" BackupRead Role = "admin:backup:read"
BackupRestore Role = "admin:backup:restore" BackupRestore Role = "admin:backup:restore"
GroupWrite Role = "admin:group:write" DashboardWrite Role = "admin:dashboard:write"
JobWrite Role = "admin:job:write" GroupWrite Role = "admin:group:write"
JobRead Role = "admin:job:read" JobRead Role = "admin:job:read"
LogRead Role = "admin:log:read" JobWrite Role = "admin:job:write"
UserdataRead Role = "admin:userdata:read" LogRead Role = "admin:log:read"
UserdataWrite Role = "admin:userdata:write" SettingsWrite Role = "admin:settings:write"
SettingsWrite Role = "admin:settings:write" TicketDelete Role = "admin:ticket:delete"
TicketDelete Role = "admin:ticket:delete" UserWrite Role = "admin:user:write"
UserWrite Role = "admin:user:write" UserdataRead Role = "admin:userdata:read"
UserdataWrite Role = "admin:userdata:write"
) )
func (p Role) String() string { func (p Role) String() string {
@@ -146,7 +148,7 @@ func List() []Role {
TicketWrite, UserRead, AutomationWrite, PlaybookWrite, RuleWrite, TicketWrite, UserRead, AutomationWrite, PlaybookWrite, RuleWrite,
TemplateWrite, TickettypeWrite, BackupRead, BackupRestore, GroupWrite, TemplateWrite, TickettypeWrite, BackupRead, BackupRestore, GroupWrite,
LogRead, UserdataWrite, TicketDelete, UserWrite, JobRead, JobWrite, LogRead, UserdataWrite, TicketDelete, UserWrite, JobRead, JobWrite,
SettingsWrite, SettingsWrite, DashboardRead, DashboardWrite,
} }
} }

49
service/dashboard.go Normal file
View 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)
}

View File

@@ -78,6 +78,27 @@ func SetupTestData(ctx context.Context, db *database.Database) error {
return err 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 return nil
} }

View File

@@ -84,7 +84,7 @@ func Index(t *testing.T) (*index.Index, func(), error) {
if err != nil { if err != nil {
return nil, nil, err 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) { func Bus(t *testing.T) (context.Context, *catalyst.Config, *bus.Bus, error) {

View File

@@ -2,7 +2,7 @@
<v-app class="background"> <v-app class="background">
<v-navigation-drawer dark permanent :mini-variant="mini" :expand-on-hover="mini" app color="statusbar"> <v-navigation-drawer dark permanent :mini-variant="mini" :expand-on-hover="mini" app color="statusbar">
<v-list> <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-list-item-avatar rounded="0">
<v-img src="/flask_white.svg" :width="40"></v-img> <v-img src="/flask_white.svg" :width="40"></v-img>
</v-list-item-avatar> </v-list-item-avatar>
@@ -182,6 +182,7 @@ export default Vue.extend({
}, },
internal: function (): Array<any> { internal: function (): Array<any> {
return [ return [
{ icon: "mdi-view-dashboard", name: "Dashboards", to: "DashboardList", role: "admin:dashboard:write" },
{ icon: "mdi-check-bold", name: "Open Tasks", to: "TaskList", count: this.$store.state.task_count }, { icon: "mdi-check-bold", name: "Open Tasks", to: "TaskList", count: this.$store.state.task_count },
] ]
}, },

View File

@@ -290,6 +290,50 @@ export interface Context {
*/ */
'ticket'?: TicketResponse; '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 * @export
@@ -2279,6 +2323,52 @@ export interface UserResponse {
*/ */
'roles': Array<string>; '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 * AutomationsApi - axios parameter creator
@@ -2657,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 * GraphApi - axios parameter creator
* @export * @export

View File

@@ -68,19 +68,26 @@ const v = new Vue({
}).$mount("#app"); }).$mount("#app");
axios.interceptors.response.use( axios.interceptors.response.use(
response => response, // response => response,
response => {
lodash.unset(response.data, 'notoast');
return Promise.resolve(response);
},
error => { error => {
console.log(error) if (!lodash.has(error.response.data, 'notoast')) {
if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) { if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) {
const problem = error.response.data as Problem; const problem = error.response.data as Problem;
v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail }); v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail });
return Promise.reject(error); 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); return Promise.reject(error);
} }
); );

View File

@@ -15,13 +15,15 @@ import Rule from "../views/Rule.vue";
import RuleList from "../views/RuleList.vue"; import RuleList from "../views/RuleList.vue";
import Template from "../views/Template.vue"; import Template from "../views/Template.vue";
import TemplateList from "../views/TemplateList.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 API from "../views/API.vue";
import User from '../views/User.vue'; import User from '../views/User.vue';
import UserList from "@/views/UserList.vue"; import UserList from "@/views/UserList.vue";
import Job from '../views/Job.vue'; import Job from '../views/Job.vue';
import JobList from "@/views/JobList.vue"; import JobList from "@/views/JobList.vue";
import GroupList from "@/views/GroupList.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 Group from "@/views/Group.vue";
import TicketType from '../views/TicketType.vue'; import TicketType from '../views/TicketType.vue';
import TicketTypeList from "@/views/TicketTypeList.vue"; import TicketTypeList from "@/views/TicketTypeList.vue";
@@ -59,10 +61,10 @@ const routes: Array<RouteConfig> = [
}, },
{ {
path: "/dashboard", path: "/home",
name: "Dashboard", name: "Home",
component: Dashboard, component: Home,
meta: { title: "Dashboard" }, meta: { title: "Home" },
}, },
{ {
@@ -227,6 +229,21 @@ const routes: Array<RouteConfig> = [
] ]
}, },
{
path: "/dashboards",
name: "DashboardList",
component: DashboardList,
meta: { title: "Dashboards" },
children: [
{
path: ":id",
name: "Dashboard",
component: Dashboard,
},
]
},
{ {
path: "/settings", path: "/settings",
name: "Settings", name: "Settings",

View File

@@ -29,7 +29,7 @@ import {
SettingsApi, SettingsApi,
SettingsApiFactory, SettingsApiFactory,
JobsApi, JobsApi,
JobsApiFactory, JobsApiFactory, DashboardsApiFactory, DashboardsApi,
} from "@/client"; } from "@/client";
const config = new Configuration({ const config = new Configuration({
@@ -56,7 +56,8 @@ export const API: TicketsApi &
SettingsApi & SettingsApi &
TickettypesApi & TickettypesApi &
JobsApi & JobsApi &
TasksApi = Object.assign( TasksApi &
DashboardsApi = Object.assign(
{}, {},
TicketsApiFactory(config), TicketsApiFactory(config),
PlaybooksApiFactory(config), PlaybooksApiFactory(config),
@@ -74,5 +75,6 @@ export const API: TicketsApi &
TickettypesApiFactory(config), TickettypesApiFactory(config),
TasksApiFactory(config), TasksApiFactory(config),
SettingsApiFactory(config), SettingsApiFactory(config),
JobsApiFactory(config) JobsApiFactory(config),
DashboardsApiFactory(config)
); );

View File

@@ -1,7 +1,7 @@
import Vue from "vue"; import Vue from "vue";
import Vuex, {ActionContext} from "vuex"; import Vuex, {ActionContext} from "vuex";
import {API} from "@/services/api"; import {API} from "@/services/api";
import {UserData, TicketList, Settings, UserResponse, SettingsResponse} from "@/client"; import {UserData, TicketList, UserResponse, SettingsResponse} from "@/client";
import {AxiosResponse} from "axios"; import {AxiosResponse} from "axios";
import {Alert} from "@/types/types"; import {Alert} from "@/types/types";
import {templateStore} from "./modules/templates"; import {templateStore} from "./modules/templates";
@@ -19,7 +19,7 @@ export default new Vuex.Store({
counts: {} as Record<string, number>, counts: {} as Record<string, number>,
task_count: 0 as number, task_count: 0 as number,
settings: {} as Settings, settings: {} as SettingsResponse,
userdata: {} as UserData, userdata: {} as UserData,
alert: {} as Alert, alert: {} as Alert,
@@ -46,7 +46,7 @@ export default new Vuex.Store({
setUserData (state, msg: UserData) { setUserData (state, msg: UserData) {
state.userdata = msg state.userdata = msg
}, },
setSettings (state, msg: Settings) { setSettings (state, msg: SettingsResponse) {
state.settings = msg state.settings = msg
}, },
setAlert (state, msg: Alert) { setAlert (state, msg: Alert) {

View File

@@ -1,110 +1,123 @@
<template> <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-row>
<v-col v-if="statistics" cols="12" lg="7"> <v-col v-for="(widget, index) in dashboard.widgets" :key="index" :cols="widget.width">
<v-row> <v-card class="mb-2">
<v-col cols="4"> <v-card-title>
<v-subheader>Unassigned tickets</v-subheader> <span v-if="!editmode">{{ widget.name }}</span>
<span style="font-size: 60pt; text-align: center; display: block"> <v-text-field v-else outlined dense hide-details v-model="widget.name" class="mr-1"></v-text-field>
<router-link :to="{ <v-btn v-if="editmode" outlined @click="removeWidget(index)">
name: 'TicketList', <v-icon>mdi-close</v-icon>
params: { query: 'status == \'open\' AND !owner' } Remove
}"> </v-btn>
{{ statistics.unassigned }} </v-card-title>
</router-link>
</span> <v-card-text v-if="editmode">
<v-subheader>Your tickets</v-subheader> <v-row>
<span style="font-size: 60pt; text-align: center; display: block"> <v-col cols="8">
<router-link :to="{ <v-select label="Type" v-model="widget.type" :items="['line', 'bar', 'pie']"></v-select>
name: 'TicketList', </v-col>
params: { query: 'status == \'open\' AND owner == \'' + $store.state.user.id + '\'' } <v-col cols="4">
}"> <v-text-field label="Width" type="number" v-model="widget.width"></v-text-field>
{{ $store.state.user.id in statistics.open_tickets_per_user ? statistics.open_tickets_per_user[$store.state.user.id] : 0 }} </v-col>
</router-link> </v-row>
</span> <v-text-field label="Aggregation" v-model="widget.aggregation"></v-text-field>
</v-col> <v-text-field label="Filter" v-model="widget.filter" clearable></v-text-field>
<v-col cols="8">
<v-subheader>Open tickets per owner</v-subheader> </v-card-text>
<bar-chart
v-if="open_tickets_per_user" <v-card-text v-if="data[index] === null">
:chart-data="open_tickets_per_user" {{ widgetErrors[index] }}
:styles="{ </v-card-text>
width: '100%', <div v-else>
'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 <line-chart
v-if="tickets_per_week" v-if="widget.type === 'line' && data[index]"
:chart-data="tickets_per_week" :chart-data="data[index]"
:styles="{ width: '100%', position: 'relative' }" :styles="{ width: '100%', position: 'relative' }"
:chart-options="{ :chart-options="{
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
legend: undefined, legend: false,
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] } scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
}" }"
> >
</line-chart> </line-chart>
</v-col>
<v-col cols="5">
<v-subheader>Ticket Types</v-subheader>
<pie-chart <pie-chart
v-if="tickets_per_type" v-if="widget.type === 'pie' && data[index]"
:chart-data="tickets_per_type" :chart-data="data[index]"
:styles="{ width: '100%', position: 'relative' }" :styles="{ width: '100%', position: 'relative' }"
:chart-options="{ :chart-options="{
onClick: clickPie, responsive: true,
hover: { maintainAspectRatio: false,
onHover: function(e) { }"
var point = this.getElementAtEvent(e);
if (point.length) e.target.style.cursor = 'pointer';
else e.target.style.cursor = 'default';
}
}
}"
> >
</pie-chart> </pie-chart>
</v-col>
</v-row> <bar-chart
</v-col> v-if="widget.type === 'bar' && data[index]"
<v-col cols="12" lg="5"> :chart-data="data[index]"
<TicketList :type="this.$route.params.type" @click="open"></TicketList> :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-col>
</v-row> </v-row>
</v-main> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; 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 LineChart from "../components/charts/Line";
import BarChart from "../components/charts/Bar"; import BarChart from "../components/charts/Bar";
import PieChart from "../components/charts/Doughnut"; import PieChart from "../components/charts/Doughnut";
import { API } from "@/services/api"; import {ChartData} from "chart.js";
import {Statistics, TicketResponse} from "@/client"; import {AxiosError, AxiosTransformer} from "axios";
import {DateTime} from "luxon";
import { colors } from "@/plugins/vuetify"; interface State {
import TicketList from "@/components/TicketList.vue"; dashboard?: DashboardResponse;
import { createHash } from "crypto"; undodashboard?: DashboardResponse;
data: Record<string, any>;
editmode: boolean;
widgetErrors: Record<number, string>;
}
export default Vue.extend({ export default Vue.extend({
name: "Dashboard", name: "Dashboard",
@@ -112,108 +125,130 @@ export default Vue.extend({
LineChart, LineChart,
BarChart, BarChart,
PieChart, PieChart,
TicketList
}, },
data() { data: (): State => ({
return { dashboard: undefined,
statistics: (undefined as unknown) as Statistics undodashboard: undefined,
}; data: {},
}, editmode: false,
computed: { widgetErrors: {},
tickets_per_type: function () { }),
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] } watch: {
this.lodash.forEach(this.statistics.tickets_per_type, (count, type) => { $route: function () {
data.labels.push(type); this.loadDashboard();
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: { methods: {
open: function (ticket: TicketResponse) { edit: function () {
if (ticket.id === undefined) { this.undodashboard = this.lodash.cloneDeep(this.dashboard);
return; this.editmode = true;
},
save: function () {
if (!this.dashboard) {
return
} }
this.$router.push({ let widgets = [] as Array<Widget>;
name: "Ticket", this.lodash.forEach(this.dashboard.widgets, (widget) => {
params: {type: '-', id: ticket.id.toString()} widget.width = this.lodash.toInteger(widget.width);
}); if (!widget.filter) {
}, this.lodash.unset(widget, "filter")
clickUser: function (evt, elem) { }
let owner = this.open_tickets_per_user.labels[elem[0]._index]; widgets.push(widget);
let query = 'status == \'open\' AND owner == \'' + owner + '\''; })
this.dashboard.widgets = widgets;
if (owner == 'unassigned') { if (this.$route.params.id === 'new') {
query = 'status == \'open\' AND !owner'; 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({ this.dashboard.widgets.push({name: "new widget", width: 6, aggregation: "", type: "line"})
name: "TicketList",
params: {query: query}
});
}, },
clickPie: function (evt, elem) { removeWidget: function (id: number) {
this.$router.push({ if (!this.dashboard) {
name: "TicketList", return
params: {type: this.tickets_per_type.labels[elem[0]._index]} }
});
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 { color: function (s: string): string {
let pos = createHash('md5').update(s).digest().readUInt32BE(0) % colors.length; let pos = createHash('md5').update(s).digest().readUInt32BE(0) % colors.length;
return colors[pos]; 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() { mounted() {
this.fillData(); this.loadDashboard();
} }
}); });
</script> </script>
<style>
canvas {
position: relative !important;
margin: 0 auto;
}
</style>

View 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
View 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>