Skip to content

Commit

Permalink
feat: import to docker form board, docker button on boards (#1714)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajnart authored Dec 30, 2023
1 parent e13a4af commit 082077e
Show file tree
Hide file tree
Showing 16 changed files with 234 additions and 138 deletions.
45 changes: 23 additions & 22 deletions public/locales/en/layout/element-selector/selector.json
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 () => {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,7 +14,7 @@ export const AvailableIntegrationElements = ({
}: AvailableIntegrationElementsProps) => {
const { t } = useTranslation('layout/element-selector/selector');
return (
<>
<Stack m="sm">
<SelectorBackArrow onClickBack={onClickBack} />

<Text mb="md" color="dimmed">
Expand All @@ -26,6 +26,6 @@ export const AvailableIntegrationElements = ({
<WidgetElementType key={k} id={k} image={v.icon} widget={v} />
))}
</Grid>
</>
</Stack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,21 @@ 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<undefined | 'integrations' | 'static_elements'>();
const [activeTab, setActiveTab] = useState<undefined | 'integrations' | 'dockerImport'>();

switch (activeTab) {
case undefined:
return (
<AvailableElementTypes
modalId={id}
onOpenIntegrations={() => setActiveTab('integrations')}
onOpenStaticElements={() => setActiveTab('static_elements')}
/>
);
case 'integrations':
return <AvailableIntegrationElements onClickBack={() => setActiveTab(undefined)} />;
case 'static_elements':
return <AvailableStaticTypes onClickBack={() => setActiveTab(undefined)} />;
default:
/* default to the main selection tab */
setActiveTab(undefined);
Expand Down
12 changes: 3 additions & 9 deletions src/components/Manage/Tools/Docker/ContainerActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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')}
Expand Down Expand Up @@ -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: <IconCheck />,
autoClose: 2000,
});
notifications.cleanQueue();
},
onError: (err) => {
notifications.update({
Expand Down
17 changes: 12 additions & 5 deletions src/components/Manage/Tools/Docker/ContainerTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Badge,
Checkbox,
Group,
Image,
ScrollArea,
Table,
Text,
Expand Down Expand Up @@ -96,6 +97,7 @@ export default function ContainerTable({
<Row
key={container.Id}
container={container}
icon={(container as any).icon ?? undefined}
selected={selected}
toggleRow={toggleRow}
width={width}
Expand All @@ -113,24 +115,29 @@ type RowProps = {
selected: boolean;
toggleRow: (container: ContainerInfo) => 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 (
<tr className={cx({ [classes.rowSelected]: selected })}>
<td>
<Checkbox checked={selected} onChange={() => toggleRow(container)} transitionDuration={0} />
</td>
<td>
<Text size="lg" weight={600}>
{container.Names[0].replace('/', '')}
</Text>
<Group noWrap>
<Image withPlaceholder src={icon} width={30} height={30} />
<Text size="lg" weight={600}>
{containerName}
</Text>
</Group>
</td>
{width > MIN_WIDTH_MOBILE && (
<td>
<Text size="lg">{container.Image}</Text>
<Text size="lg">{container.Image.slice(0, 25)}</Text>
</td>
)}
{width > MIN_WIDTH_MOBILE && (
Expand Down
38 changes: 30 additions & 8 deletions src/components/Manage/Tools/Docker/docker-select-board.modal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
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';
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';

Expand All @@ -14,21 +17,26 @@ const dockerSelectBoardSchema = z.object({
});

type InnerProps = {
containers: ContainerInfo[];
containers: (ContainerInfo & { icon?: string })[];
};
type FormType = z.infer<typeof dockerSelectBoardSchema>;

export const DockerSelectBoardModal = ({ id, innerProps }: ContextModalProps<InnerProps>) => {
const { t } = useTranslation('tools/docker');
const { mutateAsync, isLoading } = api.boards.addAppsForContainers.useMutation();
const { i18nZodResolver } = useI18nZodResolver();
const { name: configName } = useConfigContext();

const updateConfig = useConfigStore((store) => 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,
},
{
Expand All @@ -39,7 +47,21 @@ export const DockerSelectBoardModal = ({ id, innerProps }: ContextModalProps<Inn
icon: <IconCheck />,
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: () => {
Expand Down Expand Up @@ -117,5 +139,5 @@ export const openDockerSelectBoardModal = (innerProps: InnerProps) => {
),
innerProps,
});
umami.track('Add to homarr modal')
umami.track('Add to homarr modal');
};
Loading

0 comments on commit 082077e

Please sign in to comment.