mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-09 20:12:10 +10:00
Various bug fixes (#102)
* feat: set lang in html head * fix: add # in front of git ref * fix: remove unused vars from example env * fix: package name and license field * fix: enable sourcemap for client and server * fix: emojis not showing in prod this is extremely cursed, but it works * chore: refactor auth manager * feat: disable invitations if simple auth disabled * feat: add drop version to footer * feat: translate auth endpoints * chore: move oidc module * feat: add weekly tasks enabled object cleanup as weekly task * feat: add timestamp to task log msgs * feat: add guard to prevent invalid progress % * fix: add missing global scope to i18n components * feat: set base url for i18n * feat: switch task log to json format * ci: run ci on develop branch only * fix: UserWidget text not updating #109 * fix: EXTERNAL_URL being computed at build * feat: add basic language outlines for translation * feat: add more english dialects
This commit is contained in:
@ -1,8 +1,5 @@
|
|||||||
DATABASE_URL="postgres://drop:drop@127.0.0.1:5432/drop"
|
DATABASE_URL="postgres://drop:drop@127.0.0.1:5432/drop"
|
||||||
|
|
||||||
CLIENT_CERTIFICATES="./.data/ca"
|
|
||||||
|
|
||||||
FS_BACKEND_PATH="./.data/objects"
|
|
||||||
|
|
||||||
GIANT_BOMB_API_KEY=""
|
GIANT_BOMB_API_KEY=""
|
||||||
|
|
||||||
|
EXTERNAL_URL="localhost:3000"
|
||||||
|
|||||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -1,6 +1,12 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: [pull_request, push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
typecheck:
|
typecheck:
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -31,6 +31,7 @@
|
|||||||
],
|
],
|
||||||
"i18n-ally.extract.ignoredByFiles": {
|
"i18n-ally.extract.ignoredByFiles": {
|
||||||
"pages/admin/library/sources/index.vue": ["Filesystem"],
|
"pages/admin/library/sources/index.vue": ["Filesystem"],
|
||||||
"components/NewsArticleCreateButton.vue": ["[", "`", "Enter"]
|
"components/NewsArticleCreateButton.vue": ["[", "`", "Enter"],
|
||||||
|
"server/api/v1/auth/signin/simple.post.ts": ["boolean | undefined"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,7 @@ const props = defineProps<{
|
|||||||
emoji: string;
|
emoji: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emojiEl = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
const url = computed(() => {
|
const url = computed(() => {
|
||||||
return `/api/v1/emojis/${twemoji.convert.toCodePoint(props.emoji)}.svg`;
|
return `/twemoji/${twemoji.convert.toCodePoint(props.emoji)}.svg`;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -9,9 +9,10 @@
|
|||||||
class="grid w-full cursor-default grid-cols-1 rounded-md bg-zinc-900 py-1.5 pr-2 pl-3 text-left text-zinc-300 outline-1 -outline-offset-1 outline-zinc-700 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
|
class="grid w-full cursor-default grid-cols-1 rounded-md bg-zinc-900 py-1.5 pr-2 pl-3 text-left text-zinc-300 outline-1 -outline-offset-1 outline-zinc-700 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
|
||||||
>
|
>
|
||||||
<span class="col-start-1 row-start-1 flex items-center gap-3 pr-6">
|
<span class="col-start-1 row-start-1 flex items-center gap-3 pr-6">
|
||||||
<span alt="" class="-mt-0.5 shrink-0 rounded-full">{{
|
<EmojiText
|
||||||
localeToEmoji(wiredLocale)
|
:emoji="localeToEmoji(wiredLocale)"
|
||||||
}}</span>
|
class="-mt-0.5 shrink-0 max-w-6"
|
||||||
|
/>
|
||||||
<span class="block truncate">{{
|
<span class="block truncate">{{
|
||||||
currentLocaleInformation?.name ?? wiredLocale
|
currentLocaleInformation?.name ?? wiredLocale
|
||||||
}}</span>
|
}}</span>
|
||||||
@ -46,9 +47,10 @@
|
|||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="-mt-0.5 shrink-0 rounded-full">
|
<EmojiText
|
||||||
{{ localeToEmoji(listLocale.code) }}
|
:emoji="localeToEmoji(listLocale.code)"
|
||||||
</span>
|
class="-mt-0.5 shrink-0 max-w-6"
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
:class="[
|
:class="[
|
||||||
selected ? 'font-semibold' : 'font-normal',
|
selected ? 'font-semibold' : 'font-normal',
|
||||||
@ -106,21 +108,50 @@ import {
|
|||||||
} from "@headlessui/vue";
|
} from "@headlessui/vue";
|
||||||
import { ChevronUpDownIcon } from "@heroicons/vue/16/solid";
|
import { ChevronUpDownIcon } from "@heroicons/vue/16/solid";
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
|
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
|
||||||
|
import type { Locale } from "vue-i18n";
|
||||||
|
|
||||||
const { locales, locale, setLocale } = useI18n();
|
const { locales, locale: currLocale, setLocale } = useI18n();
|
||||||
|
|
||||||
|
function changeLocale(locale: Locale) {
|
||||||
|
setLocale(locale);
|
||||||
|
|
||||||
|
// dynamically update the HTML attributes for language and direction
|
||||||
|
// this is necessary for proper rendering of the page in the new language
|
||||||
|
useHead({
|
||||||
|
htmlAttrs: {
|
||||||
|
lang: locale,
|
||||||
|
dir: locales.value.find((l) => l.code === locale)?.dir || "ltr",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function localeToEmoji(local: string): string {
|
function localeToEmoji(local: string): string {
|
||||||
switch (local) {
|
switch (local) {
|
||||||
|
// Default locale
|
||||||
case "en":
|
case "en":
|
||||||
case "en-gb":
|
case "en-us":
|
||||||
case "en-ca":
|
|
||||||
case "en-au":
|
|
||||||
case "en-us": {
|
|
||||||
return "🇺🇸";
|
return "🇺🇸";
|
||||||
}
|
|
||||||
case "en-pirate": {
|
case "en-gb":
|
||||||
|
return "🇬🇧";
|
||||||
|
case "en-ca":
|
||||||
|
return "🇨🇦";
|
||||||
|
case "en-au":
|
||||||
|
return "🇦🇺";
|
||||||
|
case "en-pirate":
|
||||||
return "🏴☠️";
|
return "🏴☠️";
|
||||||
}
|
case "fr":
|
||||||
|
return "🇫🇷";
|
||||||
|
case "de":
|
||||||
|
return "🇩🇪";
|
||||||
|
case "es":
|
||||||
|
return "🇪🇸";
|
||||||
|
case "it":
|
||||||
|
return "🇮🇹";
|
||||||
|
case "zh":
|
||||||
|
return "🇨🇳";
|
||||||
|
case "zh-tw":
|
||||||
|
return "🇹🇼";
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return "❓";
|
return "❓";
|
||||||
@ -130,10 +161,10 @@ function localeToEmoji(local: string): string {
|
|||||||
|
|
||||||
const wiredLocale = computed({
|
const wiredLocale = computed({
|
||||||
get() {
|
get() {
|
||||||
return locale.value;
|
return currLocale.value;
|
||||||
},
|
},
|
||||||
set(v) {
|
set(v) {
|
||||||
setLocale(v);
|
changeLocale(v);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const currentLocaleInformation = computed(() =>
|
const currentLocaleInformation = computed(() =>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
<footer class="bg-zinc-950" aria-labelledby="footer-heading">
|
<footer class="bg-zinc-950" aria-labelledby="footer-heading">
|
||||||
<h2 id="footer-heading" class="sr-only">{{ $t("footer.footer") }}</h2>
|
<h2 id="footer-heading" class="sr-only">{{ $t("footer.footer") }}</h2>
|
||||||
<div class="mx-auto max-w-7xl px-6 py-16 sm:py-24 lg:px-8">
|
<div class="mx-auto max-w-7xl px-6 py-16 sm:py-24 lg:px-8">
|
||||||
|
<!-- Drop Info -->
|
||||||
<div class="xl:grid xl:grid-cols-3 xl:gap-8">
|
<div class="xl:grid xl:grid-cols-3 xl:gap-8">
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<DropWordmark class="h-10" />
|
<DropWordmark class="h-10" />
|
||||||
@ -24,6 +25,8 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Foot links -->
|
||||||
<div class="mt-16 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
|
<div class="mt-16 grid grid-cols-2 gap-8 xl:col-span-2 xl:mt-0">
|
||||||
<div class="md:grid md:grid-cols-2 md:gap-8">
|
<div class="md:grid md:grid-cols-2 md:gap-8">
|
||||||
<div>
|
<div>
|
||||||
@ -86,6 +89,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center xl:col-span-3 mt-8">
|
||||||
|
<p
|
||||||
|
class="text-xs text-zinc-700 hover:text-zinc-400 transition-colors duration-200 cursor-default select-none"
|
||||||
|
>
|
||||||
|
<i18n-t keypath="footer.version" tag="p" scope="global">
|
||||||
|
<template #version>
|
||||||
|
<span>{{ versionInfo.version }}</span>
|
||||||
|
</template>
|
||||||
|
<template #gitRef>
|
||||||
|
<span>{{ versionInfo.gitRef }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@ -96,6 +114,8 @@ import { IconsDiscordLogo, IconsGithubLogo } from "#components";
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const versionInfo = await $dropFetch("/api/v1");
|
||||||
|
|
||||||
const navigation = {
|
const navigation = {
|
||||||
games: [
|
games: [
|
||||||
{ name: t("store.recentlyAdded"), href: "#" },
|
{ name: t("store.recentlyAdded"), href: "#" },
|
||||||
|
|||||||
@ -85,20 +85,21 @@ import { useObject } from "~/composables/objects";
|
|||||||
import type { NavigationItem } from "~/composables/types";
|
import type { NavigationItem } from "~/composables/types";
|
||||||
|
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const navigation: NavigationItem[] = [
|
const navigation = computed<NavigationItem[]>(() =>
|
||||||
user.value?.admin
|
[
|
||||||
? {
|
user.value?.admin
|
||||||
label: t("userHeader.profile.admin"),
|
? {
|
||||||
route: "/admin",
|
label: $t("userHeader.profile.admin"),
|
||||||
prefix: "",
|
route: "/admin",
|
||||||
}
|
prefix: "",
|
||||||
: undefined,
|
}
|
||||||
{
|
: undefined,
|
||||||
label: t("userHeader.profile.settings"),
|
{
|
||||||
route: "/account",
|
label: $t("userHeader.profile.settings"),
|
||||||
prefix: "",
|
route: "/account",
|
||||||
},
|
prefix: "",
|
||||||
].filter((e) => e !== undefined);
|
},
|
||||||
|
].filter((e) => e !== undefined),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -21,7 +21,15 @@ export default defineI18nConfig(() => {
|
|||||||
// https://vue-i18n.intlify.dev/guide/essentials/datetime.html
|
// https://vue-i18n.intlify.dev/guide/essentials/datetime.html
|
||||||
datetimeFormats: {
|
datetimeFormats: {
|
||||||
"en-us": defaultDateTimeFormat,
|
"en-us": defaultDateTimeFormat,
|
||||||
|
"en-gb": defaultDateTimeFormat,
|
||||||
|
"en-au": defaultDateTimeFormat,
|
||||||
"en-pirate": defaultDateTimeFormat,
|
"en-pirate": defaultDateTimeFormat,
|
||||||
|
fr: defaultDateTimeFormat,
|
||||||
|
de: defaultDateTimeFormat,
|
||||||
|
it: defaultDateTimeFormat,
|
||||||
|
es: defaultDateTimeFormat,
|
||||||
|
zh: defaultDateTimeFormat,
|
||||||
|
"zh-tw": defaultDateTimeFormat,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
1
i18n/locales/de.json
Normal file
1
i18n/locales/de.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
i18n/locales/en_au.json
Normal file
1
i18n/locales/en_au.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
i18n/locales/en_gb.json
Normal file
1
i18n/locales/en_gb.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -107,6 +107,17 @@
|
|||||||
"listItemPlaceholder": "list item"
|
"listItemPlaceholder": "list item"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
|
"auth": {
|
||||||
|
"method": {
|
||||||
|
"signinDisabled": "Sign in method not enabled"
|
||||||
|
},
|
||||||
|
"invalidUserOrPass": "Invalid username or password.",
|
||||||
|
"disabled": "Invalid or disabled account. Please contact the server administrator.",
|
||||||
|
"invalidPassState": "Invalid password state. Please contact the server administrator.",
|
||||||
|
"inviteIdRequired": "id required in fetching invitation",
|
||||||
|
"invalidInvite": "Invalid or expired invitation",
|
||||||
|
"usernameTaken": "Username already taken."
|
||||||
|
},
|
||||||
"backHome": "{arrow} Back to home",
|
"backHome": "{arrow} Back to home",
|
||||||
"invalidBody": "Invalid request body: {0}",
|
"invalidBody": "Invalid request body: {0}",
|
||||||
"inviteRequired": "Invitation required to sign up.",
|
"inviteRequired": "Invitation required to sign up.",
|
||||||
@ -201,7 +212,8 @@
|
|||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"github": "GitHub"
|
"github": "GitHub"
|
||||||
},
|
},
|
||||||
"topSellers": "Top Sellers"
|
"topSellers": "Top Sellers",
|
||||||
|
"version": "Drop {version} {gitRef}"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"admin": {
|
"admin": {
|
||||||
@ -218,17 +230,14 @@
|
|||||||
"admin": {
|
"admin": {
|
||||||
"description": "Manage the users on your Drop instance, and configure your authentication methods.",
|
"description": "Manage the users on your Drop instance, and configure your authentication methods.",
|
||||||
"authLink": "Authentication {arrow}",
|
"authLink": "Authentication {arrow}",
|
||||||
|
|
||||||
"displayNameHeader": "Display Name",
|
"displayNameHeader": "Display Name",
|
||||||
"usernameHeader": "Username",
|
"usernameHeader": "Username",
|
||||||
"emailHeader": "Email",
|
"emailHeader": "Email",
|
||||||
"adminHeader": "Admin?",
|
"adminHeader": "Admin?",
|
||||||
"authoptionsHeader": "Auth Options",
|
"authoptionsHeader": "Auth Options",
|
||||||
"srEditLabel": "Edit",
|
"srEditLabel": "Edit",
|
||||||
|
|
||||||
"adminUserLabel": "Admin user",
|
"adminUserLabel": "Admin user",
|
||||||
"normalUserLabel": "Normal user",
|
"normalUserLabel": "Normal user",
|
||||||
|
|
||||||
"authentication": {
|
"authentication": {
|
||||||
"title": "Authentication",
|
"title": "Authentication",
|
||||||
"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.",
|
||||||
@ -237,47 +246,33 @@
|
|||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"srOpenOptions": "Open options",
|
"srOpenOptions": "Open options",
|
||||||
"configure": "Configure",
|
"configure": "Configure",
|
||||||
|
|
||||||
"simple": "Simple (username/password)",
|
"simple": "Simple (username/password)",
|
||||||
"oidc": "OpenID Connect"
|
"oidc": "OpenID Connect"
|
||||||
},
|
},
|
||||||
|
|
||||||
"simple": {
|
"simple": {
|
||||||
"title": "Simple authentication",
|
"title": "Simple authentication",
|
||||||
"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.",
|
||||||
|
|
||||||
"invitationTitle": "invitations",
|
"invitationTitle": "invitations",
|
||||||
"createInvitation": "Create invitation",
|
"createInvitation": "Create invitation",
|
||||||
|
|
||||||
"noUsernameEnforced": "No username enforced.",
|
"noUsernameEnforced": "No username enforced.",
|
||||||
"noEmailEnforced": "No email enforced.",
|
"noEmailEnforced": "No email enforced.",
|
||||||
|
|
||||||
"adminInvitation": "Admin invitation",
|
"adminInvitation": "Admin invitation",
|
||||||
"userInvitation": "User invitation",
|
"userInvitation": "User invitation",
|
||||||
|
|
||||||
"expires": "Expires: {expiry}",
|
"expires": "Expires: {expiry}",
|
||||||
"neverExpires": "Never expires.",
|
"neverExpires": "Never expires.",
|
||||||
|
|
||||||
"noInvitations": "No invitations.",
|
"noInvitations": "No invitations.",
|
||||||
|
|
||||||
"inviteTitle": "Invite user to Drop",
|
"inviteTitle": "Invite user to Drop",
|
||||||
"inviteDescription": "Drop will generate a URL that you can send to the person you want to invite. You can optionally specify a username or email for them to use.",
|
"inviteDescription": "Drop will generate a URL that you can send to the person you want to invite. You can optionally specify a username or email for them to use.",
|
||||||
|
|
||||||
"inviteUsernameLabel": "Username (optional)",
|
"inviteUsernameLabel": "Username (optional)",
|
||||||
"inviteUsernameFormat": "Must be 5 or more characters",
|
"inviteUsernameFormat": "Must be 5 or more characters",
|
||||||
"inviteUsernamePlaceholder": "myUsername",
|
"inviteUsernamePlaceholder": "myUsername",
|
||||||
|
|
||||||
"inviteEmailLabel": "Email address (optional)",
|
"inviteEmailLabel": "Email address (optional)",
|
||||||
"inviteEmailDescription": "Must be in the format user{'@'}example.com",
|
"inviteEmailDescription": "Must be in the format user{'@'}example.com",
|
||||||
"inviteEmailPlaceholder": "me{'@'}example.com",
|
"inviteEmailPlaceholder": "me{'@'}example.com",
|
||||||
|
|
||||||
"inviteAdminSwitchLabel": "Admin invitation",
|
"inviteAdminSwitchLabel": "Admin invitation",
|
||||||
"inviteAdminSwitchDescription": "Create this user as an administrator",
|
"inviteAdminSwitchDescription": "Create this user as an administrator",
|
||||||
|
|
||||||
"inviteExpiryLabel": "Expires",
|
"inviteExpiryLabel": "Expires",
|
||||||
|
|
||||||
"inviteButton": "Invite",
|
"inviteButton": "Invite",
|
||||||
|
|
||||||
"invite3Days": "3 days",
|
"invite3Days": "3 days",
|
||||||
"inviteWeek": "1 week",
|
"inviteWeek": "1 week",
|
||||||
"inviteMonth": "1 month",
|
"inviteMonth": "1 month",
|
||||||
@ -347,18 +342,14 @@
|
|||||||
"imageCarouselEmpty": "No images added to the carousel yet.",
|
"imageCarouselEmpty": "No images added to the carousel yet.",
|
||||||
"removeImageCarousel": "Remove image",
|
"removeImageCarousel": "Remove image",
|
||||||
"addCarouselNoImages": "No images to add.",
|
"addCarouselNoImages": "No images to add.",
|
||||||
|
|
||||||
"imageLibrary": "Image library",
|
"imageLibrary": "Image library",
|
||||||
"imageLibraryDescription": "Please note all images uploaded are accessible to all users through browser dev-tools.",
|
"imageLibraryDescription": "Please note all images uploaded are accessible to all users through browser dev-tools.",
|
||||||
"setBanner": "Set as banner",
|
"setBanner": "Set as banner",
|
||||||
"setCover": "Set as cover",
|
"setCover": "Set as cover",
|
||||||
"deleteImage": "Delete image",
|
"deleteImage": "Delete image",
|
||||||
|
|
||||||
"currentBanner": "banner",
|
"currentBanner": "banner",
|
||||||
"currentCover": "cover",
|
"currentCover": "cover",
|
||||||
|
|
||||||
"addDescriptionNoImages": "No images to add.",
|
"addDescriptionNoImages": "No images to add.",
|
||||||
|
|
||||||
"editGameName": "Game Name",
|
"editGameName": "Game Name",
|
||||||
"editGameDescription": "Game Description"
|
"editGameDescription": "Game Description"
|
||||||
},
|
},
|
||||||
@ -407,23 +398,19 @@
|
|||||||
"scheduled": {
|
"scheduled": {
|
||||||
"cleanupInvitationsName": "Clean up invitations",
|
"cleanupInvitationsName": "Clean up invitations",
|
||||||
"cleanupInvitationsDescription": "Cleans up expired invitations from the database to save space.",
|
"cleanupInvitationsDescription": "Cleans up expired invitations from the database to save space.",
|
||||||
|
|
||||||
"cleanupObjectsName": "Clean up objects",
|
"cleanupObjectsName": "Clean up objects",
|
||||||
"cleanupObjectsDescription": "Detects and deletes unreferenced and unused objects to save space.",
|
"cleanupObjectsDescription": "Detects and deletes unreferenced and unused objects to save space.",
|
||||||
|
|
||||||
"cleanupSessionsName": "Clean up sessions.",
|
"cleanupSessionsName": "Clean up sessions.",
|
||||||
"cleanupSessionsDescription": "Cleans up expired sessions to save space and ensure security.",
|
"cleanupSessionsDescription": "Cleans up expired sessions to save space and ensure security.",
|
||||||
|
|
||||||
"checkUpdateName": "Check update.",
|
"checkUpdateName": "Check update.",
|
||||||
"checkUpdateDescription": "Check if Drop has an update."
|
"checkUpdateDescription": "Check if Drop has an update."
|
||||||
},
|
},
|
||||||
|
|
||||||
"runningTasksTitle": "Running tasks",
|
"runningTasksTitle": "Running tasks",
|
||||||
"noTasksRunning": "No tasks currently running",
|
"noTasksRunning": "No tasks currently running",
|
||||||
"completedTasksTitle": "Completed tasks",
|
"completedTasksTitle": "Completed tasks",
|
||||||
"dailyScheduledTitle": "Daily scheduled tasks",
|
"dailyScheduledTitle": "Daily scheduled tasks",
|
||||||
|
"weeklyScheduledTitle": "Weekly scheduled tasks",
|
||||||
"viewTask": "View {arrow}",
|
"viewTask": "View {arrow}",
|
||||||
|
|
||||||
"back": "{arrow} Back to Tasks"
|
"back": "{arrow} Back to Tasks"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
1
i18n/locales/es.json
Normal file
1
i18n/locales/es.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
i18n/locales/fr.json
Normal file
1
i18n/locales/fr.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
i18n/locales/it.json
Normal file
1
i18n/locales/it.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
i18n/locales/zh.json
Normal file
1
i18n/locales/zh.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
i18n/locales/zh_tw.json
Normal file
1
i18n/locales/zh_tw.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -170,36 +170,36 @@ import { useCurrentNavigationIndex } from "~/composables/current-page-engine";
|
|||||||
import { ArrowLeftIcon } from "@heroicons/vue/16/solid";
|
import { ArrowLeftIcon } from "@heroicons/vue/16/solid";
|
||||||
import { XMarkIcon } from "@heroicons/vue/24/solid";
|
import { XMarkIcon } from "@heroicons/vue/24/solid";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const i18nHead = useLocaleHead();
|
||||||
|
|
||||||
const navigation: Array<NavigationItem & { icon: Component }> = [
|
const navigation: Array<NavigationItem & { icon: Component }> = [
|
||||||
{ label: t("home"), route: "/admin", prefix: "/admin", icon: HomeIcon },
|
{ label: $t("home"), route: "/admin", prefix: "/admin", icon: HomeIcon },
|
||||||
{
|
{
|
||||||
label: t("userHeader.links.library"),
|
label: $t("userHeader.links.library"),
|
||||||
route: "/admin/library",
|
route: "/admin/library",
|
||||||
prefix: "/admin/library",
|
prefix: "/admin/library",
|
||||||
icon: ServerStackIcon,
|
icon: ServerStackIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("header.admin.users"),
|
label: $t("header.admin.users"),
|
||||||
route: "/admin/users",
|
route: "/admin/users",
|
||||||
prefix: "/admin/users",
|
prefix: "/admin/users",
|
||||||
icon: UserGroupIcon,
|
icon: UserGroupIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("header.admin.tasks"),
|
label: $t("header.admin.tasks"),
|
||||||
route: "/admin/task",
|
route: "/admin/task",
|
||||||
prefix: "/admin/task",
|
prefix: "/admin/task",
|
||||||
icon: RectangleStackIcon,
|
icon: RectangleStackIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("settings"),
|
label: $t("settings"),
|
||||||
route: "/admin/settings",
|
route: "/admin/settings",
|
||||||
prefix: "/admin/settings",
|
prefix: "/admin/settings",
|
||||||
icon: Cog6ToothIcon,
|
icon: Cog6ToothIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("header.back"),
|
label: $t("header.back"),
|
||||||
route: "/store",
|
route: "/store",
|
||||||
prefix: ".",
|
prefix: ".",
|
||||||
icon: ArrowLeftIcon,
|
icon: ArrowLeftIcon,
|
||||||
@ -221,11 +221,12 @@ router.afterEach(() => {
|
|||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: "en",
|
lang: i18nHead.value.htmlAttrs.lang,
|
||||||
|
// @ts-expect-error head.value.htmlAttrs.dir is not typed as strictly as it should be
|
||||||
|
dir: i18nHead.value.htmlAttrs.dir,
|
||||||
},
|
},
|
||||||
link: [],
|
|
||||||
titleTemplate(title) {
|
titleTemplate(title) {
|
||||||
return title ? t("adminTitleTemplate", [title]) : t("adminTitle");
|
return title ? $t("adminTitleTemplate", [title]) : $t("adminTitle");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -13,15 +13,20 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const i18nHead = useLocaleHead();
|
||||||
const noWrapper = !!route.query.noWrapper;
|
const noWrapper = !!route.query.noWrapper;
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: "en",
|
lang: i18nHead.value.htmlAttrs.lang,
|
||||||
|
// @ts-expect-error head.value.htmlAttrs.dir is not typed as strictly as it should be
|
||||||
|
dir: i18nHead.value.htmlAttrs.dir,
|
||||||
},
|
},
|
||||||
link: [],
|
// // seo headers
|
||||||
|
// link: [...i18nHead.value.link],
|
||||||
|
// meta: [...i18nHead.value.meta],
|
||||||
titleTemplate(title) {
|
titleTemplate(title) {
|
||||||
return title ? t("titleTemplate", [title]) : t("title");
|
return title ? t("titleTemplate", [title]) : t("title");
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
|
import { cpSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import module from "module";
|
||||||
|
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||||
|
|
||||||
// get drop version
|
// get drop version
|
||||||
const dropVersion = process.env.BUILD_DROP_VERSION ?? "v0.3.0-alpha.1";
|
const dropVersion = process.env.BUILD_DROP_VERSION ?? "v0.3.0-alpha.1";
|
||||||
@ -12,6 +16,14 @@ const commitHash =
|
|||||||
|
|
||||||
console.log(`Building Drop ${dropVersion} #${commitHash}`);
|
console.log(`Building Drop ${dropVersion} #${commitHash}`);
|
||||||
|
|
||||||
|
const twemojiJson = module.findPackageJSON(
|
||||||
|
"@discordapp/twemoji",
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
|
if (!twemojiJson) {
|
||||||
|
throw new Error("Could not find @discordapp/twemoji package.");
|
||||||
|
}
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
extends: ["./drop-base"],
|
extends: ["./drop-base"],
|
||||||
@ -40,6 +52,11 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
css: ["~/assets/tailwindcss.css", "~/assets/core.scss"],
|
css: ["~/assets/tailwindcss.css", "~/assets/core.scss"],
|
||||||
|
|
||||||
|
sourcemap: {
|
||||||
|
server: true,
|
||||||
|
client: true,
|
||||||
|
},
|
||||||
|
|
||||||
experimental: {
|
experimental: {
|
||||||
buildCache: true,
|
buildCache: true,
|
||||||
viewTransition: true,
|
viewTransition: true,
|
||||||
@ -51,7 +68,31 @@ export default defineNuxtConfig({
|
|||||||
// },
|
// },
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
|
// only used in dev server, not build because nitro sucks
|
||||||
|
// see build hook below
|
||||||
|
viteStaticCopy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: "node_modules/@discordapp/twemoji/dist/svg/*",
|
||||||
|
dest: "twemoji",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
hooks: {
|
||||||
|
"nitro:build:public-assets": (nitro) => {
|
||||||
|
// this is only run during build, not dev server
|
||||||
|
// https://github.com/nuxt/nuxt/issues/18918#issuecomment-1925774964
|
||||||
|
// copy emojis to .output/public/twemoji
|
||||||
|
const targetDir = path.join(nitro.options.output.publicDir, "twemoji");
|
||||||
|
cpSync(path.join(path.dirname(twemojiJson), "dist", "svg"), targetDir, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
@ -130,6 +171,7 @@ export default defineNuxtConfig({
|
|||||||
strategy: "no_prefix",
|
strategy: "no_prefix",
|
||||||
experimental: {
|
experimental: {
|
||||||
localeDetector: "localeDetector.ts",
|
localeDetector: "localeDetector.ts",
|
||||||
|
autoImportTranslationFunctions: true,
|
||||||
},
|
},
|
||||||
detectBrowserLanguage: {
|
detectBrowserLanguage: {
|
||||||
useCookie: true,
|
useCookie: true,
|
||||||
@ -137,12 +179,61 @@ export default defineNuxtConfig({
|
|||||||
fallbackLocale: "en-us",
|
fallbackLocale: "en-us",
|
||||||
},
|
},
|
||||||
locales: [
|
locales: [
|
||||||
{ code: "en-us", name: "English", file: "en_us.json" },
|
{ code: "en-us", language: "en-us", name: "English", file: "en_us.json" },
|
||||||
|
{
|
||||||
|
code: "en-gb",
|
||||||
|
language: "en-gb",
|
||||||
|
name: "English (UK)",
|
||||||
|
file: "en_gb.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "en-au",
|
||||||
|
language: "en-au",
|
||||||
|
name: "English (Australia)",
|
||||||
|
file: "en_au.json",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
code: "en-pirate",
|
code: "en-pirate",
|
||||||
|
language: "en-pirate",
|
||||||
name: "English (Pirate)",
|
name: "English (Pirate)",
|
||||||
file: "en_pirate.json",
|
file: "en_pirate.json",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: "fr",
|
||||||
|
language: "fr",
|
||||||
|
name: "French",
|
||||||
|
file: "fr.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "de",
|
||||||
|
language: "de",
|
||||||
|
name: "German",
|
||||||
|
file: "de.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "it",
|
||||||
|
language: "it",
|
||||||
|
name: "Italian",
|
||||||
|
file: "it.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "es",
|
||||||
|
language: "es",
|
||||||
|
name: "Spanish",
|
||||||
|
file: "es.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "zh",
|
||||||
|
language: "zh",
|
||||||
|
name: "Chinese",
|
||||||
|
file: "zh.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "zh-tw",
|
||||||
|
language: "zh-tw",
|
||||||
|
name: "Chinese (Taiwan)",
|
||||||
|
file: "zh_tw.json",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "nuxt-app",
|
"name": "drop",
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
@ -45,6 +46,7 @@
|
|||||||
"stream-mime-type": "^2.0.0",
|
"stream-mime-type": "^2.0.0",
|
||||||
"turndown": "^7.2.0",
|
"turndown": "^7.2.0",
|
||||||
"unstorage": "^1.15.0",
|
"unstorage": "^1.15.0",
|
||||||
|
"vite-plugin-static-copy": "^3.0.0",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue-router": "latest",
|
"vue-router": "latest",
|
||||||
"vue3-carousel": "^0.15.0",
|
"vue3-carousel": "^0.15.0",
|
||||||
|
|||||||
@ -14,7 +14,11 @@
|
|||||||
to="/admin/library/sources"
|
to="/admin/library/sources"
|
||||||
class="block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-blue-500 hover:scale-105 hover:shadow-lg hover:shadow-blue-500/25 active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
class="block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm transition-all duration-200 hover:bg-blue-500 hover:scale-105 hover:shadow-lg hover:shadow-blue-500/25 active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
>
|
>
|
||||||
<i18n-t keypath="library.admin.sources.link" tag="span">
|
<i18n-t
|
||||||
|
keypath="library.admin.sources.link"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
>
|
||||||
<template #arrow>
|
<template #arrow>
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||||
</template>
|
</template>
|
||||||
@ -39,7 +43,11 @@
|
|||||||
href="/admin/library/import"
|
href="/admin/library/import"
|
||||||
class="whitespace-nowrap font-medium text-blue-400 hover:text-blue-500"
|
class="whitespace-nowrap font-medium text-blue-400 hover:text-blue-500"
|
||||||
>
|
>
|
||||||
<i18n-t keypath="library.admin.import.link" tag="span">
|
<i18n-t
|
||||||
|
keypath="library.admin.import.link"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
>
|
||||||
<template #arrow>
|
<template #arrow>
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||||
</template>
|
</template>
|
||||||
@ -140,7 +148,11 @@
|
|||||||
:href="`/admin/library/${game.id}/import`"
|
:href="`/admin/library/${game.id}/import`"
|
||||||
class="whitespace-nowrap font-medium text-blue-400 hover:text-blue-500"
|
class="whitespace-nowrap font-medium text-blue-400 hover:text-blue-500"
|
||||||
>
|
>
|
||||||
<i18n-t keypath="library.admin.import.link" tag="span">
|
<i18n-t
|
||||||
|
keypath="library.admin.import.link"
|
||||||
|
tag="span"
|
||||||
|
scope="global"
|
||||||
|
>
|
||||||
<template #arrow>
|
<template #arrow>
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -54,7 +54,9 @@
|
|||||||
<div
|
<div
|
||||||
class="relative bg-zinc-950/50 rounded-md p-2 text-zinc-100 h-[80vh] overflow-y-scroll"
|
class="relative bg-zinc-950/50 rounded-md p-2 text-zinc-100 h-[80vh] overflow-y-scroll"
|
||||||
>
|
>
|
||||||
<pre v-for="(line, idx) in task.log" :key="idx">{{ line }}</pre>
|
<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">
|
||||||
@ -88,6 +90,11 @@ 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",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -115,7 +115,7 @@
|
|||||||
{{ task.id }}
|
{{ task.id }}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 truncate text-sm text-zinc-400">
|
<p class="mt-1 truncate text-sm text-zinc-400">
|
||||||
{{ task.log.at(-1) }}
|
{{ parseTaskLog(task.log.at(-1) ?? "").message }}
|
||||||
</p>
|
</p>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
type="button"
|
type="button"
|
||||||
@ -151,11 +151,34 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<h3 class="text-sm font-medium text-zinc-100">
|
<h3 class="text-sm font-medium text-zinc-100">
|
||||||
{{ dailyScheduledTasks[task].name }}
|
{{ scheduledTasks[task].name }}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-sm text-zinc-400">
|
<p class="mt-1 text-sm text-zinc-400">
|
||||||
{{ dailyScheduledTasks[task].description }}
|
{{ scheduledTasks[task].description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h2 class="text-sm font-medium text-zinc-400 mt-8">
|
||||||
|
{{ $t("tasks.admin.weeklyScheduledTitle") }}
|
||||||
|
</h2>
|
||||||
|
<ul role="list" class="mt-4 grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<li
|
||||||
|
v-for="task in weeklyTasks"
|
||||||
|
:key="task"
|
||||||
|
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">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<h3 class="text-sm font-medium text-zinc-100">
|
||||||
|
{{ scheduledTasks[task].name }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 text-sm text-zinc-400">
|
||||||
|
{{ scheduledTasks[task].description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -179,12 +202,12 @@ definePageMeta({
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const { runningTasks, historicalTasks, dailyTasks } =
|
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 = await Promise.all(runningTasks.map((e) => useTask(e)));
|
||||||
|
|
||||||
const dailyScheduledTasks: {
|
const scheduledTasks: {
|
||||||
[key in TaskGroup]: { name: string; description: string };
|
[key in TaskGroup]: { name: string; description: string };
|
||||||
} = {
|
} = {
|
||||||
"cleanup:invitations": {
|
"cleanup:invitations": {
|
||||||
|
|||||||
@ -72,7 +72,7 @@
|
|||||||
{{ $t("store.recentlyReleased") }}
|
{{ $t("store.recentlyReleased") }}
|
||||||
</h1>
|
</h1>
|
||||||
<NuxtLink class="text-blue-600 font-semibold">
|
<NuxtLink class="text-blue-600 font-semibold">
|
||||||
<i18n-t keypath="store.exploreMore" tag="span">
|
<i18n-t keypath="store.exploreMore" tag="span" scope="global">
|
||||||
<template #arrow>
|
<template #arrow>
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||||
</template>
|
</template>
|
||||||
@ -89,7 +89,7 @@
|
|||||||
{{ $t("store.recentlyUpdated") }}
|
{{ $t("store.recentlyUpdated") }}
|
||||||
</h1>
|
</h1>
|
||||||
<NuxtLink class="text-blue-600 font-semibold">
|
<NuxtLink class="text-blue-600 font-semibold">
|
||||||
<i18n-t keypath="store.exploreMore" tag="span">
|
<i18n-t keypath="store.exploreMore" tag="span" scope="global">
|
||||||
<template #arrow>
|
<template #arrow>
|
||||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { AuthMec } from "~/prisma/client";
|
import { AuthMec } from "~/prisma/client";
|
||||||
import aclManager from "~/server/internal/acls";
|
import aclManager from "~/server/internal/acls";
|
||||||
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
|
import authManager from "~/server/internal/auth";
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
const allowed = await aclManager.allowSystemACL(h3, ["auth:read"]);
|
const allowed = await aclManager.allowSystemACL(h3, ["auth:read"]);
|
||||||
if (!allowed) throw createError({ statusCode: 403 });
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
|
const enabledAuthManagers = authManager.getAuthProviders();
|
||||||
|
|
||||||
const authData = {
|
const authData = {
|
||||||
[AuthMec.Simple]: enabledAuthManagers.Simple,
|
[AuthMec.Simple]: enabledAuthManagers.Simple,
|
||||||
[AuthMec.OpenID]:
|
[AuthMec.OpenID]:
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
take: 10,
|
take: 10,
|
||||||
});
|
});
|
||||||
const dailyTasks = await taskHandler.dailyTasks();
|
const dailyTasks = await taskHandler.dailyTasks();
|
||||||
|
const weeklyTasks = await taskHandler.weeklyTasks();
|
||||||
|
|
||||||
return { runningTasks, historicalTasks, dailyTasks };
|
return { runningTasks, historicalTasks, dailyTasks, weeklyTasks };
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
|
import authManager from "~/server/internal/auth";
|
||||||
|
|
||||||
export default defineEventHandler(() => {
|
export default defineEventHandler(() => {
|
||||||
const authManagers = Object.entries(enabledAuthManagers)
|
return authManager.getEnabledAuthProviders();
|
||||||
.filter((e) => !!e[1])
|
|
||||||
.map((e) => e[0]);
|
|
||||||
|
|
||||||
return authManagers;
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,12 +2,11 @@ import { AuthMec } from "~/prisma/client";
|
|||||||
import type { JsonArray } from "@prisma/client/runtime/library";
|
import type { JsonArray } from "@prisma/client/runtime/library";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import prisma from "~/server/internal/db/database";
|
import prisma from "~/server/internal/db/database";
|
||||||
import {
|
import sessionHandler from "~/server/internal/session";
|
||||||
|
import authManager, {
|
||||||
checkHashArgon2,
|
checkHashArgon2,
|
||||||
checkHashBcrypt,
|
checkHashBcrypt,
|
||||||
} from "~/server/internal/security/simple";
|
} from "~/server/internal/auth";
|
||||||
import sessionHandler from "~/server/internal/session";
|
|
||||||
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
|
|
||||||
|
|
||||||
const signinValidator = type({
|
const signinValidator = type({
|
||||||
username: "string",
|
username: "string",
|
||||||
@ -18,10 +17,12 @@ const signinValidator = type({
|
|||||||
export default defineEventHandler<{
|
export default defineEventHandler<{
|
||||||
body: typeof signinValidator.infer;
|
body: typeof signinValidator.infer;
|
||||||
}>(async (h3) => {
|
}>(async (h3) => {
|
||||||
if (!enabledAuthManagers.Simple)
|
const t = await useTranslation(h3);
|
||||||
|
|
||||||
|
if (!authManager.getAuthProviders().Simple)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 403,
|
statusCode: 403,
|
||||||
statusMessage: "Sign in method not enabled",
|
statusMessage: t("errors.auth.method.signinDisabled"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = signinValidator(await readBody(h3));
|
const body = signinValidator(await readBody(h3));
|
||||||
@ -55,14 +56,13 @@ export default defineEventHandler<{
|
|||||||
if (!authMek)
|
if (!authMek)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
statusMessage: "Invalid username or password.",
|
statusMessage: t("errors.auth.invalidUserOrPass"),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!authMek.user.enabled)
|
if (!authMek.user.enabled)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 403,
|
statusCode: 403,
|
||||||
statusMessage:
|
statusMessage: t("errors.auth.disabled"),
|
||||||
"Invalid or disabled account. Please contact the server administrator.",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// LEGACY bcrypt
|
// LEGACY bcrypt
|
||||||
@ -72,15 +72,14 @@ export default defineEventHandler<{
|
|||||||
|
|
||||||
if (!hash)
|
if (!hash)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 403,
|
statusCode: 500,
|
||||||
statusMessage:
|
statusMessage: t("errors.auth.invalidPassState"),
|
||||||
"Invalid password state. Please contact the server administrator.",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(await checkHashBcrypt(body.password, hash)))
|
if (!(await checkHashBcrypt(body.password, hash)))
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
statusMessage: "Invalid username or password.",
|
statusMessage: t("errors.auth.invalidUserOrPass"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: send user to forgot password screen or something to force them to change their password to new system
|
// TODO: send user to forgot password screen or something to force them to change their password to new system
|
||||||
@ -93,14 +92,13 @@ export default defineEventHandler<{
|
|||||||
if (!hash || typeof hash !== "string")
|
if (!hash || typeof hash !== "string")
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage:
|
statusMessage: t("errors.auth.invalidPassState"),
|
||||||
"Invalid password state. Please contact the server administrator.",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(await checkHashArgon2(body.password, hash)))
|
if (!(await checkHashArgon2(body.password, hash)))
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
statusMessage: "Invalid username or password.",
|
statusMessage: t("errors.auth.invalidUserOrPass"),
|
||||||
});
|
});
|
||||||
|
|
||||||
await sessionHandler.signin(h3, authMek.userId, body.rememberMe);
|
await sessionHandler.signin(h3, authMek.userId, body.rememberMe);
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
import prisma from "~/server/internal/db/database";
|
import prisma from "~/server/internal/db/database";
|
||||||
import taskHandler from "~/server/internal/tasks";
|
import taskHandler from "~/server/internal/tasks";
|
||||||
|
import authManager from "~/server/internal/auth";
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
|
const t = await useTranslation(h3);
|
||||||
|
|
||||||
|
if (!authManager.getAuthProviders().Simple)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: t("errors.auth.method.signinDisabled"),
|
||||||
|
});
|
||||||
|
|
||||||
const query = getQuery(h3);
|
const query = getQuery(h3);
|
||||||
const id = query.id?.toString();
|
const id = query.id?.toString();
|
||||||
if (!id)
|
if (!id)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: "id required in fetching invitation",
|
statusMessage: t("errors.auth.inviteIdRequired"),
|
||||||
});
|
});
|
||||||
taskHandler.runTaskGroupByName("cleanup:invitations");
|
taskHandler.runTaskGroupByName("cleanup:invitations");
|
||||||
|
|
||||||
@ -15,7 +24,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
if (!invitation)
|
if (!invitation)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
statusMessage: "Invalid or expired invitation",
|
statusMessage: t("errors.auth.invalidInvite"),
|
||||||
});
|
});
|
||||||
|
|
||||||
return invitation;
|
return invitation;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { AuthMec } from "~/prisma/client";
|
import { AuthMec } from "~/prisma/client";
|
||||||
import prisma from "~/server/internal/db/database";
|
import prisma from "~/server/internal/db/database";
|
||||||
import { createHashArgon2 } from "~/server/internal/security/simple";
|
import authManager, { createHashArgon2 } from "~/server/internal/auth";
|
||||||
import * as jdenticon from "jdenticon";
|
import * as jdenticon from "jdenticon";
|
||||||
import objectHandler from "~/server/internal/objects";
|
import objectHandler from "~/server/internal/objects";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
@ -18,13 +18,21 @@ export const CreateUserValidator = type({
|
|||||||
export default defineEventHandler<{
|
export default defineEventHandler<{
|
||||||
body: typeof CreateUserValidator.infer;
|
body: typeof CreateUserValidator.infer;
|
||||||
}>(async (h3) => {
|
}>(async (h3) => {
|
||||||
|
const t = await useTranslation(h3);
|
||||||
|
|
||||||
|
if (!authManager.getAuthProviders().Simple)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: t("errors.auth.method.signinDisabled"),
|
||||||
|
});
|
||||||
|
|
||||||
const user = await readValidatedBody(h3, CreateUserValidator);
|
const user = await readValidatedBody(h3, CreateUserValidator);
|
||||||
|
|
||||||
const invitationId = user.invitation;
|
const invitationId = user.invitation;
|
||||||
if (!invitationId)
|
if (!invitationId)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
statusMessage: "Invalid or expired invitation.",
|
statusMessage: t("errors.auth.invalidInvite"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const invitation = await prisma.invitation.findUnique({
|
const invitation = await prisma.invitation.findUnique({
|
||||||
@ -33,7 +41,7 @@ export default defineEventHandler<{
|
|||||||
if (!invitation)
|
if (!invitation)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
statusMessage: "Invalid or expired invitation.",
|
statusMessage: t("errors.auth.invalidInvite"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// reuse items from invite
|
// reuse items from invite
|
||||||
@ -46,7 +54,7 @@ export default defineEventHandler<{
|
|||||||
if (existing > 0)
|
if (existing > 0)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: "Username already taken.",
|
statusMessage: t("errors.auth.usernameTaken"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const userId = randomUUID();
|
const userId = randomUUID();
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
import path from "path";
|
|
||||||
import module from "module";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import sanitize from "sanitize-filename";
|
|
||||||
|
|
||||||
import aclManager from "~/server/internal/acls";
|
|
||||||
|
|
||||||
const twemojiJson = module.findPackageJSON(
|
|
||||||
"@discordapp/twemoji",
|
|
||||||
import.meta.url,
|
|
||||||
);
|
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
|
||||||
const userId = await aclManager.getUserIdACL(h3, ["object:read"]);
|
|
||||||
if (!userId)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 403,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!twemojiJson)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: "Failed to resolve emoji package",
|
|
||||||
});
|
|
||||||
|
|
||||||
const unsafeId = getRouterParam(h3, "id");
|
|
||||||
if (!unsafeId)
|
|
||||||
throw createError({ statusCode: 400, statusMessage: "Invalid ID" });
|
|
||||||
|
|
||||||
const svgPath = path.join(
|
|
||||||
path.dirname(twemojiJson),
|
|
||||||
"dist",
|
|
||||||
"svg",
|
|
||||||
sanitize(unsafeId),
|
|
||||||
);
|
|
||||||
|
|
||||||
setHeader(
|
|
||||||
h3,
|
|
||||||
"Cache-Control",
|
|
||||||
// 7 days
|
|
||||||
"public, max-age=604800, s-maxage=604800",
|
|
||||||
);
|
|
||||||
setHeader(h3, "Content-Type", "image/svg+xml");
|
|
||||||
return await fs.readFile(svgPath);
|
|
||||||
});
|
|
||||||
@ -4,6 +4,6 @@ export default defineEventHandler((_h3) => {
|
|||||||
return {
|
return {
|
||||||
appName: "Drop",
|
appName: "Drop",
|
||||||
version: systemConfig.getDropVersion(),
|
version: systemConfig.getDropVersion(),
|
||||||
ref: systemConfig.getGitRef(),
|
gitRef: `#${systemConfig.getGitRef()}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
62
server/internal/auth/index.ts
Normal file
62
server/internal/auth/index.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { AuthMec } from "~/prisma/client";
|
||||||
|
import { OIDCManager } from "./oidc";
|
||||||
|
|
||||||
|
class AuthManager {
|
||||||
|
private authProviders: {
|
||||||
|
[AuthMec.Simple]: boolean;
|
||||||
|
[AuthMec.OpenID]: OIDCManager | undefined;
|
||||||
|
} = {
|
||||||
|
[AuthMec.Simple]: false,
|
||||||
|
[AuthMec.OpenID]: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
private initFuncs: {
|
||||||
|
[K in keyof typeof this.authProviders]: () => Promise<unknown>;
|
||||||
|
} = {
|
||||||
|
[AuthMec.OpenID]: OIDCManager.prototype.create,
|
||||||
|
[AuthMec.Simple]: async () => {
|
||||||
|
const disabled = process.env.DISABLE_SIMPLE_AUTH as string | undefined;
|
||||||
|
return !disabled;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
console.log("AuthManager initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
for (const [key, init] of Object.entries(this.initFuncs)) {
|
||||||
|
try {
|
||||||
|
const object = await init();
|
||||||
|
if (!object) break;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(this.authProviders as any)[key] = object;
|
||||||
|
console.log(`enabled auth: ${key}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add every other auth mechanism here, and fall back to simple if none of them are enabled
|
||||||
|
if (!this.authProviders[AuthMec.OpenID]) {
|
||||||
|
this.authProviders[AuthMec.Simple] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAuthProviders() {
|
||||||
|
return this.authProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEnabledAuthProviders() {
|
||||||
|
const authManagers = Object.entries(this.authProviders)
|
||||||
|
.filter((e) => !!e[1])
|
||||||
|
.map((e) => e[0]);
|
||||||
|
|
||||||
|
return authManagers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const authManager = new AuthManager();
|
||||||
|
export default authManager;
|
||||||
|
|
||||||
|
export * from "./passwordHash";
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import prisma from "../db/database";
|
import prisma from "../../db/database";
|
||||||
import type { User } from "~/prisma/client";
|
import type { User } from "~/prisma/client";
|
||||||
import { AuthMec } from "~/prisma/client";
|
import { AuthMec } from "~/prisma/client";
|
||||||
import objectHandler from "../objects";
|
import objectHandler from "../../objects";
|
||||||
import type { Readable } from "stream";
|
import type { Readable } from "stream";
|
||||||
import * as jdenticon from "jdenticon";
|
import * as jdenticon from "jdenticon";
|
||||||
|
import { systemConfig } from "../../config/sys-conf";
|
||||||
|
|
||||||
interface OIDCWellKnown {
|
interface OIDCWellKnown {
|
||||||
authorization_endpoint: string;
|
authorization_endpoint: string;
|
||||||
@ -118,7 +119,7 @@ export class OIDCManager {
|
|||||||
|
|
||||||
const clientId = process.env.OIDC_CLIENT_ID as string | undefined;
|
const clientId = process.env.OIDC_CLIENT_ID as string | undefined;
|
||||||
const clientSecret = process.env.OIDC_CLIENT_SECRET as string | undefined;
|
const clientSecret = process.env.OIDC_CLIENT_SECRET as string | undefined;
|
||||||
const externalUrl = process.env.EXTERNAL_URL as string | undefined;
|
const externalUrl = systemConfig.getExternalUrl();
|
||||||
|
|
||||||
if (!clientId || !clientSecret)
|
if (!clientId || !clientSecret)
|
||||||
throw new Error("Missing client ID or secret for OIDC");
|
throw new Error("Missing client ID or secret for OIDC");
|
||||||
@ -2,6 +2,7 @@ class SystemConfig {
|
|||||||
private libraryFolder = process.env.LIBRARY ?? "./.data/library";
|
private libraryFolder = process.env.LIBRARY ?? "./.data/library";
|
||||||
private dataFolder = process.env.DATA ?? "./.data/data";
|
private dataFolder = process.env.DATA ?? "./.data/data";
|
||||||
|
|
||||||
|
private externalUrl = process.env.EXTERNAL_URL ?? "http://localhost:3000";
|
||||||
private dropVersion;
|
private dropVersion;
|
||||||
private gitRef;
|
private gitRef;
|
||||||
|
|
||||||
@ -33,6 +34,10 @@ class SystemConfig {
|
|||||||
shouldCheckForUpdates() {
|
shouldCheckForUpdates() {
|
||||||
return this.checkForUpdates;
|
return this.checkForUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExternalUrl() {
|
||||||
|
return this.externalUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const systemConfig = new SystemConfig();
|
export const systemConfig = new SystemConfig();
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import checkUpdate from "./registry/update";
|
|||||||
import cleanupObjects from "./registry/objects";
|
import cleanupObjects from "./registry/objects";
|
||||||
import { taskGroups, type TaskGroup } from "./group";
|
import { taskGroups, type TaskGroup } from "./group";
|
||||||
import prisma from "../db/database";
|
import prisma from "../db/database";
|
||||||
|
import { type } from "arktype";
|
||||||
|
|
||||||
// a task that has been run
|
// a task that has been run
|
||||||
type FinishedTask = {
|
type FinishedTask = {
|
||||||
@ -45,11 +46,12 @@ class TaskHandler {
|
|||||||
// list of all clients currently connected to tasks
|
// list of all clients currently connected to tasks
|
||||||
private clientRegistry = new Map<string, PeerImpl>();
|
private clientRegistry = new Map<string, PeerImpl>();
|
||||||
|
|
||||||
private scheduledTasks: TaskGroup[] = [
|
private dailyScheduledTasks: TaskGroup[] = [
|
||||||
"cleanup:invitations",
|
"cleanup:invitations",
|
||||||
"cleanup:sessions",
|
"cleanup:sessions",
|
||||||
"check:update",
|
"check:update",
|
||||||
];
|
];
|
||||||
|
private weeklyScheduledTasks: TaskGroup[] = ["cleanup:objects"];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// register the cleanup invitations task
|
// register the cleanup invitations task
|
||||||
@ -124,18 +126,22 @@ class TaskHandler {
|
|||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
const progress = (progress: number) => {
|
|
||||||
const taskEntry = this.taskPool.get(task.id);
|
|
||||||
if (!taskEntry) return;
|
|
||||||
taskEntry.progress = progress;
|
|
||||||
updateAllClients();
|
|
||||||
};
|
|
||||||
|
|
||||||
const log = (entry: string) => {
|
const log = (entry: string) => {
|
||||||
const taskEntry = this.taskPool.get(task.id);
|
const taskEntry = this.taskPool.get(task.id);
|
||||||
if (!taskEntry) return;
|
if (!taskEntry) return;
|
||||||
taskEntry.log.push(entry);
|
taskEntry.log.push(msgWithTimestamp(entry));
|
||||||
// console.log(`[Task ${task.taskGroup}]: ${entry}`);
|
updateAllClients();
|
||||||
|
};
|
||||||
|
|
||||||
|
const progress = (progress: number) => {
|
||||||
|
if (progress < 0 || progress > 100) {
|
||||||
|
console.error("Progress must be between 0 and 100", { progress });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const taskEntry = this.taskPool.get(task.id);
|
||||||
|
if (!taskEntry) return;
|
||||||
|
taskEntry.progress = progress;
|
||||||
|
// log(`Progress: ${progress}%`);
|
||||||
updateAllClients();
|
updateAllClients();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -288,7 +294,11 @@ class TaskHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dailyTasks() {
|
dailyTasks() {
|
||||||
return this.scheduledTasks;
|
return this.dailyScheduledTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
weeklyTasks() {
|
||||||
|
return this.weeklyScheduledTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
runTaskGroupByName(name: TaskGroup) {
|
runTaskGroupByName(name: TaskGroup) {
|
||||||
@ -304,7 +314,7 @@ class TaskHandler {
|
|||||||
* Runs all daily tasks that are scheduled to run once a day.
|
* Runs all daily tasks that are scheduled to run once a day.
|
||||||
*/
|
*/
|
||||||
async triggerDailyTasks() {
|
async triggerDailyTasks() {
|
||||||
for (const taskGroup of this.scheduledTasks) {
|
for (const taskGroup of this.dailyScheduledTasks) {
|
||||||
const mostRecent = await prisma.task.findFirst({
|
const mostRecent = await prisma.task.findFirst({
|
||||||
where: {
|
where: {
|
||||||
taskGroup,
|
taskGroup,
|
||||||
@ -324,6 +334,32 @@ class TaskHandler {
|
|||||||
}
|
}
|
||||||
await this.runTaskGroupByName(taskGroup);
|
await this.runTaskGroupByName(taskGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After running daily tasks, trigger weekly tasks as well
|
||||||
|
await this.triggerWeeklyTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async triggerWeeklyTasks() {
|
||||||
|
for (const taskGroup of this.weeklyScheduledTasks) {
|
||||||
|
const mostRecent = await prisma.task.findFirst({
|
||||||
|
where: {
|
||||||
|
taskGroup,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
ended: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (mostRecent) {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const lastRun = mostRecent.ended.getTime();
|
||||||
|
const difference = currentTime - lastRun;
|
||||||
|
if (difference < 1000 * 60 * 60 * 24 * 7) {
|
||||||
|
// If it's been less than one week
|
||||||
|
continue; // skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.runTaskGroupByName(taskGroup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,6 +419,37 @@ interface DropTask {
|
|||||||
build: () => Task;
|
build: () => Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TaskLog = type({
|
||||||
|
timestamp: "string",
|
||||||
|
message: "string",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a log message with a timestamp in the format YYYY-MM-DD HH:mm:ss.SSS UTC
|
||||||
|
* @param message
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function msgWithTimestamp(message: string): string {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const pad = (n: number, width = 2) => n.toString().padStart(width, "0");
|
||||||
|
|
||||||
|
const year = now.getUTCFullYear();
|
||||||
|
const month = pad(now.getUTCMonth() + 1);
|
||||||
|
const day = pad(now.getUTCDate());
|
||||||
|
|
||||||
|
const hours = pad(now.getUTCHours());
|
||||||
|
const minutes = pad(now.getUTCMinutes());
|
||||||
|
const seconds = pad(now.getUTCSeconds());
|
||||||
|
const milliseconds = pad(now.getUTCMilliseconds(), 3);
|
||||||
|
|
||||||
|
const log: typeof TaskLog.infer = {
|
||||||
|
timestamp: `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds} UTC`,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
return JSON.stringify(log);
|
||||||
|
}
|
||||||
|
|
||||||
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
|
// TODO: only let one task with the same taskGroup run at the same time if specified
|
||||||
|
|
||||||
|
|||||||
@ -1,39 +1,5 @@
|
|||||||
import { AuthMec } from "~/prisma/client";
|
import authManager from "~/server/internal/auth";
|
||||||
import { OIDCManager } from "../internal/oidc";
|
|
||||||
|
|
||||||
export const enabledAuthManagers: {
|
|
||||||
[AuthMec.Simple]: boolean;
|
|
||||||
[AuthMec.OpenID]: OIDCManager | undefined;
|
|
||||||
} = {
|
|
||||||
[AuthMec.Simple]: false,
|
|
||||||
[AuthMec.OpenID]: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const initFunctions: {
|
|
||||||
[K in keyof typeof enabledAuthManagers]: () => Promise<unknown>;
|
|
||||||
} = {
|
|
||||||
[AuthMec.OpenID]: OIDCManager.prototype.create,
|
|
||||||
[AuthMec.Simple]: async () => {
|
|
||||||
const disabled = process.env.DISABLE_SIMPLE_AUTH as string | undefined;
|
|
||||||
return !disabled;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineNitroPlugin(async () => {
|
export default defineNitroPlugin(async () => {
|
||||||
for (const [key, init] of Object.entries(initFunctions)) {
|
await authManager.init();
|
||||||
try {
|
|
||||||
const object = await init();
|
|
||||||
if (!object) break;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(enabledAuthManagers as any)[key] = object;
|
|
||||||
console.log(`enabled auth: ${key}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add every other auth mechanism here, and fall back to simple if none of them are enabled
|
|
||||||
if (!enabledAuthManagers[AuthMec.OpenID]) {
|
|
||||||
enabledAuthManagers[AuthMec.Simple] = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import sessionHandler from "~/server/internal/session";
|
import sessionHandler from "~/server/internal/session";
|
||||||
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
|
import authManager from "~/server/internal/auth";
|
||||||
|
|
||||||
defineRouteMeta({
|
defineRouteMeta({
|
||||||
openAPI: {
|
openAPI: {
|
||||||
@ -10,6 +10,7 @@ defineRouteMeta({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
|
const enabledAuthManagers = authManager.getAuthProviders();
|
||||||
if (!enabledAuthManagers.OpenID) return sendRedirect(h3, "/auth/signin");
|
if (!enabledAuthManagers.OpenID) return sendRedirect(h3, "/auth/signin");
|
||||||
|
|
||||||
const manager = enabledAuthManagers.OpenID;
|
const manager = enabledAuthManagers.OpenID;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { enabledAuthManagers } from "~/server/plugins/04.auth-init";
|
import authManager from "~/server/internal/auth";
|
||||||
|
|
||||||
defineRouteMeta({
|
defineRouteMeta({
|
||||||
openAPI: {
|
openAPI: {
|
||||||
@ -11,6 +11,7 @@ defineRouteMeta({
|
|||||||
export default defineEventHandler((h3) => {
|
export default defineEventHandler((h3) => {
|
||||||
const redirect = getQuery(h3).redirect?.toString();
|
const redirect = getQuery(h3).redirect?.toString();
|
||||||
|
|
||||||
|
const enabledAuthManagers = authManager.getAuthProviders();
|
||||||
if (!enabledAuthManagers.OpenID)
|
if (!enabledAuthManagers.OpenID)
|
||||||
return sendRedirect(
|
return sendRedirect(
|
||||||
h3,
|
h3,
|
||||||
|
|||||||
10
utils/parseTaskLog.ts
Normal file
10
utils/parseTaskLog.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { TaskLog } from "~/server/internal/tasks";
|
||||||
|
|
||||||
|
export function parseTaskLog(logStr: string): typeof TaskLog.infer {
|
||||||
|
const log = JSON.parse(logStr);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: log.message,
|
||||||
|
timestamp: log.timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
82
yarn.lock
82
yarn.lock
@ -2938,7 +2938,7 @@ ansis@^3.17.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7"
|
resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7"
|
||||||
integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==
|
integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==
|
||||||
|
|
||||||
anymatch@^3.1.3:
|
anymatch@^3.1.3, anymatch@~3.1.2:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||||
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
||||||
@ -3116,6 +3116,11 @@ bcryptjs@*, bcryptjs@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-3.0.2.tgz#caadcca1afefe372ed6e20f86db8e8546361c1ca"
|
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-3.0.2.tgz#caadcca1afefe372ed6e20f86db8e8546361c1ca"
|
||||||
integrity sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==
|
integrity sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==
|
||||||
|
|
||||||
|
binary-extensions@^2.0.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
|
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||||
|
|
||||||
bindings@^1.4.0:
|
bindings@^1.4.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
|
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
|
||||||
@ -3162,7 +3167,7 @@ brace-expansion@^2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^1.0.0"
|
balanced-match "^1.0.0"
|
||||||
|
|
||||||
braces@^3.0.3:
|
braces@^3.0.3, braces@~3.0.2:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||||
@ -3354,6 +3359,21 @@ cheerio@^1.0.0:
|
|||||||
undici "^6.19.5"
|
undici "^6.19.5"
|
||||||
whatwg-mimetype "^4.0.0"
|
whatwg-mimetype "^4.0.0"
|
||||||
|
|
||||||
|
chokidar@^3.5.3:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||||
|
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||||
|
dependencies:
|
||||||
|
anymatch "~3.1.2"
|
||||||
|
braces "~3.0.2"
|
||||||
|
glob-parent "~5.1.2"
|
||||||
|
is-binary-path "~2.1.0"
|
||||||
|
is-glob "~4.0.1"
|
||||||
|
normalize-path "~3.0.0"
|
||||||
|
readdirp "~3.6.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
chokidar@^4.0.0, chokidar@^4.0.1, chokidar@^4.0.3:
|
chokidar@^4.0.0, chokidar@^4.0.1, chokidar@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
|
||||||
@ -4793,6 +4813,15 @@ fs-constants@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||||
|
|
||||||
|
fs-extra@^11.3.0:
|
||||||
|
version "11.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
|
||||||
|
integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.2.0"
|
||||||
|
jsonfile "^6.0.1"
|
||||||
|
universalify "^2.0.0"
|
||||||
|
|
||||||
fs-extra@^8.0.1:
|
fs-extra@^8.0.1:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||||
@ -4920,7 +4949,7 @@ github-from-package@0.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
||||||
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
|
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
|
||||||
|
|
||||||
glob-parent@^5.1.2:
|
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||||
@ -5264,6 +5293,13 @@ is-arrayish@^0.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
|
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
|
||||||
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
|
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
|
||||||
|
|
||||||
|
is-binary-path@~2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
|
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||||
|
dependencies:
|
||||||
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
is-builtin-module@^3.1.0:
|
is-builtin-module@^3.1.0:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
|
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
|
||||||
@ -5305,7 +5341,7 @@ is-fullwidth-code-point@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
@ -5546,6 +5582,15 @@ jsonfile@^5.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
jsonfile@^6.0.1:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||||
|
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
||||||
|
dependencies:
|
||||||
|
universalify "^2.0.0"
|
||||||
|
optionalDependencies:
|
||||||
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
junk@^4.0.0:
|
junk@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/junk/-/junk-4.0.1.tgz#7ee31f876388c05177fe36529ee714b07b50fbed"
|
resolved "https://registry.yarnpkg.com/junk/-/junk-4.0.1.tgz#7ee31f876388c05177fe36529ee714b07b50fbed"
|
||||||
@ -6468,7 +6513,7 @@ normalize-path@^2.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
remove-trailing-separator "^1.0.1"
|
remove-trailing-separator "^1.0.1"
|
||||||
|
|
||||||
normalize-path@^3.0.0:
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
@ -6762,7 +6807,7 @@ p-locate@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^4.0.0"
|
p-limit "^4.0.0"
|
||||||
|
|
||||||
p-map@^7.0.0:
|
p-map@^7.0.0, p-map@^7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6"
|
resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6"
|
||||||
integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==
|
integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==
|
||||||
@ -6942,7 +6987,7 @@ picocolors@^1.0.0, picocolors@^1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.3.1:
|
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
@ -7453,6 +7498,13 @@ readdirp@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d"
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d"
|
||||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||||
|
|
||||||
|
readdirp@~3.6.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||||
|
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||||
|
dependencies:
|
||||||
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
||||||
@ -8486,6 +8538,11 @@ universalify@^0.1.0, universalify@^0.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
|
universalify@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||||
|
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||||
|
|
||||||
unixify@^1.0.0:
|
unixify@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090"
|
resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090"
|
||||||
@ -8730,6 +8787,17 @@ vite-plugin-inspect@^11.1.0:
|
|||||||
unplugin-utils "^0.2.4"
|
unplugin-utils "^0.2.4"
|
||||||
vite-dev-rpc "^1.0.7"
|
vite-dev-rpc "^1.0.7"
|
||||||
|
|
||||||
|
vite-plugin-static-copy@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite-plugin-static-copy/-/vite-plugin-static-copy-3.0.0.tgz#5d9bdf240ec25205280e48d67ab5a4c642517092"
|
||||||
|
integrity sha512-Uki9pPUQ4ZnoMEdIFabvoh9h6Bh9Q1m3iF7BrZvoiF30reREpJh2gZb4jOnW1/uYFzyRiLCmFSkM+8hwiq1vWQ==
|
||||||
|
dependencies:
|
||||||
|
chokidar "^3.5.3"
|
||||||
|
fs-extra "^11.3.0"
|
||||||
|
p-map "^7.0.3"
|
||||||
|
picocolors "^1.1.1"
|
||||||
|
tinyglobby "^0.2.13"
|
||||||
|
|
||||||
vite-plugin-vue-tracer@^0.1.4:
|
vite-plugin-vue-tracer@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/vite-plugin-vue-tracer/-/vite-plugin-vue-tracer-0.1.4.tgz#4e6c36a8f59c1b1b1fd9bceffa5d237a7687060b"
|
resolved "https://registry.yarnpkg.com/vite-plugin-vue-tracer/-/vite-plugin-vue-tracer-0.1.4.tgz#4e6c36a8f59c1b1b1fd9bceffa5d237a7687060b"
|
||||||
|
|||||||
Reference in New Issue
Block a user