mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
feat: moderation modpack scan button
This commit is contained in:
@@ -0,0 +1,120 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
|
import { FolderSearchIcon, StarIcon } from '@modrinth/assets'
|
||||||
|
import { ButtonStyled, injectModrinthClient, NewModal } from '@modrinth/ui'
|
||||||
|
import { useTemplateRef } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
project_id: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const client = injectModrinthClient()
|
||||||
|
const modalRef = useTemplateRef<InstanceType<typeof NewModal>>('modalRef')
|
||||||
|
|
||||||
|
const files = ref<Labrinth.Versions.v2.VersionFile[]>([])
|
||||||
|
const scans = ref<Map<string, Labrinth.Attribution.Internal.FileScanResponse>>(new Map())
|
||||||
|
const isFetching = ref(false)
|
||||||
|
|
||||||
|
async function fetchAllVersions() {
|
||||||
|
const versions = await client.labrinth.versions_v2.getProjectVersions(props.project_id)
|
||||||
|
files.value = versions.flatMap((version) => version.files)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAllScans() {
|
||||||
|
isFetching.value = true
|
||||||
|
for (const file of files.value) {
|
||||||
|
scans.value.set(file.id!, await client.labrinth.attribution_internal.scanFile(file.id!))
|
||||||
|
}
|
||||||
|
isFetching.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
isFetching.value = false
|
||||||
|
fetchAllVersions()
|
||||||
|
modalRef.value?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
modalRef.value?.hide()
|
||||||
|
}
|
||||||
|
defineExpose({ show, hide })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NewModal
|
||||||
|
ref="modalRef"
|
||||||
|
width="60vw"
|
||||||
|
:close-on-click-outside="false"
|
||||||
|
:close-on-esc="false"
|
||||||
|
:disable-close="isFetching"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
|
<span class="text-2xl font-semibold text-contrast">
|
||||||
|
Modpack Scan ({{ scans.size }}/{{ files.length }} Files)
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<ButtonStyled circular>
|
||||||
|
<button v-tooltip="'Scan All Files'" :disabled="isFetching" @click="fetchAllScans">
|
||||||
|
<FolderSearchIcon aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="w-full">
|
||||||
|
<table class="w-full table-fixed border-collapse border-0">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-surface-5 font-bold">
|
||||||
|
<td class="p-2" style="width: 80%">Pack File Name</td>
|
||||||
|
<td class="p-2 text-center" style="width: 10%">New Files</td>
|
||||||
|
<td class="p-2 text-center" style="width: 10%">New Groups</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template v-for="(file, index) in files" :key="file.id">
|
||||||
|
<tr :class="index % 2 === 0 ? 'bg-surface-1' : 'bg-surface-2'">
|
||||||
|
<td class="flex h-full flex-row items-center gap-1 p-2">
|
||||||
|
<StarIcon v-if="file.primary" />{{ file.filename }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="p-2 text-center">
|
||||||
|
<span v-if="!scans.get(file.id!!)">...</span>
|
||||||
|
<span v-else>{{ scans.get(file.id!!)?.new_attribution_files!!! }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="p-2 text-center">
|
||||||
|
<span v-if="!scans.get(file.id!!)">...</span>
|
||||||
|
<span v-else>{{ scans.get(file.id!!)?.new_attribution_groups!!! }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-if="scans.get(file.id!!)?.scanned_file_names"
|
||||||
|
:class="index % 2 === 0 ? 'bg-surface-1' : 'bg-surface-2'"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<td colspan="3" class="p-2 pt-0">
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
Override Files ({{ scans.get(file.id!!)?.scanned_file_names?.length || 0 }})
|
||||||
|
</summary>
|
||||||
|
<div class="flex flex-wrap gap-1 pt-2">
|
||||||
|
<span
|
||||||
|
v-for="name of scans.get(file.id!!)?.scanned_file_names || []"
|
||||||
|
:key="name"
|
||||||
|
v-tooltip="name"
|
||||||
|
class="flex items-center gap-1 text-wrap rounded-full bg-button-bg px-2 py-0.5 text-xs font-medium text-contrast"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</NewModal>
|
||||||
|
</template>
|
||||||
@@ -95,6 +95,8 @@
|
|||||||
@download="triggerDownloadAnimation"
|
@download="triggerDownloadAnimation"
|
||||||
/>
|
/>
|
||||||
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
<CollectionCreateModal ref="modal_collection" :project-ids="[project.id]" />
|
||||||
|
<ModpackScanModal ref="scanModal" :project_id="project.id" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="new-page sidebar"
|
class="new-page sidebar"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -424,13 +426,6 @@
|
|||||||
tags.staffRoles.includes(auth.user.role) &&
|
tags.staffRoles.includes(auth.user.role) &&
|
||||||
!showModerationChecklist,
|
!showModerationChecklist,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
shown:
|
|
||||||
auth.user &&
|
|
||||||
tags.staffRoles.includes(auth.user.role) &&
|
|
||||||
!showModerationChecklist,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'tech-review',
|
id: 'tech-review',
|
||||||
link: `/moderation/technical-review/${project.id}`,
|
link: `/moderation/technical-review/${project.id}`,
|
||||||
@@ -438,6 +433,16 @@
|
|||||||
hoverOnly: true,
|
hoverOnly: true,
|
||||||
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'moderation-modpack-rescan',
|
||||||
|
action: () => scanModal.show(),
|
||||||
|
color: 'orange',
|
||||||
|
hoverOnly: true,
|
||||||
|
shown:
|
||||||
|
auth.user &&
|
||||||
|
tags.staffRoles.includes(auth.user.role) &&
|
||||||
|
project.actualProjectType === 'modpack',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
shown: auth.user && tags.staffRoles.includes(auth.user.role),
|
||||||
@@ -469,6 +474,9 @@
|
|||||||
<ScaleIcon aria-hidden="true" /> {{ formatMessage(messages.reviewProject) }}
|
<ScaleIcon aria-hidden="true" /> {{ formatMessage(messages.reviewProject) }}
|
||||||
</template>
|
</template>
|
||||||
<template #tech-review> <ScanEyeIcon aria-hidden="true" /> Tech review </template>
|
<template #tech-review> <ScanEyeIcon aria-hidden="true" /> Tech review </template>
|
||||||
|
<template #moderation-modpack-rescan>
|
||||||
|
<FolderSearchIcon aria-hidden="true" /> Rescan modpack
|
||||||
|
</template>
|
||||||
<template #report>
|
<template #report>
|
||||||
<ReportIcon aria-hidden="true" />
|
<ReportIcon aria-hidden="true" />
|
||||||
{{ formatMessage(commonMessages.reportButton) }}
|
{{ formatMessage(commonMessages.reportButton) }}
|
||||||
@@ -726,6 +734,7 @@ import {
|
|||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
|
FolderSearchIcon,
|
||||||
HeartIcon,
|
HeartIcon,
|
||||||
ListIcon,
|
ListIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
@@ -785,6 +794,7 @@ import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.
|
|||||||
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
||||||
import ModerationChecklist from '~/components/ui/moderation/checklist/ModerationChecklist.vue'
|
import ModerationChecklist from '~/components/ui/moderation/checklist/ModerationChecklist.vue'
|
||||||
import ModerationProjectNags from '~/components/ui/moderation/ModerationProjectNags.vue'
|
import ModerationProjectNags from '~/components/ui/moderation/ModerationProjectNags.vue'
|
||||||
|
import ModpackScanModal from '~/components/ui/moderation/ModpackScanModal.vue'
|
||||||
import ProjectDownloadModal from '~/components/ui/ProjectDownloadModal/index.vue'
|
import ProjectDownloadModal from '~/components/ui/ProjectDownloadModal/index.vue'
|
||||||
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
|
import ProjectMemberHeader from '~/components/ui/ProjectMemberHeader.vue'
|
||||||
import { getSignInRouteObj } from '~/composables/auth.ts'
|
import { getSignInRouteObj } from '~/composables/auth.ts'
|
||||||
@@ -852,6 +862,7 @@ const debug = useDebugLogger('DownloadModal')
|
|||||||
const downloadModal = ref()
|
const downloadModal = ref()
|
||||||
const openInAppModal = ref()
|
const openInAppModal = ref()
|
||||||
const overTheTopDownloadAnimation = ref()
|
const overTheTopDownloadAnimation = ref()
|
||||||
|
const scanModal = ref()
|
||||||
|
|
||||||
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
const projectV3Loaded = computed(() => !projectV3Pending.value || projectV3.value != null)
|
||||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||||
|
|||||||
@@ -100,4 +100,21 @@ export class LabrinthAttributionInternalModule extends AbstractModule {
|
|||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan a file for attribution information.
|
||||||
|
* POST /_internal/attribution/file/{file_id}/scan
|
||||||
|
*
|
||||||
|
* @param fileId - The file ID to scan.
|
||||||
|
*/
|
||||||
|
public async scanFile(fileId: string): Promise<Labrinth.Attribution.Internal.FileScanResponse> {
|
||||||
|
return this.client.request<Labrinth.Attribution.Internal.FileScanResponse>(
|
||||||
|
`/attribution/file/${fileId}/scan`,
|
||||||
|
{
|
||||||
|
api: 'labrinth',
|
||||||
|
version: 'internal',
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,6 +397,12 @@ export namespace Labrinth {
|
|||||||
sha1: string
|
sha1: string
|
||||||
project_id: string
|
project_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FileScanResponse = {
|
||||||
|
new_attribution_groups: number
|
||||||
|
new_attribution_files: number
|
||||||
|
scanned_file_names: string[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1333,6 +1339,7 @@ export namespace Labrinth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type VersionFile = {
|
export type VersionFile = {
|
||||||
|
id?: string
|
||||||
hashes: VersionFileHash
|
hashes: VersionFileHash
|
||||||
url: string
|
url: string
|
||||||
filename: string
|
filename: string
|
||||||
@@ -1454,6 +1461,7 @@ export namespace Labrinth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface VersionFile {
|
export interface VersionFile {
|
||||||
|
id?: string
|
||||||
hashes: VersionFileHash
|
hashes: VersionFileHash
|
||||||
url: string
|
url: string
|
||||||
filename: string
|
filename: string
|
||||||
|
|||||||
Reference in New Issue
Block a user