diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 8b973d92f..c83fcc939 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -22,13 +22,3 @@ body: - High (App breaking feature) validations: required: true - - type: checkboxes - id: idiot-check - attributes: - label: Please tick the boxes - description: Before submitting, please ensure that - options: - - label: You've read the [docs](https://github.com/ajnart/homarr#readme) - required: true - - label: You've checked for [duplicate issues](https://github.com/ajnart/homarr/issues) - required: true diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..b826a6a46 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "yarn dev" + }, + { + "name": "Next.js: debug client-side", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "yarn dev", + "serverReadyAction": { + "pattern": "started server on .+, url: (https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] +} \ No newline at end of file diff --git a/data/configs/default.json b/data/configs/default.json index ba6a91144..d159270a2 100644 --- a/data/configs/default.json +++ b/data/configs/default.json @@ -18,6 +18,9 @@ }, "Date": { "enabled": false + }, + "Docker": { + "enabled": true } } } \ No newline at end of file diff --git a/data/constants.ts b/data/constants.ts index 8054f1409..cc319bc33 100644 --- a/data/constants.ts +++ b/data/constants.ts @@ -1,2 +1,2 @@ export const REPO_URL = 'ajnart/homarr'; -export const CURRENT_VERSION = 'v0.8.0'; +export const CURRENT_VERSION = 'v0.8.2'; diff --git a/next.config.js b/next.config.js index a6c9032b0..7344769da 100644 --- a/next.config.js +++ b/next.config.js @@ -6,9 +6,5 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ module.exports = withBundleAnalyzer({ reactStrictMode: false, - eslint: { - ignoreDuringBuilds: true, - }, output: 'standalone', - basePath: env.BASE_URL, }); diff --git a/package.json b/package.json index 6ef4d50e4..98042f379 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homarr", - "version": "0.8.0", + "version": "0.8.2", "description": "Homarr - A homepage for your server.", "repository": { "type": "git", @@ -25,66 +25,66 @@ }, "dependencies": { "@ctrl/deluge": "^4.1.0", - "@ctrl/qbittorrent": "^4.0.0", - "@ctrl/shared-torrent": "^4.1.0", + "@ctrl/qbittorrent": "^4.1.0", + "@ctrl/shared-torrent": "^4.1.1", "@ctrl/transmission": "^4.1.1", - "@dnd-kit/core": "^6.0.1", - "@dnd-kit/sortable": "^7.0.0", + "@dnd-kit/core": "^6.0.5", + "@dnd-kit/sortable": "^7.0.1", "@dnd-kit/utilities": "^3.2.0", - "@mantine/core": "^4.2.8", - "@mantine/dates": "^4.2.8", - "@mantine/dropzone": "^4.2.8", - "@mantine/form": "^4.2.8", - "@mantine/hooks": "^4.2.8", - "@mantine/next": "^4.2.8", - "@mantine/notifications": "^4.2.8", - "@mantine/prism": "^4.2.8", + "@mantine/core": "^4.2.12", + "@mantine/dates": "^4.2.12", + "@mantine/dropzone": "^4.2.12", + "@mantine/form": "^4.2.12", + "@mantine/hooks": "^4.2.12", + "@mantine/next": "^4.2.12", + "@mantine/notifications": "^4.2.12", + "@mantine/prism": "^4.2.12", "@nivo/core": "^0.79.0", "@nivo/line": "^0.79.1", - "@tabler/icons": "^1.68.0", + "@tabler/icons": "^1.76.0", + "add": "^2.0.6", "axios": "^0.27.2", "cookies-next": "^2.1.1", - "dayjs": "^1.11.3", + "dayjs": "^1.11.4", "dockerode": "^3.3.2", - "framer-motion": "^6.3.1", + "framer-motion": "^6.5.1", "js-file-download": "^0.4.12", - "next": "^12.2.0", - "prism-react-renderer": "^1.3.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "systeminformation": "^5.11.16", - "uuid": "^8.3.2" + "next": "^12.2.3", + "prism-react-renderer": "^1.3.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "systeminformation": "^5.12.1", + "uuid": "^8.3.2", + "yarn": "^1.22.19" }, "devDependencies": { - "@babel/core": "^7.17.8", - "@next/bundle-analyzer": "^12.2.0", - "@next/eslint-plugin-next": "^12.2.0", - "@storybook/react": "^6.5.4", + "@babel/core": "^7.18.9", + "@next/bundle-analyzer": "^12.2.3", + "@next/eslint-plugin-next": "^12.2.3", + "@storybook/react": "^6.5.9", "@types/dockerode": "^3.3.9", - "@types/node": "^17.0.23", - "@types/react": "17.0.43", + "@types/node": "^18.0.6", + "@types/react": "^18.0.15", "@types/uuid": "^8.3.4", - "@typescript-eslint/eslint-plugin": "^5.16.0", - "@typescript-eslint/parser": "^5.16.0", - "eslint": "^8.11.0", + "@typescript-eslint/eslint-plugin": "^5.30.7", + "@typescript-eslint/parser": "^5.30.7", + "eslint": "^8.20.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-mantine": "1.1.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.29.4", - "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-storybook": "^0.5.11", - "eslint-plugin-testing-library": "^5.2.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-mantine": "^2.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^26.6.0", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-storybook": "^0.6.1", + "eslint-plugin-testing-library": "^5.5.1", "eslint-plugin-unused-imports": "^2.0.0", - "jest": "^28.1.0", - "prettier": "^2.6.2", + "jest": "^28.1.3", + "prettier": "^2.7.1", "require-from-string": "^2.0.2", - "typescript": "4.6.4" - }, - "resolutions": { - "@types/react": "17.0.30" + "typescript": "^4.7.4", + "yarn-upgrade-all": "^0.7.1" }, "packageManager": "yarn@3.2.1" } diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index c9024d47c..2d41feaee 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -12,7 +12,6 @@ import { Select, Switch, Tabs, - Text, TextInput, Title, Tooltip, @@ -23,7 +22,7 @@ import { IconApps as Apps } from '@tabler/icons'; import { useEffect, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { useConfig } from '../../tools/state'; -import { ServiceTypeList, StatusCodes } from '../../tools/types'; +import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types'; import Tip from '../layout/Tip'; export function AddItemShelfButton(props: any) { @@ -55,7 +54,8 @@ export function AddItemShelfButton(props: any) { ); } -function MatchIcon(name: string, form: any) { +function MatchIcon(name: string | undefined, form: any) { + if (name === undefined || name === '') return null; fetch( `https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/${name .replace(/\s+/g, '-') @@ -77,24 +77,6 @@ function MatchService(name: string, form: any) { } } -function MatchPort(name: string, form: any) { - const portmap = [ - { name: 'qbittorrent', value: '8080' }, - { name: 'sonarr', value: '8989' }, - { name: 'radarr', value: '7878' }, - { name: 'lidarr', value: '8686' }, - { name: 'readarr', value: '8787' }, - { name: 'deluge', value: '8112' }, - { name: 'transmission', value: '9091' }, - { name: 'dash.', value: '3001' }, - ]; - // Match name with portmap key - const port = portmap.find((p) => p.name === name.toLowerCase()); - if (port) { - form.setFieldValue('url', `http://localhost:${port.value}`); - } -} - const DEFAULT_ICON = '/favicon.svg'; export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & any) { @@ -154,7 +136,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & 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); + tryMatchPort(form.values.name, form); }, [debounced]); // Try to set const hostname to new URL(form.values.url).hostname) diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index abfed55cc..a47346830 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -178,21 +178,21 @@ const AppShelf = (props: any) => { ) : null} {downloadEnabled ? ( - - + - - - - + }} + > + + + + ) : null} diff --git a/src/components/Config/ConfigChanger.tsx b/src/components/Config/ConfigChanger.tsx index a34846556..14e8cf5e5 100644 --- a/src/components/Config/ConfigChanger.tsx +++ b/src/components/Config/ConfigChanger.tsx @@ -1,5 +1,5 @@ import { Center, Loader, Select, Tooltip } from '@mantine/core'; -import { setCookies } from 'cookies-next'; +import { setCookie } from 'cookies-next'; import { useEffect, useState } from 'react'; import { useConfig } from '../../tools/state'; @@ -26,7 +26,7 @@ export default function ConfigChanger() { label="Config loader" onChange={(e) => { loadConfig(e ?? 'default'); - setCookies('config-name', e ?? 'default', { + setCookie('config-name', e ?? 'default', { maxAge: 60 * 60 * 24 * 30, sameSite: 'strict', }); diff --git a/src/components/Config/LoadConfig.tsx b/src/components/Config/LoadConfig.tsx index e98550b6a..6935c1f72 100644 --- a/src/components/Config/LoadConfig.tsx +++ b/src/components/Config/LoadConfig.tsx @@ -10,7 +10,7 @@ import { DropzoneStatus, FullScreenDropzone } from '@mantine/dropzone'; import { showNotification } from '@mantine/notifications'; import { useRef } from 'react'; import { useRouter } from 'next/router'; -import { setCookies } from 'cookies-next'; +import { setCookie } from 'cookies-next'; import { useConfig } from '../../tools/state'; import { Config } from '../../tools/types'; import { migrateToIdConfig } from '../../tools/migrate'; @@ -90,7 +90,7 @@ export default function LoadConfigComponent(props: any) { icon: , message: undefined, }); - setCookies('config-name', newConfig.name, { + setCookie('config-name', newConfig.name, { maxAge: 60 * 60 * 24 * 30, sameSite: 'strict', }); diff --git a/src/components/Docker/DockerMenu.tsx b/src/components/Docker/DockerMenu.tsx deleted file mode 100644 index 6b9bdc8fe..000000000 --- a/src/components/Docker/DockerMenu.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Menu, Text, useMantineTheme } from '@mantine/core'; -import { showNotification, updateNotification } from '@mantine/notifications'; -import { - IconCheck, - IconCodePlus, - 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: , - 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: , - 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 ( - - Actions - } onClick={() => restart(container)}> - Restart - - {container.State === 'running' ? ( - }> - Stop - - ) : ( - }> - Start - - )} - {/* }> - Pull latest image - - }> - Logs - */} - Homarr - }> - Add to Homarr - - - ); -} diff --git a/src/components/Docker/DockerTable.tsx b/src/components/Docker/DockerTable.tsx deleted file mode 100644 index 2f7d6707b..000000000 --- a/src/components/Docker/DockerTable.tsx +++ /dev/null @@ -1,90 +0,0 @@ -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 ( - - - 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/components/layout/Header.tsx b/src/components/layout/Header.tsx index 3964e7a83..9c78905b9 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,26 +1,8 @@ -import { - ActionIcon, - Box, - Burger, - createStyles, - Drawer, - Group, - Header as Head, - ScrollArea, - Title, - Transition, -} from '@mantine/core'; +import { Box, createStyles, Group, Header as Head } from '@mantine/core'; import { useBooleanToggle } from '@mantine/hooks'; import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem'; -import { - CalendarModule, - DateModule, - TotalDownloadsModule, - WeatherModule, - DashdotModule, -} from '../modules'; -import { ModuleWrapper } from '../modules/moduleWrapper'; -import DockerDrawer from '../Docker/DockerDrawer'; + +import DockerMenuButton from '../modules/docker/DockerModule'; import SearchBar from '../modules/search/SearchModule'; import { SettingsMenuButton } from '../Settings/SettingsMenu'; import { Logo } from './Logo'; @@ -53,51 +35,9 @@ export function Header(props: any) { - + - - { - toggleHidden(); - toggleOpened(); - }} - /> - - diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index a40c6a801..0df7c4374 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -19,8 +19,8 @@ export default function Layout({ children, style }: any) { return ( } - navbar={widgetPosition ? : <>} - aside={widgetPosition ? <> :