mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
✨ Add "Add to homarr" feature and move code
This commit is contained in:
@@ -92,6 +92,8 @@ function MatchPort(name: string, form: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_ICON = '/favicon.svg';
|
||||||
|
|
||||||
export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & any) {
|
export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & any) {
|
||||||
const { setOpened } = props;
|
const { setOpened } = props;
|
||||||
const { config, setConfig } = useConfig();
|
const { config, setConfig } = useConfig();
|
||||||
@@ -111,7 +113,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
type: props.type ?? 'Other',
|
type: props.type ?? 'Other',
|
||||||
category: props.category ?? undefined,
|
category: props.category ?? undefined,
|
||||||
name: props.name ?? '',
|
name: props.name ?? '',
|
||||||
icon: props.icon ?? '/favicon.svg',
|
icon: props.icon ?? DEFAULT_ICON,
|
||||||
url: props.url ?? '',
|
url: props.url ?? '',
|
||||||
apiKey: props.apiKey ?? (undefined as unknown as string),
|
apiKey: props.apiKey ?? (undefined as unknown as string),
|
||||||
username: props.username ?? (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);
|
const [debounced, cancel] = useDebouncedValue(form.values.name, 250);
|
||||||
useEffect(() => {
|
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);
|
MatchIcon(form.values.name, form);
|
||||||
MatchService(form.values.name, form);
|
MatchService(form.values.name, form);
|
||||||
MatchPort(form.values.name, form);
|
MatchPort(form.values.name, form);
|
||||||
@@ -219,7 +221,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
|
|||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
label="Icon URL"
|
label="Icon URL"
|
||||||
placeholder="/favicon.svg"
|
placeholder={DEFAULT_ICON}
|
||||||
{...form.getInputProps('icon')}
|
{...form.getInputProps('icon')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -2,20 +2,25 @@ import { Button, Group } from '@mantine/core';
|
|||||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||||
import {
|
import {
|
||||||
IconCheck,
|
IconCheck,
|
||||||
|
IconLicense,
|
||||||
IconPlayerPlay,
|
IconPlayerPlay,
|
||||||
IconPlayerStop,
|
IconPlayerStop,
|
||||||
|
IconPlus,
|
||||||
IconRefresh,
|
IconRefresh,
|
||||||
IconRotateClockwise,
|
IconRotateClockwise,
|
||||||
|
IconTrash,
|
||||||
IconX,
|
IconX,
|
||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Dockerode from 'dockerode';
|
import Dockerode from 'dockerode';
|
||||||
|
import addToHomarr from '../../tools/addToHomarr';
|
||||||
|
import { useConfig } from '../../tools/state';
|
||||||
|
|
||||||
function sendNotification(action: string, containerId: string, containerName: string) {
|
function sendDockerCommand(action: string, containerId: string, containerName: string) {
|
||||||
showNotification({
|
showNotification({
|
||||||
id: containerId,
|
id: containerId,
|
||||||
loading: true,
|
loading: true,
|
||||||
title: `${action}ing container ${containerName}`,
|
title: `${action}ing container ${containerName.substring(1)}`,
|
||||||
message: undefined,
|
message: undefined,
|
||||||
autoClose: false,
|
autoClose: false,
|
||||||
disallowClose: true,
|
disallowClose: true,
|
||||||
@@ -51,6 +56,7 @@ export interface ContainerActionBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
|
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
|
||||||
|
const { config, setConfig } = useConfig();
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
@@ -58,7 +64,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selected.map((container) =>
|
selected.map((container) =>
|
||||||
sendNotification('restart', container.Id, container.Names[0])
|
sendDockerCommand('restart', container.Id, container.Names[0].substring(1))
|
||||||
)
|
)
|
||||||
).then(() => reload())
|
).then(() => reload())
|
||||||
}
|
}
|
||||||
@@ -72,7 +78,17 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
leftIcon={<IconPlayerStop />}
|
leftIcon={<IconPlayerStop />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selected.map((container) => sendNotification('stop', container.Id, container.Names[0]))
|
selected.map((container) => {
|
||||||
|
if (container.State === 'stopped' || container.State === 'created' || container.State === 'exited') {
|
||||||
|
return showNotification({
|
||||||
|
id: container.Id,
|
||||||
|
title: `Failed to stop ${container.Names[0].substring(1)}`,
|
||||||
|
message: "You can't stop a stopped container",
|
||||||
|
autoClose: 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sendDockerCommand('stop', container.Id, container.Names[0].substring(1));
|
||||||
|
})
|
||||||
).then(() => reload())
|
).then(() => reload())
|
||||||
}
|
}
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -85,7 +101,9 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
leftIcon={<IconPlayerPlay />}
|
leftIcon={<IconPlayerPlay />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
selected.map((container) => sendNotification('start', container.Id, container.Names[0]))
|
selected.map((container) =>
|
||||||
|
sendDockerCommand('start', container.Id, container.Names[0].substring(1))
|
||||||
|
)
|
||||||
).then(() => reload())
|
).then(() => reload())
|
||||||
}
|
}
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -94,9 +112,45 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
|||||||
>
|
>
|
||||||
Start
|
Start
|
||||||
</Button>
|
</Button>
|
||||||
<Button leftIcon={<IconRefresh />} onClick={() => reload()} variant="light">
|
<Button leftIcon={<IconRefresh />} onClick={() => reload()} variant="light" radius="md">
|
||||||
Refresh data
|
Refresh data
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<IconPlus />}
|
||||||
|
color="indigo"
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
onClick={() =>
|
||||||
|
Promise.all(selected.map((container) => addToHomarr(container, config, setConfig))).then(
|
||||||
|
() => reload()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add to Homarr
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
leftIcon={<IconTrash />}
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
radius="md"
|
||||||
|
onClick={() =>
|
||||||
|
Promise.all(
|
||||||
|
selected.map((container) => {
|
||||||
|
if (container.State === 'running') {
|
||||||
|
return showNotification({
|
||||||
|
id: container.Id,
|
||||||
|
title: `Failed to delete ${container.Names[0].substring(1)}`,
|
||||||
|
message: "You can't delete a running container",
|
||||||
|
autoClose: 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sendDockerCommand('remove', container.Id, container.Names[0].substring(1));
|
||||||
|
})
|
||||||
|
).then(() => reload())
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,118 +1,41 @@
|
|||||||
import {
|
import { ActionIcon, Drawer, Group, LoadingOverlay, ScrollArea } from '@mantine/core';
|
||||||
ActionIcon,
|
|
||||||
Badge,
|
|
||||||
Checkbox,
|
|
||||||
createStyles,
|
|
||||||
Drawer,
|
|
||||||
Group,
|
|
||||||
List,
|
|
||||||
Menu,
|
|
||||||
ScrollArea,
|
|
||||||
Table,
|
|
||||||
Text,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { IconBrandDocker } from '@tabler/icons';
|
import { IconBrandDocker } from '@tabler/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import Docker from 'dockerode';
|
import Docker from 'dockerode';
|
||||||
import DockerMenu from './DockerMenu';
|
|
||||||
import ContainerState from './ContainerState';
|
|
||||||
import ContainerActionBar from './ContainerActionBar';
|
import ContainerActionBar from './ContainerActionBar';
|
||||||
|
import DockerTable from './DockerTable';
|
||||||
const useStyles = createStyles((theme) => ({
|
|
||||||
rowSelected: {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark'
|
|
||||||
? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2)
|
|
||||||
: theme.colors[theme.primaryColor][0],
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default function DockerDrawer(props: any) {
|
export default function DockerDrawer(props: any) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]);
|
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]);
|
||||||
const { classes, cx } = useStyles();
|
|
||||||
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]);
|
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
function reload() {
|
function reload() {
|
||||||
|
setVisible(true);
|
||||||
|
setTimeout(() => {
|
||||||
axios.get('/api/docker/containers').then((res) => {
|
axios.get('/api/docker/containers').then((res) => {
|
||||||
setContainers(res.data);
|
setContainers(res.data);
|
||||||
|
setSelection([]);
|
||||||
|
setVisible(false);
|
||||||
});
|
});
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload();
|
||||||
}, []);
|
}, []);
|
||||||
const rows = containers.map((element) => {
|
|
||||||
const selected = selection.includes(element);
|
|
||||||
return (
|
|
||||||
<tr key={element.Id} className={cx({ [classes.rowSelected]: selected })}>
|
|
||||||
<td>
|
|
||||||
<Checkbox
|
|
||||||
checked={selection.includes(element)}
|
|
||||||
onChange={() => toggleRow(element)}
|
|
||||||
transitionDuration={0}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>{element.Names[0].replace('/', '')}</td>
|
|
||||||
<td>{element.Image}</td>
|
|
||||||
<td>
|
|
||||||
<Group>
|
|
||||||
{element.Ports.sort((a, b) => a.PrivatePort - b.PrivatePort)
|
|
||||||
.slice(-3)
|
|
||||||
.map((port) => (
|
|
||||||
<Badge variant="outline">
|
|
||||||
{port.PrivatePort}:{port.PublicPort}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
{element.Ports.length > 3 && (
|
|
||||||
<Badge variant="filled">{element.Ports.length - 3} more</Badge>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ContainerState state={element.State} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Drawer opened={opened} onClose={() => setOpened(false)} padding="xl" size="full">
|
<Drawer opened={opened} onClose={() => setOpened(false)} padding="xl" size="full">
|
||||||
<ScrollArea>
|
|
||||||
<ContainerActionBar selected={selection} reload={reload} />
|
<ContainerActionBar selected={selection} reload={reload} />
|
||||||
<Table captionSide="bottom" highlightOnHover sx={{ minWidth: 800 }} verticalSpacing="sm">
|
<div style={{ position: 'relative' }}>
|
||||||
<caption>your docker containers</caption>
|
<LoadingOverlay visible={visible} />
|
||||||
<thead>
|
<DockerTable containers={containers} selection={selection} setSelection={setSelection} />
|
||||||
<tr>
|
</div>
|
||||||
<th style={{ width: 40 }}>
|
|
||||||
<Checkbox
|
|
||||||
onChange={toggleAll}
|
|
||||||
checked={selection.length === containers.length}
|
|
||||||
indeterminate={selection.length > 0 && selection.length !== containers.length}
|
|
||||||
transitionDuration={0}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Image</th>
|
|
||||||
<th>Ports</th>
|
|
||||||
<th>State</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>{rows}</tbody>
|
|
||||||
</Table>
|
|
||||||
</ScrollArea>
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<DockerMenu container={containers?.at(0)} />
|
|
||||||
<Group position="center">
|
<Group position="center">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|||||||
90
src/components/Docker/DockerTable.tsx
Normal file
90
src/components/Docker/DockerTable.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { Table, Checkbox, Group, Badge, createStyles } from '@mantine/core';
|
||||||
|
import Dockerode from 'dockerode';
|
||||||
|
import ContainerState from './ContainerState';
|
||||||
|
|
||||||
|
const useStyles = createStyles((theme) => ({
|
||||||
|
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 (
|
||||||
|
<tr key={element.Id} className={cx({ [classes.rowSelected]: selected })}>
|
||||||
|
<td>
|
||||||
|
<Checkbox
|
||||||
|
checked={selection.includes(element)}
|
||||||
|
onChange={() => toggleRow(element)}
|
||||||
|
transitionDuration={0}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{element.Names[0].replace('/', '')}</td>
|
||||||
|
<td>{element.Image}</td>
|
||||||
|
<td>
|
||||||
|
<Group>
|
||||||
|
{element.Ports.sort((a, b) => a.PrivatePort - b.PrivatePort)
|
||||||
|
.slice(-3)
|
||||||
|
.map((port) => (
|
||||||
|
<Badge key={port.PrivatePort} variant="outline">
|
||||||
|
{port.PrivatePort}:{port.PublicPort}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
{element.Ports.length > 3 && (
|
||||||
|
<Badge variant="filled">{element.Ports.length - 3} more</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ContainerState state={element.State} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table captionSide="bottom" highlightOnHover sx={{ minWidth: 800 }} verticalSpacing="sm">
|
||||||
|
<caption>your docker containers</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: 40 }}>
|
||||||
|
<Checkbox
|
||||||
|
onChange={toggleAll}
|
||||||
|
checked={selection.length === containers.length}
|
||||||
|
indeterminate={selection.length > 0 && selection.length !== containers.length}
|
||||||
|
transitionDuration={0}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Image</th>
|
||||||
|
<th>Ports</th>
|
||||||
|
<th>State</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{rows}</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const { id } = req.query as { id: string };
|
const { id } = req.query as { id: string };
|
||||||
const { action } = req.query;
|
const { action } = req.query;
|
||||||
// Get the action on the request (start, stop, restart)
|
// 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({
|
return res.status(400).json({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
message: 'Invalid action',
|
message: 'Invalid action',
|
||||||
@@ -29,40 +29,26 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case 'remove':
|
||||||
|
await container.remove();
|
||||||
|
break;
|
||||||
case 'start':
|
case 'start':
|
||||||
container.start((err, data) => {
|
container.start();
|
||||||
if (err) {
|
|
||||||
res.status(500).json({
|
|
||||||
message: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'stop':
|
case 'stop':
|
||||||
container.stop((err, data) => {
|
container.stop();
|
||||||
if (err) {
|
|
||||||
res.status(500).json({
|
|
||||||
message: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'restart':
|
case 'restart':
|
||||||
container.restart((err, data) => {
|
container.restart();
|
||||||
if (err) {
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
message: err,
|
message: err,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res.status(400).json({
|
|
||||||
message: 'Invalid action',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,12 +5,8 @@ import Docker from 'dockerode';
|
|||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
docker.listContainers({ all: true }, (err, containers) => {
|
const containers = await docker.listContainers({ all: true });
|
||||||
if (err) {
|
|
||||||
res.status(500).json({ error: err });
|
|
||||||
}
|
|
||||||
return res.status(200).json(containers);
|
return res.status(200).json(containers);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
|||||||
37
src/tools/addToHomarr.ts
Normal file
37
src/tools/addToHomarr.ts
Normal file
@@ -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)),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user