🚧 Work in progress on the Docker integration

This commit is contained in:
Thomas Camlong
2022-06-27 19:25:26 +02:00
parent df7e833b84
commit 72aba9d8cd
6 changed files with 373 additions and 22 deletions

View File

@@ -0,0 +1,82 @@
import { Button, Group } from '@mantine/core';
import { showNotification, updateNotification } from '@mantine/notifications';
import {
IconCheck,
IconPlayerPlay,
IconPlayerStop,
IconRotateClockwise,
IconX,
} from '@tabler/icons';
import axios from 'axios';
import Dockerode from 'dockerode';
function sendNotification(action: string, containerId: string, containerName: string) {
showNotification({
id: 'load-data',
loading: true,
title: `${action}ing container ${containerName}`,
message: 'Your password is being checked...',
autoClose: false,
disallowClose: true,
});
axios.get(`/api/docker/container/${containerId}?action=${action}`).then((res) => {
setTimeout(() => {
if (res.data.success === true) {
updateNotification({
id: 'load-data',
title: 'Container restarted',
message: 'Your container was successfully restarted',
icon: <IconCheck />,
autoClose: 2000,
});
}
if (res.data.success === false) {
updateNotification({
id: 'load-data',
color: 'red',
title: 'There was an error restarting your container.',
message: 'Your container has encountered issues while restarting.',
icon: <IconX />,
autoClose: 2000,
});
}
}, 500);
});
}
function restart(container: Dockerode.ContainerInfo) {
sendNotification('restart', container.Id, container.Names[0]);
}
function stop(container: Dockerode.ContainerInfo) {
console.log('stoping container', container.Id);
}
function start(container: Dockerode.ContainerInfo) {
console.log('starting container', container.Id);
}
export interface ContainerActionBarProps {
selected: Dockerode.ContainerInfo[];
}
export default function ContainerActionBar(props: ContainerActionBarProps) {
const { selected } = props;
return (
<Group>
<Button
leftIcon={<IconRotateClockwise />}
onClick={() => selected.map((container) => restart(container))}
variant="filled"
color="orange"
radius="md"
>
Restart
</Button>
<Button leftIcon={<IconPlayerStop />} variant="filled" color="red" radius="md">
Stop
</Button>
<Button leftIcon={<IconPlayerPlay />} variant="filled" color="green" radius="md">
Start
</Button>
</Group>
);
}

View File

@@ -0,0 +1,49 @@
import { Badge, BadgeVariant, MantineSize } from '@mantine/core';
import Dockerode from 'dockerode';
export interface ContainerStateProps {
state: Dockerode.ContainerInfo['State'];
}
export default function ContainerState(props: ContainerStateProps) {
const { state } = props;
const options: {
size: MantineSize;
radius: MantineSize;
variant: BadgeVariant;
} = {
size: 'md',
radius: 'md',
variant: 'outline',
};
switch (state) {
case 'running': {
return (
<Badge color="green" {...options}>
Running
</Badge>
);
}
case 'created': {
return (
<Badge color="cyan" {...options}>
Created
</Badge>
);
}
case 'exited': {
return (
<Badge color="red" {...options}>
Stopped
</Badge>
);
}
default: {
return (
<Badge color="purple" {...options}>
Unknown
</Badge>
);
}
}
}

View File

