mirror of
https://github.com/modrinth/code.git
synced 2026-09-03 13:36:48 +00:00
feat(moderation): modpack scanning improvements (#6843)
* feat(moderation): make modpack scanning improvements * oopsie * feat(moderation): batch size for modpack scan * feat: i18n for modpack scan modal * chore: remove debug line * run intl:extract and prepr --------- Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
co-authored by
coolbot100s
Prospector
parent
2cf71a53f2
commit
b4380aee87
@@ -1,8 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Labrinth } from '@modrinth/api-client'
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
import { FolderSearchIcon, StarIcon, TrashIcon } from '@modrinth/assets'
|
import {
|
||||||
|
FolderSearchIcon,
|
||||||
|
RotateCounterClockwiseIcon,
|
||||||
|
SpinnerIcon,
|
||||||
|
StarIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
|
Combobox,
|
||||||
|
type ComboboxOption,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
@@ -12,6 +20,7 @@ import {
|
|||||||
type TableColumn,
|
type TableColumn,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
import { renderString } from '@modrinth/utils'
|
||||||
import { useQueryClient } from '@tanstack/vue-query'
|
import { useQueryClient } from '@tanstack/vue-query'
|
||||||
import { computed, ref, useTemplateRef } from 'vue'
|
import { computed, ref, useTemplateRef } from 'vue'
|
||||||
|
|
||||||
@@ -52,6 +61,10 @@ const messages = defineMessages({
|
|||||||
id: 'modpack-scan-modal.scanning',
|
id: 'modpack-scan-modal.scanning',
|
||||||
defaultMessage: 'Scanning...',
|
defaultMessage: 'Scanning...',
|
||||||
},
|
},
|
||||||
|
success: {
|
||||||
|
id: 'modpack-scan-modal.success',
|
||||||
|
defaultMessage: 'Success',
|
||||||
|
},
|
||||||
failed: {
|
failed: {
|
||||||
id: 'modpack-scan-modal.failed',
|
id: 'modpack-scan-modal.failed',
|
||||||
defaultMessage: 'Failed',
|
defaultMessage: 'Failed',
|
||||||
@@ -66,11 +79,40 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
scanError: {
|
scanError: {
|
||||||
id: 'modpack-scan-modal.scan-error',
|
id: 'modpack-scan-modal.scan-error',
|
||||||
defaultMessage: 'Some files failed to scan: {error}',
|
defaultMessage: 'Some files failed to scan: \n\n{error}',
|
||||||
},
|
},
|
||||||
clearAllGroups: {
|
batchPlaceholder: {
|
||||||
id: 'modpack-scan-modal.clear-all-groups',
|
id: 'modpack-scan-modal.batch.placeholder',
|
||||||
defaultMessage: 'Clear All Groups',
|
defaultMessage: 'Batch amount',
|
||||||
|
},
|
||||||
|
batchLabel: {
|
||||||
|
id: 'modpack-scan-modal.batch.label',
|
||||||
|
defaultMessage: '{amount} per batch',
|
||||||
|
},
|
||||||
|
deleteAllGroups: {
|
||||||
|
id: 'project.settings.permissions.delete-all-groups',
|
||||||
|
defaultMessage: 'Delete all groups',
|
||||||
|
},
|
||||||
|
deleteAllGroupsConfirmationTitle: {
|
||||||
|
id: 'project.settings.permissions.delete-all-groups-confirmation.title',
|
||||||
|
defaultMessage: 'Delete all attribution groups?',
|
||||||
|
},
|
||||||
|
deleteAllGroupsConfirmationDescription: {
|
||||||
|
id: 'modpack-scan-modal.delete-all-groups-confirmation.description',
|
||||||
|
defaultMessage:
|
||||||
|
'This will permanently delete all attribution groups for this project and all files inside them. This action cannot be undone.',
|
||||||
|
},
|
||||||
|
deleteAllGroupsConfirmationProceed: {
|
||||||
|
id: 'modpack-scan-modal.delete-all-groups.proceed',
|
||||||
|
defaultMessage: 'Clear',
|
||||||
|
},
|
||||||
|
deleteAllGroupsSuccess: {
|
||||||
|
id: 'modpack-scan-modal.delete-all-groups.success',
|
||||||
|
defaultMessage: 'All groups cleared successfully.',
|
||||||
|
},
|
||||||
|
deleteAllGroupsError: {
|
||||||
|
id: 'modpack-scan-modal.delete-all-groups.error',
|
||||||
|
defaultMessage: 'Failed to clear all groups: {error}',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -98,23 +140,64 @@ const modalRef = useTemplateRef<InstanceType<typeof NewModal>>('modalRef')
|
|||||||
const clearModalRef = useTemplateRef<InstanceType<typeof ConfirmModal>>('clearModalRef')
|
const clearModalRef = useTemplateRef<InstanceType<typeof ConfirmModal>>('clearModalRef')
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const rows = ref<ScanRow[]>([])
|
const rows = ref<Record<string, ScanRow>>({})
|
||||||
const isLoadingVersions = ref(false)
|
const isLoadingVersions = ref(false)
|
||||||
const isScanning = ref(false)
|
const isScanning = ref(false)
|
||||||
const isClearing = ref(false)
|
const isClearing = ref(false)
|
||||||
const versionLoadError = ref<string | null>(null)
|
const versionLoadError = ref<string | null>(null)
|
||||||
const scanError = ref<string | null>(null)
|
|
||||||
const requestId = ref(0)
|
const requestId = ref(0)
|
||||||
const scanRequestId = ref(0)
|
const scanRequestId = ref(0)
|
||||||
|
|
||||||
|
const DEFAULT_BATCH_AMOUNT = 10
|
||||||
|
const batchAmountOptions: ComboboxOption<number>[] = [
|
||||||
|
{ value: 1, label: '1' },
|
||||||
|
{ value: 5, label: '5' },
|
||||||
|
{ value: 10, label: '10' },
|
||||||
|
{ value: 20, label: '20' },
|
||||||
|
{ value: 50, label: '50' },
|
||||||
|
]
|
||||||
|
const batchAmountValues = batchAmountOptions.map((opt) => opt.value)
|
||||||
|
const batchAmountCookie = useCookie<number>('moderation-modpack-scan-batch', {
|
||||||
|
default: () => DEFAULT_BATCH_AMOUNT,
|
||||||
|
maxAge: 60 * 60 * 24 * 365,
|
||||||
|
sameSite: 'lax',
|
||||||
|
path: '/',
|
||||||
|
})
|
||||||
|
const batchAmount = computed({
|
||||||
|
get() {
|
||||||
|
const value = Number(batchAmountCookie.value)
|
||||||
|
return batchAmountValues.includes(value) ? value : DEFAULT_BATCH_AMOUNT
|
||||||
|
},
|
||||||
|
set(value: number) {
|
||||||
|
batchAmountCookie.value = value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const columns = computed<TableColumn<ScanTableColumn>[]>(() => [
|
const columns = computed<TableColumn<ScanTableColumn>[]>(() => [
|
||||||
{ key: 'filename', label: formatMessage(messages.packFileName), width: '60%' },
|
{ key: 'filename', label: formatMessage(messages.packFileName), width: '60%' },
|
||||||
{ key: 'newFiles', label: formatMessage(messages.newFiles), align: 'center', width: '20%' },
|
{ key: 'newFiles', label: formatMessage(messages.newFiles), align: 'center', width: '20%' },
|
||||||
{ key: 'newGroups', label: formatMessage(messages.newGroups), align: 'center', width: '20%' },
|
{ key: 'newGroups', label: formatMessage(messages.newGroups), align: 'center', width: '20%' },
|
||||||
])
|
])
|
||||||
|
|
||||||
const scannedCount = computed(() => rows.value.filter((row) => row.scan || row.error).length)
|
const scannedCount = computed(
|
||||||
|
() => Object.entries(rows.value).filter(([_, row]) => row.scan || row.error).length,
|
||||||
|
)
|
||||||
const isBusy = computed(() => isLoadingVersions.value || isScanning.value || isClearing.value)
|
const isBusy = computed(() => isLoadingVersions.value || isScanning.value || isClearing.value)
|
||||||
|
const titleButtonsDisabled = computed(() => isBusy.value || Object.keys(rows.value).length === 0)
|
||||||
|
const rescanButtonsDisabled = computed(() => isLoadingVersions.value || isClearing.value)
|
||||||
|
|
||||||
|
const rowErrors = computed(() =>
|
||||||
|
Object.entries(rows.value)
|
||||||
|
.filter(([_, row]) => row.error)
|
||||||
|
.map(([_, row]) => row),
|
||||||
|
)
|
||||||
|
|
||||||
|
const rowScanError = computed(() => {
|
||||||
|
if (rowErrors.value.length === 0) return undefined
|
||||||
|
return formatMessage(messages.scanError, {
|
||||||
|
error: rowErrors.value.map((r) => `\n - ${r.filename}`).join(''),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
function getErrorMessage(error: unknown) {
|
function getErrorMessage(error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -131,12 +214,27 @@ function getErrorMessage(error: unknown) {
|
|||||||
return String(error)
|
return String(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runWithConcurrency<T>(
|
||||||
|
items: T[],
|
||||||
|
limit: number,
|
||||||
|
task: (item: T) => Promise<void>,
|
||||||
|
): Promise<void> {
|
||||||
|
const queue = [...items]
|
||||||
|
const workers = Array.from({ length: limit }, async () => {
|
||||||
|
while (queue.length) {
|
||||||
|
const item = queue.shift()
|
||||||
|
if (item === undefined) return
|
||||||
|
await task(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await Promise.all(workers)
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchAllVersions() {
|
async function fetchAllVersions() {
|
||||||
const currentRequestId = ++requestId.value
|
const currentRequestId = ++requestId.value
|
||||||
isLoadingVersions.value = true
|
isLoadingVersions.value = true
|
||||||
versionLoadError.value = null
|
versionLoadError.value = null
|
||||||
scanError.value = null
|
rows.value = {}
|
||||||
rows.value = []
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const versions = await client.labrinth.versions_v2.getProjectVersions(props.project_id)
|
const versions = await client.labrinth.versions_v2.getProjectVersions(props.project_id)
|
||||||
@@ -144,17 +242,20 @@ async function fetchAllVersions() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.value = versions
|
const filteredVersions = versions
|
||||||
.flatMap((version) => version.files)
|
.flatMap((version) => version.files)
|
||||||
.filter((file): file is Labrinth.Versions.v2.VersionFile & { id: string } => Boolean(file.id))
|
.filter((file): file is Labrinth.Versions.v2.VersionFile & { id: string } => Boolean(file.id))
|
||||||
.map((file) => ({
|
|
||||||
id: file.id,
|
for (const version of filteredVersions) {
|
||||||
filename: file.filename,
|
rows.value[version.id] = {
|
||||||
primary: file.primary,
|
id: version.id,
|
||||||
|
filename: version.filename,
|
||||||
|
primary: version.primary,
|
||||||
isScanning: false,
|
isScanning: false,
|
||||||
newFiles: undefined,
|
newFiles: undefined,
|
||||||
newGroups: undefined,
|
newGroups: undefined,
|
||||||
}))
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (currentRequestId === requestId.value) {
|
if (currentRequestId === requestId.value) {
|
||||||
versionLoadError.value = formatMessage(messages.loadVersionsError, {
|
versionLoadError.value = formatMessage(messages.loadVersionsError, {
|
||||||
@@ -168,54 +269,45 @@ async function fetchAllVersions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchScan(id: string) {
|
||||||
|
rows.value[id].isScanning = true
|
||||||
|
try {
|
||||||
|
const scan = await client.labrinth.attribution_internal.scanFile(id)
|
||||||
|
|
||||||
|
rows.value[id].scan = scan
|
||||||
|
rows.value[id].newFiles = scan.new_attribution_files
|
||||||
|
rows.value[id].newGroups = scan.new_attribution_groups
|
||||||
|
|
||||||
|
rows.value[id].error = undefined
|
||||||
|
} catch (error) {
|
||||||
|
rows.value[id].error = getErrorMessage(error)
|
||||||
|
} finally {
|
||||||
|
rows.value[id].isScanning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchAllScans() {
|
async function fetchAllScans() {
|
||||||
if (isBusy.value) {
|
if (isBusy.value) return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentScanRequestId = ++scanRequestId.value
|
|
||||||
isScanning.value = true
|
isScanning.value = true
|
||||||
scanError.value = null
|
|
||||||
rows.value = rows.value.map((row) => ({
|
Object.entries(rows.value).map(([id, row]) => {
|
||||||
...row,
|
rows.value[id] = {
|
||||||
scan: undefined,
|
...row,
|
||||||
isScanning: false,
|
scan: undefined,
|
||||||
error: undefined,
|
isScanning: false,
|
||||||
newFiles: undefined,
|
error: undefined,
|
||||||
newGroups: undefined,
|
newFiles: undefined,
|
||||||
}))
|
newGroups: undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const row of rows.value) {
|
await runWithConcurrency(Object.keys(rows.value), batchAmount.value, async (id: string) => {
|
||||||
if (currentScanRequestId !== scanRequestId.value) {
|
await fetchScan(id)
|
||||||
return
|
})
|
||||||
}
|
|
||||||
|
|
||||||
row.isScanning = true
|
|
||||||
try {
|
|
||||||
const scan = await client.labrinth.attribution_internal.scanFile(row.id)
|
|
||||||
if (currentScanRequestId !== scanRequestId.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
row.scan = scan
|
|
||||||
row.newFiles = scan.new_attribution_files
|
|
||||||
row.newGroups = scan.new_attribution_groups
|
|
||||||
} catch (error) {
|
|
||||||
if (currentScanRequestId !== scanRequestId.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
row.error = getErrorMessage(error)
|
|
||||||
scanError.value = formatMessage(messages.scanError, { error: row.error })
|
|
||||||
} finally {
|
|
||||||
row.isScanning = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (currentScanRequestId === scanRequestId.value) {
|
isScanning.value = false
|
||||||
isScanning.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,8 +334,8 @@ async function clearAllGroups() {
|
|||||||
failed = true
|
failed = true
|
||||||
addNotification({
|
addNotification({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: 'An error occurred',
|
title: formatMessage(messages.failed),
|
||||||
text: `Failed to clear all groups: ${getErrorMessage(error)}`,
|
text: formatMessage(messages.deleteAllGroupsError, { error: getErrorMessage(error) }),
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
isClearing.value = false
|
isClearing.value = false
|
||||||
@@ -252,8 +344,8 @@ async function clearAllGroups() {
|
|||||||
if (!failed) {
|
if (!failed) {
|
||||||
addNotification({
|
addNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Success',
|
title: formatMessage(messages.success),
|
||||||
text: 'All groups cleared successfully.',
|
text: formatMessage(messages.deleteAllGroupsSuccess),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +353,7 @@ async function clearAllGroups() {
|
|||||||
function show() {
|
function show() {
|
||||||
scanRequestId.value++
|
scanRequestId.value++
|
||||||
isScanning.value = false
|
isScanning.value = false
|
||||||
rows.value = []
|
rows.value = {}
|
||||||
void fetchAllVersions()
|
void fetchAllVersions()
|
||||||
modalRef.value?.show()
|
modalRef.value?.show()
|
||||||
}
|
}
|
||||||
@@ -275,9 +367,9 @@ defineExpose({ show, hide })
|
|||||||
<template>
|
<template>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
ref="clearModalRef"
|
ref="clearModalRef"
|
||||||
title="Clear all permission groups?"
|
:title="formatMessage(messages.deleteAllGroupsConfirmationTitle)"
|
||||||
description="This will clear **all** groups for this project. This action cannot be undone."
|
:description="formatMessage(messages.deleteAllGroupsConfirmationDescription)"
|
||||||
proceed-label="Clear"
|
:proceed-label="formatMessage(messages.deleteAllGroupsConfirmationProceed)"
|
||||||
@proceed="clearAllGroups"
|
@proceed="clearAllGroups"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -294,27 +386,43 @@ defineExpose({ show, hide })
|
|||||||
{{
|
{{
|
||||||
formatMessage(messages.title, {
|
formatMessage(messages.title, {
|
||||||
scanned: scannedCount,
|
scanned: scannedCount,
|
||||||
total: rows.length,
|
total: Object.keys(rows).length,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
<Combobox
|
||||||
|
v-model="batchAmount"
|
||||||
|
:options="batchAmountOptions"
|
||||||
|
:disabled="titleButtonsDisabled"
|
||||||
|
:placeholder="formatMessage(messages.batchPlaceholder)"
|
||||||
|
>
|
||||||
|
<template #selected>
|
||||||
|
<span class="flex flex-row gap-2 align-middle font-semibold">
|
||||||
|
<span class="truncate text-contrast">{{
|
||||||
|
formatMessage(messages.batchLabel, { amount: batchAmount })
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</Combobox>
|
||||||
<ButtonStyled circular color="red" color-fill="none">
|
<ButtonStyled circular color="red" color-fill="none">
|
||||||
<button
|
<button
|
||||||
v-tooltip="formatMessage(messages.clearAllGroups)"
|
v-tooltip="formatMessage(messages.deleteAllGroups)"
|
||||||
:disabled="isBusy || rows.length === 0"
|
:disabled="titleButtonsDisabled"
|
||||||
@click="showConfirmClearGroups"
|
@click="showConfirmClearGroups"
|
||||||
>
|
>
|
||||||
<TrashIcon aria-hidden="true" />
|
<TrashIcon v-if="!isClearing" aria-hidden="true" />
|
||||||
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled circular>
|
<ButtonStyled circular>
|
||||||
<button
|
<button
|
||||||
v-tooltip="formatMessage(messages.scanAllFiles)"
|
v-tooltip="formatMessage(messages.scanAllFiles)"
|
||||||
:disabled="isBusy || rows.length === 0"
|
:disabled="titleButtonsDisabled"
|
||||||
@click="fetchAllScans"
|
@click="fetchAllScans"
|
||||||
>
|
>
|
||||||
<FolderSearchIcon aria-hidden="true" />
|
<FolderSearchIcon v-if="!isScanning" aria-hidden="true" />
|
||||||
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,14 +431,14 @@ defineExpose({ show, hide })
|
|||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div
|
<div
|
||||||
v-if="versionLoadError || scanError"
|
v-if="versionLoadError || rowScanError"
|
||||||
class="mb-3 rounded-xl bg-highlight-red p-3 text-red"
|
class="mb-3 rounded-xl bg-highlight-red px-4 py-1 text-red"
|
||||||
>
|
>
|
||||||
{{ versionLoadError || scanError }}
|
<div v-html="renderString((versionLoadError || rowScanError) ?? '')"></div>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<Table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="rows"
|
:data="Object.entries(rows).map(([_, row]) => row)"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:row-below-visible="
|
:row-below-visible="
|
||||||
(row) => Boolean(row.scan?.scanned_file_names && row.scan.scanned_file_names.length > 0)
|
(row) => Boolean(row.scan?.scanned_file_names && row.scan.scanned_file_names.length > 0)
|
||||||
@@ -345,16 +453,36 @@ defineExpose({ show, hide })
|
|||||||
</template>
|
</template>
|
||||||
<template #cell-newFiles="{ row }">
|
<template #cell-newFiles="{ row }">
|
||||||
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
|
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
|
||||||
<span v-else-if="row.error" v-tooltip="row.error" class="text-red">
|
<span v-else-if="row.error" v-tooltip="row.error" class="flex justify-center">
|
||||||
{{ formatMessage(messages.failed) }}
|
<ButtonStyled
|
||||||
|
class="justify-self-center"
|
||||||
|
color="red"
|
||||||
|
type="outlined"
|
||||||
|
hover-color-fill="background"
|
||||||
|
>
|
||||||
|
<button :disabled="rescanButtonsDisabled" @click="() => fetchScan(row.id)">
|
||||||
|
<RotateCounterClockwiseIcon />
|
||||||
|
{{ formatMessage(messages.failed) }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="row.scan">{{ row.scan.new_attribution_files }}</span>
|
<span v-else-if="row.scan">{{ row.scan.new_attribution_files }}</span>
|
||||||
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
|
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #cell-newGroups="{ row }">
|
<template #cell-newGroups="{ row }">
|
||||||
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
|
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
|
||||||
<span v-else-if="row.error" v-tooltip="row.error" class="text-red">
|
<span v-else-if="row.error" v-tooltip="row.error" class="flex justify-center">
|
||||||
{{ formatMessage(messages.failed) }}
|
<ButtonStyled
|
||||||
|
class="justify-self-center"
|
||||||
|
color="red"
|
||||||
|
type="outlined"
|
||||||
|
hover-color-fill="background"
|
||||||
|
>
|
||||||
|
<button :disabled="rescanButtonsDisabled" @click="() => fetchScan(row.id)">
|
||||||
|
<RotateCounterClockwiseIcon />
|
||||||
|
{{ formatMessage(messages.failed) }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="row.scan">{{ row.scan.new_attribution_groups }}</span>
|
<span v-else-if="row.scan">{{ row.scan.new_attribution_groups }}</span>
|
||||||
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
|
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
|
||||||
|
|||||||
@@ -2972,9 +2972,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Technische Überprüfung"
|
"message": "Technische Überprüfung"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Alle Gruppen entfernen"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Fehlgeschlagen"
|
"message": "Fehlgeschlagen"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2972,9 +2972,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Technische Überprüfung"
|
"message": "Technische Überprüfung"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Alle Gruppen entfernen"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Fehlgeschlagen"
|
"message": "Fehlgeschlagen"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2972,8 +2972,23 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Tech review"
|
"message": "Tech review"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
"modpack-scan-modal.batch.label": {
|
||||||
"message": "Clear All Groups"
|
"message": "{amount} per batch"
|
||||||
|
},
|
||||||
|
"modpack-scan-modal.batch.placeholder": {
|
||||||
|
"message": "Batch amount"
|
||||||
|
},
|
||||||
|
"modpack-scan-modal.delete-all-groups-confirmation.description": {
|
||||||
|
"message": "This will permanently delete all attribution groups for this project and all files inside them. This action cannot be undone."
|
||||||
|
},
|
||||||
|
"modpack-scan-modal.delete-all-groups.error": {
|
||||||
|
"message": "Failed to clear all groups: {error}"
|
||||||
|
},
|
||||||
|
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||||
|
"message": "Clear"
|
||||||
|
},
|
||||||
|
"modpack-scan-modal.delete-all-groups.success": {
|
||||||
|
"message": "All groups cleared successfully."
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Failed"
|
"message": "Failed"
|
||||||
@@ -3006,11 +3021,14 @@
|
|||||||
"message": "Scan All Files"
|
"message": "Scan All Files"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.scan-error": {
|
"modpack-scan-modal.scan-error": {
|
||||||
"message": "Some files failed to scan: {error}"
|
"message": "Some files failed to scan: \n\n{error}"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.scanning": {
|
"modpack-scan-modal.scanning": {
|
||||||
"message": "Scanning..."
|
"message": "Scanning..."
|
||||||
},
|
},
|
||||||
|
"modpack-scan-modal.success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"modpack-scan-modal.title": {
|
"modpack-scan-modal.title": {
|
||||||
"message": "Modpack Scan ({scanned}/{total} Files)"
|
"message": "Modpack Scan ({scanned}/{total} Files)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2954,9 +2954,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Revue technique"
|
"message": "Revue technique"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Effacer tous les groupes"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Échec"
|
"message": "Échec"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2957,9 +2957,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Revisione tecnica"
|
"message": "Revisione tecnica"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Svuota gruppi"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Errore"
|
"message": "Errore"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2942,9 +2942,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "기술 리뷰"
|
"message": "기술 리뷰"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "모든 그룹 삭제"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "실패"
|
"message": "실패"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2939,9 +2939,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Technische beoordeling"
|
"message": "Technische beoordeling"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Alle groepen wissen"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Mislukt"
|
"message": "Mislukt"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2942,9 +2942,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Revisão técnica"
|
"message": "Revisão técnica"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Limpar todos os grupos"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Falhou"
|
"message": "Falhou"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2960,9 +2960,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Техпроверка"
|
"message": "Техпроверка"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Очистить все группы"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Ошибка"
|
"message": "Ошибка"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2936,9 +2936,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "Технічний огляд"
|
"message": "Технічний огляд"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "Очистити всі групи"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "Помилка"
|
"message": "Помилка"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2969,9 +2969,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "技术审查"
|
"message": "技术审查"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "清除所有分组"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "失败"
|
"message": "失败"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2948,9 +2948,6 @@
|
|||||||
"moderation.page.technicalReview": {
|
"moderation.page.technicalReview": {
|
||||||
"message": "技術審查"
|
"message": "技術審查"
|
||||||
},
|
},
|
||||||
"modpack-scan-modal.clear-all-groups": {
|
|
||||||
"message": "清除所有群組"
|
|
||||||
},
|
|
||||||
"modpack-scan-modal.failed": {
|
"modpack-scan-modal.failed": {
|
||||||
"message": "失敗"
|
"message": "失敗"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user