diff --git a/database/dashboard.go b/database/dashboard.go new file mode 100644 index 0000000..5280222 --- /dev/null +++ b/database/dashboard.go @@ -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 +} diff --git a/database/db.go b/database/db.go index 8a5bd8c..c43dc94 100644 --- a/database/db.go +++ b/database/db.go @@ -26,6 +26,7 @@ const ( TicketTypeCollectionName = "tickettypes" JobCollectionName = "jobs" SettingsCollectionName = "settings" + DashboardCollectionName = "dashboards" TicketArtifactsGraphName = "Graph" RelatedTicketsCollectionName = "related" @@ -46,6 +47,7 @@ type Database struct { tickettypeCollection *busdb.Collection jobCollection *busdb.Collection settingsCollection *busdb.Collection + dashboardCollection *busdb.Collection relatedCollection *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 { return nil, err } + dashboardCollection, err := arangoDB.Collection(ctx, DashboardCollectionName) + if err != nil { + return nil, err + } hookedDB, err := busdb.NewDatabase(ctx, arangoDB, bus) if err != nil { @@ -149,6 +155,7 @@ func New(ctx context.Context, index *index.Index, bus *bus.Bus, hooks *hooks.Hoo tickettypeCollection: busdb.NewCollection(tickettypeCollection, hookedDB), jobCollection: busdb.NewCollection(jobCollection, hookedDB), settingsCollection: busdb.NewCollection(settingsCollection, hookedDB), + dashboardCollection: busdb.NewCollection(dashboardCollection, hookedDB), } return db, nil @@ -197,5 +204,6 @@ func (db *Database) Truncate(ctx context.Context) { db.jobCollection.Truncate(ctx) db.relatedCollection.Truncate(ctx) db.settingsCollection.Truncate(ctx) + db.dashboardCollection.Truncate(ctx) // db.containsCollection.Truncate(ctx) } diff --git a/database/migrations/migrations.go b/database/migrations/migrations.go index 19516ae..888e6f6 100644 --- a/database/migrations/migrations.go +++ b/database/migrations/migrations.go @@ -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"}}}, &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 } diff --git a/database/statistics.go b/database/statistics.go index 55f5394..a4f5bfa 100644 --- a/database/statistics.go +++ b/database/statistics.go @@ -2,7 +2,9 @@ package database import ( "context" + "fmt" + "github.com/SecurityBrewery/catalyst/caql" "github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/generated/model" ) @@ -41,3 +43,49 @@ func (db *Database) Statistics(ctx context.Context) (*model.Statistics, error) { return &statistics, nil } + +func (db *Database) WidgetData(ctx context.Context, aggregation string, filter *string) (map[string]interface{}, error) { + parser := &caql.Parser{Searcher: db.Index, Prefix: "d."} + + queryTree, err := parser.Parse(aggregation) + if err != nil { + return nil, fmt.Errorf("invalid aggregation query (%s): syntax error\n", aggregation) + } + aggregationString, err := queryTree.String() + if err != nil { + return nil, fmt.Errorf("invalid widget aggregation query (%s): %w", aggregation, err) + } + aggregation = aggregationString + + filterQ := "" + if filter != nil && *filter != "" { + queryTree, err := parser.Parse(*filter) + if err != nil { + return nil, fmt.Errorf("invalid filter query (%s): syntax error\n", *filter) + } + filterString, err := queryTree.String() + if err != nil { + return nil, fmt.Errorf("invalid widget filter query (%s): %w", *filter, err) + } + + filterQ = "FILTER " + filterString + } + + query := `RETURN MERGE(FOR d in tickets + ` + filterQ + ` + COLLECT field = ` + aggregation + ` WITH COUNT INTO count + RETURN ZIP([field], [count]))` + + cur, _, err := db.Query(ctx, query, nil, busdb.ReadOperation) + if err != nil { + return nil, err + } + defer cur.Close() + + statistics := map[string]interface{}{} + if _, err := cur.ReadDocument(ctx, &statistics); err != nil { + return nil, err + } + + return statistics, nil +} diff --git a/database/ticket.go b/database/ticket.go index 70b5091..547de3e 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -449,8 +449,6 @@ func (db *Database) TicketDelete(ctx context.Context, ticketID int64) error { func (db *Database) TicketList(ctx context.Context, ticketType string, query string, sorts []string, desc []bool, offset, count int64) (*model.TicketList, error) { binVars := map[string]interface{}{} - parser := &caql.Parser{Searcher: db.Index, Prefix: "d."} - var typeString = "" if ticketType != "" { typeString = "FILTER d.type == @type " @@ -459,6 +457,7 @@ func (db *Database) TicketList(ctx context.Context, ticketType string, query str var filterString = "" if query != "" { + parser := &caql.Parser{Searcher: db.Index, Prefix: "d."} queryTree, err := parser.Parse(query) if err != nil { return nil, errors.New("invalid filter query: syntax error") diff --git a/definition/dashboards.yaml b/definition/dashboards.yaml new file mode 100644 index 0000000..7445cff --- /dev/null +++ b/definition/dashboards.yaml @@ -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 } \ No newline at end of file diff --git a/definition/settings.yaml b/definition/settings.yaml index 69f296c..5d56a31 100644 --- a/definition/settings.yaml +++ b/definition/settings.yaml @@ -29,10 +29,10 @@ paths: - { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" } - { icon: "mdi-check", id: "clean", name: "Clean", color: "success" } roles: [ - "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", + "admin: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: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", @@ -67,10 +67,10 @@ paths: - { icon: "mdi-skull", id: "malicious", name: "Malicious", color: "error" } - { icon: "mdi-check", id: "clean", name: "Clean", color: "success" } roles: [ - "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", + "admin: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: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", diff --git a/definition/statistics.yaml b/definition/statistics.yaml deleted file mode 100644 index acfd678..0000000 --- a/definition/statistics.yaml +++ /dev/null @@ -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 } } diff --git a/definition/users.yaml b/definition/users.yaml index 3bec567..e84b77d 100644 --- a/definition/users.yaml +++ b/definition/users.yaml @@ -12,7 +12,7 @@ paths: description: "successful operation" schema: { $ref: "#/definitions/UserResponse" } examples: - test: { id: bob, roles: [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" ] } ] /users: @@ -26,8 +26,8 @@ paths: schema: { type: array, items: { $ref: "#/definitions/UserResponse" } } examples: test: - - { id: bob, blocked: false, roles: [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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: script, roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true } + - { id: bob, blocked: false, roles: [ "admin:backup:read", "admin:backup:restore", "admin:dashboard:write", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin:settings:write", "admin:ticket:delete", "admin:user:write", "admin:userdata:read", "admin:userdata:write", "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], apikey: false } + - { id: script, roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true } security: [ { roles: [ "user:read" ] } ] post: tags: [ "users" ] @@ -40,7 +40,7 @@ paths: description: "successful operation" schema: { $ref: "#/definitions/NewUserResponse" } examples: - test: { id: "syncscript", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], secret: "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH", blocked: false } + test: { id: "syncscript", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], secret: "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH", blocked: false } security: [ { roles: [ "user:write" ] } ] /users/{id}: get: @@ -54,7 +54,7 @@ paths: description: "successful operation" schema: { $ref: "#/definitions/UserResponse" } examples: - test: { id: "script", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true } + test: { id: "script", roles: [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ], blocked: false, apikey: true } security: [ { roles: [ "user:read" ] } ] put: tags: [ "users" ] @@ -70,7 +70,7 @@ paths: examples: test: id: bob - roles: [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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 blocked: false security: [ { roles: [ "user:write" ] } ] diff --git a/generated/api/server.go b/generated/api/server.go index 8be8c44..5b90cd2 100755 --- a/generated/api/server.go +++ b/generated/api/server.go @@ -19,6 +19,12 @@ type Service interface { CurrentUser(context.Context) (*model.UserResponse, error) CurrentUserData(context.Context) (*model.UserDataResponse, error) UpdateCurrentUserData(context.Context, *model.UserData) (*model.UserDataResponse, error) + DashboardData(context.Context, string, *string) (map[string]interface{}, error) + ListDashboards(context.Context) ([]*model.DashboardResponse, error) + CreateDashboard(context.Context, *model.Dashboard) (*model.DashboardResponse, error) + GetDashboard(context.Context, string) (*model.DashboardResponse, error) + UpdateDashboard(context.Context, string, *model.Dashboard) (*model.DashboardResponse, error) + DeleteDashboard(context.Context, string) error ListJobs(context.Context) ([]*model.JobResponse, error) RunJob(context.Context, *model.JobForm) (*model.JobResponse, error) GetJob(context.Context, string) (*model.JobResponse, error) @@ -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{"currentuserdata:read"})).Get("/currentuserdata", s.currentUserDataHandler) r.With(roleAuth([]string{"currentuserdata:write"})).Put("/currentuserdata", s.updateCurrentUserDataHandler) + r.With(roleAuth([]string{"dashboard:read"})).Get("/dashboard/data", s.dashboardDataHandler) + r.With(roleAuth([]string{"dashboard:read"})).Get("/dashboards", s.listDashboardsHandler) + r.With(roleAuth([]string{"dashboard:write"})).Post("/dashboards", s.createDashboardHandler) + r.With(roleAuth([]string{"dashboard:read"})).Get("/dashboards/{id}", s.getDashboardHandler) + r.With(roleAuth([]string{"dashboard:write"})).Put("/dashboards/{id}", s.updateDashboardHandler) + r.With(roleAuth([]string{"dashboard:write"})).Delete("/dashboards/{id}", s.deleteDashboardHandler) r.With(roleAuth([]string{"job:read"})).Get("/jobs", s.listJobsHandler) r.With(roleAuth([]string{"job:write"})).Post("/jobs", s.runJobHandler) r.With(roleAuth([]string{"job:read"})).Get("/jobs/{id}", s.getJobHandler) @@ -247,6 +259,77 @@ func (s *server) updateCurrentUserDataHandler(w http.ResponseWriter, r *http.Req response(w, result, err) } +func (s *server) dashboardDataHandler(w http.ResponseWriter, r *http.Request) { + aggregationP := r.URL.Query().Get("aggregation") + + filterP := r.URL.Query().Get("filter") + + result, err := s.service.DashboardData(r.Context(), aggregationP, &filterP) + response(w, result, err) +} + +func (s *server) listDashboardsHandler(w http.ResponseWriter, r *http.Request) { + result, err := s.service.ListDashboards(r.Context()) + response(w, result, err) +} + +func (s *server) createDashboardHandler(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + JSONError(w, err) + return + } + + if validateSchema(body, model.DashboardSchema, w) { + return + } + + var templateP *model.Dashboard + if err := parseBody(body, &templateP); err != nil { + JSONError(w, err) + return + } + + result, err := s.service.CreateDashboard(r.Context(), templateP) + response(w, result, err) +} + +func (s *server) getDashboardHandler(w http.ResponseWriter, r *http.Request) { + idP := chi.URLParam(r, "id") + + result, err := s.service.GetDashboard(r.Context(), idP) + response(w, result, err) +} + +func (s *server) updateDashboardHandler(w http.ResponseWriter, r *http.Request) { + idP := chi.URLParam(r, "id") + + body, err := io.ReadAll(r.Body) + if err != nil { + JSONError(w, err) + return + } + + if validateSchema(body, model.DashboardSchema, w) { + return + } + + var dashboardP *model.Dashboard + if err := parseBody(body, &dashboardP); err != nil { + JSONError(w, err) + return + } + + result, err := s.service.UpdateDashboard(r.Context(), idP, dashboardP) + response(w, result, err) +} + +func (s *server) deleteDashboardHandler(w http.ResponseWriter, r *http.Request) { + idP := chi.URLParam(r, "id") + + response(w, nil, s.service.DeleteDashboard(r.Context(), idP)) +} + func (s *server) listJobsHandler(w http.ResponseWriter, r *http.Request) { result, err := s.service.ListJobs(r.Context()) response(w, result, err) diff --git a/generated/api/test_api.go b/generated/api/test_api.go index 932b771..ee210f5 100755 --- a/generated/api/test_api.go +++ b/generated/api/test_api.go @@ -68,7 +68,7 @@ var Tests = []struct { Args: Args{Method: "Get", URL: "/currentuser"}, Want: Want{ Status: 200, - Body: map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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", Args: Args{Method: "Get", URL: "/jobs"}, @@ -185,7 +239,7 @@ var Tests = []struct { Args: Args{Method: "Get", URL: "/settings"}, Want: Want{ Status: 200, - Body: map[string]interface{}{"artifactKinds": []interface{}{map[string]interface{}{"icon": "mdi-server", "id": "asset", "name": "Asset"}, map[string]interface{}{"icon": "mdi-bullseye", "id": "ioc", "name": "IOC"}}, "artifactStates": []interface{}{map[string]interface{}{"color": "info", "icon": "mdi-help-circle-outline", "id": "unknown", "name": "Unknown"}, map[string]interface{}{"color": "error", "icon": "mdi-skull", "id": "malicious", "name": "Malicious"}, map[string]interface{}{"color": "success", "icon": "mdi-check", "id": "clean", "name": "Clean"}}, "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin: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"}}, Want: Want{ Status: 200, - Body: map[string]interface{}{"artifactKinds": []interface{}{map[string]interface{}{"icon": "mdi-server", "id": "asset", "name": "Asset"}, map[string]interface{}{"icon": "mdi-bullseye", "id": "ioc", "name": "IOC"}}, "artifactStates": []interface{}{map[string]interface{}{"color": "info", "icon": "mdi-help-circle-outline", "id": "unknown", "name": "Unknown"}, map[string]interface{}{"color": "error", "icon": "mdi-skull", "id": "malicious", "name": "Malicious"}, map[string]interface{}{"color": "success", "icon": "mdi-check", "id": "clean", "name": "Clean"}}, "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin: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"}, Want: Want{ Status: 200, - Body: []interface{}{map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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"}}}, Want: Want{ Status: 200, - Body: map[string]interface{}{"blocked": false, "id": "syncscript", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read"}, "secret": "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"}, + Body: map[string]interface{}{"blocked": false, "id": "syncscript", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read"}, "secret": "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH"}, }, }, @@ -572,7 +626,7 @@ var Tests = []struct { Args: Args{Method: "Get", URL: "/users/script"}, Want: Want{ Status: 200, - Body: map[string]interface{}{"apikey": true, "blocked": false, "id": "script", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}}, + Body: map[string]interface{}{"apikey": true, "blocked": false, "id": "script", "roles": []interface{}{"analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write"}}, }, }, @@ -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"}}}, Want: Want{ Status: 200, - Body: map[string]interface{}{"apikey": false, "blocked": false, "id": "bob", "roles": []interface{}{"admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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"}}, }, }, diff --git a/generated/catalyst.json b/generated/catalyst.json index c70b7a2..73a09f2 100644 --- a/generated/catalyst.json +++ b/generated/catalyst.json @@ -225,7 +225,7 @@ "apikey" : false, "blocked" : false, "id" : "bob", - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" } }, + "/dashboard/data" : { + "get" : { + "operationId" : "dashboardData", + "parameters" : [ { + "description" : "Aggregation", + "example" : "type", + "in" : "query", + "name" : "aggregation", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "description" : "Filter", + "example" : "status == \"closed\"", + "in" : "query", + "name" : "filter", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object" + } + }, + "test" : { + "example" : { + "alert" : 2, + "incident" : 1 + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:read" ] + } ], + "summary" : "Get widget data", + "tags" : [ "dashboards" ] + } + }, + "/dashboards" : { + "get" : { + "operationId" : "listDashboards", + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "items" : { + "$ref" : "#/components/schemas/DashboardResponse" + }, + "type" : "array" + } + }, + "test" : { + "example" : [ { + "id" : "simple", + "name" : "Simple", + "widgets" : [ { + "aggregation" : "owner", + "filter" : "status == \"open\"", + "name" : "open_tickets_per_user", + "type" : "bar", + "width" : 4 + }, { + "aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))", + "name" : "tickets_per_week", + "type" : "line", + "width" : 8 + } ] + } ] + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:read" ] + } ], + "summary" : "List dashboards", + "tags" : [ "dashboards" ] + }, + "post" : { + "operationId" : "createDashboard", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Dashboard" + } + } + }, + "description" : "New template", + "required" : true + }, + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DashboardResponse" + } + }, + "test" : { + "example" : { + "id" : "my-dashboard", + "name" : "My Dashboard", + "widgets" : [ ] + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:write" ] + } ], + "summary" : "Create a new dashboard", + "tags" : [ "dashboards" ], + "x-codegen-request-body-name" : "template" + } + }, + "/dashboards/{id}" : { + "delete" : { + "operationId" : "deleteDashboard", + "parameters" : [ { + "description" : "Dashboard ID", + "example" : "simple", + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "content" : { }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:write" ] + } ], + "summary" : "Delete a dashboard", + "tags" : [ "dashboards" ] + }, + "get" : { + "operationId" : "getDashboard", + "parameters" : [ { + "description" : "Dashboard ID", + "example" : "simple", + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DashboardResponse" + } + }, + "test" : { + "example" : { + "id" : "simple", + "name" : "Simple", + "widgets" : [ { + "aggregation" : "owner", + "filter" : "status == \"open\"", + "name" : "open_tickets_per_user", + "type" : "bar", + "width" : 4 + }, { + "aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))", + "name" : "tickets_per_week", + "type" : "line", + "width" : 8 + } ] + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:read" ] + } ], + "summary" : "Get a single dashboard", + "tags" : [ "dashboards" ] + }, + "put" : { + "operationId" : "updateDashboard", + "parameters" : [ { + "description" : "Dashboard ID", + "example" : "simple", + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Dashboard" + } + } + }, + "description" : "Dashboard object that needs to be added", + "required" : true + }, + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DashboardResponse" + } + }, + "test" : { + "example" : { + "id" : "simple", + "name" : "Simple", + "widgets" : [ ] + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:write" ] + } ], + "summary" : "Update an existing dashboard", + "tags" : [ "dashboards" ], + "x-codegen-request-body-name" : "dashboard" + } + }, "/graph/{col}/{id}" : { "get" : { "operationId" : "graph", @@ -1173,7 +1424,7 @@ "id" : "clean", "name" : "Clean" } ], - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" : [ { "default_playbooks" : [ ], "default_template" : "default", @@ -1262,7 +1513,7 @@ "id" : "clean", "name" : "Clean" } ], - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" : [ { "default_playbooks" : [ ], "default_template" : "default", @@ -5092,12 +5343,12 @@ "apikey" : false, "blocked" : false, "id" : "bob", - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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, "blocked" : false, "id" : "script", - "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] + "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] } ] } }, @@ -5135,7 +5386,7 @@ "example" : { "blocked" : false, "id" : "syncscript", - "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], + "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], "secret" : "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH" } } @@ -5201,7 +5452,7 @@ "apikey" : true, "blocked" : false, "id" : "script", - "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] + "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] } } }, @@ -5250,7 +5501,7 @@ "apikey" : false, "blocked" : false, "id" : "bob", - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" }, + "Dashboard" : { + "properties" : { + "name" : { + "type" : "string" + }, + "widgets" : { + "items" : { + "$ref" : "#/components/schemas/Widget" + }, + "type" : "array" + } + }, + "required" : [ "name", "widgets" ], + "type" : "object" + }, + "DashboardResponse" : { + "properties" : { + "id" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "widgets" : { + "items" : { + "$ref" : "#/components/schemas/Widget" + }, + "type" : "array" + } + }, + "required" : [ "id", "name", "widgets" ], + "type" : "object" + }, "Enrichment" : { "properties" : { "created" : { @@ -6895,6 +7179,30 @@ }, "required" : [ "apikey", "blocked", "id", "roles" ], "type" : "object" + }, + "Widget" : { + "properties" : { + "aggregation" : { + "type" : "string" + }, + "filter" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "type" : { + "enum" : [ "bar", "line", "pie" ], + "type" : "string" + }, + "width" : { + "maximum" : 12, + "minimum" : 1, + "type" : "integer" + } + }, + "required" : [ "aggregation", "name", "type", "width" ], + "type" : "object" } } }, diff --git a/generated/catalyst.yml b/generated/catalyst.yml index b92a3fd..dee0a40 100644 --- a/generated/catalyst.yml +++ b/generated/catalyst.yml @@ -141,6 +141,33 @@ definitions: ticket: $ref: '#/definitions/TicketResponse' type: object + Dashboard: + properties: + name: + type: string + widgets: + items: + $ref: '#/definitions/Widget' + type: array + required: + - name + - widgets + type: object + DashboardResponse: + properties: + id: + type: string + name: + type: string + widgets: + items: + $ref: '#/definitions/Widget' + type: array + required: + - id + - name + - widgets + type: object Enrichment: properties: created: @@ -1335,6 +1362,30 @@ definitions: - roles - apikey type: object + Widget: + properties: + aggregation: + type: string + filter: + type: string + name: + type: string + type: + enum: + - bar + - line + - pie + type: string + width: + maximum: 12 + minimum: 1 + type: integer + required: + - name + - type + - aggregation + - width + type: object host: . info: description: API for the catalyst incident response platform. @@ -1576,6 +1627,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -1589,6 +1641,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -1659,6 +1712,183 @@ paths: summary: Update current user data tags: - userdata + /dashboard/data: + get: + operationId: dashboardData + parameters: + - description: Aggregation + in: query + name: aggregation + required: true + type: string + x-example: type + - description: Filter + in: query + name: filter + type: string + x-example: status == "closed" + responses: + "200": + description: successful operation + examples: + test: + alert: 2 + incident: 1 + schema: + type: object + security: + - roles: + - dashboard:read + summary: Get widget data + tags: + - dashboards + /dashboards: + get: + operationId: listDashboards + responses: + "200": + description: successful operation + examples: + test: + - id: simple + name: Simple + widgets: + - aggregation: owner + filter: status == "open" + name: open_tickets_per_user + type: bar + width: 4 + - aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) + < 10 ? "0" : "", DATE_ISOWEEK(created))' + name: tickets_per_week + type: line + width: 8 + schema: + items: + $ref: '#/definitions/DashboardResponse' + type: array + security: + - roles: + - dashboard:read + summary: List dashboards + tags: + - dashboards + post: + operationId: createDashboard + parameters: + - description: New template + in: body + name: template + required: true + schema: + $ref: '#/definitions/Dashboard' + x-example: + name: My Dashboard + widgets: [] + responses: + "200": + description: successful operation + examples: + test: + id: my-dashboard + name: My Dashboard + widgets: [] + schema: + $ref: '#/definitions/DashboardResponse' + security: + - roles: + - dashboard:write + summary: Create a new dashboard + tags: + - dashboards + /dashboards/{id}: + delete: + operationId: deleteDashboard + parameters: + - description: Dashboard ID + in: path + name: id + required: true + type: string + x-example: simple + responses: + "204": + description: successful operation + security: + - roles: + - dashboard:write + summary: Delete a dashboard + tags: + - dashboards + get: + operationId: getDashboard + parameters: + - description: Dashboard ID + in: path + name: id + required: true + type: string + x-example: simple + responses: + "200": + description: successful operation + examples: + test: + id: simple + name: Simple + widgets: + - aggregation: owner + filter: status == "open" + name: open_tickets_per_user + type: bar + width: 4 + - aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) + < 10 ? "0" : "", DATE_ISOWEEK(created))' + name: tickets_per_week + type: line + width: 8 + schema: + $ref: '#/definitions/DashboardResponse' + security: + - roles: + - dashboard:read + summary: Get a single dashboard + tags: + - dashboards + put: + operationId: updateDashboard + parameters: + - description: Dashboard ID + in: path + name: id + required: true + type: string + x-example: simple + - description: Dashboard object that needs to be added + in: body + name: dashboard + required: true + schema: + $ref: '#/definitions/Dashboard' + x-example: + name: Simple + widgets: [] + responses: + "200": + description: successful operation + examples: + test: + id: simple + name: Simple + widgets: [] + schema: + $ref: '#/definitions/DashboardResponse' + security: + - roles: + - dashboard:write + summary: Update an existing dashboard + tags: + - dashboards /graph/{col}/{id}: get: operationId: graph @@ -2601,6 +2831,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -2614,6 +2845,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -2720,6 +2952,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -2733,6 +2966,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -7122,6 +7356,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -7135,6 +7370,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -7158,6 +7394,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -7210,6 +7447,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -7270,6 +7508,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -7326,6 +7565,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -7339,6 +7579,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read diff --git a/generated/community.json b/generated/community.json index 28c28a5..464389d 100644 --- a/generated/community.json +++ b/generated/community.json @@ -225,7 +225,7 @@ "apikey" : false, "blocked" : false, "id" : "bob", - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" } }, + "/dashboard/data" : { + "get" : { + "operationId" : "dashboardData", + "parameters" : [ { + "description" : "Aggregation", + "example" : "type", + "in" : "query", + "name" : "aggregation", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "description" : "Filter", + "example" : "status == \"closed\"", + "in" : "query", + "name" : "filter", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object" + } + }, + "test" : { + "example" : { + "alert" : 2, + "incident" : 1 + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:read" ] + } ], + "summary" : "Get widget data", + "tags" : [ "dashboards" ] + } + }, + "/dashboards" : { + "get" : { + "operationId" : "listDashboards", + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "items" : { + "$ref" : "#/components/schemas/DashboardResponse" + }, + "type" : "array" + } + }, + "test" : { + "example" : [ { + "id" : "simple", + "name" : "Simple", + "widgets" : [ { + "aggregation" : "owner", + "filter" : "status == \"open\"", + "name" : "open_tickets_per_user", + "type" : "bar", + "width" : 4 + }, { + "aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))", + "name" : "tickets_per_week", + "type" : "line", + "width" : 8 + } ] + } ] + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:read" ] + } ], + "summary" : "List dashboards", + "tags" : [ "dashboards" ] + }, + "post" : { + "operationId" : "createDashboard", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Dashboard" + } + } + }, + "description" : "New template", + "required" : true + }, + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DashboardResponse" + } + }, + "test" : { + "example" : { + "id" : "my-dashboard", + "name" : "My Dashboard", + "widgets" : [ ] + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:write" ] + } ], + "summary" : "Create a new dashboard", + "tags" : [ "dashboards" ], + "x-codegen-request-body-name" : "template" + } + }, + "/dashboards/{id}" : { + "delete" : { + "operationId" : "deleteDashboard", + "parameters" : [ { + "description" : "Dashboard ID", + "example" : "simple", + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "content" : { }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:write" ] + } ], + "summary" : "Delete a dashboard", + "tags" : [ "dashboards" ] + }, + "get" : { + "operationId" : "getDashboard", + "parameters" : [ { + "description" : "Dashboard ID", + "example" : "simple", + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DashboardResponse" + } + }, + "test" : { + "example" : { + "id" : "simple", + "name" : "Simple", + "widgets" : [ { + "aggregation" : "owner", + "filter" : "status == \"open\"", + "name" : "open_tickets_per_user", + "type" : "bar", + "width" : 4 + }, { + "aggregation" : "CONCAT(DATE_YEAR(created), \"-\", DATE_ISOWEEK(created) < 10 ? \"0\" : \"\", DATE_ISOWEEK(created))", + "name" : "tickets_per_week", + "type" : "line", + "width" : 8 + } ] + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:read" ] + } ], + "summary" : "Get a single dashboard", + "tags" : [ "dashboards" ] + }, + "put" : { + "operationId" : "updateDashboard", + "parameters" : [ { + "description" : "Dashboard ID", + "example" : "simple", + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Dashboard" + } + } + }, + "description" : "Dashboard object that needs to be added", + "required" : true + }, + "responses" : { + "200" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DashboardResponse" + } + }, + "test" : { + "example" : { + "id" : "simple", + "name" : "Simple", + "widgets" : [ ] + } + } + }, + "description" : "successful operation" + } + }, + "security" : [ { + "roles" : [ "dashboard:write" ] + } ], + "summary" : "Update an existing dashboard", + "tags" : [ "dashboards" ], + "x-codegen-request-body-name" : "dashboard" + } + }, "/jobs" : { "get" : { "operationId" : "listJobs", @@ -743,7 +994,7 @@ "id" : "clean", "name" : "Clean" } ], - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" : [ { "default_playbooks" : [ ], "default_template" : "default", @@ -832,7 +1083,7 @@ "id" : "clean", "name" : "Clean" } ], - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" : [ { "default_playbooks" : [ ], "default_template" : "default", @@ -4662,12 +4913,12 @@ "apikey" : false, "blocked" : false, "id" : "bob", - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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, "blocked" : false, "id" : "script", - "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] + "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] } ] } }, @@ -4705,7 +4956,7 @@ "example" : { "blocked" : false, "id" : "syncscript", - "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], + "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read" ], "secret" : "v39bOuobnlEljfWzjAgoKzhmnh1xSMxH" } } @@ -4771,7 +5022,7 @@ "apikey" : true, "blocked" : false, "id" : "script", - "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] + "roles" : [ "analyst:automation:read", "analyst:currentsettings:write", "analyst:currentuser:read", "analyst:currentuserdata:read", "analyst:dashboard:read", "analyst:file", "analyst:group:read", "analyst:playbook:read", "analyst:rule:read", "analyst:settings:read", "analyst:template:read", "analyst:ticket:read", "analyst:ticket:write", "analyst:tickettype:read", "analyst:user:read", "engineer:automation:write", "engineer:playbook:write", "engineer:rule:write", "engineer:template:write", "engineer:tickettype:write" ] } } }, @@ -4820,7 +5071,7 @@ "apikey" : false, "blocked" : false, "id" : "bob", - "roles" : [ "admin:backup:read", "admin:backup:restore", "admin:group:write", "admin:job:read", "admin:job:write", "admin:log:read", "admin: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" }, + "Dashboard" : { + "properties" : { + "name" : { + "type" : "string" + }, + "widgets" : { + "items" : { + "$ref" : "#/components/schemas/Widget" + }, + "type" : "array" + } + }, + "required" : [ "name", "widgets" ], + "type" : "object" + }, + "DashboardResponse" : { + "properties" : { + "id" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "widgets" : { + "items" : { + "$ref" : "#/components/schemas/Widget" + }, + "type" : "array" + } + }, + "required" : [ "id", "name", "widgets" ], + "type" : "object" + }, "Enrichment" : { "properties" : { "created" : { @@ -6316,6 +6600,30 @@ }, "required" : [ "apikey", "blocked", "id", "roles" ], "type" : "object" + }, + "Widget" : { + "properties" : { + "aggregation" : { + "type" : "string" + }, + "filter" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "type" : { + "enum" : [ "bar", "line", "pie" ], + "type" : "string" + }, + "width" : { + "maximum" : 12, + "minimum" : 1, + "type" : "integer" + } + }, + "required" : [ "aggregation", "name", "type", "width" ], + "type" : "object" } } }, diff --git a/generated/community.yml b/generated/community.yml index adffefa..5812322 100644 --- a/generated/community.yml +++ b/generated/community.yml @@ -141,6 +141,33 @@ definitions: ticket: $ref: '#/definitions/TicketResponse' type: object + Dashboard: + properties: + name: + type: string + widgets: + items: + $ref: '#/definitions/Widget' + type: array + required: + - name + - widgets + type: object + DashboardResponse: + properties: + id: + type: string + name: + type: string + widgets: + items: + $ref: '#/definitions/Widget' + type: array + required: + - id + - name + - widgets + type: object Enrichment: properties: created: @@ -1216,6 +1243,30 @@ definitions: - roles - apikey type: object + Widget: + properties: + aggregation: + type: string + filter: + type: string + name: + type: string + type: + enum: + - bar + - line + - pie + type: string + width: + maximum: 12 + minimum: 1 + type: integer + required: + - name + - type + - aggregation + - width + type: object host: . info: description: API for the catalyst incident response platform. @@ -1457,6 +1508,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -1470,6 +1522,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -1540,6 +1593,183 @@ paths: summary: Update current user data tags: - userdata + /dashboard/data: + get: + operationId: dashboardData + parameters: + - description: Aggregation + in: query + name: aggregation + required: true + type: string + x-example: type + - description: Filter + in: query + name: filter + type: string + x-example: status == "closed" + responses: + "200": + description: successful operation + examples: + test: + alert: 2 + incident: 1 + schema: + type: object + security: + - roles: + - dashboard:read + summary: Get widget data + tags: + - dashboards + /dashboards: + get: + operationId: listDashboards + responses: + "200": + description: successful operation + examples: + test: + - id: simple + name: Simple + widgets: + - aggregation: owner + filter: status == "open" + name: open_tickets_per_user + type: bar + width: 4 + - aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) + < 10 ? "0" : "", DATE_ISOWEEK(created))' + name: tickets_per_week + type: line + width: 8 + schema: + items: + $ref: '#/definitions/DashboardResponse' + type: array + security: + - roles: + - dashboard:read + summary: List dashboards + tags: + - dashboards + post: + operationId: createDashboard + parameters: + - description: New template + in: body + name: template + required: true + schema: + $ref: '#/definitions/Dashboard' + x-example: + name: My Dashboard + widgets: [] + responses: + "200": + description: successful operation + examples: + test: + id: my-dashboard + name: My Dashboard + widgets: [] + schema: + $ref: '#/definitions/DashboardResponse' + security: + - roles: + - dashboard:write + summary: Create a new dashboard + tags: + - dashboards + /dashboards/{id}: + delete: + operationId: deleteDashboard + parameters: + - description: Dashboard ID + in: path + name: id + required: true + type: string + x-example: simple + responses: + "204": + description: successful operation + security: + - roles: + - dashboard:write + summary: Delete a dashboard + tags: + - dashboards + get: + operationId: getDashboard + parameters: + - description: Dashboard ID + in: path + name: id + required: true + type: string + x-example: simple + responses: + "200": + description: successful operation + examples: + test: + id: simple + name: Simple + widgets: + - aggregation: owner + filter: status == "open" + name: open_tickets_per_user + type: bar + width: 4 + - aggregation: 'CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) + < 10 ? "0" : "", DATE_ISOWEEK(created))' + name: tickets_per_week + type: line + width: 8 + schema: + $ref: '#/definitions/DashboardResponse' + security: + - roles: + - dashboard:read + summary: Get a single dashboard + tags: + - dashboards + put: + operationId: updateDashboard + parameters: + - description: Dashboard ID + in: path + name: id + required: true + type: string + x-example: simple + - description: Dashboard object that needs to be added + in: body + name: dashboard + required: true + schema: + $ref: '#/definitions/Dashboard' + x-example: + name: Simple + widgets: [] + responses: + "200": + description: successful operation + examples: + test: + id: simple + name: Simple + widgets: [] + schema: + $ref: '#/definitions/DashboardResponse' + security: + - roles: + - dashboard:write + summary: Update an existing dashboard + tags: + - dashboards /jobs: get: operationId: listJobs @@ -2189,6 +2419,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -2202,6 +2433,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -2308,6 +2540,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -2321,6 +2554,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -6710,6 +6944,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -6723,6 +6958,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -6746,6 +6982,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -6798,6 +7035,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -6858,6 +7096,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read @@ -6914,6 +7153,7 @@ paths: roles: - admin:backup:read - admin:backup:restore + - admin:dashboard:write - admin:group:write - admin:job:read - admin:job:write @@ -6927,6 +7167,7 @@ paths: - analyst:currentsettings:write - analyst:currentuser:read - analyst:currentuserdata:read + - analyst:dashboard:read - analyst:file - analyst:group:read - analyst:playbook:read diff --git a/generated/model/model.go b/generated/model/model.go index d4b3c53..bbdd53c 100755 --- a/generated/model/model.go +++ b/generated/model/model.go @@ -16,6 +16,8 @@ var ( CommentSchema = new(gojsonschema.Schema) CommentFormSchema = new(gojsonschema.Schema) ContextSchema = new(gojsonschema.Schema) + DashboardSchema = new(gojsonschema.Schema) + DashboardResponseSchema = new(gojsonschema.Schema) EnrichmentSchema = new(gojsonschema.Schema) EnrichmentFormSchema = new(gojsonschema.Schema) FileSchema = new(gojsonschema.Schema) @@ -60,6 +62,7 @@ var ( UserDataResponseSchema = new(gojsonschema.Schema) UserFormSchema = new(gojsonschema.Schema) UserResponseSchema = new(gojsonschema.Schema) + WidgetSchema = new(gojsonschema.Schema) ) 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":["message"],"$id":"#/definitions/CommentForm"}`), gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifact":{"$ref":"#/definitions/Artifact"},"playbook":{"$ref":"#/definitions/PlaybookResponse"},"task":{"$ref":"#/definitions/TaskResponse"},"ticket":{"$ref":"#/definitions/TicketResponse"}},"$id":"#/definitions/Context"}`), + gojsonschema.NewStringLoader(`{"type":"object","properties":{"name":{"type":"string"},"widgets":{"items":{"$ref":"#/definitions/Widget"},"type":"array"}},"required":["name","widgets"],"$id":"#/definitions/Dashboard"}`), + gojsonschema.NewStringLoader(`{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"widgets":{"items":{"$ref":"#/definitions/Widget"},"type":"array"}},"required":["id","name","widgets"],"$id":"#/definitions/DashboardResponse"}`), gojsonschema.NewStringLoader(`{"type":"object","properties":{"created":{"format":"date-time","type":"string"},"data":{"type":"object"},"name":{"type":"string"}},"required":["name","data","created"],"$id":"#/definitions/Enrichment"}`), gojsonschema.NewStringLoader(`{"type":"object","properties":{"data":{"type":"object"},"name":{"type":"string"}},"required":["name","data"],"$id":"#/definitions/EnrichmentForm"}`), gojsonschema.NewStringLoader(`{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"],"$id":"#/definitions/File"}`), @@ -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":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserForm"}`), gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserResponse"}`), + gojsonschema.NewStringLoader(`{"type":"object","properties":{"aggregation":{"type":"string"},"filter":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["bar","line","pie"]},"width":{"maximum":12,"type":"integer"}},"required":["name","type","aggregation","width"],"$id":"#/definitions/Widget"}`), ) if err != nil { panic(err) @@ -129,6 +135,8 @@ func init() { CommentSchema = mustCompile(`#/definitions/Comment`) CommentFormSchema = mustCompile(`#/definitions/CommentForm`) ContextSchema = mustCompile(`#/definitions/Context`) + DashboardSchema = mustCompile(`#/definitions/Dashboard`) + DashboardResponseSchema = mustCompile(`#/definitions/DashboardResponse`) EnrichmentSchema = mustCompile(`#/definitions/Enrichment`) EnrichmentFormSchema = mustCompile(`#/definitions/EnrichmentForm`) FileSchema = mustCompile(`#/definitions/File`) @@ -173,6 +181,7 @@ func init() { UserDataResponseSchema = mustCompile(`#/definitions/UserDataResponse`) UserFormSchema = mustCompile(`#/definitions/UserForm`) UserResponseSchema = mustCompile(`#/definitions/UserResponse`) + WidgetSchema = mustCompile(`#/definitions/Widget`) } type Artifact struct { @@ -230,6 +239,17 @@ type Context struct { Ticket *TicketResponse `json:"ticket,omitempty"` } +type Dashboard struct { + Name string `json:"name"` + Widgets []*Widget `json:"widgets"` +} + +type DashboardResponse struct { + ID string `json:"id"` + Name string `json:"name"` + Widgets []*Widget `json:"widgets"` +} + type Enrichment struct { Created time.Time `json:"created"` Data map[string]interface{} `json:"data"` @@ -600,6 +620,14 @@ type UserResponse struct { Roles []string `json:"roles"` } +type Widget struct { + Aggregation string `json:"aggregation"` + Filter *string `json:"filter,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Width int `json:"width"` +} + func mustCompile(uri string) *gojsonschema.Schema { s, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(uri)) if err != nil { @@ -632,4 +660,10 @@ const ( TypeColorSuccess = "success" TypeColorWarning = "warning" + + WidgetTypeBar = "bar" + + WidgetTypeLine = "line" + + WidgetTypePie = "pie" ) diff --git a/index/index.go b/index/index.go index c147ec1..cca2a54 100644 --- a/index/index.go +++ b/index/index.go @@ -78,3 +78,7 @@ func (i *Index) Truncate() error { i.internal = index return nil } + +func (i *Index) Close() error { + return i.internal.Close() +} diff --git a/role/role.go b/role/role.go index 6443ff8..4c48074 100644 --- a/role/role.go +++ b/role/role.go @@ -16,9 +16,10 @@ const ( Admin string = "admin" AutomationRead Role = "analyst:automation:read" + CurrentuserRead Role = "analyst:currentuser:read" CurrentuserdataRead Role = "analyst:currentuserdata:read" CurrentuserdataWrite Role = "analyst:currentsettings:write" - CurrentuserRead Role = "analyst:currentuser:read" + DashboardRead Role = "analyst:dashboard:read" FileReadWrite Role = "analyst:file" GroupRead Role = "analyst:group:read" PlaybookRead Role = "analyst:playbook:read" @@ -26,8 +27,8 @@ const ( SettingsRead Role = "analyst:settings:read" TemplateRead Role = "analyst:template:read" TicketRead Role = "analyst:ticket:read" - TickettypeRead Role = "analyst:tickettype:read" TicketWrite Role = "analyst:ticket:write" + TickettypeRead Role = "analyst:tickettype:read" UserRead Role = "analyst:user:read" AutomationWrite Role = "engineer:automation:write" @@ -36,17 +37,18 @@ const ( TemplateWrite Role = "engineer:template:write" TickettypeWrite Role = "engineer:tickettype:write" - BackupRead Role = "admin:backup:read" - BackupRestore Role = "admin:backup:restore" - GroupWrite Role = "admin:group:write" - JobWrite Role = "admin:job:write" - JobRead Role = "admin:job:read" - LogRead Role = "admin:log:read" - UserdataRead Role = "admin:userdata:read" - UserdataWrite Role = "admin:userdata:write" - SettingsWrite Role = "admin:settings:write" - TicketDelete Role = "admin:ticket:delete" - UserWrite Role = "admin:user:write" + BackupRead Role = "admin:backup:read" + BackupRestore Role = "admin:backup:restore" + DashboardWrite Role = "admin:dashboard:write" + GroupWrite Role = "admin:group:write" + JobRead Role = "admin:job:read" + JobWrite Role = "admin:job:write" + LogRead Role = "admin:log:read" + SettingsWrite Role = "admin:settings:write" + TicketDelete Role = "admin:ticket:delete" + UserWrite Role = "admin:user:write" + UserdataRead Role = "admin:userdata:read" + UserdataWrite Role = "admin:userdata:write" ) func (p Role) String() string { @@ -146,7 +148,7 @@ func List() []Role { TicketWrite, UserRead, AutomationWrite, PlaybookWrite, RuleWrite, TemplateWrite, TickettypeWrite, BackupRead, BackupRestore, GroupWrite, LogRead, UserdataWrite, TicketDelete, UserWrite, JobRead, JobWrite, - SettingsWrite, + SettingsWrite, DashboardRead, DashboardWrite, } } diff --git a/service/dashboard.go b/service/dashboard.go new file mode 100644 index 0000000..7a556ed --- /dev/null +++ b/service/dashboard.go @@ -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) +} diff --git a/test/data.go b/test/data.go index e8dbdfe..3b272e3 100644 --- a/test/data.go +++ b/test/data.go @@ -78,6 +78,27 @@ func SetupTestData(ctx context.Context, db *database.Database) error { return err } + if _, err := db.DashboardCreate(ctx, &model.Dashboard{ + Name: "Simple", + Widgets: []*model.Widget{ + { + Name: "open_tickets_per_user", + Type: model.WidgetTypeBar, + Aggregation: "owner", + Filter: pointer.String(`status == "open"`), + Width: 4, + }, + { + Name: "tickets_per_week", + Type: model.WidgetTypeLine, + Aggregation: `CONCAT(DATE_YEAR(created), "-", DATE_ISOWEEK(created) < 10 ? "0" : "", DATE_ISOWEEK(created))`, + Width: 8, + }, + }, + }); err != nil { + return err + } + return nil } diff --git a/test/test.go b/test/test.go index 7a4a8fd..741347e 100644 --- a/test/test.go +++ b/test/test.go @@ -84,7 +84,7 @@ func Index(t *testing.T) (*index.Index, func(), error) { if err != nil { return nil, nil, err } - return catalystIndex, func() { os.RemoveAll(dir) }, nil + return catalystIndex, func() { catalystIndex.Close(); os.RemoveAll(dir) }, nil } func Bus(t *testing.T) (context.Context, *catalyst.Config, *bus.Bus, error) { diff --git a/ui/src/App.vue b/ui/src/App.vue index 65792f3..d2f2932 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -2,7 +2,7 @@ - + @@ -182,6 +182,7 @@ export default Vue.extend({ }, internal: function (): Array { 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 }, ] }, diff --git a/ui/src/client/api.ts b/ui/src/client/api.ts index 9b57d97..4cb23e1 100644 --- a/ui/src/client/api.ts +++ b/ui/src/client/api.ts @@ -290,6 +290,50 @@ export interface Context { */ 'ticket'?: TicketResponse; } +/** + * + * @export + * @interface Dashboard + */ +export interface Dashboard { + /** + * + * @type {string} + * @memberof Dashboard + */ + 'name': string; + /** + * + * @type {Array} + * @memberof Dashboard + */ + 'widgets': Array; +} +/** + * + * @export + * @interface DashboardResponse + */ +export interface DashboardResponse { + /** + * + * @type {string} + * @memberof DashboardResponse + */ + 'id': string; + /** + * + * @type {string} + * @memberof DashboardResponse + */ + 'name': string; + /** + * + * @type {Array} + * @memberof DashboardResponse + */ + 'widgets': Array; +} /** * * @export @@ -2279,6 +2323,52 @@ export interface UserResponse { */ 'roles': Array; } +/** + * + * @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 @@ -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 => { + // 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 => { + // 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 => { + // 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 => { + // 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 => { + 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 => { + // 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> { + 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> { + 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> { + 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> { + 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>> { + 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> { + 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 { + 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 { + 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 { + 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 { + 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> { + 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 { + return localVarFp.updateDashboard(id, dashboard, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DashboardsApi - object-oriented interface + * @export + * @class DashboardsApi + * @extends {BaseAPI} + */ +export class DashboardsApi extends BaseAPI { + /** + * + * @summary Create a new dashboard + * @param {Dashboard} template New template + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DashboardsApi + */ + public createDashboard(template: Dashboard, options?: AxiosRequestConfig) { + return DashboardsApiFp(this.configuration).createDashboard(template, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Get widget data + * @param {string} aggregation Aggregation + * @param {string} [filter] Filter + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DashboardsApi + */ + public dashboardData(aggregation: string, filter?: string, options?: AxiosRequestConfig) { + return DashboardsApiFp(this.configuration).dashboardData(aggregation, filter, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Delete a dashboard + * @param {string} id Dashboard ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DashboardsApi + */ + public deleteDashboard(id: string, options?: AxiosRequestConfig) { + return DashboardsApiFp(this.configuration).deleteDashboard(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Get a single dashboard + * @param {string} id Dashboard ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DashboardsApi + */ + public getDashboard(id: string, options?: AxiosRequestConfig) { + return DashboardsApiFp(this.configuration).getDashboard(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary List dashboards + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DashboardsApi + */ + public listDashboards(options?: AxiosRequestConfig) { + return DashboardsApiFp(this.configuration).listDashboards(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Update an existing dashboard + * @param {string} id Dashboard ID + * @param {Dashboard} dashboard Dashboard object that needs to be added + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DashboardsApi + */ + public updateDashboard(id: string, dashboard: Dashboard, options?: AxiosRequestConfig) { + return DashboardsApiFp(this.configuration).updateDashboard(id, dashboard, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * GraphApi - axios parameter creator * @export diff --git a/ui/src/main.ts b/ui/src/main.ts index ef7a24b..9076f3f 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -68,19 +68,26 @@ const v = new Vue({ }).$mount("#app"); axios.interceptors.response.use( - response => response, + // response => response, + response => { + lodash.unset(response.data, 'notoast'); + + return Promise.resolve(response); + }, error => { - console.log(error) - if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) { - const problem = error.response.data as Problem; - v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail }); - return Promise.reject(error); + if (!lodash.has(error.response.data, 'notoast')) { + if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) { + const problem = error.response.data as Problem; + v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail }); + return Promise.reject(error); + } + if (error.response.data && 'error' in error.response.data) { + v.$store.dispatch("alertError", { name: "Error", detail: error.response.data.error }); + return Promise.reject(error); + } + v.$store.dispatch("alertError", { name: "Error", detail: JSON.stringify(error.response.data) }); } - if (error.response.data && 'error' in error.response.data) { - v.$store.dispatch("alertError", { name: "Error", detail: error.response.data.error }); - return Promise.reject(error); - } - v.$store.dispatch("alertError", { name: "Error", detail: JSON.stringify(error.response.data) }); + return Promise.reject(error); } ); diff --git a/ui/src/router/index.ts b/ui/src/router/index.ts index 565a1f3..d218327 100644 --- a/ui/src/router/index.ts +++ b/ui/src/router/index.ts @@ -15,13 +15,15 @@ import Rule from "../views/Rule.vue"; import RuleList from "../views/RuleList.vue"; import Template from "../views/Template.vue"; import TemplateList from "../views/TemplateList.vue"; +import Dashboard from "../views/Dashboard.vue"; +import DashboardList from "../views/DashboardList.vue"; import API from "../views/API.vue"; import User from '../views/User.vue'; import UserList from "@/views/UserList.vue"; import Job from '../views/Job.vue'; import JobList from "@/views/JobList.vue"; import GroupList from "@/views/GroupList.vue"; -import Dashboard from "@/views/Dashboard.vue"; +import Home from "@/views/Home.vue"; import Group from "@/views/Group.vue"; import TicketType from '../views/TicketType.vue'; import TicketTypeList from "@/views/TicketTypeList.vue"; @@ -59,10 +61,10 @@ const routes: Array = [ }, { - path: "/dashboard", - name: "Dashboard", - component: Dashboard, - meta: { title: "Dashboard" }, + path: "/home", + name: "Home", + component: Home, + meta: { title: "Home" }, }, { @@ -227,6 +229,21 @@ const routes: Array = [ ] }, + + { + path: "/dashboards", + name: "DashboardList", + component: DashboardList, + meta: { title: "Dashboards" }, + children: [ + { + path: ":id", + name: "Dashboard", + component: Dashboard, + }, + ] + }, + { path: "/settings", name: "Settings", diff --git a/ui/src/services/api.ts b/ui/src/services/api.ts index 51b032c..84be3d7 100644 --- a/ui/src/services/api.ts +++ b/ui/src/services/api.ts @@ -29,7 +29,7 @@ import { SettingsApi, SettingsApiFactory, JobsApi, - JobsApiFactory, + JobsApiFactory, DashboardsApiFactory, DashboardsApi, } from "@/client"; const config = new Configuration({ @@ -56,7 +56,8 @@ export const API: TicketsApi & SettingsApi & TickettypesApi & JobsApi & - TasksApi = Object.assign( + TasksApi & + DashboardsApi = Object.assign( {}, TicketsApiFactory(config), PlaybooksApiFactory(config), @@ -74,5 +75,6 @@ export const API: TicketsApi & TickettypesApiFactory(config), TasksApiFactory(config), SettingsApiFactory(config), - JobsApiFactory(config) + JobsApiFactory(config), + DashboardsApiFactory(config) ); diff --git a/ui/src/store/index.ts b/ui/src/store/index.ts index dfd66ab..fd961c4 100644 --- a/ui/src/store/index.ts +++ b/ui/src/store/index.ts @@ -1,7 +1,7 @@ import Vue from "vue"; import Vuex, {ActionContext} from "vuex"; 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 {Alert} from "@/types/types"; import {templateStore} from "./modules/templates"; @@ -19,7 +19,7 @@ export default new Vuex.Store({ counts: {} as Record, task_count: 0 as number, - settings: {} as Settings, + settings: {} as SettingsResponse, userdata: {} as UserData, alert: {} as Alert, @@ -46,7 +46,7 @@ export default new Vuex.Store({ setUserData (state, msg: UserData) { state.userdata = msg }, - setSettings (state, msg: Settings) { + setSettings (state, msg: SettingsResponse) { state.settings = msg }, setAlert (state, msg: Alert) { diff --git a/ui/src/views/Dashboard.vue b/ui/src/views/Dashboard.vue index 8088637..28b9586 100644 --- a/ui/src/views/Dashboard.vue +++ b/ui/src/views/Dashboard.vue @@ -1,110 +1,123 @@ - - diff --git a/ui/src/views/DashboardList.vue b/ui/src/views/DashboardList.vue new file mode 100644 index 0000000..bf7a913 --- /dev/null +++ b/ui/src/views/DashboardList.vue @@ -0,0 +1,64 @@ + + + diff --git a/ui/src/views/Home.vue b/ui/src/views/Home.vue new file mode 100644 index 0000000..77a05b4 --- /dev/null +++ b/ui/src/views/Home.vue @@ -0,0 +1,219 @@ + + + + +