@@ -1,37 +1,114 @@
import { ActionIcon, Drawer, Group, List, Text } from '@mantine/core';
import {
ActionIcon,
Badge,
Checkbox,
createStyles,
Drawer,
Group,
List,
Menu,
ScrollArea,
Table,
Text,
} 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],
},
}));
export default function DockerDrawer(props: any) {
const [opened, setOpened] = useState(false);
const [containers, setContainers] = useState<any[]>([]);
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]);
const { classes, cx } = useStyles();
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]);
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(() => {
axios.get('/api/docker/containers').then((res) => {
setContainers(res.data);
});
}, []);
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.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 (
<>
<Drawer
opened={opened}
onClose={() => setOpened(false)}
title="Register"
padding="xl"
size="full"
>
<List>
{containers.map((container) => (
<List.Item key={container.Id}>
<Text>{container.Names[0]}</Text>
<Text>{container.State}</Text>
<Text>{container.Status}</Text>
<Text>{container.Image}</Text>
</List.Item>
))}
</List>
<Drawer opened={opened} onClose={() => setOpened(false)} padding="xl" size="full">
<ScrollArea>
<ContainerActionBar selected={selection} />
<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>
</ScrollArea>
</Drawer>
<DockerMenu container={containers?.at(0)} />
<Group position="center">
<ActionIcon
variant="default"

View File

@@ -0,0 +1,93 @@
import { Center, createStyles, Menu, Text, useMantineTheme } from '@mantine/core';
import { showNotification, updateNotification } from '@mantine/notifications';
import {
IconCheck,
IconCodePlus,
IconDownload,
IconFileText,
IconPlayerPlay,
IconPlayerStop,
IconRotateClockwise,
IconX,
} from '@tabler/icons';
import axios from 'axios';
import Dockerode from 'dockerode';
function sendNotification(action: string, containerId: string, containerName: string) {
showNotification({
id: 'load-data',
loading: true,
title: `${action}ing container ${containerName}`,
message: 'Your password is being checked...',
autoClose: false,
disallowClose: true,
});
axios.get(`/api/docker/container/${containerId}?action=${action}`).then((res) => {
setTimeout(() => {
if (res.data.success === true) {
updateNotification({
id: 'load-data',
title: 'Container restarted',
message: 'Your container was successfully restarted',
icon: <IconCheck />,
autoClose: 2000,
});
}
if (res.data.success === false) {
updateNotification({
id: 'load-data',
color: 'red',
title: 'There was an error restarting your container.',
message: 'Your container has encountered issues while restarting.',
icon: <IconX />,
autoClose: 2000,
});
}
}, 500);
});
}
function restart(container: Dockerode.ContainerInfo) {
sendNotification('restart', container.Id, container.Names[0]);
}
function stop(container: Dockerode.ContainerInfo) {
console.log('stoping container', container.Id);
}
function start(container: Dockerode.ContainerInfo) {
console.log('starting container', container.Id);
}
export default function DockerMenu(props: any) {
const { container }: { container: Dockerode.ContainerInfo } = props;
const theme = useMantineTheme();
if (container === undefined) {
return null;
}
return (
<Menu shadow="lg" radius="md">
<Menu.Label>Actions</Menu.Label>
<Menu.Item icon={<IconRotateClockwise color="orange" />} onClick={() => restart(container)}>
<Text>Restart</Text>
</Menu.Item>
{container.State === 'running' ? (
<Menu.Item icon={<IconPlayerStop color="red" />}>
<Text>Stop</Text>
</Menu.Item>
) : (
<Menu.Item icon={<IconPlayerPlay color="green" />}>
<Text>Start</Text>
</Menu.Item>
)}
{/* <Menu.Item icon={<IconDownload color="blue" />}>
<Text>Pull latest image </Text>
</Menu.Item>
<Menu.Item icon={<IconFileText color="grey" />}>
<Text>Logs</Text>
</Menu.Item> */}
<Menu.Label>Homarr</Menu.Label>
<Menu.Item icon={<IconCodePlus color={theme.primaryColor} />}>
<Text>Add to Homarr</Text>
</Menu.Item>
</Menu>
);
}

View File

@@ -0,0 +1,52 @@
import { NextApiRequest, NextApiResponse } from 'next';
import Docker from 'dockerode';
const docker = new Docker();
async function Get(req: NextApiRequest, res: NextApiResponse) {
// Get the slug of the request
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') {
return res.status(400).json({
statusCode: 400,
message: 'Invalid action',
});
}
if (!id) {
return res.status(400).json({
message: 'Missing ID',
});
}
// Get the container with the ID
const container = docker.getContainer(id);
// Get the container info
container.inspect((err, data) => {
if (err) {
res.status(500).json({
message: err,
});
}
});
if (action === 'restart') {
await container.restart();
return res.status(200).json({
success: true,
});
}
return res.status(200).json({
success: true,
});
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a Put or a GET
if (req.method === 'GET') {
return Get(req, res);
}
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
});
};

View File

@@ -1,4 +1,3 @@
import axios from 'axios';
import { NextApiRequest, NextApiResponse } from 'next';
import Docker from 'dockerode';
@@ -6,12 +5,11 @@ import Docker from 'dockerode';
const docker = new Docker();
async function Get(req: NextApiRequest, res: NextApiResponse) {
const con: Docker.Container = docker.getContainer('hello');
docker.listContainers({ all: true }, (err, containers) => {
if (err) {
res.status(500).json({ error: err });
}
res.status(200).json(containers);
return res.status(200).json(containers);
});
}