mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 07:12:46 +01:00
Compare commits
3 Commits
v0.15.1
...
a4a4baf88a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4a4baf88a | ||
|
|
148f625b00 | ||
|
|
97ebe9f01a |
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -36,5 +36,5 @@ jobs:
|
|||||||
-w /go/src/github.com/SecurityBrewery/catalyst \
|
-w /go/src/github.com/SecurityBrewery/catalyst \
|
||||||
-e CGO_ENABLED=1 \
|
-e CGO_ENABLED=1 \
|
||||||
-e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
|
-e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
|
||||||
ghcr.io/goreleaser/goreleaser-cross:latest \
|
ghcr.io/goreleaser/goreleaser-cross:v1.25.1 \
|
||||||
release --clean
|
release --clean
|
||||||
38
ui/src/components/ticket/file/ImageModal.vue
Normal file
38
ui/src/components/ticket/file/ImageModal.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
|
import { X } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
modelValue: boolean
|
||||||
|
imageUrl: string
|
||||||
|
fileName: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="modelValue"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
|
||||||
|
@click.self="closeModal"
|
||||||
|
>
|
||||||
|
<div class="relative rounded-lg bg-white p-2 shadow-xl">
|
||||||
|
<img :src="imageUrl" :alt="fileName" class="max-h-[80vh] max-w-[80vw]" />
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="absolute -right-1 -top-1 h-8 w-8 rounded-full bg-white"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
<X class="h-5 w-5" />
|
||||||
|
<span class="sr-only">Close image modal</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -5,6 +5,7 @@ import '@uppy/dashboard/dist/style.min.css'
|
|||||||
import DeleteDialog from '@/components/common/DeleteDialog.vue'
|
import DeleteDialog from '@/components/common/DeleteDialog.vue'
|
||||||
import TicketPanel from '@/components/ticket/TicketPanel.vue'
|
import TicketPanel from '@/components/ticket/TicketPanel.vue'
|
||||||
import FileAddDialog from '@/components/ticket/file/FileAddDialog.vue'
|
import FileAddDialog from '@/components/ticket/file/FileAddDialog.vue'
|
||||||
|
import ImageModal from '@/components/ticket/file/ImageModal.vue'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { useToast } from '@/components/ui/toast/use-toast'
|
import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
|
|
||||||
@@ -30,6 +31,36 @@ const props = defineProps<{
|
|||||||
files: Array<ModelFile> | undefined
|
files: Array<ModelFile> | undefined
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const isImageModalOpen = ref(false)
|
||||||
|
const selectedImageUrl = ref('')
|
||||||
|
const selectedImageName = ref('')
|
||||||
|
|
||||||
|
const isImage = (fileName: string) => {
|
||||||
|
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg']
|
||||||
|
const extension = fileName.split('.').pop()?.toLowerCase()
|
||||||
|
return extension ? imageExtensions.includes(extension) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
const openImageModal = async (file: ModelFile) => {
|
||||||
|
if (!isImage(file.name)) return
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/files/${file.id}/download`, {
|
||||||
|
headers: { Authorization: `Bearer ${authStore.token}` }
|
||||||
|
})
|
||||||
|
const blob = await response.blob()
|
||||||
|
selectedImageUrl.value = window.URL.createObjectURL(blob)
|
||||||
|
selectedImageName.value = file.name
|
||||||
|
isImageModalOpen.value = true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching image:', err)
|
||||||
|
toast({
|
||||||
|
title: 'Error',
|
||||||
|
description: 'Could not load image for preview.',
|
||||||
|
variant: 'destructive'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const downloadFile = (file: any) => {
|
const downloadFile = (file: any) => {
|
||||||
fetch(`/api/files/${file.id}/download`, {
|
fetch(`/api/files/${file.id}/download`, {
|
||||||
headers: { Authorization: `Bearer ${authStore.token}` }
|
headers: { Authorization: `Bearer ${authStore.token}` }
|
||||||
@@ -44,6 +75,8 @@ const downloadFile = (file: any) => {
|
|||||||
link.download = file.name
|
link.download = file.name
|
||||||
document.body.appendChild(link)
|
document.body.appendChild(link)
|
||||||
link.click()
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(_url)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
@@ -82,11 +115,24 @@ watch(
|
|||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(isImageModalOpen, (isOpen) => {
|
||||||
|
if (!isOpen && selectedImageUrl.value) {
|
||||||
|
window.URL.revokeObjectURL(selectedImageUrl.value)
|
||||||
|
selectedImageUrl.value = ''
|
||||||
|
selectedImageName.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TicketPanel title="Files" @add="dialogOpen = true" :hideAdd="isDemo">
|
<TicketPanel title="Files" @add="dialogOpen = true" :hideAdd="isDemo">
|
||||||
<FileAddDialog v-if="!isDemo" v-model="dialogOpen" :ticket="ticket" />
|
<FileAddDialog v-if="!isDemo" v-model="dialogOpen" :ticket="ticket" />
|
||||||
|
<ImageModal
|
||||||
|
v-model="isImageModalOpen"
|
||||||
|
:image-url="selectedImageUrl"
|
||||||
|
:file-name="selectedImageName"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="!files || files.length === 0"
|
v-if="!files || files.length === 0"
|
||||||
class="flex h-10 items-center p-4 text-muted-foreground"
|
class="flex h-10 items-center p-4 text-muted-foreground"
|
||||||
@@ -99,7 +145,11 @@ watch(
|
|||||||
:title="file.name"
|
:title="file.name"
|
||||||
class="flex w-full items-center border-t py-1 pl-2 pr-1 first:rounded-t first:border-none last:rounded-b"
|
class="flex w-full items-center border-t py-1 pl-2 pr-1 first:rounded-t first:border-none last:rounded-b"
|
||||||
>
|
>
|
||||||
<div class="flex flex-1 items-center overflow-hidden pr-2">
|
<div
|
||||||
|
class="flex flex-1 items-center overflow-hidden pr-2"
|
||||||
|
:class="isImage(file.name) ? 'cursor-pointer' : ''"
|
||||||
|
@click="openImageModal(file)"
|
||||||
|
>
|
||||||
{{ file.name }}
|
{{ file.name }}
|
||||||
|
|
||||||
<div class="ml-1 flex-1 text-nowrap text-sm text-muted-foreground">
|
<div class="ml-1 flex-1 text-nowrap text-sm text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user