mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-27 00:40:58 +01:00
chore(release): automatic release v1.19.1
This commit is contained in:
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -31,6 +31,7 @@ body:
|
||||
label: Version
|
||||
description: What version of Homarr are you running?
|
||||
options:
|
||||
- 1.19.0
|
||||
- 1.18.0
|
||||
- 1.17.0
|
||||
- 1.16.0
|
||||
|
||||
@@ -48,17 +48,17 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^7.17.7",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/dropzone": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/modals": "^7.17.7",
|
||||
"@mantine/tiptap": "^7.17.7",
|
||||
"@mantine/colors-generator": "^8.0.0",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@mantine/dropzone": "^8.0.0",
|
||||
"@mantine/hooks": "^8.0.0",
|
||||
"@mantine/modals": "^8.0.0",
|
||||
"@mantine/tiptap": "^8.0.0",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tanstack/react-query": "^5.75.1",
|
||||
"@tanstack/react-query-devtools": "^5.75.1",
|
||||
"@tanstack/react-query-next-experimental": "^5.75.1",
|
||||
"@tanstack/react-query": "^5.75.7",
|
||||
"@tanstack/react-query-devtools": "^5.75.7",
|
||||
"@tanstack/react-query-next-experimental": "^5.75.7",
|
||||
"@trpc/client": "^11.1.2",
|
||||
"@trpc/next": "^11.1.2",
|
||||
"@trpc/react-query": "^11.1.2",
|
||||
@@ -72,33 +72,33 @@
|
||||
"dotenv": "^16.5.0",
|
||||
"flag-icons": "^7.3.2",
|
||||
"glob": "^11.0.2",
|
||||
"jotai": "^2.12.3",
|
||||
"jotai": "^2.12.4",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.87.0",
|
||||
"superjson": "2.2.2",
|
||||
"swagger-ui-react": "^5.21.0",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.15.3",
|
||||
"@types/node": "^22.15.17",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/react": "19.1.3",
|
||||
"@types/react-dom": "19.1.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"node-loader": "^2.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.15.3",
|
||||
"@types/node": "^22.15.17",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"tsx": "4.19.4",
|
||||
"ws": "^8.18.1"
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -38,20 +38,20 @@
|
||||
"@semantic-release/github": "^11.0.2",
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@semantic-release/release-notes-generator": "^14.0.3",
|
||||
"@turbo/gen": "^2.5.2",
|
||||
"@turbo/gen": "^2.5.3",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"@vitest/coverage-v8": "^3.1.2",
|
||||
"@vitest/ui": "^3.1.2",
|
||||
"@vitest/coverage-v8": "^3.1.3",
|
||||
"@vitest/ui": "^3.1.3",
|
||||
"conventional-changelog-conventionalcommits": "^8.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"semantic-release": "^24.2.3",
|
||||
"testcontainers": "^10.25.0",
|
||||
"turbo": "^2.5.2",
|
||||
"turbo": "^2.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.2"
|
||||
"vitest": "^3.1.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.10.0",
|
||||
"engines": {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,25 +40,25 @@
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.75.1",
|
||||
"@kubernetes/client-node": "^1.2.0",
|
||||
"@tanstack/react-query": "^5.75.7",
|
||||
"@trpc/client": "^11.1.2",
|
||||
"@trpc/react-query": "^11.1.2",
|
||||
"@trpc/server": "^11.1.2",
|
||||
"@trpc/tanstack-react-query": "^11.1.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"trpc-to-openapi": "^2.2.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { releasesRequestHandler } from "@homarr/request-handler/releases";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
const formatVersionFilterRegex = (versionFilter: z.infer<typeof _releaseVersionFilterSchema> | undefined) => {
|
||||
const formatVersionFilterRegex = (versionFilter: z.infer<typeof releaseVersionFilterSchema> | undefined) => {
|
||||
if (!versionFilter) return undefined;
|
||||
|
||||
const escapedPrefix = versionFilter.prefix ? escapeForRegEx(versionFilter.prefix) : "";
|
||||
@@ -15,7 +15,7 @@ const formatVersionFilterRegex = (versionFilter: z.infer<typeof _releaseVersionF
|
||||
return `^${escapedPrefix}${precision}${escapedSuffix}$`;
|
||||
};
|
||||
|
||||
const _releaseVersionFilterSchema = z.object({
|
||||
const releaseVersionFilterSchema = z.object({
|
||||
prefix: z.string().optional(),
|
||||
precision: z.number(),
|
||||
suffix: z.string().optional(),
|
||||
@@ -29,7 +29,7 @@ export const releasesRouter = createTRPCRouter({
|
||||
z.object({
|
||||
providerKey: z.string(),
|
||||
identifier: z.string(),
|
||||
versionFilter: _releaseVersionFilterSchema.optional(),
|
||||
versionFilter: releaseVersionFilterSchema.optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
|
||||
@@ -13,6 +13,7 @@ import { extractProfileName } from "./providers/oidc/oidc-provider";
|
||||
|
||||
export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["events"], undefined>["signIn"] => {
|
||||
return async ({ user, profile }) => {
|
||||
logger.debug(`SignIn EventHandler for user: ${JSON.stringify(user)} . profile: ${JSON.stringify(profile)}`);
|
||||
if (!user.id) throw new Error("User ID is missing");
|
||||
|
||||
const dbUser = await db.query.users.findFirst({
|
||||
@@ -28,11 +29,13 @@ export const createSignInEventHandler = (db: Database): Exclude<NextAuthConfig["
|
||||
const groupsKey = env.AUTH_OIDC_GROUPS_ATTRIBUTE;
|
||||
// Groups from oidc provider are provided from the profile, it's not typed.
|
||||
if (profile && groupsKey in profile && Array.isArray(profile[groupsKey])) {
|
||||
logger.debug(`Using profile groups (${groupsKey}): ${JSON.stringify(profile[groupsKey])}`);
|
||||
await synchronizeGroupsWithExternalForUserAsync(db, user.id, profile[groupsKey] as string[]);
|
||||
}
|
||||
|
||||
// In ldap-authroization we return the groups from ldap, it's not typed.
|
||||
if ("groups" in user && Array.isArray(user.groups)) {
|
||||
logger.debug(`Using profile groups: ${JSON.stringify(user.groups)}`);
|
||||
await synchronizeGroupsWithExternalForUserAsync(db, user.id, user.groups as string[]);
|
||||
}
|
||||
await addUserToEveryoneGroupIfNotMemberAsync(db, user.id);
|
||||
|
||||
@@ -34,12 +34,12 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "7.4.0",
|
||||
"next": "15.3.1",
|
||||
"ldapts": "8.0.0",
|
||||
"next": "15.3.2",
|
||||
"next-auth": "5.0.0-beta.27",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/cookies": "0.9.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"undici": "7.8.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,11 @@ export const splitToNChunks = <T>(array: T[], chunks: number): T[][] => {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const splitToChunksWithNItems = <T>(array: T[], itemCount: number): T[][] => {
|
||||
const result: T[][] = [];
|
||||
for (let i = 0; i < array.length; i += itemCount) {
|
||||
result.push(array.slice(i, i + itemCount));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
26
packages/common/src/date.ts
Normal file
26
packages/common/src/date.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { UnitTypeShort } from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
|
||||
dayjs.extend(isBetween);
|
||||
|
||||
const validUnits = ["h", "d", "w", "M", "y"] as UnitTypeShort[];
|
||||
|
||||
export const isDateWithin = (date: Date, relativeDate: string): boolean => {
|
||||
if (relativeDate.length < 2) {
|
||||
throw new Error("Relative date must be at least 2 characters long");
|
||||
}
|
||||
|
||||
const amount = parseInt(relativeDate.slice(0, -1), 10);
|
||||
if (isNaN(amount) || amount <= 0) {
|
||||
throw new Error("Relative date must be a number greater than 0");
|
||||
}
|
||||
|
||||
const unit = relativeDate.slice(-1) as dayjs.UnitTypeShort;
|
||||
if (!validUnits.includes(unit)) {
|
||||
throw new Error("Invalid relative time unit");
|
||||
}
|
||||
|
||||
const startDate = dayjs().subtract(amount, unit);
|
||||
return dayjs(date).isBetween(startDate, dayjs(), null, "[]");
|
||||
};
|
||||
@@ -2,6 +2,7 @@ export * from "./object";
|
||||
export * from "./string";
|
||||
export * from "./cookie";
|
||||
export * from "./array";
|
||||
export * from "./date";
|
||||
export * from "./stopwatch";
|
||||
export * from "./hooks";
|
||||
export * from "./url";
|
||||
|
||||
49
packages/common/src/test/array.spec.ts
Normal file
49
packages/common/src/test/array.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { splitToChunksWithNItems, splitToNChunks } from "../array";
|
||||
|
||||
describe("splitToNChunks", () => {
|
||||
it("should split an array into the specified number of chunks", () => {
|
||||
const array = [1, 2, 3, 4, 5];
|
||||
const chunks = 3;
|
||||
const result = splitToNChunks(array, chunks);
|
||||
expect(result).toEqual([[1, 2], [3, 4], [5]]);
|
||||
});
|
||||
|
||||
it("should handle an empty array", () => {
|
||||
const array: number[] = [];
|
||||
const chunks = 3;
|
||||
const result = splitToNChunks(array, chunks);
|
||||
expect(result).toEqual([[], [], []]);
|
||||
});
|
||||
|
||||
it("should handle more chunks than elements", () => {
|
||||
const array = [1, 2];
|
||||
const chunks = 5;
|
||||
const result = splitToNChunks(array, chunks);
|
||||
expect(result).toEqual([[1], [2], [], [], []]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("splitToChunksWithNItems", () => {
|
||||
it("should split an array into chunks with the specified number of items", () => {
|
||||
const array = [1, 2, 3, 4, 5];
|
||||
const items = 2;
|
||||
const result = splitToChunksWithNItems(array, items);
|
||||
expect(result).toEqual([[1, 2], [3, 4], [5]]);
|
||||
});
|
||||
|
||||
it("should handle an empty array", () => {
|
||||
const array: number[] = [];
|
||||
const items = 2;
|
||||
const result = splitToChunksWithNItems(array, items);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should handle more items per chunk than elements", () => {
|
||||
const array = [1, 2];
|
||||
const items = 5;
|
||||
const result = splitToChunksWithNItems(array, items);
|
||||
expect(result).toEqual([[1, 2]]);
|
||||
});
|
||||
});
|
||||
91
packages/common/src/test/date.spec.ts
Normal file
91
packages/common/src/test/date.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { isDateWithin } from "../date";
|
||||
|
||||
describe("isDateWithin", () => {
|
||||
it("should return true for a date within the specified hours", () => {
|
||||
const date = new Date();
|
||||
date.setHours(date.getHours() - 20);
|
||||
expect(isDateWithin(date, "100h")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for a date outside the specified hours", () => {
|
||||
const date = new Date();
|
||||
date.setHours(date.getHours() - 101);
|
||||
expect(isDateWithin(date, "100h")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for a date within the specified days", () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 5);
|
||||
expect(isDateWithin(date, "10d")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for a date outside the specified days", () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 11);
|
||||
expect(isDateWithin(date, "10d")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for a date within the specified weeks", () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 10);
|
||||
expect(isDateWithin(date, "7w")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for a date outside the specified weeks", () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 50);
|
||||
expect(isDateWithin(date, "7w")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for a date within the specified months", () => {
|
||||
const date = new Date();
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
expect(isDateWithin(date, "2M")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for a date outside the specified months", () => {
|
||||
const date = new Date();
|
||||
date.setMonth(date.getMonth() - 3);
|
||||
expect(isDateWithin(date, "2M")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true for a date within the specified years", () => {
|
||||
const date = new Date();
|
||||
date.setFullYear(date.getFullYear() - 1);
|
||||
expect(isDateWithin(date, "2y")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for a date outside the specified years", () => {
|
||||
const date = new Date();
|
||||
date.setFullYear(date.getFullYear() - 3);
|
||||
expect(isDateWithin(date, "2y")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for a date after the specified relative time", () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 2);
|
||||
expect(isDateWithin(date, "1d")).toBe(false);
|
||||
});
|
||||
|
||||
it("should throw an error for an invalid unit", () => {
|
||||
const date = new Date();
|
||||
expect(() => isDateWithin(date, "2x")).toThrow("Invalid relative time unit");
|
||||
});
|
||||
|
||||
it("should throw an error if relativeDate is less than 2 characters long", () => {
|
||||
const date = new Date();
|
||||
expect(() => isDateWithin(date, "h")).toThrow("Relative date must be at least 2 characters long");
|
||||
});
|
||||
|
||||
it("should throw an error if relativeDate has an invalid number", () => {
|
||||
const date = new Date();
|
||||
expect(() => isDateWithin(date, "hh")).toThrow("Relative date must be a number greater than 0");
|
||||
});
|
||||
|
||||
it("should throw an error if relativeDate is set to 0", () => {
|
||||
const date = new Date();
|
||||
expect(() => isDateWithin(date, "0y")).toThrow("Relative date must be a number greater than 0");
|
||||
});
|
||||
});
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +44,12 @@
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^10.25.0",
|
||||
"better-sqlite3": "^11.9.1",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-kit": "^0.31.0",
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"mysql2": "3.14.1"
|
||||
@@ -60,7 +60,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.4",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.38",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/env/package.json
vendored
4
packages/env/package.json
vendored
@@ -24,13 +24,13 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.4",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^7.17.7",
|
||||
"zod": "^3.24.3"
|
||||
"@mantine/form": "^8.0.0",
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"react": "19.1.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"tsdav": "^2.1.4",
|
||||
"undici": "7.8.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
@@ -51,7 +51,7 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node-unifi": "^2.5.1",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
"ioredis": "5.6.1",
|
||||
"superjson": "2.2.2",
|
||||
"winston": "3.17.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ export const formatErrorCause = (cause: unknown, iteration = 0): string => {
|
||||
return `\ncaused by ${formatErrorTitle(cause)}\n${formatErrorStack(cause.stack)}${formatErrorCause(cause.cause, iteration + 1)}`;
|
||||
}
|
||||
|
||||
if (cause instanceof Object) {
|
||||
return `\ncaused by ${JSON.stringify(cause)}`;
|
||||
}
|
||||
|
||||
return `\ncaused by ${cause as string}`;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,19 +33,19 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@mantine/hooks": "^8.0.0",
|
||||
"react": "19.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.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^7.17.7",
|
||||
"@mantine/notifications": "^8.0.0",
|
||||
"@tabler/icons-react": "^3.31.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@mantine/hooks": "^8.0.0",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"zod": "^3.24.3",
|
||||
"zod": "^3.24.4",
|
||||
"zod-form-data": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,7 +52,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/adm-zip": "0.5.7",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@extractus/feed-extractor": "7.1.4",
|
||||
"@extractus/feed-extractor": "7.1.5",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,8 @@ export const Providers: ProvidersProps = {
|
||||
.transform((resp) => ({
|
||||
projectUrl: `https://hub.docker.com/r/${resp.namespace === "library" ? "_" : resp.namespace}/${resp.name}`,
|
||||
projectDescription: resp.description,
|
||||
isFork: false,
|
||||
isArchived: false,
|
||||
createdAt: resp.date_registered,
|
||||
starsCount: resp.star_count,
|
||||
openIssues: 0,
|
||||
forksCount: 0,
|
||||
}))
|
||||
.safeParse(response);
|
||||
},
|
||||
@@ -67,12 +63,7 @@ export const Providers: ProvidersProps = {
|
||||
),
|
||||
})
|
||||
.transform((resp) => {
|
||||
return resp.results.map((release) => ({
|
||||
...release,
|
||||
releaseUrl: "",
|
||||
releaseDescription: "",
|
||||
isPreRelease: false,
|
||||
}));
|
||||
return resp.results;
|
||||
})
|
||||
.safeParse(response);
|
||||
},
|
||||
@@ -89,7 +80,7 @@ export const Providers: ProvidersProps = {
|
||||
return z
|
||||
.object({
|
||||
html_url: z.string(),
|
||||
description: z.string(),
|
||||
description: z.string().nullable(),
|
||||
fork: z.boolean(),
|
||||
archived: z.boolean(),
|
||||
created_at: z.string().transform((value) => new Date(value)),
|
||||
@@ -99,7 +90,7 @@ export const Providers: ProvidersProps = {
|
||||
})
|
||||
.transform((resp) => ({
|
||||
projectUrl: resp.html_url,
|
||||
projectDescription: resp.description,
|
||||
projectDescription: resp.description ?? undefined,
|
||||
isFork: resp.fork,
|
||||
isArchived: resp.archived,
|
||||
createdAt: resp.created_at,
|
||||
@@ -120,7 +111,7 @@ export const Providers: ProvidersProps = {
|
||||
tag_name: z.string(),
|
||||
published_at: z.string().transform((value) => new Date(value)),
|
||||
html_url: z.string(),
|
||||
body: z.string(),
|
||||
body: z.string().nullable(),
|
||||
prerelease: z.boolean(),
|
||||
})
|
||||
.transform((tag) => ({
|
||||
@@ -128,7 +119,7 @@ export const Providers: ProvidersProps = {
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.html_url,
|
||||
releaseDescription: tag.body,
|
||||
releaseDescription: tag.body ?? undefined,
|
||||
isPreRelease: tag.prerelease,
|
||||
})),
|
||||
)
|
||||
@@ -144,17 +135,17 @@ export const Providers: ProvidersProps = {
|
||||
.object({
|
||||
web_url: z.string(),
|
||||
description: z.string(),
|
||||
forked_from_project: z.object({ id: z.number() }).nullable(),
|
||||
archived: z.boolean(),
|
||||
forked_from_project: z.object({ id: z.number() }).optional(),
|
||||
archived: z.boolean().optional(),
|
||||
created_at: z.string().transform((value) => new Date(value)),
|
||||
star_count: z.number(),
|
||||
open_issues_count: z.number(),
|
||||
open_issues_count: z.number().optional(),
|
||||
forks_count: z.number(),
|
||||
})
|
||||
.transform((resp) => ({
|
||||
projectUrl: resp.web_url,
|
||||
projectDescription: resp.description,
|
||||
isFork: resp.forked_from_project !== null,
|
||||
isFork: resp.forked_from_project !== undefined,
|
||||
isArchived: resp.archived,
|
||||
createdAt: resp.created_at,
|
||||
starsCount: resp.star_count,
|
||||
@@ -217,7 +208,6 @@ export const Providers: ProvidersProps = {
|
||||
...release,
|
||||
releaseUrl: `https://www.npmjs.com/package/${resp.name}/v/${release.latestRelease}`,
|
||||
releaseDescription: resp.versions[release.latestRelease]?.description ?? "",
|
||||
isPreRelease: false,
|
||||
}));
|
||||
})
|
||||
.safeParse(response);
|
||||
@@ -282,23 +272,31 @@ export const Providers: ProvidersProps = {
|
||||
},
|
||||
};
|
||||
|
||||
const _detailsSchema = z.object({
|
||||
projectUrl: z.string(),
|
||||
projectDescription: z.string(),
|
||||
isFork: z.boolean(),
|
||||
isArchived: z.boolean(),
|
||||
createdAt: z.date(),
|
||||
starsCount: z.number(),
|
||||
openIssues: z.number(),
|
||||
forksCount: z.number(),
|
||||
});
|
||||
const _detailsSchema = z
|
||||
.object({
|
||||
projectUrl: z.string().optional(),
|
||||
projectDescription: z.string().optional(),
|
||||
isFork: z.boolean().optional(),
|
||||
isArchived: z.boolean().optional(),
|
||||
createdAt: z.date().optional(),
|
||||
starsCount: z.number().optional(),
|
||||
openIssues: z.number().optional(),
|
||||
forksCount: z.number().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
const _releasesSchema = z.object({
|
||||
latestRelease: z.string(),
|
||||
latestReleaseAt: z.date(),
|
||||
releaseUrl: z.string(),
|
||||
releaseDescription: z.string(),
|
||||
isPreRelease: z.boolean(),
|
||||
releaseUrl: z.string().optional(),
|
||||
releaseDescription: z.string().optional(),
|
||||
isPreRelease: z.boolean().optional(),
|
||||
error: z
|
||||
.object({
|
||||
code: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type DetailsResponse = z.infer<typeof _detailsSchema>;
|
||||
|
||||
@@ -8,22 +8,49 @@ import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-ha
|
||||
import { Providers } from "./releases-providers";
|
||||
import type { DetailsResponse } from "./releases-providers";
|
||||
|
||||
const errorSchema = z.object({
|
||||
code: z.string().optional(),
|
||||
message: z.string().optional(),
|
||||
});
|
||||
|
||||
type ReleasesError = z.infer<typeof errorSchema>;
|
||||
|
||||
const _reponseSchema = z.object({
|
||||
identifier: z.string(),
|
||||
providerKey: z.string(),
|
||||
latestRelease: z.string(),
|
||||
latestReleaseAt: z.date(),
|
||||
releaseUrl: z.string(),
|
||||
releaseDescription: z.string(),
|
||||
isPreRelease: z.boolean(),
|
||||
projectUrl: z.string(),
|
||||
projectDescription: z.string(),
|
||||
isFork: z.boolean(),
|
||||
isArchived: z.boolean(),
|
||||
createdAt: z.date(),
|
||||
starsCount: z.number(),
|
||||
openIssues: z.number(),
|
||||
forksCount: z.number(),
|
||||
latestRelease: z.string().optional(),
|
||||
latestReleaseAt: z.date().optional(),
|
||||
releaseUrl: z.string().optional(),
|
||||
releaseDescription: z.string().optional(),
|
||||
isPreRelease: z.boolean().optional(),
|
||||
projectUrl: z.string().optional(),
|
||||
projectDescription: z.string().optional(),
|
||||
isFork: z.boolean().optional(),
|
||||
isArchived: z.boolean().optional(),
|
||||
createdAt: z.date().optional(),
|
||||
starsCount: z.number().optional(),
|
||||
openIssues: z.number().optional(),
|
||||
forksCount: z.number().optional(),
|
||||
error: errorSchema.optional(),
|
||||
});
|
||||
|
||||
const formatErrorRelease = (identifier: string, providerKey: string, error: ReleasesError) => ({
|
||||
identifier,
|
||||
providerKey,
|
||||
latestRelease: undefined,
|
||||
latestReleaseAt: undefined,
|
||||
releaseUrl: undefined,
|
||||
releaseDescription: undefined,
|
||||
isPreRelease: undefined,
|
||||
projectUrl: undefined,
|
||||
projectDescription: undefined,
|
||||
isFork: undefined,
|
||||
isArchived: undefined,
|
||||
createdAt: undefined,
|
||||
starsCount: undefined,
|
||||
openIssues: undefined,
|
||||
forksCount: undefined,
|
||||
error,
|
||||
});
|
||||
|
||||
export const releasesRequestHandler = createCachedWidgetRequestHandler({
|
||||
@@ -34,17 +61,7 @@ export const releasesRequestHandler = createCachedWidgetRequestHandler({
|
||||
|
||||
if (!provider) return undefined;
|
||||
|
||||
let detailsResult: DetailsResponse = {
|
||||
projectUrl: "",
|
||||
projectDescription: "",
|
||||
isFork: false,
|
||||
isArchived: false,
|
||||
createdAt: new Date(0),
|
||||
starsCount: 0,
|
||||
openIssues: 0,
|
||||
forksCount: 0,
|
||||
};
|
||||
|
||||
let detailsResult: DetailsResponse;
|
||||
const detailsUrl = provider.getDetailsUrl(input.identifier);
|
||||
if (detailsUrl !== undefined) {
|
||||
const detailsResponse = await fetchWithTimeout(detailsUrl);
|
||||
@@ -53,7 +70,8 @@ export const releasesRequestHandler = createCachedWidgetRequestHandler({
|
||||
if (parsedDetails?.success) {
|
||||
detailsResult = parsedDetails.data;
|
||||
} else {
|
||||
logger.warn("Failed to parse details response", {
|
||||
detailsResult = undefined;
|
||||
logger.warn(`Failed to parse details response for ${input.identifier} on ${input.providerKey}`, {
|
||||
provider: input.providerKey,
|
||||
identifier: input.identifier,
|
||||
detailsUrl,
|
||||
@@ -63,43 +81,42 @@ export const releasesRequestHandler = createCachedWidgetRequestHandler({
|
||||
}
|
||||
|
||||
const releasesResponse = await fetchWithTimeout(provider.getReleasesUrl(input.identifier));
|
||||
const releasesResult = provider.parseReleasesResponse(await releasesResponse.json());
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const releasesResult = provider.parseReleasesResponse(releasesResponseJson);
|
||||
|
||||
if (!releasesResult.success) return undefined;
|
||||
|
||||
const latest: ResponseResponse = releasesResult.data
|
||||
.filter((result) => (input.versionRegex ? new RegExp(input.versionRegex).test(result.latestRelease) : true))
|
||||
.reduce(
|
||||
(latest, result) => {
|
||||
return {
|
||||
...detailsResult,
|
||||
...(result.latestReleaseAt > latest.latestReleaseAt ? result : latest),
|
||||
identifier: input.identifier,
|
||||
providerKey: input.providerKey,
|
||||
};
|
||||
},
|
||||
{
|
||||
identifier: "",
|
||||
providerKey: "",
|
||||
latestRelease: "",
|
||||
latestReleaseAt: new Date(0),
|
||||
releaseUrl: "",
|
||||
releaseDescription: "",
|
||||
isPreRelease: false,
|
||||
projectUrl: "",
|
||||
projectDescription: "",
|
||||
isFork: false,
|
||||
isArchived: false,
|
||||
createdAt: new Date(0),
|
||||
starsCount: 0,
|
||||
openIssues: 0,
|
||||
forksCount: 0,
|
||||
},
|
||||
if (!releasesResult.success) {
|
||||
return formatErrorRelease(input.identifier, input.providerKey, {
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : releasesResult.error.message,
|
||||
});
|
||||
} else {
|
||||
const releases = releasesResult.data.filter((result) =>
|
||||
input.versionRegex && result.latestRelease ? new RegExp(input.versionRegex).test(result.latestRelease) : true,
|
||||
);
|
||||
|
||||
return latest;
|
||||
const latest =
|
||||
releases.length === 0
|
||||
? formatErrorRelease(input.identifier, input.providerKey, { code: "noMatchingVersion" })
|
||||
: releases.reduce(
|
||||
(latest, result) => {
|
||||
return {
|
||||
...detailsResult,
|
||||
...(result.latestReleaseAt > latest.latestReleaseAt ? result : latest),
|
||||
identifier: input.identifier,
|
||||
providerKey: input.providerKey,
|
||||
};
|
||||
},
|
||||
{
|
||||
identifier: "",
|
||||
providerKey: "",
|
||||
latestRelease: "",
|
||||
latestReleaseAt: new Date(0),
|
||||
},
|
||||
);
|
||||
|
||||
return latest;
|
||||
}
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
});
|
||||
|
||||
export type ResponseResponse = z.infer<typeof _reponseSchema>;
|
||||
export type ReleaseResponse = z.infer<typeof _reponseSchema>;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^7.17.7",
|
||||
"next": "15.3.1",
|
||||
"@mantine/dates": "^8.0.0",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
},
|
||||
@@ -35,7 +35,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/spotlight": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@mantine/hooks": "^8.0.0",
|
||||
"@mantine/spotlight": "^8.0.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"jotai": "^2.12.3",
|
||||
"next": "15.3.1",
|
||||
"jotai": "^2.12.4",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
@@ -47,7 +47,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"dayjs": "^1.11.13",
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"next-intl": "4.1.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
@@ -41,7 +41,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2059,11 +2059,11 @@
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "New Release Within",
|
||||
"description": "Usage example: 1w (1 week), 10m (10 months). Accepted unit types h (hours), d (days), w (weeks), m (months), y (years). Leave empty for no highlighting of new releases."
|
||||
"description": "Usage example: 1w (1 week), 10M (10 months). Accepted unit types h (hours), d (days), w (weeks), M (months), y (years). Leave empty for no highlighting of new releases."
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "Stale Release Within",
|
||||
"description": "Usage example: 1w (1 week), 10m (10 months). Accepted unit types h (hours), d (days), w (weeks), m (months), y (years). Leave empty for no highlighting of stale releases."
|
||||
"description": "Usage example: 1w (1 week), 10M (10 months). Accepted unit types h (hours), d (days), w (weeks), M (months), y (years). Leave empty for no highlighting of stale releases."
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
"label": "Show Only Highlighted",
|
||||
@@ -2130,7 +2130,13 @@
|
||||
"openProjectPage": "Open Project Page",
|
||||
"openReleasePage": "Open Release Page",
|
||||
"releaseDescription": "Release Description",
|
||||
"created": "Created"
|
||||
"created": "Created",
|
||||
"error": {
|
||||
"label": "Error",
|
||||
"options": {
|
||||
"noMatchingVersion": "No matching version found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"networkControllerSummary": {
|
||||
"option": {},
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
},
|
||||
"toLarge": {
|
||||
"title": "Bilden är för stor",
|
||||
"message": ""
|
||||
"message": "Maximal bildstorlek är {size}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -612,17 +612,17 @@
|
||||
"select": {
|
||||
"label": "",
|
||||
"notFound": "",
|
||||
"search": "",
|
||||
"search": "Sök efter en applikation",
|
||||
"noResults": "",
|
||||
"action": "",
|
||||
"title": ""
|
||||
"action": "Välj {app}",
|
||||
"title": "Välj en applikation att lägga till på tavlan"
|
||||
},
|
||||
"create": {
|
||||
"title": "Addera en ny applikation",
|
||||
"description": "Addera en ny applikation ",
|
||||
"action": ""
|
||||
"action": "Addera applikation"
|
||||
},
|
||||
"add": ""
|
||||
"add": "Lägg till en applikation"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
@@ -954,7 +954,7 @@
|
||||
"unsavedChanges": "",
|
||||
"preview": {
|
||||
"show": "Förhandsgranska",
|
||||
"hide": ""
|
||||
"hide": "Dölj förhandsgranskning"
|
||||
},
|
||||
"zod": {
|
||||
"errors": {
|
||||
@@ -990,7 +990,7 @@
|
||||
"section": {
|
||||
"dynamic": {
|
||||
"action": {
|
||||
"create": "",
|
||||
"create": "Ny dynamisk sektion",
|
||||
"remove": ""
|
||||
},
|
||||
"option": {
|
||||
@@ -1013,7 +1013,7 @@
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"create": "",
|
||||
"create": "Ny kategori",
|
||||
"edit": "",
|
||||
"remove": "",
|
||||
"moveUp": "Flytta uppåt",
|
||||
@@ -1024,7 +1024,7 @@
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"submit": ""
|
||||
"submit": "Lägg till kategori"
|
||||
},
|
||||
"remove": {
|
||||
"title": "",
|
||||
@@ -1048,7 +1048,7 @@
|
||||
},
|
||||
"item": {
|
||||
"action": {
|
||||
"create": "",
|
||||
"create": "Nytt objekt",
|
||||
"import": "",
|
||||
"edit": "Redigera objekt",
|
||||
"moveResize": "",
|
||||
@@ -1063,7 +1063,7 @@
|
||||
"create": {
|
||||
"title": "Välj objekt du vill lägga till",
|
||||
"search": "",
|
||||
"addToBoard": ""
|
||||
"addToBoard": "Lägg till på tavlan"
|
||||
},
|
||||
"moveResize": {
|
||||
"title": "",
|
||||
@@ -1138,7 +1138,7 @@
|
||||
},
|
||||
"bookmarks": {
|
||||
"name": "Bokmärken",
|
||||
"description": "",
|
||||
"description": "Visar länkar till flera applikationer",
|
||||
"option": {
|
||||
"title": {
|
||||
"label": "Titel"
|
||||
@@ -1252,7 +1252,7 @@
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"name": "",
|
||||
"name": "Datum och tid",
|
||||
"description": "Visar aktuellt datum och tid.",
|
||||
"option": {
|
||||
"customTitleToggle": {
|
||||
@@ -2298,16 +2298,16 @@
|
||||
"label": "Namn på sidan"
|
||||
},
|
||||
"metaTitle": {
|
||||
"label": ""
|
||||
"label": "Metarubrik (visas i huvudet eller fliken i webbläsaren)"
|
||||
},
|
||||
"logoImageUrl": {
|
||||
"label": "URL-adress till logo för tavlan"
|
||||
},
|
||||
"faviconImageUrl": {
|
||||
"label": ""
|
||||
"label": "URL-adress till bilden som visas som favoritbild"
|
||||
},
|
||||
"backgroundImageUrl": {
|
||||
"label": "",
|
||||
"label": "URL-adress till bakgrundsbilden",
|
||||
"placeholder": "",
|
||||
"group": {
|
||||
"your": "",
|
||||
@@ -2315,49 +2315,49 @@
|
||||
}
|
||||
},
|
||||
"backgroundImageAttachment": {
|
||||
"label": "Bilaga med bakgrundsbild",
|
||||
"label": "Bakgrundsbildens beteende",
|
||||
"option": {
|
||||
"fixed": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Fast",
|
||||
"description": "Bakgrunden stannar i samma läge."
|
||||
},
|
||||
"scroll": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Förflyttas",
|
||||
"description": "Bakgrunden förflyttas med musens rörelse."
|
||||
}
|
||||
}
|
||||
},
|
||||
"backgroundImageRepeat": {
|
||||
"label": "",
|
||||
"label": "Upprepa bakgrundsbilden",
|
||||
"option": {
|
||||
"repeat": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Upprepa",
|
||||
"description": "Bilden kommer att upprepas så mycket som krävs för att täcka bakgrunden."
|
||||
},
|
||||
"no-repeat": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Ingen upprepning",
|
||||
"description": "Bilden upprepas inte och kommer eventuellt inte fylla hela bakgrunden."
|
||||
},
|
||||
"repeat-x": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Upprepa horisontellt",
|
||||
"description": "Samma sak om 'Upprepa' men endast horisontellt."
|
||||
},
|
||||
"repeat-y": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Upprepa vertikalt",
|
||||
"description": "Samma sak som 'Upprepa' men endast vertikalt."
|
||||
}
|
||||
}
|
||||
},
|
||||
"backgroundImageSize": {
|
||||
"label": "Storlek på bakgrundsbild",
|
||||
"label": "Storlek på bakgrundsbilden",
|
||||
"option": {
|
||||
"cover": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Täck",
|
||||
"description": "Gör bilden så liten som möjligt för att täcka hela tavlan genom att beskära överflödig den av bilden."
|
||||
},
|
||||
"contain": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Maximera",
|
||||
"description": "Gör bilden så stor som möjligt för att täcka hela tavlan utan att beskära eller sträcka ut bilden."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2411,7 +2411,7 @@
|
||||
"metaTitle": ""
|
||||
},
|
||||
"setting": {
|
||||
"title": "Inställningar för tavlan {boardName}",
|
||||
"title": "Inställningar för tavlan \"{boardName}\"",
|
||||
"section": {
|
||||
"general": {
|
||||
"title": "Generellt",
|
||||
@@ -2430,7 +2430,7 @@
|
||||
"title": "Bakgrund"
|
||||
},
|
||||
"appearance": {
|
||||
"title": ""
|
||||
"title": "Utseende"
|
||||
},
|
||||
"customCss": {
|
||||
"title": ""
|
||||
@@ -3388,11 +3388,11 @@
|
||||
"label": "Grupp"
|
||||
},
|
||||
"permission": {
|
||||
"label": ""
|
||||
"label": "Behörighet"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"saveUser": "",
|
||||
"saveUser": "Spara behörighet",
|
||||
"saveGroup": ""
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1950,7 +1950,7 @@
|
||||
"approved": "Onaylandı",
|
||||
"declined": "Reddedildi",
|
||||
"failed": "Başarısız",
|
||||
"completed": ""
|
||||
"completed": "Tamamlandı"
|
||||
},
|
||||
"toBeDetermined": "-Yapım Aşamasında-"
|
||||
},
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/dates": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@mantine/dates": "^8.0.0",
|
||||
"@mantine/hooks": "^8.0.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
},
|
||||
@@ -43,7 +43,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/css-modules": "^1.0.5",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"zod": "^3.24.3",
|
||||
"zod": "^3.24.4",
|
||||
"zod-form-data": "^2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,42 +47,42 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^7.17.7",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/charts": "^8.0.0",
|
||||
"@mantine/core": "^8.0.0",
|
||||
"@mantine/hooks": "^8.0.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tiptap/extension-color": "2.11.9",
|
||||
"@tiptap/extension-highlight": "2.11.9",
|
||||
"@tiptap/extension-image": "2.11.9",
|
||||
"@tiptap/extension-link": "^2.11.9",
|
||||
"@tiptap/extension-table": "2.11.9",
|
||||
"@tiptap/extension-table-cell": "2.11.9",
|
||||
"@tiptap/extension-table-header": "2.11.9",
|
||||
"@tiptap/extension-table-row": "2.11.9",
|
||||
"@tiptap/extension-task-item": "2.11.9",
|
||||
"@tiptap/extension-task-list": "2.11.9",
|
||||
"@tiptap/extension-text-align": "2.11.9",
|
||||
"@tiptap/extension-text-style": "2.11.9",
|
||||
"@tiptap/extension-underline": "2.11.9",
|
||||
"@tiptap/react": "^2.11.9",
|
||||
"@tiptap/starter-kit": "^2.11.9",
|
||||
"@tiptap/extension-color": "2.12.0",
|
||||
"@tiptap/extension-highlight": "2.12.0",
|
||||
"@tiptap/extension-image": "2.12.0",
|
||||
"@tiptap/extension-link": "^2.12.0",
|
||||
"@tiptap/extension-table": "2.12.0",
|
||||
"@tiptap/extension-table-cell": "2.12.0",
|
||||
"@tiptap/extension-table-header": "2.12.0",
|
||||
"@tiptap/extension-table-row": "2.12.0",
|
||||
"@tiptap/extension-task-item": "2.12.0",
|
||||
"@tiptap/extension-task-list": "2.12.0",
|
||||
"@tiptap/extension-text-align": "2.12.0",
|
||||
"@tiptap/extension-text-style": "2.12.0",
|
||||
"@tiptap/extension-underline": "2.12.0",
|
||||
"@tiptap/react": "^2.12.0",
|
||||
"@tiptap/starter-kit": "^2.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.1",
|
||||
"next": "15.3.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.3",
|
||||
"video.js": "^8.22.0",
|
||||
"zod": "^3.24.3"
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
const item = {
|
||||
providerKey: "DockerHub",
|
||||
identifier: "",
|
||||
} as ReleasesRepository;
|
||||
};
|
||||
|
||||
form.setValues((previous) => {
|
||||
const previousValues = previous.options?.[property] as ReleasesRepository[];
|
||||
@@ -98,7 +98,6 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Fieldset legend={t("label")}>
|
||||
<Stack gap="5">
|
||||
|
||||
@@ -72,8 +72,8 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
|
||||
return (
|
||||
<Calendar
|
||||
defaultDate={new Date()}
|
||||
onPreviousMonth={setMonth}
|
||||
onNextMonth={setMonth}
|
||||
onPreviousMonth={(month) => setMonth(new Date(month))}
|
||||
onNextMonth={(month) => setMonth(new Date(month))}
|
||||
highlightToday
|
||||
locale={locale}
|
||||
hideWeekdays={false}
|
||||
@@ -126,7 +126,7 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
|
||||
.filter((event): event is CalendarEvent => Boolean(event.date));
|
||||
return (
|
||||
<CalendarDay
|
||||
date={tileDate}
|
||||
date={new Date(tileDate)}
|
||||
events={eventsForDate}
|
||||
disabled={isEditMode || eventsForDate.length === 0}
|
||||
rootWidth={width}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
IconGitFork,
|
||||
IconProgressCheck,
|
||||
IconStar,
|
||||
IconTriangleFilled,
|
||||
} from "@tabler/icons-react";
|
||||
import combineClasses from "clsx";
|
||||
import { useFormatter, useNow } from "next-intl";
|
||||
@@ -17,118 +18,116 @@ import ReactMarkdown from "react-markdown";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { isDateWithin, splitToChunksWithNItems } from "@homarr/common";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { MaskedOrNormalImage } from "@homarr/ui";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import classes from "./component.module.scss";
|
||||
import { Providers } from "./releases-providers";
|
||||
import type { ReleasesRepository } from "./releases-repository";
|
||||
import type { ReleasesRepositoryResponse } from "./releases-repository";
|
||||
|
||||
function isDateWithin(date: Date, relativeDate: string): boolean {
|
||||
const amount = parseInt(relativeDate.slice(0, -1), 10);
|
||||
const unit = relativeDate.slice(-1);
|
||||
|
||||
const startTime = new Date().getTime();
|
||||
const endTime = new Date(date).getTime();
|
||||
const diffTime = Math.abs(endTime - startTime);
|
||||
const diffHours = Math.ceil(diffTime / (1000 * 60 * 60));
|
||||
|
||||
switch (unit) {
|
||||
case "h":
|
||||
return diffHours < amount;
|
||||
|
||||
case "d":
|
||||
return diffHours / 24 < amount;
|
||||
|
||||
case "w":
|
||||
return diffHours / (24 * 7) < amount;
|
||||
|
||||
case "m":
|
||||
return diffHours / (24 * 30) < amount;
|
||||
|
||||
case "y":
|
||||
return diffHours / (24 * 365) < amount;
|
||||
|
||||
default:
|
||||
throw new Error("Invalid unit");
|
||||
}
|
||||
}
|
||||
const formatRelativeDate = (value: string): string => {
|
||||
const isMonths = /\d+m/g.test(value);
|
||||
const isOtherUnits = /\d+[HDWY]/g.test(value);
|
||||
return isMonths ? value.toUpperCase() : isOtherUnits ? value.toLowerCase() : value;
|
||||
};
|
||||
|
||||
export default function ReleasesWidget({ options }: WidgetComponentProps<"releases">) {
|
||||
const t = useScopedI18n("widget.releases");
|
||||
const now = useNow();
|
||||
const formatter = useFormatter();
|
||||
const board = useRequiredBoard();
|
||||
const [expandedRepository, setExpandedRepository] = useState("");
|
||||
const [expandedRepository, setExpandedRepository] = useState({ providerKey: "", identifier: "" });
|
||||
const hasIconColor = useMemo(() => board.iconColor !== null, [board.iconColor]);
|
||||
const relativeDateOptions = useMemo(
|
||||
() => ({
|
||||
newReleaseWithin: formatRelativeDate(options.newReleaseWithin),
|
||||
staleReleaseWithin: formatRelativeDate(options.staleReleaseWithin),
|
||||
}),
|
||||
[options.newReleaseWithin, options.staleReleaseWithin],
|
||||
);
|
||||
|
||||
const [results] = clientApi.widget.releases.getLatest.useSuspenseQuery(
|
||||
{
|
||||
repositories: options.repositories.map((repository) => ({
|
||||
providerKey: repository.providerKey,
|
||||
identifier: repository.identifier,
|
||||
versionFilter: repository.versionFilter,
|
||||
})),
|
||||
},
|
||||
{
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: false,
|
||||
},
|
||||
const batchedRepositories = useMemo(() => splitToChunksWithNItems(options.repositories, 5), [options.repositories]);
|
||||
const [results] = clientApi.useSuspenseQueries((t) =>
|
||||
batchedRepositories.flatMap((chunk) =>
|
||||
t.widget.releases.getLatest({
|
||||
repositories: chunk.map((repository) => ({
|
||||
providerKey: repository.providerKey,
|
||||
identifier: repository.identifier,
|
||||
versionFilter: repository.versionFilter,
|
||||
})),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const repositories = useMemo(() => {
|
||||
return results
|
||||
.flat()
|
||||
.map(({ data }) => {
|
||||
if (data === undefined) return undefined;
|
||||
|
||||
const repository = options.repositories.find(
|
||||
(repository: ReleasesRepository) =>
|
||||
repository.providerKey === data.providerKey && repository.identifier === data.identifier,
|
||||
(repository) => repository.providerKey === data.providerKey && repository.identifier === data.identifier,
|
||||
);
|
||||
|
||||
if (repository === undefined) return undefined;
|
||||
|
||||
return {
|
||||
...repository,
|
||||
...data,
|
||||
iconUrl: repository.iconUrl,
|
||||
isNewRelease:
|
||||
options.newReleaseWithin !== "" ? isDateWithin(data.latestReleaseAt, options.newReleaseWithin) : false,
|
||||
relativeDateOptions.newReleaseWithin !== "" && data.latestReleaseAt
|
||||
? isDateWithin(data.latestReleaseAt, relativeDateOptions.newReleaseWithin)
|
||||
: false,
|
||||
isStaleRelease:
|
||||
options.staleReleaseWithin !== "" ? !isDateWithin(data.latestReleaseAt, options.staleReleaseWithin) : false,
|
||||
relativeDateOptions.staleReleaseWithin !== "" && data.latestReleaseAt
|
||||
? !isDateWithin(data.latestReleaseAt, relativeDateOptions.staleReleaseWithin)
|
||||
: false,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(repository) =>
|
||||
repository !== undefined &&
|
||||
(!options.showOnlyHighlighted || repository.isNewRelease || repository.isStaleRelease),
|
||||
(repository.error !== undefined ||
|
||||
!options.showOnlyHighlighted ||
|
||||
repository.isNewRelease ||
|
||||
repository.isStaleRelease),
|
||||
)
|
||||
.sort((repoA, repoB) => {
|
||||
if (repoA?.latestReleaseAt === undefined) return 1;
|
||||
if (repoB?.latestReleaseAt === undefined) return -1;
|
||||
return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
|
||||
}) as ReleasesRepository[];
|
||||
}) as ReleasesRepositoryResponse[];
|
||||
}, [
|
||||
results,
|
||||
options.repositories,
|
||||
options.showOnlyHighlighted,
|
||||
options.newReleaseWithin,
|
||||
options.staleReleaseWithin,
|
||||
relativeDateOptions.newReleaseWithin,
|
||||
relativeDateOptions.staleReleaseWithin,
|
||||
]);
|
||||
|
||||
const toggleExpandedRepository = useCallback(
|
||||
(identifier: string) => {
|
||||
setExpandedRepository(expandedRepository === identifier ? "" : identifier);
|
||||
(repository: ReleasesRepositoryResponse) => {
|
||||
if (
|
||||
expandedRepository.providerKey === repository.providerKey &&
|
||||
expandedRepository.identifier === repository.identifier
|
||||
) {
|
||||
setExpandedRepository({ providerKey: "", identifier: "" });
|
||||
} else {
|
||||
setExpandedRepository({ providerKey: repository.providerKey, identifier: repository.identifier });
|
||||
}
|
||||
},
|
||||
[expandedRepository],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
{repositories.map((repository: ReleasesRepository) => {
|
||||
const isActive = expandedRepository === repository.identifier;
|
||||
{repositories.map((repository: ReleasesRepositoryResponse) => {
|
||||
const isActive =
|
||||
expandedRepository.providerKey === repository.providerKey &&
|
||||
expandedRepository.identifier === repository.identifier;
|
||||
const hasError = repository.error !== undefined;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -141,7 +140,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
[classes.active ?? ""]: isActive,
|
||||
})}
|
||||
p="xs"
|
||||
onClick={() => toggleExpandedRepository(repository.identifier)}
|
||||
onClick={() => toggleExpandedRepository(repository)}
|
||||
>
|
||||
<MaskedOrNormalImage
|
||||
imageUrl={repository.iconUrl ?? Providers[repository.providerKey]?.iconUrl}
|
||||
@@ -155,9 +154,14 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
<Group gap={5} justify="space-between" style={{ flex: 1 }}>
|
||||
<Text size="xs">{repository.identifier}</Text>
|
||||
|
||||
<Tooltip label={repository.latestRelease ?? t("not-found")}>
|
||||
<Text size="xs" fw={700} truncate="end" style={{ flexShrink: 1 }}>
|
||||
{repository.latestRelease ?? t("not-found")}
|
||||
<Tooltip
|
||||
withArrow
|
||||
arrowSize={5}
|
||||
label={repository.latestRelease}
|
||||
events={{ hover: repository.latestRelease !== undefined, focus: false, touch: false }}
|
||||
>
|
||||
<Text size="xs" fw={700} truncate="end" c={hasError ? "red" : "text"} style={{ flexShrink: 1 }}>
|
||||
{hasError ? t("error.label") : (repository.latestRelease ?? t("not-found"))}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
@@ -168,20 +172,25 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
c={repository.isNewRelease ? "primaryColor" : repository.isStaleRelease ? "secondaryColor" : "dimmed"}
|
||||
>
|
||||
{repository.latestReleaseAt &&
|
||||
!hasError &&
|
||||
formatter.relativeTime(repository.latestReleaseAt, {
|
||||
now,
|
||||
style: "narrow",
|
||||
})}
|
||||
</Text>
|
||||
{(repository.isNewRelease || repository.isStaleRelease) && (
|
||||
<IconCircleFilled
|
||||
size={10}
|
||||
color={
|
||||
repository.isNewRelease
|
||||
? "var(--mantine-color-primaryColor-filled)"
|
||||
: "var(--mantine-color-secondaryColor-filled)"
|
||||
}
|
||||
/>
|
||||
{!hasError ? (
|
||||
(repository.isNewRelease || repository.isStaleRelease) && (
|
||||
<IconCircleFilled
|
||||
size={10}
|
||||
color={
|
||||
repository.isNewRelease
|
||||
? "var(--mantine-color-primaryColor-filled)"
|
||||
: "var(--mantine-color-secondaryColor-filled)"
|
||||
}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<IconTriangleFilled size={10} color={"var(--mantine-color-red-filled)"} />
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
@@ -198,8 +207,8 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
}
|
||||
|
||||
interface DetailsDisplayProps {
|
||||
repository: ReleasesRepository;
|
||||
toggleExpandedRepository: (identifier: string) => void;
|
||||
repository: ReleasesRepositoryResponse;
|
||||
toggleExpandedRepository: (repository: ReleasesRepositoryResponse) => void;
|
||||
}
|
||||
|
||||
const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplayProps) => {
|
||||
@@ -208,15 +217,15 @@ const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplay
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider onClick={() => toggleExpandedRepository(repository.identifier)} />
|
||||
<Divider onClick={() => toggleExpandedRepository(repository)} />
|
||||
<Group
|
||||
className={classes.releasesRepositoryDetails}
|
||||
justify="space-between"
|
||||
p={5}
|
||||
onClick={() => toggleExpandedRepository(repository.identifier)}
|
||||
onClick={() => toggleExpandedRepository(repository)}
|
||||
>
|
||||
<Group>
|
||||
<Tooltip label={t("pre-release")}>
|
||||
<Tooltip label={t("pre-release")} withArrow arrowSize={5}>
|
||||
<IconProgressCheck
|
||||
size={13}
|
||||
color={
|
||||
@@ -225,14 +234,14 @@ const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplay
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("archived")}>
|
||||
<Tooltip label={t("archived")} withArrow arrowSize={5}>
|
||||
<IconArchive
|
||||
size={13}
|
||||
color={repository.isArchived ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("forked")}>
|
||||
<Tooltip label={t("forked")} withArrow arrowSize={5}>
|
||||
<IconGitFork
|
||||
size={13}
|
||||
color={repository.isFork ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"}
|
||||
@@ -240,16 +249,16 @@ const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplay
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Group>
|
||||
<Tooltip label={t("starsCount")}>
|
||||
<Tooltip label={t("starsCount")} withArrow arrowSize={5}>
|
||||
<Group gap={5}>
|
||||
<IconStar
|
||||
size={12}
|
||||
color={repository.starsCount === 0 ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
color={!repository.starsCount ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
/>
|
||||
<Text size="xs" c={repository.starsCount === 0 ? "dimmed" : ""}>
|
||||
{repository.starsCount === 0
|
||||
<Text size="xs" c={!repository.starsCount ? "dimmed" : ""}>
|
||||
{!repository.starsCount
|
||||
? "-"
|
||||
: formatter.number(repository.starsCount ?? 0, {
|
||||
: formatter.number(repository.starsCount, {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
})}
|
||||
@@ -257,16 +266,16 @@ const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplay
|
||||
</Group>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("forksCount")}>
|
||||
<Tooltip label={t("forksCount")} withArrow arrowSize={5}>
|
||||
<Group gap={5}>
|
||||
<IconGitFork
|
||||
size={12}
|
||||
color={repository.forksCount === 0 ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
color={!repository.forksCount ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
/>
|
||||
<Text size="xs" c={repository.forksCount === 0 ? "dimmed" : ""}>
|
||||
{repository.forksCount === 0
|
||||
<Text size="xs" c={!repository.forksCount ? "dimmed" : ""}>
|
||||
{!repository.forksCount
|
||||
? "-"
|
||||
: formatter.number(repository.forksCount ?? 0, {
|
||||
: formatter.number(repository.forksCount, {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
})}
|
||||
@@ -274,16 +283,16 @@ const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplay
|
||||
</Group>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label={t("issuesCount")}>
|
||||
<Tooltip label={t("issuesCount")} withArrow arrowSize={5}>
|
||||
<Group gap={5}>
|
||||
<IconCircleDot
|
||||
size={12}
|
||||
color={repository.openIssues === 0 ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
color={!repository.openIssues ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
/>
|
||||
<Text size="xs" c={repository.openIssues === 0 ? "dimmed" : ""}>
|
||||
{repository.openIssues === 0
|
||||
<Text size="xs" c={!repository.openIssues ? "dimmed" : ""}>
|
||||
{!repository.openIssues
|
||||
? "-"
|
||||
: formatter.number(repository.openIssues ?? 0, {
|
||||
: formatter.number(repository.openIssues, {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
})}
|
||||
@@ -297,7 +306,7 @@ const DetailsDisplay = ({ repository, toggleExpandedRepository }: DetailsDisplay
|
||||
};
|
||||
|
||||
interface ExtendedDisplayProps {
|
||||
repository: ReleasesRepository;
|
||||
repository: ReleasesRepositoryResponse;
|
||||
hasIconColor: boolean;
|
||||
}
|
||||
|
||||
@@ -337,17 +346,32 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
<Divider my={10} mx="30%" />
|
||||
<Button
|
||||
variant="light"
|
||||
component="a"
|
||||
href={repository.releaseUrl ?? repository.projectUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<IconExternalLink />
|
||||
{repository.releaseUrl ? t("openReleasePage") : t("openProjectPage")}
|
||||
</Button>
|
||||
{(repository.releaseUrl ?? repository.projectUrl) && (
|
||||
<>
|
||||
<Divider my={10} mx="30%" />
|
||||
<Button
|
||||
variant="light"
|
||||
component="a"
|
||||
href={repository.releaseUrl ?? repository.projectUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<IconExternalLink />
|
||||
{repository.releaseUrl ? t("openReleasePage") : t("openProjectPage")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{repository.error && (
|
||||
<>
|
||||
<Divider my={10} mx="30%" />
|
||||
<Title order={4} ta="center">
|
||||
{t("error.label")}
|
||||
</Title>
|
||||
<Text size="xs" ff="monospace" c="red" style={{ whiteSpace: "pre-wrap" }}>
|
||||
{repository.error.code ? t(`error.options.${repository.error.code}` as never) : repository.error.message}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
{repository.releaseDescription && (
|
||||
<>
|
||||
<Divider my={10} mx="30%" />
|
||||
|
||||
@@ -4,6 +4,11 @@ import { z } from "zod";
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
const relativeDateSchema = z
|
||||
.string()
|
||||
.regex(/^\d+[hdwmyHDWMY]$/)
|
||||
.or(z.literal(""));
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("releases", {
|
||||
icon: IconRocket,
|
||||
createOptions() {
|
||||
@@ -11,18 +16,12 @@ export const { definition, componentLoader } = createWidgetDefinition("releases"
|
||||
newReleaseWithin: factory.text({
|
||||
defaultValue: "1w",
|
||||
withDescription: true,
|
||||
validate: z
|
||||
.string()
|
||||
.regex(/^\d+[hdwmy]$/)
|
||||
.or(z.literal("")),
|
||||
validate: relativeDateSchema,
|
||||
}),
|
||||
staleReleaseWithin: factory.text({
|
||||
defaultValue: "6m",
|
||||
defaultValue: "6M",
|
||||
withDescription: true,
|
||||
validate: z
|
||||
.string()
|
||||
.regex(/^\d+[hdwmy]$/)
|
||||
.or(z.literal("")),
|
||||
validate: relativeDateSchema,
|
||||
}),
|
||||
showOnlyHighlighted: factory.switch({
|
||||
withDescription: true,
|
||||
|
||||
@@ -9,7 +9,9 @@ export interface ReleasesRepository {
|
||||
identifier: string;
|
||||
versionFilter?: ReleasesVersionFilter;
|
||||
iconUrl?: string;
|
||||
}
|
||||
|
||||
export interface ReleasesRepositoryResponse extends ReleasesRepository {
|
||||
latestRelease?: string;
|
||||
latestReleaseAt?: Date;
|
||||
isNewRelease: boolean;
|
||||
@@ -27,4 +29,6 @@ export interface ReleasesRepository {
|
||||
starsCount?: number;
|
||||
forksCount?: number;
|
||||
openIssues?: number;
|
||||
|
||||
error?: { code?: string; message?: string };
|
||||
}
|
||||
|
||||
2441
pnpm-lock.yaml
generated
2441
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -17,19 +17,19 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "15.3.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-config-turbo": "^2.5.2",
|
||||
"@next/eslint-plugin-next": "15.3.2",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-config-turbo": "^2.5.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
"typescript-eslint": "^8.32.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.26.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.10",
|
||||
"prettier-plugin-packagejson": "^2.5.11",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user