From 082077ec9d4d19388a36ee82a089730122caf516 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sat, 30 Dec 2023 20:11:38 +0100 Subject: [PATCH] feat: import to docker form board, docker button on boards (#1714) --- .../en/layout/element-selector/selector.json | 45 +++++++------ .../Overview/AvailableElementsOverview.tsx | 5 +- .../AvailableStaticElementsTab.tsx | 34 ---------- .../WidgetsTab/AvailableWidgetsTab.tsx | 6 +- .../SelectElement/SelectElementModal.tsx | 6 +- .../Tools/Docker/ContainerActionBar.tsx | 12 +--- .../Manage/Tools/Docker/ContainerTable.tsx | 17 +++-- .../Docker/docker-select-board.modal.tsx | 38 ++++++++--- .../layout/Templates/BoardLayout.tsx | 67 +++++++++++++++++-- src/pages/board/index.tsx | 40 ++++++++--- src/server/api/routers/app.ts | 16 +++-- src/server/api/routers/board.ts | 7 +- src/server/api/routers/docker/router.ts | 23 ++++++- src/server/api/routers/icon.ts | 51 +++++++------- src/server/api/routers/overseerr.ts | 4 +- src/tools/server/translation-namespaces.ts | 1 + 16 files changed, 234 insertions(+), 138 deletions(-) delete mode 100644 src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx diff --git a/public/locales/en/layout/element-selector/selector.json b/public/locales/en/layout/element-selector/selector.json index 3d2b934b02f..491d44ca8bf 100644 --- a/public/locales/en/layout/element-selector/selector.json +++ b/public/locales/en/layout/element-selector/selector.json @@ -1,25 +1,26 @@ { - "modal": { - "title": "Add a new tile", - "text": "Tiles are the main element of Homarr. They are used to display your apps and other information. You can add as many tiles as you want." - }, - "widgetDescription": "Widgets interact with your apps, to provide you with more control over your applications. They usually require additional configuration before use.", - "goBack": "Go back to the previous step", - "actionIcon": { - "tooltip": "Add a tile" - }, - "apps": "Apps", - "app": { - "defaultName": "Your App" - }, - "widgets": "Widgets", - "categories": "Categories", - "category": { - "newName": "Name of new category", - "defaultName": "New Category", - "created": { - "title": "Category created", - "message": "The category \"{{name}}\" has been created" - } + "modal": { + "title": "Add a new tile", + "text": "Tiles are the main element of Homarr. They are used to display your apps and other information. You can add as many tiles as you want." + }, + "widgetDescription": "Widgets interact with your apps, to provide you with more control over your applications. They usually require additional configuration before use.", + "goBack": "Go back to the previous step", + "actionIcon": { + "tooltip": "Add a tile" + }, + "apps": "Apps", + "app": { + "defaultName": "Your App" + }, + "widgets": "Widgets", + "categories": "Categories", + "category": { + "newName": "Name of new category", + "defaultName": "New Category", + "created": { + "title": "Category created", + "message": "The category \"{{name}}\" has been created" } + }, + "importFromDocker": "Import from docker" } diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx index c1b2045dba8..3c7ce526278 100644 --- a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx +++ b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx @@ -3,6 +3,7 @@ import { closeModal } from '@mantine/modals'; import { showNotification } from '@mantine/notifications'; import { IconBox, IconBoxAlignTop, IconStack } from '@tabler/icons-react'; import { motion } from 'framer-motion'; +import { useSession } from 'next-auth/react'; import { useTranslation } from 'next-i18next'; import { ReactNode } from 'react'; import { v4 as uuidv4 } from 'uuid'; @@ -18,17 +19,17 @@ import { useStyles } from '../Shared/styles'; interface AvailableElementTypesProps { modalId: string; onOpenIntegrations: () => void; - onOpenStaticElements: () => void; } export const AvailableElementTypes = ({ modalId, onOpenIntegrations: onOpenWidgets, - onOpenStaticElements, }: AvailableElementTypesProps) => { const { t } = useTranslation('layout/element-selector/selector'); const { config, name: configName } = useConfigContext(); const { updateConfig } = useConfigStore(); + const { data } = useSession(); + const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0]; const onClickCreateCategory = async () => { diff --git a/src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx b/src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx deleted file mode 100644 index 3c4a6682120..00000000000 --- a/src/components/Dashboard/Modals/SelectElement/Components/StaticElementsTab/AvailableStaticElementsTab.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Container, Grid, Text } from '@mantine/core'; -import { IconCursorText } from '@tabler/icons-react'; -import { useTranslation } from 'next-i18next'; - -import { GenericAvailableElementType } from '../Shared/GenericElementType'; -import { SelectorBackArrow } from '../Shared/SelectorBackArrow'; - -interface AvailableStaticTypesProps { - onClickBack: () => void; -} - -export const AvailableStaticTypes = ({ onClickBack }: AvailableStaticTypesProps) => { - const { t } = useTranslation('layout/element-selector/selector'); - return ( - <> - - - - Static elements provide you additional control over your dashboard. They are static, because - they don't integrate with any apps and their content never changes. - - - - {/* - {}} - /> */} - - - ); -}; diff --git a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx index d013567a38d..83831ac5fc6 100644 --- a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx +++ b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/AvailableWidgetsTab.tsx @@ -1,4 +1,4 @@ -import { Grid, Text } from '@mantine/core'; +import { Grid, Stack, Text } from '@mantine/core'; import { useTranslation } from 'next-i18next'; import widgets from '../../../../../../widgets'; @@ -14,7 +14,7 @@ export const AvailableIntegrationElements = ({ }: AvailableIntegrationElementsProps) => { const { t } = useTranslation('layout/element-selector/selector'); return ( - <> + @@ -26,6 +26,6 @@ export const AvailableIntegrationElements = ({ ))} - + ); }; diff --git a/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx b/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx index fca0a4eed7a..7a1cd56482c 100644 --- a/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx +++ b/src/components/Dashboard/Modals/SelectElement/SelectElementModal.tsx @@ -2,11 +2,10 @@ import { ContextModalProps } from '@mantine/modals'; import { useState } from 'react'; import { AvailableElementTypes } from './Components/Overview/AvailableElementsOverview'; -import { AvailableStaticTypes } from './Components/StaticElementsTab/AvailableStaticElementsTab'; import { AvailableIntegrationElements } from './Components/WidgetsTab/AvailableWidgetsTab'; export const SelectElementModal = ({ context, id }: ContextModalProps) => { - const [activeTab, setActiveTab] = useState(); + const [activeTab, setActiveTab] = useState(); switch (activeTab) { case undefined: @@ -14,13 +13,10 @@ export const SelectElementModal = ({ context, id }: ContextModalProps) => { setActiveTab('integrations')} - onOpenStaticElements={() => setActiveTab('static_elements')} /> ); case 'integrations': return setActiveTab(undefined)} />; - case 'static_elements': - return setActiveTab(undefined)} />; default: /* default to the main selection tab */ setActiveTab(undefined); diff --git a/src/components/Manage/Tools/Docker/ContainerActionBar.tsx b/src/components/Manage/Tools/Docker/ContainerActionBar.tsx index cf18de50c8d..cd35d96813f 100644 --- a/src/components/Manage/Tools/Docker/ContainerActionBar.tsx +++ b/src/components/Manage/Tools/Docker/ContainerActionBar.tsx @@ -16,7 +16,7 @@ import { RouterInputs, api } from '~/utils/api'; import { openDockerSelectBoardModal } from './docker-select-board.modal'; export interface ContainerActionBarProps { - selected: Dockerode.ContainerInfo[]; + selected: (Dockerode.ContainerInfo & { icon?: string })[]; reload: () => void; isLoading: boolean; } @@ -94,7 +94,7 @@ export default function ContainerActionBar({ color="indigo" variant="light" radius="md" - disabled={selected.length !== 1} + disabled={selected.length < 1} onClick={() => openDockerSelectBoardModal({ containers: selected })} > {t('actionBar.addToHomarr.title')} @@ -127,13 +127,7 @@ const useDockerActionMutation = () => { { action, id: container.Id }, { onSuccess: () => { - notifications.update({ - id: container.Id, - title: containerName, - message: `${t(`actions.${action}.end`)} ${containerName}`, - icon: , - autoClose: 2000, - }); + notifications.cleanQueue(); }, onError: (err) => { notifications.update({ diff --git a/src/components/Manage/Tools/Docker/ContainerTable.tsx b/src/components/Manage/Tools/Docker/ContainerTable.tsx index bd0f76c843d..aec474ea8d8 100644 --- a/src/components/Manage/Tools/Docker/ContainerTable.tsx +++ b/src/components/Manage/Tools/Docker/ContainerTable.tsx @@ -2,6 +2,7 @@ import { Badge, Checkbox, Group, + Image, ScrollArea, Table, Text, @@ -96,6 +97,7 @@ export default function ContainerTable({ void; width: number; + icon?: string; }; -const Row = ({ container, selected, toggleRow, width }: RowProps) => { +const Row = ({ icon, container, selected, toggleRow, width }: RowProps) => { const { t } = useTranslation('modules/docker'); const { classes, cx } = useStyles(); + const containerName = container.Names[0].replace('/', ''); return ( @@ -124,13 +128,16 @@ const Row = ({ container, selected, toggleRow, width }: RowProps) => { toggleRow(container)} transitionDuration={0} /> - - {container.Names[0].replace('/', '')} - + + + + {containerName} + + {width > MIN_WIDTH_MOBILE && ( - {container.Image} + {container.Image.slice(0, 25)} )} {width > MIN_WIDTH_MOBILE && ( diff --git a/src/components/Manage/Tools/Docker/docker-select-board.modal.tsx b/src/components/Manage/Tools/Docker/docker-select-board.modal.tsx index 8eabedb7d65..688c6796ce0 100644 --- a/src/components/Manage/Tools/Docker/docker-select-board.modal.tsx +++ b/src/components/Manage/Tools/Docker/docker-select-board.modal.tsx @@ -1,4 +1,4 @@ -import { Button, Group, Select, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Button, Group, Select, Stack, Text, Title } from '@mantine/core'; import { useForm } from '@mantine/form'; import { ContextModalProps, modals } from '@mantine/modals'; import { showNotification } from '@mantine/notifications'; @@ -6,6 +6,9 @@ import { IconCheck, IconX } from '@tabler/icons-react'; import { ContainerInfo } from 'dockerode'; import { Trans, useTranslation } from 'next-i18next'; import { z } from 'zod'; +import { useConfigContext } from '~/config/provider'; +import { useConfigStore } from '~/config/store'; +import { generateDefaultApp } from '~/tools/shared/app'; import { api } from '~/utils/api'; import { useI18nZodResolver } from '~/utils/i18n-zod-resolver'; @@ -14,7 +17,7 @@ const dockerSelectBoardSchema = z.object({ }); type InnerProps = { - containers: ContainerInfo[]; + containers: (ContainerInfo & { icon?: string })[]; }; type FormType = z.infer; @@ -22,13 +25,18 @@ export const DockerSelectBoardModal = ({ id, innerProps }: ContextModalProps store.updateConfig); const handleSubmit = async (values: FormType) => { + const newApps = innerProps.containers.map((container) => ({ + name: (container.Names.at(0) ?? 'App').replace('/', ''), + port: container.Ports.at(0)?.PublicPort, + icon: container.icon, + })); await mutateAsync( { - apps: innerProps.containers.map((container) => ({ - name: (container.Names.at(0) ?? 'App').replace('/', ''), - port: container.Ports.at(0)?.PublicPort, - })), + apps: newApps, boardName: values.board, }, { @@ -39,7 +47,21 @@ export const DockerSelectBoardModal = ({ id, innerProps }: ContextModalProps, color: 'green', }); - + updateConfig(configName!, (config) => { + const lowestWrapper = config?.wrappers.sort((a, b) => a.position - b.position)[0]; + const defaultApp = generateDefaultApp(lowestWrapper.id); + return { + ...config, + apps: [ + ...config.apps, + ...newApps.map((app) => ({ + ...defaultApp, + ...app, + wrapperId: lowestWrapper.id, + })), + ], + }; + }); modals.close(id); }, onError: () => { @@ -117,5 +139,5 @@ export const openDockerSelectBoardModal = (innerProps: InnerProps) => { ), innerProps, }); - umami.track('Add to homarr modal') + umami.track('Add to homarr modal'); }; diff --git a/src/components/layout/Templates/BoardLayout.tsx b/src/components/layout/Templates/BoardLayout.tsx index 3e82ea7759e..81f4ed71256 100644 --- a/src/components/layout/Templates/BoardLayout.tsx +++ b/src/components/layout/Templates/BoardLayout.tsx @@ -1,16 +1,26 @@ -import { Button, Global, Text, Title, Tooltip, clsx } from '@mantine/core'; -import { useHotkeys, useWindowEvent } from '@mantine/hooks'; +import { Button, Global, Modal, Stack, Text, Title, Tooltip, clsx } from '@mantine/core'; +import { useDisclosure, useHotkeys, useWindowEvent } from '@mantine/hooks'; import { openContextModal } from '@mantine/modals'; import { hideNotification, showNotification } from '@mantine/notifications'; -import { IconApps, IconEditCircle, IconEditCircleOff, IconSettings } from '@tabler/icons-react'; +import { + IconApps, + IconBrandDocker, + IconEditCircle, + IconEditCircleOff, + IconSettings, +} from '@tabler/icons-react'; import Consola from 'consola'; +import { ContainerInfo } from 'dockerode'; import { useSession } from 'next-auth/react'; import { Trans, useTranslation } from 'next-i18next'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { env } from 'process'; +import { useState } from 'react'; import { useEditModeStore } from '~/components/Dashboard/Views/useEditModeStore'; import { useNamedWrapperColumnCount } from '~/components/Dashboard/Wrappers/gridstack/store'; +import ContainerActionBar from '~/components/Manage/Tools/Docker/ContainerActionBar'; +import ContainerTable from '~/components/Manage/Tools/Docker/ContainerTable'; import { BoardHeadOverride } from '~/components/layout/Meta/BoardHeadOverride'; import { HeaderActionButton } from '~/components/layout/header/ActionButton'; import { useConfigContext } from '~/config/provider'; @@ -20,14 +30,15 @@ import { MainLayout } from './MainLayout'; type BoardLayoutProps = { children: React.ReactNode; + isDockerEnabled?: boolean; }; -export const BoardLayout = ({ children }: BoardLayoutProps) => { +export const BoardLayout = ({ children, isDockerEnabled = false }: BoardLayoutProps) => { const { config } = useConfigContext(); const { data: session } = useSession(); return ( - }> + }> {children} @@ -36,7 +47,7 @@ export const BoardLayout = ({ children }: BoardLayoutProps) => { ); }; -export const HeaderActions = () => { +export const HeaderActions = ({isDockerEnabled = false} : { isDockerEnabled: boolean}) => { const { data: sessionData } = useSession(); if (!sessionData?.user?.isAdmin) return null; @@ -44,11 +55,55 @@ export const HeaderActions = () => { return ( <> + {isDockerEnabled && } ); }; +const DockerButton = () => { + const [selection, setSelection] = useState<(ContainerInfo & { icon?: string })[]>([]); + const [opened, { open, close, toggle }] = useDisclosure(false); + useHotkeys([['mod+B', toggle]]); + + const { data, refetch, isRefetching } = api.docker.containers.useQuery(undefined, { + cacheTime: 60 * 1000 * 5, + staleTime: 60 * 1000 * 1, + }); + const { t } = useTranslation('tools/docker'); + const reload = () => { + refetch(); + setSelection([]); + }; + + return ( + <> + + + + + + + + + + + + + ); +}; + const CustomizeBoardButton = () => { const { name } = useConfigContext(); const { t } = useTranslation('boards/common'); diff --git a/src/pages/board/index.tsx b/src/pages/board/index.tsx index 9822d125bed..661db269c4a 100644 --- a/src/pages/board/index.tsx +++ b/src/pages/board/index.tsx @@ -1,8 +1,9 @@ -import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; +import { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next'; import { SSRConfig } from 'next-i18next'; import { Dashboard } from '~/components/Dashboard/Dashboard'; import { BoardLayout } from '~/components/layout/Templates/BoardLayout'; import { useInitConfig } from '~/config/init'; +import { dockerRouter } from '~/server/api/routers/docker/router'; import { getServerAuthSession } from '~/server/auth'; import { getDefaultBoardAsync } from '~/server/db/queries/userSettings'; import { getFrontendConfig } from '~/tools/config/getFrontendConfig'; @@ -10,14 +11,23 @@ import { getServerSideTranslations } from '~/tools/server/getServerSideTranslati import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder'; import { boardNamespaces } from '~/tools/server/translation-namespaces'; import { ConfigType } from '~/types/config'; +import { api } from '~/utils/api'; export default function BoardPage({ config: initialConfig, + isDockerEnabled, + initialContainers, }: InferGetServerSidePropsType) { useInitConfig(initialConfig); + const { data } = api.docker.containers.useQuery(undefined, { + initialData: initialContainers ?? undefined, + enabled: isDockerEnabled, + cacheTime: 60 * 1000 * 5, + staleTime: 60 * 1000 * 1, + }); return ( - + ); @@ -28,33 +38,45 @@ type BoardGetServerSideProps = { _nextI18Next?: SSRConfig['_nextI18Next']; }; -export const getServerSideProps: GetServerSideProps = async (ctx) => { - const session = await getServerAuthSession(ctx); +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + const session = await getServerAuthSession(context); const boardName = await getDefaultBoardAsync(session?.user?.id, 'default'); const translations = await getServerSideTranslations( boardNamespaces, - ctx.locale, - ctx.req, - ctx.res + context.locale, + context.req, + context.res ); const config = await getFrontendConfig(boardName); const result = checkForSessionOrAskForLogin( - ctx, + context, session, () => config.settings.access.allowGuests || session?.user != undefined ); if (result) { return result; } - + const caller = dockerRouter.createCaller({ + session: session, + cookies: context.req.cookies, + }); + let containers = undefined; + // Fetch containers if user is admin, otherwise we don't need them + try { + if (session?.user.isAdmin == true) containers = await caller.containers(); + } catch (error) { + + } return { props: { config, primaryColor: config.settings.customization.colors.primary, secondaryColor: config.settings.customization.colors.secondary, primaryShade: config.settings.customization.colors.shade, + isDockerEnabled: containers != undefined, + initialContainers: containers ?? null, ...translations, }, }; diff --git a/src/server/api/routers/app.ts b/src/server/api/routers/app.ts index e7c3f5e2362..4ec416c71da 100644 --- a/src/server/api/routers/app.ts +++ b/src/server/api/routers/app.ts @@ -1,7 +1,6 @@ import { TRPCError } from '@trpc/server'; -import axios, { AxiosError } from 'axios'; +import { AxiosError } from 'axios'; import Consola from 'consola'; -import https from 'https'; import { z } from 'zod'; import { isStatusOk } from '~/components/Dashboard/Tiles/Apps/AppPing'; import { getConfig } from '~/tools/config/getConfig'; @@ -18,7 +17,6 @@ export const appRouter = createTRPCRouter({ }) ) .query(async ({ input }) => { - const agent = new https.Agent({ rejectUnauthorized: false }); const config = getConfig(input.configName); const app = config.apps.find((app) => app.id === input.id); @@ -30,8 +28,16 @@ export const appRouter = createTRPCRouter({ message: `App ${input.id} was not found`, }); } - const res = await axios - .get(app.url, { httpsAgent: agent, timeout: 10000 }) + + const res = await fetch(app.url, { + method: 'GET', + cache: 'force-cache', + headers: { + // Cache for 5 minutes + 'Cache-Control': 'max-age=300', + 'Content-Type': 'application/json', + }, + }) .then((response) => ({ status: response.status, statusText: response.statusText, diff --git a/src/server/api/routers/board.ts b/src/server/api/routers/board.ts index 05c74c2e4c8..72bab7eaf89 100644 --- a/src/server/api/routers/board.ts +++ b/src/server/api/routers/board.ts @@ -41,6 +41,7 @@ export const boardRouter = createTRPCRouter({ apps: z.array( z.object({ name: z.string(), + icon: z.string().optional(), port: z.number().optional(), }) ), @@ -54,7 +55,6 @@ export const boardRouter = createTRPCRouter({ }); } const config = await getConfig(input.boardName); - const lowestWrapper = config?.wrappers.sort((a, b) => a.position - b.position)[0]; const newConfig = { @@ -66,11 +66,14 @@ export const boardRouter = createTRPCRouter({ const address = container.port ? `http://localhost:${container.port}` : 'http://localhost'; - return { ...defaultApp, name: container.name, url: address, + appearance: { + ...defaultApp.appearance, + iconUrl: container.icon, + }, behaviour: { ...defaultApp.behaviour, externalUrl: address, diff --git a/src/server/api/routers/docker/router.ts b/src/server/api/routers/docker/router.ts index 44b2a35da71..cccd7362bf4 100644 --- a/src/server/api/routers/docker/router.ts +++ b/src/server/api/routers/docker/router.ts @@ -3,6 +3,7 @@ import Dockerode from 'dockerode'; import { z } from 'zod'; import { adminProcedure, createTRPCRouter } from '../../trpc'; +import { IconRespositories } from '../icon'; import DockerSingleton from './DockerSingleton'; const dockerActionSchema = z.enum(['remove', 'start', 'stop', 'restart']); @@ -12,7 +13,27 @@ export const dockerRouter = createTRPCRouter({ try { const docker = new Dockerode({}); const containers = await docker.listContainers({ all: true }); - return containers; + const fetches = IconRespositories.map((rep) => rep.fetch()); + const data = await Promise.all(fetches); + const returnedData = containers.map((container) => { + const imageParsed = container.Image.split('/'); + // Remove the version + const image = imageParsed[imageParsed.length - 1].split(':')[0]; + const foundIcon = data + .flatMap((repository) => + repository.entries.map((entry) => ({ + ...entry, + repository: repository.name, + })) + ) + .find((entry) => entry.name.toLowerCase().includes(image.toLowerCase())); + + return { + ...container, + icon: foundIcon?.url ?? '/public/imgs/logo/logo.svg' + }; + }); + return returnedData; } catch (err) { throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', diff --git a/src/server/api/routers/icon.ts b/src/server/api/routers/icon.ts index a5b6c24931e..02e8630b69d 100644 --- a/src/server/api/routers/icon.ts +++ b/src/server/api/routers/icon.ts @@ -1,36 +1,37 @@ +import { GitHubIconsRepository } from '~/tools/server/images/github-icons-repository'; import { JsdelivrIconsRepository } from '~/tools/server/images/jsdelivr-icons-repository'; import { LocalIconsRepository } from '~/tools/server/images/local-icons-repository'; import { UnpkgIconsRepository } from '~/tools/server/images/unpkg-icons-repository'; -import { GitHubIconsRepository } from '~/tools/server/images/github-icons-repository'; import { createTRPCRouter, publicProcedure } from '../trpc'; +export const IconRespositories = [ + new LocalIconsRepository(), + new GitHubIconsRepository( + GitHubIconsRepository.walkxcode, + 'Walkxcode Dashboard Icons', + 'Walkxcode on Github' + ), + new UnpkgIconsRepository( + UnpkgIconsRepository.tablerRepository, + 'Tabler Icons', + 'Tabler Icons - GitHub (MIT)' + ), + new JsdelivrIconsRepository( + JsdelivrIconsRepository.papirusRepository, + 'Papirus Icons', + 'Papirus Development Team on GitHub (Apache 2.0)' + ), + new JsdelivrIconsRepository( + JsdelivrIconsRepository.homelabSvgAssetsRepository, + 'Homelab Svg Assets', + 'loganmarchione on GitHub (MIT)' + ), +]; + export const iconRouter = createTRPCRouter({ all: publicProcedure.query(async () => { - const respositories = [ - new LocalIconsRepository(), - new GitHubIconsRepository( - GitHubIconsRepository.walkxcode, - 'Walkxcode Dashboard Icons', - 'Walkxcode on Github' - ), - new UnpkgIconsRepository( - UnpkgIconsRepository.tablerRepository, - 'Tabler Icons', - 'Tabler Icons - GitHub (MIT)' - ), - new JsdelivrIconsRepository( - JsdelivrIconsRepository.papirusRepository, - 'Papirus Icons', - 'Papirus Development Team on GitHub (Apache 2.0)' - ), - new JsdelivrIconsRepository( - JsdelivrIconsRepository.homelabSvgAssetsRepository, - 'Homelab Svg Assets', - 'loganmarchione on GitHub (MIT)' - ), - ]; - const fetches = respositories.map((rep) => rep.fetch()); + const fetches = IconRespositories.map((rep) => rep.fetch()); const data = await Promise.all(fetches); return data; }), diff --git a/src/server/api/routers/overseerr.ts b/src/server/api/routers/overseerr.ts index 22208260339..fc778c43316 100644 --- a/src/server/api/routers/overseerr.ts +++ b/src/server/api/routers/overseerr.ts @@ -7,10 +7,10 @@ import { OriginalLanguage, Result } from '~/modules/overseerr/SearchResult'; import { TvShowResult } from '~/modules/overseerr/TvShow'; import { getConfig } from '~/tools/config/getConfig'; -import { createTRPCRouter, publicProcedure } from '../trpc'; +import { protectedProcedure, createTRPCRouter, publicProcedure } from '../trpc'; export const overseerrRouter = createTRPCRouter({ - search: publicProcedure + search: protectedProcedure .input( z.object({ configName: z.string(), diff --git a/src/tools/server/translation-namespaces.ts b/src/tools/server/translation-namespaces.ts index f9af2b59330..22092ad53bb 100644 --- a/src/tools/server/translation-namespaces.ts +++ b/src/tools/server/translation-namespaces.ts @@ -1,4 +1,5 @@ export const boardNamespaces = [ + 'tools/docker', 'layout/element-selector/selector', 'layout/modals/add-app', 'layout/modals/change-position',