mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 16:30:57 +01:00
feat: add tasks page (#692)
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/form": "workspace:^0.1.0",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
IconMailForward,
|
||||
IconPlug,
|
||||
IconQuestionMark,
|
||||
IconReport,
|
||||
IconSettings,
|
||||
IconTool,
|
||||
IconUser,
|
||||
@@ -86,6 +87,11 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
|
||||
icon: IconLogs,
|
||||
href: "/manage/tools/logs",
|
||||
},
|
||||
{
|
||||
label: t("items.tools.items.tasks"),
|
||||
icon: IconReport,
|
||||
href: "/manage/tools/tasks",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { ActionIcon, Badge, Card, Group, Stack, Text } from "@mantine/core";
|
||||
import { useListState } from "@mantine/hooks";
|
||||
import { IconPlayerPlay } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useTimeAgo } from "@homarr/common";
|
||||
import type { TaskStatus } from "@homarr/cron-job-status";
|
||||
import type { TranslationKeys } from "@homarr/translation";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
interface JobsListProps {
|
||||
initialJobs: RouterOutputs["cronJobs"]["getJobs"];
|
||||
}
|
||||
|
||||
interface JobState {
|
||||
job: JobsListProps["initialJobs"][number];
|
||||
status: TaskStatus | null;
|
||||
}
|
||||
|
||||
export const JobsList = ({ initialJobs }: JobsListProps) => {
|
||||
const t = useScopedI18n("management.page.tool.tasks");
|
||||
const [jobs, handlers] = useListState<JobState>(
|
||||
initialJobs.map((job) => ({
|
||||
job,
|
||||
status: null,
|
||||
})),
|
||||
);
|
||||
clientApi.cronJobs.subscribeToStatusUpdates.useSubscription(undefined, {
|
||||
onData: (data) => {
|
||||
const jobByName = jobs.find((job) => job.job.name === data.name);
|
||||
if (!jobByName) {
|
||||
return;
|
||||
}
|
||||
handlers.applyWhere(
|
||||
(job) => job.job.name === data.name,
|
||||
(job) => ({ ...job, status: data }),
|
||||
);
|
||||
},
|
||||
});
|
||||
const { mutateAsync } = clientApi.cronJobs.triggerJob.useMutation();
|
||||
const handleJobTrigger = React.useCallback(
|
||||
async (job: JobState) => {
|
||||
if (job.status?.status === "running") {
|
||||
return;
|
||||
}
|
||||
await mutateAsync(job.job.name);
|
||||
},
|
||||
[mutateAsync],
|
||||
);
|
||||
return (
|
||||
<Stack>
|
||||
{jobs.map((job) => (
|
||||
<Card key={job.job.name}>
|
||||
<Group justify={"space-between"} gap={"md"}>
|
||||
<Stack gap={0}>
|
||||
<Group>
|
||||
<Text>{t(`${job.job.name}.label` as TranslationKeys)}</Text>
|
||||
{job.status?.status === "idle" && <Badge variant="default">{t("status.idle")}</Badge>}
|
||||
{job.status?.status === "running" && <Badge color="green">{t("status.running")}</Badge>}
|
||||
{job.status?.lastExecutionStatus === "error" && <Badge color="red">{t("status.error")}</Badge>}
|
||||
</Group>
|
||||
{job.status && <TimeAgo timestamp={job.status.lastExecutionTimestamp} />}
|
||||
</Stack>
|
||||
|
||||
<ActionIcon
|
||||
onClick={() => handleJobTrigger(job)}
|
||||
disabled={job.status?.status === "running"}
|
||||
variant={"default"}
|
||||
size={"xl"}
|
||||
radius={"xl"}
|
||||
>
|
||||
<IconPlayerPlay stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const TimeAgo = ({ timestamp }: { timestamp: string }) => {
|
||||
const timeAgo = useTimeAgo(new Date(timestamp));
|
||||
|
||||
return (
|
||||
<Text size={"sm"} c={"dimmed"}>
|
||||
{timeAgo}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
25
apps/nextjs/src/app/[locale]/manage/tools/tasks/page.tsx
Normal file
25
apps/nextjs/src/app/[locale]/manage/tools/tasks/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Box, Title } from "@mantine/core";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
import { getScopedI18n } from "@homarr/translation/server";
|
||||
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { JobsList } from "./_components/jobs-list";
|
||||
|
||||
export async function generateMetadata() {
|
||||
const t = await getScopedI18n("management");
|
||||
|
||||
return {
|
||||
title: createMetaTitle(t("metaTitle")),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function TasksPage() {
|
||||
const jobs = await api.cronJobs.getJobs();
|
||||
return (
|
||||
<Box>
|
||||
<Title mb={"md"}>Tasks</Title>
|
||||
<JobsList initialJobs={jobs} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -32,6 +32,8 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-runner": "workspace:^0.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"superjson": "2.2.1",
|
||||
"undici": "6.19.2"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { client } from "./queues";
|
||||
|
||||
export const queueClient = client;
|
||||
@@ -1,17 +0,0 @@
|
||||
import { iconsUpdaterJob } from "~/jobs/icons-updater";
|
||||
import { smartHomeEntityStateJob } from "~/jobs/integrations/home-assistant";
|
||||
import { analyticsJob } from "./jobs/analytics";
|
||||
import { pingJob } from "./jobs/ping";
|
||||
import { queuesJob } from "./jobs/queue";
|
||||
import { createCronJobGroup } from "./lib/jobs";
|
||||
|
||||
export const jobs = createCronJobGroup({
|
||||
// Add your jobs here:
|
||||
analytics: analyticsJob,
|
||||
iconsUpdater: iconsUpdaterJob,
|
||||
ping: pingJob,
|
||||
smartHomeEntityState: smartHomeEntityStateJob,
|
||||
|
||||
// This job is used to process queues.
|
||||
queues: queuesJob,
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
import { queueWorkerAsync } from "../lib/queue/worker";
|
||||
|
||||
// This job processes queues, it runs every minute.
|
||||
export const queuesJob = createCronJob("queues", EVERY_MINUTE).withCallback(async () => {
|
||||
await queueWorkerAsync();
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import { objectEntries, objectKeys } from "@homarr/common";
|
||||
import type { MaybePromise } from "@homarr/common/types";
|
||||
import { queueChannel } from "@homarr/redis";
|
||||
import type { z } from "@homarr/validation";
|
||||
|
||||
import type { createQueue } from "./creator";
|
||||
|
||||
interface Queue<TInput extends z.ZodType = z.ZodType> {
|
||||
name: string;
|
||||
callback: (input: z.infer<TInput>) => MaybePromise<void>;
|
||||
inputValidator: TInput;
|
||||
}
|
||||
|
||||
type Queues = Record<string, ReturnType<ReturnType<typeof createQueue>["withCallback"]>>;
|
||||
|
||||
export const createQueueClient = <TQueues extends Queues>(queues: TQueues) => {
|
||||
const queueRegistry = new Map<string, Queue>();
|
||||
for (const [name, queue] of objectEntries(queues)) {
|
||||
if (typeof name !== "string") continue;
|
||||
queueRegistry.set(name, {
|
||||
name,
|
||||
callback: queue._callback,
|
||||
inputValidator: queue._input,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
queueRegistry,
|
||||
client: objectKeys(queues).reduce(
|
||||
(acc, name) => {
|
||||
acc[name] = async (data: z.infer<TQueues[typeof name]["_input"]>, options) => {
|
||||
if (typeof name !== "string") return;
|
||||
const queue = queueRegistry.get(name);
|
||||
if (!queue) return;
|
||||
|
||||
await queueChannel.addAsync({
|
||||
name,
|
||||
data,
|
||||
executionDate: typeof options === "object" && options.executionDate ? options.executionDate : new Date(),
|
||||
});
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as {
|
||||
[key in keyof TQueues]: (
|
||||
data: z.infer<TQueues[key]["_input"]>,
|
||||
props: {
|
||||
executionDate?: Date;
|
||||
} | void,
|
||||
) => Promise<void>;
|
||||
},
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { MaybePromise } from "@homarr/common/types";
|
||||
import type { z } from "@homarr/validation";
|
||||
|
||||
export const createQueue = <TInput extends z.ZodType>(input: TInput) => {
|
||||
return {
|
||||
withCallback: (callback: (data: z.infer<TInput>) => MaybePromise<void>) => {
|
||||
return {
|
||||
_input: input,
|
||||
_callback: callback,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { logger } from "@homarr/log";
|
||||
import { queueChannel } from "@homarr/redis";
|
||||
|
||||
import { queueRegistry } from "~/queues";
|
||||
|
||||
/**
|
||||
* This function reads all the queue executions that are due and processes them.
|
||||
* Those executions are stored in the redis queue channel.
|
||||
*/
|
||||
export const queueWorkerAsync = async () => {
|
||||
const now = new Date();
|
||||
const executions = await queueChannel.filterAsync((item) => {
|
||||
return item.executionDate < now;
|
||||
});
|
||||
for (const execution of executions) {
|
||||
const queue = queueRegistry.get(execution.name);
|
||||
if (!queue) continue;
|
||||
|
||||
try {
|
||||
await queue.callback(execution.data);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`apps/tasks/src/lib/queue/worker.ts: Error occured when executing queue ${execution.name} with data`,
|
||||
execution.data,
|
||||
"and error:",
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
await queueChannel.markAsDoneAsync(execution._id);
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
// This import has to be the first import in the file so that the agent is overridden before any other modules are imported.
|
||||
import "./undici-log-agent-override";
|
||||
|
||||
import { jobs } from "./jobs";
|
||||
import { registerCronJobRunner } from "@homarr/cron-job-runner";
|
||||
import { jobGroup } from "@homarr/cron-jobs";
|
||||
|
||||
import { seedServerSettingsAsync } from "./seed-server-settings";
|
||||
|
||||
void (async () => {
|
||||
await jobs.startAllAsync();
|
||||
registerCronJobRunner();
|
||||
await jobGroup.startAllAsync();
|
||||
await seedServerSettingsAsync();
|
||||
})();
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { createQueueClient } from "./lib/queue/client";
|
||||
import { testQueue } from "./queues/test";
|
||||
|
||||
export const { client, queueRegistry } = createQueueClient({
|
||||
// Add your queues here
|
||||
test: testQueue,
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
import { createQueue } from "~/lib/queue/creator";
|
||||
|
||||
export const testQueue = createQueue(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
).withCallback(({ id }) => {
|
||||
console.log(`Test queue with id ${id}`);
|
||||
});
|
||||
@@ -21,6 +21,9 @@
|
||||
"dependencies": {
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-runner": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { appRouter as innerAppRouter } from "./router/app";
|
||||
import { boardRouter } from "./router/board";
|
||||
import { cronJobsRouter } from "./router/cron-jobs";
|
||||
import { dockerRouter } from "./router/docker/docker-router";
|
||||
import { groupRouter } from "./router/group";
|
||||
import { homeRouter } from "./router/home";
|
||||
@@ -27,6 +28,7 @@ export const appRouter = createTRPCRouter({
|
||||
home: homeRouter,
|
||||
docker: dockerRouter,
|
||||
serverSettings: serverSettingsRouter,
|
||||
cronJobs: cronJobsRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
34
packages/api/src/router/cron-jobs.ts
Normal file
34
packages/api/src/router/cron-jobs.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import { jobNameSchema, triggerCronJobAsync } from "@homarr/cron-job-runner";
|
||||
import type { TaskStatus } from "@homarr/cron-job-status";
|
||||
import { createCronJobStatusChannel } from "@homarr/cron-job-status";
|
||||
import { jobGroup } from "@homarr/cron-jobs";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
export const cronJobsRouter = createTRPCRouter({
|
||||
triggerJob: publicProcedure.input(jobNameSchema).mutation(async ({ input }) => {
|
||||
await triggerCronJobAsync(input);
|
||||
}),
|
||||
getJobs: publicProcedure.query(() => {
|
||||
const registry = jobGroup.getJobRegistry();
|
||||
return [...registry.values()].map((job) => ({
|
||||
name: job.name,
|
||||
expression: job.cronExpression,
|
||||
}));
|
||||
}),
|
||||
subscribeToStatusUpdates: publicProcedure.subscription(() => {
|
||||
return observable<TaskStatus>((emit) => {
|
||||
for (const job of jobGroup.getJobRegistry().values()) {
|
||||
const channel = createCronJobStatusChannel(job.name);
|
||||
channel.subscribe((data) => {
|
||||
emit.next(data);
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("A tRPC client has connected to the cron job status updates procedure");
|
||||
});
|
||||
}),
|
||||
});
|
||||
9
packages/cron-job-runner/eslint.config.js
Normal file
9
packages/cron-job-runner/eslint.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
1
packages/cron-job-runner/index.ts
Normal file
1
packages/cron-job-runner/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
36
packages/cron-job-runner/package.json
Normal file
36
packages/cron-job-runner/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@homarr/cron-job-runner",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.6.0",
|
||||
"typescript": "^5.5.2"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
}
|
||||
27
packages/cron-job-runner/src/index.ts
Normal file
27
packages/cron-job-runner/src/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { JobGroupKeys } from "@homarr/cron-jobs";
|
||||
import { jobGroup } from "@homarr/cron-jobs";
|
||||
|
||||
import { createSubPubChannel } from "../../redis/src/lib/channel";
|
||||
import { zodEnumFromArray } from "../../validation/src/enums";
|
||||
|
||||
const cronJobRunnerChannel = createSubPubChannel<JobGroupKeys>("cron-job-runner", { persist: false });
|
||||
|
||||
/**
|
||||
* Registers the cron job runner to listen to the Redis PubSub channel.
|
||||
*/
|
||||
export const registerCronJobRunner = () => {
|
||||
cronJobRunnerChannel.subscribe((jobName) => {
|
||||
jobGroup.runManually(jobName);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers a cron job to run immediately.
|
||||
* This works over the Redis PubSub channel.
|
||||
* @param jobName name of the job to be triggered
|
||||
*/
|
||||
export const triggerCronJobAsync = async (jobName: JobGroupKeys) => {
|
||||
await cronJobRunnerChannel.publishAsync(jobName);
|
||||
};
|
||||
|
||||
export const jobNameSchema = zodEnumFromArray(jobGroup.getKeys());
|
||||
8
packages/cron-job-runner/tsconfig.json
Normal file
8
packages/cron-job-runner/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
9
packages/cron-job-status/eslint.config.js
Normal file
9
packages/cron-job-status/eslint.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
1
packages/cron-job-status/index.ts
Normal file
1
packages/cron-job-status/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
35
packages/cron-job-status/package.json
Normal file
35
packages/cron-job-status/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@homarr/cron-job-status",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./publisher": "./src/publisher.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/redis": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.6.0",
|
||||
"typescript": "^5.5.2"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
}
|
||||
10
packages/cron-job-status/src/index.ts
Normal file
10
packages/cron-job-status/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSubPubChannel } from "../../redis/src/lib/channel";
|
||||
|
||||
export interface TaskStatus {
|
||||
name: string;
|
||||
status: "running" | "idle";
|
||||
lastExecutionTimestamp: string;
|
||||
lastExecutionStatus: "success" | "error" | null;
|
||||
}
|
||||
|
||||
export const createCronJobStatusChannel = (name: string) => createSubPubChannel<TaskStatus>(`cron-job-status:${name}`);
|
||||
38
packages/cron-job-status/src/publisher.ts
Normal file
38
packages/cron-job-status/src/publisher.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createCronJobStatusChannel } from ".";
|
||||
|
||||
export const beforeCallbackAsync = async (name: string) => {
|
||||
const channel = createCronJobStatusChannel(name);
|
||||
|
||||
const previous = await channel.getLastDataAsync();
|
||||
|
||||
await channel.publishAsync({
|
||||
name,
|
||||
lastExecutionStatus: previous?.lastExecutionStatus ?? null,
|
||||
lastExecutionTimestamp: new Date().toISOString(),
|
||||
status: "running",
|
||||
});
|
||||
};
|
||||
|
||||
export const onCallbackSuccessAsync = async (name: string) => {
|
||||
const channel = createCronJobStatusChannel(name);
|
||||
|
||||
const previous = await channel.getLastDataAsync();
|
||||
await channel.publishAsync({
|
||||
name,
|
||||
lastExecutionStatus: "success",
|
||||
lastExecutionTimestamp: previous?.lastExecutionTimestamp ?? new Date().toISOString(),
|
||||
status: "idle",
|
||||
});
|
||||
};
|
||||
|
||||
export const onCallbackErrorAsync = async (name: string, _error: unknown) => {
|
||||
const channel = createCronJobStatusChannel(name);
|
||||
|
||||
const previous = await channel.getLastDataAsync();
|
||||
await channel.publishAsync({
|
||||
name,
|
||||
lastExecutionStatus: "error",
|
||||
lastExecutionTimestamp: previous?.lastExecutionTimestamp ?? new Date().toISOString(),
|
||||
status: "idle",
|
||||
});
|
||||
};
|
||||
8
packages/cron-job-status/tsconfig.json
Normal file
8
packages/cron-job-status/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import { objectEntries, objectKeys } from "@homarr/common";
|
||||
|
||||
import type { JobCallback } from "./creator";
|
||||
import type { Logger } from "./logger";
|
||||
@@ -43,6 +43,13 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
|
||||
job.scheduledTask.start();
|
||||
}
|
||||
},
|
||||
runManually: (name: keyof TJobs) => {
|
||||
const job = jobRegistry.get(name as string);
|
||||
if (!job) return;
|
||||
|
||||
options.logger.logInfo(`Running schedule cron job ${job.name} manually.`);
|
||||
job.scheduledTask.now();
|
||||
},
|
||||
stop: (name: keyof TJobs) => {
|
||||
const job = jobRegistry.get(name as string);
|
||||
if (!job) return;
|
||||
@@ -59,6 +66,9 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
|
||||
getJobRegistry() {
|
||||
return jobRegistry as Map<TAllowedNames, ReturnType<JobCallback<TAllowedNames, TAllowedNames>>>;
|
||||
},
|
||||
getKeys() {
|
||||
return objectKeys(jobs);
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
9
packages/cron-jobs/eslint.config.js
Normal file
9
packages/cron-jobs/eslint.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [
|
||||
{
|
||||
ignores: [],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
1
packages/cron-jobs/index.ts
Normal file
1
packages/cron-jobs/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
45
packages/cron-jobs/package.json
Normal file
45
packages/cron-jobs/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@homarr/cron-jobs",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-status": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.6.0",
|
||||
"typescript": "^5.5.2"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config"
|
||||
}
|
||||
14
packages/cron-jobs/src/index.ts
Normal file
14
packages/cron-jobs/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { analyticsJob } from "./jobs/analytics";
|
||||
import { iconsUpdaterJob } from "./jobs/icons-updater";
|
||||
import { smartHomeEntityStateJob } from "./jobs/integrations/home-assistant";
|
||||
import { pingJob } from "./jobs/ping";
|
||||
import { createCronJobGroup } from "./lib";
|
||||
|
||||
export const jobGroup = createCronJobGroup({
|
||||
analytics: analyticsJob,
|
||||
iconsUpdater: iconsUpdaterJob,
|
||||
ping: pingJob,
|
||||
smartHomeEntityState: smartHomeEntityStateJob,
|
||||
});
|
||||
|
||||
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
||||
@@ -4,9 +4,9 @@ import { sendServerAnalyticsAsync } from "@homarr/analytics";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db, eq } from "@homarr/db";
|
||||
import { serverSettings } from "@homarr/db/schema/sqlite";
|
||||
import type { defaultServerSettings } from "@homarr/server-settings";
|
||||
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
import type { defaultServerSettings } from "../../../../packages/server-settings";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const analyticsJob = createCronJob("analytics", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
@@ -7,7 +7,7 @@ import { iconRepositories, icons } from "@homarr/db/schema/sqlite";
|
||||
import { fetchIconsAsync } from "@homarr/icons";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
@@ -7,9 +7,10 @@ import { items } from "@homarr/db/schema/sqlite";
|
||||
import { HomeAssistantIntegration } from "@homarr/integrations";
|
||||
import { logger } from "@homarr/log";
|
||||
import { homeAssistantEntityState } from "@homarr/redis";
|
||||
import type { WidgetComponentProps } from "@homarr/widgets";
|
||||
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
// This import is done that way to avoid circular dependencies.
|
||||
import type { WidgetComponentProps } from "../../../../widgets";
|
||||
import { createCronJob } from "../../lib";
|
||||
|
||||
export const smartHomeEntityStateJob = createCronJob("smartHomeEntityState", EVERY_MINUTE).withCallback(async () => {
|
||||
const itemsForIntegration = await db.query.items.findMany({
|
||||
@@ -3,7 +3,7 @@ import { logger } from "@homarr/log";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { createCronJob } from "~/lib/jobs";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const pingJob = createCronJob("ping", EVERY_MINUTE).withCallback(async () => {
|
||||
const urls = await pingUrlChannel.getAllAsync();
|
||||
@@ -1,6 +1,8 @@
|
||||
import { beforeCallbackAsync, onCallbackErrorAsync, onCallbackSuccessAsync } from "@homarr/cron-job-status/publisher";
|
||||
import { createCronJobFunctions } from "@homarr/cron-jobs-core";
|
||||
import type { Logger } from "@homarr/cron-jobs-core/logger";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
|
||||
class WinstonCronJobLogger implements Logger {
|
||||
logDebug(message: string) {
|
||||
@@ -16,6 +18,11 @@ class WinstonCronJobLogger implements Logger {
|
||||
}
|
||||
}
|
||||
|
||||
export const { createCronJob, createCronJobGroup } = createCronJobFunctions({
|
||||
export const { createCronJob, createCronJobGroup } = createCronJobFunctions<
|
||||
keyof TranslationObject["management"]["page"]["tool"]["tasks"]["job"]
|
||||
>({
|
||||
logger: new WinstonCronJobLogger(),
|
||||
beforeCallback: beforeCallbackAsync,
|
||||
onCallbackSuccess: onCallbackSuccessAsync,
|
||||
onCallbackError: onCallbackErrorAsync,
|
||||
});
|
||||
8
packages/cron-jobs/tsconfig.json
Normal file
8
packages/cron-jobs/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -11,6 +11,6 @@ export const integrationCreatorByKind = (kind: IntegrationKind, integration: Int
|
||||
case "homeAssistant":
|
||||
return new HomeAssistantIntegration(integration);
|
||||
default:
|
||||
throw new Error(`Unknown integration kind ${kind}`);
|
||||
throw new Error(`Unknown integration kind ${kind}. Did you forget to add it to the integration creator?`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ const lastDataClient = createRedisConnection();
|
||||
* @param name name of the channel
|
||||
* @returns pub/sub channel object
|
||||
*/
|
||||
export const createSubPubChannel = <TData>(name: string) => {
|
||||
export const createSubPubChannel = <TData>(name: string, { persist }: { persist: boolean } = { persist: true }) => {
|
||||
const lastChannelName = `pubSub:last:${name}`;
|
||||
const channelName = `pubSub:${name}`;
|
||||
return {
|
||||
@@ -23,11 +23,13 @@ export const createSubPubChannel = <TData>(name: string) => {
|
||||
* @param callback callback function to be called when new data is published
|
||||
*/
|
||||
subscribe: (callback: (data: TData) => void) => {
|
||||
void lastDataClient.get(lastChannelName).then((data) => {
|
||||
if (data) {
|
||||
callback(superjson.parse(data));
|
||||
}
|
||||
});
|
||||
if (persist) {
|
||||
void lastDataClient.get(lastChannelName).then((data) => {
|
||||
if (data) {
|
||||
callback(superjson.parse(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
void subscriber.subscribe(channelName, (err) => {
|
||||
if (!err) {
|
||||
return;
|
||||
@@ -45,9 +47,15 @@ export const createSubPubChannel = <TData>(name: string) => {
|
||||
* @param data data to be published
|
||||
*/
|
||||
publishAsync: async (data: TData) => {
|
||||
await lastDataClient.set(lastChannelName, superjson.stringify(data));
|
||||
if (persist) {
|
||||
await lastDataClient.set(lastChannelName, superjson.stringify(data));
|
||||
}
|
||||
await publisher.publish(channelName, superjson.stringify(data));
|
||||
},
|
||||
getLastDataAsync: async () => {
|
||||
const data = await lastDataClient.get(lastChannelName);
|
||||
return data ? superjson.parse<TData>(data) : null;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1252,6 +1252,7 @@ export default {
|
||||
items: {
|
||||
docker: "Docker",
|
||||
logs: "Logs",
|
||||
tasks: "Tasks",
|
||||
},
|
||||
},
|
||||
settings: "Settings",
|
||||
@@ -1451,6 +1452,30 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
tool: {
|
||||
tasks: {
|
||||
title: "Tasks",
|
||||
status: {
|
||||
idle: "Idle",
|
||||
running: "Running",
|
||||
error: "Error",
|
||||
},
|
||||
job: {
|
||||
iconsUpdater: {
|
||||
label: "Icons Updater",
|
||||
},
|
||||
analytics: {
|
||||
label: "Analytics",
|
||||
},
|
||||
smartHomeEntityState: {
|
||||
label: "Smart Home Entity State",
|
||||
},
|
||||
ping: {
|
||||
label: "Pings",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
about: {
|
||||
version: "Version {version}",
|
||||
text: "Homarr is a community driven open source project that is being maintained by volunteers. Thanks to these people, Homarr has been a growing project since 2021. Our team is working completely remote from many different countries on Homarr in their leisure time for no compensation.",
|
||||
|
||||
181
pnpm-lock.yaml
generated
181
pnpm-lock.yaml
generated
@@ -62,6 +62,9 @@ importers:
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/common
|
||||
'@homarr/cron-job-status':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/cron-job-status
|
||||
'@homarr/db':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/db
|
||||
@@ -258,6 +261,12 @@ importers:
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/common
|
||||
'@homarr/cron-job-runner':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/cron-job-runner
|
||||
'@homarr/cron-jobs':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/cron-jobs
|
||||
'@homarr/cron-jobs-core':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/cron-jobs-core
|
||||
@@ -429,6 +438,15 @@ importers:
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
'@homarr/cron-job-runner':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../cron-job-runner
|
||||
'@homarr/cron-job-status':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../cron-job-status
|
||||
'@homarr/cron-jobs':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../cron-jobs
|
||||
'@homarr/db':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../db
|
||||
@@ -589,6 +607,111 @@ importers:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
|
||||
packages/cron-job-runner:
|
||||
dependencies:
|
||||
'@homarr/cron-jobs':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../cron-jobs
|
||||
'@homarr/log':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../log
|
||||
'@homarr/redis':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../redis
|
||||
devDependencies:
|
||||
'@homarr/eslint-config':
|
||||
specifier: workspace:^0.2.0
|
||||
version: link:../../tooling/eslint
|
||||
'@homarr/prettier-config':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/prettier
|
||||
'@homarr/tsconfig':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/typescript
|
||||
eslint:
|
||||
specifier: ^9.6.0
|
||||
version: 9.6.0
|
||||
typescript:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
|
||||
packages/cron-job-status:
|
||||
dependencies:
|
||||
'@homarr/redis':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../redis
|
||||
devDependencies:
|
||||
'@homarr/eslint-config':
|
||||
specifier: workspace:^0.2.0
|
||||
version: link:../../tooling/eslint
|
||||
'@homarr/prettier-config':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/prettier
|
||||
'@homarr/tsconfig':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/typescript
|
||||
eslint:
|
||||
specifier: ^9.6.0
|
||||
version: 9.6.0
|
||||
typescript:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
|
||||
packages/cron-jobs:
|
||||
dependencies:
|
||||
'@homarr/analytics':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../analytics
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
'@homarr/cron-job-status':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../cron-job-status
|
||||
'@homarr/cron-jobs-core':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../cron-jobs-core
|
||||
'@homarr/db':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../db
|
||||
'@homarr/icons':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../icons
|
||||
'@homarr/integrations':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../integrations
|
||||
'@homarr/log':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../log
|
||||
'@homarr/ping':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../ping
|
||||
'@homarr/redis':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../redis
|
||||
'@homarr/server-settings':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../server-settings
|
||||
'@homarr/translation':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../translation
|
||||
devDependencies:
|
||||
'@homarr/eslint-config':
|
||||
specifier: workspace:^0.2.0
|
||||
version: link:../../tooling/eslint
|
||||
'@homarr/prettier-config':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/prettier
|
||||
'@homarr/tsconfig':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/typescript
|
||||
eslint:
|
||||
specifier: ^9.6.0
|
||||
version: 9.6.0
|
||||
typescript:
|
||||
specifier: ^5.5.2
|
||||
version: 5.5.2
|
||||
|
||||
packages/cron-jobs-core:
|
||||
dependencies:
|
||||
'@homarr/common':
|
||||
@@ -1380,10 +1503,18 @@ packages:
|
||||
resolution: {integrity: sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-string-parser@7.23.4':
|
||||
resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-string-parser@7.24.6':
|
||||
resolution: {integrity: sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.22.20':
|
||||
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.24.6':
|
||||
resolution: {integrity: sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -1400,6 +1531,11 @@ packages:
|
||||
resolution: {integrity: sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.24.0':
|
||||
resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.24.6':
|
||||
resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -1433,6 +1569,10 @@ packages:
|
||||
resolution: {integrity: sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.24.0':
|
||||
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.24.6':
|
||||
resolution: {integrity: sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2900,6 +3040,11 @@ packages:
|
||||
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@8.11.3:
|
||||
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
acorn@8.12.0:
|
||||
resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -6545,7 +6690,7 @@ snapshots:
|
||||
'@babel/traverse': 7.24.6
|
||||
'@babel/types': 7.24.6
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.5
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -6601,8 +6746,12 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.24.6
|
||||
|
||||
'@babel/helper-string-parser@7.23.4': {}
|
||||
|
||||
'@babel/helper-string-parser@7.24.6': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.22.20': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.24.6': {}
|
||||
|
||||
'@babel/helper-validator-option@7.24.6': {}
|
||||
@@ -6619,6 +6768,10 @@ snapshots:
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.0.0
|
||||
|
||||
'@babel/parser@7.24.0':
|
||||
dependencies:
|
||||
'@babel/types': 7.24.6
|
||||
|
||||
'@babel/parser@7.24.6':
|
||||
dependencies:
|
||||
'@babel/types': 7.24.6
|
||||
@@ -6663,6 +6816,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/types@7.24.0':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.23.4
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
'@babel/types@7.24.6':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.24.6
|
||||
@@ -8050,6 +8209,8 @@ snapshots:
|
||||
|
||||
acorn-walk@8.3.2: {}
|
||||
|
||||
acorn@8.11.3: {}
|
||||
|
||||
acorn@8.12.0: {}
|
||||
|
||||
aes-decrypter@4.0.1:
|
||||
@@ -8777,7 +8938,7 @@ snapshots:
|
||||
|
||||
docker-modem@5.0.3:
|
||||
dependencies:
|
||||
debug: 4.3.5
|
||||
debug: 4.3.4
|
||||
readable-stream: 3.6.2
|
||||
split-ca: 1.0.1
|
||||
ssh2: 1.15.0
|
||||
@@ -9027,7 +9188,7 @@ snapshots:
|
||||
|
||||
esbuild-register@3.5.0(esbuild@0.19.12):
|
||||
dependencies:
|
||||
debug: 4.3.5
|
||||
debug: 4.3.4
|
||||
esbuild: 0.19.12
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -9640,7 +9801,7 @@ snapshots:
|
||||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.0
|
||||
debug: 4.3.5
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -9654,7 +9815,7 @@ snapshots:
|
||||
https-proxy-agent@7.0.4:
|
||||
dependencies:
|
||||
agent-base: 7.1.0
|
||||
debug: 4.3.5
|
||||
debug: 4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -9927,7 +10088,7 @@ snapshots:
|
||||
istanbul-lib-source-maps@5.0.4:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
debug: 4.3.5
|
||||
debug: 4.3.4
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -10169,8 +10330,8 @@ snapshots:
|
||||
|
||||
magicast@0.3.3:
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.6
|
||||
'@babel/types': 7.24.6
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
source-map-js: 1.2.0
|
||||
|
||||
make-dir@3.1.0:
|
||||
@@ -10270,7 +10431,7 @@ snapshots:
|
||||
|
||||
mlly@1.5.0:
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
acorn: 8.11.3
|
||||
pathe: 1.1.2
|
||||
pkg-types: 1.0.3
|
||||
ufo: 1.4.0
|
||||
@@ -11617,7 +11778,7 @@ snapshots:
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 20.14.9
|
||||
acorn: 8.12.0
|
||||
acorn: 8.11.3
|
||||
acorn-walk: 8.3.2
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
|
||||
Reference in New Issue
Block a user