Compare commits

...

9 Commits

Author SHA1 Message Date
Prospector
daab8d9235
Merge branch 'main' into cal/dev-46-servers-scheduling-frontend 2025-06-19 08:03:53 -07:00
Calum H.
3203681e3b fix: lint issues 2025-06-15 01:47:12 +01:00
Calum H.
e5cec38ac5 feat: move to modal 2025-06-15 01:22:03 +01:00
Calum H.
3961876cb4 feat: add seconds to cron 2025-06-14 19:33:04 +01:00
Calum H.
7eee57eca3 feat: start work on edit/create page 2025-06-14 19:33:04 +01:00
Calum H.
6482d5b465 refactor: yeet modal, start work on subpage for editing 2025-06-14 19:33:04 +01:00
Calum H.
c48a229900 feat: start on modal 2025-06-14 19:33:04 +01:00
Calum H.
39a37096dd feat: start on adding server module 2025-06-14 19:33:04 +01:00
Calum H.
ab2b41a74d feat: scheduling 2025-06-14 19:32:48 +01:00
24 changed files with 1788 additions and 177 deletions

View File

@ -41,10 +41,12 @@
"@modrinth/ui": "workspace:*",
"@modrinth/utils": "workspace:*",
"@pinia/nuxt": "^0.5.1",
"@types/three": "^0.172.0",
"@vintl/vintl": "^4.4.1",
"@vueuse/core": "^11.1.0",
"ace-builds": "^1.36.2",
"ansi-to-html": "^0.7.2",
"cronstrue": "^2.61.0",
"dayjs": "^1.11.7",
"dompurify": "^3.1.7",
"floating-vue": "^5.2.2",
@ -59,7 +61,6 @@
"qrcode.vue": "^3.4.0",
"semver": "^7.5.4",
"three": "^0.172.0",
"@types/three": "^0.172.0",
"vue-multiselect": "3.0.0-alpha.2",
"vue-typed-virtual-list": "^1.0.10",
"vue3-ace-editor": "^2.2.4",

View File

