mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-14 09:25:47 +01:00
🚧 WIP on Docker import group
This commit is contained in:
@@ -1,25 +1,26 @@
|
|||||||
{
|
{
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Add a new tile",
|
"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."
|
"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.",
|
"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",
|
"goBack": "Go back to the previous step",
|
||||||
"actionIcon": {
|
"actionIcon": {
|
||||||
"tooltip": "Add a tile"
|
"tooltip": "Add a tile"
|
||||||
},
|
},
|
||||||
"apps": "Apps",
|
"apps": "Apps",
|
||||||
"app": {
|
"app": {
|
||||||
"defaultName": "Your App"
|
"defaultName": "Your App"
|
||||||
},
|
},
|
||||||
"widgets": "Widgets",
|
"widgets": "Widgets",
|
||||||
"categories": "Categories",
|
"categories": "Categories",
|
||||||
"category": {
|
"category": {
|
||||||
"newName": "Name of new category",
|
"newName": "Name of new category",
|
||||||
"defaultName": "New Category",
|
"defaultName": "New Category",
|
||||||
"created": {
|
"created": {
|
||||||
"title": "Category created",
|
"title": "Category created",
|
||||||
"message": "The category \"{{name}}\" has been created"
|
"message": "The category \"{{name}}\" has been created"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"importFromDocker": "Import from docker"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import { Button, Card, Center, Checkbox, Grid, Group, Image, Stack, Text } from '@mantine/core';
|
||||||
|
import { closeAllModals, closeModal } from '@mantine/modals';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
import Dockerode from 'dockerode';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import ContainerState from '~/components/Manage/Tools/Docker/ContainerState';
|
||||||
|
import { useConfigContext } from '~/config/provider';
|
||||||
|
import { NormalizedIconRepositoryResult } from '~/tools/server/images/abstract-icons-repository';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
|
import { ConditionalWrapper } from '~/utils/security';
|
||||||
|
import { WidgetLoading } from '~/widgets/loading';
|
||||||
|
|
||||||
|
import { SelectorBackArrow } from './Shared/SelectorBackArrow';
|
||||||
|
|
||||||
|
function DockerDispaly({
|
||||||
|
container,
|
||||||
|
selected,
|
||||||
|
setSelected,
|
||||||
|
iconsData,
|
||||||
|
}: {
|
||||||
|
container: Dockerode.ContainerInfo;
|
||||||
|
selected: Dockerode.ContainerInfo[];
|
||||||
|
setSelected: (containers: Dockerode.ContainerInfo[]) => void;
|
||||||
|
iconsData: NormalizedIconRepositoryResult[];
|
||||||
|
}) {
|
||||||
|
const containerName = container.Names[0].replace('/', '');
|
||||||
|
const isSelected = selected.includes(container);
|
||||||
|
// Example image : linuxserver.io/sonarr:latest
|
||||||
|
// Remove the slashes
|
||||||
|
const imageParsed = container.Image.split('/');
|
||||||
|
// Remove the version
|
||||||
|
const image = imageParsed[imageParsed.length - 1].split(':')[0];
|
||||||
|
const foundIcon = iconsData
|
||||||
|
.flatMap((repository) =>
|
||||||
|
repository.entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
repository: repository.name,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.find((entry) => entry.name.toLowerCase().includes(image.toLowerCase()));
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() =>
|
||||||
|
setSelected(isSelected ? selected.filter((c) => c !== container) : [...selected, container])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Card style={{ height: '100%' }} shadow="sm" radius="md" withBorder>
|
||||||
|
<Stack justify="space-between" style={{ height: '100%' }}>
|
||||||
|
<Group
|
||||||
|
style={{
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ContainerState state={container.State} />
|
||||||
|
<Checkbox radius="xl" checked={isSelected} size="sm" />
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Center>
|
||||||
|
<Image
|
||||||
|
withPlaceholder
|
||||||
|
h={60}
|
||||||
|
maw={60}
|
||||||
|
w={60}
|
||||||
|
src={foundIcon?.url ?? `https://placehold.co/60x60?text=${containerName}`}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Stack spacing={0}>
|
||||||
|
<Text lineClamp={1} align="center">
|
||||||
|
{containerName}
|
||||||
|
</Text>
|
||||||
|
{container.Image && (
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
style={{ overflow: 'hidden', textOverflow: 'elipsis' }}
|
||||||
|
align="center"
|
||||||
|
color="dimmed"
|
||||||
|
>
|
||||||
|
{container.Image}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findIconForContainer(
|
||||||
|
container: Dockerode.ContainerInfo,
|
||||||
|
iconsData: NormalizedIconRepositoryResult[]
|
||||||
|
) {
|
||||||
|
const imageParsed = container.Image.split('/');
|
||||||
|
// Remove the version
|
||||||
|
const image = imageParsed[imageParsed.length - 1].split(':')[0];
|
||||||
|
const foundIcon = iconsData
|
||||||
|
.flatMap((repository) =>
|
||||||
|
repository.entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
repository: repository.name,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.find((entry) => entry.name.toLowerCase().includes(image.toLowerCase()));
|
||||||
|
return foundIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImportFromDockerModal({ onClickBack }: { onClickBack: () => void }) {
|
||||||
|
const { data, isLoading } = api.docker.containers.useQuery(undefined, {});
|
||||||
|
const { data: iconsData } = api.icon.all.useQuery();
|
||||||
|
const { config } = useConfigContext();
|
||||||
|
const { mutateAsync, isLoading: mutationIsLoading } =
|
||||||
|
api.boards.addAppsForContainers.useMutation();
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<Dockerode.ContainerInfo[]>([]);
|
||||||
|
|
||||||
|
if (isLoading || !data || !iconsData) return <WidgetLoading />;
|
||||||
|
return (
|
||||||
|
<Stack m="sm">
|
||||||
|
<SelectorBackArrow onClickBack={onClickBack} />
|
||||||
|
<Grid>
|
||||||
|
{data?.map((container) => (
|
||||||
|
<Grid.Col xs={12} sm={4} md={3}>
|
||||||
|
<DockerDispaly
|
||||||
|
selected={selected}
|
||||||
|
setSelected={setSelected}
|
||||||
|
iconsData={iconsData}
|
||||||
|
container={container}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Button
|
||||||
|
loading={mutationIsLoading}
|
||||||
|
style={{
|
||||||
|
zIndex: 1000,
|
||||||
|
}}
|
||||||
|
pos={'sticky'}
|
||||||
|
bottom={10}
|
||||||
|
disabled={selected.length === 0}
|
||||||
|
onClick={async () => {
|
||||||
|
mutateAsync({
|
||||||
|
apps: selected.map((container) => ({
|
||||||
|
name: (container.Names.at(0) ?? 'App').replace('/', ''),
|
||||||
|
port: container.Ports.at(0)?.PublicPort,
|
||||||
|
icon: findIconForContainer(container, iconsData)?.url,
|
||||||
|
})),
|
||||||
|
boardName: config?.configProperties.name!,
|
||||||
|
}).then(() => {
|
||||||
|
//TODO: Reload config
|
||||||
|
closeAllModals();
|
||||||
|
notifications.show({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Containers added to dashboard',
|
||||||
|
color: 'green',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add {selected.length} container{selected.length > 1 && 's'} to dashboard
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Group, Space, Stack, Text, UnstyledButton } from '@mantine/core';
|
import { Group, Space, Stack, Text, UnstyledButton } from '@mantine/core';
|
||||||
import { closeModal } from '@mantine/modals';
|
import { closeModal } from '@mantine/modals';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconBox, IconBoxAlignTop, IconStack } from '@tabler/icons-react';
|
import { IconBox, IconBoxAlignTop, IconBrandDocker, IconStack } from '@tabler/icons-react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@@ -18,17 +19,19 @@ import { useStyles } from '../Shared/styles';
|
|||||||
interface AvailableElementTypesProps {
|
interface AvailableElementTypesProps {
|
||||||
modalId: string;
|
modalId: string;
|
||||||
onOpenIntegrations: () => void;
|
onOpenIntegrations: () => void;
|
||||||
onOpenStaticElements: () => void;
|
onOpenDocker: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvailableElementTypes = ({
|
export const AvailableElementTypes = ({
|
||||||
modalId,
|
modalId,
|
||||||
onOpenIntegrations: onOpenWidgets,
|
onOpenIntegrations: onOpenWidgets,
|
||||||
onOpenStaticElements,
|
onOpenDocker,
|
||||||
}: AvailableElementTypesProps) => {
|
}: AvailableElementTypesProps) => {
|
||||||
const { t } = useTranslation('layout/element-selector/selector');
|
const { t } = useTranslation('layout/element-selector/selector');
|
||||||
const { config, name: configName } = useConfigContext();
|
const { config, name: configName } = useConfigContext();
|
||||||
const { updateConfig } = useConfigStore();
|
const { updateConfig } = useConfigStore();
|
||||||
|
const { data } = useSession();
|
||||||
|
|
||||||
const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
|
const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
|
||||||
|
|
||||||
const onClickCreateCategory = async () => {
|
const onClickCreateCategory = async () => {
|
||||||
@@ -96,6 +99,13 @@ export const AvailableElementTypes = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{data && data.user.isAdmin && (
|
||||||
|
<ElementItem
|
||||||
|
name={t('importFromDocker')}
|
||||||
|
icon={<IconBrandDocker size={40} strokeWidth={1.3} />}
|
||||||
|
onClick={onOpenDocker}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ElementItem
|
<ElementItem
|
||||||
name={t('widgets')}
|
name={t('widgets')}
|
||||||
icon={<IconStack size={40} strokeWidth={1.3} />}
|
icon={<IconStack size={40} strokeWidth={1.3} />}
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<>
|
|
||||||
<SelectorBackArrow onClickBack={onClickBack} />
|
|
||||||
|
|
||||||
<Text mb="md" color="dimmed">
|
|
||||||
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.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Grid grow>
|
|
||||||
{/*
|
|
||||||
<GenericAvailableElementType
|
|
||||||
name="Static Text"
|
|
||||||
description="Display a fixed string on your dashboard"
|
|
||||||
image={IconCursorText}
|
|
||||||
handleAddition={async () => {}}
|
|
||||||
/> */}
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Grid, Text } from '@mantine/core';
|
import { Grid, Stack, Text } from '@mantine/core';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import widgets from '../../../../../../widgets';
|
import widgets from '../../../../../../widgets';
|
||||||
@@ -14,7 +14,7 @@ export const AvailableIntegrationElements = ({
|
|||||||
}: AvailableIntegrationElementsProps) => {
|
}: AvailableIntegrationElementsProps) => {
|
||||||
const { t } = useTranslation('layout/element-selector/selector');
|
const { t } = useTranslation('layout/element-selector/selector');
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack m="sm">
|
||||||
<SelectorBackArrow onClickBack={onClickBack} />
|
<SelectorBackArrow onClickBack={onClickBack} />
|
||||||
|
|
||||||
<Text mb="md" color="dimmed">
|
<Text mb="md" color="dimmed">
|
||||||
@@ -26,6 +26,6 @@ export const AvailableIntegrationElements = ({
|
|||||||
<WidgetElementType key={k} id={k} image={v.icon} widget={v} />
|
<WidgetElementType key={k} id={k} image={v.icon} widget={v} />
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { ContextModalProps } from '@mantine/modals';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { AvailableElementTypes } from './Components/Overview/AvailableElementsOverview';
|
import { AvailableElementTypes } from './Components/Overview/AvailableElementsOverview';
|
||||||
import { AvailableStaticTypes } from './Components/StaticElementsTab/AvailableStaticElementsTab';
|
|
||||||
import { AvailableIntegrationElements } from './Components/WidgetsTab/AvailableWidgetsTab';
|
import { AvailableIntegrationElements } from './Components/WidgetsTab/AvailableWidgetsTab';
|
||||||
|
import ImportFromDockerModal from './Components/DockerImportModal';
|
||||||
|
|
||||||
export const SelectElementModal = ({ context, id }: ContextModalProps) => {
|
export const SelectElementModal = ({ context, id }: ContextModalProps) => {
|
||||||
const [activeTab, setActiveTab] = useState<undefined | 'integrations' | 'static_elements'>();
|
const [activeTab, setActiveTab] = useState<undefined | 'integrations' | 'dockerImport'>();
|
||||||
|
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case undefined:
|
case undefined:
|
||||||
@@ -14,13 +14,13 @@ export const SelectElementModal = ({ context, id }: ContextModalProps) => {
|
|||||||
<AvailableElementTypes
|
<AvailableElementTypes
|
||||||
modalId={id}
|
modalId={id}
|
||||||
onOpenIntegrations={() => setActiveTab('integrations')}
|
onOpenIntegrations={() => setActiveTab('integrations')}
|
||||||
onOpenStaticElements={() => setActiveTab('static_elements')}
|
onOpenDocker={() => setActiveTab('dockerImport')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'integrations':
|
case 'integrations':
|
||||||
return <AvailableIntegrationElements onClickBack={() => setActiveTab(undefined)} />;
|
return <AvailableIntegrationElements onClickBack={() => setActiveTab(undefined)} />;
|
||||||
case 'static_elements':
|
case 'dockerImport':
|
||||||
return <AvailableStaticTypes onClickBack={() => setActiveTab(undefined)} />;
|
return <ImportFromDockerModal onClickBack={() => setActiveTab(undefined)} />;
|
||||||
default:
|
default:
|
||||||
/* default to the main selection tab */
|
/* default to the main selection tab */
|
||||||
setActiveTab(undefined);
|
setActiveTab(undefined);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default function ContainerActionBar({
|
|||||||
color="indigo"
|
color="indigo"
|
||||||
variant="light"
|
variant="light"
|
||||||
radius="md"
|
radius="md"
|
||||||
disabled={selected.length !== 1}
|
disabled={selected.length < 1}
|
||||||
onClick={() => openDockerSelectBoardModal({ containers: selected })}
|
onClick={() => openDockerSelectBoardModal({ containers: selected })}
|
||||||
>
|
>
|
||||||
{t('actionBar.addToHomarr.title')}
|
{t('actionBar.addToHomarr.title')}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Group,
|
Group,
|
||||||
|
Image,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
@@ -13,6 +14,7 @@ import { IconSearch } from '@tabler/icons-react';
|
|||||||
import Dockerode, { ContainerInfo } from 'dockerode';
|
import Dockerode, { ContainerInfo } from 'dockerode';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
import { MIN_WIDTH_MOBILE } from '../../../../constants/constants';
|
import { MIN_WIDTH_MOBILE } from '../../../../constants/constants';
|
||||||
import ContainerState from './ContainerState';
|
import ContainerState from './ContainerState';
|
||||||
@@ -117,6 +119,22 @@ type RowProps = {
|
|||||||
const Row = ({ container, selected, toggleRow, width }: RowProps) => {
|
const Row = ({ container, selected, toggleRow, width }: RowProps) => {
|
||||||
const { t } = useTranslation('modules/docker');
|
const { t } = useTranslation('modules/docker');
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
|
const { data: iconsData } = api.icon.all.useQuery();
|
||||||
|
if (!iconsData) return null;
|
||||||
|
const containerName = container.Names[0].replace('/', '');
|
||||||
|
// Example image : linuxserver.io/sonarr:latest
|
||||||
|
// Remove the slashes
|
||||||
|
const imageParsed = container.Image.split('/');
|
||||||
|
// Remove the version
|
||||||
|
const image = imageParsed[imageParsed.length - 1].split(':')[0];
|
||||||
|
const foundIcon = iconsData
|
||||||
|
.flatMap((repository) =>
|
||||||
|
repository.entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
repository: repository.name,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.find((entry) => entry.name.toLowerCase().includes(image.toLowerCase()));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={cx({ [classes.rowSelected]: selected })}>
|
<tr className={cx({ [classes.rowSelected]: selected })}>
|
||||||
@@ -124,9 +142,12 @@ const Row = ({ container, selected, toggleRow, width }: RowProps) => {
|
|||||||
<Checkbox checked={selected} onChange={() => toggleRow(container)} transitionDuration={0} />
|
<Checkbox checked={selected} onChange={() => toggleRow(container)} transitionDuration={0} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Text size="lg" weight={600}>
|
<Group noWrap>
|
||||||
{container.Names[0].replace('/', '')}
|
<Image withPlaceholder src={foundIcon?.url} alt={image} width={30} height={30} />
|
||||||
</Text>
|
<Text size="lg" weight={600}>
|
||||||
|
{containerName}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
</td>
|
</td>
|
||||||
{width > MIN_WIDTH_MOBILE && (
|
{width > MIN_WIDTH_MOBILE && (
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function OnboardPage({
|
|||||||
|
|
||||||
const [onboardingSteps, { open: showOnboardingSteps }] = useDisclosure(false);
|
const [onboardingSteps, { open: showOnboardingSteps }] = useDisclosure(false);
|
||||||
|
|
||||||
const isUpgradeFromSchemaOne = configSchemaVersions.includes(1);
|
const isUpgradeFromSchemaOne = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -81,12 +81,12 @@ export default function OnboardPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const userCount = await getTotalUserCountAsync();
|
// const userCount = await getTotalUserCountAsync();
|
||||||
if (userCount >= 1) {
|
// if (userCount >= 1) {
|
||||||
return {
|
// return {
|
||||||
notFound: true,
|
// notFound: true,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
|
const files = fs.readdirSync('./data/configs').filter((file) => file.endsWith('.json'));
|
||||||
const configs = files.map((file) => getConfig(file));
|
const configs = files.map((file) => getConfig(file));
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
apps: z.array(
|
apps: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
icon: z.string().optional(),
|
||||||
port: z.number().optional(),
|
port: z.number().optional(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@@ -71,6 +72,10 @@ export const boardRouter = createTRPCRouter({
|
|||||||
...defaultApp,
|
...defaultApp,
|
||||||
name: container.name,
|
name: container.name,
|
||||||
url: address,
|
url: address,
|
||||||
|
appearance: {
|
||||||
|
...defaultApp.appearance,
|
||||||
|
icon: container.icon,
|
||||||
|
},
|
||||||
behaviour: {
|
behaviour: {
|
||||||
...defaultApp.behaviour,
|
...defaultApp.behaviour,
|
||||||
externalUrl: address,
|
externalUrl: address,
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
|
import { GitHubIconsRepository } from '~/tools/server/images/github-icons-repository';
|
||||||
import { JsdelivrIconsRepository } from '~/tools/server/images/jsdelivr-icons-repository';
|
import { JsdelivrIconsRepository } from '~/tools/server/images/jsdelivr-icons-repository';
|
||||||
import { LocalIconsRepository } from '~/tools/server/images/local-icons-repository';
|
import { LocalIconsRepository } from '~/tools/server/images/local-icons-repository';
|
||||||
import { UnpkgIconsRepository } from '~/tools/server/images/unpkg-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';
|
import { createTRPCRouter, publicProcedure } from '../trpc';
|
||||||
|
|
||||||
|
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)'
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
export const iconRouter = createTRPCRouter({
|
export const iconRouter = createTRPCRouter({
|
||||||
all: publicProcedure.query(async () => {
|
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 = respositories.map((rep) => rep.fetch());
|
||||||
const data = await Promise.all(fetches);
|
const data = await Promise.all(fetches);
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { OriginalLanguage, Result } from '~/modules/overseerr/SearchResult';
|
|||||||
import { TvShowResult } from '~/modules/overseerr/TvShow';
|
import { TvShowResult } from '~/modules/overseerr/TvShow';
|
||||||
import { getConfig } from '~/tools/config/getConfig';
|
import { getConfig } from '~/tools/config/getConfig';
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from '../trpc';
|
import { adminProcedure, createTRPCRouter, publicProcedure } from '../trpc';
|
||||||
|
|
||||||
export const overseerrRouter = createTRPCRouter({
|
export const overseerrRouter = createTRPCRouter({
|
||||||
search: publicProcedure
|
search: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
configName: z.string(),
|
configName: z.string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user