mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 15:22:47 +01:00
Compare commits
5 Commits
v0.15.1
...
e148ae0e0c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e148ae0e0c | ||
|
|
2b6320c2d4 | ||
|
|
a4a4baf88a | ||
|
|
148f625b00 | ||
|
|
97ebe9f01a |
4
.github/workflows/goreleaser.yml
vendored
4
.github/workflows/goreleaser.yml
vendored
@@ -36,5 +36,5 @@ jobs:
|
||||
-w /go/src/github.com/SecurityBrewery/catalyst \
|
||||
-e CGO_ENABLED=1 \
|
||||
-e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
|
||||
ghcr.io/goreleaser/goreleaser-cross:latest \
|
||||
release --clean
|
||||
ghcr.io/goreleaser/goreleaser-cross:v1.25.1 \
|
||||
release --clean
|
||||
|
||||
14
go.mod
14
go.mod
@@ -21,7 +21,7 @@ require (
|
||||
github.com/tus/tusd/v2 v2.8.0
|
||||
github.com/urfave/cli/v3 v3.3.8
|
||||
github.com/wneessen/go-mail v0.7.1
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/crypto v0.45.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -82,12 +82,12 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
|
||||
28
go.sum
28
go.sum
@@ -227,35 +227,35 @@ go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4=
|
||||
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "catalyst",
|
||||
|
||||
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 TicketPanel from '@/components/ticket/TicketPanel.vue'
|
||||
import FileAddDialog from '@/components/ticket/file/FileAddDialog.vue'
|
||||
import ImageModal from '@/components/ticket/file/ImageModal.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useToast } from '@/components/ui/toast/use-toast'
|
||||
|
||||
@@ -30,6 +31,36 @@ const props = defineProps<{
|
||||
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) => {
|
||||
fetch(`/api/files/${file.id}/download`, {
|
||||
headers: { Authorization: `Bearer ${authStore.token}` }
|
||||
@@ -44,6 +75,8 @@ const downloadFile = (file: any) => {
|
||||
link.download = file.name
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(_url)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
@@ -82,11 +115,24 @@ watch(
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(isImageModalOpen, (isOpen) => {
|
||||
if (!isOpen && selectedImageUrl.value) {
|
||||
window.URL.revokeObjectURL(selectedImageUrl.value)
|
||||
selectedImageUrl.value = ''
|
||||
selectedImageName.value = ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TicketPanel title="Files" @add="dialogOpen = true" :hideAdd="isDemo">
|
||||
<FileAddDialog v-if="!isDemo" v-model="dialogOpen" :ticket="ticket" />
|
||||
<ImageModal
|
||||
v-model="isImageModalOpen"
|
||||
:image-url="selectedImageUrl"
|
||||
:file-name="selectedImageName"
|
||||
/>
|
||||
<div
|
||||
v-if="!files || files.length === 0"
|
||||
class="flex h-10 items-center p-4 text-muted-foreground"
|
||||
@@ -99,7 +145,11 @@ watch(
|
||||
: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"
|
||||
>
|
||||
<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 }}
|
||||
|
||||
<div class="ml-1 flex-1 text-nowrap text-sm text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user