@ -6,7 +6,7 @@
<NuxtLink
:to="link.href"
class="flex items-center gap-2 rounded-xl p-2 hover:bg-button-bg"
:class="{ 'bg-button-bg text-contrast': route.path === link.href }"
:class="{ 'bg-button-bg text-contrast': isLinkActive(link) }"
>
<div class="flex items-center gap-2 font-bold">
<component :is="link.icon" class="size-6" />
@ -39,13 +39,33 @@ import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const emit = defineEmits(["reinstall"]);
defineProps<{
navLinks: { label: string; href: string; icon: Component; external?: boolean }[];
const props = defineProps<{
navLinks: {
label: string;
href: string;
icon: Component;
external?: boolean;
matches?: RegExp[];
}[];
route: RouteLocationNormalized;
server: ModrinthServer;
backupInProgress?: BackupInProgressReason;
}>();
const isLinkActive = (link: (typeof props.navLinks)[0]) => {
if (props.route.path === link.href) {
return true;
}
if (link.matches && link.matches.length > 0) {
return link.matches.some((regex: RegExp) => {
return regex.test(props.route.path);
});
}
return false;
};
const onReinstall = (...args: any[]) => {
emit("reinstall", ...args);
};

View File

@ -0,0 +1,531 @@
<template>
<NewModal
ref="modal"
:header="isNew ? 'New scheduled task' : 'Editing scheduled task'"
@hide="onModalHide"
>
<div class="modal-content flex flex-col gap-2">
<div v-if="error" class="text-sm text-brand-red">
{{ error }}
</div>
<div class="flex max-w-md flex-col gap-2">
<label for="title">
<span class="text-lg font-bold text-contrast">
Title <span class="text-brand-red">*</span>
</span>
</label>
<input
id="title"
v-model="task.title"
type="text"
maxlength="64"
placeholder="Enter task title..."
autocomplete="off"
class="input input-bordered"
required
/>
</div>
<div class="flex flex-col gap-2">
<label><span class="text-lg font-bold text-contrast">Schedule Type</span></label>
<RadioButtons v-model="scheduleType" :items="['daily', 'custom']" class="mb-2">
<template #default="{ item }">
<span class="flex items-center gap-2">
<ClockIcon v-if="item === 'daily'" class="size-5" />
<CodeIcon v-else class="size-5" />
<span>{{
item === "daily" ? "Every X day(s) at specific time" : "Custom cron expression"
}}</span>
</span>
</template>
</RadioButtons>
</div>
<div class="card flex h-[140px] items-center overflow-hidden rounded-lg !bg-bg">
<Transition
name="schedule-content"
mode="out-in"
enter-active-class="transition-all duration-300 ease-in-out"
leave-active-class="transition-all duration-300 ease-in-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="scheduleType === 'daily'" key="daily" class="flex w-full flex-col gap-2">
<div class="flex w-full gap-8">
<div class="flex flex-col gap-2">
<label for="dayInterval" class="text-md font-medium">Every X day(s)</label>
<div class="flex items-center gap-2">
<input
id="dayInterval"
v-model="dayInterval"
type="text"
inputmode="numeric"
maxlength="3"
class="input input-bordered w-20"
placeholder="1"
@input="onDayIntervalInput"
/>
<span class="text-muted-foreground text-sm">day(s)</span>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="text-md font-medium">At time</label>
<TimePicker v-model="selectedTime" use-utc-values placeholder="Select time" />
</div>
</div>
<div class="text-xs">
{{ humanReadableDescription }}
</div>
</div>
<div v-else key="custom" class="flex w-full flex-col gap-2">
<label for="customCron" class="text-md font-medium"
>Cron Expression
<nuxt-link
v-tooltip="
`Click to read more about what cron expressions are, and how to create them.`
"
class="align-middle text-link"
target="_blank"
to="https://www.geeksforgeeks.org/writing-cron-expressions-for-scheduling-tasks/"
><UnknownIcon class="size-4" />
</nuxt-link>
</label>
<input
id="customCron"
v-model="customCron"
type="text"
class="input input-bordered font-mono"
placeholder="0 0 9 * * *"
/>
<div v-if="!isValidCron" class="text-xs text-brand-red">
Invalid cron format. Please use 6 space-separated values (e.g.,
<code>second minute hour day month day-of-week</code>).
</div>
<div v-else class="text-xs">
{{ humanReadableDescription }}
</div>
</div>
</Transition>
</div>
<div class="flex max-w-md flex-col gap-2">
<label for="action_kind"><span class="text-lg font-bold text-contrast">Action</span></label>
<RadioButtons v-model="task.action_kind" :items="actionKinds" class="mb-2">
<template #default="{ item }">
<span>{{ item === "restart" ? "Restart server" : "Run game command" }}</span>
</template>
</RadioButtons>
</div>
<Transition
name="command-section"
enter-active-class="transition-all duration-300 ease-in-out"
leave-active-class="transition-all duration-300 ease-in-out"
enter-from-class="opacity-0 max-h-0 overflow-hidden"
enter-to-class="opacity-100 max-h-96 overflow-visible"
leave-from-class="opacity-100 max-h-96 overflow-visible"
leave-to-class="opacity-0 max-h-0 overflow-hidden"
>
<div v-if="task.action_kind === 'game-command'" class="flex max-w-lg flex-col gap-4">
<label for="command" class="flex flex-col gap-2"
><span class="text-lg font-bold text-contrast">
Command <span class="text-brand-red">*</span>
</span>
<span
>The command that will be ran when the task is executed. If the command is invalid
nothing will happen.</span
></label
>
<div class="max-w-md">
<input
id="command"
v-model="commandValue"
type="text"
maxlength="256"
placeholder="Enter command to run..."
class="input input-bordered"
required
/>
</div>
</div>
</Transition>
<div class="flex max-w-lg flex-col gap-4">
<label for="warn_msg" class="flex flex-col gap-2"
><span class="text-lg font-bold text-contrast">
Warning Command
<span
v-if="task.warn_intervals && task.warn_intervals.length > 0"
class="text-brand-red"
>*</span
>
</span>
<span
>You can use <code>{}</code> to insert the time until the task is executed. If the
command is invalid nothing will happen.</span
></label
>
<div class="max-w-md">
<input
id="warn_msg"
v-model="task.warn_msg"
type="text"
maxlength="128"
:placeholder="`/tellraw @a \u0022Restarting in {}!\u0022`"
class="input input-bordered max-w-md"
/>
</div>
</div>
<div class="flex max-w-lg flex-col gap-4">
<label for="warn_msg" class="flex flex-col gap-2"
><span class="text-lg font-bold text-contrast">Warning Intervals</span>
<span
>When, at the time before the scheduled task is executed, should the warning command be
executed?</span
></label
>
<div class="flex flex-row flex-wrap gap-4">
<Chips
v-model="task.warn_intervals"
:items="[5, 15, 30, 60, 120, 300, 600]"
multi
:never-empty="false"
:format-label="formatWarningIntervalLabel"
/>
</div>
</div>
<div class="mt-2 flex max-w-md gap-2">
<ButtonStyled color="brand">
<button :disabled="!canSave" @click="saveTask">
<SaveIcon />
Save
</button>
</ButtonStyled>
<ButtonStyled>
<button :disabled="loading" @click="closeModal">
<XIcon />
Cancel
</button>
</ButtonStyled>
</div>
</div>
</NewModal>
</template>
<script setup lang="ts">
import type { Schedule, ServerSchedule, ActionKind } from "@modrinth/utils";
import { ref, computed, watch } from "vue";
import { RadioButtons, TimePicker, Chips, ButtonStyled, NewModal } from "@modrinth/ui";
import { ClockIcon, CodeIcon, SaveIcon, XIcon, UnknownIcon } from "@modrinth/assets";
import { toString as cronToString } from "cronstrue";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const props = defineProps<{ server: ModrinthServer }>();
const modal = ref<InstanceType<typeof NewModal>>();
const loading = ref(false);
const error = ref<string | null>(null);
const task = ref<Partial<ServerSchedule | Schedule>>({});
const isNew = ref(true);
const originalTaskId = ref<number | null>(null);
const scheduleType = ref<"daily" | "custom">("daily");
const dayInterval = ref("1");
const selectedTime = ref({ hour: "9", minute: "0" });
const customCron = ref("0 0 9 * * *");
const actionKinds: ActionKind[] = ["restart", "game-command"];
const commandValue = computed({
get() {
return task.value.options && "command" in task.value.options ? task.value.options.command : "";
},
set(val: string) {
if (task.value.options && "command" in task.value.options) {
task.value.options.command = val;
} else if (task.value.action_kind === "game-command") {
task.value.options = { command: val };
}
},
});
const cronString = computed(() => {
if (scheduleType.value === "custom") {
return customCron.value.trim();
}
const minute = selectedTime.value.minute === "" ? "0" : selectedTime.value.minute;
const hour = selectedTime.value.hour === "" ? "0" : selectedTime.value.hour;
const days = dayInterval.value === "" || Number(dayInterval.value) < 1 ? "1" : dayInterval.value;
if (days === "1") {
return `0 ${minute} ${hour} * * *`;
} else {
return `0 ${minute} ${hour} */${days} * *`;
}
});
const CRON_REGEX =
/^\s*([0-9*/,-]+)\s+([0-9*/,-]+)\s+([0-9*/,-]+)\s+([0-9*/,-]+)\s+([0-9*/,-]+)\s+([0-9*/,-]+)\s*$/;
const isValidCron = computed(() => {
const cronToTest = scheduleType.value === "custom" ? customCron.value.trim() : cronString.value;
if (!CRON_REGEX.test(cronToTest)) {
return false;
}
if (scheduleType.value === "custom") {
try {
const parts = cronToTest.split(/\s+/);
if (parts.length === 6) {
cronToString(parts.slice(1).join(" "));
}
return true;
} catch {
return false;
}
}
return true;
});
const isValidGameCommand = computed(() => {
if (task.value.action_kind === "game-command") {
return commandValue.value && commandValue.value.trim().length > 0;
}
return true;
});
const isValidWarningCommand = computed(() => {
if (task.value.warn_intervals && task.value.warn_intervals.length > 0) {
return task.value.warn_msg && task.value.warn_msg.trim().length > 0;
}
return true;
});
const canSave = computed(() => {
return (
!loading.value &&
task.value.title &&
task.value.title.trim().length > 0 &&
isValidCron.value &&
isValidGameCommand.value &&
isValidWarningCommand.value
);
});
const humanReadableDescription = computed<string | undefined>(() => {
if (!isValidCron.value && scheduleType.value === "custom") return undefined;
if (scheduleType.value === "custom") {
try {
const parts = customCron.value.trim().split(/\s+/);
if (parts.length === 6) {
return cronToString(parts.slice(1).join(" "));
}
return "Invalid cron expression";
} catch {
return "Invalid cron expression";
}
}
const minute = selectedTime.value.minute === "" ? "0" : selectedTime.value.minute;
const hour = selectedTime.value.hour === "" ? "0" : selectedTime.value.hour;
const daysNum = Number(dayInterval.value || "1");
const timeStr = `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`;
if (daysNum <= 1) {
return `Every day at ${timeStr}`;
} else {
return `Every ${daysNum} days at ${timeStr}`;
}
});
function formatWarningIntervalLabel(seconds: number): string {
if (seconds < 60) {
return `${seconds}s`;
} else {
const minutes = Math.floor(seconds / 60);
return `${minutes}m`;
}
}
function onDayIntervalInput(event: Event) {
const input = event.target as HTMLInputElement;
let value = input.value.replace(/\D/g, "");
if (value === "") {
dayInterval.value = "";
return;
}
if (value.length > 1 && value.startsWith("0")) {
value = value.replace(/^0+/, "");
}
const num = parseInt(value);
if (num < 1) {
dayInterval.value = "1";
} else if (num > 365) {
dayInterval.value = "365";
} else {
dayInterval.value = String(num);
}
}
function resetForm() {
task.value = {
title: "",
action_kind: "restart",
options: {},
enabled: true,
warn_msg: '/tellraw @a "Restarting in {}!"',
warn_intervals: [],
};
scheduleType.value = "daily";
dayInterval.value = "1";
selectedTime.value = { hour: "9", minute: "0" };
customCron.value = "0 0 9 * * *";
isNew.value = true;
originalTaskId.value = null;
error.value = null;
if (isValidCron.value) {
task.value.every = cronString.value;
}
}
function loadTaskData(taskData: ServerSchedule | Schedule) {
task.value = { ...taskData };
isNew.value = false;
originalTaskId.value = "id" in taskData ? taskData.id : null;
if (task.value.every && typeof task.value.every === "string") {
const parts = task.value.every.split(/\s+/);
if (parts.length === 6) {
const second = parts[0];
const minute = parts[1];
const hour = parts[2];
const dayOfMonth = parts[3];
if (second === "0" && dayOfMonth.startsWith("*/")) {
scheduleType.value = "daily";
dayInterval.value = dayOfMonth.substring(2);
selectedTime.value = { hour, minute };
} else if (second === "0" && dayOfMonth === "*" && parts[4] === "*" && parts[5] === "*") {
scheduleType.value = "daily";
dayInterval.value = "1";
selectedTime.value = { hour, minute };
} else {
scheduleType.value = "custom";
customCron.value = task.value.every;
}
} else if (parts.length === 5) {
const minute = parts[0];
const hour = parts[1];
const dayOfMonth = parts[2];
if (dayOfMonth.startsWith("*/")) {
scheduleType.value = "daily";
dayInterval.value = dayOfMonth.substring(2);
selectedTime.value = { hour, minute };
} else if (dayOfMonth === "*" && parts[3] === "*" && parts[4] === "*") {
scheduleType.value = "daily";
dayInterval.value = "1";
selectedTime.value = { hour, minute };
} else {
scheduleType.value = "custom";
customCron.value = `0 ${task.value.every}`;
}
} else {
scheduleType.value = "custom";
customCron.value = task.value.every;
}
}
}
function show(taskData?: ServerSchedule | Schedule) {
if (taskData) {
loadTaskData(taskData);
} else {
resetForm();
}
modal.value?.show();
}
function closeModal() {
modal.value?.hide();
}
function onModalHide() {
// Reset form when modal is hidden
resetForm();
}
async function saveTask() {
loading.value = true;
error.value = null;
if (isValidCron.value) {
task.value.every = cronString.value;
} else {
error.value = "Cannot save with invalid cron expression.";
loading.value = false;
return;
}
try {
if (isNew.value) {
const scheduleToCreate: Schedule = {
title: task.value.title || "",
every: task.value.every!,
action_kind: task.value.action_kind!,
options: task.value.options!,
enabled: task.value.enabled !== undefined ? task.value.enabled : true,
warn_msg: task.value.warn_msg !== undefined ? task.value.warn_msg : "",
warn_intervals: task.value.warn_intervals || [],
};
await props.server.scheduling.createTask(scheduleToCreate);
closeModal();
} else if (originalTaskId.value != null) {
await props.server.scheduling.editTask(originalTaskId.value, task.value);
closeModal();
}
} catch (e) {
error.value = (e as Error).message || "Failed to save task.";
} finally {
loading.value = false;
}
}
watch([cronString, isValidCron], ([cron, valid]) => {
if (valid) {
task.value.every = cron;
}
});
watch(
() => task.value.action_kind,
(kind) => {
if (kind === "game-command") {
if (
!task.value.options ||
typeof task.value.options !== "object" ||
!("command" in task.value.options)
) {
task.value.options = { command: "" };
}
} else {
task.value.options = {};
}
},
{ immediate: true },
);
defineExpose({
show,
hide: closeModal,
});
</script>
<style scoped></style>

View File

@ -11,6 +11,7 @@ import {
WSModule,
FSModule,
} from "./modules/index.ts";
import { SchedulingModule } from "./modules/scheduling.ts";
export function handleError(err: any) {
if (err instanceof ModrinthServerError && err.v1Error) {
@ -40,6 +41,7 @@ export class ModrinthServer {
readonly startup: StartupModule;
readonly ws: WSModule;
readonly fs: FSModule;
readonly scheduling: SchedulingModule;
constructor(serverId: string) {
this.serverId = serverId;
@ -51,6 +53,7 @@ export class ModrinthServer {
this.startup = new StartupModule(this);
this.ws = new WSModule(this);
this.fs = new FSModule(this);
this.scheduling = new SchedulingModule(this);
}
async createMissingFolders(path: string): Promise<void> {
@ -197,7 +200,16 @@ export class ModrinthServer {
const modulesToRefresh =
modules.length > 0
? modules
: (["general", "content", "backups", "network", "startup", "ws", "fs"] as ModuleName[]);
: ([
"general",
"content",
"backups",
"network",
"startup",
"ws",
"fs",
"scheduling",
] as ModuleName[]);
for (const module of modulesToRefresh) {
try {
@ -242,6 +254,8 @@ export class ModrinthServer {
case "fs":
await this.fs.fetch();
break;
case "scheduling":
await this.scheduling.fetch();
}
} catch (error) {
if (error instanceof ModrinthServerError) {

View File

@ -0,0 +1,80 @@
import type { Schedule, ServerSchedule } from "@modrinth/utils";
import { useServersFetch } from "../servers-fetch.ts";
import { ServerModule } from "./base.ts";
export class SchedulingModule extends ServerModule {
tasks: ServerSchedule[] = [];
private optimisticUpdate(action: () => void): () => void {
const originalTasks = [...this.tasks];
action();
return () => {
this.tasks = originalTasks;
};
}
async fetch(): Promise<void> {
const response = await useServersFetch<{ schedules: { quota: 32; items: ServerSchedule[] } }>(
`servers/${this.serverId}/options`,
{ version: 1 },
);
this.tasks = response.schedules.items;
}
async deleteTask(task: ServerSchedule): Promise<void> {
const rollback = this.optimisticUpdate(() => {
this.tasks = this.tasks.filter((t) => t.id !== task.id);
});
try {
await useServersFetch(`servers/${this.serverId}/options/schedules/${task.id}`, {
method: "DELETE",
version: 1,
});
} catch (error) {
rollback();
throw error;
}
}
async createTask(task: Schedule): Promise<number> {
const rollback = this.optimisticUpdate(() => {});
try {
const response = await useServersFetch<{ id: number }>(
`servers/${this.serverId}/options/schedules`,
{
method: "POST",
body: task,
version: 1,
},
);
this.tasks.push({ ...task, id: response.id } as ServerSchedule);
return response.id;
} catch (error) {
rollback();
throw error;
}
}
async editTask(taskId: number, updatedTask: Partial<Schedule>): Promise<void> {
const rollback = this.optimisticUpdate(() => {
const taskIndex = this.tasks.findIndex((t) => t.id === taskId);
if (taskIndex !== -1) {
this.tasks[taskIndex] = { ...this.tasks[taskIndex], ...updatedTask };
}
});
try {
await useServersFetch(`servers/${this.serverId}/options/schedules/${taskId}`, {
method: "PATCH",
body: updatedTask,
version: 1,
});
} catch (error) {
rollback();
throw error;
}
}
}

View File

@ -417,7 +417,7 @@ const loadModulesPromise = Promise.resolve().then(() => {
if (server.general?.status === "suspended") {
return;
}
return server.refresh(["content", "backups", "network", "startup", "fs"]);
return server.refresh(["content", "backups", "network", "startup", "fs", "scheduling"]);
});
provide("modulesLoaded", loadModulesPromise);

View File

@ -16,6 +16,7 @@ import {
CardIcon,
UserIcon,
WrenchIcon,
CalendarSyncIcon,
} from "@modrinth/assets";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
@ -35,6 +36,11 @@ useHead({
const navLinks = [
{ icon: SettingsIcon, label: "General", href: `/servers/manage/${serverId}/options` },
{ icon: WrenchIcon, label: "Platform", href: `/servers/manage/${serverId}/options/loader` },
{
icon: CalendarSyncIcon,
label: "Task Scheduling",
href: `/servers/manage/${serverId}/options/scheduling`,
},
{ icon: TextQuoteIcon, label: "Startup", href: `/servers/manage/${serverId}/options/startup` },
{ icon: VersionIcon, label: "Network", href: `/servers/manage/${serverId}/options/network` },
{ icon: ListIcon, label: "Properties", href: `/servers/manage/${serverId}/options/properties` },

View File

@ -0,0 +1,437 @@
<template>
<EditScheduledTaskModal ref="modal" :server="server"></EditScheduledTaskModal>
<div class="relative h-full w-full overflow-y-auto">
<div class="flex h-full w-full flex-col">
<section class="universal-card">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-full flex-col justify-center">
<h2 class="m-0 text-lg font-bold text-contrast">Task scheduling</h2>
<span v-if="tasks.length < 1">
No scheduled tasks yet. Click the button to create your first task.
</span>
<span v-else-if="!isMobile"
>You can manage multiple tasks at once by selecting them below.</span
>
</div>
<div>
<ButtonStyled color="green"
><button @click="modal?.show()"><PlusIcon /> Create task</button></ButtonStyled
>
</div>
</div>
<template v-if="tasks.length > 0">
<div v-if="!isMobile" class="input-group">
<ButtonStyled>
<button :disabled="selectedTasks.length === 0" @click="handleBulkToggle">
<ToggleRightIcon />
Toggle selected
</button>
</ButtonStyled>
<ButtonStyled color="red">
<button :disabled="selectedTasks.length === 0" @click="handleBulkDelete">
<TrashIcon />
Delete selected
</button>
</ButtonStyled>
<div class="push-right">
<div class="labeled-control-row">
Sort by
<Multiselect
v-model="sortBy"
:searchable="false"
class="small-select"
:options="['Name', 'Type', 'Enabled', 'Schedule']"
:close-on-select="true"
:show-labels="false"
:allow-empty="false"
@update:model-value="updateSort"
/>
<button
v-tooltip="descending ? 'Descending' : 'Ascending'"
class="square-button"
@click="updateDescending"
>
<DescendingIcon v-if="descending" />
<AscendingIcon v-else />
</button>
</div>
</div>
</div>
<div v-if="!isMobile" class="grid-table">
<div class="grid-table__row grid-table__header">
<div>
<Checkbox
:model-value="selectedTasks.length === tasks.length && tasks.length > 0"
@update:model-value="handleSelectAll"
/>
</div>
<div>Type</div>
<div>Task Details</div>
<div class="schedule-col">Schedule</div>
<div class="details-col">Warnings</div>
<div>Enabled</div>
<div>Actions</div>
</div>
<div
v-for="(task, index) in sortedTasks"
:key="`task-${index}`"
class="grid-table__row"
>
<div>
<Checkbox
:model-value="selectedTasks.includes(task)"
@update:model-value="(value) => handleTaskSelect(task, value)"
/>
</div>
<div>
<RaisedBadge
:text="task.action_kind === 'restart' ? 'Restart' : 'Game Command'"
:icon="task.action_kind === 'restart' ? UpdatedIcon : CodeIcon"
/>
</div>
<div>
<span class="mb-1 block font-medium text-primary">{{ task.title }}</span>
<div
v-if="task.action_kind === 'game-command' && task.options?.command"
class="mt-1"
>
<code
class="break-all rounded-sm bg-button-bg px-1 py-0.5 text-xs text-secondary"
>
{{ task.options.command }}
</code>
</div>
</div>
<div class="schedule-col">
<span class="text-sm text-secondary">{{ getHumanReadableCron(task.every) }}</span>
</div>
<div class="details-col">
<div
v-if="task.warn_intervals && task.warn_intervals.length > 0"
class="flex flex-col gap-1"
>
<span class="text-sm font-medium text-primary">
{{ task.warn_intervals.length }} warnings
</span>
<div class="font-mono text-xs text-secondary">
{{ formatWarningIntervals(task.warn_intervals) }}
</div>
</div>
<span v-else class="text-sm italic text-secondary">No warnings</span>
</div>
<div>
<Toggle
:model-value="task.enabled"
@update:model-value="(value) => handleTaskToggle(task, value || false)"
/>
</div>
<div>
<div class="flex gap-1">
<ButtonStyled icon-only circular>
<button :v-tooltip="'Edit Task'" @click="modal?.show(task)">
<EditIcon />
</button>
</ButtonStyled>
<ButtonStyled icon-only circular color="red">
<button @click="handleTaskDelete(task)">
<TrashIcon />
</button>
</ButtonStyled>
</div>
</div>
</div>
</div>
<div v-else class="mt-4 flex flex-col gap-3">
<Card
v-for="(task, index) in sortedTasks"
:key="`mobile-task-${index}`"
class="rounded-lg border !bg-bg p-4 shadow-sm"
>
<div class="flex items-center justify-between gap-2">
<h3 class="mb-0 truncate font-medium">{{ task.title }}</h3>
<Toggle
:model-value="task.enabled"
@update:model-value="(value) => handleTaskToggle(task, value || false)"
/>
</div>
<div class="mt-2 flex items-center gap-2">
<RaisedBadge
:text="task.action_kind === 'restart' ? 'Restart' : 'Command'"
:icon="task.action_kind === 'restart' ? UpdatedIcon : CodeIcon"
class="text-xs"
/>
<div class="ml-auto flex flex-row gap-2">
<ButtonStyled icon-only circular>
<button class="p-1" @click="modal?.show(task)">
<EditIcon />
</button>
</ButtonStyled>
<ButtonStyled icon-only circular color="red">
<button class="p-1" @click="handleTaskDelete(task)">
<TrashIcon />
</button>
</ButtonStyled>
</div>
</div>
</Card>
</div>
</template>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, onBeforeMount } from "vue";
import { Multiselect } from "vue-multiselect";
import {
PlusIcon,
TrashIcon,
ToggleRightIcon,
EditIcon,
UpdatedIcon,
CodeIcon,
SortAscendingIcon as AscendingIcon,
SortDescendingIcon as DescendingIcon,
} from "@modrinth/assets";
import { Toggle, Checkbox, RaisedBadge, ButtonStyled, Card } from "@modrinth/ui";
import cronstrue from "cronstrue";
import type { ServerSchedule } from "@modrinth/utils";
import { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
import EditScheduledTaskModal from "~/components/ui/servers/scheduling/EditScheduledTaskModal.vue";
const props = defineProps<{
server: ModrinthServer;
}>();
onBeforeMount(async () => {
await props.server.scheduling.fetch();
});
const selectedTasks = ref<ServerSchedule[]>([]);
const sortBy = ref("Name");
const descending = ref(false);
const modal = ref<typeof EditScheduledTaskModal>();
const isMobile = ref(true);
const tasks = computed(() => props.server.scheduling.tasks as ServerSchedule[]);
const sortedTasks = computed(() => {
const sorted = [...tasks.value];
switch (sortBy.value) {
case "Name":
sorted.sort((a, b) => a.title.localeCompare(b.title));
break;
case "Type":
sorted.sort((a, b) => a.action_kind.localeCompare(b.action_kind));
break;
case "Enabled":
sorted.sort((a, b) => {
if (a.enabled === b.enabled) return 0;
return a.enabled ? -1 : 1;
});
break;
case "Schedule":
sorted.sort((a, b) => a.every.localeCompare(b.every));
break;
}
return descending.value ? sorted.reverse() : sorted;
});
function checkMobile() {
isMobile.value = window.innerWidth < 874;
}
onMounted(() => {
checkMobile();
window.addEventListener("resize", checkMobile);
});
onUnmounted(() => {
window.removeEventListener("resize", checkMobile);
});
function handleSelectAll(selected: boolean): void {
selectedTasks.value = selected ? [...tasks.value] : [];
}
function handleTaskSelect(task: ServerSchedule, selected: boolean): void {
if (selected) {
selectedTasks.value.push(task);
} else {
selectedTasks.value = selectedTasks.value.filter((t) => t !== task);
}
}
async function handleTaskToggle(task: ServerSchedule, enabled: boolean): Promise<void> {
try {
await props.server.scheduling.editTask(task.id, { enabled });
console.log("Toggle task:", task.id, enabled);
} catch (error) {
console.error("Failed to toggle task:", error);
task.enabled = !enabled;
}
}
async function handleTaskDelete(task: ServerSchedule): Promise<void> {
if (confirm(`Are you sure you want to delete "${task.title}"?`)) {
try {
await props.server.scheduling.deleteTask(task);
selectedTasks.value = selectedTasks.value.filter((t) => t !== task);
console.log("Delete task:", task.title);
} catch (error) {
console.error("Failed to delete task:", error);
}
}
}
async function handleBulkToggle(): Promise<void> {
const enabledCount = selectedTasks.value.filter((t) => t.enabled).length;
const shouldEnable = enabledCount < selectedTasks.value.length / 2;
try {
await Promise.all(
selectedTasks.value.map((task) =>
props.server.scheduling.editTask(task.id, { enabled: shouldEnable }),
),
);
console.log("Bulk toggle tasks:", selectedTasks.value.length, "to", shouldEnable);
} catch (error) {
console.error("Failed to bulk toggle tasks:", error);
}
}
async function handleBulkDelete(): Promise<void> {
if (confirm(`Are you sure you want to delete ${selectedTasks.value.length} selected tasks?`)) {
try {
await Promise.all(
selectedTasks.value.map((task) => props.server.scheduling.deleteTask(task)),
);
selectedTasks.value = [];
console.log("Bulk delete completed");
} catch (error) {
console.error("Failed to bulk delete tasks:", error);
}
}
}
function updateSort(): void {
// Trigger reactivity for sortedTasks
}
function updateDescending(): void {
descending.value = !descending.value;
}
function getHumanReadableCron(cronExpression: string): string {
try {
return cronstrue.toString(cronExpression);
} catch {
return cronExpression;
}
}
function formatWarningIntervals(intervals: number[]): string {
return intervals
.sort((a, b) => b - a)
.map((seconds) => {
if (seconds >= 3600) return `${Math.floor(seconds / 3600)}h`;
if (seconds >= 60) return `${Math.floor(seconds / 60)}m`;
return `${seconds}s`;
})
.join(", ");
}
</script>
<style lang="scss" scoped>
.grid-table {
display: grid;
grid-template-columns:
min-content minmax(min-content, 120px) minmax(min-content, 2fr)
minmax(min-content, 1fr) minmax(min-content, 120px) min-content min-content;
border-radius: var(--size-rounded-sm);
overflow: hidden;
margin-top: var(--spacing-card-md);
outline: 1px solid transparent;
.grid-table__row {
display: contents;
> div {
display: flex;
flex-direction: column;
justify-content: center;
padding: var(--spacing-card-sm);
&:first-child {
padding-left: var(--spacing-card-bg);
}
&:last-child {
padding-right: var(--spacing-card-bg);
}
}
&:nth-child(2n + 1) > div {
background-color: var(--color-table-alternate-row);
}
&.grid-table__header > div {
background-color: var(--color-bg);
font-weight: bold;
color: var(--color-text-dark);
padding-top: var(--spacing-card-bg);
padding-bottom: var(--spacing-card-bg);
}
}
}
.labeled-control-row {
flex: 1;
display: flex;
flex-direction: row;
min-width: 0;
align-items: center;
gap: var(--spacing-card-md);
white-space: nowrap;
}
.small-select {
width: -moz-fit-content;
width: fit-content;
}
.push-right {
margin-left: auto;
}
@media (max-width: 1050px) {
.schedule-col {
display: none !important;
}
.grid-table {
grid-template-columns:
min-content minmax(min-content, 120px) minmax(min-content, 2fr) minmax(min-content, 120px)
min-content min-content;
}
}
@media (max-width: 1010px) {
.details-col {
display: none !important;
}
.grid-table {
grid-template-columns:
min-content minmax(min-content, 120px) minmax(min-content, 2fr) minmax(min-content, 120px)
min-content;
}
}
</style>

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-calendar-sync-icon lucide-calendar-sync">
<path d="M11 10v4h4"/>
<path d="m11 14 1.535-1.605a5 5 0 0 1 8 1.5"/>
<path d="M16 2v4"/>
<path d="m21 18-1.535 1.605a5 5 0 0 1-8-1.5"/>
<path d="M21 22v-4h-4"/>
<path d="M21 8.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4.3"/>
<path d="M3 10h4"/>
<path d="M8 2v4"/>
</svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock-icon lucide-clock"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>

After

Width:  |  Height:  |  Size: 302 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-toggle-right-icon lucide-toggle-right"><circle cx="15" cy="12" r="3"/><rect width="20" height="14" x="2" y="5" rx="7"/></svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>

After

Width:  |  Height:  |  Size: 376 B

View File

@ -66,6 +66,7 @@ import _ChevronRightIcon from './icons/chevron-right.svg?component'
import _ClearIcon from './icons/clear.svg?component'
import _ClientIcon from './icons/client.svg?component'
import _ClipboardCopyIcon from './icons/clipboard-copy.svg?component'
import _ClockIcon from './icons/clock.svg?component'
import _CodeIcon from './icons/code.svg?component'
import _CoffeeIcon from './icons/coffee.svg?component'
import _CoinsIcon from './icons/coins.svg?component'
@ -182,6 +183,7 @@ import _TagsIcon from './icons/tags.svg?component'
import _TerminalSquareIcon from './icons/terminal-square.svg?component'
import _TransferIcon from './icons/transfer.svg?component'
import _TrashIcon from './icons/trash.svg?component'
import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
import _UndoIcon from './icons/undo.svg?component'
import _RedoIcon from './icons/redo.svg?component'
import _UnknownIcon from './icons/unknown.svg?component'
@ -210,6 +212,8 @@ import _CPUIcon from './icons/cpu.svg?component'
import _LoaderIcon from './icons/loader.svg?component'
import _ImportIcon from './icons/import.svg?component'
import _TimerIcon from './icons/timer.svg?component'
import _CalendarSyncIcon from './icons/calendar-sync.svg?component'
import _ToggleRightIcon from './icons/toggle-right.svg?component'
// Editor Icons
import _BoldIcon from './icons/bold.svg?component'
@ -285,6 +289,7 @@ export const ChevronRightIcon = _ChevronRightIcon
export const ClearIcon = _ClearIcon
export const ClientIcon = _ClientIcon
export const ClipboardCopyIcon = _ClipboardCopyIcon
export const ClockIcon = _ClockIcon
export const CodeIcon = _CodeIcon
export const CoffeeIcon = _CoffeeIcon
export const CoinsIcon = _CoinsIcon
@ -441,3 +446,6 @@ export const LoaderIcon = _LoaderIcon
export const ImportIcon = _ImportIcon
export const CardIcon = _CardIcon
export const TimerIcon = _TimerIcon
export const CalendarSyncIcon = _CalendarSyncIcon
export const ToggleRightIcon = _ToggleRightIcon
export const TriangleAlertIcon = _TriangleAlertIcon

View File

@ -33,6 +33,7 @@
"@types/markdown-it": "^14.1.1",
"@vintl/how-ago": "^3.0.1",
"apexcharts": "^3.44.0",
"cronstrue": "^2.61.0",
"dayjs": "^1.11.10",
"floating-vue": "^5.2.2",
"highlight.js": "^11.9.0",

View File

@ -4,10 +4,10 @@
v-for="item in items"
:key="formatLabel(item)"
class="btn"
:class="{ selected: selected === item, capitalize: capitalize }"
:class="{ selected: isSelected(item), capitalize: capitalize }"
@click="toggleItem(item)"
>
<CheckIcon v-if="selected === item" />
<CheckIcon v-if="isSelected(item)" />
<span>{{ formatLabel(item) }}</span>
</Button>
</div>
@ -23,26 +23,48 @@ const props = withDefaults(
formatLabel?: (item: T) => string
neverEmpty?: boolean
capitalize?: boolean
multi?: boolean
}>(),
{
neverEmpty: true,
// Intentional any type, as this default should only be used for primitives (string or number)
formatLabel: (item) => item.toString(),
capitalize: true,
multi: false,
},
)
const selected = defineModel<T | null>()
const selected = defineModel<T | null | T[]>()
// If one always has to be selected, default to the first one
if (props.items.length > 0 && props.neverEmpty && !selected.value) {
selected.value = props.items[0]
selected.value = props.multi ? [props.items[0]] : props.items[0]
}
function isSelected(item: T): boolean {
if (props.multi) {
return Array.isArray(selected.value) && selected.value.includes(item)
}
return selected.value === item
}
function toggleItem(item: T) {
if (selected.value === item && !props.neverEmpty) {
selected.value = null
if (props.multi) {
const currentSelection = Array.isArray(selected.value) ? selected.value : []
const isCurrentlySelected = currentSelection.includes(item)
if (isCurrentlySelected) {
if (!props.neverEmpty || currentSelection.length > 1) {
selected.value = currentSelection.filter((i) => i !== item)
}
} else {
selected.value = [...currentSelection, item]
}
} else {
selected.value = item
if (selected.value === item && !props.neverEmpty) {
selected.value = null
} else {
selected.value = item
}
}
}
</script>

View File

@ -0,0 +1,17 @@
<template>
<div class="flex items-center gap-2 w-fit px-3 py-1 bg-button-bg rounded-full text-sm">
<component :is="icon" v-if="icon" class="w-4 h-4" />
<span class="whitespace-nowrap">
{{ text }}
</span>
</div>
</template>
<script setup lang="ts">
import type { Component } from 'vue'
defineProps<{
text: string
icon?: Component
}>()
</script>

View File

@ -0,0 +1,161 @@
<template>
<div>
<div
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-button-bg p-1 text-sm font-bold"
>
<button
v-for="(tab, index) in tabs"
:key="tab"
ref="tabElements"
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full bg-transparent"
:class="{
'text-button-textSelected': activeTabIndex === index,
'text-contrast': activeTabIndex !== index,
}"
@click="setActiveTab(index)"
>
<span class="text-nowrap font-bold text-center mx-auto">{{ getTabLabel(tab) }}</span>
</button>
<div
:class="`tabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 bg-button-bgSelected`"
:style="{
left: sliderLeftPx,
top: sliderTopPx,
right: sliderRightPx,
bottom: sliderBottomPx,
opacity:
sliderLeft === 4 && sliderLeft === sliderRight ? 0 : activeTabIndex === -1 ? 0 : 1,
}"
aria-hidden="true"
/>
</div>
<!-- Tab Content -->
<div class="tab-content mt-4">
<template v-for="(tab, index) in tabs" :key="tab">
<div v-show="activeTabIndex === index" class="tab-panel">
<slot :name="tab" />
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue'
interface Props {
tabs: string[]
formatFunction?: (tab: string) => string
defaultTab?: string
}
const props = withDefaults(defineProps<Props>(), {
formatFunction: undefined,
defaultTab: undefined,
})
const activeTabIndex = ref(0)
const tabElements = ref<HTMLElement[]>([])
const sliderLeft = ref(4)
const sliderTop = ref(4)
const sliderRight = ref(4)
const sliderBottom = ref(4)
const sliderLeftPx = computed(() => `${sliderLeft.value}px`)
const sliderTopPx = computed(() => `${sliderTop.value}px`)
const sliderRightPx = computed(() => `${sliderRight.value}px`)
const sliderBottomPx = computed(() => `${sliderBottom.value}px`)
function getTabLabel(tab: string): string {
return props.formatFunction ? props.formatFunction(tab) : tab
}
function setActiveTab(index: number) {
activeTabIndex.value = index
updateSliderPosition()
}
function updateSliderPosition() {
nextTick(() => {
const el = tabElements.value[activeTabIndex.value]
if (!el || !el.offsetParent) return
const parent = el.offsetParent as HTMLElement
const newValues = {
left: el.offsetLeft,
top: el.offsetTop,
right: parent.offsetWidth - el.offsetLeft - el.offsetWidth,
bottom: parent.offsetHeight - el.offsetTop - el.offsetHeight,
}
if (sliderLeft.value === 4 && sliderRight.value === 4) {
// Initial position
sliderLeft.value = newValues.left
sliderRight.value = newValues.right
sliderTop.value = newValues.top
sliderBottom.value = newValues.bottom
} else {
const delay = 200
if (newValues.left < sliderLeft.value) {
sliderLeft.value = newValues.left
setTimeout(() => {
sliderRight.value = newValues.right
}, delay)
} else {
sliderRight.value = newValues.right
setTimeout(() => {
sliderLeft.value = newValues.left
}, delay)
}
if (newValues.top < sliderTop.value) {
sliderTop.value = newValues.top
setTimeout(() => {
sliderBottom.value = newValues.bottom
}, delay)
} else {
sliderBottom.value = newValues.bottom
setTimeout(() => {
sliderTop.value = newValues.top
}, delay)
}
}
})
}
onMounted(() => {
if (props.defaultTab) {
const defaultIndex = props.tabs.indexOf(props.defaultTab)
if (defaultIndex !== -1) {
activeTabIndex.value = defaultIndex
}
}
updateSliderPosition()
})
watch(activeTabIndex, () => {
updateSliderPosition()
})
</script>
<style scoped>
.tabs-transition {
transition:
all 150ms cubic-bezier(0.4, 0, 0.2, 1),
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
}
.card-shadow {
box-shadow: var(--shadow-card);
}
.tab-content {
min-height: 200px;
}
</style>

View File

@ -0,0 +1,264 @@
<template>
<Dropdown
ref="dropdown"
:disabled="disabled"
placement="bottom-start"
theme="ribbit-popout"
:distance="8"
no-arrow
@apply-show="onDropdownShow"
>
<ButtonStyled
:class="['w-full justify-start text-left font-normal', !modelValue && 'text-secondary']"
:disabled="disabled"
>
<button>
<ClockIcon class="h-4 w-4" />
{{ modelValue ? formatTime(modelValue) : placeholder }} {{ useUtcValues ? 'UTC' : '' }}
</button>
</ButtonStyled>
<template #popper>
<div class="flex flex-col gap-4 p-4 w-64">
<div class="grid grid-cols-2 gap-4">
<div class="flex flex-col gap-2">
<label for="hour" class="text-sm font-medium text-contrast"> Hour </label>
<input
id="hour"
v-model="currentTime.hour"
type="text"
inputmode="numeric"
placeholder="00"
class="bg-bg-input w-full rounded-lg p-2 text-center"
maxlength="2"
@input="handleHourChange"
/>
<div class="text-xs text-secondary text-center">0-23</div>
</div>
<div class="flex flex-col gap-2">
<label for="minute" class="text-sm font-medium text-contrast"> Minute </label>
<input
id="minute"
v-model="currentTime.minute"
type="text"
inputmode="numeric"
placeholder="00"
class="bg-bg-input w-full rounded-lg p-2 text-center"
maxlength="2"
@input="handleMinuteChange"
/>
<div class="text-xs text-secondary text-center">0-59</div>
</div>
</div>
<div class="text-center p-3 bg-bg-raised rounded-lg">
<div class="text-sm text-secondary">Selected Time (Local)</div>
<div class="text-2xl font-bold text-contrast">
{{ formatTime(currentTime) }}
</div>
<div class="text-xs text-secondary mt-1">
{{ getUTCTime(currentTime) }}
</div>
</div>
<div class="flex flex-col gap-2">
<div class="text-sm font-medium text-contrast">Quick Select</div>
<div class="grid grid-cols-2 gap-2">
<ButtonStyled v-for="preset in quickSelects" :key="preset.label">
<button class="" @click="handleQuickSelect(preset.hour, preset.minute)">
{{ preset.label }}
</button>
</ButtonStyled>
</div>
</div>
<div class="flex gap-2 pt-2">
<ButtonStyled class="flex-1">
<button @click="handleCancel">Cancel</button>
</ButtonStyled>
<ButtonStyled color="brand" class="flex-1">
<button @click="handleDone">Done</button>
</ButtonStyled>
</div>
</div>
</template>
</Dropdown>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { Dropdown } from 'floating-vue'
import { ButtonStyled } from '@modrinth/ui'
import { ClockIcon } from '@modrinth/assets'
interface TimeValue {
hour: string
minute: string
}
interface Props {
modelValue?: TimeValue
disabled?: boolean
placeholder?: string
useUtcValues?: boolean
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => ({ hour: '12', minute: '00' }),
disabled: false,
placeholder: 'Select time',
useUtcValues: false,
})
const emit = defineEmits<{
'update:modelValue': [value: TimeValue]
}>()
function utcToLocal(utcTime: TimeValue): TimeValue {
const today = new Date()
const utcHour = utcTime.hour === '' ? 0 : parseInt(utcTime.hour)
const utcMinute = utcTime.minute === '' ? 0 : parseInt(utcTime.minute)
const utcDate = new Date(
Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), utcHour, utcMinute),
)
return {
hour: utcDate.getHours().toString(),
minute: utcDate.getMinutes().toString(),
}
}
function localToUtc(localTime: TimeValue): TimeValue {
const today = new Date()
const localHour = localTime.hour === '' ? 0 : parseInt(localTime.hour)
const localMinute = localTime.minute === '' ? 0 : parseInt(localTime.minute)
const localDate = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate(),
localHour,
localMinute,
)
return {
hour: localDate.getUTCHours().toString(),
minute: localDate.getUTCMinutes().toString(),
}
}
function emitTime(localTime: TimeValue) {
const timeToEmit = props.useUtcValues ? localToUtc(localTime) : localTime
emit('update:modelValue', timeToEmit)
}
const dropdown = ref()
const originalTime = ref<TimeValue>({ hour: '12', minute: '00' })
const getInitialTime = (): TimeValue => {
const defaultTime = { hour: '12', minute: '00' }
if (!props.modelValue) return defaultTime
return props.useUtcValues ? utcToLocal(props.modelValue) : props.modelValue
}
const currentTime = reactive<TimeValue>(getInitialTime())
watch(
() => props.modelValue,
(newValue) => {
if (newValue) {
const displayTime = props.useUtcValues ? utcToLocal(newValue) : newValue
currentTime.hour = displayTime.hour
currentTime.minute = displayTime.minute
}
},
{ deep: true },
)
function formatTime(time: TimeValue): string {
const hour = (time.hour === '' ? '0' : time.hour).padStart(2, '0')
const minute = (time.minute === '' ? '0' : time.minute).padStart(2, '0')
return `${hour}:${minute}`
}
function getUTCTime(time: TimeValue): string {
const today = new Date()
const hourValue = time.hour === '' ? 0 : parseInt(time.hour)
const minuteValue = time.minute === '' ? 0 : parseInt(time.minute)
const localDate = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate(),
hourValue,
minuteValue,
)
const utcHour = localDate.getUTCHours().toString().padStart(2, '0')
const utcMinute = localDate.getUTCMinutes().toString().padStart(2, '0')
return `${utcHour}:${utcMinute} UTC`
}
function handleHourChange(event: Event) {
const target = event.target as HTMLInputElement
const cleanValue = target.value.replace(/\D/g, '').slice(0, 2)
let hour = cleanValue
if (cleanValue !== '' && (parseInt(cleanValue) > 23 || parseInt(cleanValue) < 0)) {
hour = Math.min(23, Math.max(0, parseInt(cleanValue))).toString()
}
currentTime.hour = hour
emitTime(currentTime)
}
function handleMinuteChange(event: Event) {
const target = event.target as HTMLInputElement
const cleanValue = target.value.replace(/\D/g, '').slice(0, 2)
let minute = cleanValue
if (cleanValue !== '' && (parseInt(cleanValue) > 59 || parseInt(cleanValue) < 0)) {
minute = Math.min(59, Math.max(0, parseInt(cleanValue))).toString()
}
currentTime.minute = minute
emitTime(currentTime)
}
function handleQuickSelect(hour: string, minute: string) {
currentTime.hour = hour
currentTime.minute = minute
emitTime(currentTime)
}
function handleCancel() {
currentTime.hour = originalTime.value.hour
currentTime.minute = originalTime.value.minute
emitTime(originalTime.value)
dropdown.value?.hide()
}
function handleDone() {
dropdown.value?.hide()
}
function onDropdownShow() {
originalTime.value = { ...currentTime }
}
const quickSelects = [
{ label: '00:00', hour: '0', minute: '0' },
{ label: '04:00', hour: '4', minute: '0' },
{ label: '12:00', hour: '12', minute: '0' },
{ label: '21:00', hour: '21', minute: '0' },
]
</script>
<style lang="scss">
.v-popper--theme-dropdown .v-popper__arrow-container {
display: none;
}
</style>

View File

@ -34,15 +34,18 @@ export { default as ProgressBar } from './base/ProgressBar.vue'
export { default as ProjectCard } from './base/ProjectCard.vue'
export { default as RadialHeader } from './base/RadialHeader.vue'
export { default as RadioButtons } from './base/RadioButtons.vue'
export { default as RaisedBadge } from './base/RaisedBadge.vue'
export { default as ScrollablePanel } from './base/ScrollablePanel.vue'
export { default as ServerNotice } from './base/ServerNotice.vue'
export { default as SimpleBadge } from './base/SimpleBadge.vue'
export { default as Slider } from './base/Slider.vue'
export { default as SmartClickable } from './base/SmartClickable.vue'
export { default as StatItem } from './base/StatItem.vue'
export { default as TabbedContent } from './base/TabbedContent.vue'
export { default as TagItem } from './base/TagItem.vue'
export { default as TeleportDropdownMenu } from './base/TeleportDropdownMenu.vue'
export { default as Timeline } from './base/Timeline.vue'
export { default as TimePicker } from './base/TimePicker.vue'
export { default as Toggle } from './base/Toggle.vue'
// Branding

View File

@ -37,7 +37,7 @@
</button>
</ButtonStyled>
</div>
<div class="overflow-y-auto p-6">
<div class="modal-content overflow-y-auto p-6">
<slot> You just lost the game.</slot>
</div>
</div>
@ -237,6 +237,10 @@ function handleKeyDown(event: KeyboardEvent) {
opacity: 0;
transition: all 0.2s ease-in-out;
.modal-content {
scrollbar-gutter: stable;
}
@media (prefers-reduced-motion) {
transition: none !important;
}

View File

@ -16,4 +16,12 @@ export interface ModuleError {
timestamp: number
}
export type ModuleName = 'general' | 'content' | 'backups' | 'network' | 'startup' | 'ws' | 'fs'
export type ModuleName =
| 'general'
| 'content'
| 'backups'
| 'network'
| 'startup'
| 'ws'
| 'fs'
| 'scheduling'

View File

@ -6,3 +6,4 @@ export * from './filesystem'
export * from './websocket'
export * from './stats'
export * from './common'
export * from './scheduling'

View File

@ -0,0 +1,19 @@
export type ActionKind = 'game-command' | 'restart'
export type ScheduleOptions = { command: string } | Record<string, never>
export interface Schedule {
title: string
every: string
action_kind: ActionKind
options: ScheduleOptions
enabled: boolean
warn_msg: string
warn_intervals: number[]
}
export interface ServerSchedule extends Schedule {
id: number
server_id: string
added_on: string
}

323
pnpm-lock.yaml generated
View File

@ -85,7 +85,7 @@ importers:
version: 1.11.11
floating-vue:
specifier: ^5.2.2
version: 5.2.2(@nuxt/kit@3.14.1592)(vue@3.5.13(typescript@5.5.4))
version: 5.2.2(@nuxt/kit@3.14.1592(magicast@0.3.5))(vue@3.5.13(typescript@5.5.4))
ofetch:
specifier: ^1.3.4
version: 1.4.1
@ -125,7 +125,7 @@ importers:
version: 1.0.7(vue@3.5.13(typescript@5.5.4))
'@vitejs/plugin-vue':
specifier: ^5.0.4
version: 5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))
version: 5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))
autoprefixer:
specifier: ^10.4.19
version: 10.4.20(postcss@8.4.49)
@ -158,7 +158,7 @@ importers:
version: 5.5.4
vite:
specifier: ^5.4.6
version: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.31.6)
version: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0)
vue-tsc:
specifier: ^2.1.6
version: 2.1.6(typescript@5.5.4)
@ -178,19 +178,19 @@ importers:
version: 0.9.4(prettier@3.3.2)(typescript@5.8.2)
'@astrojs/starlight':
specifier: ^0.32.2
version: 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))
version: 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))
'@modrinth/assets':
specifier: workspace:*
version: link:../../packages/assets
astro:
specifier: ^5.4.1
version: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)
version: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)
sharp:
specifier: ^0.33.5
version: 0.33.5
starlight-openapi:
specifier: ^0.14.0
version: 0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)))(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))(openapi-types@12.1.3)
version: 0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)))(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))(openapi-types@12.1.3)
typescript:
specifier: ^5.8.2
version: 5.8.2
@ -233,6 +233,9 @@ importers:
ansi-to-html:
specifier: ^0.7.2
version: 0.7.2
cronstrue:
specifier: ^2.61.0
version: 2.61.0
dayjs:
specifier: ^1.11.7
version: 1.11.11
@ -296,7 +299,7 @@ importers:
version: 6.2.12(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4))
'@nuxt/devtools':
specifier: ^1.3.3
version: 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))
version: 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))
'@types/dompurify':
specifier: ^3.0.5
version: 3.0.5
@ -311,7 +314,7 @@ importers:
version: 3.0.1(@formatjs/intl@2.10.4(typescript@5.5.4))
'@vintl/nuxt':
specifier: ^1.9.2
version: 1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)
version: 1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)
autoprefixer:
specifier: ^10.4.19
version: 10.4.20(postcss@8.4.49)
@ -323,7 +326,7 @@ importers:
version: 10.4.2
nuxt:
specifier: ^3.14.1592
version: 3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.31.6)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue-tsc@2.1.6(typescript@5.5.4))
version: 3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4))
postcss:
specifier: ^8.4.39
version: 8.4.49
@ -383,7 +386,7 @@ importers:
version: 2.0.7(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-prettier:
specifier: ^5.2.1
version: 5.2.1(@types/eslint@9.6.0)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))(prettier@3.3.2)
version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))(prettier@3.3.2)
eslint-plugin-unicorn:
specifier: ^54.0.0
version: 54.0.0(eslint@9.13.0(jiti@2.4.1))
@ -429,12 +432,15 @@ importers:
apexcharts:
specifier: ^3.44.0
version: 3.49.2
cronstrue:
specifier: ^2.61.0
version: 2.61.0
dayjs:
specifier: ^1.11.10
version: 1.11.11
floating-vue:
specifier: ^5.2.2
version: 5.2.2(@nuxt/kit@3.14.1592(rollup@3.29.4))(vue@3.5.13(typescript@5.5.4))
version: 5.2.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@3.29.4))(vue@3.5.13(typescript@5.5.4))
highlight.js:
specifier: ^11.9.0
version: 11.9.0
@ -468,7 +474,7 @@ importers:
version: 7.3.1
'@vintl/unplugin':
specifier: ^1.5.1
version: 1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)
version: 1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)
'@vintl/vintl':
specifier: ^4.4.1
version: 4.4.1(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))
@ -2476,9 +2482,6 @@ packages:
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
'@types/eslint@9.6.0':
resolution: {integrity: sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==}
'@types/eslint@9.6.1':
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
@ -3606,8 +3609,8 @@ packages:
resolution: {integrity: sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA==}
engines: {node: '>=18.0'}
cronstrue@2.52.0:
resolution: {integrity: sha512-NKgHbWkSZXJUcaBHSsyzC8eegD6bBd4O0oCI6XMIJ+y4Bq3v4w7sY3wfWoKPuVlq9pQHRB6od0lmKpIqi8TlKA==}
cronstrue@2.61.0:
resolution: {integrity: sha512-ootN5bvXbIQI9rW94+QsXN5eROtXWwew6NkdGxIRpS/UFWRggL0G5Al7a9GTBFEsuvVhJ2K3CntIIVt7L2ILhA==}
hasBin: true
cross-spawn@6.0.6:
@ -8148,12 +8151,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@astrojs/mdx@4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))':
'@astrojs/mdx@4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))':
dependencies:
'@astrojs/markdown-remark': 6.2.0
'@mdx-js/mdx': 3.1.0(acorn@8.14.0)
acorn: 8.14.0
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)
es-module-lexer: 1.6.0
estree-util-visit: 2.0.0
hast-util-to-html: 9.0.5
@ -8177,16 +8180,16 @@ snapshots:
stream-replace-string: 2.0.0
zod: 3.23.8
'@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))':
'@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))':
dependencies:
'@astrojs/mdx': 4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))
'@astrojs/mdx': 4.1.0(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))
'@astrojs/sitemap': 3.2.1
'@pagefind/default-ui': 1.3.0
'@types/hast': 3.0.4
'@types/js-yaml': 4.0.9
'@types/mdast': 4.0.4
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)
astro-expressive-code: 0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)
astro-expressive-code: 0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))
bcp-47: 2.1.0
hast-util-from-html: 2.0.2
hast-util-select: 6.0.2
@ -9267,12 +9270,12 @@ snapshots:
'@nuxt/devalue@2.0.2': {}
'@nuxt/devtools-kit@1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))':
'@nuxt/devtools-kit@1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))':
dependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
'@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
execa: 7.2.0
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
transitivePeerDependencies:
- magicast
- rollup
@ -9291,17 +9294,17 @@ snapshots:
rc9: 2.1.2
semver: 7.7.1
'@nuxt/devtools@1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))':
'@nuxt/devtools@1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))':
dependencies:
'@antfu/utils': 0.7.10
'@nuxt/devtools-kit': 1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))
'@nuxt/devtools-kit': 1.6.3(magicast@0.3.5)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))
'@nuxt/devtools-wizard': 1.6.3
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
'@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))
'@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))
'@vue/devtools-kit': 7.6.4
birpc: 0.2.19
consola: 3.2.3
cronstrue: 2.52.0
cronstrue: 2.61.0
destr: 2.0.3
error-stack-parser-es: 0.1.5
execa: 7.2.0
@ -9326,9 +9329,9 @@ snapshots:
sirv: 3.0.0
tinyglobby: 0.2.10
unimport: 3.14.4(rollup@4.28.1)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite-plugin-inspect: 0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))
vite-plugin-vue-inspector: 5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vite-plugin-inspect: 0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))
vite-plugin-vue-inspector: 5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))
which: 3.0.1
ws: 8.18.0
transitivePeerDependencies:
@ -9370,9 +9373,9 @@ snapshots:
- supports-color
- typescript
'@nuxt/kit@3.14.1592':
'@nuxt/kit@3.14.1592(magicast@0.3.5)':
dependencies:
'@nuxt/schema': 3.14.1592
'@nuxt/schema': 3.14.1592(magicast@0.3.5)
c12: 2.0.1(magicast@0.3.5)
consola: 3.2.3
defu: 6.1.4
@ -9398,6 +9401,34 @@ snapshots:
- supports-color
optional: true
'@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@3.29.4)':
dependencies:
'@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@3.29.4)
c12: 2.0.1(magicast@0.3.5)
consola: 3.2.3
defu: 6.1.4
destr: 2.0.3
globby: 14.0.2
hash-sum: 2.0.0
ignore: 6.0.2
jiti: 2.4.1
klona: 2.0.6
knitwork: 1.1.0
mlly: 1.7.3
pathe: 1.1.2
pkg-types: 1.2.1
scule: 1.3.0
semver: 7.7.1
ufo: 1.5.4
unctx: 2.3.1
unimport: 3.14.4(rollup@3.29.4)
untyped: 1.5.1
transitivePeerDependencies:
- magicast
- rollup
- supports-color
optional: true
'@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1)':
dependencies:
'@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
@ -9425,35 +9456,7 @@ snapshots:
- rollup
- supports-color
'@nuxt/kit@3.14.1592(rollup@3.29.4)':
dependencies:
'@nuxt/schema': 3.14.1592(rollup@3.29.4)
c12: 2.0.1(magicast@0.3.5)
consola: 3.2.3
defu: 6.1.4
destr: 2.0.3
globby: 14.0.2
hash-sum: 2.0.0
ignore: 6.0.2
jiti: 2.4.1
klona: 2.0.6
knitwork: 1.1.0
mlly: 1.7.3
pathe: 1.1.2
pkg-types: 1.2.1
scule: 1.3.0
semver: 7.7.1
ufo: 1.5.4
unctx: 2.3.1
unimport: 3.14.4(rollup@3.29.4)
untyped: 1.5.1
transitivePeerDependencies:
- magicast
- rollup
- supports-color
optional: true
'@nuxt/schema@3.14.1592':
'@nuxt/schema@3.14.1592(magicast@0.3.5)':
dependencies:
c12: 2.0.1(magicast@0.3.5)
compatx: 0.1.8
@ -9474,6 +9477,27 @@ snapshots:
- supports-color
optional: true
'@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@3.29.4)':
dependencies:
c12: 2.0.1(magicast@0.3.5)
compatx: 0.1.8
consola: 3.2.3
defu: 6.1.4
hookable: 5.5.3
pathe: 1.1.2
pkg-types: 1.2.1
scule: 1.3.0
std-env: 3.8.0
ufo: 1.5.4
uncrypto: 0.1.3
unimport: 3.14.4(rollup@3.29.4)
untyped: 1.5.1
transitivePeerDependencies:
- magicast
- rollup
- supports-color
optional: true
'@nuxt/schema@3.14.1592(magicast@0.3.5)(rollup@4.28.1)':
dependencies:
c12: 2.0.1(magicast@0.3.5)
@ -9494,27 +9518,6 @@ snapshots:
- rollup
- supports-color
'@nuxt/schema@3.14.1592(rollup@3.29.4)':
dependencies:
c12: 2.0.1(magicast@0.3.5)
compatx: 0.1.8
consola: 3.2.3
defu: 6.1.4
hookable: 5.5.3
pathe: 1.1.2
pkg-types: 1.2.1
scule: 1.3.0
std-env: 3.8.0
ufo: 1.5.4
uncrypto: 0.1.3
unimport: 3.14.4(rollup@3.29.4)
untyped: 1.5.1
transitivePeerDependencies:
- magicast
- rollup
- supports-color
optional: true
'@nuxt/telemetry@2.6.0(magicast@0.3.5)(rollup@4.28.1)':
dependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
@ -9540,12 +9543,12 @@ snapshots:
- rollup
- supports-color
'@nuxt/vite-builder@3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.31.6)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4))':
'@nuxt/vite-builder@3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4))':
dependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
'@rollup/plugin-replace': 6.0.1(rollup@4.28.1)
'@vitejs/plugin-vue': 5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))
'@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))
'@vitejs/plugin-vue': 5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))
'@vitejs/plugin-vue-jsx': 4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))
autoprefixer: 10.4.20(postcss@8.4.49)
clear: 0.1.0
consola: 3.2.3
@ -9572,9 +9575,9 @@ snapshots:
ufo: 1.5.4
unenv: 1.10.0
unplugin: 1.16.0
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite-node: 2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite-plugin-checker: 0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue-tsc@2.1.6(typescript@5.5.4))
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vite-node: 2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vite-plugin-checker: 0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4))
vue: 3.5.13(typescript@5.5.4)
vue-bundle-renderer: 2.1.1
transitivePeerDependencies:
@ -9601,7 +9604,7 @@ snapshots:
'@nuxtjs/eslint-config-typescript@12.1.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)':
dependencies:
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
'@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
eslint: 9.13.0(jiti@2.4.1)
@ -9614,10 +9617,10 @@ snapshots:
- supports-color
- typescript
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))':
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))':
dependencies:
eslint: 9.13.0(jiti@2.4.1)
eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-node: 11.1.0(eslint@9.13.0(jiti@2.4.1))
@ -10182,12 +10185,6 @@ snapshots:
'@types/estree': 1.0.8
optional: true
'@types/eslint@9.6.0':
dependencies:
'@types/estree': 1.0.8
'@types/json-schema': 7.0.15
optional: true
'@types/eslint@9.6.1':
dependencies:
'@types/estree': 1.0.8
@ -10597,12 +10594,12 @@ snapshots:
'@formatjs/intl': 2.10.4(typescript@5.5.4)
intl-messageformat: 10.5.14
'@vintl/nuxt@1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)':
'@vintl/nuxt@1.9.2(@vue/compiler-core@3.5.13)(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)':
dependencies:
'@formatjs/intl': 2.10.4(typescript@5.5.4)
'@formatjs/intl-localematcher': 0.5.4
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
'@vintl/unplugin': 2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)
'@vintl/unplugin': 2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)
'@vintl/vintl': 4.4.1(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))
astring: 1.8.6
consola: 3.2.3
@ -10629,7 +10626,7 @@ snapshots:
- vue
- webpack
'@vintl/unplugin@1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)':
'@vintl/unplugin@1.5.2(@vue/compiler-core@3.5.13)(rollup@3.29.4)(vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)':
dependencies:
'@formatjs/cli-lib': 6.4.2(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4))
'@formatjs/icu-messageformat-parser': 2.7.8
@ -10640,7 +10637,7 @@ snapshots:
unplugin: 1.16.0
optionalDependencies:
rollup: 3.29.4
vite: 4.5.3(@types/node@22.4.1)
vite: 4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0)
webpack: 5.92.1
transitivePeerDependencies:
- '@glimmer/env'
@ -10653,7 +10650,7 @@ snapshots:
- ts-jest
- vue
'@vintl/unplugin@2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)':
'@vintl/unplugin@2.0.0(@vue/compiler-core@3.5.13)(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))(webpack@5.92.1)':
dependencies:
'@formatjs/cli-lib': 6.4.2(@vue/compiler-core@3.5.13)(vue@3.5.13(typescript@5.5.4))
'@formatjs/icu-messageformat-parser': 2.7.8
@ -10664,7 +10661,7 @@ snapshots:
unplugin: 1.16.0
optionalDependencies:
rollup: 4.28.1
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
webpack: 5.92.1
transitivePeerDependencies:
- '@glimmer/env'
@ -10688,24 +10685,24 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))':
'@vitejs/plugin-vue-jsx@4.1.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))':
dependencies:
'@babel/core': 7.26.0
'@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.26.0)
'@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vue: 3.5.13(typescript@5.5.4)
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))':
'@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))':
dependencies:
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vue: 3.5.13(typescript@5.5.4)
'@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))':
'@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))':
dependencies:
vite: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0)
vue: 3.5.13(typescript@5.5.4)
'@volar/kit@2.4.11(typescript@5.8.2)':
@ -10850,14 +10847,14 @@ snapshots:
'@vue/devtools-api@6.6.4': {}
'@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))':
'@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))':
dependencies:
'@vue/devtools-kit': 7.6.4
'@vue/devtools-shared': 7.6.7
mitt: 3.0.1
nanoid: 3.3.7
pathe: 1.1.2
vite-hot-client: 0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))
vite-hot-client: 0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))
vue: 3.5.13(typescript@5.5.4)
transitivePeerDependencies:
- vite
@ -11286,12 +11283,12 @@ snapshots:
astring@1.8.6: {}
astro-expressive-code@0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)):
astro-expressive-code@0.40.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)):
dependencies:
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)
rehype-expressive-code: 0.40.2
astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1):
astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1):
dependencies:
'@astrojs/compiler': 2.10.4
'@astrojs/internal-helpers': 0.6.0
@ -11343,8 +11340,8 @@ snapshots:
unist-util-visit: 5.0.0
unstorage: 1.15.0(db0@0.2.1)
vfile: 6.0.3
vite: 6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.6.1)
vitefu: 1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.6.1))
vite: 6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.42.0)(yaml@2.6.1)
vitefu: 1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.42.0)(yaml@2.6.1))
which-pm: 3.0.1
xxhash-wasm: 1.1.0
yargs-parser: 21.1.1
@ -11725,7 +11722,7 @@ snapshots:
croner@9.0.0: {}
cronstrue@2.52.0: {}
cronstrue@2.61.0: {}
cross-spawn@6.0.6:
dependencies:
@ -12276,10 +12273,10 @@ snapshots:
dependencies:
eslint: 9.13.0(jiti@2.4.1)
eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
dependencies:
eslint: 9.13.0(jiti@2.4.1)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.4.1))
@ -12305,7 +12302,7 @@ snapshots:
debug: 4.4.0(supports-color@9.4.0)
enhanced-resolve: 5.17.1
eslint: 9.13.0(jiti@2.4.1)
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))
fast-glob: 3.3.2
get-tsconfig: 4.7.5
@ -12317,7 +12314,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
@ -12460,14 +12457,14 @@ snapshots:
resolve: 1.22.8
semver: 6.3.1
eslint-plugin-prettier@5.2.1(@types/eslint@9.6.0)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))(prettier@3.3.2):
eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))(prettier@3.3.2):
dependencies:
eslint: 9.13.0(jiti@2.4.1)
prettier: 3.3.2
prettier-linter-helpers: 1.0.0
synckit: 0.9.1
optionalDependencies:
'@types/eslint': 9.6.0
'@types/eslint': 9.6.1
eslint-config-prettier: 9.1.0(eslint@9.13.0(jiti@2.4.1))
eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)):
@ -12894,6 +12891,14 @@ snapshots:
flattie@1.1.1: {}
floating-vue@5.2.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@3.29.4))(vue@3.5.13(typescript@5.5.4)):
dependencies:
'@floating-ui/dom': 1.1.1
vue: 3.5.13(typescript@5.5.4)
vue-resize: 2.0.0-alpha.1(vue@3.5.13(typescript@5.5.4))
optionalDependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@3.29.4)
floating-vue@5.2.2(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(vue@3.5.13(typescript@5.5.4)):
dependencies:
'@floating-ui/dom': 1.1.1
@ -12902,21 +12907,13 @@ snapshots:
optionalDependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
floating-vue@5.2.2(@nuxt/kit@3.14.1592(rollup@3.29.4))(vue@3.5.13(typescript@5.5.4)):
floating-vue@5.2.2(@nuxt/kit@3.14.1592(magicast@0.3.5))(vue@3.5.13(typescript@5.5.4)):
dependencies:
'@floating-ui/dom': 1.1.1
vue: 3.5.13(typescript@5.5.4)
vue-resize: 2.0.0-alpha.1(vue@3.5.13(typescript@5.5.4))
optionalDependencies:
'@nuxt/kit': 3.14.1592(rollup@3.29.4)
floating-vue@5.2.2(@nuxt/kit@3.14.1592)(vue@3.5.13(typescript@5.5.4)):
dependencies:
'@floating-ui/dom': 1.1.1
vue: 3.5.13(typescript@5.5.4)
vue-resize: 2.0.0-alpha.1(vue@3.5.13(typescript@5.5.4))
optionalDependencies:
'@nuxt/kit': 3.14.1592
'@nuxt/kit': 3.14.1592(magicast@0.3.5)
for-each@0.3.3:
dependencies:
@ -13686,7 +13683,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 22.4.1
'@types/node': 20.14.11
merge-stream: 2.0.0
supports-color: 8.1.1
optional: true
@ -14664,14 +14661,14 @@ snapshots:
nuxi@3.16.0: {}
nuxt@3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.31.6)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue-tsc@2.1.6(typescript@5.5.4)):
nuxt@3.14.1592(@parcel/watcher@2.4.1)(@types/node@20.14.11)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4)):
dependencies:
'@nuxt/devalue': 2.0.2
'@nuxt/devtools': 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue@3.5.13(typescript@5.5.4))
'@nuxt/devtools': 1.6.3(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue@3.5.13(typescript@5.5.4))
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
'@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
'@nuxt/telemetry': 2.6.0(magicast@0.3.5)(rollup@4.28.1)
'@nuxt/vite-builder': 3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.31.6)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4))
'@nuxt/vite-builder': 3.14.1592(@types/node@20.14.11)(eslint@8.57.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.28.1)(sass@1.77.6)(terser@5.42.0)(typescript@5.5.4)(vue-tsc@2.1.6(typescript@5.5.4))(vue@3.5.13(typescript@5.5.4))
'@unhead/dom': 1.11.13
'@unhead/shared': 1.11.13
'@unhead/ssr': 1.11.13
@ -16008,12 +16005,12 @@ snapshots:
standard-as-callback@2.1.0: {}
starlight-openapi@0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)))(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))(openapi-types@12.1.3):
starlight-openapi@0.14.0(@astrojs/markdown-remark@6.2.0)(@astrojs/starlight@0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)))(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))(openapi-types@12.1.3):
dependencies:
'@astrojs/markdown-remark': 6.2.0
'@astrojs/starlight': 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1))
'@astrojs/starlight': 0.32.2(astro@5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1))
'@readme/openapi-parser': 2.5.0(openapi-types@12.1.3)
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.31.6)(typescript@5.8.2)(yaml@2.6.1)
astro: 5.4.1(@types/node@22.4.1)(db0@0.2.1)(jiti@2.4.1)(rollup@4.34.9)(sass@1.77.6)(terser@5.42.0)(typescript@5.8.2)(yaml@2.6.1)
github-slugger: 2.0.0
url-template: 3.1.1
transitivePeerDependencies:
@ -16484,7 +16481,7 @@ snapshots:
unimport@3.14.4:
dependencies:
'@rollup/pluginutils': 5.1.3(rollup@3.29.4)
'@rollup/pluginutils': 5.1.3(rollup@4.28.1)
acorn: 8.14.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
@ -16723,17 +16720,17 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
vite-hot-client@0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)):
vite-hot-client@0.2.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)):
dependencies:
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vite-node@2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6):
vite-node@2.1.8(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0):
dependencies:
cac: 6.7.14
debug: 4.4.0(supports-color@9.4.0)
es-module-lexer: 1.5.4
pathe: 1.1.2
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
transitivePeerDependencies:
- '@types/node'
- less
@ -16745,7 +16742,7 @@ snapshots:
- supports-color
- terser
vite-plugin-checker@0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6))(vue-tsc@2.1.6(typescript@5.5.4)):
vite-plugin-checker@0.8.0(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0))(vue-tsc@2.1.6(typescript@5.5.4)):
dependencies:
'@babel/code-frame': 7.26.2
ansi-escapes: 4.3.2
@ -16757,7 +16754,7 @@ snapshots:
npm-run-path: 4.0.1
strip-ansi: 6.0.1
tiny-invariant: 1.3.3
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
vscode-languageclient: 7.0.0
vscode-languageserver: 7.0.0
vscode-languageserver-textdocument: 1.0.12
@ -16768,7 +16765,7 @@ snapshots:
typescript: 5.5.4
vue-tsc: 2.1.6(typescript@5.5.4)
vite-plugin-inspect@0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)):
vite-plugin-inspect@0.8.9(@nuxt/kit@3.14.1592(magicast@0.3.5)(rollup@4.28.1))(rollup@4.28.1)(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)):
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.3(rollup@4.28.1)
@ -16779,14 +16776,14 @@ snapshots:
perfect-debounce: 1.0.0
picocolors: 1.1.1
sirv: 3.0.0
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
optionalDependencies:
'@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.28.1)
transitivePeerDependencies:
- rollup
- supports-color
vite-plugin-vue-inspector@5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)):
vite-plugin-vue-inspector@5.1.3(vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)):
dependencies:
'@babel/core': 7.26.0
'@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.26.0)
@ -16797,7 +16794,7 @@ snapshots:
'@vue/compiler-dom': 3.5.13
kolorist: 1.8.0
magic-string: 0.30.14
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6)
vite: 5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0)
transitivePeerDependencies:
- supports-color
@ -16806,7 +16803,7 @@ snapshots:
svgo: 3.3.2
vue: 3.5.13(typescript@5.5.4)
vite@4.5.3(@types/node@22.4.1):
vite@4.5.3(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0):
dependencies:
esbuild: 0.18.20
postcss: 8.5.5
@ -16814,9 +16811,11 @@ snapshots:
optionalDependencies:
'@types/node': 22.4.1
fsevents: 2.3.3
sass: 1.77.6
terser: 5.42.0
optional: true
vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.31.6):
vite@5.4.11(@types/node@20.14.11)(sass@1.77.6)(terser@5.42.0):
dependencies:
esbuild: 0.21.5
postcss: 8.4.49
@ -16825,9 +16824,9 @@ snapshots:
'@types/node': 20.14.11
fsevents: 2.3.3
sass: 1.77.6
terser: 5.31.6
terser: 5.42.0
vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.31.6):
vite@5.4.11(@types/node@22.4.1)(sass@1.77.6)(terser@5.42.0):
dependencies:
esbuild: 0.21.5
postcss: 8.4.49
@ -16836,9 +16835,9 @@ snapshots:
'@types/node': 22.4.1
fsevents: 2.3.3
sass: 1.77.6
terser: 5.31.6
terser: 5.42.0
vite@6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.6.1):
vite@6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.42.0)(yaml@2.6.1):
dependencies:
esbuild: 0.25.0
postcss: 8.5.3
@ -16848,12 +16847,12 @@ snapshots:
fsevents: 2.3.3
jiti: 2.4.1
sass: 1.77.6
terser: 5.31.6
terser: 5.42.0
yaml: 2.6.1
vitefu@1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.6.1)):
vitefu@1.0.6(vite@6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.42.0)(yaml@2.6.1)):
optionalDependencies:
vite: 6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.6.1)
vite: 6.2.0(@types/node@22.4.1)(jiti@2.4.1)(sass@1.77.6)(terser@5.42.0)(yaml@2.6.1)
volar-service-css@0.0.62(@volar/language-service@2.4.11):
dependencies: