diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index 206cb5ee4..6f598b96a 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -92,6 +92,8 @@ function MatchPort(name: string, form: any) { } } +const DEFAULT_ICON = '/favicon.svg'; + export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & any) { const { setOpened } = props; const { config, setConfig } = useConfig(); @@ -111,7 +113,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & type: props.type ?? 'Other', category: props.category ?? undefined, name: props.name ?? '', - icon: props.icon ?? '/favicon.svg', + icon: props.icon ?? DEFAULT_ICON, url: props.url ?? '', apiKey: props.apiKey ?? (undefined as unknown as string), username: props.username ?? (undefined as unknown as string), @@ -146,7 +148,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & const [debounced, cancel] = useDebouncedValue(form.values.name, 250); useEffect(() => { - if (form.values.name !== debounced || props.name || props.type) return; + if (form.values.name !== debounced || form.values.icon !== DEFAULT_ICON) return; MatchIcon(form.values.name, form); MatchService(form.values.name, form); MatchPort(form.values.name, form); @@ -219,7 +221,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & - + + ); } diff --git a/src/components/Docker/DockerDrawer.tsx b/src/components/Docker/DockerDrawer.tsx index 8e07dda66..ed728f21b 100644 --- a/src/components/Docker/DockerDrawer.tsx +++ b/src/components/Docker/DockerDrawer.tsx @@ -1,118 +1,41 @@ -import { - ActionIcon, - Badge, - Checkbox, - createStyles, - Drawer, - Group, - List, - Menu, - ScrollArea, - Table, - Text, -} from '@mantine/core'; +import { ActionIcon, Drawer, Group, LoadingOverlay, ScrollArea } from '@mantine/core'; import { IconBrandDocker } from '@tabler/icons'; import axios from 'axios'; import { useEffect, useState } from 'react'; import Docker from 'dockerode'; -import DockerMenu from './DockerMenu'; -import ContainerState from './ContainerState'; import ContainerActionBar from './ContainerActionBar'; - -const useStyles = createStyles((theme) => ({ - rowSelected: { - backgroundColor: - theme.colorScheme === 'dark' - ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2) - : theme.colors[theme.primaryColor][0], - }, -})); +import DockerTable from './DockerTable'; export default function DockerDrawer(props: any) { const [opened, setOpened] = useState(false); const [containers, setContainers] = useState([]); - const { classes, cx } = useStyles(); const [selection, setSelection] = useState([]); - function reload() { - axios.get('/api/docker/containers').then((res) => { - setContainers(res.data); - }); - } + const [visible, setVisible] = useState(false); - const toggleRow = (container: Docker.ContainerInfo) => - setSelection((current) => - current.includes(container) ? current.filter((c) => c !== container) : [...current, container] - ); - const toggleAll = () => - setSelection((current) => - current.length === containers.length ? [] : containers.map((c) => c) - ); + function reload() { + setVisible(true); + setTimeout(() => { + axios.get('/api/docker/containers').then((res) => { + setContainers(res.data); + setSelection([]); + setVisible(false); + }); + }, 300); + } useEffect(() => { reload(); }, []); - const rows = containers.map((element) => { - const selected = selection.includes(element); - return ( - - - toggleRow(element)} - transitionDuration={0} - /> - - {element.Names[0].replace('/', '')} - {element.Image} - - - {element.Ports.sort((a, b) => a.PrivatePort - b.PrivatePort) - .slice(-3) - .map((port) => ( - - {port.PrivatePort}:{port.PublicPort} - - ))} - {element.Ports.length > 3 && ( - {element.Ports.length - 3} more - )} - - - - - - - ); - }); return ( <> setOpened(false)} padding="xl" size="full"> - - - - - - - - - - - - - - {rows} -
your docker containers
- 0 && selection.length !== containers.length} - transitionDuration={0} - /> - NameImagePortsState
-
+ +
+ + +
- ({ + rowSelected: { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2) + : theme.colors[theme.primaryColor][0], + }, +})); + +export default function DockerTable({ + containers, + selection, + setSelection, +}: { + setSelection: any; + containers: Dockerode.ContainerInfo[]; + selection: Dockerode.ContainerInfo[]; +}) { + const { classes, cx } = useStyles(); + + const toggleRow = (container: Dockerode.ContainerInfo) => + setSelection((current: Dockerode.ContainerInfo[]) => + current.includes(container) ? current.filter((c) => c !== container) : [...current, container] + ); + const toggleAll = () => + setSelection((current: any) => + current.length === containers.length ? [] : containers.map((c) => c) + ); + + const rows = containers.map((element) => { + const selected = selection.includes(element); + return ( + + + toggleRow(element)} + transitionDuration={0} + /> + + {element.Names[0].replace('/', '')} + {element.Image} + + + {element.Ports.sort((a, b) => a.PrivatePort - b.PrivatePort) + .slice(-3) + .map((port) => ( + + {port.PrivatePort}:{port.PublicPort} + + ))} + {element.Ports.length > 3 && ( + {element.Ports.length - 3} more + )} + + + + + + + ); + }); + + return ( + + + + + + + + + + + + {rows} +
your docker containers
+ 0 && selection.length !== containers.length} + transitionDuration={0} + /> + NameImagePortsState
+ ); +} diff --git a/src/pages/_middleware.ts b/src/pages/_middleware.ts deleted file mode 100644 index 0975e7c9e..000000000 --- a/src/pages/_middleware.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; - -export function middleware(req: NextRequest, ev: NextFetchEvent) { - const ok = req.cookies.password === process.env.PASSWORD; - const url = req.nextUrl.clone(); - if ( - !ok && - url.pathname !== '/login' && - process.env.PASSWORD && - url.pathname !== '/api/configs/tryPassword' - ) { - url.pathname = '/login'; - } - return NextResponse.rewrite(url); -} diff --git a/src/pages/api/docker/container/[id].tsx b/src/pages/api/docker/container/[id].tsx index dd7c2fa65..3373e4142 100644 --- a/src/pages/api/docker/container/[id].tsx +++ b/src/pages/api/docker/container/[id].tsx @@ -8,7 +8,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) { const { id } = req.query as { id: string }; const { action } = req.query; // Get the action on the request (start, stop, restart) - if (action !== 'start' && action !== 'stop' && action !== 'restart') { + if (action !== 'start' && action !== 'stop' && action !== 'restart' && action !== 'remove') { return res.status(400).json({ statusCode: 400, message: 'Invalid action', @@ -29,39 +29,25 @@ async function Get(req: NextApiRequest, res: NextApiResponse) { }); } }); - - switch (action) { - case 'start': - container.start((err, data) => { - if (err) { - res.status(500).json({ - message: err, - }); - } - }); - break; - case 'stop': - container.stop((err, data) => { - if (err) { - res.status(500).json({ - message: err, - }); - } - }); - break; - case 'restart': - container.restart((err, data) => { - if (err) { - res.status(500).json({ - message: err, - }); - } - }); - break; - default: - res.status(400).json({ - message: 'Invalid action', - }); + try { + switch (action) { + case 'remove': + await container.remove(); + break; + case 'start': + container.start(); + break; + case 'stop': + container.stop(); + break; + case 'restart': + container.restart(); + break; + } + } catch (err) { + res.status(500).json({ + message: err, + }); } return res.status(200).json({ success: true, diff --git a/src/pages/api/docker/containers.tsx b/src/pages/api/docker/containers.tsx index 6a75d985d..c4d6bf95d 100644 --- a/src/pages/api/docker/containers.tsx +++ b/src/pages/api/docker/containers.tsx @@ -5,12 +5,8 @@ import Docker from 'dockerode'; const docker = new Docker(); async function Get(req: NextApiRequest, res: NextApiResponse) { - docker.listContainers({ all: true }, (err, containers) => { - if (err) { - res.status(500).json({ error: err }); - } - return res.status(200).json(containers); - }); + const containers = await docker.listContainers({ all: true }); + return res.status(200).json(containers); } export default async (req: NextApiRequest, res: NextApiResponse) => { diff --git a/src/tools/addToHomarr.ts b/src/tools/addToHomarr.ts new file mode 100644 index 000000000..91ff8accc --- /dev/null +++ b/src/tools/addToHomarr.ts @@ -0,0 +1,37 @@ +import Dockerode from 'dockerode'; +import { Config } from './types'; + +async function MatchIcon(name: string) { + const res = await fetch( + `https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/${name + .replace(/\s+/g, '-') + .toLowerCase()}.png` + ); + return res.ok ? res.url : '/favicon.svg'; +} + +function tryMatchType(imageName: string) { + // Search for a match with the Image name from the MATCH_TYPES array + console.log(`Trying to match type for: ${imageName}`); + return 'Other'; +} + +export default async function addToHomarr( + container: Dockerode.ContainerInfo, + config: Config, + setConfig: (newconfig: Config) => void +) { + setConfig({ + ...config, + services: [ + ...config.services, + { + name: container.Names[0].substring(1), + id: container.Id, + type: tryMatchType(container.Image), + url: `${container.Ports.at(0)?.IP}:${container.Ports.at(0)?.PublicPort}`, + icon: await MatchIcon(container.Names[0].substring(1)), + }, + ], + }); +}