mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 07:12:46 +01:00
Improve user info (#47)
This commit is contained in:
6
cmd/catalyst-dev/images.go
Normal file
6
cmd/catalyst-dev/images.go
Normal file
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/arangodb/go-driver"
|
"github.com/arangodb/go-driver"
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||||
"github.com/SecurityBrewery/catalyst/generated/api"
|
"github.com/SecurityBrewery/catalyst/generated/api"
|
||||||
"github.com/SecurityBrewery/catalyst/generated/model"
|
"github.com/SecurityBrewery/catalyst/generated/model"
|
||||||
|
"github.com/SecurityBrewery/catalyst/generated/pointer"
|
||||||
"github.com/SecurityBrewery/catalyst/hooks"
|
"github.com/SecurityBrewery/catalyst/hooks"
|
||||||
"github.com/SecurityBrewery/catalyst/role"
|
"github.com/SecurityBrewery/catalyst/role"
|
||||||
"github.com/SecurityBrewery/catalyst/test"
|
"github.com/SecurityBrewery/catalyst/test"
|
||||||
@@ -39,12 +41,35 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "eve", Roles: []string{"admin"}})
|
||||||
|
_ = theCatalyst.DB.UserDataCreate(context.Background(), "eve", &model.UserData{
|
||||||
|
Name: pointer.String("Eve"),
|
||||||
|
Email: pointer.String("eve@example.com"),
|
||||||
|
Image: &avatarEve,
|
||||||
|
})
|
||||||
|
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "kevin", Roles: []string{"admin"}})
|
||||||
|
_ = theCatalyst.DB.UserDataCreate(context.Background(), "kevin", &model.UserData{
|
||||||
|
Name: pointer.String("Kevin"),
|
||||||
|
Email: pointer.String("kevin@example.com"),
|
||||||
|
Image: &avatarKevin,
|
||||||
|
})
|
||||||
|
|
||||||
// proxy static requests
|
// proxy static requests
|
||||||
middlewares := []func(next http.Handler) http.Handler{
|
middlewares := []func(next http.Handler) http.Handler{
|
||||||
catalyst.Authenticate(theCatalyst.DB, config.Auth),
|
catalyst.Authenticate(theCatalyst.DB, config.Auth),
|
||||||
catalyst.AuthorizeBlockedUser(),
|
catalyst.AuthorizeBlockedUser(),
|
||||||
}
|
}
|
||||||
theCatalyst.Server.With(middlewares...).NotFound(api.Proxy("http://localhost:8080"))
|
theCatalyst.Server.With(middlewares...).NotFound(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var handler http.Handler = http.HandlerFunc(api.Proxy("http://localhost:8080/static/"))
|
||||||
|
|
||||||
|
if strings.HasPrefix(request.URL.Path, "/static/") {
|
||||||
|
handler = http.StripPrefix("/static/", handler)
|
||||||
|
} else {
|
||||||
|
request.URL.Path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(writer, request)
|
||||||
|
})
|
||||||
|
|
||||||
if err := http.ListenAndServe(":8000", theCatalyst.Server); err != nil {
|
if err := http.ListenAndServe(":8000", theCatalyst.Server); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
63
ui/src/components/User.vue
Normal file
63
ui/src/components/User.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<span v-if="id === undefined">
|
||||||
|
<v-icon small class="mr-1">mdi-account</v-icon>
|
||||||
|
unassigned
|
||||||
|
</span>
|
||||||
|
<span v-else-if="user === undefined">
|
||||||
|
<v-icon small class="mr-1">mdi-account</v-icon>
|
||||||
|
{{ id }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<v-avatar v-if="user.image" :size="lodash.isInteger(size) ? size : 24">
|
||||||
|
<v-img :src="user.image"></v-img>
|
||||||
|
</v-avatar>
|
||||||
|
<v-icon v-else small class="">mdi-account</v-icon>
|
||||||
|
{{ user.name ? user.name : id }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import {UserData} from "@/client";
|
||||||
|
import {API} from "@/services/api";
|
||||||
|
import {AxiosTransformer} from "axios";
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
user?: UserData,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: "User",
|
||||||
|
props: ["id", "size"],
|
||||||
|
data: (): State => ({
|
||||||
|
user: undefined,
|
||||||
|
}),
|
||||||
|
watch: {
|
||||||
|
id: function(): void {
|
||||||
|
this.loadUserData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadUserData: function () {
|
||||||
|
if (this.id === undefined) {
|
||||||
|
this.user = undefined;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultTransformers = this.axios.defaults.transformResponse as AxiosTransformer[]
|
||||||
|
let transformResponse = defaultTransformers.concat((data) => {
|
||||||
|
data.notoast = true;
|
||||||
|
return data
|
||||||
|
});
|
||||||
|
API.getUserData(this.id, {transformResponse: transformResponse}).then(response => {
|
||||||
|
this.user = response.data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadUserData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -12,8 +12,7 @@
|
|||||||
<v-icon small class="mr-1" :color="statusColor">{{ statusIcon }}</v-icon>
|
<v-icon small class="mr-1" :color="statusColor">{{ statusIcon }}</v-icon>
|
||||||
<span :class="statusColor + '--text'">{{ ticket.status | capitalize }}</span>
|
<span :class="statusColor + '--text'">{{ ticket.status | capitalize }}</span>
|
||||||
|
|
||||||
<v-icon small class="mx-1">mdi-account</v-icon>
|
<User :id="ticket.owner" :size="16" class="mx-2"></User>
|
||||||
{{ ticket.owner ? ticket.owner : 'unassigned' }}
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-icon small class="mr-1">mdi-source-branch</v-icon>
|
<v-icon small class="mr-1">mdi-source-branch</v-icon>
|
||||||
<span class="mr-1">{{ ticket.playbooks ? lodash.size(ticket.playbooks) : 0 }}</span>
|
<span class="mr-1">{{ ticket.playbooks ? lodash.size(ticket.playbooks) : 0 }}</span>
|
||||||
@@ -38,10 +37,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import {Playbook, Task, Type, TypeColorEnum} from "@/client";
|
import {Playbook, Task, Type, TypeColorEnum} from "@/client";
|
||||||
|
import User from "@/components/User.vue";
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: "TicketSnippet",
|
name: "TicketSnippet",
|
||||||
props: ["ticket", "to", "action"],
|
props: ["ticket", "to", "action"],
|
||||||
|
components: {
|
||||||
|
User
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
opentaskcount: function() {
|
opentaskcount: function() {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|||||||
@@ -103,38 +103,6 @@
|
|||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
·
|
·
|
||||||
<v-menu offset-y class="mr-2">
|
|
||||||
<template v-slot:activator="{ on, attrs }">
|
|
||||||
<span v-bind="attrs" v-on="on">
|
|
||||||
<v-icon small class="mr-1">mdi-account</v-icon>
|
|
||||||
{{ ticket.owner ? ticket.owner : "unassigned" }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item
|
|
||||||
dense
|
|
||||||
link
|
|
||||||
v-for="user in otherUsers(ticket.owner)"
|
|
||||||
:key="user.id"
|
|
||||||
@click="setOwner(user.id)"
|
|
||||||
>
|
|
||||||
<v-list-item-title>
|
|
||||||
Change owner to {{ user.id }}
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item
|
|
||||||
v-if="ticket.owner"
|
|
||||||
dense
|
|
||||||
link
|
|
||||||
@click="setOwner(null)"
|
|
||||||
>
|
|
||||||
<v-list-item-title>
|
|
||||||
Unassign ticket
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
·
|
|
||||||
<v-icon small class="mr-1">mdi-calendar-plus</v-icon>
|
<v-icon small class="mr-1">mdi-calendar-plus</v-icon>
|
||||||
{{ ticket.created | formatdate($store.state.settings.timeformat) }}
|
{{ ticket.created | formatdate($store.state.settings.timeformat) }}
|
||||||
·
|
·
|
||||||
@@ -243,6 +211,63 @@
|
|||||||
>
|
>
|
||||||
<v-icon class="mr-1">mdi-graph</v-icon> open graph
|
<v-icon class="mr-1">mdi-graph</v-icon> open graph
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
|
<v-list dense color="background">
|
||||||
|
<v-list-item class="pa-0 ma-0" style="min-height: 32px">
|
||||||
|
<span class="text--disabled" style="width: 50px;">Owner</span>
|
||||||
|
<v-menu offset-y class="mr-2">
|
||||||
|
<template v-slot:activator="{ on, attrs }">
|
||||||
|
<span v-bind="attrs" v-on="on">
|
||||||
|
<User :id="ticket.owner" class="ml-3"></User>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
dense
|
||||||
|
link
|
||||||
|
v-for="user in otherUsers(ticket.owner)"
|
||||||
|
:key="user.id"
|
||||||
|
@click="setOwner(user.id)"
|
||||||
|
>
|
||||||
|
<v-list-item-title>
|
||||||
|
Change owner to <User :id="user.id"></User>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="ticket.owner"
|
||||||
|
dense
|
||||||
|
link
|
||||||
|
@click="setOwner(undefined)"
|
||||||
|
>
|
||||||
|
<v-list-item-title>
|
||||||
|
Unassign ticket
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-list-item>
|
||||||
|
<!--v-list-item class="pa-0 ma-0" style="min-height: 32px">
|
||||||
|
<span class="text--disabled" style="width: 50px;">Editors</span>
|
||||||
|
<span v-for="writer in ticket.write" :key="writer">
|
||||||
|
<User :id="writer" class="ml-3"></User>
|
||||||
|
</span>
|
||||||
|
<v-btn v-if="!ticket.write || ticket.write.length === 0" small text elevation="0" @click="referenceDialog = true">
|
||||||
|
<v-icon small>mdi-plus</v-icon> Add editor
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="pa-0 ma-0" style="min-height: 32px">
|
||||||
|
<span class="text--disabled" style="width: 50px;">Viewers</span>
|
||||||
|
<span v-for="reader in ticket.read" :key="reader">
|
||||||
|
<User :id="reader" class="ml-3"></User>
|
||||||
|
</span>
|
||||||
|
<v-btn v-if="!ticket.read || ticket.read.length === 0" small text elevation="0" @click="referenceDialog = true">
|
||||||
|
<v-icon small>mdi-plus</v-icon> Add viewer
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list-->
|
||||||
|
|
||||||
|
<v-divider class="mb-5"></v-divider>
|
||||||
|
|
||||||
<div style="align-items: center" class="d-flex pb-1">
|
<div style="align-items: center" class="d-flex pb-1">
|
||||||
<span class="text--disabled">Playbooks</span>
|
<span class="text--disabled">Playbooks</span>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@@ -796,19 +821,19 @@ import {alg, Graph} from "graphlib";
|
|||||||
import ArtifactSnippet from "../components/snippets/ArtifactSnippet.vue";
|
import ArtifactSnippet from "../components/snippets/ArtifactSnippet.vue";
|
||||||
import TicketSnippet from "../components/snippets/TicketSnippet.vue";
|
import TicketSnippet from "../components/snippets/TicketSnippet.vue";
|
||||||
import TicketList from "../components/TicketList.vue";
|
import TicketList from "../components/TicketList.vue";
|
||||||
|
import User from "../components/User.vue";
|
||||||
import ArtifactPopup from "./ArtifactPopup.vue";
|
import ArtifactPopup from "./ArtifactPopup.vue";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Ticket,
|
Ticket,
|
||||||
TicketResponse,
|
TicketResponse,
|
||||||
TicketTemplate,
|
TicketTemplate,
|
||||||
ModelFile,
|
|
||||||
PlaybookTemplate,
|
PlaybookTemplate,
|
||||||
Reference,
|
Reference,
|
||||||
Task,
|
Task,
|
||||||
Type,
|
Type,
|
||||||
TypeColorEnum,
|
TypeColorEnum,
|
||||||
TaskResponse, PlaybookResponse, UserResponse, TaskTypeEnum, TicketWithTickets,
|
TaskResponse, PlaybookResponse, UserResponse, TicketWithTickets,
|
||||||
} from "@/client";
|
} from "@/client";
|
||||||
import {API} from "@/services/api";
|
import {API} from "@/services/api";
|
||||||
|
|
||||||
@@ -818,7 +843,7 @@ import Tus from "@uppy/tus";
|
|||||||
import "@uppy/core/dist/style.css";
|
import "@uppy/core/dist/style.css";
|
||||||
import "@uppy/dashboard/dist/style.css";
|
import "@uppy/dashboard/dist/style.css";
|
||||||
|
|
||||||
import {Uppy, UppyFile} from "@uppy/core";
|
import {Uppy} from "@uppy/core";
|
||||||
import {AxiosError, AxiosResponse} from "axios";
|
import {AxiosError, AxiosResponse} from "axios";
|
||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
import VueMarkdown from "vue-markdown";
|
import VueMarkdown from "vue-markdown";
|
||||||
@@ -897,7 +922,8 @@ export default Vue.extend({
|
|||||||
"vue-markdown": VueMarkdown,
|
"vue-markdown": VueMarkdown,
|
||||||
TicketList,
|
TicketList,
|
||||||
ArtifactPopup,
|
ArtifactPopup,
|
||||||
JSONHTML
|
JSONHTML,
|
||||||
|
User
|
||||||
},
|
},
|
||||||
data: (): State => ({
|
data: (): State => ({
|
||||||
valid: false,
|
valid: false,
|
||||||
@@ -1447,12 +1473,16 @@ export default Vue.extend({
|
|||||||
this.editName = "";
|
this.editName = "";
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setOwner: function(owner: string) {
|
setOwner: function(owner?: string) {
|
||||||
if (!this.ticket || !this.ticket.id) {
|
if (!this.ticket || !this.ticket.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ticket.owner = owner;
|
if (owner === undefined) {
|
||||||
|
this.lodash.unset(this.ticket, "owner")
|
||||||
|
} else {
|
||||||
|
this.ticket.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
API.updateTicket(this.ticket.id, this.toTicket(this.ticket)).then(response => {
|
API.updateTicket(this.ticket.id, this.toTicket(this.ticket)).then(response => {
|
||||||
this.$store.dispatch("alertSuccess", { name: "Ticket owner changed" });
|
this.$store.dispatch("alertSuccess", { name: "Ticket owner changed" });
|
||||||
|
|||||||
Reference in New Issue
Block a user