Files
AstralRinth/apps/frontend/src/components/ui/moderation/MaliciousSummaryModal.vue
aecsocket 39f2b0ecb6 Technical review queue (#4775)
* chore: fix typo in status message

* feat(labrinth): overhaul malware scanner report storage and routes

* chore: address some review comments

* feat: add Delphi to Docker Compose `with-delphi` profile

* chore: fix unused import Clippy lint

* feat(labrinth/delphi): use PAT token authorization with project read scopes

* chore: expose file IDs in version queries

* fix: accept null decompiled source payloads from Delphi

* tweak(labrinth): expose base62 file IDs more consistently for Delphi

* feat(labrinth/delphi): support new Delphi report severity field

* chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors

* tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types

* chore: run `cargo sqlx prepare`

* chore: fix typo on frontend generated state file message

* feat: update to use new Delphi issue schema

* wip: tech review endpoints

* wip: add ToSchema for dependent types

* wip: report issues return

* wip

* wip: returning more data

* wip

* Fix up db query

* Delphi configuration to talk to Labrinth

* Get Delphi working with Labrinth

* Add Delphi dummy fixture

* Better Delphi logging

* Improve utoipa for tech review routes

* Add more sorting options for tech review queue

* Oops join

* New routes for fetching issues and reports

* Fix which kind of ID is returned in tech review endpoints

* Deduplicate tech review report rows

* Reduce info sent for projects

* Fetch more thread info

* Address PR comments

* fix ci

* fix postgres version mismatch

* fix version creation

* Implement routes

* fix up tech review

* Allow adding a moderation comment to Delphi rejections

* fix up rebase

* exclude rejected projects from tech review

* add status change msg to tech review thread

* cargo sqlx prepare

* also ignore withheld projects

* More filtering on issue search

* wip: report routes

* Fix up for build

* cargo sqlx prepare

* fix thread message privacy

* New tech review search route

* submit route

* details have statuses now

* add default to drid status

* dedup issue details

* fix sqlx query on empty files

* fixes

* Dedupe issue detail statuses and message on entering tech rev

* Fix qa issues

* Fix qa issues

* fix review comments

* typos

* fix ci

* feat: tech review frontend (#4781)

* chore: fix typo in status message

* feat(labrinth): overhaul malware scanner report storage and routes

* chore: address some review comments

* feat: add Delphi to Docker Compose `with-delphi` profile

* chore: fix unused import Clippy lint

* feat(labrinth/delphi): use PAT token authorization with project read scopes

* chore: expose file IDs in version queries

* fix: accept null decompiled source payloads from Delphi

* tweak(labrinth): expose base62 file IDs more consistently for Delphi

* feat(labrinth/delphi): support new Delphi report severity field

* chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors

* tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types

* chore: run `cargo sqlx prepare`

* chore: fix typo on frontend generated state file message

* feat: update to use new Delphi issue schema

* wip: tech review endpoints

* wip: add ToSchema for dependent types

* wip: report issues return

* wip

* wip: returning more data

* wip

* Fix up db query

* Delphi configuration to talk to Labrinth

* Get Delphi working with Labrinth

* Add Delphi dummy fixture

* Better Delphi logging

* Improve utoipa for tech review routes

* Add more sorting options for tech review queue

* Oops join

* New routes for fetching issues and reports

* Fix which kind of ID is returned in tech review endpoints

* Deduplicate tech review report rows

* Reduce info sent for projects

* Fetch more thread info

* Address PR comments

* fix ci

* fix ci

* fix postgres version mismatch

* fix version creation

* Implement routes

* feat: batch scan alert

* feat: layout

* feat: introduce surface variables

* fix: theme selector

* feat: rough draft of tech review card

* feat: tab switcher

* feat: batch scan btn

* feat: api-client module for tech review

* draft: impl

* feat: auto icons

* fix: layout issues

* feat: fixes to code blocks + flag labels

* feat: temp remove mock data

* fix: search sort types

* fix: intl & lint

* chore: re-enable mock data

* fix: flag badges + auto open first issue in file tab

* feat: update for new routes

* fix: more qa issues

* feat: lazy load sources

* fix: re-enable auth middleware

* feat: impl threads

* fix: lint & severity

* feat: download btn + switch to using NavTabs with new local mode option

* feat: re-add toplevel btns

* feat: reports page consistency

* fix: consistency on project queue

* fix: icons + sizing

* fix: colors and gaps

* fix: impl endpoints

* feat: load all flags on file tab

* feat: thread generics changes

* feat: more qa

* feat: fix collapse

* fix: qa

* feat: msg modal

* fix: ISO import

* feat: qa fixes

* fix: empty state basic

* fix: collapsible region

* fix: collapse thread by default

* feat: rough draft of new process/flow

* fix labrinth build

* fix thread message privacy

* New tech review search route

* feat: qa fixes

* feat: QA changes

* fix: verdict on detail not whole issue

* fix: lint + intl

* fix: lint

* fix: thread message for tech rev verdict

* feat: use anim frames

* fix: exports + typecheck

* polish: qa changes

* feat: qa

* feat: qa polish

* feat: fix malic modal

* fix: lint

* fix: qa + lint

* fix: pagination

* fix: lint

* fix: qa

* intl extract

* fix ci

---------

Signed-off-by: Calum H. <contact@cal.engineer>
Co-authored-by: Alejandro González <me@alegon.dev>
Co-authored-by: aecsocket <aecsocket@tutanota.com>

---------

Signed-off-by: Calum H. <contact@cal.engineer>
Co-authored-by: Alejandro González <me@alegon.dev>
Co-authored-by: Calum H. <contact@cal.engineer>
2025-12-20 11:43:04 +00:00

169 lines
4.6 KiB
Vue

<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { ClipboardCopyIcon, LoaderCircleIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, CopyCode, NewModal } from '@modrinth/ui'
import { ref, useTemplateRef } from 'vue'
export type UnsafeFile = {
file: Labrinth.TechReview.Internal.FileReport & { version_id: string }
projectName: string
projectId: string
userId: string
username: string
}
const props = defineProps<{
unsafeFiles: UnsafeFile[]
}>()
const modalRef = useTemplateRef<InstanceType<typeof NewModal>>('modalRef')
const versionDataCache = ref<
Map<
string,
{
files: Map<string, string>
loading: boolean
error?: string
}
>
>(new Map())
async function fetchVersionHashes(versionIds: string[]) {
const uniqueIds = [...new Set(versionIds)]
for (const versionId of uniqueIds) {
if (versionDataCache.value.has(versionId)) continue
versionDataCache.value.set(versionId, { files: new Map(), loading: true })
try {
// TODO: switch to api-client once truman's vers stuff is merged
const version = (await useBaseFetch(`version/${versionId}`)) as {
files: Array<{
filename: string
file_name?: string
hashes: { sha512: string; sha1: string }
}>
}
const filesMap = new Map<string, string>()
for (const file of version.files) {
const name = file.file_name ?? file.filename
filesMap.set(name, file.hashes.sha512)
}
versionDataCache.value.set(versionId, { files: filesMap, loading: false })
} catch (error) {
console.error(`Failed to fetch version ${versionId}:`, error)
versionDataCache.value.set(versionId, {
files: new Map(),
loading: false,
error: 'Failed',
})
}
}
}
function getFileHash(versionId: string, fileName: string): string | undefined {
return versionDataCache.value.get(versionId)?.files.get(fileName)
}
function isHashLoading(versionId: string): boolean {
return versionDataCache.value.get(versionId)?.loading ?? false
}
function show() {
const versionIds = props.unsafeFiles.map((f) => f.file.version_id)
fetchVersionHashes(versionIds)
modalRef.value?.show()
}
function hide() {
modalRef.value?.hide()
}
async function copy(text: string) {
await navigator.clipboard.writeText(text)
}
defineExpose({ show, hide })
</script>
<template>
<NewModal
ref="modalRef"
header="Malicious file(s) summary"
:close-on-click-outside="false"
:close-on-esc="false"
:closable="false"
>
<div class="markdown-body inset-0">
<div v-if="unsafeFiles.length > 0" class="mb-4 flex flex-col gap-2">
<div class="flex items-center gap-2">
<span class="text-tertiary text-sm font-medium">Project:</span>
<CopyCode :text="unsafeFiles[0].projectName" />
<CopyCode :text="unsafeFiles[0].projectId" />
</div>
<div class="flex items-center gap-2">
<span class="text-tertiary text-sm font-medium">User:</span>
<CopyCode :text="unsafeFiles[0].username" />
<CopyCode :text="unsafeFiles[0].userId" />
</div>
</div>
<table v-if="unsafeFiles.length > 0" class="w-full text-sm">
<thead>
<tr class="text-tertiary text-left text-xs font-medium">
<th class="pb-2">Hash</th>
<th class="pb-2">Version ID</th>
<th class="pb-2">File Name</th>
<th class="pb-2">CDN Link</th>
</tr>
</thead>
<tbody>
<tr v-for="item in unsafeFiles" :key="item.file.file_id">
<td class="py-1 pr-2">
<LoaderCircleIcon
v-if="isHashLoading(item.file.version_id)"
class="size-4 animate-spin text-secondary"
/>
<ButtonStyled
v-else-if="getFileHash(item.file.version_id, item.file.file_name)"
size="small"
type="standard"
>
<button @click="copy(getFileHash(item.file.version_id, item.file.file_name)!)">
<ClipboardCopyIcon class="size-4" />
Copy
</button>
</ButtonStyled>
<span v-else class="text-tertiary italic">N/A</span>
</td>
<td class="py-1 pr-2">
<CopyCode :text="item.file.version_id" />
</td>
<td class="py-1 pr-2">
<CopyCode :text="item.file.file_name" />
</td>
<td class="py-1">
<ButtonStyled size="small" type="standard">
<button @click="copy(item.file.download_url)">
<ClipboardCopyIcon class="size-4" />
Copy
</button>
</ButtonStyled>
</td>
</tr>
</tbody>
</table>
<p v-else class="text-sm italic text-secondary">No files currently marked as malicious.</p>
<div class="flex justify-end">
<ButtonStyled>
<button @click="hide">
<XIcon class="size-4" />
Close
</button>
</ButtonStyled>
</div>
</div>
</NewModal>
</template>