generated from mantinedev/next-pages-template
-
-
Notifications
You must be signed in to change notification settings - Fork 284
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔀 Merge pull request #289 from ajnart/docker-integration
Add Docker integration 🚀
- Loading branch information
Showing
15 changed files
with
936 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import { Button, Group, Modal, Title } from '@mantine/core'; | ||
import { useBooleanToggle } from '@mantine/hooks'; | ||
import { showNotification, updateNotification } from '@mantine/notifications'; | ||
import { | ||
IconCheck, | ||
IconPlayerPlay, | ||
IconPlayerStop, | ||
IconPlus, | ||
IconRefresh, | ||
IconRotateClockwise, | ||
IconTrash, | ||
IconX, | ||
} from '@tabler/icons'; | ||
import axios from 'axios'; | ||
import Dockerode from 'dockerode'; | ||
import { tryMatchService } from '../../tools/addToHomarr'; | ||
import { useConfig } from '../../tools/state'; | ||
import { AddAppShelfItemForm } from '../AppShelf/AddAppShelfItem'; | ||
|
||
function sendDockerCommand(action: string, containerId: string, containerName: string) { | ||
showNotification({ | ||
id: containerId, | ||
loading: true, | ||
title: `${action}ing container ${containerName.substring(1)}`, | ||
message: undefined, | ||
autoClose: false, | ||
disallowClose: true, | ||
}); | ||
axios.get(`/api/docker/container/${containerId}?action=${action}`).then((res) => { | ||
setTimeout(() => { | ||
if (res.data.success === true) { | ||
updateNotification({ | ||
id: containerId, | ||
title: `Container ${containerName} ${action}ed`, | ||
message: `Your container was successfully ${action}ed`, | ||
icon: <IconCheck />, | ||
autoClose: 2000, | ||
}); | ||
} | ||
if (res.data.success === false) { | ||
updateNotification({ | ||
id: containerId, | ||
color: 'red', | ||
title: 'There was an error with your container.', | ||
message: undefined, | ||
icon: <IconX />, | ||
autoClose: 2000, | ||
}); | ||
} | ||
}, 500); | ||
}); | ||
} | ||
|
||
export interface ContainerActionBarProps { | ||
selected: Dockerode.ContainerInfo[]; | ||
reload: () => void; | ||
} | ||
|
||
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) { | ||
const { config, setConfig } = useConfig(); | ||
const [opened, setOpened] = useBooleanToggle(false); | ||
return ( | ||
<Group> | ||
<Modal | ||
size="xl" | ||
radius="md" | ||
opened={opened} | ||
onClose={() => setOpened(false)} | ||
title="Add service" | ||
> | ||
<AddAppShelfItemForm | ||
setOpened={setOpened} | ||
{...tryMatchService(selected.at(0))} | ||
message="Add service to homarr" | ||
/> | ||
</Modal> | ||
<Button | ||
leftIcon={<IconRotateClockwise />} | ||
onClick={() => | ||
Promise.all( | ||
selected.map((container) => | ||
sendDockerCommand('restart', container.Id, container.Names[0].substring(1)) | ||
) | ||
).then(() => reload()) | ||
} | ||
variant="light" | ||
color="orange" | ||
radius="md" | ||
> | ||
Restart | ||
</Button> | ||
<Button | ||
leftIcon={<IconPlayerStop />} | ||
onClick={() => | ||
Promise.all( | ||
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()) | ||
} | ||
variant="light" | ||
color="red" | ||
radius="md" | ||
> | ||
Stop | ||
</Button> | ||
<Button | ||
leftIcon={<IconPlayerPlay />} | ||
onClick={() => | ||
Promise.all( | ||
selected.map((container) => | ||
sendDockerCommand('start', container.Id, container.Names[0].substring(1)) | ||
) | ||
).then(() => reload()) | ||
} | ||
variant="light" | ||
color="green" | ||
radius="md" | ||
> | ||
Start | ||
</Button> | ||
<Button leftIcon={<IconRefresh />} onClick={() => reload()} variant="light" radius="md"> | ||
Refresh data | ||
</Button> | ||
<Button | ||
leftIcon={<IconPlus />} | ||
color="indigo" | ||
variant="light" | ||
radius="md" | ||
onClick={() => { | ||
if (selected.length !== 1) { | ||
showNotification({ | ||
autoClose: 5000, | ||
title: <Title order={4}>Please only add one service at a time!</Title>, | ||
color: 'red', | ||
message: undefined, | ||
}); | ||
} else { | ||
setOpened(true); | ||
} | ||
}} | ||
> | ||
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { ActionIcon, Drawer, Group, LoadingOverlay } from '@mantine/core'; | ||
import { IconBrandDocker } from '@tabler/icons'; | ||
import axios from 'axios'; | ||
import { useEffect, useState } from 'react'; | ||
import Docker from 'dockerode'; | ||
import ContainerActionBar from './ContainerActionBar'; | ||
import DockerTable from './DockerTable'; | ||
|
||
export default function DockerDrawer(props: any) { | ||
const [opened, setOpened] = useState(false); | ||
const [containers, setContainers] = useState<Docker.ContainerInfo[]>([]); | ||
const [selection, setSelection] = useState<Docker.ContainerInfo[]>([]); | ||
const [visible, setVisible] = useState(false); | ||
|
||
function reload() { | ||
setVisible(true); | ||
setTimeout(() => { | ||
axios.get('/api/docker/containers').then((res) => { | ||
setContainers(res.data); | ||
setSelection([]); | ||
setVisible(false); | ||
}); | ||
}, 300); | ||
} | ||
|
||
useEffect(() => { | ||
reload(); | ||
}, []); | ||
// Check if the user has at least one container | ||
if (containers.length < 1) return null; | ||
return ( | ||
<> | ||
<Drawer opened={opened} onClose={() => setOpened(false)} padding="xl" size="full"> | ||
<ContainerActionBar selected={selection} reload={reload} /> | ||
<div style={{ position: 'relative' }}> | ||
<LoadingOverlay transitionDuration={500} visible={visible} /> | ||
<DockerTable containers={containers} selection={selection} setSelection={setSelection} /> | ||
</div> | ||
</Drawer> | ||
<Group position="center"> | ||
<ActionIcon | ||
variant="default" | ||
radius="md" | ||
size="xl" | ||
color="blue" | ||
onClick={() => setOpened(true)} | ||
> | ||
<IconBrandDocker /> | ||
</ActionIcon> | ||
</Group> | ||
</> | ||
); | ||
} |
Oops, something went wrong.