mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-06-22 04:11:32 +10:00
Admin task UI update & QoL (#194)
* feat: revise library source names & update droplet * feat: add internal name hint to library sources * feat: update library source table with new name + icons * fix: admin invitation localisation issue * feat: #164 * feat: overhaul task UIs, #163 * fix: remove debug task * fix: lint
This commit is contained in:
@@ -10,6 +10,16 @@
|
|||||||
d="M4 13.5C4 11.0008 5.38798 8.76189 7.00766 7C8.43926 5.44272 10.0519 4.25811 11.0471 3.5959C11.6287 3.20893 12.3713 3.20893 12.9529 3.5959C13.9481 4.25811 15.5607 5.44272 16.9923 7C18.612 8.76189 20 11.0008 20 13.5C20 17.9183 16.4183 21.5 12 21.5C7.58172 21.5 4 17.9183 4 13.5Z"
|
d="M4 13.5C4 11.0008 5.38798 8.76189 7.00766 7C8.43926 5.44272 10.0519 4.25811 11.0471 3.5959C11.6287 3.20893 12.3713 3.20893 12.9529 3.5959C13.9481 4.25811 15.5607 5.44272 16.9923 7C18.612 8.76189 20 11.0008 20 13.5C20 17.9183 16.4183 21.5 12 21.5C7.58172 21.5 4 17.9183 4 13.5Z"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
|
stroke-dasharray="100"
|
||||||
|
:stroke-dashoffset="dashArray"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ progress?: number }>();
|
||||||
|
|
||||||
|
const dashArray = computed(() =>
|
||||||
|
props.progress === undefined ? 0 : ((100 - props.progress) / 100) * 50 + 50,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<span class="text-xs font-mono text-zinc-400 inline-flex items-top gap-x-2"
|
||||||
|
><span v-if="!short" class="text-zinc-500">{{ log.timestamp }}</span>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
colours[log.level] || 'text-green-400',
|
||||||
|
'uppercase font-display font-semibold',
|
||||||
|
]"
|
||||||
|
>{{ log.level }}</span
|
||||||
|
>
|
||||||
|
<pre :class="[short ? 'line-clamp-1' : '', 'mt-[1px]']">{{
|
||||||
|
log.message
|
||||||
|
}}</pre>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { TaskLog } from "~/server/internal/tasks";
|
||||||
|
|
||||||
|
defineProps<{ log: typeof TaskLog.infer; short?: boolean }>();
|
||||||
|
|
||||||
|
const colours: { [key: string]: string } = {
|
||||||
|
info: "text-blue-400",
|
||||||
|
warn: "text-yellow-400",
|
||||||
|
error: "text-red-400",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="task"
|
||||||
|
class="flex w-full items-center justify-between space-x-6 p-6"
|
||||||
|
>
|
||||||
|
<div class="flex-1 truncate">
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<div>
|
||||||
|
<CheckCircleIcon v-if="task.success" class="size-5 text-green-600" />
|
||||||
|
<XMarkIcon v-else-if="task.error" class="size-5 text-red-600" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="size-2 bg-blue-600 rounded-full animate-pulse m-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 class="truncate text-sm font-medium text-zinc-100">
|
||||||
|
{{ task.name }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="active"
|
||||||
|
class="mt-2 w-full rounded-full overflow-hidden bg-zinc-900"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:style="{ width: `${task.progress}%` }"
|
||||||
|
class="bg-blue-600 h-[3px] transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 bg-zinc-950 px-2 pb-1 rounded-sm">
|
||||||
|
<LogLine :short="true" :log="parseTaskLog(task.log.at(-1))" />
|
||||||
|
</div>
|
||||||
|
<NuxtLink
|
||||||
|
type="button"
|
||||||
|
:href="`/admin/task/${task.id}`"
|
||||||
|
class="mt-3 ml-1 rounded-md text-xs font-medium text-zinc-100 hover:text-zinc-300 focus:outline-none focus:ring-2 focus:ring-zinc-100 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<i18n-t keypath="tasks.admin.viewTask" tag="span" scope="global">
|
||||||
|
<template #arrow>
|
||||||
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<!-- renders server side when we don't want to access the current tasks -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CheckCircleIcon, XMarkIcon } from "@heroicons/vue/24/solid";
|
||||||
|
import type { TaskMessage } from "~/server/internal/tasks";
|
||||||
|
|
||||||
|
defineProps<{ task: TaskMessage | undefined; active?: boolean }>();
|
||||||
|
</script>
|
||||||
+26
-20
@@ -137,6 +137,10 @@
|
|||||||
"usernameTaken": "Username already taken."
|
"usernameTaken": "Username already taken."
|
||||||
},
|
},
|
||||||
"backHome": "{arrow} Back to home",
|
"backHome": "{arrow} Back to home",
|
||||||
|
"externalUrl": {
|
||||||
|
"subtitle": "This message is only visible to admins.",
|
||||||
|
"title": "Accessing over different EXTERNAL_URL. Please check the docs."
|
||||||
|
},
|
||||||
"game": {
|
"game": {
|
||||||
"banner": {
|
"banner": {
|
||||||
"description": "Drop failed to update the banner image: {0}",
|
"description": "Drop failed to update the banner image: {0}",
|
||||||
@@ -212,10 +216,6 @@
|
|||||||
"desc": "Drop encountered an error while updating the version: {error}",
|
"desc": "Drop encountered an error while updating the version: {error}",
|
||||||
"title": "There an error while updating the version order"
|
"title": "There an error while updating the version order"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"externalUrl": {
|
|
||||||
"title": "Accessing over different EXTERNAL_URL. Please check the docs.",
|
|
||||||
"subtitle": "This message is only visible to admins."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
@@ -327,6 +327,7 @@
|
|||||||
"description": "Companies organize games by who they were developed or published by.",
|
"description": "Companies organize games by who they were developed or published by.",
|
||||||
"editor": {
|
"editor": {
|
||||||
"action": "Add Game {plus}",
|
"action": "Add Game {plus}",
|
||||||
|
"descriptionPlaceholder": "{'<'}description{'>'}",
|
||||||
"developed": "Developed",
|
"developed": "Developed",
|
||||||
"libraryDescription": "Add, remove, or customise what this company has developed and/or published.",
|
"libraryDescription": "Add, remove, or customise what this company has developed and/or published.",
|
||||||
"libraryTitle": "Game Library",
|
"libraryTitle": "Game Library",
|
||||||
@@ -334,25 +335,23 @@
|
|||||||
"published": "Published",
|
"published": "Published",
|
||||||
"uploadBanner": "Upload banner",
|
"uploadBanner": "Upload banner",
|
||||||
"uploadIcon": "Upload icon",
|
"uploadIcon": "Upload icon",
|
||||||
"descriptionPlaceholder": "{'<'}description{'>'}",
|
|
||||||
"websitePlaceholder": "{'<'}website{'>'}"
|
"websitePlaceholder": "{'<'}website{'>'}"
|
||||||
},
|
},
|
||||||
"modals": {
|
"modals": {
|
||||||
|
"createDescription": "Create a company to further organize your games.",
|
||||||
|
"createFieldDescription": "Company Description",
|
||||||
|
"createFieldDescriptionPlaceholder": "A small indie studio that...",
|
||||||
|
"createFieldName": "Company Name",
|
||||||
|
"createFieldNamePlaceholder": "My New Company...",
|
||||||
|
"createFieldWebsite": "Company Website",
|
||||||
|
"createFieldWebsitePlaceholder": "https://example.com/",
|
||||||
|
"createTitle": "Create a company",
|
||||||
"nameDescription": "Edit the company's name. Used to match to new game imports.",
|
"nameDescription": "Edit the company's name. Used to match to new game imports.",
|
||||||
"nameTitle": "Edit company name",
|
"nameTitle": "Edit company name",
|
||||||
"shortDeckDescription": "Edit the company's description. Doesn't affect long (markdown) description.",
|
"shortDeckDescription": "Edit the company's description. Doesn't affect long (markdown) description.",
|
||||||
"shortDeckTitle": "Edit company description",
|
"shortDeckTitle": "Edit company description",
|
||||||
"websiteDescription": "Edit the company's website. Note: this will be a link, and won't have redirect protection.",
|
"websiteDescription": "Edit the company's website. Note: this will be a link, and won't have redirect protection.",
|
||||||
"websiteTitle": "Edit company website",
|
"websiteTitle": "Edit company website"
|
||||||
|
|
||||||
"createTitle": "Create a company",
|
|
||||||
"createDescription": "Create a company to further organize your games.",
|
|
||||||
"createFieldName": "Company Name",
|
|
||||||
"createFieldNamePlaceholder": "My New Company...",
|
|
||||||
"createFieldDescription": "Company Description",
|
|
||||||
"createFieldDescriptionPlaceholder": "A small indie studio that...",
|
|
||||||
"createFieldWebsite": "Company Website",
|
|
||||||
"createFieldWebsitePlaceholder": "https://example.com/"
|
|
||||||
},
|
},
|
||||||
"noCompanies": "No companies",
|
"noCompanies": "No companies",
|
||||||
"noGames": "No games",
|
"noGames": "No games",
|
||||||
@@ -373,6 +372,8 @@
|
|||||||
},
|
},
|
||||||
"metadataProvider": "Metadata provider",
|
"metadataProvider": "Metadata provider",
|
||||||
"noGames": "No games imported",
|
"noGames": "No games imported",
|
||||||
|
"libraryHint": "No libraries configured.",
|
||||||
|
"libraryHintDocsLink": "What does this mean? {arrow}",
|
||||||
"offline": "Drop couldn't access this game.",
|
"offline": "Drop couldn't access this game.",
|
||||||
"offlineTitle": "Game offline",
|
"offlineTitle": "Game offline",
|
||||||
"openEditor": "Open in Editor {arrow}",
|
"openEditor": "Open in Editor {arrow}",
|
||||||
@@ -382,12 +383,15 @@
|
|||||||
"create": "Create source",
|
"create": "Create source",
|
||||||
"createDesc": "Drop will use this source to access your game library, and make them available.",
|
"createDesc": "Drop will use this source to access your game library, and make them available.",
|
||||||
"desc": "Configure your library sources, where Drop will look for new games and versions to import.",
|
"desc": "Configure your library sources, where Drop will look for new games and versions to import.",
|
||||||
|
"documentationLink": "Documentation {arrow}",
|
||||||
"edit": "Edit source",
|
"edit": "Edit source",
|
||||||
"fsDesc": "Imports games from a path on disk. Requires version-based folder structure, and supports archived games.",
|
"fsDesc": "Imports games from a path on disk. Requires version-based folder structure, and supports archived games.",
|
||||||
"fsFlatDesc": "Imports games from a path on disk, but without a separate version subfolder. Useful when migrating an existing library to Drop.",
|
"fsFlatDesc": "Imports games from a path on disk, but without a separate version subfolder. Useful when migrating an existing library to Drop.",
|
||||||
|
"fsFlatTitle": "Compatibility",
|
||||||
"fsPath": "Path",
|
"fsPath": "Path",
|
||||||
"fsPathDesc": "An absolute path to your game library.",
|
"fsPathDesc": "An absolute path to your game library.",
|
||||||
"fsPathPlaceholder": "/mnt/games",
|
"fsPathPlaceholder": "/mnt/games",
|
||||||
|
"fsTitle": "Drop-style",
|
||||||
"link": "Sources {arrow}",
|
"link": "Sources {arrow}",
|
||||||
"nameDesc": "The name of your source, for reference.",
|
"nameDesc": "The name of your source, for reference.",
|
||||||
"namePlaceholder": "My New Source",
|
"namePlaceholder": "My New Source",
|
||||||
@@ -514,13 +518,13 @@
|
|||||||
"images": "Game Images",
|
"images": "Game Images",
|
||||||
"lookAt": "Check it out",
|
"lookAt": "Check it out",
|
||||||
"noDevelopers": "No developers",
|
"noDevelopers": "No developers",
|
||||||
"noGame": "NO GAME",
|
|
||||||
"noFeatured": "NO FEATURED GAMES",
|
"noFeatured": "NO FEATURED GAMES",
|
||||||
"openFeatured": "Star games in Admin Library {arrow}",
|
"noGame": "NO GAME",
|
||||||
"noImages": "No images",
|
"noImages": "No images",
|
||||||
"noPublishers": "No publishers.",
|
"noPublishers": "No publishers.",
|
||||||
"noTags": "No tags",
|
"noTags": "No tags",
|
||||||
"openAdminDashboard": "Open in Admin Dashboard",
|
"openAdminDashboard": "Open in Admin Dashboard",
|
||||||
|
"openFeatured": "Star games in Admin Library {arrow}",
|
||||||
"platform": "Platform | Platform | Platforms",
|
"platform": "Platform | Platform | Platforms",
|
||||||
"publishers": "Publishers | Publisher | Publishers",
|
"publishers": "Publishers | Publisher | Publishers",
|
||||||
"rating": "Rating",
|
"rating": "Rating",
|
||||||
@@ -560,7 +564,9 @@
|
|||||||
"cleanupSessionsName": "Clean up sessions."
|
"cleanupSessionsName": "Clean up sessions."
|
||||||
},
|
},
|
||||||
"viewTask": "View {arrow}",
|
"viewTask": "View {arrow}",
|
||||||
"weeklyScheduledTitle": "Weekly scheduled tasks"
|
"weeklyScheduledTitle": "Weekly scheduled tasks",
|
||||||
|
"progress": "{0}%",
|
||||||
|
"execute": "{arrow} Execute"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Drop",
|
"title": "Drop",
|
||||||
@@ -585,7 +591,6 @@
|
|||||||
"admin": {
|
"admin": {
|
||||||
"adminHeader": "Admin?",
|
"adminHeader": "Admin?",
|
||||||
"adminUserLabel": "Admin user",
|
"adminUserLabel": "Admin user",
|
||||||
"authLink": "Authentication {arrow}",
|
|
||||||
"authentication": {
|
"authentication": {
|
||||||
"configure": "Configure",
|
"configure": "Configure",
|
||||||
"description": "Drop supports a variety of \"authentication mechanisms\". As you enable or disable them, they are shown on the sign in screen for users to select from. Click the dot menu to configure the authentication mechanism.",
|
"description": "Drop supports a variety of \"authentication mechanisms\". As you enable or disable them, they are shown on the sign in screen for users to select from. Click the dot menu to configure the authentication mechanism.",
|
||||||
@@ -597,6 +602,7 @@
|
|||||||
"srOpenOptions": "Open options",
|
"srOpenOptions": "Open options",
|
||||||
"title": "Authentication"
|
"title": "Authentication"
|
||||||
},
|
},
|
||||||
|
"authLink": "Authentication {arrow}",
|
||||||
"authoptionsHeader": "Auth Options",
|
"authoptionsHeader": "Auth Options",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleteUser": "Delete user {0}",
|
"deleteUser": "Delete user {0}",
|
||||||
@@ -609,7 +615,7 @@
|
|||||||
"createInvitation": "Create invitation",
|
"createInvitation": "Create invitation",
|
||||||
"description": "Simple authentication uses a system of 'invitations' to create users. You can create an invitation, and optionally specify a username or email for the user, and then it will generate a magic URL that can be used to create an account.",
|
"description": "Simple authentication uses a system of 'invitations' to create users. You can create an invitation, and optionally specify a username or email for the user, and then it will generate a magic URL that can be used to create an account.",
|
||||||
"expires": "Expires: {expiry}",
|
"expires": "Expires: {expiry}",
|
||||||
"invitationTitle": "invitations",
|
"invitationTitle": "Invitations",
|
||||||
"invite3Days": "3 days",
|
"invite3Days": "3 days",
|
||||||
"invite6Months": "6 months",
|
"invite6Months": "6 months",
|
||||||
"inviteAdminSwitchDescription": "Create this user as an administrator",
|
"inviteAdminSwitchDescription": "Create this user as an administrator",
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "drop",
|
"name": "drop",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "^16.0.1",
|
"@discordapp/twemoji": "^16.0.1",
|
||||||
"@drop-oss/droplet": "2.3.0",
|
"@drop-oss/droplet": "2.3.1",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@heroicons/vue": "^2.1.5",
|
"@heroicons/vue": "^2.1.5",
|
||||||
"@lobomfz/prismark": "0.0.3",
|
"@lobomfz/prismark": "0.0.3",
|
||||||
|
|||||||
@@ -242,11 +242,40 @@
|
|||||||
{{ $t("common.noResults") }}
|
{{ $t("common.noResults") }}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
v-if="filteredLibraryGames.length == 0 && libraryGames.length == 0"
|
v-if="
|
||||||
|
filteredLibraryGames.length == 0 &&
|
||||||
|
libraryGames.length == 0 &&
|
||||||
|
libraryState.hasLibraries
|
||||||
|
"
|
||||||
class="text-zinc-600 text-sm font-display font-bold uppercase text-center col-span-4"
|
class="text-zinc-600 text-sm font-display font-bold uppercase text-center col-span-4"
|
||||||
>
|
>
|
||||||
{{ $t("library.admin.noGames") }}
|
{{ $t("library.admin.noGames") }}
|
||||||
</p>
|
</p>
|
||||||
|
<p
|
||||||
|
v-else-if="!libraryState.hasLibraries"
|
||||||
|
class="flex flex-col gap-2 text-zinc-600 text-center col-span-4"
|
||||||
|
>
|
||||||
|
<span class="text-sm font-display font-bold uppercase">{{
|
||||||
|
$t("library.admin.libraryHint")
|
||||||
|
}}</span>
|
||||||
|
|
||||||
|
<NuxtLink
|
||||||
|
class="transition text-xs text-zinc-600 hover:underline hover:text-zinc-400"
|
||||||
|
href="https://docs.droposs.org/docs/library"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="library.admin.libraryHintDocsLink"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
class="inline-flex items-center gap-x-1"
|
||||||
|
>
|
||||||
|
<template #arrow>
|
||||||
|
<ArrowTopRightOnSquareIcon class="size-4" />
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</NuxtLink>
|
||||||
|
</p>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -256,7 +285,11 @@ import {
|
|||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
ExclamationCircleIcon,
|
ExclamationCircleIcon,
|
||||||
} from "@heroicons/vue/16/solid";
|
} from "@heroicons/vue/16/solid";
|
||||||
import { InformationCircleIcon, StarIcon } from "@heroicons/vue/20/solid";
|
import {
|
||||||
|
ArrowTopRightOnSquareIcon,
|
||||||
|
InformationCircleIcon,
|
||||||
|
StarIcon,
|
||||||
|
} from "@heroicons/vue/20/solid";
|
||||||
import { MagnifyingGlassIcon } from "@heroicons/vue/24/outline";
|
import { MagnifyingGlassIcon } from "@heroicons/vue/24/outline";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|||||||
@@ -64,8 +64,14 @@
|
|||||||
>
|
>
|
||||||
{{ source.name }}
|
{{ source.name }}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
<td
|
||||||
{{ source.backend }}
|
class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400 inline-flex gap-x-1 items-center"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="optionsMetadata[source.backend].icon"
|
||||||
|
class="size-5 text-zinc-400"
|
||||||
|
/>
|
||||||
|
{{ optionsMetadata[source.backend].title }}
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||||
<CheckIcon
|
<CheckIcon
|
||||||
@@ -189,11 +195,34 @@
|
|||||||
<RadioGroupLabel
|
<RadioGroupLabel
|
||||||
as="span"
|
as="span"
|
||||||
class="font-semibold text-zinc-100"
|
class="font-semibold text-zinc-100"
|
||||||
>{{ source }}</RadioGroupLabel
|
>{{ metadata.title }}
|
||||||
|
<span class="ml-2 font-mono text-zinc-500 text-xs">{{
|
||||||
|
source
|
||||||
|
}}</span></RadioGroupLabel
|
||||||
|
>
|
||||||
|
<RadioGroupDescription
|
||||||
|
as="span"
|
||||||
|
class="text-zinc-400 text-xs"
|
||||||
>
|
>
|
||||||
<RadioGroupDescription as="span" class="text-zinc-400">
|
|
||||||
{{ metadata.description }}
|
{{ metadata.description }}
|
||||||
</RadioGroupDescription>
|
</RadioGroupDescription>
|
||||||
|
<NuxtLink
|
||||||
|
:href="metadata.docsLink"
|
||||||
|
:external="true"
|
||||||
|
target="_blank"
|
||||||
|
class="mt-2 block w-fit rounded-md bg-blue-600 px-2 py-1 text-center text-xs font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="library.admin.sources.documentationLink"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
class="inline-flex items-center gap-x-1"
|
||||||
|
>
|
||||||
|
<template #arrow>
|
||||||
|
<ArrowTopRightOnSquareIcon class="size-4" />
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</NuxtLink>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
@@ -269,6 +298,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DropLogo,
|
||||||
SourceOptionsFilesystem,
|
SourceOptionsFilesystem,
|
||||||
SourceOptionsFlatFilesystem,
|
SourceOptionsFlatFilesystem,
|
||||||
} from "#components";
|
} from "#components";
|
||||||
@@ -279,8 +309,11 @@ import {
|
|||||||
RadioGroupLabel,
|
RadioGroupLabel,
|
||||||
RadioGroupOption,
|
RadioGroupOption,
|
||||||
} from "@headlessui/vue";
|
} from "@headlessui/vue";
|
||||||
import { XCircleIcon } from "@heroicons/vue/20/solid";
|
import {
|
||||||
import { CheckIcon, DocumentIcon, XMarkIcon } from "@heroicons/vue/24/outline";
|
XCircleIcon,
|
||||||
|
ArrowTopRightOnSquareIcon,
|
||||||
|
} from "@heroicons/vue/20/solid";
|
||||||
|
import { BackwardIcon, CheckIcon, XMarkIcon } from "@heroicons/vue/24/outline";
|
||||||
import { FetchError } from "ofetch";
|
import { FetchError } from "ofetch";
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
import type { LibraryBackend } from "~/prisma/client/enums";
|
import type { LibraryBackend } from "~/prisma/client/enums";
|
||||||
@@ -324,17 +357,23 @@ const optionUIs: { [key in LibraryBackend]: Component } = {
|
|||||||
};
|
};
|
||||||
const optionsMetadata: {
|
const optionsMetadata: {
|
||||||
[key in LibraryBackend]: {
|
[key in LibraryBackend]: {
|
||||||
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
docsLink: string;
|
||||||
icon: Component;
|
icon: Component;
|
||||||
};
|
};
|
||||||
} = {
|
} = {
|
||||||
Filesystem: {
|
Filesystem: {
|
||||||
|
title: t("library.admin.sources.fsTitle"),
|
||||||
description: t("library.admin.sources.fsDesc"),
|
description: t("library.admin.sources.fsDesc"),
|
||||||
icon: DocumentIcon,
|
docsLink: "https://docs.droposs.org/docs/library#drop-style",
|
||||||
|
icon: DropLogo,
|
||||||
},
|
},
|
||||||
FlatFilesystem: {
|
FlatFilesystem: {
|
||||||
|
title: t("library.admin.sources.fsFlatTitle"),
|
||||||
description: t("library.admin.sources.fsFlatDesc"),
|
description: t("library.admin.sources.fsFlatDesc"),
|
||||||
icon: DocumentIcon,
|
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat",
|
||||||
|
icon: BackwardIcon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const optionsMetadataIter = Object.entries(optionsMetadata);
|
const optionsMetadataIter = Object.entries(optionsMetadata);
|
||||||
|
|||||||
@@ -44,19 +44,26 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ task.name }}
|
{{ task.name }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="h-2 rounded-full bg-zinc-950 overflow-hidden">
|
<div
|
||||||
|
class="bg-zinc-950 p-2 rounded-md h-[80vh] flex flex-col flex-col-reverse overflow-y-scroll gap-y-1"
|
||||||
|
>
|
||||||
|
<LogLine
|
||||||
|
v-for="(_, idx) in task.log"
|
||||||
|
:key="idx"
|
||||||
|
:log="parseTaskLog(task.log.at(-(idx + 1)))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="relative h-5 rounded-xl bg-zinc-950 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
:style="{ width: `${task.progress}%` }"
|
:style="{ width: `${task.progress}%` }"
|
||||||
class="transition-all bg-blue-600 h-full"
|
class="transition-all bg-blue-600 h-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
<span
|
||||||
|
class="absolute inset-0 flex items-center justify-center text-blue-200 text-sm font-bold font-display"
|
||||||
<div
|
>{{
|
||||||
class="relative bg-zinc-950/50 rounded-md p-2 text-zinc-100 h-[80vh] overflow-y-scroll"
|
$t("tasks.admin.progress", [Math.round(task.progress * 10) / 10])
|
||||||
>
|
}}</span
|
||||||
<pre v-for="(line, idx) in task.log" :key="idx">{{
|
>
|
||||||
formatLine(line)
|
|
||||||
}}</pre>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else role="status" class="w-full flex items-center justify-center">
|
<div v-else role="status" class="w-full flex items-center justify-center">
|
||||||
@@ -90,11 +97,6 @@ const taskId = route.params.id.toString();
|
|||||||
|
|
||||||
const task = useTask(taskId);
|
const task = useTask(taskId);
|
||||||
|
|
||||||
function formatLine(line: string): string {
|
|
||||||
const res = parseTaskLog(line);
|
|
||||||
return `[${res.timestamp}] ${res.message}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
});
|
});
|
||||||
|
|||||||
+50
-103
@@ -13,62 +13,7 @@
|
|||||||
:key="task.value?.id"
|
:key="task.value?.id"
|
||||||
class="col-span-1 divide-y divide-gray-200 rounded-lg bg-zinc-800 border border-zinc-700 shadow-sm"
|
class="col-span-1 divide-y divide-gray-200 rounded-lg bg-zinc-800 border border-zinc-700 shadow-sm"
|
||||||
>
|
>
|
||||||
<div
|
<TaskWidget :task="task.value" :active="true" />
|
||||||
v-if="task.value"
|
|
||||||
class="flex w-full items-center justify-between space-x-6 p-6"
|
|
||||||
>
|
|
||||||
<div class="flex-1 truncate">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div>
|
|
||||||
<CheckIcon
|
|
||||||
v-if="task.value.success"
|
|
||||||
class="size-4 text-green-600"
|
|
||||||
/>
|
|
||||||
<XMarkIcon
|
|
||||||
v-else-if="task.value.error"
|
|
||||||
class="size-4 text-red-600"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="size-2 bg-blue-600 rounded-full animate-pulse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h3 class="truncate text-sm font-medium text-zinc-100">
|
|
||||||
{{ task.value.name }}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-zinc-600 mt-0.5 font-mono">
|
|
||||||
{{ task.value.id }}
|
|
||||||
</p>
|
|
||||||
<div class="mt-1 w-full rounded-full overflow-hidden bg-zinc-900">
|
|
||||||
<div
|
|
||||||
:style="{ width: `${task.value.progress}%` }"
|
|
||||||
class="bg-blue-600 h-1.5 transition-all"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 truncate text-sm text-zinc-400">
|
|
||||||
{{ parseTaskLog(task.value.log.at(-1)).message }}
|
|
||||||
</p>
|
|
||||||
<NuxtLink
|
|
||||||
type="button"
|
|
||||||
:href="`/admin/task/${task.value.id}`"
|
|
||||||
class="mt-3 rounded-md text-xs font-medium text-zinc-100 hover:text-zinc-300 focus:outline-none focus:ring-2 focus:ring-zinc-100 focus:ring-offset-2"
|
|
||||||
>
|
|
||||||
<i18n-t
|
|
||||||
keypath="tasks.admin.viewTask"
|
|
||||||
tag="span"
|
|
||||||
scope="global"
|
|
||||||
>
|
|
||||||
<template #arrow>
|
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<!-- renders server side when we don't want to access the current tasks -->
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div
|
<div
|
||||||
@@ -89,51 +34,7 @@
|
|||||||
:key="task.id"
|
:key="task.id"
|
||||||
class="col-span-1 divide-y divide-gray-200 rounded-lg bg-zinc-800 border border-zinc-700 shadow-sm"
|
class="col-span-1 divide-y divide-gray-200 rounded-lg bg-zinc-800 border border-zinc-700 shadow-sm"
|
||||||
>
|
>
|
||||||
<div class="flex w-full items-center justify-between space-x-6 p-6">
|
<TaskWidget :task="task" />
|
||||||
<div class="flex-1 truncate">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div>
|
|
||||||
<CheckIcon
|
|
||||||
v-if="task.success"
|
|
||||||
class="size-4 text-green-600"
|
|
||||||
/>
|
|
||||||
<XMarkIcon
|
|
||||||
v-else-if="task.error"
|
|
||||||
class="size-4 text-red-600"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="size-2 bg-blue-600 rounded-full animate-pulse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h3 class="truncate text-sm font-medium text-zinc-100">
|
|
||||||
{{ task.name }}
|
|
||||||
</h3>
|
|
||||||
<RelativeTime class="text-zinc-500" :date="task.ended" />
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-zinc-600 mt-0.5 font-mono">
|
|
||||||
{{ task.id }}
|
|
||||||
</p>
|
|
||||||
<p class="mt-1 truncate text-sm text-zinc-400">
|
|
||||||
{{ parseTaskLog(task.log.at(-1)).message }}
|
|
||||||
</p>
|
|
||||||
<NuxtLink
|
|
||||||
type="button"
|
|
||||||
:href="`/admin/task/${task.id}`"
|
|
||||||
class="mt-3 rounded-md text-xs font-medium text-zinc-100 hover:text-zinc-300 focus:outline-none focus:ring-2 focus:ring-zinc-100 focus:ring-offset-2"
|
|
||||||
>
|
|
||||||
<i18n-t
|
|
||||||
keypath="tasks.admin.viewTask"
|
|
||||||
tag="span"
|
|
||||||
scope="global"
|
|
||||||
>
|
|
||||||
<template #arrow>
|
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,6 +58,21 @@
|
|||||||
<p class="mt-1 text-sm text-zinc-400">
|
<p class="mt-1 text-sm text-zinc-400">
|
||||||
{{ scheduledTasks[task].description }}
|
{{ scheduledTasks[task].description }}
|
||||||
</p>
|
</p>
|
||||||
|
<button
|
||||||
|
class="mt-3 rounded-md text-xs font-medium text-zinc-100 hover:text-zinc-300 focus:outline-none focus:ring-2 focus:ring-zinc-100 focus:ring-offset-2"
|
||||||
|
@click="() => startTask(task)"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="tasks.admin.execute"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
class="inline-flex items-center gap-x-1"
|
||||||
|
>
|
||||||
|
<template #arrow>
|
||||||
|
<PlayIcon class="size-4" aria-hidden="true" />
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -180,6 +96,21 @@
|
|||||||
<p class="mt-1 text-sm text-zinc-400">
|
<p class="mt-1 text-sm text-zinc-400">
|
||||||
{{ scheduledTasks[task].description }}
|
{{ scheduledTasks[task].description }}
|
||||||
</p>
|
</p>
|
||||||
|
<button
|
||||||
|
class="mt-3 rounded-md text-xs font-medium text-zinc-100 hover:text-zinc-300 focus:outline-none focus:ring-2 focus:ring-zinc-100 focus:ring-offset-2"
|
||||||
|
@click="() => startTask(task)"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="tasks.admin.execute"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
class="inline-flex items-center gap-x-1"
|
||||||
|
>
|
||||||
|
<template #arrow>
|
||||||
|
<PlayIcon class="size-4" aria-hidden="true" />
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -189,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { CheckIcon, XMarkIcon } from "@heroicons/vue/24/outline";
|
import { PlayIcon } from "@heroicons/vue/24/outline";
|
||||||
import type { TaskGroup } from "~/server/internal/tasks/group";
|
import type { TaskGroup } from "~/server/internal/tasks/group";
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
@@ -205,7 +136,9 @@ const { t } = useI18n();
|
|||||||
const { runningTasks, historicalTasks, dailyTasks, weeklyTasks } =
|
const { runningTasks, historicalTasks, dailyTasks, weeklyTasks } =
|
||||||
await $dropFetch("/api/v1/admin/task");
|
await $dropFetch("/api/v1/admin/task");
|
||||||
|
|
||||||
const liveRunningTasks = await Promise.all(runningTasks.map((e) => useTask(e)));
|
const liveRunningTasks = ref(
|
||||||
|
await Promise.all(runningTasks.map((e) => useTask(e))),
|
||||||
|
);
|
||||||
|
|
||||||
const scheduledTasks: {
|
const scheduledTasks: {
|
||||||
[key in TaskGroup]: { name: string; description: string };
|
[key in TaskGroup]: { name: string; description: string };
|
||||||
@@ -230,5 +163,19 @@ const scheduledTasks: {
|
|||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
},
|
},
|
||||||
|
debug: {
|
||||||
|
name: "Debug Task",
|
||||||
|
description: "Does debugging things.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function startTask(taskGroup: string) {
|
||||||
|
const task = await $dropFetch("/api/v1/admin/task", {
|
||||||
|
method: "POST",
|
||||||
|
body: { taskGroup },
|
||||||
|
failTitle: "Failed to start task",
|
||||||
|
});
|
||||||
|
const taskRef = await useTask(task.id);
|
||||||
|
liveRunningTasks.value.push(taskRef);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ export default defineEventHandler(async (h3) => {
|
|||||||
|
|
||||||
const unimportedGames = await libraryManager.fetchUnimportedGames();
|
const unimportedGames = await libraryManager.fetchUnimportedGames();
|
||||||
const games = await libraryManager.fetchGamesWithStatus();
|
const games = await libraryManager.fetchGamesWithStatus();
|
||||||
|
const libraries = await libraryManager.fetchLibraries();
|
||||||
|
|
||||||
// Fetch other library data here
|
// Fetch other library data here
|
||||||
|
|
||||||
return { unimportedGames, games };
|
return { unimportedGames, games, hasLibraries: libraries.length > 0 };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import aclManager from "~/server/internal/acls";
|
import aclManager from "~/server/internal/acls";
|
||||||
import prisma from "~/server/internal/db/database";
|
import prisma from "~/server/internal/db/database";
|
||||||
|
import type { TaskMessage } from "~/server/internal/tasks";
|
||||||
import taskHandler from "~/server/internal/tasks";
|
import taskHandler from "~/server/internal/tasks";
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
@@ -13,7 +14,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const runningTasks = (await taskHandler.runningTasks()).map((e) => e.id);
|
const runningTasks = (await taskHandler.runningTasks()).map((e) => e.id);
|
||||||
const historicalTasks = await prisma.task.findMany({
|
const historicalTasks = (await prisma.task.findMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
@@ -28,7 +29,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
ended: "desc",
|
ended: "desc",
|
||||||
},
|
},
|
||||||
take: 10,
|
take: 10,
|
||||||
});
|
})) as Array<TaskMessage>;
|
||||||
const dailyTasks = await taskHandler.dailyTasks();
|
const dailyTasks = await taskHandler.dailyTasks();
|
||||||
const weeklyTasks = await taskHandler.weeklyTasks();
|
const weeklyTasks = await taskHandler.weeklyTasks();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { type } from "arktype";
|
||||||
|
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||||
|
import aclManager from "~/server/internal/acls";
|
||||||
|
import taskHandler from "~/server/internal/tasks";
|
||||||
|
import type { TaskGroup } from "~/server/internal/tasks/group";
|
||||||
|
import { taskGroups } from "~/server/internal/tasks/group";
|
||||||
|
|
||||||
|
const StartTask = type({
|
||||||
|
taskGroup: type("string"),
|
||||||
|
}).configure(throwingArktype);
|
||||||
|
|
||||||
|
export default defineEventHandler(async (h3) => {
|
||||||
|
const allowed = await aclManager.allowSystemACL(h3, ["task:start"]);
|
||||||
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
|
const body = await readDropValidatedBody(h3, StartTask);
|
||||||
|
const taskGroup = body.taskGroup as TaskGroup;
|
||||||
|
if (!taskGroups[taskGroup])
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Invalid task group.",
|
||||||
|
});
|
||||||
|
|
||||||
|
const task = await taskHandler.runTaskGroupByName(taskGroup);
|
||||||
|
if (!task)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: "Could not start task.",
|
||||||
|
});
|
||||||
|
return { id: task };
|
||||||
|
});
|
||||||
@@ -116,7 +116,11 @@ class LibraryManager {
|
|||||||
async fetchGamesWithStatus() {
|
async fetchGamesWithStatus() {
|
||||||
const games = await prisma.game.findMany({
|
const games = await prisma.game.findMany({
|
||||||
include: {
|
include: {
|
||||||
versions: true,
|
versions: {
|
||||||
|
select: {
|
||||||
|
versionName: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
library: true,
|
library: true,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export const taskGroups = {
|
|||||||
"import:game": {
|
"import:game": {
|
||||||
concurrency: true,
|
concurrency: true,
|
||||||
},
|
},
|
||||||
|
debug: {
|
||||||
|
concurrency: true,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type TaskGroup = keyof typeof taskGroups;
|
export type TaskGroup = keyof typeof taskGroups;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class TaskHandler {
|
|||||||
"cleanup:invitations",
|
"cleanup:invitations",
|
||||||
"cleanup:sessions",
|
"cleanup:sessions",
|
||||||
"check:update",
|
"check:update",
|
||||||
|
"debug",
|
||||||
];
|
];
|
||||||
private weeklyScheduledTasks: TaskGroup[] = ["cleanup:objects"];
|
private weeklyScheduledTasks: TaskGroup[] = ["cleanup:objects"];
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ class TaskHandler {
|
|||||||
this.saveScheduledTask(cleanupSessions);
|
this.saveScheduledTask(cleanupSessions);
|
||||||
this.saveScheduledTask(checkUpdate);
|
this.saveScheduledTask(checkUpdate);
|
||||||
this.saveScheduledTask(cleanupObjects);
|
this.saveScheduledTask(cleanupObjects);
|
||||||
|
//this.saveScheduledTask(debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,6 +164,13 @@ class TaskHandler {
|
|||||||
// You can configure timestamp, level, etc. here
|
// You can configure timestamp, level, etc. here
|
||||||
timestamp: pino.stdTimeFunctions.isoTime,
|
timestamp: pino.stdTimeFunctions.isoTime,
|
||||||
base: null, // Remove pid/hostname if not needed
|
base: null, // Remove pid/hostname if not needed
|
||||||
|
formatters: {
|
||||||
|
level(label) {
|
||||||
|
return {
|
||||||
|
level: label,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
logStream,
|
logStream,
|
||||||
);
|
);
|
||||||
@@ -339,13 +348,15 @@ class TaskHandler {
|
|||||||
return this.weeklyScheduledTasks;
|
return this.weeklyScheduledTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
runTaskGroupByName(name: TaskGroup) {
|
async runTaskGroupByName(name: TaskGroup) {
|
||||||
const task = this.taskCreators.get(name);
|
const taskConstructor = this.taskCreators.get(name);
|
||||||
if (!task) {
|
if (!taskConstructor) {
|
||||||
logger.warn(`No task found for group ${name}`);
|
logger.warn(`No task found for group ${name}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.create(task());
|
const task = taskConstructor();
|
||||||
|
await this.create(task);
|
||||||
|
return task.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -444,7 +455,7 @@ export type TaskMessage = {
|
|||||||
name: string;
|
name: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
progress: number;
|
progress: number;
|
||||||
error: undefined | { title: string; description: string };
|
error: null | undefined | { title: string; description: string };
|
||||||
log: string[];
|
log: string[];
|
||||||
reset?: boolean;
|
reset?: boolean;
|
||||||
};
|
};
|
||||||
@@ -470,6 +481,7 @@ interface DropTask {
|
|||||||
export const TaskLog = type({
|
export const TaskLog = type({
|
||||||
timestamp: "string",
|
timestamp: "string",
|
||||||
message: "string",
|
message: "string",
|
||||||
|
level: "string",
|
||||||
});
|
});
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
@@ -499,8 +511,6 @@ export const TaskLog = type({
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
export function defineDropTask(buildTask: BuildTask): DropTask {
|
export function defineDropTask(buildTask: BuildTask): DropTask {
|
||||||
// TODO: only let one task with the same taskGroup run at the same time if specified
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taskGroup: buildTask.taskGroup,
|
taskGroup: buildTask.taskGroup,
|
||||||
build: () => ({
|
build: () => ({
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineDropTask } from "..";
|
||||||
|
|
||||||
|
export default defineDropTask({
|
||||||
|
buildId: () => `debug:${new Date().toISOString()}`,
|
||||||
|
name: "Debug Task",
|
||||||
|
acls: ["system:maintenance:read"],
|
||||||
|
taskGroup: "debug",
|
||||||
|
async run({ progress, logger }) {
|
||||||
|
const amount = 1000;
|
||||||
|
for (let i = 0; i < amount; i++) {
|
||||||
|
progress((i / amount) * 100);
|
||||||
|
logger.info(`dajksdkajd ${i}`);
|
||||||
|
logger.warn("warning");
|
||||||
|
logger.error("error\nmultiline and stuff\nwoah more lines");
|
||||||
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
+19
-1
@@ -1,13 +1,31 @@
|
|||||||
import type { TaskLog } from "~/server/internal/tasks";
|
import type { TaskLog } from "~/server/internal/tasks";
|
||||||
|
|
||||||
|
const labelNumberMap = {
|
||||||
|
100: "silent",
|
||||||
|
60: "fatal",
|
||||||
|
50: "error",
|
||||||
|
40: "warn",
|
||||||
|
30: "info",
|
||||||
|
20: "debug",
|
||||||
|
10: "trace",
|
||||||
|
0: "off",
|
||||||
|
};
|
||||||
|
|
||||||
export function parseTaskLog(
|
export function parseTaskLog(
|
||||||
logStr?: string | undefined,
|
logStr?: string | undefined,
|
||||||
): typeof TaskLog.infer {
|
): typeof TaskLog.infer {
|
||||||
if (!logStr) return { message: "", timestamp: "" };
|
if (!logStr) return { message: "", timestamp: "", level: "" };
|
||||||
const log = JSON.parse(logStr);
|
const log = JSON.parse(logStr);
|
||||||
|
|
||||||
|
if (typeof log.level === "number") {
|
||||||
|
log.level = labelNumberMap[
|
||||||
|
log.level as keyof typeof labelNumberMap
|
||||||
|
] as string;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: log.msg,
|
message: log.msg,
|
||||||
timestamp: log.time,
|
timestamp: log.time,
|
||||||
|
level: log.level,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,71 +342,71 @@
|
|||||||
jsonfile "^5.0.0"
|
jsonfile "^5.0.0"
|
||||||
universalify "^0.1.2"
|
universalify "^0.1.2"
|
||||||
|
|
||||||
"@drop-oss/droplet-darwin-arm64@2.3.0":
|
"@drop-oss/droplet-darwin-arm64@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-2.3.0.tgz#f4f0ded9c9f5b5cac25dd56f59817e1c13e865ab"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-arm64/-/droplet-darwin-arm64-2.3.1.tgz#ee1c32b4f180fd40bde57b4d428bdf51119cf6c8"
|
||||||
integrity sha512-5k1VwGZTFc61FvKyL4cvYxFYB7aCY5cWCo0Q7yTkkj+KR+ewH6ucylU8kDG7M+aBLvbC/zbntXUp4RtYZi4AZQ==
|
integrity sha512-s0qORIlGTyjnDL2W6Eljoz8yloBXVTpuyiEqnOCVTTtse3uLgqG+78wF2sVhiCKKCNuNwELv5icomJ2k4w2K5w==
|
||||||
|
|
||||||
"@drop-oss/droplet-darwin-universal@2.3.0":
|
"@drop-oss/droplet-darwin-universal@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-2.3.0.tgz#1d8659bc2869e5d30308622bcc6cb230030d738e"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-universal/-/droplet-darwin-universal-2.3.1.tgz#c6529184bd959d326c85ed97c7e8d2b07529abbb"
|
||||||
integrity sha512-4V/HMnNtmHgn156pTpa3mVTAwTmO9jqtZrDcVko7PdSotEbXiwBpTFzbgb4bPafbPmkSNoRh4G9d3BLQCh4mgw==
|
integrity sha512-L1fZjj2LQpLAesNtP3s0PzFPsJ77nVbyXbBeQGaoaq5kF0/npO4NYQ/7flkllGhIlJf/OQENWcARvfablM/G8g==
|
||||||
|
|
||||||
"@drop-oss/droplet-darwin-x64@2.3.0":
|
"@drop-oss/droplet-darwin-x64@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-2.3.0.tgz#c7ff5dae8ba520866b7cd49714625ada8fa0a7c2"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-darwin-x64/-/droplet-darwin-x64-2.3.1.tgz#711855d85821080ac11e80b81ec91df1eaa23405"
|
||||||
integrity sha512-PUcNjE09N7qEFsbssKxL8rjmCt9AUYPz1yK34d8N2W9DboS1KI+PShWdd/NOk4GYzTJQuJhMp8wNcUrljfqXmQ==
|
integrity sha512-4UvveQRCq10iEoVfArdCeBuQg51jbn5tkW68LXRoFWzicQz6MFhbowVmzz5RbjYcT5kYwI4c7jBZgeKBGOvc+g==
|
||||||
|
|
||||||
"@drop-oss/droplet-linux-arm64-gnu@2.3.0":
|
"@drop-oss/droplet-linux-arm64-gnu@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-2.3.0.tgz#8819b34c5ff8bd8182c5cd0c3f1784dc2afd9507"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-gnu/-/droplet-linux-arm64-gnu-2.3.1.tgz#f3a3a00239a9063bba1fbcb2209303b337cd862d"
|
||||||
integrity sha512-6VyOwYu9sMrCL82UZOvvjU9G/4wHdA8P6q3+EDIVdABg5jVEYZsxkrT0Kp/5h9Xs0mPFNB/su8ZwB9FRQ63o1w==
|
integrity sha512-URlpIZZDG/u2pwxM+PaWK240+Phr4RETzwcUQyERAPv22vTjl4l/s4sYa4o4fHX/eosgNLncO+jYgO0f9FS3kg==
|
||||||
|
|
||||||
"@drop-oss/droplet-linux-arm64-musl@2.3.0":
|
"@drop-oss/droplet-linux-arm64-musl@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-2.3.0.tgz#06601aa8af4bffeb26956ff79ed494265e313342"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-arm64-musl/-/droplet-linux-arm64-musl-2.3.1.tgz#8743132ad2fb050ef5f3cd5ddbe93b1b35ebda73"
|
||||||
integrity sha512-2BZreAg1XOBxr+iY2hFcX4x6bFC7AKXkIHa9130rmStH/HxnGq6K5H49eJd6ezzNMH/lQ7Sm7uJP2+sH8mjeCw==
|
integrity sha512-+m5IVmD4J979KggUSpyrE4PD4mjHAnvJ8EY2AOMSxmCu3JmONMIKHNmx1TA/k9PlesGKe6GjAs2WGlxwZRCZ2g==
|
||||||
|
|
||||||
"@drop-oss/droplet-linux-riscv64-gnu@2.3.0":
|
"@drop-oss/droplet-linux-riscv64-gnu@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-2.3.0.tgz#6d5629631aeeceadb292998e21b6e2b2cf839bdc"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-riscv64-gnu/-/droplet-linux-riscv64-gnu-2.3.1.tgz#d7f03d78f107a04f952c8958a024c27df6ad9818"
|
||||||
integrity sha512-E7i86Q8IU7rh2FVtXa0NxoGRhB7AZU+AWPumTAuDQS3xPg3sj+c3q/A7YI1Ay4lnvzR/fevP2p/7iSJUEDcchQ==
|
integrity sha512-AkSbSjt8nvcuxJYBeEvGGh6HaQSPO+OeLYUjc4pBZDACUOULHLWnZ5LdD/GUN97JcZOfq+BYfP50mz/1BvWoSA==
|
||||||
|
|
||||||
"@drop-oss/droplet-linux-x64-gnu@2.3.0":
|
"@drop-oss/droplet-linux-x64-gnu@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-2.3.0.tgz#a924aada38dbc54f6967b17435c10bf3b6e6ffb0"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-gnu/-/droplet-linux-x64-gnu-2.3.1.tgz#7deff7a3de0697de13685c81ec4e9c8e6186ed0a"
|
||||||
integrity sha512-eIHhhoSgpvMAc9tn/K0ldZRXvDe1Xyd9okSSqaclCEKjdVfWU8UMycUz1SzQH9YefiqEB4Qjd3y1iRgaEa8niA==
|
integrity sha512-jaswkLtTJ4U5GhyBn+86r7cEf8dnHQmcklxomEA8P/DWTHkab1g23XlgaSGHX/FaiRpKAxXGNLTPyY8lR6wePQ==
|
||||||
|
|
||||||
"@drop-oss/droplet-linux-x64-musl@2.3.0":
|
"@drop-oss/droplet-linux-x64-musl@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-2.3.0.tgz#4eb71112f7641e1fad3b53f5f8d1b98b9cb84bf0"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-linux-x64-musl/-/droplet-linux-x64-musl-2.3.1.tgz#31a2f5107817a59ff5a516cd1912261c85b2e0c7"
|
||||||
integrity sha512-0taR945NvK+xNBicSYriKDJgBxpcozzgcALDp/cX2UaYV9cb5PF/xw80DArCyUDvKOfRzeFALx4KRC2ghPr6tw==
|
integrity sha512-xn+W31FOnPVvkoxks104QusnHS1/J0V4Lwzegu3hPjPpZnc0YqFEo74pcxb2VfltgjMTtBOmzyKaApt9Snhd8Q==
|
||||||
|
|
||||||
"@drop-oss/droplet-win32-arm64-msvc@2.3.0":
|
"@drop-oss/droplet-win32-arm64-msvc@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-2.3.0.tgz#36568f87024eb48ce7e82d76ea83a2c6ec25a856"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-arm64-msvc/-/droplet-win32-arm64-msvc-2.3.1.tgz#4cf431b654b33c58df4cef256e84881b0556a046"
|
||||||
integrity sha512-5HkO98h/PboM+/wPulKVGFTklijlqht8w13iW1ipUcRFsOHmS1o8nejjLL7KEr2X8G4JwYOqBeX8tY3OhaU9bw==
|
integrity sha512-MwDz/4SfFSCEYzGCsptVD38amB18c2MSheJM+mV7Y8qjVDfjzAGrdX9923e8BYIm8eY8ypnOQBTy4/qae8+3uw==
|
||||||
|
|
||||||
"@drop-oss/droplet-win32-x64-msvc@2.3.0":
|
"@drop-oss/droplet-win32-x64-msvc@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-2.3.0.tgz#e794ea7cfdc0ea148707e4f3e60f2aa547328c03"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet-win32-x64-msvc/-/droplet-win32-x64-msvc-2.3.1.tgz#01b33c5241cca9aa026605cbb18027d82f57cc6f"
|
||||||
integrity sha512-6lNXOMyy9sPaO4wbklOIr2jbuvZHIVrd+dXu2UOI2YqFlHdxiDD1sZnqSZmlfCP58yeA+SpTfhxDHwUHJTFI/g==
|
integrity sha512-5FRfblFZBI9jpLNS0z+QSqYzxpDg3YSTP/2LvEqI2Z7ZYC02j4SKnW/+C4zzblNuR3e/igfaP0I9yBv4uIb6ew==
|
||||||
|
|
||||||
"@drop-oss/droplet@2.3.0":
|
"@drop-oss/droplet@2.3.1":
|
||||||
version "2.3.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-2.3.0.tgz#eb2891346cf7fadcc847d5dee37674fc1106d2fc"
|
resolved "https://registry.yarnpkg.com/@drop-oss/droplet/-/droplet-2.3.1.tgz#664e42bf0b205ffa06f5d5f48f0080012145d8c2"
|
||||||
integrity sha512-ffEoS3LYBfPm0++p7f7F/NkYH5PfauQzuj1gTz7qVWZOSP5VQWYhOc9BEg0fsCCzTB/mct0jwOsK92URmthpxA==
|
integrity sha512-oar7wvwMC1Evau4lXYcGerFcK/HqTi2Fn/4mcBldIF7B33NhsXhg4RncSNGS5J0WfwG4rkbI249nbs4aDeIrFg==
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@drop-oss/droplet-darwin-arm64" "2.3.0"
|
"@drop-oss/droplet-darwin-arm64" "2.3.1"
|
||||||
"@drop-oss/droplet-darwin-universal" "2.3.0"
|
"@drop-oss/droplet-darwin-universal" "2.3.1"
|
||||||
"@drop-oss/droplet-darwin-x64" "2.3.0"
|
"@drop-oss/droplet-darwin-x64" "2.3.1"
|
||||||
"@drop-oss/droplet-linux-arm64-gnu" "2.3.0"
|
"@drop-oss/droplet-linux-arm64-gnu" "2.3.1"
|
||||||
"@drop-oss/droplet-linux-arm64-musl" "2.3.0"
|
"@drop-oss/droplet-linux-arm64-musl" "2.3.1"
|
||||||
"@drop-oss/droplet-linux-riscv64-gnu" "2.3.0"
|
"@drop-oss/droplet-linux-riscv64-gnu" "2.3.1"
|
||||||
"@drop-oss/droplet-linux-x64-gnu" "2.3.0"
|
"@drop-oss/droplet-linux-x64-gnu" "2.3.1"
|
||||||
"@drop-oss/droplet-linux-x64-musl" "2.3.0"
|
"@drop-oss/droplet-linux-x64-musl" "2.3.1"
|
||||||
"@drop-oss/droplet-win32-arm64-msvc" "2.3.0"
|
"@drop-oss/droplet-win32-arm64-msvc" "2.3.1"
|
||||||
"@drop-oss/droplet-win32-x64-msvc" "2.3.0"
|
"@drop-oss/droplet-win32-x64-msvc" "2.3.1"
|
||||||
|
|
||||||
"@emnapi/core@^1.4.3":
|
"@emnapi/core@^1.4.3":
|
||||||
version "1.4.5"
|
version "1.4.5"
|
||||||
|
|||||||
Reference in New Issue
Block a user