mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-25 16:33:10 +01:00
feat: improve python actions (#1083)
This commit is contained in:
@@ -4,13 +4,17 @@ import DeleteDialog from '@/components/common/DeleteDialog.vue'
|
||||
import ReactionForm from '@/components/reaction/ReactionForm.vue'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { toast } from '@/components/ui/toast'
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { pb } from '@/lib/pocketbase'
|
||||
import type { Reaction } from '@/lib/types'
|
||||
import { handleError } from '@/lib/utils'
|
||||
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -32,6 +36,35 @@ const updateReactionMutation = useMutation({
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['reactions'] }),
|
||||
onError: handleError
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
pb.collection('reactions').subscribe(props.id, (data) => {
|
||||
if (data.action === 'delete') {
|
||||
toast({
|
||||
title: 'Reaction deleted',
|
||||
description: 'The reaction has been deleted.',
|
||||
variant: 'destructive'
|
||||
})
|
||||
|
||||
router.push({ name: 'reactions' })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (data.action === 'update') {
|
||||
toast({
|
||||
title: 'Reaction updated',
|
||||
description: 'The reaction has been updated.'
|
||||
})
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['reactions', props.id] })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
pb.collection('reactions').unsubscribe(props.id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -54,7 +87,7 @@ const updateReactionMutation = useMutation({
|
||||
|
||||
<ScrollArea v-if="reaction" class="flex-1">
|
||||
<div class="flex max-w-[640px] flex-col gap-4 p-4">
|
||||
<ReactionForm :reaction="reaction" @submit="updateReactionMutation.mutate" hide-cancel />
|
||||
<ReactionForm :reaction="reaction" @submit="updateReactionMutation.mutate" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
@@ -166,6 +166,8 @@ watch(
|
||||
() => {
|
||||
if (equalReaction(values, props.reaction)) {
|
||||
submitDisabledReason.value = 'Make changes to save'
|
||||
} else {
|
||||
submitDisabledReason.value = ''
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -312,7 +314,7 @@ const curlExample = computed(() => {
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<slot name="cancel" />
|
||||
<slot name="cancel"></slot>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import TanView from '@/components/TanView.vue'
|
||||
import ResourceListElement from '@/components/common/ResourceListElement.vue'
|
||||
import ReactionNewDialog from '@/components/reaction/ReactionNewDialog.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { pb } from '@/lib/pocketbase'
|
||||
import type { Reaction } from '@/lib/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const {
|
||||
isPending,
|
||||
@@ -47,6 +50,20 @@ const reactionNiceName = (reaction: Reaction) => {
|
||||
return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
const openNew = () => {
|
||||
router.push({ name: 'reactions', params: { id: 'new' } })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
pb.collection('reactions').subscribe('*', () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['reactions'] })
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
pb.collection('reactions').unsubscribe('*')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,7 +72,7 @@ const reactionNiceName = (reaction: Reaction) => {
|
||||
<div class="flex items-center bg-background px-4 py-2">
|
||||
<h1 class="text-xl font-bold">Reactions</h1>
|
||||
<div class="ml-auto">
|
||||
<ReactionNewDialog />
|
||||
<Button variant="ghost" @click="openNew"> New Reaction </Button>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
37
ui/src/components/reaction/ReactionNew.vue
Normal file
37
ui/src/components/reaction/ReactionNew.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import ReactionForm from '@/components/reaction/ReactionForm.vue'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { pb } from '@/lib/pocketbase'
|
||||
import type { Reaction, Ticket } from '@/lib/types'
|
||||
import { handleError } from '@/lib/utils'
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const router = useRouter()
|
||||
|
||||
const addReactionMutation = useMutation({
|
||||
mutationFn: (values: Reaction): Promise<Reaction> => pb.collection('reactions').create(values),
|
||||
onSuccess: (data: Ticket) => {
|
||||
router.push({ name: 'reactions', params: { id: data.id } })
|
||||
queryClient.invalidateQueries({ queryKey: ['reactions'] })
|
||||
},
|
||||
onError: handleError
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full flex-1 flex-col overflow-hidden">
|
||||
<div class="flex min-h-14 items-center bg-background px-4 py-2"></div>
|
||||
<Separator />
|
||||
|
||||
<ScrollArea class="flex-1">
|
||||
<div class="flex max-w-[640px] flex-col gap-4 p-4">
|
||||
<ReactionForm @submit="addReactionMutation.mutate" />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,63 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import ReactionForm from '@/components/reaction/ReactionForm.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogScrollContent,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog'
|
||||
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { pb } from '@/lib/pocketbase'
|
||||
import type { Reaction, Ticket } from '@/lib/types'
|
||||
import { handleError } from '@/lib/utils'
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const router = useRouter()
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
const addReactionMutation = useMutation({
|
||||
mutationFn: (values: Reaction): Promise<Reaction> => pb.collection('reactions').create(values),
|
||||
onSuccess: (data: Ticket) => {
|
||||
router.push({ name: 'reactions', params: { id: data.id } })
|
||||
queryClient.invalidateQueries({ queryKey: ['reactions'] })
|
||||
isOpen.value = false
|
||||
},
|
||||
onError: handleError
|
||||
})
|
||||
|
||||
const cancel = () => (isOpen.value = false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="isOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button variant="ghost">New Reaction</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>New Reaction</DialogTitle>
|
||||
<DialogDescription>Create a new reaction</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogScrollContent>
|
||||
<ReactionForm @submit="addReactionMutation.mutate">
|
||||
<template #cancel>
|
||||
<DialogClose as-child>
|
||||
<Button type="button" variant="secondary">Cancel</Button>
|
||||
</DialogClose>
|
||||
</template>
|
||||
</ReactionForm>
|
||||
</DialogScrollContent>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -22,7 +22,7 @@ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Edit } from 'lucide-vue-next'
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { pb } from '@/lib/pocketbase'
|
||||
|
||||
@@ -19,7 +19,7 @@ defineProps<{
|
||||
:key="item.id"
|
||||
:title="item.name"
|
||||
:created="item.created"
|
||||
:subtitle="item.expand.owner.name"
|
||||
:subtitle="item.expand.owner ? item.expand.owner.name : ''"
|
||||
:description="item.description ? item.description.substring(0, 300) : ''"
|
||||
:active="route.params.id === item.id"
|
||||
:to="`/tickets/${item.expand.type.id}/${item.id}`"
|
||||
|
||||
@@ -15,7 +15,7 @@ const queryClient = useQueryClient()
|
||||
|
||||
const props = defineProps<{
|
||||
ticket: Ticket
|
||||
uID: string
|
||||
uID?: string
|
||||
}>()
|
||||
|
||||
const {
|
||||
@@ -25,7 +25,13 @@ const {
|
||||
error
|
||||
} = useQuery({
|
||||
queryKey: ['tickets', props.ticket.id, 'owner', props.uID],
|
||||
queryFn: (): Promise<User> => pb.collection('users').getOne(props.uID)
|
||||
queryFn: (): Promise<User | null> => {
|
||||
if (!props.uID) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
return pb.collection('users').getOne(props.uID)
|
||||
}
|
||||
})
|
||||
|
||||
const setTicketOwnerMutation = useMutation({
|
||||
@@ -48,12 +54,12 @@ const update = (user: User) => setTicketOwnerMutation.mutate(user)
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>{{ error }}</AlertDescription>
|
||||
</Alert>
|
||||
<div v-if="!user">
|
||||
<Button variant="outline" role="combobox" disabled>
|
||||
<UserSelect v-if="!user" @update:modelValue="update">
|
||||
<Button variant="outline" role="combobox">
|
||||
<User2 class="mr-2 size-4 h-4 w-4 shrink-0 opacity-50" />
|
||||
{{ props.uID }}
|
||||
Unassigned
|
||||
</Button>
|
||||
</div>
|
||||
</UserSelect>
|
||||
<UserSelect v-else :modelValue="user" @update:modelValue="update">
|
||||
<Button variant="outline" role="combobox">
|
||||
<User2 class="mr-2 size-4 h-4 w-4 shrink-0 opacity-50" />
|
||||
|
||||
@@ -20,7 +20,7 @@ const queryClient = useQueryClient()
|
||||
|
||||
const props = defineProps<{
|
||||
ticket: Ticket
|
||||
tasks: Array<Task>
|
||||
tasks?: Array<Task>
|
||||
}>()
|
||||
|
||||
const setTaskOwnerMutation = useMutation({
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { Ticket, TimelineItem } from '@/lib/types'
|
||||
|
||||
const props = defineProps<{
|
||||
ticket: Ticket
|
||||
timeline: Array<TimelineItem>
|
||||
timeline?: Array<TimelineItem>
|
||||
}>()
|
||||
|
||||
const commentsByDate: ComputedRef<Record<string, Array<TimelineItem>>> = computed(() => {
|
||||
@@ -41,7 +41,7 @@ const commentsByDate: ComputedRef<Record<string, Array<TimelineItem>>> = compute
|
||||
<template>
|
||||
<div class="mt-2 flex flex-col gap-2">
|
||||
<Card
|
||||
v-if="!props.timeline || props.timeline.length === 0"
|
||||
v-if="!timeline || timeline.length === 0"
|
||||
class="flex h-10 items-center p-4 text-muted-foreground"
|
||||
>
|
||||
No timeline entries added yet.
|
||||
@@ -61,6 +61,6 @@ const commentsByDate: ComputedRef<Record<string, Array<TimelineItem>>> = compute
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<TicketTimelineInput :ticket="props.ticket" class="w-full" />
|
||||
<TicketTimelineInput :ticket="ticket" class="w-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -47,7 +47,7 @@ onMounted(() => {
|
||||
<template>
|
||||
<TwoColumn>
|
||||
<div class="flex h-screen flex-1 flex-col">
|
||||
<div class="flex h-14 items-center bg-background px-4 py-2">
|
||||
<div class="flex h-14 min-h-14 items-center bg-background px-4 py-2">
|
||||
<h1 class="text-xl font-bold">Dashboard</h1>
|
||||
</div>
|
||||
<Separator class="shrink-0" />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import ThreeColumn from '@/components/layout/ThreeColumn.vue'
|
||||
import ReactionDisplay from '@/components/reaction/ReactionDisplay.vue'
|
||||
import ReactionList from '@/components/reaction/ReactionList.vue'
|
||||
import ReactionNew from '@/components/reaction/ReactionNew.vue'
|
||||
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -29,6 +30,7 @@ onMounted(() => {
|
||||
<div v-if="!id" class="flex h-full w-full items-center justify-center text-lg text-gray-500">
|
||||
No reaction selected
|
||||
</div>
|
||||
<ReactionNew v-else-if="id === 'new'" key="new" />
|
||||
<ReactionDisplay v-else :key="id" :id="id" />
|
||||
</template>
|
||||
</ThreeColumn>
|
||||
|
||||
Reference in New Issue
Block a user