chore(release): automatic release v1.19.0
This commit is contained in:
@@ -48,21 +48,21 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/colors-generator": "^7.17.5",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/dropzone": "^7.17.5",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mantine/modals": "^7.17.5",
|
||||
"@mantine/tiptap": "^7.17.5",
|
||||
"@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",
|
||||
"@million/lint": "1.0.14",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-devtools": "^5.74.4",
|
||||
"@tanstack/react-query-next-experimental": "^5.74.4",
|
||||
"@trpc/client": "^11.1.1",
|
||||
"@trpc/next": "^11.1.1",
|
||||
"@trpc/react-query": "^11.1.1",
|
||||
"@trpc/server": "^11.1.1",
|
||||
"@tanstack/react-query": "^5.75.1",
|
||||
"@tanstack/react-query-devtools": "^5.75.1",
|
||||
"@tanstack/react-query-next-experimental": "^5.75.1",
|
||||
"@trpc/client": "^11.1.2",
|
||||
"@trpc/next": "^11.1.2",
|
||||
"@trpc/react-query": "^11.1.2",
|
||||
"@trpc/server": "^11.1.2",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -92,10 +92,10 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.15.2",
|
||||
"@types/node": "^22.15.3",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/react-dom": "19.1.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.25.1",
|
||||
|
||||
@@ -4,15 +4,21 @@ import { TRPCError } from "@trpc/server";
|
||||
// Placed here because gridstack styles are used for board content
|
||||
import "~/styles/gridstack.scss";
|
||||
|
||||
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
||||
|
||||
import { getQueryClient } from "@homarr/api/server";
|
||||
import { IntegrationProvider } from "@homarr/auth/client";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
import { getIntegrationsWithPermissionsAsync } from "@homarr/auth/server";
|
||||
import { isNullOrWhitespace } from "@homarr/common";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
import { getI18n } from "@homarr/translation/server";
|
||||
import { prefetchForKindAsync } from "@homarr/widgets/prefetch";
|
||||
|
||||
import { createMetaTitle } from "~/metadata";
|
||||
import { createBoardLayout } from "../_layout-creator";
|
||||
import type { Board } from "../_types";
|
||||
import type { Board, Item } from "../_types";
|
||||
import { DynamicClientBoard } from "./_dynamic-client";
|
||||
import { BoardContentHeaderActions } from "./_header-actions";
|
||||
|
||||
@@ -31,14 +37,36 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
|
||||
getInitialBoardAsync: getInitialBoard,
|
||||
}),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
page: async () => {
|
||||
page: async ({ params }: { params: Promise<TParams> }) => {
|
||||
const session = await auth();
|
||||
const integrations = await getIntegrationsWithPermissionsAsync(session);
|
||||
|
||||
const board = await getInitialBoard(await params);
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
// Prefetch item data
|
||||
const itemsMap = board.items.reduce((acc, item) => {
|
||||
const existing = acc.get(item.kind);
|
||||
if (existing) {
|
||||
existing.push(item);
|
||||
} else {
|
||||
acc.set(item.kind, [item]);
|
||||
}
|
||||
return acc;
|
||||
}, new Map<WidgetKind, Item[]>());
|
||||
|
||||
for (const [kind, items] of itemsMap) {
|
||||
await prefetchForKindAsync(kind, queryClient, items).catch((error) => {
|
||||
logger.error(new Error("Failed to prefetch widget", { cause: error }));
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<IntegrationProvider integrations={integrations}>
|
||||
<DynamicClientBoard />
|
||||
</IntegrationProvider>
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<IntegrationProvider integrations={integrations}>
|
||||
<DynamicClientBoard />
|
||||
</IntegrationProvider>
|
||||
</HydrationBoundary>
|
||||
);
|
||||
},
|
||||
generateMetadataAsync: async ({ params }: { params: Promise<TParams> }): Promise<Metadata> => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import "@homarr/ui/styles.css";
|
||||
import "~/styles/scroll-area.scss";
|
||||
|
||||
import { notFound } from "next/navigation";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
|
||||
import { api } from "@homarr/api/server";
|
||||
@@ -87,7 +88,15 @@ export default async function Layout(props: {
|
||||
},
|
||||
(innerProps) => (
|
||||
<SettingsProvider
|
||||
user={user}
|
||||
user={
|
||||
user
|
||||
? {
|
||||
...user,
|
||||
// Convert type, because output schema is not smart enough to infer $type from drizzle
|
||||
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
|
||||
}
|
||||
: null
|
||||
}
|
||||
serverSettings={{
|
||||
board: {
|
||||
homeBoardId: serverSettings.board.homeBoardId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import type { SettingsContextProps } from "@homarr/settings";
|
||||
import type { SettingsContextProps } from "@homarr/settings/creator";
|
||||
import type { WidgetComponentProps } from "@homarr/widgets";
|
||||
import { reduceWidgetOptionsWithDefaultValues } from "@homarr/widgets";
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.15.2",
|
||||
"@types/node": "^22.15.3",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"eslint": "^9.25.1",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.3",
|
||||
"tsx": "4.19.4",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"tsx": "4.19.3",
|
||||
"tsx": "4.19.4",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"semantic-release": "^24.2.3",
|
||||
"testcontainers": "^10.24.2",
|
||||
"testcontainers": "^10.25.0",
|
||||
"turbo": "^2.5.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.9.0",
|
||||
"packageManager": "pnpm@10.10.0",
|
||||
"engines": {
|
||||
"node": ">=22.15.0"
|
||||
},
|
||||
@@ -72,9 +72,6 @@
|
||||
"overrides": {
|
||||
"proxmox-api>undici": "7.8.0"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"pretty-print-error": "patches/pretty-print-error.patch"
|
||||
},
|
||||
"allowUnusedPatches": true,
|
||||
"ignoredBuiltDependencies": [
|
||||
"@scarf/scarf",
|
||||
|
||||
@@ -41,12 +41,13 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.1.2",
|
||||
"@trpc/client": "^11.1.1",
|
||||
"@trpc/react-query": "^11.1.1",
|
||||
"@trpc/server": "^11.1.1",
|
||||
"@tanstack/react-query": "^5.75.1",
|
||||
"@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",
|
||||
"pretty-print-error": "^1.1.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"superjson": "2.2.2",
|
||||
|
||||
@@ -20,7 +20,7 @@ export type IntegrationAction = "query" | "interact";
|
||||
* @param action query for showing data or interact for mutating data
|
||||
* @param kinds kinds of integrations that are supported
|
||||
* @returns middleware that can be used with trpc
|
||||
* @example publicProcedure.unstable_concat(createOneIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
|
||||
* @example publicProcedure.concat(createOneIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
|
||||
* @throws TRPCError NOT_FOUND if the integration was not found
|
||||
* @throws TRPCError FORBIDDEN if the user does not have permission to perform the specified action on the specified integration
|
||||
*/
|
||||
@@ -82,7 +82,7 @@ export const createOneIntegrationMiddleware = <TKind extends IntegrationKind>(
|
||||
* @param action query for showing data or interact for mutating data
|
||||
* @param kinds kinds of integrations that are supported
|
||||
* @returns middleware that can be used with trpc
|
||||
* @example publicProcedure.unstable_concat(createManyIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
|
||||
* @example publicProcedure.concat(createManyIntegrationMiddleware("query", "piHole", "homeAssistant")).query(...)
|
||||
* @throws TRPCError NOT_FOUND if the integration was not found
|
||||
* @throws TRPCError FORBIDDEN if the user does not have permission to perform the specified action on at least one of the specified integrations
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@ const dockerCache = createCacheChannel<{
|
||||
export const dockerRouter = createTRPCRouter({
|
||||
getContainers: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(dockerMiddleware())
|
||||
.concat(dockerMiddleware())
|
||||
.query(async () => {
|
||||
const result = await dockerCache
|
||||
.consumeAsync(async () => {
|
||||
@@ -81,14 +81,14 @@ export const dockerRouter = createTRPCRouter({
|
||||
}),
|
||||
invalidate: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(dockerMiddleware())
|
||||
.concat(dockerMiddleware())
|
||||
.mutation(async () => {
|
||||
await dockerCache.invalidateAsync();
|
||||
return;
|
||||
}),
|
||||
startAll: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(dockerMiddleware())
|
||||
.concat(dockerMiddleware())
|
||||
.input(z.object({ ids: z.array(z.string()) }))
|
||||
.mutation(async ({ input }) => {
|
||||
await Promise.allSettled(
|
||||
@@ -102,7 +102,7 @@ export const dockerRouter = createTRPCRouter({
|
||||
}),
|
||||
stopAll: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(dockerMiddleware())
|
||||
.concat(dockerMiddleware())
|
||||
.input(z.object({ ids: z.array(z.string()) }))
|
||||
.mutation(async ({ input }) => {
|
||||
await Promise.allSettled(
|
||||
@@ -116,7 +116,7 @@ export const dockerRouter = createTRPCRouter({
|
||||
}),
|
||||
restartAll: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(dockerMiddleware())
|
||||
.concat(dockerMiddleware())
|
||||
.input(z.object({ ids: z.array(z.string()) }))
|
||||
.mutation(async ({ input }) => {
|
||||
await Promise.allSettled(
|
||||
@@ -130,7 +130,7 @@ export const dockerRouter = createTRPCRouter({
|
||||
}),
|
||||
removeAll: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(dockerMiddleware())
|
||||
.concat(dockerMiddleware())
|
||||
.input(z.object({ ids: z.array(z.string()) }))
|
||||
.mutation(async ({ input }) => {
|
||||
await Promise.allSettled(
|
||||
|
||||
@@ -467,7 +467,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
searchInIntegration: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("search")))
|
||||
.concat(createOneIntegrationMiddleware("query", ...getIntegrationKindsByCategory("search")))
|
||||
.input(z.object({ integrationId: z.string(), query: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const integrationInstance = await createIntegrationAsync(ctx.integration);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { MemoryResourceParser } from "../resource-parser/memory-resource-parser"
|
||||
export const clusterRouter = createTRPCRouter({
|
||||
getCluster: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesCluster> => {
|
||||
const { coreApi, metricsApi, versionApi, kubeConfig } = KubernetesClient.getInstance();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const configMapsRouter = createTRPCRouter({
|
||||
getConfigMaps: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesBaseResource[]> => {
|
||||
const { coreApi } = KubernetesClient.getInstance();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const ingressesRouter = createTRPCRouter({
|
||||
getIngresses: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesIngress[]> => {
|
||||
const { networkingApi } = KubernetesClient.getInstance();
|
||||
try {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const namespacesRouter = createTRPCRouter({
|
||||
getNamespaces: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesNamespace[]> => {
|
||||
const { coreApi } = KubernetesClient.getInstance();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { MemoryResourceParser } from "../resource-parser/memory-resource-parser"
|
||||
export const nodesRouter = createTRPCRouter({
|
||||
getNodes: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesNode[]> => {
|
||||
const { coreApi, metricsApi } = KubernetesClient.getInstance();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const podsRouter = createTRPCRouter({
|
||||
getPods: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesPod[]> => {
|
||||
const { coreApi, kubeConfig } = KubernetesClient.getInstance();
|
||||
try {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const secretsRouter = createTRPCRouter({
|
||||
getSecrets: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesSecret[]> => {
|
||||
const { coreApi } = KubernetesClient.getInstance();
|
||||
try {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const servicesRouter = createTRPCRouter({
|
||||
getServices: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesService[]> => {
|
||||
const { coreApi } = KubernetesClient.getInstance();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { KubernetesClient } from "../kubernetes-client";
|
||||
export const volumesRouter = createTRPCRouter({
|
||||
getVolumes: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.unstable_concat(kubernetesMiddleware())
|
||||
.concat(kubernetesMiddleware())
|
||||
.query(async (): Promise<KubernetesVolume[]> => {
|
||||
const { coreApi } = KubernetesClient.getInstance();
|
||||
|
||||
|
||||
@@ -133,14 +133,14 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
getMediaRequestOptions: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
|
||||
.concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
|
||||
.input(mediaRequestOptionsSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const integration = await createIntegrationAsync(ctx.integration);
|
||||
return await integration.getSeriesInformationAsync(input.mediaType, input.mediaId);
|
||||
}),
|
||||
requestMedia: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
|
||||
.concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
|
||||
.input(mediaRequestRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const integration = await createIntegrationAsync(ctx.integration);
|
||||
|
||||
@@ -17,7 +17,7 @@ export const calendarRouter = createTRPCRouter({
|
||||
showUnmonitored: z.boolean(),
|
||||
}),
|
||||
)
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("calendar")))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trp
|
||||
|
||||
export const dnsHoleRouter = createTRPCRouter({
|
||||
summary: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -36,7 +36,7 @@ export const dnsHoleRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
subscribeToSummary: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"dnsHole"> }>;
|
||||
@@ -63,7 +63,7 @@ export const dnsHoleRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
enable: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.mutation(async ({ ctx: { integration } }) => {
|
||||
const client = await createIntegrationAsync(integration);
|
||||
await client.enableAsync();
|
||||
@@ -81,7 +81,7 @@ export const dnsHoleRouter = createTRPCRouter({
|
||||
duration: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole")))
|
||||
.mutation(async ({ ctx: { integration }, input }) => {
|
||||
const client = await createIntegrationAsync(integration);
|
||||
await client.disableAsync(input.duration);
|
||||
|
||||
@@ -18,7 +18,7 @@ const createDownloadClientIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
|
||||
export const downloadsRouter = createTRPCRouter({
|
||||
getJobsAndStatuses: publicProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("query"))
|
||||
.concat(createDownloadClientIntegrationMiddleware("query"))
|
||||
.query(async ({ ctx }) => {
|
||||
return await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -39,7 +39,7 @@ export const downloadsRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
subscribeToJobsAndStatuses: publicProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("query"))
|
||||
.concat(createDownloadClientIntegrationMiddleware("query"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"downloadClient"> }>;
|
||||
@@ -64,18 +64,16 @@ export const downloadsRouter = createTRPCRouter({
|
||||
};
|
||||
});
|
||||
}),
|
||||
pause: protectedProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.mutation(async ({ ctx }) => {
|
||||
await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
await integrationInstance.pauseQueueAsync();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
pause: protectedProcedure.concat(createDownloadClientIntegrationMiddleware("interact")).mutation(async ({ ctx }) => {
|
||||
await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
await integrationInstance.pauseQueueAsync();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
pauseItem: protectedProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.input(z.object({ item: downloadClientItemSchema }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await Promise.all(
|
||||
@@ -85,18 +83,16 @@ export const downloadsRouter = createTRPCRouter({
|
||||
}),
|
||||
);
|
||||
}),
|
||||
resume: protectedProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.mutation(async ({ ctx }) => {
|
||||
await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
await integrationInstance.resumeQueueAsync();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
resume: protectedProcedure.concat(createDownloadClientIntegrationMiddleware("interact")).mutation(async ({ ctx }) => {
|
||||
await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
await integrationInstance.resumeQueueAsync();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
resumeItem: protectedProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.input(z.object({ item: downloadClientItemSchema }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await Promise.all(
|
||||
@@ -107,7 +103,7 @@ export const downloadsRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
deleteItem: protectedProcedure
|
||||
.unstable_concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.concat(createDownloadClientIntegrationMiddleware("interact"))
|
||||
.input(z.object({ item: downloadClientItemSchema, fromDisk: z.boolean() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await Promise.all(
|
||||
|
||||
@@ -9,7 +9,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const healthMonitoringRouter = createTRPCRouter({
|
||||
getSystemHealthStatus: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
|
||||
.query(async ({ ctx }) => {
|
||||
return await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -26,7 +26,7 @@ export const healthMonitoringRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
subscribeSystemHealthStatus: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
|
||||
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{ integrationId: string; healthInfo: HealthMonitoring; timestamp: Date }>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
@@ -49,14 +49,14 @@ export const healthMonitoringRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
getClusterHealthStatus: publicProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("query", "proxmox"))
|
||||
.concat(createOneIntegrationMiddleware("query", "proxmox"))
|
||||
.query(async ({ ctx }) => {
|
||||
const innerHandler = clusterInfoRequestHandler.handler(ctx.integration, {});
|
||||
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
return data;
|
||||
}),
|
||||
subscribeClusterHealthStatus: publicProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("query", "proxmox"))
|
||||
.concat(createOneIntegrationMiddleware("query", "proxmox"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<ProxmoxClusterInfo>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
|
||||
@@ -16,7 +16,7 @@ const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
|
||||
export const indexerManagerRouter = createTRPCRouter({
|
||||
getIndexersStatus: publicProcedure
|
||||
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -33,7 +33,7 @@ export const indexerManagerRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
subscribeIndexersStatus: publicProcedure
|
||||
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{ integrationId: string; indexers: Indexer[] }>((emit) => {
|
||||
const unsubscribes: (() => void)[] = [];
|
||||
@@ -55,7 +55,7 @@ export const indexerManagerRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
testAllIndexers: protectedProcedure
|
||||
.unstable_concat(createIndexerManagerIntegrationMiddleware("interact"))
|
||||
.concat(createIndexerManagerIntegrationMiddleware("interact"))
|
||||
.mutation(async ({ ctx }) => {
|
||||
await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trp
|
||||
|
||||
export const mediaRequestsRouter = createTRPCRouter({
|
||||
getLatestRequests: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -39,7 +39,7 @@ export const mediaRequestsRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
subscribeToLatestRequests: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integrationId: string;
|
||||
@@ -65,7 +65,7 @@ export const mediaRequestsRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
getStats: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -91,7 +91,7 @@ export const mediaRequestsRouter = createTRPCRouter({
|
||||
};
|
||||
}),
|
||||
answerRequest: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("mediaRequest")))
|
||||
.input(z.object({ requestId: z.number(), answer: z.enum(["approve", "decline"]) }))
|
||||
.mutation(async ({ ctx: { integration }, input }) => {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
|
||||
@@ -14,7 +14,7 @@ const createMediaServerIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
|
||||
export const mediaServerRouter = createTRPCRouter({
|
||||
getCurrentStreams: publicProcedure
|
||||
.unstable_concat(createMediaServerIntegrationMiddleware("query"))
|
||||
.concat(createMediaServerIntegrationMiddleware("query"))
|
||||
.input(z.object({ showOnlyPlaying: z.boolean() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await Promise.all(
|
||||
@@ -32,7 +32,7 @@ export const mediaServerRouter = createTRPCRouter({
|
||||
);
|
||||
}),
|
||||
subscribeToCurrentStreams: publicProcedure
|
||||
.unstable_concat(createMediaServerIntegrationMiddleware("query"))
|
||||
.concat(createMediaServerIntegrationMiddleware("query"))
|
||||
.input(z.object({ showOnlyPlaying: z.boolean() }))
|
||||
.subscription(({ ctx, input }) => {
|
||||
return observable<{ integrationId: string; data: StreamSession[] }>((emit) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
|
||||
export const mediaTranscodingRouter = createTRPCRouter({
|
||||
getDataAsync: publicProcedure
|
||||
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.input(paginatedSchema.pick({ page: true, pageSize: true }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const networkControllerRouter = createTRPCRouter({
|
||||
summary: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
|
||||
.query(async ({ ctx }) => {
|
||||
const results = await Promise.all(
|
||||
ctx.integrations.map(async (integration) => {
|
||||
@@ -34,7 +34,7 @@ export const networkControllerRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
subscribeToSummary: publicProcedure
|
||||
.unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
|
||||
.concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("networkController")))
|
||||
.subscription(({ ctx }) => {
|
||||
return observable<{
|
||||
integration: Modify<Integration, { kind: IntegrationKindByCategory<"networkController"> }>;
|
||||
|
||||
@@ -15,14 +15,14 @@ const createSmartHomeIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
export const smartHomeRouter = createTRPCRouter({
|
||||
entityState: publicProcedure
|
||||
.input(z.object({ entityId: z.string() }))
|
||||
.unstable_concat(createSmartHomeIntegrationMiddleware("query"))
|
||||
.concat(createSmartHomeIntegrationMiddleware("query"))
|
||||
.query(async ({ ctx: { integration }, input }) => {
|
||||
const innerHandler = smartHomeEntityStateRequestHandler.handler(integration, { entityId: input.entityId });
|
||||
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
return data;
|
||||
}),
|
||||
subscribeEntityState: publicProcedure
|
||||
.unstable_concat(createSmartHomeIntegrationMiddleware("query"))
|
||||
.concat(createSmartHomeIntegrationMiddleware("query"))
|
||||
.input(z.object({ entityId: z.string() }))
|
||||
.subscription(({ input, ctx }) => {
|
||||
return observable<{
|
||||
@@ -42,7 +42,7 @@ export const smartHomeRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
switchEntity: protectedProcedure
|
||||
.unstable_concat(createSmartHomeIntegrationMiddleware("interact"))
|
||||
.concat(createSmartHomeIntegrationMiddleware("interact"))
|
||||
.input(z.object({ entityId: z.string() }))
|
||||
.mutation(async ({ ctx: { integration }, input }) => {
|
||||
const client = await createIntegrationAsync(integration);
|
||||
@@ -54,7 +54,7 @@ export const smartHomeRouter = createTRPCRouter({
|
||||
return success;
|
||||
}),
|
||||
executeAutomation: protectedProcedure
|
||||
.unstable_concat(createSmartHomeIntegrationMiddleware("interact"))
|
||||
.concat(createSmartHomeIntegrationMiddleware("interact"))
|
||||
.input(z.object({ automationId: z.string() }))
|
||||
.mutation(async ({ ctx: { integration }, input }) => {
|
||||
const client = await createIntegrationAsync(integration);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { cache } from "react";
|
||||
import { headers } from "next/headers";
|
||||
import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
|
||||
|
||||
import { createCaller, createTRPCContext } from "@homarr/api";
|
||||
import { appRouter, createCaller, createTRPCContext } from "@homarr/api";
|
||||
import { auth } from "@homarr/auth/next";
|
||||
|
||||
import { makeQueryClient } from "./shared";
|
||||
|
||||
/**
|
||||
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
|
||||
* handling a tRPC call from a React Server Component.
|
||||
@@ -19,3 +22,12 @@ const createContext = cache(async () => {
|
||||
});
|
||||
|
||||
export const api = createCaller(createContext);
|
||||
|
||||
// IMPORTANT: Create a stable getter for the query client that
|
||||
// will return the same client during the same request.
|
||||
export const getQueryClient = cache(makeQueryClient);
|
||||
export const trpc = createTRPCOptionsProxy({
|
||||
ctx: createContext,
|
||||
router: appRouter,
|
||||
queryClient: getQueryClient,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { defaultShouldDehydrateQuery, QueryClient } from "@tanstack/react-query";
|
||||
|
||||
/**
|
||||
* Creates a headers callback for a given source
|
||||
* It will set the x-trpc-source header and cookies if needed
|
||||
@@ -51,3 +53,16 @@ export const trpcPath = "/api/trpc";
|
||||
export function getTrpcUrl() {
|
||||
return `${getBaseUrl()}${trpcPath}`;
|
||||
}
|
||||
|
||||
export const makeQueryClient = () => {
|
||||
return new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 5 * 1000,
|
||||
},
|
||||
dehydrate: {
|
||||
shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending",
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"ldapts": "7.4.0",
|
||||
"next": "15.3.1",
|
||||
"next-auth": "5.0.0-beta.27",
|
||||
"pretty-print-error": "^1.1.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.24.3"
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
import type { CookieSerializeOptions } from "cookie";
|
||||
import { serialize } from "cookie";
|
||||
import { parse, serialize } from "cookie";
|
||||
|
||||
export function parseCookies(cookieString: string) {
|
||||
const list: Record<string, string> = {};
|
||||
const cookieHeader = cookieString;
|
||||
if (!cookieHeader) return list;
|
||||
|
||||
cookieHeader.split(";").forEach(function (cookie) {
|
||||
const items = cookie.split("=");
|
||||
let name = items.shift();
|
||||
name = name?.trim();
|
||||
if (!name) return;
|
||||
const value = items.join("=").trim();
|
||||
if (!value) return;
|
||||
list[name] = decodeURIComponent(value);
|
||||
});
|
||||
|
||||
return list;
|
||||
return parse(cookieString);
|
||||
}
|
||||
|
||||
export function setClientCookie(name: string, value: string, options: CookieSerializeOptions = {}) {
|
||||
|
||||
40
packages/common/src/test/url.spec.ts
Normal file
40
packages/common/src/test/url.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { getPortFromUrl } from "../url";
|
||||
|
||||
describe("getPortFromUrl", () => {
|
||||
test.each([
|
||||
[80, "http"],
|
||||
[443, "https"],
|
||||
])("should return %s for %s protocol without port", (expectedPort, protocol) => {
|
||||
// Arrange
|
||||
const url = new URL(`${protocol}://example.com`);
|
||||
|
||||
// Act
|
||||
const port = getPortFromUrl(url);
|
||||
|
||||
// Assert
|
||||
expect(port).toBe(expectedPort);
|
||||
});
|
||||
test.each([["http"], ["https"], ["anything"]])("should return the specified port for %s protocol", (protocol) => {
|
||||
// Arrange
|
||||
const expectedPort = 3000;
|
||||
const url = new URL(`${protocol}://example.com:${expectedPort}`);
|
||||
|
||||
// Act
|
||||
const port = getPortFromUrl(url);
|
||||
|
||||
// Assert
|
||||
expect(port).toBe(expectedPort);
|
||||
});
|
||||
test("should throw an error for unsupported protocol", () => {
|
||||
// Arrange
|
||||
const url = new URL("ftp://example.com");
|
||||
|
||||
// Act
|
||||
const act = () => getPortFromUrl(url);
|
||||
|
||||
// Act & Assert
|
||||
expect(act).toThrowError("Unsupported protocol: ftp:");
|
||||
});
|
||||
});
|
||||
@@ -21,3 +21,20 @@ export const extractBaseUrlFromHeaders = (
|
||||
|
||||
return `${protocol}://${host}`;
|
||||
};
|
||||
|
||||
export const getPortFromUrl = (url: URL): number => {
|
||||
const port = url.port;
|
||||
if (port) {
|
||||
return Number(port);
|
||||
}
|
||||
|
||||
if (url.protocol === "https:") {
|
||||
return 443;
|
||||
}
|
||||
|
||||
if (url.protocol === "http:") {
|
||||
return 80;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported protocol: ${url.protocol}`);
|
||||
};
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
"@homarr/env": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^10.24.2",
|
||||
"@testcontainers/mysql": "^10.25.0",
|
||||
"better-sqlite3": "^11.9.1",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-kit": "^0.31.0",
|
||||
@@ -62,7 +62,7 @@
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"eslint": "^9.25.1",
|
||||
"prettier": "^3.5.3",
|
||||
"tsx": "4.19.3",
|
||||
"tsx": "4.19.4",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/proxmox"
|
||||
| "/docs/tags/proxy"
|
||||
| "/docs/tags/puid"
|
||||
| "/docs/tags/releases"
|
||||
| "/docs/tags/repositories"
|
||||
| "/docs/tags/responsive"
|
||||
| "/docs/tags/roles"
|
||||
| "/docs/tags/rss"
|
||||
@@ -212,6 +214,7 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/widgets/media-server"
|
||||
| "/docs/widgets/minecraft-server-status"
|
||||
| "/docs/widgets/notebook"
|
||||
| "/docs/widgets/releases"
|
||||
| "/docs/widgets/rss"
|
||||
| "/docs/widgets/stocks"
|
||||
| "/docs/widgets/video"
|
||||
|
||||
2
packages/env/package.json
vendored
2
packages/env/package.json
vendored
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.0",
|
||||
"@t3-oss/env-nextjs": "^0.13.4",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "^7.17.5",
|
||||
"@mantine/form": "^7.17.7",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"react": "19.1.0",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
|
||||
@@ -48,6 +48,7 @@ export enum MediaRequestStatus {
|
||||
Approved = 2,
|
||||
Declined = 3,
|
||||
Failed = 4,
|
||||
Completed = 5,
|
||||
}
|
||||
|
||||
export enum MediaAvailability {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { SiteStats } from "node-unifi";
|
||||
import { Controller } from "node-unifi";
|
||||
|
||||
import { getPortFromUrl } from "@homarr/common";
|
||||
|
||||
import { Integration } from "../base/integration";
|
||||
import type { NetworkControllerSummaryIntegration } from "../interfaces/network-controller-summary/network-controller-summary-integration";
|
||||
import type { NetworkControllerSummary } from "../interfaces/network-controller-summary/network-controller-summary-types";
|
||||
@@ -42,20 +44,16 @@ export class UnifiControllerIntegration extends Integration implements NetworkCo
|
||||
}
|
||||
|
||||
private async createControllerClientAsync() {
|
||||
const portString = new URL(this.integration.url).port;
|
||||
const port = Number.isInteger(portString) ? Number(portString) : undefined;
|
||||
const hostname = new URL(this.integration.url).hostname;
|
||||
const url = new URL(this.integration.url);
|
||||
|
||||
const client = new Controller({
|
||||
host: hostname,
|
||||
// @ts-expect-error the URL construction is incorrect and does not append the required / at the end: https://github.com/jens-maus/node-unifi/blob/05665e8f82a900a15a9ea8b1071750b29825b3bc/unifi.js#L56, https://github.com/jens-maus/node-unifi/blob/05665e8f82a900a15a9ea8b1071750b29825b3bc/unifi.js#L95
|
||||
port: port === undefined ? "/" : `${port}/`,
|
||||
host: url.hostname,
|
||||
port: getPortFromUrl(url),
|
||||
sslverify: false, // TODO: implement a "ignore certificate toggle", see https://github.com/homarr-labs/homarr/issues/2553
|
||||
username: this.getSecretValue("username"),
|
||||
password: this.getSecretValue("password"),
|
||||
});
|
||||
|
||||
// Object.defineProperty(client, '_baseurl', { value: url });
|
||||
await client.login(this.getSecretValue("username"), this.getSecretValue("password"), null);
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "15.3.1",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"dependencies": {
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"react": "19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/notifications": "^7.17.5",
|
||||
"@mantine/notifications": "^7.17.7",
|
||||
"@tabler/icons-react": "^3.31.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"adm-zip": "0.5.16",
|
||||
"next": "15.3.1",
|
||||
"react": "19.1.0",
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"pretty-print-error": "^1.1.2"
|
||||
"@homarr/log": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"octokit": "^4.1.3",
|
||||
"pretty-print-error": "^1.1.2",
|
||||
"superjson": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
".": "./index.ts",
|
||||
"./creator": "./src/creator.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
@@ -25,7 +26,7 @@
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/dates": "^7.17.5",
|
||||
"@mantine/dates": "^7.17.7",
|
||||
"next": "15.3.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
|
||||
@@ -2,30 +2,9 @@
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { createContext, useContext } from "react";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import type { User } from "@homarr/db/schema";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
|
||||
export type SettingsContextProps = Pick<
|
||||
User,
|
||||
| "firstDayOfWeek"
|
||||
| "defaultSearchEngineId"
|
||||
| "homeBoardId"
|
||||
| "mobileHomeBoardId"
|
||||
| "openSearchInNewTab"
|
||||
| "pingIconsEnabled"
|
||||
> &
|
||||
Pick<ServerSettings["board"], "enableStatusByDefault" | "forceDisableStatus">;
|
||||
|
||||
interface PublicServerSettings {
|
||||
search: Pick<ServerSettings["search"], "defaultSearchEngineId">;
|
||||
board: Pick<
|
||||
ServerSettings["board"],
|
||||
"homeBoardId" | "mobileHomeBoardId" | "enableStatusByDefault" | "forceDisableStatus"
|
||||
>;
|
||||
}
|
||||
import type { PublicServerSettings, SettingsContextProps, UserSettings } from "./creator";
|
||||
import { createSettings } from "./creator";
|
||||
|
||||
const SettingsContext = createContext<SettingsContextProps | null>(null);
|
||||
|
||||
@@ -33,22 +12,9 @@ export const SettingsProvider = ({
|
||||
user,
|
||||
serverSettings,
|
||||
children,
|
||||
}: PropsWithChildren<{ user: RouterOutputs["user"]["getById"] | null; serverSettings: PublicServerSettings }>) => {
|
||||
}: PropsWithChildren<{ user: UserSettings | null; serverSettings: PublicServerSettings }>) => {
|
||||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{
|
||||
defaultSearchEngineId: user?.defaultSearchEngineId ?? serverSettings.search.defaultSearchEngineId,
|
||||
openSearchInNewTab: user?.openSearchInNewTab ?? true,
|
||||
firstDayOfWeek: (user?.firstDayOfWeek as DayOfWeek | undefined) ?? (1 as const),
|
||||
homeBoardId: user?.homeBoardId ?? serverSettings.board.homeBoardId,
|
||||
mobileHomeBoardId: user?.mobileHomeBoardId ?? serverSettings.board.mobileHomeBoardId,
|
||||
pingIconsEnabled: user?.pingIconsEnabled ?? false,
|
||||
enableStatusByDefault: serverSettings.board.enableStatusByDefault,
|
||||
forceDisableStatus: serverSettings.board.forceDisableStatus,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SettingsContext.Provider>
|
||||
<SettingsContext.Provider value={createSettings({ user, serverSettings })}>{children}</SettingsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
48
packages/settings/src/creator.ts
Normal file
48
packages/settings/src/creator.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { User } from "@homarr/db/schema";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
|
||||
export type SettingsContextProps = Pick<
|
||||
User,
|
||||
| "firstDayOfWeek"
|
||||
| "defaultSearchEngineId"
|
||||
| "homeBoardId"
|
||||
| "mobileHomeBoardId"
|
||||
| "openSearchInNewTab"
|
||||
| "pingIconsEnabled"
|
||||
> &
|
||||
Pick<ServerSettings["board"], "enableStatusByDefault" | "forceDisableStatus">;
|
||||
|
||||
export interface PublicServerSettings {
|
||||
search: Pick<ServerSettings["search"], "defaultSearchEngineId">;
|
||||
board: Pick<
|
||||
ServerSettings["board"],
|
||||
"homeBoardId" | "mobileHomeBoardId" | "enableStatusByDefault" | "forceDisableStatus"
|
||||
>;
|
||||
}
|
||||
|
||||
export type UserSettings = Pick<
|
||||
User,
|
||||
| "firstDayOfWeek"
|
||||
| "defaultSearchEngineId"
|
||||
| "homeBoardId"
|
||||
| "mobileHomeBoardId"
|
||||
| "openSearchInNewTab"
|
||||
| "pingIconsEnabled"
|
||||
>;
|
||||
|
||||
export const createSettings = ({
|
||||
user,
|
||||
serverSettings,
|
||||
}: {
|
||||
user: UserSettings | null;
|
||||
serverSettings: PublicServerSettings;
|
||||
}) => ({
|
||||
defaultSearchEngineId: user?.defaultSearchEngineId ?? serverSettings.search.defaultSearchEngineId,
|
||||
openSearchInNewTab: user?.openSearchInNewTab ?? true,
|
||||
firstDayOfWeek: user?.firstDayOfWeek ?? (1 as const),
|
||||
homeBoardId: user?.homeBoardId ?? serverSettings.board.homeBoardId,
|
||||
mobileHomeBoardId: user?.mobileHomeBoardId ?? serverSettings.board.mobileHomeBoardId,
|
||||
pingIconsEnabled: user?.pingIconsEnabled ?? false,
|
||||
enableStatusByDefault: serverSettings.board.enableStatusByDefault,
|
||||
forceDisableStatus: serverSettings.board.forceDisableStatus,
|
||||
});
|
||||
@@ -33,9 +33,9 @@
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mantine/spotlight": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@mantine/spotlight": "^7.17.7",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"jotai": "^2.12.3",
|
||||
"next": "15.3.1",
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "待处理",
|
||||
"approved": "已批准",
|
||||
"declined": "已拒绝",
|
||||
"failed": "失败"
|
||||
"failed": "失败",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "待定"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "Afventende",
|
||||
"approved": "Godkendt",
|
||||
"declined": "Afvist",
|
||||
"failed": "Mislykket"
|
||||
"failed": "Mislykket",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "TBD"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "Ausstehend",
|
||||
"approved": "Bestätigt",
|
||||
"declined": "Abgelehnt",
|
||||
"failed": "Fehlgeschlagen"
|
||||
"failed": "Fehlgeschlagen",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "Noch Festzulegen"
|
||||
},
|
||||
|
||||
@@ -618,11 +618,11 @@
|
||||
"title": ""
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"title": "Neue App erstellen",
|
||||
"description": "",
|
||||
"action": ""
|
||||
},
|
||||
"add": ""
|
||||
"add": "App hinzufügen"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
@@ -995,7 +995,7 @@
|
||||
},
|
||||
"option": {
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "Titel"
|
||||
},
|
||||
"borderColor": {
|
||||
"label": "Rahmenfarbe"
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "Ausstehend",
|
||||
"approved": "Bestätigt",
|
||||
"declined": "Abgelehnt",
|
||||
"failed": "Fehlgeschlagen"
|
||||
"failed": "Fehlgeschlagen",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "Noch Festzulegen"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"declined": "Declined",
|
||||
"failed": "Failed"
|
||||
"failed": "Failed",
|
||||
"completed": "Completed"
|
||||
},
|
||||
"toBeDetermined": "TBD"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "Bienvenido a Homarr",
|
||||
"subtitle": "",
|
||||
"subtitle": "Comencemos con la configuración de tu instancia de Homar.",
|
||||
"description": "Para empezar, por favor, seleccione cómo desea configurar su instancia de Homarr.",
|
||||
"action": {
|
||||
"scratch": "Empezar de cero",
|
||||
@@ -27,18 +27,18 @@
|
||||
"description": ""
|
||||
},
|
||||
"boardSelection": {
|
||||
"title": "",
|
||||
"title": "Se encontraron {count} tableros",
|
||||
"description": "",
|
||||
"action": {
|
||||
"selectAll": "",
|
||||
"unselectAll": ""
|
||||
"selectAll": "Seleccionar todo",
|
||||
"unselectAll": "Deseleccionar todo"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"title": "Importar resumen",
|
||||
"description": "",
|
||||
"action": {
|
||||
"import": ""
|
||||
"import": "Confirmar la importación y continuar"
|
||||
},
|
||||
"entities": {
|
||||
"apps": "Aplicaciones",
|
||||
@@ -78,24 +78,24 @@
|
||||
},
|
||||
"group": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"subtitle": "Especifique el grupo que debe ser usado para usuarios externos.",
|
||||
"form": {
|
||||
"name": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Nombre del grupo",
|
||||
"description": "El nombre debe coincidir con el grupo de administración del proveedor externo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes",
|
||||
"subtitle": ""
|
||||
"subtitle": "Configurar ajustes del servidor."
|
||||
},
|
||||
"finish": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"action": {
|
||||
"goToBoard": "",
|
||||
"goToBoard": "Ir al tablero {name}",
|
||||
"createBoard": "",
|
||||
"inviteUser": "",
|
||||
"docs": ""
|
||||
@@ -109,7 +109,7 @@
|
||||
"name": "Usuario",
|
||||
"page": {
|
||||
"login": {
|
||||
"title": "",
|
||||
"title": "Inicia sesión en tu cuenta",
|
||||
"subtitle": ""
|
||||
},
|
||||
"invite": {
|
||||
@@ -160,12 +160,12 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"usernameTaken": ""
|
||||
"usernameTaken": "Nombre de usuario ya está ocupado"
|
||||
},
|
||||
"action": {
|
||||
"login": {
|
||||
"label": "Iniciar sesión",
|
||||
"labelWith": "",
|
||||
"labelWith": "Iniciar sesión con {provider}",
|
||||
"notification": {
|
||||
"success": {
|
||||
"title": "",
|
||||
@@ -229,10 +229,10 @@
|
||||
"changeFirstDayOfWeek": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Primer día de la semana cambiado con éxito"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "No se ha podido cambiar el primer día de la semana"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -251,7 +251,7 @@
|
||||
"label": "",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "La imagen ha sido cambiada con éxito"
|
||||
},
|
||||
"error": {
|
||||
"message": "No se puede cambiar la imagen"
|
||||
@@ -267,7 +267,7 @@
|
||||
"confirm": "Por favor, confirma que deseas eliminar esta imagen",
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Imagen eliminada con éxito"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
@@ -278,7 +278,7 @@
|
||||
"editProfile": {
|
||||
"notification": {
|
||||
"success": {
|
||||
"message": ""
|
||||
"message": "Perfil actualizado con éxito"
|
||||
},
|
||||
"error": {
|
||||
"message": ""
|
||||
@@ -288,10 +288,10 @@
|
||||
"delete": {
|
||||
"label": "",
|
||||
"description": "",
|
||||
"confirm": ""
|
||||
"confirm": "¿Estás seguro de que quieres eliminar el usuario {username} con sus preferencias?"
|
||||
},
|
||||
"select": {
|
||||
"label": "",
|
||||
"label": "Seleccionar usuario",
|
||||
"notFound": ""
|
||||
},
|
||||
"transfer": {
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "En attente",
|
||||
"approved": "Approuvé",
|
||||
"declined": "Refusé",
|
||||
"failed": "Échec"
|
||||
"failed": "Échec",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "À déterminer"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "ממתין",
|
||||
"approved": "אושר",
|
||||
"declined": "נדחה",
|
||||
"failed": "נכשל"
|
||||
"failed": "נכשל",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "ייקבע בהמשך"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "In afwachting",
|
||||
"approved": "Goedgekeurd",
|
||||
"declined": "Afgewezen",
|
||||
"failed": "Mislukt"
|
||||
"failed": "Mislukt",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "TBD"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "Venter",
|
||||
"approved": "Godkjent",
|
||||
"declined": "Avslått",
|
||||
"failed": "Feilet"
|
||||
"failed": "Feilet",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "Uavklart"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "В ожидании",
|
||||
"approved": "Одобрено",
|
||||
"declined": "Отклонено",
|
||||
"failed": "Ошибка"
|
||||
"failed": "Ошибка",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "Будет определено позже"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "TBD"
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "Bekleyen",
|
||||
"approved": "Onaylandı",
|
||||
"declined": "Reddedildi",
|
||||
"failed": "Başarısız"
|
||||
"failed": "Başarısız",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": "-Yapım Aşamasında-"
|
||||
},
|
||||
@@ -2053,83 +2054,83 @@
|
||||
}
|
||||
},
|
||||
"releases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Sürümler",
|
||||
"description": "Verilen sürüm düzenli ifadesiyle, belirtilen depoların mevcut sürümlerinin bir listesini gösterir.",
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Yeni Sürüm Süresi (içinde)",
|
||||
"description": "Kullanım örneği: 1w (1 hafta), 10m (10 ay). Kabul edilen birim türleri: h (saat), d (gün), w (hafta), m (ay), y (yıl). Yeni sürümleri vurgulamak istemiyorsanız boş bırakın."
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Eski Sürüm Süresi (içinde)",
|
||||
"description": "Kullanım örneği: 1w (1 hafta), 10m (10 ay). Kabul edilen birim türleri h (saat), d (gün), w (hafta), m (ay), y (yıl). Eski sürümleri vurgulamak istemiyorsanız boş bırakın."
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Yalnızca Vurgulananları Göster",
|
||||
"description": "Yalnızca yeni veya eski sürümleri göster. Yukarıdaki açıklamaya göre."
|
||||
},
|
||||
"showDetails": {
|
||||
"label": ""
|
||||
"label": "Ayrıntıları Göster"
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"label": "Depolar",
|
||||
"addRRepository": {
|
||||
"label": ""
|
||||
"label": "Depo Ekle"
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
"label": "Sağlayıcı"
|
||||
},
|
||||
"identifier": {
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
"label": "Tanımlayıcı",
|
||||
"placeholder": "Ad veya Sahip/Ad"
|
||||
},
|
||||
"versionFilter": {
|
||||
"label": "",
|
||||
"label": "Sürüm Filtresi",
|
||||
"prefix": {
|
||||
"label": ""
|
||||
"label": "Önek"
|
||||
},
|
||||
"precision": {
|
||||
"label": "",
|
||||
"label": "Hassasiyet",
|
||||
"options": {
|
||||
"none": ""
|
||||
"none": "Hiçbiri"
|
||||
}
|
||||
},
|
||||
"suffix": {
|
||||
"label": ""
|
||||
"label": "Sonek"
|
||||
},
|
||||
"regex": {
|
||||
"label": ""
|
||||
"label": "Düzenli İfade"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"label": ""
|
||||
"label": "Düzenle"
|
||||
},
|
||||
"editForm": {
|
||||
"title": "",
|
||||
"title": "Depoyu Düzenle",
|
||||
"cancel": {
|
||||
"label": ""
|
||||
"label": "Vazgeç"
|
||||
},
|
||||
"confirm": {
|
||||
"label": ""
|
||||
"label": "Onayla"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
"label": "Örnek"
|
||||
},
|
||||
"invalid": ""
|
||||
"invalid": "Geçersiz depo tanımı, lütfen değerleri kontrol edin"
|
||||
}
|
||||
},
|
||||
"not-found": "",
|
||||
"pre-release": "",
|
||||
"archived": "",
|
||||
"forked": "",
|
||||
"starsCount": "",
|
||||
"forksCount": "",
|
||||
"issuesCount": "",
|
||||
"openProjectPage": "",
|
||||
"openReleasePage": "",
|
||||
"releaseDescription": "",
|
||||
"created": ""
|
||||
"not-found": "Bulunamadı",
|
||||
"pre-release": "Ön Sürüm",
|
||||
"archived": "Arşivlenmiş",
|
||||
"forked": "Çatallanmış",
|
||||
"starsCount": "Yıldızlar",
|
||||
"forksCount": "Çatallar",
|
||||
"issuesCount": "Sorunları Aç",
|
||||
"openProjectPage": "Proje Sayfasını Aç",
|
||||
"openReleasePage": "Sürüm Sayfasını Aç",
|
||||
"releaseDescription": "Sürüm Açıklaması",
|
||||
"created": "Oluşturuldu"
|
||||
},
|
||||
"networkControllerSummary": {
|
||||
"option": {},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": ""
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
},
|
||||
"toBeDetermined": ""
|
||||
},
|
||||
|
||||
@@ -1093,7 +1093,7 @@
|
||||
"label": "集成"
|
||||
},
|
||||
"title": {
|
||||
"label": ""
|
||||
"label": "標題"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": "自定義 CSS html"
|
||||
@@ -1771,8 +1771,8 @@
|
||||
"description": "顯示當前多媒體伺服器的串流",
|
||||
"option": {
|
||||
"showOnlyPlaying": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "只顯示目前播放中的項目",
|
||||
"description": "停用此功能將無法在 Plex 上運作"
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
@@ -1949,7 +1949,8 @@
|
||||
"pending": "待處理",
|
||||
"approved": "已批准",
|
||||
"declined": "已拒絕",
|
||||
"failed": "失敗"
|
||||
"failed": "失敗",
|
||||
"completed": "已完成"
|
||||
},
|
||||
"toBeDetermined": "多媒體請求狀態"
|
||||
},
|
||||
@@ -2053,83 +2054,83 @@
|
||||
}
|
||||
},
|
||||
"releases": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "版本發佈",
|
||||
"description": "顯示符合指定版本規則的儲存庫及其目前版本列表",
|
||||
"option": {
|
||||
"newReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "新版本發布於",
|
||||
"description": "使用範例:1w(1週)、10m(10個月)。接受的單位類型有:h(小時)、d(天)、w(週)、m(個月)、y(年)。若留空,則不會特別標示新版本"
|
||||
},
|
||||
"staleReleaseWithin": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "過時版本範圍內",
|
||||
"description": "使用範例:1w(1週)、10m(10個月)。接受的單位類型有:h(小時)、d(天)、w(週)、m(個月)、y(年)。若留空,則不會特別標示過時版本"
|
||||
},
|
||||
"showOnlyHighlighted": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "只顯示已標示的項目",
|
||||
"description": "只顯示新版本或過時版本。如上所述"
|
||||
},
|
||||
"showDetails": {
|
||||
"label": ""
|
||||
"label": "顯示詳情"
|
||||
},
|
||||
"repositories": {
|
||||
"label": "",
|
||||
"label": "儲存庫",
|
||||
"addRRepository": {
|
||||
"label": ""
|
||||
"label": "新增儲存庫"
|
||||
},
|
||||
"provider": {
|
||||
"label": ""
|
||||
"label": "提供者"
|
||||
},
|
||||
"identifier": {
|
||||
"label": "",
|
||||
"placeholder": ""
|
||||
"label": "識別碼",
|
||||
"placeholder": "名稱或擁有者/名稱"
|
||||
},
|
||||
"versionFilter": {
|
||||
"label": "",
|
||||
"label": "版本篩選",
|
||||
"prefix": {
|
||||
"label": ""
|
||||
"label": "前綴"
|
||||
},
|
||||
"precision": {
|
||||
"label": "",
|
||||
"label": "精確度",
|
||||
"options": {
|
||||
"none": ""
|
||||
"none": "無"
|
||||
}
|
||||
},
|
||||
"suffix": {
|
||||
"label": ""
|
||||
"label": "後綴"
|
||||
},
|
||||
"regex": {
|
||||
"label": ""
|
||||
"label": "正則表達式"
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"label": ""
|
||||
"label": "編輯"
|
||||
},
|
||||
"editForm": {
|
||||
"title": "",
|
||||
"title": "編輯儲存庫",
|
||||
"cancel": {
|
||||
"label": ""
|
||||
"label": "取消"
|
||||
},
|
||||
"confirm": {
|
||||
"label": ""
|
||||
"label": "確認"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"label": ""
|
||||
"label": "範例"
|
||||
},
|
||||
"invalid": ""
|
||||
"invalid": "無效的儲存庫定義,請檢查值的設定"
|
||||
}
|
||||
},
|
||||
"not-found": "",
|
||||
"pre-release": "",
|
||||
"archived": "",
|
||||
"forked": "",
|
||||
"starsCount": "",
|
||||
"forksCount": "",
|
||||
"issuesCount": "",
|
||||
"openProjectPage": "",
|
||||
"openReleasePage": "",
|
||||
"releaseDescription": "",
|
||||
"created": ""
|
||||
"not-found": "未找到",
|
||||
"pre-release": "預發佈",
|
||||
"archived": "已歸檔",
|
||||
"forked": "已分支",
|
||||
"starsCount": "星標",
|
||||
"forksCount": "分支數",
|
||||
"issuesCount": "開放問題",
|
||||
"openProjectPage": "開啟專案頁面",
|
||||
"openReleasePage": "開啟發布葉面",
|
||||
"releaseDescription": "版本描述",
|
||||
"created": "已創建"
|
||||
},
|
||||
"networkControllerSummary": {
|
||||
"option": {},
|
||||
@@ -3888,8 +3889,8 @@
|
||||
"title": "尚無憑證"
|
||||
},
|
||||
"invalid": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "無效的證書",
|
||||
"description": "解析證書失敗"
|
||||
},
|
||||
"expires": "到期 {when}"
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/dates": "^7.17.5",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/dates": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.3.1",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./errors": "./src/errors/component.tsx",
|
||||
"./modals": "./src/modals/index.ts"
|
||||
"./modals": "./src/modals/index.ts",
|
||||
"./prefetch": "./src/prefetch.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
@@ -35,6 +36,7 @@
|
||||
"@homarr/form": "workspace:^0.1.0",
|
||||
"@homarr/forms-collection": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/modals": "workspace:^0.1.0",
|
||||
"@homarr/modals-collection": "workspace:^0.1.0",
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
@@ -45,25 +47,25 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^7.17.5",
|
||||
"@mantine/core": "^7.17.5",
|
||||
"@mantine/hooks": "^7.17.5",
|
||||
"@mantine/charts": "^7.17.7",
|
||||
"@mantine/core": "^7.17.7",
|
||||
"@mantine/hooks": "^7.17.7",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@tiptap/extension-color": "2.11.7",
|
||||
"@tiptap/extension-highlight": "2.11.7",
|
||||
"@tiptap/extension-image": "2.11.7",
|
||||
"@tiptap/extension-link": "^2.11.7",
|
||||
"@tiptap/extension-table": "2.11.7",
|
||||
"@tiptap/extension-table-cell": "2.11.7",
|
||||
"@tiptap/extension-table-header": "2.11.7",
|
||||
"@tiptap/extension-table-row": "2.11.7",
|
||||
"@tiptap/extension-task-item": "2.11.7",
|
||||
"@tiptap/extension-task-list": "2.11.7",
|
||||
"@tiptap/extension-text-align": "2.11.7",
|
||||
"@tiptap/extension-text-style": "2.11.7",
|
||||
"@tiptap/extension-underline": "2.11.7",
|
||||
"@tiptap/react": "^2.11.7",
|
||||
"@tiptap/starter-kit": "^2.11.7",
|
||||
"@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",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
|
||||
23
packages/widgets/src/app/prefetch.ts
Normal file
23
packages/widgets/src/app/prefetch.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { trpc } from "@homarr/api/server";
|
||||
import { db, inArray } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { Prefetch } from "../definition";
|
||||
|
||||
const prefetchAllAsync: Prefetch<"app"> = async (queryClient, items) => {
|
||||
const appIds = items.map((item) => item.options.appId);
|
||||
const distinctAppIds = [...new Set(appIds)];
|
||||
|
||||
const dbApps = await db.query.apps.findMany({
|
||||
where: inArray(apps.id, distinctAppIds),
|
||||
});
|
||||
|
||||
for (const app of dbApps) {
|
||||
queryClient.setQueryData(trpc.app.byId.queryKey({ id: app.id }), app);
|
||||
}
|
||||
|
||||
logger.info(`Successfully prefetched ${dbApps.length} apps for app widget`);
|
||||
};
|
||||
|
||||
export default prefetchAllAsync;
|
||||
30
packages/widgets/src/bookmarks/prefetch.ts
Normal file
30
packages/widgets/src/bookmarks/prefetch.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { trpc } from "@homarr/api/server";
|
||||
import { db, inArray } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { Prefetch } from "../definition";
|
||||
|
||||
const prefetchAllAsync: Prefetch<"bookmarks"> = async (queryClient, items) => {
|
||||
const appIds = items.flatMap((item) => item.options.items);
|
||||
const distinctAppIds = [...new Set(appIds)];
|
||||
|
||||
const dbApps = await db.query.apps.findMany({
|
||||
where: inArray(apps.id, distinctAppIds),
|
||||
});
|
||||
|
||||
for (const item of items) {
|
||||
if (item.options.items.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queryClient.setQueryData(
|
||||
trpc.app.byIds.queryKey(item.options.items),
|
||||
dbApps.filter((app) => item.options.items.includes(app.id)),
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Successfully prefetched ${dbApps.length} apps for bookmarks`);
|
||||
};
|
||||
|
||||
export default prefetchAllAsync;
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { LoaderComponent } from "next/dynamic";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import type { DefaultErrorData } from "@trpc/server/unstable-core-do-not-import";
|
||||
|
||||
import type { IntegrationKind, WidgetKind } from "@homarr/definitions";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import type { SettingsContextProps } from "@homarr/settings";
|
||||
import type { SettingsContextProps } from "@homarr/settings/creator";
|
||||
import type { stringOrTranslation } from "@homarr/translation";
|
||||
import type { TablerIcon } from "@homarr/ui";
|
||||
|
||||
@@ -21,6 +22,15 @@ const createWithDynamicImport =
|
||||
componentLoader,
|
||||
});
|
||||
|
||||
export type PrefetchLoader<TKind extends WidgetKind> = () => Promise<{ default: Prefetch<TKind> }>;
|
||||
export type Prefetch<TKind extends WidgetKind> = (
|
||||
queryClient: QueryClient,
|
||||
items: {
|
||||
options: inferOptionsFromCreator<WidgetOptionsRecordOf<TKind>>;
|
||||
integrationIds: string[];
|
||||
}[],
|
||||
) => Promise<void>;
|
||||
|
||||
export const createWidgetDefinition = <TKind extends WidgetKind, TDefinition extends WidgetDefinition>(
|
||||
kind: TKind,
|
||||
definition: TDefinition,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Center, Loader as UiLoader } from "@mantine/core";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import type { IntegrationKind, WidgetKind } from "@homarr/definitions";
|
||||
import type { SettingsContextProps } from "@homarr/settings";
|
||||
import type { SettingsContextProps } from "@homarr/settings/creator";
|
||||
|
||||
import * as app from "./app";
|
||||
import * as bookmarks from "./bookmarks";
|
||||
|
||||
@@ -215,6 +215,7 @@ const statusMapping = {
|
||||
[MediaRequestStatus.Approved]: { color: "green", label: (t) => t("approved") },
|
||||
[MediaRequestStatus.Declined]: { color: "red", label: (t) => t("declined") },
|
||||
[MediaRequestStatus.Failed]: { color: "red", label: (t) => t("failed") },
|
||||
[MediaRequestStatus.Completed]: { color: "green", label: (t) => t("completed") },
|
||||
} satisfies Record<
|
||||
MediaRequestStatus,
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ import { objectEntries } from "@homarr/common";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { zodResolver } from "@homarr/form";
|
||||
import { createModal, useModalAction } from "@homarr/modals";
|
||||
import type { SettingsContextProps } from "@homarr/settings";
|
||||
import type { SettingsContextProps } from "@homarr/settings/creator";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { zodErrorMap } from "@homarr/validation/form/i18n";
|
||||
|
||||
|
||||
45
packages/widgets/src/prefetch.ts
Normal file
45
packages/widgets/src/prefetch.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { cache } from "react";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingsAsync } from "@homarr/db/queries";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { createSettings } from "@homarr/settings/creator";
|
||||
|
||||
import { reduceWidgetOptionsWithDefaultValues } from ".";
|
||||
import prefetchForApps from "./app/prefetch";
|
||||
import prefetchForBookmarks from "./bookmarks/prefetch";
|
||||
import type { Prefetch, WidgetOptionsRecordOf } from "./definition";
|
||||
import type { inferOptionsFromCreator } from "./options";
|
||||
|
||||
const cachedGetServerSettingsAsync = cache(getServerSettingsAsync);
|
||||
|
||||
const prefetchCallbacks: Partial<{
|
||||
[TKind in WidgetKind]: Prefetch<TKind>;
|
||||
}> = {
|
||||
bookmarks: prefetchForBookmarks,
|
||||
app: prefetchForApps,
|
||||
};
|
||||
|
||||
export const prefetchForKindAsync = async <TKind extends WidgetKind>(
|
||||
kind: TKind,
|
||||
queryClient: QueryClient,
|
||||
items: {
|
||||
options: inferOptionsFromCreator<WidgetOptionsRecordOf<TKind>>;
|
||||
integrationIds: string[];
|
||||
}[],
|
||||
) => {
|
||||
const callback = prefetchCallbacks[kind];
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverSettings = await cachedGetServerSettingsAsync(db);
|
||||
|
||||
const itemsWithDefaultOptions = items.map((item) => ({
|
||||
...item,
|
||||
options: reduceWidgetOptionsWithDefaultValues(kind, createSettings({ user: null, serverSettings }), item.options),
|
||||
}));
|
||||
|
||||
await callback(queryClient, itemsWithDefaultOptions as never[]);
|
||||
};
|
||||
@@ -141,7 +141,6 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
[classes.active ?? ""]: isActive,
|
||||
})}
|
||||
p="xs"
|
||||
wrap="nowrap"
|
||||
onClick={() => toggleExpandedRepository(repository.identifier)}
|
||||
>
|
||||
<MaskedOrNormalImage
|
||||
@@ -153,7 +152,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
}}
|
||||
/>
|
||||
|
||||
<Group gap={5} justify="space-between" style={{ flex: 1, minWidth: 0 }} wrap="nowrap">
|
||||
<Group gap={5} justify="space-between" style={{ flex: 1 }}>
|
||||
<Text size="xs">{repository.identifier}</Text>
|
||||
|
||||
<Tooltip label={repository.latestRelease ?? t("not-found")}>
|
||||
@@ -163,7 +162,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Group gap={5} wrap="nowrap">
|
||||
<Group gap={5}>
|
||||
<Text
|
||||
size="xs"
|
||||
c={repository.isNewRelease ? "primaryColor" : repository.isStaleRelease ? "secondaryColor" : "dimmed"}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
import type { SettingsContextProps } from "@homarr/settings";
|
||||
import type { SettingsContextProps } from "@homarr/settings/creator";
|
||||
import { createLanguageMapping } from "@homarr/translation";
|
||||
|
||||
import { widgetImports } from "..";
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
diff --git a/src/index.ts b/src/index.ts
|
||||
index c8f62a743b89040c1d10a8ae3795bec8afcbc134..7cd7c1e98c1c9516b40f678627f51fd1adfa18c7 100644
|
||||
--- a/src/index.ts
|
||||
+++ b/src/index.ts
|
||||
@@ -109,9 +109,9 @@ export function formatError(
|
||||
(name) => name !== "stack" && name !== "message"
|
||||
);
|
||||
if (propNames.length > 0) {
|
||||
- const props = {};
|
||||
+ const props: Record<string, unknown> = {};
|
||||
propNames.forEach((name) => {
|
||||
- props[name] = err[name];
|
||||
+ props[name] = (err as Record<string, object>)[name];
|
||||
});
|
||||
|
||||
let propertiesString;
|
||||
1428
pnpm-lock.yaml
generated
1428
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user